From 0d41556fa0b6e8521b2890259a6bf78f0a0da352 Mon Sep 17 00:00:00 2001 From: Pierre Kestener Date: Thu, 19 Mar 2026 16:13:13 +0100 Subject: [PATCH 1/2] Initial import of public version (version of March, 19th 2026). --- .clang-format | 136 + .clangd | 2 + .cmake-format.py | 242 ++ .editorconfig | 38 + .github/dependabot.yaml | 11 + .github/workflows/ci.yaml | 94 + .github/workflows/pre-commit.yaml | 19 + .gitignore | 44 + .pre-commit-config.yaml | 45 + CMakeLists.txt | 392 +++ CONTRIBUTORS.md | 12 + COPYRIGHT.md | 24 + LICENSES/Apache-2.0.txt | 73 + LICENSES/BSD-1-Clause.txt | 7 + LICENSES/LLVM-exception.txt | 15 + LICENSES/MIT.txt | 18 + LICENSES/Unlicense.txt | 10 + REUSE.toml | 60 + Readme.md | 215 ++ _typos.toml | 20 + cmake/CheckMPIFeatures.cmake | 107 + cmake/CompilerWarnings.cmake | 197 ++ cmake/FindLAPACKE.cmake | 51 + cmake/FindP4EST.cmake | 84 + cmake/FindPAPI.cmake | 46 + cmake/FindPNETCDF.cmake | 84 + cmake/FindSC.cmake | 75 + cmake/better-enums-config.cmake | 11 + cmake/build_or_find_cnpy.cmake | 82 + cmake/build_or_find_cpptrace.cmake | 40 + cmake/build_or_find_kokkos.cmake | 221 ++ cmake/build_or_find_nvtx3.cmake | 84 + cmake/build_or_find_p4est.cmake | 134 + cmake/build_or_find_spdlog.cmake | 127 + cmake/check_file_exists.cmake | 16 + cmake/check_file_exists_at_build_time.cmake | 11 + cmake/cmake_utils.cmake | 38 + cmake/config_hdf5.cmake | 27 + cmake/config_highfive.cmake | 7 + cmake/config_mpi.cmake | 40 + cmake/config_pnetcdf.cmake | 12 + cmake/config_vtk.cmake | 20 + cmake/find_python_module.cmake | 53 + cmake/generate_config_h.cmake | 91 + cmake/initBuildType.cmake | 15 + cmake/options.cmake | 68 + cmake/plantuml.cmake | 17 + cmake/prevent_build_in_source.cmake | 20 + cmake/print_target_properties.cmake | 51 + cmake/setup_install.cmake | 24 + dependencies/CMakeLists.txt | 97 + dependencies/Readme.md | 63 + dependencies/cmake/build_dependencies.cmake | 13 + dependencies/cmake/build_hdf5.cmake | 193 ++ dependencies/cmake/build_p4est.cmake | 187 ++ dependencies/cmake/modulefile.in | 41 + dependencies/cmake/modulefiles.cmake | 24 + doc/.gitignore | 3 + doc/CMakeLists.txt | 122 + doc/doxyfile.in | 27 + doc/mkdocs/docs/index.md.in | 24 + doc/mkdocs/docs/introduction.md | 75 + doc/mkdocs/docs/javascripts/mathjax.js | 19 + doc/mkdocs/mkdocs.yml.in | 95 + doc/requirements.txt | 8 + kalypsso-core-config.cmake.in | 23 + src/CMakeLists.txt | 1 + src/kalypsso/CMakeLists.txt | 4 + src/kalypsso/core/AMRContext.h | 629 ++++ src/kalypsso/core/AMRCycleDriver.h | 99 + src/kalypsso/core/AMRMeshInfo.h | 307 ++ src/kalypsso/core/AMRMeshMonitoring.cpp | 123 + src/kalypsso/core/AMRMeshMonitoring.h | 79 + src/kalypsso/core/AMRmesh.h | 479 +++ src/kalypsso/core/AMRmesh_utils.h | 96 + .../core/AlternatingDirectionScheduler.cpp | 273 ++ .../core/AlternatingDirectionScheduler.h | 126 + src/kalypsso/core/BinomialCoef.h | 85 + src/kalypsso/core/BitFieldHelper.h | 671 +++++ src/kalypsso/core/BitFieldInteger.h | 94 + src/kalypsso/core/CMakeLists.txt | 296 ++ .../core/CheckEdgeSiblingsConnectivity.cpp | 339 +++ .../core/CheckEdgeSiblingsConnectivity.h | 181 ++ .../core/CheckFaceBorderCompatibility.cpp | 273 ++ .../core/CheckFaceBorderCompatibility.h | 164 ++ .../core/ComputeConformalFullStatus.h | 591 ++++ src/kalypsso/core/ComputeConformalStatus.h | 265 ++ src/kalypsso/core/ComputeCurvature.cpp | 121 + src/kalypsso/core/ComputeCurvature.h | 103 + .../core/ComputeDataSliceAlongLine.cpp | 629 ++++ src/kalypsso/core/ComputeDataSliceAlongLine.h | 315 ++ src/kalypsso/core/ComputeDivergence.cpp | 157 + src/kalypsso/core/ComputeDivergence.h | 207 ++ src/kalypsso/core/ComputeError.h | 220 ++ .../core/ComputeFilteredCurvature.cpp | 191 ++ src/kalypsso/core/ComputeFilteredCurvature.h | 128 + .../core/ComputeGhostedScalarField.cpp | 452 +++ src/kalypsso/core/ComputeGhostedScalarField.h | 335 +++ .../core/ComputeInterfaceNormalVector.cpp | 132 + .../core/ComputeInterfaceNormalVector.h | 91 + src/kalypsso/core/ComputeRefineFlags.cpp | 404 +++ src/kalypsso/core/ComputeRefineFlags.h | 290 ++ src/kalypsso/core/ComputeRefineFlags_utils.h | 104 + src/kalypsso/core/ComputeSchlieren.cpp | 235 ++ src/kalypsso/core/ComputeSchlieren.h | 261 ++ .../core/ComputeSmoothInterfaceFunction.cpp | 89 + .../core/ComputeSmoothInterfaceFunction.h | 97 + .../core/ComputeVolumeIntegralValue.cpp | 94 + .../core/ComputeVolumeIntegralValue.h | 115 + src/kalypsso/core/ConformalFaceStatus.h | 191 ++ src/kalypsso/core/ConformalFullStatus.h | 365 +++ src/kalypsso/core/ConformalNeighborStatus.h | 68 + src/kalypsso/core/ConservativityCheck.cpp | 159 + src/kalypsso/core/ConservativityCheck.h | 110 + src/kalypsso/core/DataArray.h | 273 ++ src/kalypsso/core/DataArrayBlock.h | 697 +++++ src/kalypsso/core/DataArrayBlockMultiVar.h | 985 +++++++ src/kalypsso/core/DataArrayBlock_utils.h | 409 +++ src/kalypsso/core/DataArrayGhostedBlock.h | 682 +++++ src/kalypsso/core/DataArrayGhostedBlock_old.h | 238 ++ src/kalypsso/core/DataArrayUtils.cpp | 43 + src/kalypsso/core/DataArrayUtils.h | 63 + src/kalypsso/core/EdgeDataArrayBlock.h | 506 ++++ src/kalypsso/core/EdgeDataArrayBlock_utils.h | 705 +++++ src/kalypsso/core/ExtractNonGhostedArray.h | 76 + src/kalypsso/core/FaceDataArrayBlock.h | 932 ++++++ src/kalypsso/core/FaceDataArrayBlock_utils.h | 299 ++ src/kalypsso/core/FieldMap.h | 118 + src/kalypsso/core/FillBlockGhostCells.cpp | 640 ++++ src/kalypsso/core/FillBlockGhostCells.h | 251 ++ .../core/FillBlockGhostCellsInPlace.cpp | 258 ++ .../core/FillBlockGhostCellsInPlace.h | 148 + src/kalypsso/core/FillBlockGhostCells_v2.cpp | 403 +++ src/kalypsso/core/FillBlockGhostCells_v2.h | 279 ++ src/kalypsso/core/FillBlockGhostFaces.cpp | 902 ++++++ src/kalypsso/core/FillBlockGhostFaces.h | 298 ++ src/kalypsso/core/FillBlockGhostFluxes.cpp | 363 +++ src/kalypsso/core/FillBlockGhostFluxes.h | 226 ++ src/kalypsso/core/FillBlockGhosts_common.h | 635 ++++ src/kalypsso/core/FillOutside_utils.h | 142 + src/kalypsso/core/FiniteDifferenceData.h | 160 + .../FirstOrderDerivativeFiniteDifference.cpp | 334 +++ .../FirstOrderDerivativeFiniteDifference.h | 473 +++ src/kalypsso/core/GravityField.cpp | 22 + src/kalypsso/core/GravityField.h | 124 + src/kalypsso/core/HDF5_IO_common.cpp | 283 ++ src/kalypsso/core/HDF5_IO_common.h | 167 ++ src/kalypsso/core/HDF5_Xdmf_Reader.h | 754 +++++ src/kalypsso/core/HDF5_Xdmf_Writer.h | 2605 +++++++++++++++++ src/kalypsso/core/HDF5_Xdmf_Writer_legacy.h | 1919 ++++++++++++ src/kalypsso/core/HydroParams.cpp | 127 + src/kalypsso/core/HydroParams.h | 99 + .../InterfaceNormalVectorAlgorithmParams.h | 76 + src/kalypsso/core/Kokkos_Array_extensions.h | 319 ++ src/kalypsso/core/Kokkos_extensions.h | 163 ++ src/kalypsso/core/LinearCombination.h | 112 + src/kalypsso/core/Locations.h | 245 ++ src/kalypsso/core/MaterialPresence.h | 516 ++++ .../core/MaterialPresenceExchanger.cpp | 243 ++ src/kalypsso/core/MaterialPresenceExchanger.h | 162 + src/kalypsso/core/MeshGhostsExchanger.cpp | 876 ++++++ src/kalypsso/core/MeshGhostsExchanger.h | 405 +++ .../core/MeshGhostsExchangerMultiVar.cpp | 368 +++ .../core/MeshGhostsExchangerMultiVar.h | 190 ++ src/kalypsso/core/MeshMap.cpp | 728 +++++ src/kalypsso/core/MeshMap.h | 538 ++++ src/kalypsso/core/MeshPartitioner.h | 655 +++++ src/kalypsso/core/MeshPartitioner_helper.h | 519 ++++ .../core/MultiMatFillBlockGhostCells.h | 810 +++++ src/kalypsso/core/OutsideQuadsInfo.h | 461 +++ src/kalypsso/core/Point.h | 20 + src/kalypsso/core/SimpleVTKIO.h | 232 ++ .../core/SmoothInterfaceFunctionData.cpp | 219 ++ .../core/SmoothInterfaceFunctionData.h | 225 ++ src/kalypsso/core/SolverBase.cpp | 441 +++ src/kalypsso/core/SolverBase.h | 327 +++ src/kalypsso/core/StencilHelper.cpp | 2244 ++++++++++++++ src/kalypsso/core/StencilHelper.h | 1046 +++++++ src/kalypsso/core/THINCParams.h | 48 + src/kalypsso/core/TimeIntegratorConfig.h | 83 + src/kalypsso/core/UserDataRemapper.cpp | 248 ++ src/kalypsso/core/UserDataRemapper.h | 171 ++ src/kalypsso/core/UserDataRemapperImplBCC.cpp | 596 ++++ src/kalypsso/core/UserDataRemapperImplBCC.h | 195 ++ src/kalypsso/core/UserDataRemapperImplBFC.cpp | 701 +++++ src/kalypsso/core/UserDataRemapperImplBFC.h | 192 ++ src/kalypsso/core/UserDataRemapperImplMD.cpp | 442 +++ src/kalypsso/core/UserDataRemapperImplMD.h | 112 + src/kalypsso/core/UserDataRemapperImplMP.cpp | 152 + src/kalypsso/core/UserDataRemapperImplMP.h | 82 + src/kalypsso/core/ViscosityParams.h | 44 + src/kalypsso/core/amr_hashmap.h | 33 + src/kalypsso/core/amr_key.h | 252 ++ src/kalypsso/core/brick_base.h | 24 + src/kalypsso/core/brick_connectivity_utils.h | 358 +++ src/kalypsso/core/brick_utils.h | 210 ++ src/kalypsso/core/cmdline_utils.h | 86 + src/kalypsso/core/cnpy_io.h | 109 + src/kalypsso/core/config_utils.h | 68 + src/kalypsso/core/cpptrace_utils.cpp | 180 ++ src/kalypsso/core/cpptrace_utils.h | 80 + src/kalypsso/core/enums.h | 388 +++ src/kalypsso/core/eos/IdealGasEos.h | 163 ++ src/kalypsso/core/eos/IdealGasMixture.h | 151 + src/kalypsso/core/eos/README.md | 90 + src/kalypsso/core/eos/StiffenedGas.py | 59 + src/kalypsso/core/eos/StiffenedGasEos.h | 179 ++ src/kalypsso/core/eos/StiffenedGasMixture.h | 179 ++ src/kalypsso/core/eos/VanDerWaalsGasEos.h | 213 ++ src/kalypsso/core/eos/eos_utils.h | 84 + src/kalypsso/core/geometry_utils.h | 81 + src/kalypsso/core/init_func.h | 171 ++ src/kalypsso/core/io_utils.cpp | 48 + src/kalypsso/core/io_utils.h | 51 + src/kalypsso/core/kalypsso_comm_config.h | 26 + src/kalypsso/core/kalypsso_core_base.h | 195 ++ .../core/kalypsso_core_build_info.cpp | 164 ++ src/kalypsso/core/kalypsso_core_build_info.h | 62 + .../core/kalypsso_core_config.h.cmake.in | 71 + src/kalypsso/core/kalypsso_core_git_info.cpp | 122 + src/kalypsso/core/kalypsso_core_git_info.h | 49 + src/kalypsso/core/kalypsso_data_container.h | 21 + src/kalypsso/core/kalypsso_macros.h | 61 + src/kalypsso/core/kalypsso_stack_printer.cpp | 46 + src/kalypsso/core/kokkos_shared.h | 90 + src/kalypsso/core/mesh_utils.h | 350 +++ src/kalypsso/core/misc_utils.cpp | 87 + src/kalypsso/core/misc_utils.h | 73 + src/kalypsso/core/models/EulerEquations.h | 889 ++++++ src/kalypsso/core/models/Hydro.cpp | 36 + src/kalypsso/core/models/Hydro.h | 185 ++ src/kalypsso/core/models/HydroSettings.cpp | 51 + src/kalypsso/core/models/HydroSettings.h | 56 + src/kalypsso/core/models/HydroState.h | 46 + src/kalypsso/core/models/MHD.cpp | 42 + src/kalypsso/core/models/MHD.h | 191 ++ src/kalypsso/core/models/MHDSettings.cpp | 41 + src/kalypsso/core/models/MHDSettings.h | 55 + src/kalypsso/core/models/MHDState.h | 67 + src/kalypsso/core/models/RiemannSolvers.h | 818 ++++++ src/kalypsso/core/models/RiemannSolvers_MHD.h | 2115 +++++++++++++ src/kalypsso/core/models/mhd_utils.h | 923 ++++++ .../core/models/riemann_solver_types.h | 54 + src/kalypsso/core/models/utils_hydro.h | 327 +++ src/kalypsso/core/morton_utils.h | 527 ++++ src/kalypsso/core/multimaterial_utils.h | 71 + src/kalypsso/core/myKokkos_Sort.h | 79 + src/kalypsso/core/orchard_key.h | 81 + src/kalypsso/core/orchard_key_base.h | 39 + src/kalypsso/core/orchard_key_impl_2d.h | 1352 +++++++++ src/kalypsso/core/orchard_key_impl_3d.h | 1555 ++++++++++ src/kalypsso/core/orchard_key_utils.h | 762 +++++ src/kalypsso/core/p4est_utils.h | 66 + src/kalypsso/core/physical_constants.h | 36 + src/kalypsso/core/problems/AlfvenParams.h | 59 + src/kalypsso/core/problems/BlastParams.h | 91 + .../core/problems/BreakingWaveParams.h | 104 + .../core/problems/DiamagCavityParams.h | 176 ++ .../problems/DoubleMachReflectionParams.h | 98 + .../core/problems/DropletAdvectionParams.h | 76 + .../core/problems/FieldLoopAdvectionParams.h | 71 + .../core/problems/FourQuadrantParams.h | 37 + .../core/problems/GreshoVortexParams.h | 48 + src/kalypsso/core/problems/ImplodeParams.h | 108 + .../core/problems/IsentropicVortexParams.h | 91 + src/kalypsso/core/problems/KHParams.h | 115 + .../core/problems/MHDShockTubeParams.h | 112 + src/kalypsso/core/problems/OrszagTangParams.h | 45 + .../core/problems/RayleighTaylorParams.h | 112 + .../core/problems/RichtmyerMeshkovParams.h | 115 + src/kalypsso/core/problems/RotorParams.h | 93 + .../core/problems/ShockBubbleParams.h | 152 + src/kalypsso/core/problems/ShuOsherParams.h | 59 + src/kalypsso/core/problems/SodParams.h | 60 + .../core/problems/StaticDropletParams.h | 81 + .../core/problems/TriplePointParams.h | 92 + .../core/problems/TwoFluidShockTubeParams.h | 56 + .../core/problems/UnderwaterExplosionParams.h | 58 + src/kalypsso/core/problems/WoodwardParams.h | 74 + .../core/problems/initRiemannConfig2d.cpp | 681 +++++ .../core/problems/initRiemannConfig2d.h | 136 + .../core/problems/init_cond_utils.cpp | 26 + src/kalypsso/core/problems/init_cond_utils.h | 47 + src/kalypsso/core/prolongation.h | 262 ++ src/kalypsso/core/real_type.h | 167 ++ src/kalypsso/core/region_utils.h | 66 + src/kalypsso/core/scan_utils.h | 209 ++ src/kalypsso/core/utils/Readme.md | 3 + ...vative_polynomial_interpolation_order_2.py | 60 + ...vative_polynomial_interpolation_order_4.py | 66 + .../core/utils/stencil_coefs_helper.py | 122 + src/kalypsso/core/utils_block.h | 829 ++++++ .../core/vof/interface_tracking_utils.cpp | 85 + .../core/vof/interface_tracking_utils.h | 598 ++++ src/kalypsso/core/vof/youngs.h | 389 +++ src/kalypsso/utils/CMakeLists.txt | 6 + src/kalypsso/utils/config/CMakeLists.txt | 42 + src/kalypsso/utils/config/ConfigMap.cpp | 669 +++++ src/kalypsso/utils/config/ConfigMap.h | 397 +++ src/kalypsso/utils/config/inih/LICENSE.txt | 27 + .../utils/config/inih/ParameterValue.h | 65 + src/kalypsso/utils/config/inih/README.txt | 5 + src/kalypsso/utils/config/inih/ini.c | 329 +++ src/kalypsso/utils/config/inih/ini.h | 198 ++ src/kalypsso/utils/io/CMakeLists.txt | 47 + src/kalypsso/utils/io/FileHandler.cpp | 54 + src/kalypsso/utils/io/FileHandler.h | 85 + src/kalypsso/utils/io/FileHandlerVtk.cpp | 79 + src/kalypsso/utils/io/FileHandlerVtk.h | 78 + src/kalypsso/utils/io/IO_VTK_shared.cpp | 228 ++ src/kalypsso/utils/io/IO_VTK_shared.h | 60 + src/kalypsso/utils/io/VTKWriter.cpp | 417 +++ src/kalypsso/utils/io/VTKWriter.h | 593 ++++ src/kalypsso/utils/io/libb64/AUTHORS | 7 + src/kalypsso/utils/io/libb64/CHANGELOG | 16 + src/kalypsso/utils/io/libb64/LICENSE | 29 + src/kalypsso/utils/io/libb64/README | 142 + src/kalypsso/utils/io/libb64/README2 | 1 + src/kalypsso/utils/io/libb64/b64dec.c | 42 + src/kalypsso/utils/io/libb64/b64enc.c | 45 + src/kalypsso/utils/io/libb64/cdecode.c | 98 + src/kalypsso/utils/io/libb64/cencode.c | 119 + src/kalypsso/utils/io/libb64/libb64.h | 144 + src/kalypsso/utils/log/CMakeLists.txt | 48 + src/kalypsso/utils/log/kalypsso_log.cpp | 91 + src/kalypsso/utils/log/kalypsso_log.h | 206 ++ src/kalypsso/utils/monitoring/CMakeLists.txt | 45 + .../utils/monitoring/CudaAsyncTimer.h | 218 ++ .../utils/monitoring/CudaBlockingTimer.h | 91 + src/kalypsso/utils/monitoring/HostTimer.h | 84 + src/kalypsso/utils/monitoring/NvtxProfiling.h | 410 +++ src/kalypsso/utils/monitoring/PapiInfo.cpp | 121 + src/kalypsso/utils/monitoring/PapiInfo.h | 69 + .../utils/monitoring/ProfilingManager.cpp | 273 ++ .../utils/monitoring/ProfilingManager.h | 226 ++ src/kalypsso/utils/monitoring/memory_utils.h | 133 + src/kalypsso/utils/mpi/CMakeLists.txt | 57 + src/kalypsso/utils/mpi/GlobalMpiSession.cpp | 115 + src/kalypsso/utils/mpi/GlobalMpiSession.h | 144 + src/kalypsso/utils/mpi/MpiComm.cpp | 75 + src/kalypsso/utils/mpi/MpiComm.h | 535 ++++ src/kalypsso/utils/mpi/MpiCommCart.cpp | 74 + src/kalypsso/utils/mpi/MpiCommCart.h | 189 ++ src/kalypsso/utils/mpi/ParallelEnv.cpp | 231 ++ src/kalypsso/utils/mpi/ParallelEnv.h | 141 + src/kalypsso/utils/mpi/mpiEnums.h | 78 + src/kalypsso/utils/mpi/mpi_utils.h | 38 + src/kalypsso/utils/p4est/CMakeLists.txt | 44 + src/kalypsso/utils/p4est/connectivity.cpp | 1430 +++++++++ src/kalypsso/utils/p4est/connectivity.h | 208 ++ src/kalypsso/utils/p4est/geometry.cpp | 544 ++++ src/kalypsso/utils/p4est/geometry.h | 90 + src/kalypsso/utils/p4est/p4est_wrapper.cpp | 989 +++++++ src/kalypsso/utils/p4est/p4est_wrapper.h | 716 +++++ .../utils/p4est/p4est_wrapper_old.cpp | 321 ++ src/kalypsso/utils/p4est/p4est_wrapper_old.h | 413 +++ src/kalypsso_core_version.h.in | 33 + test/AMRkey/CMakeLists.txt | 27 + test/AMRkey/decode_orchard.cpp | 132 + test/AMRkey/encode_orchard.cpp | 174 ++ test/AMRkey/test_orchard.cpp | 365 +++ test/AMRmesh/CMakeLists.txt | 308 ++ test/AMRmesh/clean_data.cmake | 3 + test/AMRmesh/plot_brick_connectivity_2d.py | 40 + test/AMRmesh/plot_brick_connectivity_3d.py | 40 + test/AMRmesh/plot_orchard_keys.py | 52 + test/AMRmesh/test_AMRContext.cpp | 542 ++++ test/AMRmesh/test_AMR_ghost_exchange_mpi.cpp | 583 ++++ test/AMRmesh/test_AMRmesh.cpp | 95 + test/AMRmesh/test_AMRmesh_2d.ini | 66 + test/AMRmesh/test_AMRmesh_3d.ini | 71 + test/AMRmesh/test_AMRmesh_brick_2d.ini | 72 + ...st_AMRmesh_brick_2d_ghost_exchange_mpi.ini | 71 + test/AMRmesh/test_AMRmesh_brick_3d.ini | 79 + ...st_AMRmesh_brick_3d_ghost_exchange_mpi.ini | 78 + .../test_AMRmesh_fill_block_ghost_cells.cpp | 419 +++ ...MRmesh_fill_block_ghost_cells_in_place.cpp | 366 +++ ...test_AMRmesh_fill_block_ghost_cells_v2.cpp | 416 +++ .../test_AMRmesh_fill_block_ghost_faces.cpp | 431 +++ .../test_AMRmesh_fill_ghost_brick_2d.ini | 81 + .../test_AMRmesh_fill_ghost_brick_3d.ini | 89 + test/AMRmesh/test_AMRmesh_refine_flags.cpp | 426 +++ test/AMRmesh/test_AMRmesh_simple_2d.ini | 72 + test/AMRmesh/test_AMRmesh_simple_3d.ini | 79 + test/AMRmesh/test_AMRmesh_stencil.cpp | 831 ++++++ test/AMRmesh/test_AMRmesh_stencil_2d.ini | 75 + test/AMRmesh/test_AMRmesh_stencil_3d.ini | 86 + test/AMRmesh/test_AMRmesh_stencil_helper.cpp | 284 ++ test/AMRmesh/test_AMRmesh_userdata.cpp | 285 ++ test/AMRmesh/test_AMRmesh_userdata_2d.ini | 71 + test/AMRmesh/test_AMRmesh_userdata_3d.ini | 78 + test/AMRmesh/test_brick_connectivity.cpp | 170 ++ test/AMRmesh/test_first_derivative.cpp | 260 ++ test/CMakeLists.txt | 51 + test/HighFive/CMakeLists.txt | 15 + .../parallel_hdf5_collective_io_read.cpp | 120 + .../parallel_hdf5_collective_io_write.cpp | 116 + test/MeshMap/CMakeLists.txt | 31 + test/MeshMap/test_MeshMap.cpp | 660 +++++ test/MeshMap/test_MeshMap_brick_2d.ini | 72 + test/MeshMap/test_MeshMap_brick_3d.ini | 79 + test/MeshMap/test_MeshMap_fillOutside.cpp | 219 ++ test/container/CMakeLists.txt | 32 + test/container/create_DataArrayBlock.cpp | 177 ++ test/container/create_EdgeDataArrayBlock.cpp | 96 + test/container/create_FaceDataArrayBlock.cpp | 199 ++ test/core/CMakeLists.txt | 8 + test/core/test_binomial_coef.cpp | 81 + test/cpptrace/CMakeLists.txt | 3 + test/cpptrace/test_cpptrace.cpp | 34 + test/io/CMakeLists.txt | 91 + test/io/clean_data.cmake | 3 + test/io/test_io.cpp | 1429 +++++++++ test/io/test_io_brick.cpp | 749 +++++ test/io/test_io_hdf5.ini | 58 + test/io/test_io_hdf5_block.ini | 63 + test/io/test_io_vtk.ini | 58 + test/kokkos/CMakeLists.txt | 122 + test/kokkos/amr_key.h | 235 ++ test/kokkos/compute_mandelbrot_amr.cpp | 1026 +++++++ test/kokkos/query_device_kokkos.cpp | 99 + test/kokkos/test_kokkos_mdrange.cpp | 185 ++ test/kokkos/test_kokkos_mpi.cpp | 74 + test/kokkos/test_kokkos_team_mdrange.cpp | 135 + test/kokkos/test_kokkos_teamvector.cpp | 489 ++++ test/kokkos/test_morton_key.cpp | 138 + test/kokkos/test_parallel_scan.cpp | 287 ++ test/kokkos/test_unordered_map.cpp | 201 ++ test/monitoring/CMakeLists.txt | 5 + test/monitoring/test_config.ini | 71 + test/monitoring/test_profiling.cpp | 75 + test/p4est_simple/CMakeLists.txt | 44 + test/p4est_simple/simple_2d.cpp | 549 ++++ test/p4est_simple/simple_3d.cpp | 447 +++ test/p4est_wrapper/CMakeLists.txt | 19 + test/p4est_wrapper/test_p4est_wrapper.cpp | 1014 +++++++ test/p4est_wrapper/test_p4est_wrapper_old.cpp | 954 ++++++ test/spdlog/CMakeLists.txt | 9 + test/spdlog/test_spdlog.cpp | 89 + test/test_common/CMakeLists.txt | 19 + test/test_common/DataWriter.cpp | 532 ++++ test/test_common/DataWriter.h | 153 + test/test_common/FillOutside.cpp | 268 ++ test/test_common/FillOutside.h | 262 ++ test/test_common/FillOutside2.cpp | 263 ++ test/test_common/FillOutside2.h | 265 ++ test/test_common/InitialAMRSetup.cpp | 1059 +++++++ test/test_common/InitialAMRSetup.h | 401 +++ test/test_common/test_func.h | 222 ++ test/utils/CMakeLists.txt | 1 + test/utils/config/CMakeLists.txt | 2 + test/utils/config/ConfigMapTest.cpp | 62 + tools/CMakeLists.txt | 3 + tools/ParaViewKalypssoPlugin/CMakeLists.txt | 43 + tools/ParaViewKalypssoPlugin/Readme.md | 41 + .../ParaViewKalypssoPlugin/src/CMakeLists.txt | 14 + .../src/Reader/CMakeLists.txt | 22 + .../src/Reader/KalypssoHDF5_Server.xml | 74 + .../src/Reader/vtk.module | 12 + .../src/Reader/vtkKalypssoHDF5Reader.cxx | 534 ++++ .../src/Reader/vtkKalypssoHDF5Reader.h | 367 +++ .../src/Testing/CMakeLists.txt | 1 + .../src/Testing/Cxx/CMakeLists.txt | 6 + .../Testing/Cxx/TestKalypssoHDF5Reader.cxx | 111 + .../src/paraview.plugin | 8 + tools/archive/Readme.md | 13 + tools/archive/git_archive_all.py | 671 +++++ .../paraview_surface_plot_screenshot.py | 130 + .../paraview_surface_plot_screenshot_3d.py | 147 + tools/xdmf/create_xdmf.py | 182 ++ unit_test/CMakeLists.txt | 138 + unit_test/StencilHelper_3d.ini | 79 + unit_test/core/BitFieldInteger_test.cpp | 176 ++ .../core/DataArrayBlockMultiVar_test.cpp | 423 +++ unit_test/core/DataArrayBlock_utils_test.cpp | 225 ++ .../core/EdgeDataArrayBlock_utils_test.cpp | 204 ++ .../core/Kokkos_Array_extensions_test.cpp | 43 + unit_test/core/MaterialPresence_test.cpp | 442 +++ unit_test/core/MaterialPresence_test_mpi.cpp | 73 + unit_test/core/StencilHelper_test.cpp | 1353 +++++++++ unit_test/core/geometry_utils_test.cpp | 90 + .../core/kalypsso_data_container_test.cpp | 847 ++++++ unit_test/core/mesh_utils_test.cpp | 39 + unit_test/core/morton_utils_test.cpp | 28 + unit_test/core/orchard_key_test.cpp | 334 +++ .../vof/interface_tracking_utils_test.cpp | 412 +++ unit_test/core/vof/youngs_test.cpp | 266 ++ unit_test/kalypsso_unittest_utils.h | 13 + unit_test/main.cpp | 35 + unit_test/main_kalypsso_unittest.h | 51 + unit_test/utils/config/ConfigMap_test.cpp | 152 + unit_test/utils/monitoring/HostTimer_test.cpp | 35 + unit_test/utils/mpi/ParallelEnv_test.cpp | 27 + 493 files changed, 110996 insertions(+) create mode 100644 .clang-format create mode 100644 .clangd create mode 100644 .cmake-format.py create mode 100644 .editorconfig create mode 100644 .github/dependabot.yaml create mode 100644 .github/workflows/ci.yaml create mode 100644 .github/workflows/pre-commit.yaml create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 CMakeLists.txt create mode 100644 CONTRIBUTORS.md create mode 100644 COPYRIGHT.md create mode 100644 LICENSES/Apache-2.0.txt create mode 100644 LICENSES/BSD-1-Clause.txt create mode 100644 LICENSES/LLVM-exception.txt create mode 100644 LICENSES/MIT.txt create mode 100644 LICENSES/Unlicense.txt create mode 100644 REUSE.toml create mode 100644 Readme.md create mode 100644 _typos.toml create mode 100644 cmake/CheckMPIFeatures.cmake create mode 100644 cmake/CompilerWarnings.cmake create mode 100644 cmake/FindLAPACKE.cmake create mode 100644 cmake/FindP4EST.cmake create mode 100644 cmake/FindPAPI.cmake create mode 100644 cmake/FindPNETCDF.cmake create mode 100644 cmake/FindSC.cmake create mode 100644 cmake/better-enums-config.cmake create mode 100644 cmake/build_or_find_cnpy.cmake create mode 100644 cmake/build_or_find_cpptrace.cmake create mode 100644 cmake/build_or_find_kokkos.cmake create mode 100644 cmake/build_or_find_nvtx3.cmake create mode 100644 cmake/build_or_find_p4est.cmake create mode 100644 cmake/build_or_find_spdlog.cmake create mode 100644 cmake/check_file_exists.cmake create mode 100644 cmake/check_file_exists_at_build_time.cmake create mode 100644 cmake/cmake_utils.cmake create mode 100644 cmake/config_hdf5.cmake create mode 100644 cmake/config_highfive.cmake create mode 100644 cmake/config_mpi.cmake create mode 100644 cmake/config_pnetcdf.cmake create mode 100644 cmake/config_vtk.cmake create mode 100644 cmake/find_python_module.cmake create mode 100644 cmake/generate_config_h.cmake create mode 100644 cmake/initBuildType.cmake create mode 100644 cmake/options.cmake create mode 100644 cmake/plantuml.cmake create mode 100644 cmake/prevent_build_in_source.cmake create mode 100644 cmake/print_target_properties.cmake create mode 100644 cmake/setup_install.cmake create mode 100644 dependencies/CMakeLists.txt create mode 100644 dependencies/Readme.md create mode 100644 dependencies/cmake/build_dependencies.cmake create mode 100644 dependencies/cmake/build_hdf5.cmake create mode 100644 dependencies/cmake/build_p4est.cmake create mode 100644 dependencies/cmake/modulefile.in create mode 100644 dependencies/cmake/modulefiles.cmake create mode 100644 doc/.gitignore create mode 100644 doc/CMakeLists.txt create mode 100644 doc/doxyfile.in create mode 100644 doc/mkdocs/docs/index.md.in create mode 100644 doc/mkdocs/docs/introduction.md create mode 100644 doc/mkdocs/docs/javascripts/mathjax.js create mode 100644 doc/mkdocs/mkdocs.yml.in create mode 100644 doc/requirements.txt create mode 100644 kalypsso-core-config.cmake.in create mode 100644 src/CMakeLists.txt create mode 100644 src/kalypsso/CMakeLists.txt create mode 100644 src/kalypsso/core/AMRContext.h create mode 100644 src/kalypsso/core/AMRCycleDriver.h create mode 100644 src/kalypsso/core/AMRMeshInfo.h create mode 100644 src/kalypsso/core/AMRMeshMonitoring.cpp create mode 100644 src/kalypsso/core/AMRMeshMonitoring.h create mode 100644 src/kalypsso/core/AMRmesh.h create mode 100644 src/kalypsso/core/AMRmesh_utils.h create mode 100644 src/kalypsso/core/AlternatingDirectionScheduler.cpp create mode 100644 src/kalypsso/core/AlternatingDirectionScheduler.h create mode 100644 src/kalypsso/core/BinomialCoef.h create mode 100644 src/kalypsso/core/BitFieldHelper.h create mode 100644 src/kalypsso/core/BitFieldInteger.h create mode 100644 src/kalypsso/core/CMakeLists.txt create mode 100644 src/kalypsso/core/CheckEdgeSiblingsConnectivity.cpp create mode 100644 src/kalypsso/core/CheckEdgeSiblingsConnectivity.h create mode 100644 src/kalypsso/core/CheckFaceBorderCompatibility.cpp create mode 100644 src/kalypsso/core/CheckFaceBorderCompatibility.h create mode 100644 src/kalypsso/core/ComputeConformalFullStatus.h create mode 100644 src/kalypsso/core/ComputeConformalStatus.h create mode 100644 src/kalypsso/core/ComputeCurvature.cpp create mode 100644 src/kalypsso/core/ComputeCurvature.h create mode 100644 src/kalypsso/core/ComputeDataSliceAlongLine.cpp create mode 100644 src/kalypsso/core/ComputeDataSliceAlongLine.h create mode 100644 src/kalypsso/core/ComputeDivergence.cpp create mode 100644 src/kalypsso/core/ComputeDivergence.h create mode 100644 src/kalypsso/core/ComputeError.h create mode 100644 src/kalypsso/core/ComputeFilteredCurvature.cpp create mode 100644 src/kalypsso/core/ComputeFilteredCurvature.h create mode 100644 src/kalypsso/core/ComputeGhostedScalarField.cpp create mode 100644 src/kalypsso/core/ComputeGhostedScalarField.h create mode 100644 src/kalypsso/core/ComputeInterfaceNormalVector.cpp create mode 100644 src/kalypsso/core/ComputeInterfaceNormalVector.h create mode 100644 src/kalypsso/core/ComputeRefineFlags.cpp create mode 100644 src/kalypsso/core/ComputeRefineFlags.h create mode 100644 src/kalypsso/core/ComputeRefineFlags_utils.h create mode 100644 src/kalypsso/core/ComputeSchlieren.cpp create mode 100644 src/kalypsso/core/ComputeSchlieren.h create mode 100644 src/kalypsso/core/ComputeSmoothInterfaceFunction.cpp create mode 100644 src/kalypsso/core/ComputeSmoothInterfaceFunction.h create mode 100644 src/kalypsso/core/ComputeVolumeIntegralValue.cpp create mode 100644 src/kalypsso/core/ComputeVolumeIntegralValue.h create mode 100644 src/kalypsso/core/ConformalFaceStatus.h create mode 100644 src/kalypsso/core/ConformalFullStatus.h create mode 100644 src/kalypsso/core/ConformalNeighborStatus.h create mode 100644 src/kalypsso/core/ConservativityCheck.cpp create mode 100644 src/kalypsso/core/ConservativityCheck.h create mode 100644 src/kalypsso/core/DataArray.h create mode 100644 src/kalypsso/core/DataArrayBlock.h create mode 100644 src/kalypsso/core/DataArrayBlockMultiVar.h create mode 100644 src/kalypsso/core/DataArrayBlock_utils.h create mode 100644 src/kalypsso/core/DataArrayGhostedBlock.h create mode 100644 src/kalypsso/core/DataArrayGhostedBlock_old.h create mode 100644 src/kalypsso/core/DataArrayUtils.cpp create mode 100644 src/kalypsso/core/DataArrayUtils.h create mode 100644 src/kalypsso/core/EdgeDataArrayBlock.h create mode 100644 src/kalypsso/core/EdgeDataArrayBlock_utils.h create mode 100644 src/kalypsso/core/ExtractNonGhostedArray.h create mode 100644 src/kalypsso/core/FaceDataArrayBlock.h create mode 100644 src/kalypsso/core/FaceDataArrayBlock_utils.h create mode 100644 src/kalypsso/core/FieldMap.h create mode 100644 src/kalypsso/core/FillBlockGhostCells.cpp create mode 100644 src/kalypsso/core/FillBlockGhostCells.h create mode 100644 src/kalypsso/core/FillBlockGhostCellsInPlace.cpp create mode 100644 src/kalypsso/core/FillBlockGhostCellsInPlace.h create mode 100644 src/kalypsso/core/FillBlockGhostCells_v2.cpp create mode 100644 src/kalypsso/core/FillBlockGhostCells_v2.h create mode 100644 src/kalypsso/core/FillBlockGhostFaces.cpp create mode 100644 src/kalypsso/core/FillBlockGhostFaces.h create mode 100644 src/kalypsso/core/FillBlockGhostFluxes.cpp create mode 100644 src/kalypsso/core/FillBlockGhostFluxes.h create mode 100644 src/kalypsso/core/FillBlockGhosts_common.h create mode 100644 src/kalypsso/core/FillOutside_utils.h create mode 100644 src/kalypsso/core/FiniteDifferenceData.h create mode 100644 src/kalypsso/core/FirstOrderDerivativeFiniteDifference.cpp create mode 100644 src/kalypsso/core/FirstOrderDerivativeFiniteDifference.h create mode 100644 src/kalypsso/core/GravityField.cpp create mode 100644 src/kalypsso/core/GravityField.h create mode 100644 src/kalypsso/core/HDF5_IO_common.cpp create mode 100644 src/kalypsso/core/HDF5_IO_common.h create mode 100644 src/kalypsso/core/HDF5_Xdmf_Reader.h create mode 100644 src/kalypsso/core/HDF5_Xdmf_Writer.h create mode 100644 src/kalypsso/core/HDF5_Xdmf_Writer_legacy.h create mode 100644 src/kalypsso/core/HydroParams.cpp create mode 100644 src/kalypsso/core/HydroParams.h create mode 100644 src/kalypsso/core/InterfaceNormalVectorAlgorithmParams.h create mode 100644 src/kalypsso/core/Kokkos_Array_extensions.h create mode 100644 src/kalypsso/core/Kokkos_extensions.h create mode 100644 src/kalypsso/core/LinearCombination.h create mode 100644 src/kalypsso/core/Locations.h create mode 100644 src/kalypsso/core/MaterialPresence.h create mode 100644 src/kalypsso/core/MaterialPresenceExchanger.cpp create mode 100644 src/kalypsso/core/MaterialPresenceExchanger.h create mode 100644 src/kalypsso/core/MeshGhostsExchanger.cpp create mode 100644 src/kalypsso/core/MeshGhostsExchanger.h create mode 100644 src/kalypsso/core/MeshGhostsExchangerMultiVar.cpp create mode 100644 src/kalypsso/core/MeshGhostsExchangerMultiVar.h create mode 100644 src/kalypsso/core/MeshMap.cpp create mode 100644 src/kalypsso/core/MeshMap.h create mode 100644 src/kalypsso/core/MeshPartitioner.h create mode 100644 src/kalypsso/core/MeshPartitioner_helper.h create mode 100644 src/kalypsso/core/MultiMatFillBlockGhostCells.h create mode 100644 src/kalypsso/core/OutsideQuadsInfo.h create mode 100644 src/kalypsso/core/Point.h create mode 100644 src/kalypsso/core/SimpleVTKIO.h create mode 100644 src/kalypsso/core/SmoothInterfaceFunctionData.cpp create mode 100644 src/kalypsso/core/SmoothInterfaceFunctionData.h create mode 100644 src/kalypsso/core/SolverBase.cpp create mode 100644 src/kalypsso/core/SolverBase.h create mode 100644 src/kalypsso/core/StencilHelper.cpp create mode 100644 src/kalypsso/core/StencilHelper.h create mode 100644 src/kalypsso/core/THINCParams.h create mode 100644 src/kalypsso/core/TimeIntegratorConfig.h create mode 100644 src/kalypsso/core/UserDataRemapper.cpp create mode 100644 src/kalypsso/core/UserDataRemapper.h create mode 100644 src/kalypsso/core/UserDataRemapperImplBCC.cpp create mode 100644 src/kalypsso/core/UserDataRemapperImplBCC.h create mode 100644 src/kalypsso/core/UserDataRemapperImplBFC.cpp create mode 100644 src/kalypsso/core/UserDataRemapperImplBFC.h create mode 100644 src/kalypsso/core/UserDataRemapperImplMD.cpp create mode 100644 src/kalypsso/core/UserDataRemapperImplMD.h create mode 100644 src/kalypsso/core/UserDataRemapperImplMP.cpp create mode 100644 src/kalypsso/core/UserDataRemapperImplMP.h create mode 100644 src/kalypsso/core/ViscosityParams.h create mode 100644 src/kalypsso/core/amr_hashmap.h create mode 100644 src/kalypsso/core/amr_key.h create mode 100644 src/kalypsso/core/brick_base.h create mode 100644 src/kalypsso/core/brick_connectivity_utils.h create mode 100644 src/kalypsso/core/brick_utils.h create mode 100644 src/kalypsso/core/cmdline_utils.h create mode 100644 src/kalypsso/core/cnpy_io.h create mode 100644 src/kalypsso/core/config_utils.h create mode 100644 src/kalypsso/core/cpptrace_utils.cpp create mode 100644 src/kalypsso/core/cpptrace_utils.h create mode 100644 src/kalypsso/core/enums.h create mode 100644 src/kalypsso/core/eos/IdealGasEos.h create mode 100644 src/kalypsso/core/eos/IdealGasMixture.h create mode 100644 src/kalypsso/core/eos/README.md create mode 100644 src/kalypsso/core/eos/StiffenedGas.py create mode 100644 src/kalypsso/core/eos/StiffenedGasEos.h create mode 100644 src/kalypsso/core/eos/StiffenedGasMixture.h create mode 100644 src/kalypsso/core/eos/VanDerWaalsGasEos.h create mode 100644 src/kalypsso/core/eos/eos_utils.h create mode 100644 src/kalypsso/core/geometry_utils.h create mode 100644 src/kalypsso/core/init_func.h create mode 100644 src/kalypsso/core/io_utils.cpp create mode 100644 src/kalypsso/core/io_utils.h create mode 100644 src/kalypsso/core/kalypsso_comm_config.h create mode 100644 src/kalypsso/core/kalypsso_core_base.h create mode 100644 src/kalypsso/core/kalypsso_core_build_info.cpp create mode 100644 src/kalypsso/core/kalypsso_core_build_info.h create mode 100644 src/kalypsso/core/kalypsso_core_config.h.cmake.in create mode 100644 src/kalypsso/core/kalypsso_core_git_info.cpp create mode 100644 src/kalypsso/core/kalypsso_core_git_info.h create mode 100644 src/kalypsso/core/kalypsso_data_container.h create mode 100644 src/kalypsso/core/kalypsso_macros.h create mode 100644 src/kalypsso/core/kalypsso_stack_printer.cpp create mode 100644 src/kalypsso/core/kokkos_shared.h create mode 100644 src/kalypsso/core/mesh_utils.h create mode 100644 src/kalypsso/core/misc_utils.cpp create mode 100644 src/kalypsso/core/misc_utils.h create mode 100644 src/kalypsso/core/models/EulerEquations.h create mode 100644 src/kalypsso/core/models/Hydro.cpp create mode 100644 src/kalypsso/core/models/Hydro.h create mode 100644 src/kalypsso/core/models/HydroSettings.cpp create mode 100644 src/kalypsso/core/models/HydroSettings.h create mode 100644 src/kalypsso/core/models/HydroState.h create mode 100644 src/kalypsso/core/models/MHD.cpp create mode 100644 src/kalypsso/core/models/MHD.h create mode 100644 src/kalypsso/core/models/MHDSettings.cpp create mode 100644 src/kalypsso/core/models/MHDSettings.h create mode 100644 src/kalypsso/core/models/MHDState.h create mode 100644 src/kalypsso/core/models/RiemannSolvers.h create mode 100644 src/kalypsso/core/models/RiemannSolvers_MHD.h create mode 100644 src/kalypsso/core/models/mhd_utils.h create mode 100644 src/kalypsso/core/models/riemann_solver_types.h create mode 100644 src/kalypsso/core/models/utils_hydro.h create mode 100644 src/kalypsso/core/morton_utils.h create mode 100644 src/kalypsso/core/multimaterial_utils.h create mode 100644 src/kalypsso/core/myKokkos_Sort.h create mode 100644 src/kalypsso/core/orchard_key.h create mode 100644 src/kalypsso/core/orchard_key_base.h create mode 100644 src/kalypsso/core/orchard_key_impl_2d.h create mode 100644 src/kalypsso/core/orchard_key_impl_3d.h create mode 100644 src/kalypsso/core/orchard_key_utils.h create mode 100644 src/kalypsso/core/p4est_utils.h create mode 100644 src/kalypsso/core/physical_constants.h create mode 100644 src/kalypsso/core/problems/AlfvenParams.h create mode 100644 src/kalypsso/core/problems/BlastParams.h create mode 100644 src/kalypsso/core/problems/BreakingWaveParams.h create mode 100644 src/kalypsso/core/problems/DiamagCavityParams.h create mode 100644 src/kalypsso/core/problems/DoubleMachReflectionParams.h create mode 100644 src/kalypsso/core/problems/DropletAdvectionParams.h create mode 100644 src/kalypsso/core/problems/FieldLoopAdvectionParams.h create mode 100644 src/kalypsso/core/problems/FourQuadrantParams.h create mode 100644 src/kalypsso/core/problems/GreshoVortexParams.h create mode 100644 src/kalypsso/core/problems/ImplodeParams.h create mode 100644 src/kalypsso/core/problems/IsentropicVortexParams.h create mode 100644 src/kalypsso/core/problems/KHParams.h create mode 100644 src/kalypsso/core/problems/MHDShockTubeParams.h create mode 100644 src/kalypsso/core/problems/OrszagTangParams.h create mode 100644 src/kalypsso/core/problems/RayleighTaylorParams.h create mode 100644 src/kalypsso/core/problems/RichtmyerMeshkovParams.h create mode 100644 src/kalypsso/core/problems/RotorParams.h create mode 100644 src/kalypsso/core/problems/ShockBubbleParams.h create mode 100644 src/kalypsso/core/problems/ShuOsherParams.h create mode 100644 src/kalypsso/core/problems/SodParams.h create mode 100644 src/kalypsso/core/problems/StaticDropletParams.h create mode 100644 src/kalypsso/core/problems/TriplePointParams.h create mode 100644 src/kalypsso/core/problems/TwoFluidShockTubeParams.h create mode 100644 src/kalypsso/core/problems/UnderwaterExplosionParams.h create mode 100644 src/kalypsso/core/problems/WoodwardParams.h create mode 100644 src/kalypsso/core/problems/initRiemannConfig2d.cpp create mode 100644 src/kalypsso/core/problems/initRiemannConfig2d.h create mode 100644 src/kalypsso/core/problems/init_cond_utils.cpp create mode 100644 src/kalypsso/core/problems/init_cond_utils.h create mode 100644 src/kalypsso/core/prolongation.h create mode 100644 src/kalypsso/core/real_type.h create mode 100644 src/kalypsso/core/region_utils.h create mode 100644 src/kalypsso/core/scan_utils.h create mode 100644 src/kalypsso/core/utils/Readme.md create mode 100755 src/kalypsso/core/utils/conservative_polynomial_interpolation_order_2.py create mode 100755 src/kalypsso/core/utils/conservative_polynomial_interpolation_order_4.py create mode 100755 src/kalypsso/core/utils/stencil_coefs_helper.py create mode 100644 src/kalypsso/core/utils_block.h create mode 100644 src/kalypsso/core/vof/interface_tracking_utils.cpp create mode 100644 src/kalypsso/core/vof/interface_tracking_utils.h create mode 100644 src/kalypsso/core/vof/youngs.h create mode 100644 src/kalypsso/utils/CMakeLists.txt create mode 100644 src/kalypsso/utils/config/CMakeLists.txt create mode 100644 src/kalypsso/utils/config/ConfigMap.cpp create mode 100644 src/kalypsso/utils/config/ConfigMap.h create mode 100644 src/kalypsso/utils/config/inih/LICENSE.txt create mode 100644 src/kalypsso/utils/config/inih/ParameterValue.h create mode 100644 src/kalypsso/utils/config/inih/README.txt create mode 100644 src/kalypsso/utils/config/inih/ini.c create mode 100644 src/kalypsso/utils/config/inih/ini.h create mode 100644 src/kalypsso/utils/io/CMakeLists.txt create mode 100644 src/kalypsso/utils/io/FileHandler.cpp create mode 100644 src/kalypsso/utils/io/FileHandler.h create mode 100644 src/kalypsso/utils/io/FileHandlerVtk.cpp create mode 100644 src/kalypsso/utils/io/FileHandlerVtk.h create mode 100644 src/kalypsso/utils/io/IO_VTK_shared.cpp create mode 100644 src/kalypsso/utils/io/IO_VTK_shared.h create mode 100644 src/kalypsso/utils/io/VTKWriter.cpp create mode 100644 src/kalypsso/utils/io/VTKWriter.h create mode 100644 src/kalypsso/utils/io/libb64/AUTHORS create mode 100644 src/kalypsso/utils/io/libb64/CHANGELOG create mode 100644 src/kalypsso/utils/io/libb64/LICENSE create mode 100644 src/kalypsso/utils/io/libb64/README create mode 100644 src/kalypsso/utils/io/libb64/README2 create mode 100644 src/kalypsso/utils/io/libb64/b64dec.c create mode 100644 src/kalypsso/utils/io/libb64/b64enc.c create mode 100644 src/kalypsso/utils/io/libb64/cdecode.c create mode 100644 src/kalypsso/utils/io/libb64/cencode.c create mode 100644 src/kalypsso/utils/io/libb64/libb64.h create mode 100644 src/kalypsso/utils/log/CMakeLists.txt create mode 100644 src/kalypsso/utils/log/kalypsso_log.cpp create mode 100644 src/kalypsso/utils/log/kalypsso_log.h create mode 100644 src/kalypsso/utils/monitoring/CMakeLists.txt create mode 100644 src/kalypsso/utils/monitoring/CudaAsyncTimer.h create mode 100644 src/kalypsso/utils/monitoring/CudaBlockingTimer.h create mode 100644 src/kalypsso/utils/monitoring/HostTimer.h create mode 100644 src/kalypsso/utils/monitoring/NvtxProfiling.h create mode 100644 src/kalypsso/utils/monitoring/PapiInfo.cpp create mode 100644 src/kalypsso/utils/monitoring/PapiInfo.h create mode 100644 src/kalypsso/utils/monitoring/ProfilingManager.cpp create mode 100644 src/kalypsso/utils/monitoring/ProfilingManager.h create mode 100644 src/kalypsso/utils/monitoring/memory_utils.h create mode 100644 src/kalypsso/utils/mpi/CMakeLists.txt create mode 100644 src/kalypsso/utils/mpi/GlobalMpiSession.cpp create mode 100644 src/kalypsso/utils/mpi/GlobalMpiSession.h create mode 100644 src/kalypsso/utils/mpi/MpiComm.cpp create mode 100644 src/kalypsso/utils/mpi/MpiComm.h create mode 100644 src/kalypsso/utils/mpi/MpiCommCart.cpp create mode 100644 src/kalypsso/utils/mpi/MpiCommCart.h create mode 100644 src/kalypsso/utils/mpi/ParallelEnv.cpp create mode 100644 src/kalypsso/utils/mpi/ParallelEnv.h create mode 100644 src/kalypsso/utils/mpi/mpiEnums.h create mode 100644 src/kalypsso/utils/mpi/mpi_utils.h create mode 100644 src/kalypsso/utils/p4est/CMakeLists.txt create mode 100644 src/kalypsso/utils/p4est/connectivity.cpp create mode 100644 src/kalypsso/utils/p4est/connectivity.h create mode 100644 src/kalypsso/utils/p4est/geometry.cpp create mode 100644 src/kalypsso/utils/p4est/geometry.h create mode 100644 src/kalypsso/utils/p4est/p4est_wrapper.cpp create mode 100644 src/kalypsso/utils/p4est/p4est_wrapper.h create mode 100644 src/kalypsso/utils/p4est/p4est_wrapper_old.cpp create mode 100644 src/kalypsso/utils/p4est/p4est_wrapper_old.h create mode 100644 src/kalypsso_core_version.h.in create mode 100644 test/AMRkey/CMakeLists.txt create mode 100644 test/AMRkey/decode_orchard.cpp create mode 100644 test/AMRkey/encode_orchard.cpp create mode 100644 test/AMRkey/test_orchard.cpp create mode 100644 test/AMRmesh/CMakeLists.txt create mode 100644 test/AMRmesh/clean_data.cmake create mode 100644 test/AMRmesh/plot_brick_connectivity_2d.py create mode 100644 test/AMRmesh/plot_brick_connectivity_3d.py create mode 100644 test/AMRmesh/plot_orchard_keys.py create mode 100644 test/AMRmesh/test_AMRContext.cpp create mode 100644 test/AMRmesh/test_AMR_ghost_exchange_mpi.cpp create mode 100644 test/AMRmesh/test_AMRmesh.cpp create mode 100644 test/AMRmesh/test_AMRmesh_2d.ini create mode 100644 test/AMRmesh/test_AMRmesh_3d.ini create mode 100644 test/AMRmesh/test_AMRmesh_brick_2d.ini create mode 100644 test/AMRmesh/test_AMRmesh_brick_2d_ghost_exchange_mpi.ini create mode 100644 test/AMRmesh/test_AMRmesh_brick_3d.ini create mode 100644 test/AMRmesh/test_AMRmesh_brick_3d_ghost_exchange_mpi.ini create mode 100644 test/AMRmesh/test_AMRmesh_fill_block_ghost_cells.cpp create mode 100644 test/AMRmesh/test_AMRmesh_fill_block_ghost_cells_in_place.cpp create mode 100644 test/AMRmesh/test_AMRmesh_fill_block_ghost_cells_v2.cpp create mode 100644 test/AMRmesh/test_AMRmesh_fill_block_ghost_faces.cpp create mode 100644 test/AMRmesh/test_AMRmesh_fill_ghost_brick_2d.ini create mode 100644 test/AMRmesh/test_AMRmesh_fill_ghost_brick_3d.ini create mode 100644 test/AMRmesh/test_AMRmesh_refine_flags.cpp create mode 100644 test/AMRmesh/test_AMRmesh_simple_2d.ini create mode 100644 test/AMRmesh/test_AMRmesh_simple_3d.ini create mode 100644 test/AMRmesh/test_AMRmesh_stencil.cpp create mode 100644 test/AMRmesh/test_AMRmesh_stencil_2d.ini create mode 100644 test/AMRmesh/test_AMRmesh_stencil_3d.ini create mode 100644 test/AMRmesh/test_AMRmesh_stencil_helper.cpp create mode 100644 test/AMRmesh/test_AMRmesh_userdata.cpp create mode 100644 test/AMRmesh/test_AMRmesh_userdata_2d.ini create mode 100644 test/AMRmesh/test_AMRmesh_userdata_3d.ini create mode 100644 test/AMRmesh/test_brick_connectivity.cpp create mode 100644 test/AMRmesh/test_first_derivative.cpp create mode 100644 test/CMakeLists.txt create mode 100644 test/HighFive/CMakeLists.txt create mode 100644 test/HighFive/parallel_hdf5_collective_io_read.cpp create mode 100644 test/HighFive/parallel_hdf5_collective_io_write.cpp create mode 100644 test/MeshMap/CMakeLists.txt create mode 100644 test/MeshMap/test_MeshMap.cpp create mode 100644 test/MeshMap/test_MeshMap_brick_2d.ini create mode 100644 test/MeshMap/test_MeshMap_brick_3d.ini create mode 100644 test/MeshMap/test_MeshMap_fillOutside.cpp create mode 100644 test/container/CMakeLists.txt create mode 100644 test/container/create_DataArrayBlock.cpp create mode 100644 test/container/create_EdgeDataArrayBlock.cpp create mode 100644 test/container/create_FaceDataArrayBlock.cpp create mode 100644 test/core/CMakeLists.txt create mode 100644 test/core/test_binomial_coef.cpp create mode 100644 test/cpptrace/CMakeLists.txt create mode 100644 test/cpptrace/test_cpptrace.cpp create mode 100644 test/io/CMakeLists.txt create mode 100644 test/io/clean_data.cmake create mode 100644 test/io/test_io.cpp create mode 100644 test/io/test_io_brick.cpp create mode 100644 test/io/test_io_hdf5.ini create mode 100644 test/io/test_io_hdf5_block.ini create mode 100644 test/io/test_io_vtk.ini create mode 100644 test/kokkos/CMakeLists.txt create mode 100644 test/kokkos/amr_key.h create mode 100644 test/kokkos/compute_mandelbrot_amr.cpp create mode 100644 test/kokkos/query_device_kokkos.cpp create mode 100644 test/kokkos/test_kokkos_mdrange.cpp create mode 100644 test/kokkos/test_kokkos_mpi.cpp create mode 100644 test/kokkos/test_kokkos_team_mdrange.cpp create mode 100644 test/kokkos/test_kokkos_teamvector.cpp create mode 100644 test/kokkos/test_morton_key.cpp create mode 100644 test/kokkos/test_parallel_scan.cpp create mode 100644 test/kokkos/test_unordered_map.cpp create mode 100644 test/monitoring/CMakeLists.txt create mode 100644 test/monitoring/test_config.ini create mode 100644 test/monitoring/test_profiling.cpp create mode 100644 test/p4est_simple/CMakeLists.txt create mode 100644 test/p4est_simple/simple_2d.cpp create mode 100644 test/p4est_simple/simple_3d.cpp create mode 100644 test/p4est_wrapper/CMakeLists.txt create mode 100644 test/p4est_wrapper/test_p4est_wrapper.cpp create mode 100644 test/p4est_wrapper/test_p4est_wrapper_old.cpp create mode 100644 test/spdlog/CMakeLists.txt create mode 100644 test/spdlog/test_spdlog.cpp create mode 100644 test/test_common/CMakeLists.txt create mode 100644 test/test_common/DataWriter.cpp create mode 100644 test/test_common/DataWriter.h create mode 100644 test/test_common/FillOutside.cpp create mode 100644 test/test_common/FillOutside.h create mode 100644 test/test_common/FillOutside2.cpp create mode 100644 test/test_common/FillOutside2.h create mode 100644 test/test_common/InitialAMRSetup.cpp create mode 100644 test/test_common/InitialAMRSetup.h create mode 100644 test/test_common/test_func.h create mode 100644 test/utils/CMakeLists.txt create mode 100644 test/utils/config/CMakeLists.txt create mode 100644 test/utils/config/ConfigMapTest.cpp create mode 100644 tools/CMakeLists.txt create mode 100644 tools/ParaViewKalypssoPlugin/CMakeLists.txt create mode 100644 tools/ParaViewKalypssoPlugin/Readme.md create mode 100644 tools/ParaViewKalypssoPlugin/src/CMakeLists.txt create mode 100644 tools/ParaViewKalypssoPlugin/src/Reader/CMakeLists.txt create mode 100644 tools/ParaViewKalypssoPlugin/src/Reader/KalypssoHDF5_Server.xml create mode 100644 tools/ParaViewKalypssoPlugin/src/Reader/vtk.module create mode 100644 tools/ParaViewKalypssoPlugin/src/Reader/vtkKalypssoHDF5Reader.cxx create mode 100644 tools/ParaViewKalypssoPlugin/src/Reader/vtkKalypssoHDF5Reader.h create mode 100644 tools/ParaViewKalypssoPlugin/src/Testing/CMakeLists.txt create mode 100644 tools/ParaViewKalypssoPlugin/src/Testing/Cxx/CMakeLists.txt create mode 100644 tools/ParaViewKalypssoPlugin/src/Testing/Cxx/TestKalypssoHDF5Reader.cxx create mode 100644 tools/ParaViewKalypssoPlugin/src/paraview.plugin create mode 100644 tools/archive/Readme.md create mode 100755 tools/archive/git_archive_all.py create mode 100644 tools/pvpython/paraview_surface_plot_screenshot.py create mode 100644 tools/pvpython/paraview_surface_plot_screenshot_3d.py create mode 100755 tools/xdmf/create_xdmf.py create mode 100644 unit_test/CMakeLists.txt create mode 100644 unit_test/StencilHelper_3d.ini create mode 100644 unit_test/core/BitFieldInteger_test.cpp create mode 100644 unit_test/core/DataArrayBlockMultiVar_test.cpp create mode 100644 unit_test/core/DataArrayBlock_utils_test.cpp create mode 100644 unit_test/core/EdgeDataArrayBlock_utils_test.cpp create mode 100644 unit_test/core/Kokkos_Array_extensions_test.cpp create mode 100644 unit_test/core/MaterialPresence_test.cpp create mode 100644 unit_test/core/MaterialPresence_test_mpi.cpp create mode 100644 unit_test/core/StencilHelper_test.cpp create mode 100644 unit_test/core/geometry_utils_test.cpp create mode 100644 unit_test/core/kalypsso_data_container_test.cpp create mode 100644 unit_test/core/mesh_utils_test.cpp create mode 100644 unit_test/core/morton_utils_test.cpp create mode 100644 unit_test/core/orchard_key_test.cpp create mode 100644 unit_test/core/vof/interface_tracking_utils_test.cpp create mode 100644 unit_test/core/vof/youngs_test.cpp create mode 100644 unit_test/kalypsso_unittest_utils.h create mode 100644 unit_test/main.cpp create mode 100644 unit_test/main_kalypsso_unittest.h create mode 100644 unit_test/utils/config/ConfigMap_test.cpp create mode 100644 unit_test/utils/monitoring/HostTimer_test.cpp create mode 100644 unit_test/utils/mpi/ParallelEnv_test.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..8fd2662 --- /dev/null +++ b/.clang-format @@ -0,0 +1,136 @@ +# clang-format version required : 12 (Ubuntu 20.04) +BasedOnStyle: Mozilla +Language: Cpp +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: true +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +# clang 9.0 AllowAllArgumentsOnNextLine: true +# clang 14 : PackConstructorInitializers: CurrentLine +AllowAllConstructorInitializersOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +# clang 9.0 AllowShortLambdasOnASingleLine: All +# clang 9.0 features AllowShortIfStatementsOnASingleLine: Never +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: All +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: false +BinPackParameters: false +BreakBeforeBraces: Custom +BraceWrapping: + # clang 9.0 feature AfterCaseLabel: false + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: true + BeforeElse: true +## This is the big change from historical ITK formatting! +# Historically ITK used a style similar to https://en.wikipedia.org/wiki/Indentation_style#Whitesmiths_style +# with indented braces, and not indented code. This style is very difficult to automatically +# maintain with code beautification tools. Not indenting braces is more common among +# formatting tools. + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakBeforeBinaryOperators: None +#clang 6.0 BreakBeforeInheritanceComma: true +BreakInheritanceList: BeforeComma +BreakBeforeTernaryOperators: true +#clang 6.0 BreakConstructorInitializersBeforeComma: true +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +## The following line allows larger lines in non-documentation code +ColumnLimit: 100 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 2 +ContinuationIndentWidth: 2 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: true +IndentPPDirectives: AfterHash +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: false +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +## The following line allows larger lines in non-documentation code +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Middle +ReflowComments: true +# We may want to sort the includes as a separate pass +SortIncludes: false +# We may want to revisit this later +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +# SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 2 +UseTab: Never +... diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..05cbbbb --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + Add: [-ferror-limit=0, -std=c++17] diff --git a/.cmake-format.py b/.cmake-format.py new file mode 100644 index 0000000..421b2ff --- /dev/null +++ b/.cmake-format.py @@ -0,0 +1,242 @@ +# ---------------------------------- +# Options affecting listfile parsing +# ---------------------------------- +with section("parse"): + + # Specify structure for custom cmake functions + additional_commands = { 'foo': { 'flags': ['BAR', 'BAZ'], + 'kwargs': {'DEPENDS': '*', 'HEADERS': '*', 'SOURCES': '*'}}} + + # Override configurations per-command where available + override_spec = {} + + # Specify variable tags. + vartags = [] + + # Specify property tags. + proptags = [] + +# ----------------------------- +# Options affecting formatting. +# ----------------------------- +with section("format"): + + # Disable formatting entirely, making cmake-format a no-op + disable = False + + # How wide to allow formatted cmake files + line_width = 100 + + # How many spaces to tab for indent + tab_size = 2 + + # If true, lines are indented using tab characters (utf-8 0x09) instead of + # space characters (utf-8 0x20). In cases where the layout would + # require a fractional tab character, the behavior of the fractional + # indentation is governed by + use_tabchars = False + + # If is True, then the value of this variable indicates how + # fractional indentions are handled during whitespace replacement. If set to + # 'use-space', fractional indentation is left as spaces (utf-8 0x20). If set + # to `round-up` fractional indentation is replaced with a single tab character + # (utf-8 0x09) effectively shifting the column to the next tabstop + fractional_tab_policy = 'use-space' + + # If an argument group contains more than this many sub-groups (parg or kwarg + # groups) then force it to a vertical layout. + max_subgroups_hwrap = 2 + + # If a positional argument group contains more than this many arguments, then + # force it to a vertical layout. + max_pargs_hwrap = 6 + + # If a cmdline positional group consumes more than this many lines without + # nesting, then invalidate the layout (and nest) + max_rows_cmdline = 2 + + # If true, separate flow control names from their parentheses with a space + separate_ctrl_name_with_space = False + + # If true, separate function names from parentheses with a space + separate_fn_name_with_space = False + + # If a statement is wrapped to more than one line, than dangle the closing + # parenthesis on its own line. + dangle_parens = False + + # If the trailing parenthesis must be 'dangled' on its on line, then align it + # to this reference: `prefix`: the start of the statement, `prefix-indent`: + # the start of the statement, plus one indentation level, `child`: align to + # the column of the arguments + dangle_align = 'prefix' + + # If the statement spelling length (including space and parenthesis) is + # smaller than this amount, then force reject nested layouts. + min_prefix_chars = 4 + + # If the statement spelling length (including space and parenthesis) is larger + # than the tab width by more than this amount, then force reject un-nested + # layouts. + max_prefix_chars = 10 + + # If a candidate layout is wrapped horizontally but it exceeds this many + # lines, then reject the layout. + max_lines_hwrap = 2 + + # What style line endings to use in the output. + line_ending = 'unix' + + # Format command names consistently as 'lower' or 'upper' case + command_case = 'canonical' + + # Format keywords consistently as 'lower' or 'upper' case + keyword_case = 'unchanged' + + # A list of command names which should always be wrapped + always_wrap = [] + + # If true, the argument lists which are known to be sortable will be sorted + # lexicographicall + enable_sort = True + + # If true, the parsers may infer whether or not an argument list is sortable + # (without annotation). + autosort = False + + # By default, if cmake-format cannot successfully fit everything into the + # desired linewidth it will apply the last, most aggressive attempt that it + # made. If this flag is True, however, cmake-format will print error, exit + # with non-zero status code, and write-out nothing + require_valid_layout = False + + # A dictionary mapping layout nodes to a list of wrap decisions. See the + # documentation for more information. + layout_passes = {} + +# ------------------------------------------------ +# Options affecting comment reflow and formatting. +# ------------------------------------------------ +with section("markup"): + + # What character to use for bulleted lists + bullet_char = '*' + + # What character to use as punctuation after numerals in an enumerated list + enum_char = '.' + + # If comment markup is enabled, don't reflow the first comment block in each + # listfile. Use this to preserve formatting of your copyright/license + # statements. + first_comment_is_literal = True + + # If comment markup is enabled, don't reflow any comment block which matches + # this (regex) pattern. Default is `None` (disabled). + literal_comment_pattern = None + + # Regular expression to match preformat fences in comments default= + # ``r'^\s*([`~]{3}[`~]*)(.*)$'`` + fence_pattern = '^\\s*([`~]{3}[`~]*)(.*)$' + + # Regular expression to match rulers in comments default= + # ``r'^\s*[^\w\s]{3}.*[^\w\s]{3}$'`` + ruler_pattern = '^\\s*[^\\w\\s]{3}.*[^\\w\\s]{3}$' + + # If a comment line matches starts with this pattern then it is explicitly a + # trailing comment for the preceding argument. Default is '#<' + explicit_trailing_pattern = '#<' + + # If a comment line starts with at least this many consecutive hash + # characters, then don't lstrip() them off. This allows for lazy hash rulers + # where the first hash char is not separated by space + hashruler_min_length = 10 + + # If true, then insert a space between the first hash char and remaining hash + # chars in a hash ruler, and normalize its length to fill the column + canonicalize_hashrulers = True + + # enable comment markup parsing and reflow + enable_markup = True + +# ---------------------------- +# Options affecting the linter +# ---------------------------- +with section("lint"): + + # a list of lint codes to disable + # E1120 is detected in cmake/find_python_module.cmake but I don't understand + # why (positional args seems ok to me) + disabled_codes = ["C0103", "C0111", "C0301", "C0113", "E1120", "W0105", "R0912", "R0913", "R0915"] + + # regular expression pattern describing valid function names + function_pattern = '[0-9a-z_]+' + + # regular expression pattern describing valid macro names + macro_pattern = '[0-9A-Z_]+' + + # regular expression pattern describing valid names for variables with global + # (cache) scope + global_var_pattern = '[A-Z][0-9A-Z_]+' + + # regular expression pattern describing valid names for variables with global + # scope (but internal semantic) + internal_var_pattern = '_[A-Z][0-9A-Z_]+' + + # regular expression pattern describing valid names for variables with local + # scope + local_var_pattern = '[a-z][a-z0-9_]+' + + # regular expression pattern describing valid names for privatedirectory + # variables + private_var_pattern = '_[0-9a-z_]+' + + # regular expression pattern describing valid names for public directory + # variables + public_var_pattern = '[A-Z][0-9A-Z_]+' + + # regular expression pattern describing valid names for function/macro + # arguments and loop variables. + argument_var_pattern = '[a-z][a-z0-9_]+' + + # regular expression pattern describing valid names for keywords used in + # functions or macros + keyword_pattern = '[A-Z][0-9A-Z_]+' + + # In the heuristic for C0201, how many conditionals to match within a loop in + # before considering the loop a parser. + max_conditionals_custom_parser = 2 + + # Require at least this many newlines between statements + min_statement_spacing = 1 + + # Require no more than this many newlines between statements + max_statement_spacing = 2 + max_returns = 6 + max_branches = 12 + max_arguments = 5 + max_localvars = 15 + max_statements = 50 + +# ------------------------------- +# Options affecting file encoding +# ------------------------------- +with section("encode"): + + # If true, emit the unicode byte-order mark (BOM) at the start of the file + emit_byteorder_mark = False + + # Specify the encoding of the input file. Defaults to utf-8 + input_encoding = 'utf-8' + + # Specify the encoding of the output file. Defaults to utf-8. Note that cmake + # only claims to support utf-8 so be careful when using anything else + output_encoding = 'utf-8' + +# ------------------------------------- +# Miscellaneous configurations options. +# ------------------------------------- +with section("misc"): + + # A dictionary containing any per-command configuration overrides. Currently + # only `command_case` is supported. + per_command = {} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..52bbd20 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,38 @@ +# EditorConfig: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*.{cpp,hpp,c,h}] +end_of_line = lf +insert_final_newline = true +charset = utf-8 + +# Various options +trim_trailing_whitespace = true +insert_final_newline = true + +# 4 space indentation +tab_width = 4 +indent_style = space +indent_size = 2 + +# YAML should not use tab as indentation +[*.yml] +indent_style = space +indent_size = 2 + +# Docstrings and comments use max_line_length = 79 +[*.py] +max_line_length = 119 +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true +end_of_line = lf +charset = utf-8 + +[{CMakeLists.txt,*.cmake,*.rst}] +indent_size = 2 +indent_style = space diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 0000000..48750c3 --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,11 @@ +# https://docs.github.com/en/code-security/how-tos/secure-your-supply-chain/secure-your-dependencies/keeping-your-actions-up-to-date-with-dependabot +# Set update schedule for GitHub Actions + +version: 2 +updates: + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every week + interval: "weekly" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..a391533 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,94 @@ +name: C/C++ CI + +on: + push: + branches: + - main + paths-ignore: + - "doc/**" + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + build: + name: C++ build + runs-on: "ubuntu-24.04" + strategy: + fail-fast: false + matrix: + architecture: ["x86_64"] + backend: ["OpenMP", "Cuda"] + include: + - architecture: "x86_64" + env: + config: "Release" + + steps: + # make sure to explicitly checkout git submodule ! + - name: checkout repository + uses: actions/checkout@v6 + with: + submodules: recursive + + - name: hardware info + run: cat /proc/cpuinfo + + - name: Install native dependencies + run: | + sudo apt update + sudo apt-get install -y libhwloc-dev cmake build-essential mpi-default-dev libhdf5-openmpi-dev libspdlog-dev + + - name: Install CUDA toolkit + uses: Jimver/cuda-toolkit@v0.2.30 + id: cuda-toolkit + if: runner.os == 'Linux' + with: + sub-packages: '["nvcc", "nvrtc", "cudart"]' + cuda: '12.9.0' + method: network + use-github-cache: true + + - name: nvcc check + shell: bash + run: | + nvcc -V + echo "Installed cuda version is: ${{steps.cuda-toolkit.outputs.cuda}}" + echo "Cuda install location: ${{steps.cuda-toolkit.outputs.CUDA_PATH}}" + ls "$CUDA_PATH" + ls "$CUDA_PATH/bin" + ls "$CUDA_PATH/include" + + - name: cmake version + shell: bash + run: cmake --version + + - name: configure_openmp + run: cmake -S . -B build_openmp -DCMAKE_BUILD_TYPE=Debug -DKALYPSSO_CORE_ENABLE_UNIT_TESTING=ON -DKALYPSSO_CORE_KOKKOS_BUILD=ON -DKALYPSSO_CORE_KOKKOS_BACKEND=OpenMP -DKALYPSSO_CORE_BUILD_P4EST:BOOL=ON -DKALYPSSO_CORE_ENABLE_WARNINGS=ON + + - name: make_openmp + id: make_openmp + run: cmake --build build_openmp --parallel 4 + + - name: upload_build_openmp_log + if: ${{ failure() && steps.make_openmp.outcome == 'failure' }} + uses: actions/upload-artifact@v6 + with: + name: build_openmp_log + path: build_openmp + retention-days: 1 + + - name: configure_cuda + run: cmake -S . -B build_cuda -DKALYPSSO_CORE_KOKKOS_BUILD=ON -DKALYPSSO_CORE_KOKKOS_BACKEND=Cuda -DKokkos_ARCH_AMPERE80=ON -DKALYPSSO_CORE_BUILD_P4EST:BOOL=ON -DKALYPSSO_CORE_ENABLE_WARNINGS=ON + + - name: make_cuda + run: cmake --build build_cuda --parallel 4 + + - name: upload_build_cuda_log + if: ${{ failure() && steps.make_cuda.outcome == 'failure' }} + uses: actions/upload-artifact@v6 + with: + name: build_cuda_log + path: build_cuda + retention-days: 3 diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml new file mode 100644 index 0000000..b253cc1 --- /dev/null +++ b/.github/workflows/pre-commit.yaml @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2026 kalypsso-core authors +# SPDX-License-Identifier: Unlicense + +name: pre-commit + +on: + pull_request: + push: + branches: [main] + +jobs: + pre-commit: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - uses: pre-commit/action@v3.0.1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed8b7f7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,44 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +build +_build + +# ignore directory manage by cmake FetchContent +external/googletest + +# ignore vscode config directory +.vscode + +# ignore emacs backup files +*~ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..f45a1ad --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,45 @@ +--- +exclude: 'LICENSE' +repos: + # common checks + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: check-added-large-files + - id: check-case-conflict + - id: check-merge-conflict + - id: check-symlinks + - id: check-yaml + args: [--allow-multiple-documents] + - id: detect-private-key + - id: end-of-file-fixer + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + # clang format c++ sources + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: 'v18.1.3' # The default in Ubuntu 24.04 + hooks: + - id: clang-format + types_or: [c++] + # cmake format + - repo: https://github.com/cheshirekow/cmake-format-precommit + rev: v0.6.13 + hooks: + - id: cmake-format + args: ['--config=.cmake-format.py', '--in-place'] + - id: cmake-lint + # Fix typos + - repo: https://github.com/crate-ci/typos + rev: v1.23.1 + hooks: + - id: typos + # REUSE : check licenses + - repo: https://github.com/fsfe/reuse-tool + rev: v6.2.0 + hooks: + - id: reuse + # Security + - repo: https://github.com/gitleaks/gitleaks + rev: v8.18.2 + hooks: + - id: gitleaks diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d7e5998 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,392 @@ +# set minimal version to the one requested by kokkos +cmake_minimum_required(VERSION 3.18) + +if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.13.0") + message(STATUS "Setting policy CMP0076 to use new behavior") + cmake_policy(SET CMP0076 NEW) +endif() + +# CMake 3.24 and above prefers to set the timestamps of all extracted contents to the time of the +# extraction. +if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) + cmake_policy(SET CMP0135 NEW) +endif() + +set(KALYPSSO_CORE_VERSION "0.0.0") + +# deduce KALYPSSO_CORE_SHORT_VERSION using regex +string(REGEX MATCH "^[0-9]+\.[0-9]+\.[0-9]+" KALYPSSO_CORE_SHORT_VERSION ${KALYPSSO_CORE_VERSION}) +if("${KALYPSSO_CORE_SHORT_VERSION}" STREQUAL "") + message( + FATAL_ERROR + "Unable to compute short version from KALYPSSO_CORE_VERSION=${KALYPSSO_CORE_VERSION}") +endif() + +project( + kalypsso-core + VERSION ${KALYPSSO_CORE_SHORT_VERSION} + LANGUAGES C CXX Fortran) + +# project_name in small caps +string(TOLOWER ${PROJECT_NAME} PROJECT_NAME_SC) + +# +# default local cmake macro repository +# +list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +# +# Prevent from build in source tree +# +include(cmake/prevent_build_in_source.cmake) +prevent_build_in_source() + +# +# Init build type: Release, Debug, ... (default is Release) +# +include(cmake/initBuildType.cmake) + +# always export compile commands database +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# +# kalypsso options +# +include(cmake/options.cmake) + +# C++17 is required for Kokkos >= 4.0 CMAKE_CXX_STANDARD is the default value for the CXX_STANDARD +# target property +if(NOT DEFINED CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_EXTENSIONS OFF) +else() + message(STATUS "CMAKE_CXX_STANDARD already set to ${CMAKE_CXX_STANDARD}") +endif() + +# +# kalypsso developer settings +# + +add_library(kalypsso_warnings INTERFACE) + +include(cmake/CompilerWarnings.cmake) + +if(KALYPSSO_CORE_ENABLE_WARNINGS) + kalypsso_set_project_warnings( + kalypsso_warnings + ${KALYPSSO_CORE_ENABLE_WARNINGS_AS_ERRORS} + "" + "" + "" + "" + "") +else() + kalypsso_set_project_warnings( + kalypsso_warnings + ${KALYPSSO_CORE_ENABLE_WARNINGS_AS_ERRORS} + CLANG_C_WARNINGS_DEFAULT + CLANG_CXX_WARNINGS_DEFAULT + GCC_C_WARNINGS_DEFAULT + GCC_CXX_WARNINGS_DEFAULT + CUDA_WARNINGS_DEFAULT) +endif() +add_library(kalypsso::warnings ALIAS kalypsso_warnings) + +include(cmake/setup_install.cmake) +install( + TARGETS kalypsso_warnings + EXPORT kalypsso-core-targets + LIBRARY DESTINATION ${INSTALL_LIBDIR} + ARCHIVE DESTINATION ${INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +# +# Build or find p4est ? +# +include(cmake/build_or_find_p4est.cmake) + +# +# Build or find spdlog ? +# +include(cmake/build_or_find_spdlog.cmake) + +# +# Build or find cnpy ? +# +include(cmake/build_or_find_cnpy.cmake) + +# +# Build or find nvtx3 ? +# +include(cmake/build_or_find_nvtx3.cmake) + +# +# Configure better-enums +# +include(cmake/better-enums-config.cmake) + +# +# Configure cpptrace +# +include(cmake/build_or_find_cpptrace.cmake) + +# +# check MPI, VTK, HDF5, PNETCDF, ... +# +include(cmake/config_mpi.cmake) +include(cmake/config_vtk.cmake) +include(cmake/config_hdf5.cmake) +include(cmake/config_pnetcdf.cmake) +include(cmake/config_highfive.cmake) + +# +# Generate kalypsso_core_config.h and kalypsso_core_version.h (with git info and build date) +# +include(cmake/generate_config_h.cmake) + +# ################################################################################################## +# ################################################################################################## +# now we build kalypsso +# ################################################################################################## +# ################################################################################################## + +# build kokkos +include(cmake/build_or_find_kokkos.cmake) + +# build kalypsso +add_subdirectory(src) + +# build additional tools +add_subdirectory(tools) + +include(CTest) + +add_subdirectory(test) + +# add target kalypsso-test +if(KALYPSSO_CORE_ENABLE_UNIT_TESTING) + add_subdirectory(unit_test) +endif(KALYPSSO_CORE_ENABLE_UNIT_TESTING) + +# add target kalypsso-doc +if(KALYPSSO_CORE_BUILD_DOC) + add_subdirectory(doc) +endif(KALYPSSO_CORE_BUILD_DOC) + +# ################################################################################################## +# Install #### +# ################################################################################################## +include(cmake/setup_install.cmake) + +# make a list of targets to installed (kalypsso itself and all dependencies that are built along) +set(TARGETS_LIST + kalypsso_shared + kalypsso_config + kalypsso_log + kalypsso_monitoring + kalypsso_mpiUtils + kalypsso_p4est_wrap + HighFive + HighFiveInclude) +if(KALYPSSO_CORE_KOKKOS_BUILD) + list( + APPEND + targets_list + kokkos + kokkoscore + kokkoscontainers + kokkosalgorithms + kokkossimd + LIBDL) + if(Kokkos_ENABLE_CUDA) + list(APPEND targets_list CUDA) + endif() + + # TODO update targets_list when other Kokkos backend are used +endif(KALYPSSO_CORE_KOKKOS_BUILD) + +# Use a namespace because CMake provides better diagnostics for namespaced imported targets. +export( + TARGETS ${targets_list} + NAMESPACE ${PROJECT_NAME_SC}:: + FILE ${PROJECT_BINARY_DIR}/kalypsso-core-targets.cmake) + +install( + EXPORT kalypsso-core-targets + NAMESPACE kalypsso:: + FILE kalypsso-core-targets.cmake + DESTINATION ${INSTALL_CMAKE_DIR}) + +# +# Install cmake related files (targets, exports, ...) +# + +set(VERSION_CONFIG ${PROJECT_BINARY_DIR}/${PROJECT_NAME_SC}-config-version.cmake) +set(PROJECT_CONFIG_SRC ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME_SC}-config.cmake.in) +set(PROJECT_CONFIG_DST ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME_SC}-config.cmake) + +install(FILES ${PROJECT_CONFIG_DST} ${VERSION_CONFIG} DESTINATION ${INSTALL_CMAKE_DIR}) + +include(CMakePackageConfigHelpers) + +# Generate the version, config and target files into the build directory. +write_basic_package_version_file( + ${VERSION_CONFIG} + VERSION ${PROJECT_VERSION} + COMPATIBILITY AnyNewerVersion) + +# generate cmake my_package-config.cmake file +configure_package_config_file(${PROJECT_CONFIG_SRC} ${PROJECT_CONFIG_DST} + INSTALL_DESTINATION ${INSTALL_CMAKE_DIR}) + +# +# Install third-party libraries +# + +# install better-enums (header only) +install( + DIRECTORY ${CMAKE_SOURCE_DIR}/external/better-enums + DESTINATION ${INSTALL_INCLUDEDIR} + FILES_MATCHING + PATTERN "*.h" + PATTERN "test" EXCLUDE + PATTERN "doc" EXCLUDE + PATTERN "example" EXCLUDE + PATTERN "script" EXCLUDE + PATTERN ".github" EXCLUDE) + +# #################### PRINT CONFIGURE STATUS ###################### +message("//===================================================") +message("// ${PROJECT_NAME} build configuration:") +message("//===================================================") +message("") +message(" CMake version : ${CMAKE_VERSION}") +if(NOT CMAKE_BUILD_TYPE) + message(" CMake build type : NOT SET !") +else() + message(" CMake build type : ${CMAKE_BUILD_TYPE}") +endif() +message(" CMake install prefix : ${CMAKE_INSTALL_PREFIX}") +message(" CMake system processor : ${CMAKE_SYSTEM_PROCESSOR}") +message(" CMake system name (OS) : ${CMAKE_SYSTEM_NAME}") +message("") +message(" C++ Compiler : ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}") +if(DEFINED CMAKE_CXX_COMPILER_WRAPPER) + message(" ${CMAKE_CXX_COMPILER_WRAPPER}") +endif() +message(" ${CMAKE_CXX_COMPILER}") +message(" CMAKE_CXX_STANDARD : ${CMAKE_CXX_STANDARD}") +message("") + +if(KALYPSSO_CORE_USE_MPI) + message(" MPI enabled") +else(KALYPSSO_CORE_USE_MPI) + message(" MPI not enabled") +endif(KALYPSSO_CORE_USE_MPI) + +message(" Kokkos version = ${Kokkos_VERSION}") +message(" Kokkos_CXX_COMPILER = ${Kokkos_CXX_COMPILER}") +message(" Kokkos_CXX_COMPILER_ID = ${Kokkos_CXX_COMPILER_ID}") +message(" Kokkos_CXX_STANDARD = ${Kokkos_CXX_STANDARD}") +message(" Kokkos_OPTIONS = ${Kokkos_OPTIONS}") +message(" Kokkos_TPLS = ${Kokkos_TPLS}") +message(" Kokkos_DIRS = ${Kokkos_DIR}") + +message(" KALYPSSO_CORE_KOKKOS_BACKEND = ${KALYPSSO_CORE_KOKKOS_BACKEND}") + +if(Kokkos_ENABLE_OPENMP) + message(" Kokkos_ENABLE_OPENMP = ${Kokkos_ENABLE_OPENMP}") +endif() + +if(Kokkos_ENABLE_CUDA) + message(" Kokkos_ENABLE_CUDA = ${Kokkos_ENABLE_CUDA}") + + if((${Kokkos_CUDA_LAMBDA_ENABLED}) OR (${Kokkos_ENABLE_CUDA_LAMBDA})) + message(" Kokkos_ENABLE_CUDA_LAMBDA = ON") + else() + message(" Kokkos_ENABLE_CUDA_LAMBDA = OFF") + endif() + + if((${Kokkos_CUDA_CONSTEXPR_ENABLED}) OR (${Kokkos_ENABLE_CUDA_CONSTEXPR})) + message(" Kokkos_ENABLE_CUDA_CONSTEXPR = ON") + else() + message(" Kokkos_ENABLE_CUDA_CONSTEXPR = OFF") + endif() + + if((${Kokkos_CUDA_UVM_ENABLED}) OR (${Kokkos_ENABLE_CUDA_UVM})) + message(" Kokkos_ENABLE_CUDA_UVM = ON") + else() + message(" Kokkos_ENABLE_CUDA_UVM = OFF") + endif() + + message(" Kokkos CUDA flags = ${KOKKOS_CUDA_OPTIONS}") +endif(Kokkos_ENABLE_CUDA) + +if(Kokkos_ENABLE_HIP) + message(" Kokkos_ENABLE_HIP = ${Kokkos_ENABLE_HIP}") +endif(Kokkos_ENABLE_HIP) + +if((${Kokkos_TPLS_HWLOC_ENABLED}) OR (${Kokkos_ENABLE_HWLOC})) + message(" Kokkos_ENABLE_HWLOC = ON") +else() + message(" Kokkos_ENABLE_HWLOC = OFF") +endif() + +message(" Kokkos architecture = ${Kokkos_ARCH}") + +message("") +if(KALYPSSO_CORE_BUILD_P4EST) + message(" P4EST built using ExternalProject_Add") +else() + message(" P4EST found in environment") +endif() + +message("") +if(KALYPSSO_CORE_BUILD_SPDLOG) + message(" SPDLOG built using ExternalProject_Add") +else() + message(" SPDLOG found in environment") +endif() + +message("") +if(KALYPSSO_CORE_BUILD_CNPY) + message(" CNPY built using ExternalProject_Add") +else() + message(" CNPY found in environment") +endif() + +if(KALYPSSO_CORE_USE_HDF5) + if(HDF5_FOUND) + message(" HDF5 found version : ${HDF5_VERSION}") + message(" HDF5 definitions : ${HDF5_DEFINITIONS}") + message(" HDF5 parallel : ${HDF5_IS_PARALLEL}") + message(" HDF5 includes dirs : ${HDF5_INCLUDE_DIRS}") + message(" HDF5 libraries : ${HDF5_LIBRARIES}") + endif(HDF5_FOUND) +endif(KALYPSSO_CORE_USE_HDF5) +message("") + +if(KALYPSSO_CORE_USE_SPDLOG) + message(" SPDLOG : enabled") +else(KALYPSSO_CORE_USE_SPDLOG) + message(" SPDLOG : disabled") +endif(KALYPSSO_CORE_USE_SPDLOG) +message(" builtin : ${USE_SPDLOG_BUILTIN}") +message(" version : ${spdlog_VERSION}") + +if(KALYPSSO_CORE_USE_CNPY) + message(" CNPY : enabled") +else(KALYPSSO_CORE_USE_CNPY) + message(" CNPY : disabled") +endif(KALYPSSO_CORE_USE_CNPY) + +if(KALYPSSO_CORE_USE_NVTX3) + message(" NVTX : enabled") +else(KALYPSSO_CORE_USE_NVTX3) + message(" NVTX : disabled") +endif(KALYPSSO_CORE_USE_NVTX3) + +if(KALYPSSO_CORE_BUILD_DOC) + message(" Building doc of type ${KALYPSSO_CORE_DOC}") +endif(KALYPSSO_CORE_BUILD_DOC) +message("") diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 0000000..c59bd3b --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,12 @@ + + +# Contributors + +- Yohann Bosqued, yohann.bosqued@cea.fr +- Pierre Kestener, pierre.kestener@cea.fr, current maintainer +- Hoel Keraudren, hoel.keraudren@cea.fr +- Paul-Antoine Matignon, paul-antoine.matignon@cea.fr diff --git a/COPYRIGHT.md b/COPYRIGHT.md new file mode 100644 index 0000000..2516658 --- /dev/null +++ b/COPYRIGHT.md @@ -0,0 +1,24 @@ + + +# Intellectual Property Notice + +This source code is licensed under the Apache license v2.0 with LLVM exception. The full license can be found in [LICENSES/Apache-2.0.txt](LICENSES/Apache-2.0.txt) and [LICENSES/LLVM-exception.txt](LICENSES/LLVM-exception.txt) in the root directory of this source tree. + +Written by CEA + +(C) CEA: Commissariat à l'Énergie Atomique et aux Énergies Alternatives + (https://www.cea.fr) + +All content is the property of the respective authors or their employers. + +No copyright assignment is required to contribute to kalypsso-core. + +# SPDX usage + +Individual files contain SPDX tags instead of the full license text. +This enables machine processing of license information based on the SPDX +License Identifiers that are available here: https://spdx.org/licenses/ diff --git a/LICENSES/Apache-2.0.txt b/LICENSES/Apache-2.0.txt new file mode 100644 index 0000000..137069b --- /dev/null +++ b/LICENSES/Apache-2.0.txt @@ -0,0 +1,73 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSES/BSD-1-Clause.txt b/LICENSES/BSD-1-Clause.txt new file mode 100644 index 0000000..4005b63 --- /dev/null +++ b/LICENSES/BSD-1-Clause.txt @@ -0,0 +1,7 @@ +Copyright (c) . All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSES/LLVM-exception.txt b/LICENSES/LLVM-exception.txt new file mode 100644 index 0000000..fa4b725 --- /dev/null +++ b/LICENSES/LLVM-exception.txt @@ -0,0 +1,15 @@ +---- LLVM Exceptions to the Apache 2.0 License ---- + + As an exception, if, as a result of your compiling your source code, portions + of this Software are embedded into an Object form of such source code, you + may redistribute such embedded portions in such Object form without complying + with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + + In addition, if you combine or link compiled forms of this Software with + software that is licensed under the GPLv2 ("Combined Software") and if a + court of competent jurisdiction determines that the patent provision (Section + 3), the indemnity provision (Section 9) or other Section of the License + conflicts with the conditions of the GPLv2, you may retroactively and + prospectively choose to deem waived or otherwise exclude such Section(s) of + the License, but only in their entirety and only with respect to the Combined + Software. diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt new file mode 100644 index 0000000..d817195 --- /dev/null +++ b/LICENSES/MIT.txt @@ -0,0 +1,18 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/LICENSES/Unlicense.txt b/LICENSES/Unlicense.txt new file mode 100644 index 0000000..cde4ac6 --- /dev/null +++ b/LICENSES/Unlicense.txt @@ -0,0 +1,10 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/REUSE.toml b/REUSE.toml new file mode 100644 index 0000000..0618e8d --- /dev/null +++ b/REUSE.toml @@ -0,0 +1,60 @@ +version = 1 +SPDX-PackageName = "kalypsso-core" +SPDX-PackageSupplier = "kalypsso-core authors" +SPDX-PackageDownloadLocation = "https://github.com/kalypsso-dev/kalypsso-core" + +[[annotations]] +path = [ + ".github/**", "doc/**", "test/AMRmesh/*.py", "tools/pvpython/*.py", "tools/ParaViewKalypssoPlugin/Readme.md", "tools/ParaViewKalypssoPlugin/src/Reader/KalypssoHDF5_Server.xml", "tools/ParaViewKalypssoPlugin/src/Reader/vtk.module", "tools/ParaViewKalypssoPlugin/src/paraview.plugin" +] +precedence = "aggregate" +SPDX-FileCopyrightText = "2025 kalypsso-core authors" +SPDX-License-Identifier = "Apache-2.0 WITH LLVM-exception" + +[[annotations]] +path = [ + ".clang-format", ".clangd", ".clang-tidy", ".pre-commit-config.yaml", "REUSE.toml", ".git-blame-ignore-revs", ".editorconfig", ".cmake-format.py", "**/.gitignore", ".gitmodules", ".travis.yml" +] +precedence = "aggregate" +SPDX-FileCopyrightText = "2025 kalypsso-core authors" +SPDX-License-Identifier = "Unlicense" + +[[annotations]] +path = [ + "**/CMakeLists.txt", "**/*.cmake" +] +precedence = "override" +SPDX-FileCopyrightText = "2025 kalypsso-core authors" +SPDX-License-Identifier = "Unlicense" + +[[annotations]] +path = [ + "test/**/*.ini", "unit_test/*.ini", "src/**/Readme.md", "dependencies/**/.in", "dependencies/Readme.md", "src/kalypsso/core/eos/*.md" +] +precedence = "override" +SPDX-FileCopyrightText = "2025 kalypsso-core authors" +SPDX-License-Identifier = "Apache-2.0 WITH LLVM-exception" + +[[annotations]] +path = [ + "src/kalypsso/utils/config/inih/**" +] +precedence = "override" +SPDX-FileCopyrightText = "2009, Ben Hoyt" +SPDX-License-Identifier = "BSD-1-Clause" + +[[annotations]] +path = [ + "src/kalypsso/utils/io/libb64/**" +] +precedence = "override" +SPDX-FileCopyrightText = "Chris Venter, chris.venter@gmail.com" +SPDX-License-Identifier = "Unlicense" + +[[annotations]] +path = [ + "tools/archive/git_archive_all.py" +] +precedence = "override" +SPDX-FileCopyrightText = "2010 Ilya Kulakov" +SPDX-License-Identifier = "MIT" diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..e8b3b27 --- /dev/null +++ b/Readme.md @@ -0,0 +1,215 @@ + + +![C/C++ build](https://github.com/pkestene/kalypsso-core-prive/actions/workflows/ci.yml/badge.svg?branch=main) + +# What is kalypsso-core ? + +kalypsso-core is just another adaptive mesh refinement package mainly (but not only) for CFD applications but designed with performance portability in mind. No CFD application can be found here, but you will find some in the companion code named [kalypsso-app-public](https://github.com/kalypsso-dev/kalypsso-app-public). kalypsso-core is designed as a library and it only provides core concepts for developing distributed octree-based adaptive mesh refinement applications. As such, distributed mesh management is provided by AMR library [p4est](https://github.com/cburstedde/p4est) and all data containers and device aware algorithms are implemented using the C++ [kokkos](https://github.com/kokkos/kokkos/) library for performance portability. We do not write from scratch but try to couple some of the best state-of-the-art tools. + +- AMR is delegated to library [p4est](https://github.com/cburstedde/p4est). p4est is written in C, and has about 40 kSLOC; it implements cell-based AMR; manage of a forest of octrees, i.e. the physical domain is made of a coarse mesh (p4est connectivity), and each cell of this coarse mesh serves as a root to an octree. +- numerical schemes are designed on top of the p4est mesh, in a decoupled manner, using [kokkos](https://github.com/kokkos/kokkos) for shared memory parallelism. + +You will find more technical details about kalypsso in the following preprint: + +- Kestener, Pierre, Kalypsso: A Performance Portable Platform for Compressible Hydrodynamics Simulations using Adaptive Mesh Refinement (October 24, 2025). Available at SSRN: https://ssrn.com/abstract=5912463 or http://dx.doi.org/10.2139/ssrn.5912463 + +kalypso can be used on a laptop as well as largest parallel clusters of GPUs. + +# How to build (short version) ? + +## Get the source code + +Make sure to clone this repository recursively, this will also download required third party sources as git submodules. + +```bash +git clone --recurse-submodules git@github.com:kalypsso-dev/kalypsso-core.git +``` + +Kokkos and p4est are (optionally) built as part of kalypsso with the cmake build system. + +## Prerequisites + +External dependencies are: + +- [kokkos](https://github.com/kokkos/kokkos) 4.7.0 +- [p4est](https://github.com/cburstedde/p4est) 2.8.7 +- [spdlog](https://github.com/gabime/spdlog) +- [HighFive](https://highfive-devs.github.io/highfive/) and also HDF5 (preferably a parallel version of HDF5) +- [better-enums](https://aantron.github.io/better-enums/) +- optional [cpptrace](https://github.com/jeremy-rifkin/cpptrace) +- optional [cnpy](https://github.com/pkestene/cnpy-cmake) for numpy array outputs + +You'll also need [cmake](https://cmake.org/) (minimum version 3.18) and optionally an [MPI](https://www.mpi-forum.org/) implementation (only ) + +These dependencies can either be : +- built along kalypsso (most convenient and recommended for a beginner) +- built in a separate cmake sub-project (located in sub-directory `dependencies`); this third option is a bit cleaner, it additionally provides a modulefiles to ease the use of these dependencies +- detected from your system's environment + +Note: [spack](https://spack.readthedocs.io/en/latest/) can be used to install all dependencies (except cnpy). We'll provide a recipe soon in user documentation. + +## Build kalypsso-core + +### build p4est, Kokkos and kalypsso all together + +You can chose to build p4est, Kokkos and kalypsso all together at once. + +Please note that +- p4est will be built using cmake macro `ExternalProject` +- unfortunately kokkos cannot be built as easily using `ExternalProject` (mostly because kokkos heavily modify the compilation environment); one must use either `add_subdirectory` or `FetchContent` to a tight integration of kokkos build into kalypsso. Alternatively, if kokkos is already installed on your system for the target hardware, kalypsso will detect it (provided `CMAKE_INSTALL_PREFIX` is adequatly set). + +Here is an example command line to build kalypsso with p4est and Kokkos for Kokkos/OpenMP backend target (which is the default): + +```bash +cd kalypsso-core +cmake -B _build/openmp -S . \ + -DKALYPSSO_CORE_KOKKOS_BUILD:BOOL=ON \ + -DKALYPSSO_CORE_KOKKOS_BACKEND=OpenMP \ + -DKALYPSSO_CORE_BUILD_P4EST:BOOL=ON +cmake --build _build/openmp -j 8 +``` + +The same for Kokkos/CUDA (target CUDA architecture will be detected during the build): +```bash +cmake -B _build/cuda -S . \ + -DKALYPSSO_CORE_KOKKOS_BUILD:BOOL=ON \ + -DKALYPSSO_CORE_KOKKOS_BACKEND=Cuda \ + -DKALYPSSO_CORE_BUILD_P4EST:BOOL=ON +cmake --build _build/cuda -j 8 +``` + +Important note regarding Kokkos/Cuda backend: +- if you build on the same platform as the one used to run kalypsso-core, you're all set, kokkos build system will auto-detect GPU architecture +- if you build on a different system, you need to specify the target architecture, e.g. `-DKokkos_ARCH_HOPPER90=ON` (for Nvidia Hopper aka `sm_90` architecture). Run `ccmake --build _build/cuda` to navigate all available Kokkos architecture cmake options. + +Please note that you don't have to specify environment variable CXX (set to `nvcc_wrapper` when targeting CUDA backend), each sub-project (p4est / Kokkos / kalypsso) is built with a custom specific `CMAKE_CXX_COMPILER` variable; if `KALYPSSO_CORE_KOKKOS_BACKEND` is `Cuda`, internally `nvcc_wrapper` will be selected to build both Kokkos and kalypsso-core. + +## What should I do if I want to use an already installed version of Kokkos ? + +Just set environment variable `CMAKE_PREFIX_PATH` to ensure it contains path to file `KokkosConfig.cmake`: +```shell +export CMAKE_PREFIX_PATH={KOKKOS_INSTALL_DIR}/lib/cmake/Kokkos:$CMAKE_PREFIX_PATH +``` +then Kokkos will be recognized, and building Kokkos will be skipped. + +In that case, you don't need to specify `KALYPSSO_CORE_KOKKOS_BACKEND`, kokkos will be detected by `find_package(Kokkos)` + +## What should I do if I want to use an already installed version of p4est ? + +Use environment variable `CMAKE_PREFIX_PATH`. + +```bash +```shell +# P4EST_ROOT is assumed to be the root directory where p4est was installed +export CMAKE_PREFIX_PATH={P4EST_ROOT}/cmake:$CMAKE_PREFIX_PATH +``` + +## Install requirements with spack (TODO: check if this is really working) + +If you don't know what [spack](https://spack.readthedocs.io/en/latest/index.html) is, take a look at [Spack tutorial 101](https://spack-tutorial.readthedocs.io/en/latest/index.html) + +- install spack +``` shell +wget https://github.com/spack/spack/releases/download/v1.1.1/spack-1.1.1.tar.gz +tar zxf spack-1.1.1.tar.gz +. spack-1.1.1/share/spack/setup-env.sh +# you can source this script in your .bashrc if you plan to use it +# regularly +``` +- install hdf5 + mpi + p4est with spack +``` shell +# spack with automatically install parallel version of hdf5, and so +# install also mpi as a dependance of hdf5 +spack install hdf5+hl +spack install p4est +``` + +### Build kalypsso using dependencies provided by spack (TODO: check this is really working) + +``` shell +# load modules created by spack +# the following is an example on a Ubuntu-18.04 machine +# use command `module avail` to list available module +# created by spack +module load openmpi-3.1.5-gcc-8.3.0-vshzyel +module load hdf5-1.12.0-gcc-8.3.0-bngbam7 +module load p4est-2.2-gcc-8.3.0-shspwhi +# build kalypsso +mkdir build; cd build +# you don't need to set env variable CMAKE_PREFIX_PATH, it is already +# set by spack according to the loaded modules +cmake .. +make -j 4 +``` + +# More information + +## build and run unit tests + +```shell +# configure cmake for building unit tests +# make sure to have boost installed (with libs) +ccmake -DKALYPSSO_CORE_ENABLE_UNIT_TESTING=ON .. +make + +# run unit tests +make test +``` + +## Build documentation + +### Requirements + +- [doxygen](https://www.doxygen.nl/) +- (optional, but recommended) [mkdocs](https://www.mkdocs.org/) for building a static webpage with documentation, written in markdown, with [MkDoxy plugin](https://github.com/JakubAndrysek/MkDoxy) + ```shell + # we recommend using miniconda for installing python packages + conda create -n MkDoxy + conda activate MkDoxy + conda install pip + cd doc + pip install -r requirements.txt + ``` + +### [doxygen](https://www.doxygen.nl/) + +```shell +# re-run cmake with additional options +cmake -B _build/doc -S . -DKALYPSSO_CORE_BUILD_DOC=ON -DKALYPSSO_CORE_DOC=doxygen +cd _build/doc +make kalypsso-doc +``` + +This will generate the html doxygen page in `doc/doxygen/html` + +### [mkdocs](https://www.mkdocs.org/) with [MkDoxy plugin](https://github.com/JakubAndrysek/MkDoxy) + +```shell +# generate mkdocs sources +cd build +cmake --build _build/doc -S . -DKALYPSSO_CORE_BUILD_DOC=ON -DKALYPSSO_CORE_DOC=mkdocs +cd _build/doc +make +make mkdocs +``` + +This will generate the markdown sources for the mkdocs static webpage. + +```shell +# from the build directory +cd doc/mkdocs + +# preview of the webpage +mkdocs serve +# open url localhost:8000 + +# if you want to build the html sources (before deployment) +mkdocs build + +# this will create directory `site` that can directly be uploaded to +# a web server +``` diff --git a/_typos.toml b/_typos.toml new file mode 100644 index 0000000..02d7f44 --- /dev/null +++ b/_typos.toml @@ -0,0 +1,20 @@ +# SPDX-FileCopyrightText: 2025 kalypsso-core authors +# +# SPDX-License-Identifier: Unlicense + +[files] +extend-exclude = ["external/*", "doc/doxyfile.in", "dependencies/cmake/modulefile.in"] + +[default.extend-words] +IY = "IY" + +[default.extend-identifiers] +gam0 = "gam0" +gam_int = "gam_int" +iten = "iten" +StateNd = "StateNd" +som_vit = "som_vit" +som_vit2 = "som_vit2" +otParams = "otParams" +m_otParams = "m_otParams" +Numer = "Numer" diff --git a/cmake/CheckMPIFeatures.cmake b/cmake/CheckMPIFeatures.cmake new file mode 100644 index 0000000..ec7dea8 --- /dev/null +++ b/cmake/CheckMPIFeatures.cmake @@ -0,0 +1,107 @@ +# +# This file is borrowed and slightly modified from +# https://github.com/eschnett/MPIwrapper/blob/main/cmake/CheckMPIFeatures.cmake +# +# function check_mpi_features provides helper to check if MPI implementation has the runtime ability +# to probe GPU-awareness +# +# * cuda-aware (Nvidia GPU), +# * hip-aware (AMD GPU), +# * ze-aware (INTEL GPU) +# +# Apparently Intel MPI (as of version 2021.7.0) doesn't provide header mpi-ext.h. +# + +include(CheckCSourceCompiles) + +# check_mpi_features +macro(CHECK_MPI_FEATURES) + if(NOT DEFINED KALYPSSO_CORE_USE_MPI_EXT OR NOT DEFINED KALYPSSO_CORE_MPI_HAS_QUERY_CUDA_SUPPORT) + list(JOIN MPI_COMPILE_FLAGS " " cmake_required_flags) + + # set(cmake_required_includes ${MPI_INCLUDE_PATH}) set(cmake_required_libraries + # ${MPI_LIBRARIES}) + set(cmake_required_libraries MPI::MPI_C) + + # We cannot use check_include_file here as needs to be included before , and + # check_include_file doesn't support this. + check_c_source_compiles( + " + #include + #include + int main() { + return 0; + } + " + KALYPSSO_CORE_USE_MPI_EXT) + + if(NOT KALYPSSO_CORE_USE_MPI_EXT) + set(KALYPSSO_CORE_USE_MPI_EXT 0) + else() + set(KALYPSSO_CORE_USE_MPI_EXT 1) + endif() + + list(APPEND CMAKE_REQUIRED_DEFINITIONS -DKALYPSSO_CORE_USE_MPI_EXT=${KALYPSSO_CORE_USE_MPI_EXT}) + + check_c_source_compiles( + " + #include + #if KALYPSSO_CORE_USE_MPI_EXT + #include + #endif + int main() { + int result = MPIX_Query_cuda_support(); + return 0; + } + " + KALYPSSO_CORE_MPI_HAS_QUERY_CUDA_SUPPORT) + + if(NOT KALYPSSO_CORE_MPI_HAS_QUERY_CUDA_SUPPORT) + set(KALYPSSO_CORE_MPI_HAS_QUERY_CUDA_SUPPORT 0) + else() + set(KALYPSSO_CORE_MPI_HAS_QUERY_CUDA_SUPPORT 1) + endif() + + check_c_source_compiles( + " + #include + #if KALYPSSO_CORE_USE_MPI_EXT + #include + #endif + int main() { + int result = MPIX_Query_hip_support(); + return 0; + } + " + KALYPSSO_CORE_MPI_HAS_QUERY_HIP_SUPPORT) + + if(NOT KALYPSSO_CORE_MPI_HAS_QUERY_HIP_SUPPORT) + set(KALYPSSO_CORE_MPI_HAS_QUERY_HIP_SUPPORT 0) + else() + set(KALYPSSO_CORE_MPI_HAS_QUERY_HIP_SUPPORT 1) + endif() + + check_c_source_compiles( + " + #include + #if KALYPSSO_CORE_USE_MPI_EXT + #include + #endif + int main() { + int result = MPIX_Query_ze_support(); + return 0; + } + " + KALYPSSO_CORE_MPI_HAS_QUERY_ZE_SUPPORT) + + if(NOT KALYPSSO_CORE_MPI_HAS_QUERY_ZE_SUPPORT) + set(KALYPSSO_CORE_MPI_HAS_QUERY_ZE_SUPPORT 0) + else() + set(KALYPSSO_CORE_MPI_HAS_QUERY_ZE_SUPPORT 1) + endif() + + list(REMOVE_ITEM CMAKE_REQUIRED_DEFINITIONS -DKALYPSSO_CORE_USE_MPI_EXT) + endif() +endmacro(CHECK_MPI_FEATURES) + +check_mpi_features() diff --git a/cmake/CompilerWarnings.cmake b/cmake/CompilerWarnings.cmake new file mode 100644 index 0000000..49624d6 --- /dev/null +++ b/cmake/CompilerWarnings.cmake @@ -0,0 +1,197 @@ +# SPDX-License-Identifier: Unlicense +# SPDX-FileCopyrightText: 2023 Jason Turner +# SPDX-FileCopyrightText: 2025 kalypsso-core authors +# original version: https://github.com/cpp-best-practices/cmake_template + +# +# Default values for warning flags are lightweight; Wall is a minimum. +# +# This macro will set the following variables (if not already set on the command-line or via +# environment variables): +# +# * CLANG_C_WARNINGS_DEFAULT +# * CLANG_CXX_WARNINGS_DEFAULT +# * GCC_C_WARNINGS_DEFAULT +# * GCC_CXX_WARNINGS_DEFAULT +# * CUDA_WARNINGS_DEFAULT +# +# example: +# +# * using cmake variable: GCC_CXX_WARNINGS_DEFAULT="-Wall;-Wshadow" +# * using environment variable: export GCC_CXX_WARNINGS_DEFAULT="-Wall;-Wshadow" +# +macro(kalypsso_set_default_warning_flags) + + # use basic default warnings flags + if(DEFINED ENV{CLANG_C_WARNINGS_DEFAULT}) + set(CLANG_C_WARNINGS_DEFAULT $ENV{CLANG_C_WARNINGS_DEFAULT}) + elseif(NOT DEFINED CLANG_C_WARNINGS_DEFAULT) + set(CLANG_C_WARNINGS_DEFAULT -Wall) + endif() + + if(DEFINED ENV{CLANG_CXX_WARNINGS_DEFAULT}) + set(CLANG_CXX_WARNINGS_DEFAULT $ENV{CLANG_CXX_WARNINGS_DEFAULT}) + elseif(NOT DEFINED CLANG_CXX_WARNINGS_DEFAULT) + set(CLANG_CXX_WARNINGS_DEFAULT -Wall) + endif() + + if(DEFINED ENV{GCC_C_WARNINGS_DEFAULT}) + set(GCC_C_WARNINGS_DEFAULT $ENV{GCC_C_WARNINGS_DEFAULT}) + elseif(NOT DEFINED GCC_C_WARNINGS_DEFAULT) + set(GCC_C_WARNINGS_DEFAULT -Wall) + endif() + + if(DEFINED ENV{GCC_CXX_WARNINGS_DEFAULT}) + set(GCC_CXX_WARNINGS_DEFAULT $ENV{GCC_CXX_WARNINGS_DEFAULT}) + elseif(NOT DEFINED GCC_CXX_WARNINGS_DEFAULT) + set(GCC_CXX_WARNINGS_DEFAULT -Wall) + endif() + + if(DEFINED ENV{CUDA_WARNINGS_DEFAULT}) + set(CUDA_WARNINGS_DEFAULT $ENV{CUDA_WARNINGS_DEFAULT}) + elseif(NOT DEFINED CUDA_WARNINGS_DEFAULT) + set(CUDA_WARNINGS_DEFAULT -Wall) + endif() + +endmacro(kalypsso_set_default_warning_flags) + +kalypsso_set_default_warning_flags() + +# +# Setup developer warnings (full set) +# +# This function will do two things: +# +# * warning flags setup: either use default warnings flags, or use the full set of developer warning +# flags +# * define a library target name "kalypsso_warning" that every final target to link with in order to +# transitively receive the compilation flags; that way each time the flag set changes, it will +# trigger re-compilation of every depending targets. +# +function( + kalypsso_set_project_warnings + project_name + WARNINGS_AS_ERRORS + CLANG_C_WARNINGS + CLANG_CXX_WARNINGS + GCC_C_WARNINGS + GCC_CXX_WARNINGS + CUDA_WARNINGS) + + if("${CLANG_C_WARNINGS}" STREQUAL "") + set(CLANG_C_WARNINGS + -Wall + -Wextra # reasonable and standard + -Wshadow # warn the user if a variable declaration shadows one from a parent context + # catch hard to track down memory errors + -Wcast-align # warn for potential performance problem casts + -Wunused # warn on anything being unused + -Wpedantic # warn if non-standard C++ is used + -Wconversion # warn on type conversions that may lose data + -Wsign-conversion # warn on type conversions that may lose data + -Wnull-dereference # warn if a null dereference is detected + -Wdouble-promotion # warn if float is implicit promoted to double + -Wformat=2 # warn on security issues around functions that format output (ie printf) + -Wimplicit-fallthrough # warn on statements that fallthrough without an explicit annotation + ) + if(KALYPSSO_CORE_DISABLE_DEPRECATED_WARNING) + set(CLANG_C_WARNINGS ${CLANG_C_WARNINGS} -Wno-deprecated-declarations # do not warn about + # deprecated features + ) + endif() + if(DEFINED KALYPSSO_CORE_KOKKOS_BACKEND) + if(KALYPSSO_CORE_KOKKOS_BACKEND MATCHES "Cuda") + # don't do anything, -Wold-style-cast generates too many false positive warnings + endif() + endif() + else() + set(CLANG_C_WARNINGS ${${CLANG_C_WARNINGS}}) + endif() + message(STATUS "Clang C warnings flags are: ${CLANG_C_WARNINGS}") + + if("${CLANG_CXX_WARNINGS}" STREQUAL "") + set(CLANG_CXX_WARNINGS + ${CLANG_C_WARNING} + -Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual + # destructor. This helps + # catch hard to track down memory errors + -Woverloaded-virtual # warn if you overload (not override) a virtual function + ) + else() + set(CLANG_CXX_WARNINGS ${${CLANG_CXX_WARNINGS}}) + endif() + message(STATUS "Clang CXX warnings flags are: ${CLANG_CXX_WARNINGS}") + + if("${GCC_C_WARNINGS}" STREQUAL "") + set(GCC_C_WARNINGS + ${CLANG_C_WARNINGS} + -Wmisleading-indentation # warn if indentation implies blocks where blocks do not exist + -Wduplicated-cond # warn if if / else chain has duplicated conditions + -Wduplicated-branches # warn if if / else branches have duplicated code + -Wlogical-op # warn about logical operations being used where bitwise were probably wanted + ) + else() + set(GCC_C_WARNINGS ${${GCC_C_WARNINGS}}) + endif() + message(STATUS "Gcc C warnings flags are: ${GCC_C_WARNINGS}") + + if("${GCC_CXX_WARNINGS}" STREQUAL "") + set(GCC_CXX_WARNINGS ${GCC_C_WARNINGS} -Wuseless-cast # warn if you perform a cast to the same + # type + ) + else() + set(GCC_CXX_WARNINGS ${${GCC_CXX_WARNINGS}}) + endif() + if(DEFINED KALYPSSO_CORE_KOKKOS_BACKEND) + if(KALYPSSO_CORE_KOKKOS_BACKEND MATCHES "Cuda") + # don't do anything, -Wold-style-cast generates too many false positive warnings + else() + set(GCC_CXX_WARNINGS ${GCC_C_WARNINGS} -Wold-style-cast # warn for c-style casts + ) + endif() + endif() + message(STATUS "Gcc CXX warnings flags are: ${GCC_CXX_WARNINGS}") + + if("${CUDA_WARNINGS}" STREQUAL "") + set(CUDA_WARNINGS -Wall -Wextra -Wunused -Wconversion -Wshadow + # TODO add more Cuda warnings + ) + else() + set(CUDA_WARNINGS ${${CUDA_WARNINGS}}) + endif() + message(STATUS "Cuda warnings flags are: ${CUDA_WARNINGS}") + + if(WARNINGS_AS_ERRORS) + message(TRACE "Warnings are treated as errors") + list(APPEND CLANG_C_WARNINGS -Werror) + list(APPEND CLANG_CXX_WARNINGS -Werror) + list(APPEND GCC_C_WARNINGS -Werror) + list(APPEND GCC_CXX_WARNINGS -Werror) + list(APPEND MSVC_WARNINGS /WX) + endif() + + if(MSVC) + set(PROJECT_WARNINGS_C ${MSVC_WARNINGS}) + set(PROJECT_WARNINGS_CXX ${MSVC_WARNINGS}) + elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + set(PROJECT_WARNINGS_C ${CLANG_C_WARNINGS}) + set(PROJECT_WARNINGS_CXX ${CLANG_CXX_WARNINGS}) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(PROJECT_WARNINGS_C ${GCC_C_WARNINGS}) + set(PROJECT_WARNINGS_CXX ${GCC_CXX_WARNINGS}) + else() + message(AUTHOR_WARNING "No compiler warnings set for CXX compiler: '${CMAKE_CXX_COMPILER_ID}'") + # TODO support Intel compiler + endif() + + set(PROJECT_WARNINGS_CUDA "${CUDA_WARNINGS}") + + target_compile_options( + ${project_name} + INTERFACE # C++ warnings + $<$:${PROJECT_WARNINGS_CXX}> + # C warnings + $<$:${PROJECT_WARNINGS_C}> + # Cuda warnings + $<$:${PROJECT_WARNINGS_CUDA}>) +endfunction() diff --git a/cmake/FindLAPACKE.cmake b/cmake/FindLAPACKE.cmake new file mode 100644 index 0000000..cc82c17 --- /dev/null +++ b/cmake/FindLAPACKE.cmake @@ -0,0 +1,51 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or +# https://cmake.org/licensing for details. + +# cmake-format: off +#.rst: +# FindLAPACKE +# ------------- +# +# Find LAPACKE +# +# Find the LAPACKE C library +# +# Using LAPACKE: +# +# :: +# +# find_package(LAPACKE REQUIRED) +# include_directories(${LAPACKE_INCLUDE_DIRS}) +# add_executable(foo foo.cc) +# target_link_libraries(foo ${LAPACKE_LIBRARIES}) +# +# This module sets the following variables: +# +# :: +# +# LAPACKE_FOUND - set to true if the library is found +# LAPACKE_INCLUDE_DIRS - list of required include directories +# LAPACKE_LIBRARIES - list of libraries to be linked +# cmake-format: on + +# UNIX paths are standard, no need to write. +find_library( + LAPACKE_LIBRARY + NAMES lapacke + PATHS "$ENV{LAPACKE_LIB_DIR}") +find_path( + LAPACKE_INCLUDE_DIR + NAMES lapacke.h + PATHS "$ENV{LAPACKE_INC_DIR}") + +# ------------------------------------------------------------------------ +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Lapacke REQUIRED_VARS LAPACKE_LIBRARY LAPACKE_INCLUDE_DIR) + +if(LAPACKE_FOUND) + set(LAPACKE_INCLUDE_DIRS ${LAPACKE_INCLUDE_DIR}) + set(LAPACKE_LIBRARIES ${LAPACKE_LIBRARY}) +endif() + +# Hide internal variables +mark_as_advanced(LAPACKE_INCLUDE_DIR LAPACKE_LIBRARY) diff --git a/cmake/FindP4EST.cmake b/cmake/FindP4EST.cmake new file mode 100644 index 0000000..a052842 --- /dev/null +++ b/cmake/FindP4EST.cmake @@ -0,0 +1,84 @@ +# FindP4EST.cmake +# --------------- +# +# Try to find p4est library (use env variable P4EST_ROOT as a hint. If libsc is installed in a +# different location than p4est, you can use SC_ROOT to provide an hint) +# +# Recommendation: if you use module-environment, just make sure variable P4EST_ROOT is set to +# top-level directory where p4est was install. +# +# Result Variables +# ---------------- +# +# This module defines the following variables:: +# +# * P4EST_FOUND - True if P4EST was found +# * P4EST_INCLUDE_DIRS - include directories for P4EST +# * P4EST_LIBRARIES - link against this library to use P4EST +# +# The module will also define two cache variables:: +# +# * P4EST_INCLUDE_DIR - the P4EST include directory +# * P4EST_LIBRARY - the path to the P4EST library +# +# This module also exports the following target:: p4est + +if(NOT P4EST_ROOT) + if(DEFINED ENV{P4EST_ROOT}) + set(SC_ROOT $ENV{P4EST_ROOT}) + else() + message(STATUS "(WARNING) Env variable P4EST_ROOT not defined...") + endif() +endif() + +find_path( + P4EST_INCLUDE_DIR + NAMES p4est.h + PATH_SUFFIXES include + HINTS ${P4EST_DIR} ${P4EST_ROOT}) +mark_as_advanced(P4EST_INCLUDE_DIR) + +find_library( + P4EST_LIBRARY + NAMES p4est + PATH_SUFFIXES lib + HINTS ${P4EST_DIR} ${P4EST_ROOT}) +mark_as_advanced(P4EST_LIBRARY) + +# try to detect P4EST version +if(P4EST_INCLUDE_DIR AND EXISTS "${P4EST_INCLUDE_DIR}/p4est_config.h") + file(STRINGS "${P4EST_INCLUDE_DIR}/p4est_config.h" P4EST_H + REGEX "^#define P4EST_VERSION \"[^\"]*\"$") + + # try to extract the string inside quote + string(REGEX REPLACE "^.*P4EST_VERSION \"([^\"]+).*\"$" "\\1" P4EST_VERSION_STRING "${P4EST_H}") + +else() + set(P4EST_VERSION_STRING "unknown") +endif() +mark_as_advanced(P4EST_VERSION_STRING) + +# handle the QUIETLY and REQUIRED arguments and set P4EST_FOUND to TRUE if all listed variables are +# TRUE +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + P4EST + REQUIRED_VARS P4EST_LIBRARY P4EST_INCLUDE_DIR + VERSION_VAR P4EST_VERSION_STRING) + +if(P4EST_FOUND) + set(P4EST_INCLUDE_DIRS ${P4EST_INCLUDE_DIR}) +endif(P4EST_FOUND) + +if(P4EST_FOUND AND NOT TARGET p4est::p4est) + add_library(p4est::p4est SHARED IMPORTED) + + # We need to link libsc, so we look for it + find_package(SC REQUIRED) + + target_link_libraries(p4est::p4est INTERFACE sc::sc) + + set_target_properties( + p4est::p4est PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${P4EST_INCLUDE_DIRS}" + IMPORTED_LOCATION "${P4EST_LIBRARY}") +endif() diff --git a/cmake/FindPAPI.cmake b/cmake/FindPAPI.cmake new file mode 100644 index 0000000..82bf644 --- /dev/null +++ b/cmake/FindPAPI.cmake @@ -0,0 +1,46 @@ +# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other perf-dump Developers. See +# the top-level LICENSE file for details. +# +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-Exception + +# ################################################################################################## +# This file was found and adapted from original LLNL tools called perf-dump. +# +# For details, see https://github.com/LLNL/perf-dump +# +# ################################################################################################## +# +# Try to find PAPI headers and libraries. +# +# Usage of this module as follows: +# +# find_package(PAPI) +# +# Variables used by this module, they can change the default behaviour and need to be set before +# calling find_package: +# +# * PAPI_ROOT : Set this environment variable to the root installation of libpapi if the module has +# problems finding the proper installation path. +# +# Variables defined by this module: +# +# * PAPI_FOUND System has PAPI libraries and headers +# * PAPI_LIBRARY The PAPI library +# * PAPI_INCLUDE_DIR The location of PAPI headers + +find_library( + PAPI_LIBRARY + NAMES libpapi.so libpapi.a papi + HINTS ENV PAPI_ROOT + PATH_SUFFIXES lib lib64) + +find_path( + PAPI_INCLUDE_DIR + NAMES papi.h + HINTS ENV PAPI_ROOT + PATH_SUFFIXES include) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(PAPI REQUIRED_VARS PAPI_LIBRARY PAPI_INCLUDE_DIR) + +mark_as_advanced(PAPI_LIBRARY PAPI_INCLUDE_DIR) diff --git a/cmake/FindPNETCDF.cmake b/cmake/FindPNETCDF.cmake new file mode 100644 index 0000000..8a9d220 --- /dev/null +++ b/cmake/FindPNETCDF.cmake @@ -0,0 +1,84 @@ +# +# FindPNETCDF +# ----------- +# +# Find PNETCDF, a parallel I/O library for accessing NetCDF files. +# +# Possible input environment variables: +# +# * PNETCDF_ROOT - Root directory of PNETCDF. +# * PARALLEL_NETCDF_ROOT - Alias of the above. +# +# The MPI package is also found, and the include directories and linking libraries are appended to +# PNETCDF ones. By doing this, users who do not need MPI can use the normal compilers without +# linking errors. +# +# Output CMake variables: +# +# * PNETCDF_FOUND - True if PNETCDF was found on the system. +# * PNETCDF_INCLUDE_DIRS - List of the PNETCDF include and the dependency includes. +# * PNETCDF_LIBRARY_DIRS - List of the PNETCDF lib and the dependency libs. +# * PNETCDF_LIBRARIES - List of the PNETCDF libraries. +# +# Authors: +# +# * Li Dong +# +# Source : https://github.com/dongli/geomtk +# +if(${PNETCDF_FIND_REQUIRED}) + set(REQUIRED_OR_NOT REQUIRED) +endif() +if(${PNETCDF_FIND_QUIETLY}) + set(QUIET_OR_NOT QUIET) +endif() + +# Use PNETCDF_VERSION command to query some PNETCDF library information. +find_program(PNETCDF_VERSION NAMES pnetcdf_version) +if(PNETCDF_VERSION MATCHES "NOTFOUND") + # If pnetcdf_version can not be found, check some environment variables. + foreach(var IN ITEMS "PNETCDF_ROOT" "PARALLEL_NETCDF_ROOT") + if(DEFINED ENV{${var}}) + set(PNETCDF_ROOT $ENV{${var}}) + break() + endif() + endforeach() + if(DEFINED PNETCDF_ROOT) + set(PNETCDF_VERSION "${PNETCDF_ROOT}/bin/pnetcdf_version") + endif() +else() + get_filename_component(pnetcdf_bin ${PNETCDF_VERSION} PATH) + string(REGEX REPLACE "/bin$" "" PNETCDF_ROOT ${pnetcdf_bin}) + set(PNETCDF_FOUND TRUE) +endif() +if(DEFINED PNETCDF_ROOT) + list(APPEND PNETCDF_INCLUDE_DIRS "${PNETCDF_ROOT}/include") + list(APPEND PNETCDF_LIBRARY_DIRS "${PNETCDF_ROOT}/lib") + find_library( + PNETCDF_LIBRARIES + NAMES libpnetcdf.a + HINTS ${PNETCDF_LIBRARY_DIRS}) + # Get version string. + execute_process(COMMAND ${PNETCDF_VERSION} -v OUTPUT_VARIABLE output) + string(REGEX MATCH "[0-9]+.[0-9]+.[0-9]+" PNETCDF_VERSION_STRING ${output}) + # Find the dependency MPI package and append it to PNETCDF stuffs. + execute_process(COMMAND ${PNETCDF_VERSION} -b OUTPUT_VARIABLE output) + string(REGEX MATCH "MPICC: */[^ ]+" tmp ${output}) + string(REGEX REPLACE "MPICC: *" "" MPI_C_COMPILER ${tmp}) + find_package(MPI ${QUIET_OR_NOT} ${REQUIRED_OR_NOT}) + foreach(lang IN ITEMS "C" "CXX" "Fortran") + if(${MPI_${lang}_FOUND}) + list(APPEND PNETCDF_INCLUDE_DIRS ${MPI_${lang}_INCLUDE_PATH}) + list(APPEND PNETCDF_LIBRARIES ${MPI_${lang}_LIBRARIES}) + endif() + endforeach() + list(REMOVE_DUPLICATES PNETCDF_INCLUDE_DIRS) + list(REMOVE_DUPLICATES PNETCDF_LIBRARIES) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + PNETCDF + FOUND_VAR PNETCDF_FOUND + REQUIRED_VARS PNETCDF_INCLUDE_DIRS PNETCDF_LIBRARY_DIRS PNETCDF_LIBRARIES + VERSION_VAR PNETCDF_VERSION_STRING) diff --git a/cmake/FindSC.cmake b/cmake/FindSC.cmake new file mode 100644 index 0000000..d5b82eb --- /dev/null +++ b/cmake/FindSC.cmake @@ -0,0 +1,75 @@ +# FindSC.cmake +# --------------- +# +# Try to find sc library (use env variable SC_ROOT as a hint. If libsc is installed in a different +# location than sc, you can use SC_ROOT to provide an hint) +# +# Recommendation: if you use module-environment, just make sure variable SC_ROOT is set to top-level +# directory where sc was install. +# +# Result Variables +# ---------------- +# +# This module defines the following variables:: +# +# SC_FOUND - True if SC was found SC_INCLUDE_DIRS - include directories for SC +# SC_LIBRARIES - link against this library to use SC +# +# The module will also define two cache variables:: +# +# SC_INCLUDE_DIR - the SC include directory SC_LIBRARY - the path to the SC library +# +# This module also exports the following target:: sc + +if(NOT SC_ROOT) + if(DEFINED ENV{SC_ROOT}) + set(SC_ROOT $ENV{SC_ROOT}) + else() + message(STATUS "Env variable SC_ROOT not defined...") + endif() +endif() + +find_path( + SC_INCLUDE_DIR + NAMES sc.h + PATH_SUFFIXES include + HINTS ${SC_ROOT} ${SC_DIR}) +mark_as_advanced(SC_INCLUDE_DIR) + +find_library( + SC_LIBRARY + NAMES sc + PATH_SUFFIXES lib + HINTS ${SC_ROOT} ${SC_DIR}) +mark_as_advanced(SC_LIBRARY) + +# try to detect SC version +if(SC_INCLUDE_DIR AND EXISTS "${SC_INCLUDE_DIR}/sc_config.h") + file(STRINGS "${SC_INCLUDE_DIR}/sc_config.h" SC_H REGEX "^#define SC_VERSION \"[^\"]*\"$") + + # try to extract the string inside quote + string(REGEX REPLACE "^.*SC_VERSION \"([^\"]+).*\"$" "\\1" SC_VERSION_STRING "${SC_H}") + +else() + set(SC_VERSION_STRING "unknown") +endif() +mark_as_advanced(SC_VERSION_STRING) + +# handle the QUIETLY and REQUIRED arguments and set SC_FOUND to TRUE if all listed variables are +# TRUE +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + SC + REQUIRED_VARS SC_LIBRARY SC_INCLUDE_DIR + VERSION_VAR SC_VERSION_STRING) + +if(SC_FOUND) + set(SC_INCLUDE_DIRS ${SC_INCLUDE_DIR}) +endif(SC_FOUND) + +if(SC_FOUND AND NOT TARGET sc::sc) + add_library(sc::sc SHARED IMPORTED) + + set_target_properties(sc::sc PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${SC_INCLUDE_DIR}" + IMPORTED_LOCATION "${SC_LIBRARY}") +endif() diff --git a/cmake/better-enums-config.cmake b/cmake/better-enums-config.cmake new file mode 100644 index 0000000..7b5ef8b --- /dev/null +++ b/cmake/better-enums-config.cmake @@ -0,0 +1,11 @@ +message("[kalypsso-core] Create imported target for better-enums") + +add_library(better-enums INTERFACE IMPORTED GLOBAL) + +# cmake-format: off +set_target_properties( + better-enums PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${PROJECT_SOURCE_DIR}/external/better-enums) +# cmake-format: on + +# create library alias +add_library(better-enums::better-enums ALIAS better-enums) diff --git a/cmake/build_or_find_cnpy.cmake b/cmake/build_or_find_cnpy.cmake new file mode 100644 index 0000000..fa8dd10 --- /dev/null +++ b/cmake/build_or_find_cnpy.cmake @@ -0,0 +1,82 @@ +# +# find or build cnpy (cnpy is optional); use CNPY_FOUND to appropriately enable it. +# + +# default value is ON +option(KALYPSSO_CORE_BUILD_CNPY "Building cnpy" ON) + +# build cnpy ? +if(KALYPSSO_CORE_BUILD_CNPY) + + # enforce a local build of cnpy + message("[kalypsso-core] Building cnpy from source") + + set_property(DIRECTORY PROPERTY EP_BASE ${CMAKE_BINARY_DIR}/external/cnpy) + + # gnu compatibility, see https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html this is + # used to retrieve portable install paths e.g. some platforms install libraries in lib, others in + # lib64 + include(GNUInstallDirs) + + set(CNPY_INSTALL_PREFIX ${PROJECT_BINARY_DIR}/external/install/cnpy) + set(CNPY_INCLUDEDIR ${CNPY_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}) + set(CNPY_LIBDIR ${CNPY_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}) + + # set minimal cmake options + set(CMAKE_CNPY_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${CNPY_INSTALL_PREFIX} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_VERBOSE_BUILD:BOOL=TRUE) + + # + # Make ExternalProject macro available + # + include(ExternalProject) + + # cmake-format: off + ExternalProject_Add( + cnpy_external + # GIT_REPOSITORY https://github.com/pkestene/cnpy-cmake.git + # GIT_TAG master + SOURCE_DIR ${PROJECT_SOURCE_DIR}/external/cnpy + CMAKE_ARGS ${CMAKE_CNPY_ARGS} + BUILD_COMMAND ${CMAKE_MAKE_PROGRAM} VERBOSE=1 -j 8 + LOG_CONFIGURE 1 + LOG_BUILD 1 + LOG_INSTALL 1) + # cmake-format: on + + # work around to prevent cmake to complain about non existing path + file(MAKE_DIRECTORY ${CNPY_INCLUDEDIR}) + + # + # need to import target, so that we can build kalypsso-core + # + + # ##### CNPY ##### + add_library(cnpy SHARED IMPORTED GLOBAL) + add_dependencies(cnpy cnpy_external) + + set_target_properties(cnpy PROPERTIES IMPORTED_LOCATION "${CNPY_LIBDIR}/libcnpy.so" + INTERFACE_INCLUDE_DIRECTORIES ${CNPY_INCLUDEDIR}) + + # create library alias + add_library(cnpy::cnpy ALIAS cnpy) + + # cnpy is found ! + set(CNPY_FOUND true) + set(KALYPSSO_CORE_USE_CNPY ON) + set(USE_CNPY_BUILTIN TRUE) + +else(KALYPSSO_CORE_BUILD_CNPY) + + # try to detect CNPY using env variable CNPY_ROOT + find_package(cnpy CONFIG) + + if(cnpy_FOUND) + set(KALYPSSO_CORE_USE_CNPY ON) + else() + set(KALYPSSO_CORE_USE_CNPY OFF) + endif() + + set(USE_CNPY_BUILTIN FALSE) + +endif(KALYPSSO_CORE_BUILD_CNPY) diff --git a/cmake/build_or_find_cpptrace.cmake b/cmake/build_or_find_cpptrace.cmake new file mode 100644 index 0000000..789617d --- /dev/null +++ b/cmake/build_or_find_cpptrace.cmake @@ -0,0 +1,40 @@ +# check if user requested a build of kalypsso-core with cpptrace enabled, so make it available +# +# Notes on important cpptrace options +# +# * set CPPTRACE_USE_EXTERNAL_LIBDWARF to ON if you want to use libdwarf from your system (by +# default cpptrace will download libdwarf sources) +# * set CPPTRACE_USE_EXTERNAL_ZSTD to ON if you want to use libzstd from your system (by +# default cpptrace will download zstd sources) +# * set CPPTRACE_UNWIND_WITH_UNWIND to ON is recommended for stack trace print +# +# About libunwind: on ubuntu 24.04 there are two packages providing libunwind: +# +# - libunwind-dev which packages https://github.com/libunwind/libunwind +# - libunwind-18-dev provided by llvm +# +# You should prefer libunwind-dev over libunwind-18-dev because the first one contains a pkg-config file +# and while the second does'nt. +# +include(cmake/cmake_utils.cmake) + +if(KALYPSSO_CORE_USE_CPPTRACE) + + message("[kalypsso-core / cpptrace] enabled") + + include(FetchContent) + + if(DEFINED CPPTRACE_UNWIND_WITH_UNWIND) + if(NOT CPPTRACE_UNWIND_WITH_UNWIND) + message(STATUS "CPPTRACE_UNWIND_WITH_UNWIND is OFF; it is recommended to set it ON for ") + endif() + endif() + + FetchContent_Declare(cpptrace_external SOURCE_DIR ${PROJECT_SOURCE_DIR}/external/cpptrace SYSTEM) + + # libunwind is necessary to print stack + + fetchcontent_makeavailablewithargs(cpptrace_external "CPPTRACE_UNWIND_WITH_LIBUNWIND=ON" + "CPPTRACE_UNWIND_WITH_UNWIND=ON") + +endif(KALYPSSO_CORE_USE_CPPTRACE) diff --git a/cmake/build_or_find_kokkos.cmake b/cmake/build_or_find_kokkos.cmake new file mode 100644 index 0000000..33d1014 --- /dev/null +++ b/cmake/build_or_find_kokkos.cmake @@ -0,0 +1,221 @@ +# Two alternatives: +# +# 1. If KALYPSSO_CORE_KOKKOS_BUILD is ON, we download kokkos sources and build them using FetchContent (which +# actually uses add_subdirectory) +# 2. If KALYPSSO_CORE_KOKKOS_BUILD is OFF (default), we don't build kokkos, but use find_package for setup +# (you must have kokkos already installed) + +# NOTE about required C++ standard we better chose to set the minimum C++ standard level if not +# already done: +# +# * when building kokkos < 4.0.00, it defaults to c++-14 +# * when building kokkos >= 4.0.00, it defaults to c++-17 +# * when using installed kokkos, we set C++ standard according to kokkos version + +# +# Does kalypsso-core builds kokkos (https://github.com/kokkos/kokkos) ? +# +option(KALYPSSO_CORE_KOKKOS_BUILD "Turn ON if you want to build kokkos (default: OFF)" OFF) + +# +# Option to use git (instead of tarball release) for downloading kokkos +# +option(KALYPSSO_CORE_KOKKOS_USE_GIT + "Turn ON if you want to use git to download Kokkos sources (default: OFF)" OFF) + +# +# Options to specify target device backend +# + +# set default backend +set(KALYPSSO_CORE_KOKKOS_BACKEND + "Undefined" + CACHE STRING "Kokkos default backend device") + +# Set the possible values for kokkos backend device +set_property(CACHE KALYPSSO_CORE_KOKKOS_BACKEND PROPERTY STRINGS "OpenMP" "Cuda" "HIP" "Undefined") + +# raise the minimum C++ standard level if not already done when build kokkos, it defaults to c++-17 +# when using installed kokkos, it is not set, so defaulting to c++-17 kokkos 4.0.00 requires c++-17 +# anyway +if(NOT "${CMAKE_CXX_STANDARD}") + set(CMAKE_CXX_STANDARD 17) +endif() + +# check if user requested a build of kokkos use carefully, it may strongly increase build time +if(KALYPSSO_CORE_KOKKOS_BUILD) + + message("[kalypsso-core / kokkos] Building kokkos from source") + + set_property(DIRECTORY PROPERTY EP_BASE ${CMAKE_BINARY_DIR}/external) + + # Kokkos default build options + + # set install path + list(APPEND KALYPSSO_CORE_KOKKOS_CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${KOKKOS_INSTALL_DIR}) + + # use predefined cmake args can be override on the command line + if(KALYPSSO_CORE_KOKKOS_BACKEND MATCHES "Cuda") + + if((NOT DEFINED Kokkos_ENABLE_HWLOC) OR (NOT Kokkos_ENABLE_HWLOC)) + set(Kokkos_ENABLE_HWLOC + ON + CACHE BOOL "") + endif() + + if((NOT DEFINED Kokkos_ENABLE_OPENMP) OR (NOT Kokkos_ENABLE_OPENMP)) + set(Kokkos_ENABLE_OPENMP + ON + CACHE BOOL "") + endif() + + if((NOT DEFINED Kokkos_ENABLE_CUDA) OR (NOT Kokkos_ENABLE_CUDA)) + set(Kokkos_ENABLE_CUDA + ON + CACHE BOOL "") + endif() + + if((NOT DEFINED Kokkos_ENABLE_CUDA_LAMBDA) OR (NOT Kokkos_ENABLE_CUDA_LAMBDA)) + set(Kokkos_ENABLE_CUDA_LAMBDA + ON + CACHE BOOL "") + endif() + + if((NOT DEFINED Kokkos_ENABLE_CUDA_CONSTEXPR) OR (NOT Kokkos_ENABLE_CUDA_CONSTEXPR)) + set(Kokkos_ENABLE_CUDA_CONSTEXPR + ON + CACHE BOOL "") + endif() + + if((NOT DEFINED Kokkos_ENABLE_CUDA_RELOCATABLE_DEVICE_CODE) + OR (NOT Kokkos_ENABLE_CUDA_RELOCATABLE_DEVICE_CODE)) + set(Kokkos_ENABLE_CUDA_RELOCATABLE_DEVICE_CODE + ON + CACHE BOOL "") + endif() + + if((NOT DEFINED Kokkos_ENABLE_CUDA_UVM) OR (NOT Kokkos_ENABLE_CUDA_UVM)) + set(Kokkos_ENABLE_CUDA_UVM + OFF + CACHE BOOL "") + endif() + + # Note : cuda architecture will probed by kokkos cmake configure + + elseif(KALYPSSO_CORE_KOKKOS_BACKEND MATCHES "HIP") + + message(FATAL_ERROR "[kalypsso-core / kokkos] HIP backend to supported yet") + + if(NOT KALYPSSO_CORE_ENABLE_GPU_HIP) + message( + FATAL_ERROR + "[kalypsso-core / kokkos] You can't use Kokkos::HIP backend if KALYPSSO_CORE_ENABLE_GPU_HIP is OFF" + ) + endif() + + if((NOT DEFINED Kokkos_ENABLE_HWLOC) OR (NOT Kokkos_ENABLE_HWLOC)) + set(Kokkos_ENABLE_HWLOC + ON + CACHE BOOL "") + endif() + + if((NOT DEFINED Kokkos_ENABLE_OPENMP) OR (NOT Kokkos_ENABLE_OPENMP)) + set(Kokkos_ENABLE_OPENMP + ON + CACHE BOOL "") + endif() + + if((NOT DEFINED Kokkos_ENABLE_HIP) OR (NOT Kokkos_ENABLE_HIP)) + set(Kokkos_ENABLE_HIP + ON + CACHE BOOL "") + endif() + + elseif(KALYPSSO_CORE_KOKKOS_BACKEND MATCHES "OpenMP") + + if((NOT DEFINED Kokkos_ENABLE_HWLOC) OR (NOT Kokkos_ENABLE_HWLOC)) + set(Kokkos_ENABLE_HWLOC + ON + CACHE BOOL "") + endif() + + if((NOT DEFINED Kokkos_ENABLE_OPENMP) OR (NOT Kokkos_ENABLE_OPENMP)) + set(Kokkos_ENABLE_OPENMP + ON + CACHE BOOL "") + endif() + + elseif(KALYPSSO_CORE_KOKKOS_BACKEND MATCHES "Undefined") + + message( + FATAL_ERROR "[kalypsso-core / kokkos] You must chose a valid KALYPSSO_CORE_KOKKOS_BACKEND !") + + endif() + + # find_package(Git REQUIRED) + include(FetchContent) + + if(KALYPSSO_CORE_KOKKOS_USE_GIT) + FetchContent_Declare( + kokkos_external + SYSTEM + GIT_REPOSITORY https://github.com/kokkos/kokkos.git + GIT_TAG 4.2.00) + else() + FetchContent_Declare( + kokkos_external + SYSTEM + # URL https://github.com/kokkos/kokkos/archive/refs/tags/4.2.00.tar.gz + SOURCE_DIR ${PROJECT_SOURCE_DIR}/external/kokkos) + endif() + + # Import kokkos targets (download, and call add_subdirectory) + FetchContent_MakeAvailable(kokkos_external) + + if(TARGET Kokkos::kokkos) + message("[kalypsso-core / kokkos] Kokkos found (using FetchContent)") + set(KALYPSSO_CORE_KOKKOS_FOUND True) + set(HAVE_KOKKOS 1) + else() + message( + "[kalypsso-core / kokkos] we shouldn't be here. We've just integrated kokkos build into kalypsso-core build !" + ) + endif() + + set(KALYPSSO_CORE_KOKKOS_BUILTIN TRUE) + +else() + + # + # check if an already installed kokkos exists + # + find_package(Kokkos 4.0.00 CONFIG REQUIRED) + + if(TARGET Kokkos::kokkos) + + kokkos_check(DEVICES "OpenMP") + + if(KALYPSSO_CORE_ENABLE_GPU_CUDA) + # kokkos_check is defined in KokkosConfigCommon.cmake + kokkos_check(DEVICES "Cuda") + kokkos_check(OPTIONS CUDA_LAMBDA) + kokkos_check(OPTIONS CUDA_CONSTEXPR) + elseif(KALYPSSO_CORE_ENABLE_GPU_HIP) + # TODO + kokkos_check(DEVICES "HIP") + endif() + + message("[kalypsso-core / kokkos] Kokkos found via find_package") + set(KALYPSSO_CORE_KOKKOS_FOUND True) + set(HAVE_KOKKOS 1) + + else() + + message( + FATAL_ERROR + "[kalypsso-core / kokkos] Kokkos is required but not found by find_package. Please adjust your env variable CMAKE_PREFIX_PATH (or Kokkos_ROOT) to where Kokkos is installed on your machine !" + ) + + endif() + +endif() diff --git a/cmake/build_or_find_nvtx3.cmake b/cmake/build_or_find_nvtx3.cmake new file mode 100644 index 0000000..46303de --- /dev/null +++ b/cmake/build_or_find_nvtx3.cmake @@ -0,0 +1,84 @@ +# +# Nvtx can be used even if Kokkos::Cuda backend is not activated, for a pure CPU run. All we need to +# perform tracing is to run on a platform where cuda driver and nsys profile/tracing tools are +# installed +# + +# +# Nvtx is already shipped with nvcc toolkit and nvhpc compiler, but we want to be able to instrument +# code event for pure CPU build and run; so we need a way to provide nvtx when not building with +# nvcc or nvc++ +# + +# 1. if using an Nvidia compiler, we don't need to download nvtx sources (they are already available) +# 2. else we need to download nvtx sources to enable NVTX annotations + +# +# Option to use git (instead of tarball release) for downloading nvtx +# +option( + KALYPSSO_CORE_NVTX_USE_GIT + "Turn ON if you want to use git (instead of archive file) to download nvtx sources (default: ON)" + ON) + +set(KALYPSSO_CORE_USE_NVTX_FROM_GITHUB FALSE) +set(KALYPSSO_CORE_USE_NVTX3 OFF) + +# If use requested NVTX annotations, we need to check which compiler is used. If compiler is not an +# nvidia compiler, we need to download nvtx sources +if(KALYPSSO_CORE_NVTX_ANNOTATION_ENABLE) + + # check if compiler is nvcc or nvc++, nvtx is available + if(CMAKE_CXX_COMPILER_ID MATCHES "NVHPC" OR KOKKOS_CXX_COMPILER_ID MATCHES "NVIDIA") + message("[kalypsso-core / nvtx] NVTX found, using a nvidia compiler") + set(KALYPSSO_CORE_NVTX3_FOUND TRUE) + else() + + message("[kalypsso-core / nvtx] Downloading nvtx sources") + + set_property(DIRECTORY PROPERTY EP_BASE ${CMAKE_BINARY_DIR}/external) + + include(FetchContent) + + if(KALYPSSO_CORE_NVTX_USE_GIT) + FetchContent_Declare( + nvtx_external + SYSTEM + GIT_REPOSITORY https://github.com/nvidia/nvtx.git + GIT_TAG release-v3 + SOURCE_SUBDIR c) + else() + FetchContent_Declare( + nvtx_external + SYSTEM + URL https://github.com/NVIDIA/NVTX/archive/refs/tags/v3.1.0.tar.gz) + endif() + + # Import nvtx targets (download, and call add_subdirectory) + FetchContent_MakeAvailable(nvtx_external) + + if(TARGET nvtx3-cpp) + message("[kalypsso-core / nvtx] NVTX found (using FetchContent)") + set(KALYPSSO_CORE_NVTX3_FOUND True) + else() + message( + "[kalypsso-core / nvtx] we shouldn't be here. We've just integrated nvtx build into kalypsso-core build !" + ) + endif() + + set(KALYPSSO_CORE_USE_NVTX_FROM_GITHUB TRUE) + + endif() + +endif(KALYPSSO_CORE_NVTX_ANNOTATION_ENABLE) + +if(KALYPSSO_CORE_NVTX3_FOUND) + set(KALYPSSO_CORE_USE_NVTX3 ON) +endif() + +# symbol KALYPSSO_CORE_NVTX_ANNOTATION_ENABLED is used to feed kalypsso_core_config.h.cmake +if(KALYPSSO_CORE_NVTX_ANNOTATION_ENABLE AND KALYPSSO_CORE_NVTX3_FOUND) + set(KALYPSSO_CORE_NVTX_ANNOTATION_ENABLED TRUE) +else() + set(KALYPSSO_CORE_NVTX_ANNOTATION_ENABLED FALSE) +endif() diff --git a/cmake/build_or_find_p4est.cmake b/cmake/build_or_find_p4est.cmake new file mode 100644 index 0000000..50df03c --- /dev/null +++ b/cmake/build_or_find_p4est.cmake @@ -0,0 +1,134 @@ +# +# find or build p4est +# + +# default value is OFF +option(KALYPSSO_CORE_BUILD_P4EST "Building p4est" OFF) + +# build p4est ? +if(KALYPSSO_CORE_BUILD_P4EST) + + message("[kalypsso-core] Building p4est from source") + + set(DEFAULT_BUILD_TYPE_P4EST "Release") + if(NOT CMAKE_BUILD_TYPE_P4EST) + message(STATUS "Setting p4est build type to 'Release' as none was specified.") + set(CMAKE_BUILD_TYPE_P4EST + "${DEFAULT_BUILD_TYPE_P4EST}" + CACHE + STRING + "Choose the type of build, options are: Debug, Release, RelWithDebInfo and MinSizeRel." + FORCE) + # Set the possible values of build type for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE_P4EST PROPERTY STRINGS "Debug" "Release" "MinSizeRel" + "RelWithDebInfo") + endif() + + set_property(DIRECTORY PROPERTY EP_BASE ${CMAKE_BINARY_DIR}/external/p4est) + + # gnu compatibility, see https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html this is + # used to retrieve portable install paths e.g. some platforms install libraries in lib, others in + # lib64 + include(GNUInstallDirs) + + # enforce a local build of p4est + set(P4EST_INSTALL_PREFIX ${PROJECT_BINARY_DIR}/external/install/p4est) + set(P4EST_INCLUDEDIR ${P4EST_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}) + + # + # note about p4est build system: + # + # * old versions p4est were not following GnuInstallDirs standards. p4est was always selecting to + # install libraries in lib, whereas GnuInstallDirs will chose to install in a directory which OS + # dependent, e.g. lib64 on RedHat like system and lib in Debian-like systems. + # + # * newer p4est release v2.8.7, p4est is not using GnuInstallDirs standard + # + set(P4EST_LIBDIR ${P4EST_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}) + # set(P4EST_LIBDIR ${P4EST_INSTALL_PREFIX}/lib) + + # set minimal cmake options don't build zlib, just use the one from system + set(CMAKE_P4EST_ARGS + -DCMAKE_INSTALL_PREFIX:PATH=${P4EST_INSTALL_PREFIX} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE_P4EST} + -DBUILD_SHARED_LIBS=ON + -DP4EST_ENABLE_BUILD_2D=ON + -DP4EST_USE_SYSTEM_SC=OFF + -Denable_p8est=ON + -Dvtk_binary=ON + -Dzlib=OFF) + + if(KALYPSSO_CORE_USE_MPI) + find_package(MPI COMPONENTS C CXX) + if(MPI_FOUND) + list(APPEND CMAKE_P4EST_ARGS -DSC_ENABLE_MPI=ON) + else(MPI_FOUND) + message(FATAL_ERROR "MPI was requested but not found.") + endif(MPI_FOUND) + else(KALYPSSO_CORE_USE_MPI) + list(APPEND CMAKE_P4EST_ARGS -DSC_ENABLE_MPI=OFF) + endif(KALYPSSO_CORE_USE_MPI) + + # + # Make ExternalProject macro available + # + include(ExternalProject) + + # cmake-format: off + ExternalProject_Add( + p4est_external + # GIT_REPOSITORY https://github.com/cburstedde/p4est.git + # GIT_TAG v${${TOP_PROJECT_NAME}_P4EST_USE_GIT_P4EST_VERSION} + # GIT_REPOSITORY https://github.com/pkestene/p4est.git + # GIT_TAG 2.8.5-ahead + SOURCE_DIR ${PROJECT_SOURCE_DIR}/external/p4est + CMAKE_ARGS ${CMAKE_P4EST_ARGS} + BUILD_COMMAND ${CMAKE_MAKE_PROGRAM} -j 8 + LOG_CONFIGURE 1 + LOG_BUILD 1 + LOG_INSTALL 1) + # cmake-format: on + + # work around to prevent cmake to complain about non existing path + file(MAKE_DIRECTORY ${P4EST_INCLUDEDIR}) + + # + # need to import target, so that we can build kalypsso-core + # + + # ##### SC ##### + add_library(sc SHARED IMPORTED GLOBAL) + add_dependencies(sc p4est_external) + + set_target_properties(sc PROPERTIES IMPORTED_LOCATION "${P4EST_LIBDIR}/libsc.so" + INTERFACE_INCLUDE_DIRECTORIES ${P4EST_INCLUDEDIR}) + + # create library alias + add_library(SC::SC ALIAS sc) + + # ##### P4EST ##### + add_library(p4est SHARED IMPORTED GLOBAL) + add_dependencies(p4est p4est_external) + + set_target_properties( + p4est + PROPERTIES IMPORTED_LOCATION "${P4EST_LIBDIR}/libp4est.so" + INTERFACE_INCLUDE_DIRECTORIES ${P4EST_INCLUDEDIR} + INTERFACE_LINK_LIBRARIES SC::SC) + + # create library alias + add_library(P4EST::P4EST ALIAS p4est) + + # p4est is found ! + set(P4EST_FOUND true) + + set(USE_P4EST_BUILTIN TRUE) + +else(KALYPSSO_CORE_BUILD_P4EST) + + # try to detect P4EST using env variable P4EST_ROOT + find_package(P4EST CONFIG REQUIRED) + find_package(SC CONFIG REQUIRED) + set(USE_P4EST_BUILTIN FALSE) + +endif(KALYPSSO_CORE_BUILD_P4EST) diff --git a/cmake/build_or_find_spdlog.cmake b/cmake/build_or_find_spdlog.cmake new file mode 100644 index 0000000..b908da9 --- /dev/null +++ b/cmake/build_or_find_spdlog.cmake @@ -0,0 +1,127 @@ +# +# find or build spdlog +# +# - spdlog is not genuinely required but we strongly recommend to use it +# - if spdlog is not detected, you can always use build-in (sources available in git submodule) +# just use cmake option \"-DKALYPSSO_CORE_BUILD_SPDLOG=ON\" to enable it +# + +# default value is OFF +option(KALYPSSO_CORE_BUILD_SPDLOG "Building spdlog" OFF) + +# build spdlog ? +if(KALYPSSO_CORE_BUILD_SPDLOG) + + # enforce a local build of spdlog + message("[kalypsso-core] Building spdlog from source") + + set_property(DIRECTORY PROPERTY EP_BASE ${CMAKE_BINARY_DIR}/external/spdlog) + + # gnu compatibility, see https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html this is + # used to retrieve portable install paths e.g. some platforms install libraries in lib, others in + # lib64 + include(GNUInstallDirs) + + set(SPDLOG_INSTALL_PREFIX ${PROJECT_BINARY_DIR}/external/install/spdlog) + set(SPDLOG_INCLUDEDIR ${SPDLOG_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}) + set(SPDLOG_LIBDIR ${SPDLOG_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}) + + # set minimal cmake options + set(CMAKE_SPDLOG_ARGS + -DCMAKE_INSTALL_PREFIX:PATH=${SPDLOG_INSTALL_PREFIX} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_VERBOSE_BUILD:BOOL=TRUE -DSPDLOG_BUILD_SHARED:BOOL=ON) + + # + # Make ExternalProject macro available + # + include(ExternalProject) + + ExternalProject_Add( + spdlog_external + # GIT_REPOSITORY https://github.com/gabime/spdlog.git GIT_TAG v1.11.0 + SOURCE_DIR ${PROJECT_SOURCE_DIR}/external/spdlog + CMAKE_ARGS ${CMAKE_SPDLOG_ARGS} + BUILD_COMMAND ${CMAKE_MAKE_PROGRAM} VERBOSE=1 -j 8 + LOG_CONFIGURE 1 + LOG_BUILD 1 + LOG_INSTALL 1) + + # work around to prevent cmake to complain about non existing path + file(MAKE_DIRECTORY ${SPDLOG_INCLUDEDIR}) + + # + # need to import target, so that we can build kalypsso-core + # + + # ##### SPDLOG ##### + add_library(spdlog SHARED IMPORTED GLOBAL) + add_dependencies(spdlog spdlog_external) + + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set(SPDLOG_LIBNAME "libspdlogd.so") + else() + set(SPDLOG_LIBNAME "libspdlog.so") + endif() + + set_target_properties(spdlog PROPERTIES IMPORTED_LOCATION "${SPDLOG_LIBDIR}/${SPDLOG_LIBNAME}" + INTERFACE_INCLUDE_DIRECTORIES ${SPDLOG_INCLUDEDIR}) + + # create library alias + add_library(spdlog::spdlog ALIAS spdlog) + + # spdlog is found ! + set(KALYPSSO_CORE_USE_SPDLOG ON) + set(USE_SPDLOG_BUILTIN TRUE) + + # get spdlog version / tag + find_package(Git QUIET) + + if(GIT_FOUND) + execute_process( + COMMAND ${GIT_EXECUTABLE} describe --abbrev=0 --match=v[0-9]* + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/external/spdlog + OUTPUT_VARIABLE spdlog_VERSION + RESULT_VARIABLE spdlog_VERSION_FOUND + OUTPUT_STRIP_TRAILING_WHITESPACE) + + if(NOT spdlog_VERSION_FOUND EQUAL 0) + set(spdlog_VERSION "Unknown1") + endif() + + else() + + set(spdlog_VERSION "Unknown") + + endif() + +else(KALYPSSO_CORE_BUILD_SPDLOG) + + # + # try to detect SPDLOG using env variable SPDLOG_ROOT + # + find_package(spdlog CONFIG) + + if(spdlog_FOUND) + set(KALYPSSO_CORE_USE_SPDLOG ON) + else() + message( + WARNING "#################################################################################\n" + "spdlog was not found in your system. Re-run cmake configuration with option " + "\"-DKALYPSSO_CORE_BUILD_SPDLOG=ON\" to enable building spdlog from source using " + "git submodule.\n" + "#################################################################################") + endif() + + set(USE_SPDLOG_BUILTIN FALSE) + +endif(KALYPSSO_CORE_BUILD_SPDLOG) + +# default log level in kalypsso-core everything below this value will be removed at compile time +# from the code here we chose the lowest level : everything is kept we can increase this level in a +# different build. +set(KALYPSSO_CORE_LOG_LEVELS SPDLOG_LEVEL_TRACE SPDLOG_LEVEL_DEBUG SPDLOG_LEVEL_INFO + SPDLOG_LEVEL_WARN SPDLOG_LEVEL_ERROR SPDLOG_LEVEL_CRITICAL) +set(KALYPSSO_CORE_LOG_LEVEL + SPDLOG_LEVEL_TRACE + CACHE STRING "Default value for SPDLOG_ACTIVE_LEVEL in spdlog macros") +set_property(CACHE KALYPSSO_CORE_LOG_LEVEL PROPERTY STRINGS ${KALYPSSO_CORE_LOG_LEVELS}) diff --git a/cmake/check_file_exists.cmake b/cmake/check_file_exists.cmake new file mode 100644 index 0000000..353ada8 --- /dev/null +++ b/cmake/check_file_exists.cmake @@ -0,0 +1,16 @@ +# this macro is only meant to be used with add_custom_command, to check if a given file name exist +# at build time (not configure time) +# +# see +# https://stackoverflow.com/questions/18785168/cmake-check-if-file-exists-at-build-time-rather-than-during-cmake-configuration + +string(ASCII 27 Esc) +set(COLOUR_RESET "${Esc}[m") +set(RED "${Esc}[31m") +set(GREEN "${Esc}[32m") + +if(EXISTS ${FileToCheck}) + message("${GREEN}${FileToCheck} exists.${COLOUR_RESET}") +else() + message("${RED}${FileToCheck} doesn't exist.${COLOUR_RESET}") +endif() diff --git a/cmake/check_file_exists_at_build_time.cmake b/cmake/check_file_exists_at_build_time.cmake new file mode 100644 index 0000000..2af8efd --- /dev/null +++ b/cmake/check_file_exists_at_build_time.cmake @@ -0,0 +1,11 @@ +# check_file_exists_at_build_time +function(check_file_exists_at_build_time the_target file_to_check) + + add_custom_command( + TARGET ${the_target} + PRE_BUILD + COMMAND ${CMAKE_COMMAND} -DFileToCheck=${file_to_check} -P + ${CMAKE_SOURCE_DIR}/cmake/check_file_exists.cmake + COMMENT "Checking if ${file_to_check} exists...") + +endfunction() diff --git a/cmake/cmake_utils.cmake b/cmake/cmake_utils.cmake new file mode 100644 index 0000000..8f14cd7 --- /dev/null +++ b/cmake/cmake_utils.cmake @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# adapted from https://github.com/apache/kvrocks/tree/unstable + +include_guard() + +include(FetchContent) + +macro(parse_var arg key value) + string(REGEX REPLACE "^(.+)=(.+)$" "\\1;\\2" REGEX_RESULT ${arg}) + list(GET REGEX_RESULT 0 ${key}) + list(GET REGEX_RESULT 1 ${value}) +endmacro() + +function(FetchContent_MakeAvailableWithArgs dep) + if(NOT ${dep}_POPULATED) + message("Fetching ${dep}...") + FetchContent_Populate(${dep}) + + foreach(arg IN LISTS ARGN) + parse_var(${arg} key value) + set(${key}_OLD ${${key}}) + set(${key} + ${value} + CACHE INTERNAL "") + message("In ${dep}: ${key} set to ${value}") + endforeach() + + add_subdirectory(${${dep}_SOURCE_DIR} ${${dep}_BINARY_DIR} EXCLUDE_FROM_ALL) + + foreach(arg IN LISTS ARGN) + parse_var(${arg} key value) + set(${key} + ${${key}_OLD} + CACHE INTERNAL "") + endforeach() + endif() +endfunction() diff --git a/cmake/config_hdf5.cmake b/cmake/config_hdf5.cmake new file mode 100644 index 0000000..c8aa7a4 --- /dev/null +++ b/cmake/config_hdf5.cmake @@ -0,0 +1,27 @@ +# ################################################################################################## +# HDF5 +# ################################################################################################## +# prefer using parallel HDF5 when build with mpi +if(KALYPSSO_CORE_USE_MPI) + set(HDF5_PREFER_PARALLEL TRUE) +endif(KALYPSSO_CORE_USE_MPI) + +if(KALYPSSO_CORE_USE_HDF5) + find_package( + HDF5 + COMPONENTS C + REQUIRED) + if(HDF5_FOUND) + set(KALYPSSO_CORE_USE_HDF5_VERSION ${HDF5_VERSION}) + include_directories(${HDF5_INCLUDE_DIRS}) + set(MY_HDF5_LIBS hdf5 hdf5_cpp) + if(HDF5_IS_PARALLEL) + set(KALYPSSO_CORE_USE_HDF5_PARALLEL True) + message(STATUS "PARALLEL HDF5 found, version=${KALYPSSO_CORE_USE_HDF5_VERSION}") + else() + message(STATUS "SERIAL HDF5 found, version=${KALYPSSO_CORE_USE_HDF5_VERSION}") + endif() + else() + message(FATAL_ERROR "HDF5 requested but not found.") + endif(HDF5_FOUND) +endif(KALYPSSO_CORE_USE_HDF5) diff --git a/cmake/config_highfive.cmake b/cmake/config_highfive.cmake new file mode 100644 index 0000000..374d5f9 --- /dev/null +++ b/cmake/config_highfive.cmake @@ -0,0 +1,7 @@ +# HighFive is a high level HDF5 wrapper library. If HDF5 is parallel, HighFive will use it; no +# special flags to use. +set(HIGHFIVE_UNIT_TESTS OFF) +set(HIGHFIVE_EXAMPLES OFF) +set(HIGHFIVE_BUILD_DOCS OFF) + +add_subdirectory(external/HighFive SYSTEM) diff --git a/cmake/config_mpi.cmake b/cmake/config_mpi.cmake new file mode 100644 index 0000000..72c502e --- /dev/null +++ b/cmake/config_mpi.cmake @@ -0,0 +1,40 @@ +# ################################################################################################## +# MPI +# ################################################################################################## +if(KALYPSSO_CORE_USE_MPI) + find_package(MPI) + if(MPI_CXX_FOUND) + message(STATUS "MPI support found") + message(STATUS "MPI compile flags: " ${MPI_CXX_COMPILE_FLAGS}) + message(STATUS "MPI include path: " ${MPI_CXX_INCLUDE_PATH}) + message(STATUS "MPI LINK flags path: " ${MPI_CXX_LINK_FLAGS}) + message(STATUS "MPI libraries: " ${MPI_CXX_LIBRARIES}) + + # set(CMAKE_EXE_LINKER_FLAGS ${MPI_CXX_LINK_FLAGS}) + find_program( + OMPI_INFO + NAMES ompi_info + HINTS ${MPI_CXX_LIBRARIES}/../bin) + + # Full command line to probe if cuda support in MPI implementation is enabled ompi_info + # --parsable --all | grep mpi_built_with_cuda_support:value + if(OMPI_INFO) + execute_process(COMMAND ${OMPI_INFO} OUTPUT_VARIABLE _output) + if((_output MATCHES "smcuda") OR (KALYPSSO_CORE_USE_MPI_CUDA_AWARE_ENFORCED)) + message(STATUS "Found OpenMPI with CUDA support built.") + else() + message(WARNING "OpenMPI found, but it is not built with CUDA support.") + add_compile_options(-DMPI_CUDA_AWARE_OFF) + endif() + endif(OMPI_INFO) + + else(MPI_CXX_FOUND) + message( + WARNING "Not compiling with MPI. Suppress this warning with -DKALYPSSO_CORE_USE_MPI=OFF") + set(KALYPSSO_CORE_USE_MPI OFF) + endif(MPI_CXX_FOUND) + + # test if mpi-ext.h is available + include(cmake/CheckMPIFeatures.cmake) + +endif(KALYPSSO_CORE_USE_MPI) diff --git a/cmake/config_pnetcdf.cmake b/cmake/config_pnetcdf.cmake new file mode 100644 index 0000000..5db1fc0 --- /dev/null +++ b/cmake/config_pnetcdf.cmake @@ -0,0 +1,12 @@ +# ################################################################################################## +# PNETCDF +# ################################################################################################## +if(KALYPSSO_CORE_USE_MPI) + if(KALYPSSO_CORE_USE_PNETCDF) + find_package(PNETCDF) + if(PNETCDF_FOUND) + add_compile_options(-DKALYPSSO_CORE_USE_PNETCDF) + include_directories(${PNETCDF_INCLUDE_DIRS}) + endif(PNETCDF_FOUND) + endif(KALYPSSO_CORE_USE_PNETCDF) +endif(KALYPSSO_CORE_USE_MPI) diff --git a/cmake/config_vtk.cmake b/cmake/config_vtk.cmake new file mode 100644 index 0000000..932ceb6 --- /dev/null +++ b/cmake/config_vtk.cmake @@ -0,0 +1,20 @@ +# ################################################################################################## +# VTK configuration tips, see /usr/lib/cmake/vtk-6.3/VTKConfig.cmake +# /usr/lib/cmake/vtk-6.3/UseVTK.cmake +# ################################################################################################## +if(KALYPSSO_CORE_USE_VTK) + # look for VTK only if requested; VTK macro might even be not present on the target platform + find_package(VTK) + + # the following add VTK to all targets + # cmake-format: off + # if(VTK_FOUND) + # include(${VTK_USE_FILE}) + # endif(VTK_FOUND) + # cmake-format: on + if(VTK_FOUND) + message("***VTK FOUND ${VTK_MAJOR_VERSION}.${VTK_MINOR_VERSION}") + else() + message("*** VTK NOT FOUND") + endif() +endif(KALYPSSO_CORE_USE_VTK) diff --git a/cmake/find_python_module.cmake b/cmake/find_python_module.cmake new file mode 100644 index 0000000..7ef8f24 --- /dev/null +++ b/cmake/find_python_module.cmake @@ -0,0 +1,53 @@ +# this macro is borrowed to https://github.com/melvinvermeeren/template_cpp under unlicence +function(find_python_module module module_u) + + # set(MODULE_U ${module_u}) + + if(PY_${module_u}_FOUND) + return() + endif() + + # do not override user-specified values + if(NOT PY_${module_u}_PATH) + # set the default in case nothing is found + set(PY_${module_u}_PATH + "" + CACHE STRING "Path to ${module}." FORCE) + + # A module's location is usually a directory, but for binary modules it's an .so file. + execute_process( + COMMAND "${PYTHON_EXECUTABLE}" "-c" "import os, re, ${module}; print(os.path.dirname(\ + re.compile('/__init__.py.*').sub('',${module}.__file__)))" + RESULT_VARIABLE "PY_${module_u}_RESULT" + OUTPUT_VARIABLE "PY_${module_u}_OUTPUT" + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + + # strip module directories to get the base path abc.foo.bar.xyz -> ... (three dots) + string(REGEX REPLACE "[^\\.]+" "" module_strip "${module}") + string(LENGTH "${module_strip}" module_strip) + # strip ${module_strip} times the final directory from path + if(module_strip GREATER 0) + # CMake's loops are inclusive so start at 1 + foreach(module_strip_i RANGE 1 ${module_strip}) + string(REGEX REPLACE "/[^/]+$" "" "PY_${module_u}_OUTPUT" "${PY_${module_u}_OUTPUT}") + endforeach(module_strip_i) + endif() + + # if the exit code (RESULT) is non-zero python couldn't import module + if(NOT PY_${module_u}_RESULT) + set(PY_${module_u}_PATH + "${PY_${module_u}_OUTPUT}" + CACHE STRING "Path to ${module}." FORCE) + endif() + endif() + + if(NOT PY_${module_u}_PATH) + message(FATAL_ERROR "Could not find ${module}.") + endif() + set(PY_${module_u}_FOUND + ON + CACHE BOOL "Found ${module}.") + mark_as_advanced(PY_${module_u}_FOUND) + message(STATUS "Found ${module}: ${PY_${module_u}_PATH}") + +endfunction(find_python_module) diff --git a/cmake/generate_config_h.cmake b/cmake/generate_config_h.cmake new file mode 100644 index 0000000..93f45d9 --- /dev/null +++ b/cmake/generate_config_h.cmake @@ -0,0 +1,91 @@ +# +# Generate kalypsso_core_config.h and kalypsso_core_version.h +# +set(KALYPSSO_CORE_HAS_GIT_INFO "TRUE") + +find_package(Git QUIET) + +if(GIT_FOUND) + execute_process( + COMMAND ${GIT_EXECUTABLE} describe --abbrev=0 --match=v[0-9]* + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE KALYPSSO_CORE_GIT_TAG + RESULT_VARIABLE KALYPSSO_CORE_GIT_TAG_FOUND + OUTPUT_STRIP_TRAILING_WHITESPACE) + + if(KALYPSSO_CORE_GIT_TAG_FOUND EQUAL 0) + execute_process( + COMMAND ${GIT_EXECUTABLE} reflog -1 "--format=%H" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE KALYPSSO_CORE_GIT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE) + + execute_process( + COMMAND ${GIT_EXECUTABLE} symbolic-ref HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE KALYPSSO_CORE_GIT_HEAD + OUTPUT_STRIP_TRAILING_WHITESPACE) + + execute_process( + COMMAND ${GIT_EXECUTABLE} diff-index --quiet HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + RESULT_VARIABLE KALYPSSO_CORE_GIT_DIFF_STATE + OUTPUT_STRIP_TRAILING_WHITESPACE) + + if(KALYPSSO_CORE_GIT_DIFF_STATE EQUAL 0) + set(KALYPSSO_CORE_GIT_HAS_LOCAL_CHANGES "CLEAN") + set(KALYPSSO_CORE_GIT_IS_CLEAN true) + else() + set(KALYPSSO_CORE_GIT_IS_CLEAN false) + set(KALYPSSO_CORE_GIT_HAS_LOCAL_CHANGES "DIRTY") + endif() + + execute_process( + COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE KALYPSSO_CORE_GIT_BRANCH + OUTPUT_STRIP_TRAILING_WHITESPACE) + + execute_process( + COMMAND ${GIT_EXECUTABLE} config --get branch.${GIT_BRANCH}.remote + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE KALYPSSO_CORE_GIT_REMOTE + OUTPUT_STRIP_TRAILING_WHITESPACE) + + execute_process( + COMMAND ${GIT_EXECUTABLE} config --get remote.${GIT_REMOTE}.url + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE KALYPSSO_CORE_GIT_REMOTE_URL + OUTPUT_STRIP_TRAILING_WHITESPACE) + else() + unset(KALYPSSO_CORE_HAS_GIT_INFO) + endif() +else() + unset(KALYPSSO_CORE_HAS_GIT_INFO) +endif() + +execute_process( + COMMAND date "+%d/%m/%y" + OUTPUT_VARIABLE KALYPSSO_CORE_COMPILE_DATE + OUTPUT_STRIP_TRAILING_WHITESPACE) + +execute_process( + COMMAND date "+%H:%M:%S" + OUTPUT_VARIABLE KALYPSSO_CORE_COMPILE_TIME + OUTPUT_STRIP_TRAILING_WHITESPACE) + +if(CMAKE_BUILD_TYPE STREQUAL "Release") + set(RELEASE_BUILD True) +endif() + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set(KALYPSSO_CORE_ENABLE_DEBUG 1) +endif() + +message("Generating kalypsso_core_version.h") +configure_file(${PROJECT_SOURCE_DIR}/src/kalypsso_core_version.h.in + ${PROJECT_BINARY_DIR}/src/kalypsso_core_version.h) + +message("Generating kalypsso_core_config.h") +configure_file(${PROJECT_SOURCE_DIR}/src/kalypsso/core/kalypsso_core_config.h.cmake.in + ${PROJECT_BINARY_DIR}/src/kalypsso/core/kalypsso_core_config.h @ONLY) diff --git a/cmake/initBuildType.cmake b/cmake/initBuildType.cmake new file mode 100644 index 0000000..798d211 --- /dev/null +++ b/cmake/initBuildType.cmake @@ -0,0 +1,15 @@ +# +# Set default build type to "Release" +# +set(DEFAULT_BUILD_TYPE "Release") +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "Setting build type to '${DEFAULT_BUILD_TYPE}' as none was specified.") + set(CMAKE_BUILD_TYPE + "${DEFAULT_BUILD_TYPE}" + CACHE STRING + "Choose the type of build, options are: Debug, Release, RelWithDebInfo and MinSizeRel." + FORCE) + # Set the possible values of build type for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" + "RelWithDebInfo") +endif() diff --git a/cmake/options.cmake b/cmake/options.cmake new file mode 100644 index 0000000..e2ff2a3 --- /dev/null +++ b/cmake/options.cmake @@ -0,0 +1,68 @@ +# +# Declare optional features +# + +option(KALYPSSO_CORE_USE_MPI "Activate / want MPI build" ON) +option(KALYPSSO_CORE_USE_VTK "Activate / want VTK build" OFF) +option(KALYPSSO_CORE_USE_DOUBLE "build with double precision" ON) +option(KALYPSSO_CORE_USE_HDF5 "build HDF5 input/output support" ON) +option(KALYPSSO_CORE_USE_PNETCDF "build PNETCDF input/output support (MPI required)" OFF) +option(KALYPSSO_CORE_USE_FPE_DEBUG "build with floating point Nan tracing (signal handler)" OFF) +option(KALYPSSO_CORE_USE_MPI_CUDA_AWARE_ENFORCED + "Some MPI cuda-aware implementation are not well detected; use this to enforce" OFF) +option(KALYPSSO_CORE_TIMING_ENABLED "build with run-timing measurements enabled (default ON)" ON) +option(KALYPSSO_CORE_NVTX_ANNOTATION_ENABLE "build with nvtx annotations enabled (default OFF)" OFF) +option(KALYPSSO_CORE_BUILD_PARAVIEW_PLUGIN + "build paraview plugin for loading hdf5 data into a vtkNonOverlappingAMR object." OFF) +option(KALYPSSO_CORE_DEBUG_BOUNDS_CHECK + "Check all array access bounds at runtime (for debug purpose)." OFF) +option(KALYPSSO_CORE_ENABLE_WARNINGS "Enable developer warnings (default ON)" ON) +option(KALYPSSO_CORE_ENABLE_WARNINGS_AS_ERRORS "Treat compiler warnings as errors (default OFF)." + OFF) +option(KALYPSSO_CORE_DISABLE_DEPRECATED_WARNINGS + "Disable deprecated declaration warnings (default OFF)" OFF) +option(KALYPSSO_CORE_USE_CPPTRACE "Use cpptrace to print a stack trace upon failure (default OFF)." + OFF) + +# option (USE_MOOD "build MOOD numerical schemes" OFF) + +# option (USE_SDM "build Spectral Difference Method numerical schemes" OFF) + +# +# documentation related options +# +option(KALYPSSO_CORE_BUILD_DOC "Enable / disable documentation build" OFF) + +# documentation type - the only valid values are : doxygen and mkdocs +if(NOT KALYPSSO_CORE_DOC) + set(KALYPSSO_CORE_DOC + "doxygen" + CACHE STRING "documentation type (doxygen or mkdocs)" FORCE) + set_property(CACHE KALYPSSO_CORE_DOC PROPERTY STRINGS "doxygen" "mkdocs") +endif() + +option(KALYPSSO_CORE_ENABLE_UNIT_TESTING "Enable unit testing" OFF) + +if(${KALYPSSO_CORE_KOKKOS_BACKEND} STREQUAL "Cuda" OR ${KALYPSSO_CORE_KOKKOS_BACKEND} STREQUAL + "HIP") + set(KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE ON) +else() + set(KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE OFF) +endif() + +# use clang-tidy default : OFF but we provide a default config +option(KALYPSSO_CORE_ENABLE_CLANG_TIDY "Enable clang-tidy" OFF) + +if(KALYPSSO_CORE_ENABLE_CLANG_TIDY) + + # BEWARE : kokkos checks are only available when using a special version of clang-tidy see + # https://github.com/kokkos/kokkos-tutorials/blob/main/LectureSeries/KokkosTutorial_07_Tools.pdf + # you will need to re-build clang-tidy from https://github.com/kokkos/llvm-project using branch + # kokkos-ensure-kokkos-function + set(DEFAULT_CLANG_TIDY_CONFIG "clang-tidy;-checks=kokkos-*") + + if(NOT CMAKE_CXX_CLANG_TIDY) + set(CMAKE_CXX_CLANG_TIDY "${DEFAULT_CLANG_TIDY_CONFIG}") + endif() + +endif(KALYPSSO_CORE_ENABLE_CLANG_TIDY) diff --git a/cmake/plantuml.cmake b/cmake/plantuml.cmake new file mode 100644 index 0000000..22cd4d7 --- /dev/null +++ b/cmake/plantuml.cmake @@ -0,0 +1,17 @@ +if(PLANTUML_FOUND) + return() +endif() + +find_program( + PLANTUML_PATH + NAMES plantuml plantuml.bat + DOC "Path to PlantUML wrapper script.") + +if(NOT PLANTUML_PATH) + message(FATAL_ERROR "Could not find PlantUML.") +endif() +set(PLANTUML_FOUND + ON + CACHE BOOL "Found PlantUML.") +mark_as_advanced(PLANTUML_FOUND) +message(STATUS "Found PlantUML: ${PLANTUML_PATH}") diff --git a/cmake/prevent_build_in_source.cmake b/cmake/prevent_build_in_source.cmake new file mode 100644 index 0000000..ff89c14 --- /dev/null +++ b/cmake/prevent_build_in_source.cmake @@ -0,0 +1,20 @@ +# prevent_build_in_source +function(prevent_build_in_source) + + # make sure the user doesn't play dirty with symlinks + get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH) + get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH) + + # disallow in-source builds + if("${srcdir}" STREQUAL "${bindir}") + message("######################################################") + message("# ${PROJECT_NAME} should not be configured and built in the source directory") + message("# You must run cmake from a build directory.") + message("# For example:") + message("# mkdir _build ; cd _build") + message("# run cmake from the build directory.") + message("######################################################") + message(FATAL_ERROR "Quitting configuration") + endif() + +endfunction(prevent_build_in_source) diff --git a/cmake/print_target_properties.cmake b/cmake/print_target_properties.cmake new file mode 100644 index 0000000..97424fc --- /dev/null +++ b/cmake/print_target_properties.cmake @@ -0,0 +1,51 @@ +# this is a utility to explore all possible properties a given target may have see +# https://stackoverflow.com/questions/32183975/how-to-print-all-the-properties-of-a-target-in-cmake + +# +# Get all properties that cmake supports +# +execute_process(COMMAND cmake --help-property-list OUTPUT_VARIABLE CMAKE_PROPERTY_LIST) + +# +# Convert command output into a CMake list +# +string(REGEX REPLACE ";" "\\\\;" CMAKE_PROPERTY_LIST "${CMAKE_PROPERTY_LIST}") +string(REGEX REPLACE "\n" ";" CMAKE_PROPERTY_LIST "${CMAKE_PROPERTY_LIST}") + +# ################################################################################################## +# ################################################################################################## +# ################################################################################################## +function(print_properties) + message("CMAKE_PROPERTY_LIST = ${CMAKE_PROPERTY_LIST}") +endfunction(print_properties) + +# ################################################################################################## +# ################################################################################################## +# ################################################################################################## +function(print_target_properties tgt) + if(NOT TARGET ${tgt}) + message("There is no target named '${tgt}'") + return() + endif() + + foreach(prop ${CMAKE_PROPERTY_LIST}) + string(REPLACE "" "${CMAKE_BUILD_TYPE}" prop ${prop}) + # Fix + # https://stackoverflow.com/questions/32197663/how-can-i-remove-the-the-location-property-may-not-be-read-from-target-error-i + if(prop STREQUAL "LOCATION" + OR prop MATCHES "^LOCATION_" + OR prop MATCHES "_LOCATION$") + continue() + endif() + # message ("Checking ${prop}") + get_property( + propval + TARGET ${tgt} + PROPERTY ${prop} + SET) + if(propval) + get_target_property(propval ${tgt} ${prop}) + message("${tgt} ${prop} = ${propval}") + endif() + endforeach(prop) +endfunction(print_target_properties) diff --git a/cmake/setup_install.cmake b/cmake/setup_install.cmake new file mode 100644 index 0000000..50796aa --- /dev/null +++ b/cmake/setup_install.cmake @@ -0,0 +1,24 @@ +# gnu compatibility : https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html +include(GNUInstallDirs) + +# some important variables +set(INSTALL_BINDIR + ${CMAKE_INSTALL_BINDIR} + CACHE STRING "Installation directory for binaries, relative to ${CMAKE_INSTALL_PREFIX}.") + +set(INSTALL_LIBDIR + ${CMAKE_INSTALL_LIBDIR} + CACHE STRING "Installation directory for libraries, relative to ${CMAKE_INSTALL_PREFIX}.") + +set(INSTALL_INCLUDEDIR + ${CMAKE_INSTALL_INCLUDEDIR} + CACHE STRING "Installation directory for include files, relative to ${CMAKE_INSTALL_PREFIX}.") + +set(INSTALL_PKGCONFIG_DIR + ${CMAKE_INSTALL_LIBDIR}/pkgconfig + CACHE PATH + "Installation directory for pkgconfig (.pc) files, relative to ${CMAKE_INSTALL_PREFIX}.") + +set(INSTALL_CMAKE_DIR + ${CMAKE_INSTALL_LIBDIR}/cmake + CACHE STRING "Installation directory for cmake files, relative to ${CMAKE_INSTALL_PREFIX}.") diff --git a/dependencies/CMakeLists.txt b/dependencies/CMakeLists.txt new file mode 100644 index 0000000..fe0cca8 --- /dev/null +++ b/dependencies/CMakeLists.txt @@ -0,0 +1,97 @@ +cmake_minimum_required(VERSION 3.18) + +# +# Allow find_package() to use _ROOT variables, either cmake variable or environment +# variables see https://cmake.org/cmake/help/latest/policy/CMP0074.html +# +if(NOT CMAKE_VERSION VERSION_LESS 3.12) + cmake_policy(SET CMP0074 NEW) +endif() + +project( + kalypsso_dependencies + LANGUAGES C CXX Fortran + VERSION 0.1 + DESCRIPTION + "kalypsso is a small CFD application built on top of p4est library for adaptive mesh refinement features." +) + +set(TOP_PROJECT_NAME KALYPSSO) +string(TOLOWER ${TOP_PROJECT_NAME} TOP_PROJECT_NAME_LC) + +# +# Enforce user to use a build directory outside of source tree +# +include(../cmake/prevent_build_in_source.cmake) +prevent_build_in_source() + +# +# Setup default install path +# +# Notice: cmake sets the default install directory to /usr/local which is usually not writable by a +# normal user. So we change this default to be ~/.kalypsso_dependencies users can overwrite this +# default by using e.g. "-DCMAKE_INSTALL_PREFIX=/my/custom/dir" +# +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + # will not take effect without FORCE + set(CMAKE_INSTALL_PREFIX + "$ENV{HOME}/.kalypsso_dependencies" + CACHE PATH "Path where kalypsso dependencies will be installed" FORCE) +endif() + +# +# Set default compile optimization flag used for all dependencies. Usually Release is fine, most of +# the time if you need to debug, only kalypsso build need debug. +# +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(DEFAULT_BUILD_TYPE "Release") + message(STATUS "Setting build type to '${DEFAULT_BUILD_TYPE}' as none was specified.") + set(CMAKE_BUILD_TYPE + "${DEFAULT_BUILD_TYPE}" + CACHE STRING + "Choose the type of build, options are: Debug, Release, RelWithDebInfo and MinSizeRel." + FORCE) + # Set the possible values of build type for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" + "RelWithDebInfo") +endif() + +# +# setup the list of dependencies to build +# +include(cmake/build_dependencies.cmake) + +# +# create and install a modulefile to ease the use of kalypsso dependencies +# +include(cmake/modulefiles.cmake) + +# #################### PRINT CONFIGURE STATUS ###################### +message("//===================================================") +message("// ${PROJECT_NAME} build configuration:") +message("// ${PROJECT_NAME} version : ${PROJECT_VERSION}") +message("//===================================================") +message(" CMake version : ${CMAKE_VERSION}") +if(NOT CMAKE_BUILD_TYPE) + message(" CMake build type : NOT SET !") +else() + message(" CMake build type : ${CMAKE_BUILD_TYPE}") +endif() +message(" CMake install prefix : ${CMAKE_INSTALL_PREFIX}") +message(" CMake system processor : ${CMAKE_SYSTEM_PROCESSOR}") +message(" CMake system name (OS) : ${CMAKE_SYSTEM_NAME}") +message("") + +message(STATUS "List of kalypsso dependencies :") +message("") + +message(" ${TOP_PROJECT_NAME}_P4EST_BUILD : ${${TOP_PROJECT_NAME}_P4EST_BUILD}") +message(" ${TOP_PROJECT_NAME}_HDF5_BUILD : ${${TOP_PROJECT_NAME}_HDF5_BUILD}") + +message("") + +message("To actually use these dependencies, just do:") +message("") +message(" module use ${CMAKE_INSTALL_PREFIX}/share/modulefiles") +message(" module load ${PROJECT_NAME}/1.0") +message("") diff --git a/dependencies/Readme.md b/dependencies/Readme.md new file mode 100644 index 0000000..acf6629 --- /dev/null +++ b/dependencies/Readme.md @@ -0,0 +1,63 @@ +# What is `kalypsso_dependencies` + +This is just a small cmake sub-project that only aims at facilitating the build of some optional or required dependencies of kalypsso. + +## Quick start + +Example of use when building only `p4est`: + +```shell +# from kalypsso top-level source directory +mkdir -p _build/dependencies +cd _build/dependencies +cmake -DKALYPSSO_P4EST_BUILD=ON ../../dependencies +make +``` + +By default, all dependencies will be installed in `$HOME/.kalypsso_dependencies`. +You can change that by setting cmake variable `CMAKE_INSTALL_PREFIX`. + +Additionally, a modulefile is also provided; once you built all chosen dependencies, +you just need to do the following: + +```shell +module use ~/.kalypsso_dependencies/share/modulefiles +module use kalypsso_dependencies/1.0 +``` + +and you're good to go building `kalypsso` with this dependencies. + +## List of dependencies + +Here is the list of dependencies that can be built: + +- [p4est](https://github.com/cburstedde/p4est) +- [hdf5](https://www.hdfgroup.org/solutions/hdf5/) + +## Some features + +### Specify source to compile + +For most dependencies, you can chose which source you want to build, either a git tag or release archive (remote or local file). + +E.g. if your already have downloaded a p4est archive, you can configure `kalypsso_dependencies` using cmake var `KALYPSSO_P4EST_SOURCE_ARCHIVE` to specify the tarball path on your local machine. + +```shell +# from kalypsso top-level source directory +mkdir -p _build/dependencies +cd _build/dependencies +cmake -DKALYPSSO_P4EST_BUILD=ON -DKALYPSSO_P4EST_SOURCE_ARCHIVE=$HOME/p4est-2.8.5.tar.gz -DKALYPSSO_HDF5_BUILD=ON ../../dependencies +make +``` + +Warning: don't use `~` in the archive filepath, because this is passed to cmake as a string, that can either be a local filesystem path or a remote url; just the full path, of `$HOME`. + +### Recompile a dependency + +Suppose you want to recompile `p4est`, just remove directory `external/p4est` and re-execute cmake configure and then `make` again. + +We also advise you to also clean the install location (default is $HOME/.kalypsso_dependencies), to make sure to restart in a clean directory. + +### dependencies of dependencies + +Currently all dependencies listed above are independent one for another, but we could have the situation where two dependencies actually depend from a third one. In this, this actually possible since cmake macro `ExternalProject_Add` can manage dependencies upon completion of another `ExternalProject_Add`. diff --git a/dependencies/cmake/build_dependencies.cmake b/dependencies/cmake/build_dependencies.cmake new file mode 100644 index 0000000..23594b3 --- /dev/null +++ b/dependencies/cmake/build_dependencies.cmake @@ -0,0 +1,13 @@ +message(STATUS "Building kalypsso dependencies") + +# +# Do we want to build p4est (https://github.com/cburstedde/p4est) ? +# +option(KALYPSSO_P4EST_BUILD "Turn ON if you want to build p4est (default: OFF)" OFF) +include(cmake/build_p4est.cmake) + +# +# Do we want to build hdf5-parallel (https://github.com/HDFGroup/hdf5) ? +# +option(KALYPSSO_HDF5_BUILD "Turn ON if you want to build hdf5-parallel (default: OFF)" OFF) +include(cmake/build_hdf5.cmake) diff --git a/dependencies/cmake/build_hdf5.cmake b/dependencies/cmake/build_hdf5.cmake new file mode 100644 index 0000000..e8c9f86 --- /dev/null +++ b/dependencies/cmake/build_hdf5.cmake @@ -0,0 +1,193 @@ +# +# Option to use git repository (instead of tarball release) for downloading Hdf5 +# +option(${TOP_PROJECT_NAME}_HDF5_USE_GIT + "Turn ON if you want to use git to download HDF5 sources (default: OFF)" OFF) + +option(${TOP_PROJECT_NAME}_HDF5_USE_AUTOTOOLS + "Turn ON if you want to build hdf5 with autotools (default: OFF)" OFF) + +set(CMAKE_BUILD_TYPE_HDF5 + "Release" + CACHE STRING "hdf5 build type") +if(NOT CMAKE_BUILD_TYPE_HDF5) + message(STATUS "Setting hdf5 build type to 'Release' as none was specified.") + set(CMAKE_BUILD_TYPE_HDF5 + "Release" + CACHE STRING + "Choose the type of build, options are: Debug, Release, RelWithDebInfo and MinSizeRel." + FORCE) + # Set the possible values of build type for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE_HDF5 PROPERTY STRINGS "Debug" "Release" "MinSizeRel" + "RelWithDebInfo") +endif() + +# +# check if user requested a build of HDF5 +# +if(${TOP_PROJECT_NAME}_HDF5_BUILD) + + message("[${TOP_PROJECT_NAME} / HDF5] Building HDF5 from source") + + set_property(DIRECTORY PROPERTY EP_BASE ${CMAKE_BINARY_DIR}/external/hdf5) + + # set install path + set(HDF5_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}) + + # + include(ExternalProject) + + # gnu compatibility, see https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html this is + # used to retrieve portable install paths e.g. some platforms install libraries in lib, others in + # lib64 + include(GNUInstallDirs) + + # set minimal autotools options + set(AUTOTOOLS_HDF5_ARGS + --prefix=${HDF5_INSTALL_PREFIX} + --with-pthread + --enable-linux-lfs + --enable-shared + --enable-build-mode=production + --disable-sharedlib-rpath + --with-zlib + --with-default-api-version=v18 + --with-szlib) + + # append options for a MPI/parallel-hdf5 build + list( + APPEND + AUTOTOOLS_HDF5_ARGS + --enable-unsupported + --enable-cxx + --enable-parallel=yes + CC=mpicc + CXX=mpicxx + FC=mpif90 + F9X=mpif90) + + # set minimal cmake options + set(CMAKE_HDF5_ARGS + -DCMAKE_INSTALL_PREFIX:PATH=${HDF5_INSTALL_PREFIX} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE_HDF5} -DDEFAULT_API_VERSION=v18 + -DHDF5_ENABLE_PARALLEL=ON -DHDF5_ENABLE_SZIP_SUPPORT=OFF -DHDF5_ENABLE_Z_LIB_SUPPORT=ON) + + # + # there are 2 ways to retrieve HDF5 sources: + # + # 1. Use git to perform a local clone of the github HDF5 repository + # 2. Use an archive file, variable ${TOP_PROJECT_NAME}_HDF5_SOURCE_ARCHIVE is used to specify the + # full path or remote URL to that archive, this variable can be set on the cmake command line, + # By default we download + # https://support.hdfgroup.org/ftp/HDF5/releases/hdf5-1.10/hdf5-1.10.9/src/hdf5-1.10.9.tar.gz + # But you can also use a local file, just add + # `-D${TOP_PROJECT_NAME}_HDF5_SOURCE_ARCHIVE=$HOME/hdf5-x.y.z.tar.gz` on the configure command + # line + + # define the default HDF5 version used to download source from github + if(NOT DEFINED DEP_${TOP_PROJECT_NAME}_HDF5_USE_GIT_HDF5_VERSION) + set(DEP_${TOP_PROJECT_NAME}_HDF5_USE_GIT_HDF5_VERSION 1.10.9) + endif() + + # + # source archive setup: git repository or archive file (remote or local) + # + if(NOT DEFINED ${TOP_PROJECT_NAME}_HDF5_SOURCE_ARCHIVE) + set(DEP_${TOP_PROJECT_NAME}_HDF5_SOURCE_ARCHIVE + https://support.hdfgroup.org/ftp/HDF5/releases/hdf5-1.10/hdf5-1.10.9/src/hdf5-1.10.9.tar.gz + CACHE STRING "HDF5 source archive (URL or local filepath).") + else() + if(NOT EXISTS "${${TOP_PROJECT_NAME}_HDF5_SOURCE_ARCHIVE}") + message( + WARNING + "HDF5 source archive ${${TOP_PROJECT_NAME}_HDF5_SOURCE_ARCHIVE} doesn't exist on local filesystem. If you manually specified a remote URL or use GIT, you can ignore this warning." + ) + endif() + endif() + + message( + STATUS "HDF5 build with mpi support using autotools command line : ${AUTOTOOLS_HDF5_ARGS}") + + # if we chose to build p4est, let hdf5 depends upon it (because currently p4est also builds zlib, + # so we want to use that version of zlib) + set(DEPENDENCIES_HDF5) + if(${TOP_PROJECT_NAME}_P4EST_BUILD) + list(APPEND DEPENDENCIES_HDF5 p4est_external) + endif() + + # + # By default use cmake build + # + if(${TOP_PROJECT_NAME}_HDF5_USE_AUTOTOOLS) + + if(${TOP_PROJECT_NAME}_HDF5_USE_GIT) + message( + "Building HDF5 using github repository with tag version ${DEP_${TOP_PROJECT_NAME}_HDF5_USE_GIT_HDF5_VERSION}" + ) + ExternalProject_Add( + hdf5_external + DEPENDS ${DEPENDENCIES_HDF5} + GIT_REPOSITORY https://github.com/HDFGroup/hdf5.git + GIT_TAG ${DEP_${TOP_PROJECT_NAME}_HDF5_USE_GIT_HDF5_VERSION} + UPDATE_DISCONNECTED true + CONFIGURE_HANDLED_BY_BUILD true + CONFIGURE_COMMAND touch /.abi_stamp + COMMAND autoreconf -i + COMMAND /configure ${AUTOTOOLS_HDF5_ARGS} + BUILD_COMMAND ${CMAKE_MAKE_PROGRAM} -j 8 + LOG_DOWNLOAD 1 + LOG_CONFIGURE 1 + LOG_BUILD 1 + LOG_INSTALL 1) + else() + message("Building HDF5 using source archive ${${TOP_PROJECT_NAME}_HDF5_SOURCE_ARCHIVE}") + ExternalProject_Add( + hdf5_external + URL ${${TOP_PROJECT_NAME}_HDF5_SOURCE_ARCHIVE} + UPDATE_DISCONNECTED true + CONFIGURE_HANDLED_BY_BUILD true + CONFIGURE_COMMAND /configure ${AUTOTOOLS_HDF5_ARGS} + BUILD_COMMAND ${CMAKE_MAKE_PROGRAM} -j 8 + LOG_DOWNLOAD 1 + LOG_CONFIGURE 1 + LOG_BUILD 1 + LOG_INSTALL 1) + endif() + + else() + # use cmake + if(${TOP_PROJECT_NAME}_HDF5_USE_GIT) + message( + "Building HDF5 using github repository with tag version ${DEP_${TOP_PROJECT_NAME}_HDF5_USE_GIT_HDF5_VERSION}" + ) + ExternalProject_Add( + hdf5_external + DEPENDS ${DEPENDENCIES_HDF5} + GIT_REPOSITORY https://github.com/HDFGroup/hdf5.git + GIT_TAG ${DEP_${TOP_PROJECT_NAME}_HDF5_USE_GIT_HDF5_VERSION} + UPDATE_DISCONNECTED true + CMAKE_ARGS ${CMAKE_HDF5_ARGS} + BUILD_COMMAND ${CMAKE_MAKE_PROGRAM} -j 8 + LOG_DOWNLOAD 1 + LOG_CONFIGURE 1 + LOG_BUILD 1 + LOG_INSTALL 1) + else() + message("Building HDF5 using source archive ${${TOP_PROJECT_NAME}_HDF5_SOURCE_ARCHIVE}") + ExternalProject_Add( + hdf5_external + URL ${${TOP_PROJECT_NAME}_HDF5_SOURCE_ARCHIVE} + UPDATE_DISCONNECTED true + CMAKE_ARGS ${CMAKE_HDF5_ARGS} + BUILD_COMMAND ${CMAKE_MAKE_PROGRAM} -j 8 + LOG_DOWNLOAD 1 + LOG_CONFIGURE 1 + LOG_BUILD 1 + LOG_INSTALL 1) + endif() + + endif() + + message("[${TOP_PROJECT_NAME} / HDF5] HDF5 will be installed into ${HDF5_INSTALL_PREFIX}") + +endif(${TOP_PROJECT_NAME}_HDF5_BUILD) diff --git a/dependencies/cmake/build_p4est.cmake b/dependencies/cmake/build_p4est.cmake new file mode 100644 index 0000000..7d83468 --- /dev/null +++ b/dependencies/cmake/build_p4est.cmake @@ -0,0 +1,187 @@ +# +# Option to use git (instead of tarball release) for downloading p4est +# +# Turn ON by default, to retrieve p4est and submodule sc. +# +option(${TOP_PROJECT_NAME}_P4EST_USE_GIT + "Turn ON if you want to use git to download p4est sources (default: ON)" ON) + +# +# This option is only as a "fix" because currently cmake enforce downloading zlib sources, while the +# autotools build doesn't; so for now just keep using the autotools build. +# +option(${TOP_PROJECT_NAME}_P4EST_USE_AUTOTOOLS + "Turn ON if you want to build p4est with autotools (default: OFF)" OFF) + +set(CMAKE_BUILD_TYPE_P4EST + "Release" + CACHE STRING "p4est build type") +if(NOT CMAKE_BUILD_TYPE_P4EST) + message(STATUS "Setting p4est build type to 'Release' as none was specified.") + set(CMAKE_BUILD_TYPE_P4EST + "Release" + CACHE STRING + "Choose the type of build, options are: Debug, Release, RelWithDebInfo and MinSizeRel." + FORCE) + # Set the possible values of build type for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE_P4EST PROPERTY STRINGS "Debug" "Release" "MinSizeRel" + "RelWithDebInfo") +endif() + +# +# check if user requested a build of p4est +# +if(${TOP_PROJECT_NAME}_P4EST_BUILD) + + message("[kalypsso / p4est] Building p4est from source") + + set_property(DIRECTORY PROPERTY EP_BASE ${CMAKE_BINARY_DIR}/external/p4est) + + # set install path + set(P4EST_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}) + + # set minimal autotools options + set(AUTOTOOLS_P4EST_ARGS --prefix=${P4EST_INSTALL_PREFIX} --enable-mpi --enable-mpiio + --enable-shared) + + # set minimal cmake options don't build zlib, just use the one from system + set(CMAKE_P4EST_ARGS + -DCMAKE_INSTALL_PREFIX:PATH=${P4EST_INSTALL_PREFIX} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE_P4EST} + -DBUILD_SHARED_LIBS=ON + -DP4EST_ENABLE_BUILD_2D=ON + -Dsc_external=ON + -Denable_p8est=ON + -Dvtk_binary=ON + -Dzlib=OFF) + + find_package(MPI COMPONENTS Fortran C CXX) + if(MPI_FOUND) + list(APPEND CMAKE_P4EST_ARGS -Dmpi=ON) + else() + message(FATAL_ERROR "We want MPI when using p4est inside kalypsso.") + endif() + + # + # Make ExternalProject macro available + # + include(ExternalProject) + + # gnu compatibility, see https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html this is + # used to retrieve portable install paths e.g. some platforms install libraries in lib, others in + # lib64 + include(GNUInstallDirs) + + # + # there are 2 ways to retrieve p4est sources: + # + # 1. Use git to perform a local clone of the github p4est repository + # 2. Use an archive file, variable DEP_${TOP_PROJECT_NAME}_P4EST_SOURCE_ARCHIVE is used to specify + # the full path or remote URL to that archive. This variable can be set on the cmake command + # line, e.g. `-DDEP_${TOP_PROJECT_NAME}_P4EST_SOURCE_ARCHIVE=$HOME/p4est-2.8.5.tar.gz` Be + # careful to provide a tarball with p4est and sc sources. + + # define the default p4est version used to download source from github + if(NOT DEFINED DEP_${TOP_PROJECT_NAME}_P4EST_USE_GIT_P4EST_VERSION) + set(DEP_${TOP_PROJECT_NAME}_P4EST_USE_GIT_P4EST_VERSION 2.8.5) + endif() + + # + if(NOT DEFINED DEP_${TOP_PROJECT_NAME}_P4EST_SOURCE_ARCHIVE) + set(DEP_${TOP_PROJECT_NAME}_P4EST_SOURCE_ARCHIVE + https://p4est.github.io/release/p4est-2.8.5.5-9ddbb.tar.gz + CACHE STRING "p4est source archive (URL or local filepath).") + else() + if(NOT EXISTS "${DEP_${TOP_PROJECT_NAME}_P4EST_SOURCE_ARCHIVE}") + message( + WARNING + "P4EST source archive ${DEP_${TOP_PROJECT_NAME}_P4EST_SOURCE_ARCHIVE} doesn't exist on local filesystem. If you manually specified a remote URL or use GIT, you can ignore this warning." + ) + endif() + endif() + + message(STATUS "p4est build with cmake command line : ${CMAKE_P4EST_ARGS}") + + # + # By default use autotools build + # + if(${TOP_PROJECT_NAME}_P4EST_USE_AUTOTOOLS) + # use Autotools + if(${TOP_PROJECT_NAME}_P4EST_USE_GIT) + message( + "Building p4est with autotools using github repository with tag version ${DEP_${TOP_PROJECT_NAME}_P4EST_USE_GIT_P4EST_VERSION}" + ) + # cmake-format: off + ExternalProject_Add( + p4est_external + # GIT_REPOSITORY https://github.com/cburstedde/p4est.git + # GIT_TAG v${DEP_${TOP_PROJECT_NAME}_P4EST_USE_GIT_P4EST_VERSION} + GIT_REPOSITORY https://github.com/pkestene/p4est.git + GIT_TAG 2.8.5-ahead + UPDATE_DISCONNECTED true + CONFIGURE_HANDLED_BY_BUILD true + CONFIGURE_COMMAND touch /.p4est_stamp + COMMAND ./bootstrap + COMMAND /configure ${AUTOTOOLS_P4EST_ARGS} + BUILD_COMMAND ${CMAKE_MAKE_PROGRAM} -j 8 + BUILD_IN_SOURCE 1 + LOG_DOWNLOAD 1 + LOG_CONFIGURE 1 + LOG_BUILD 1 + LOG_INSTALL 1) + # cmake-format: on + else() + message( + "Building p4est with autotools using source archive ${DEP_${TOP_PROJECT_NAME}_P4EST_SOURCE_ARCHIVE}" + ) + ExternalProject_Add( + p4est_external + URL ${DEP_${TOP_PROJECT_NAME}_P4EST_SOURCE_ARCHIVE} CONFIGURE_HANDLED_BY_BUILD true + CONFIGURE_COMMAND touch /.p4est_stamp + COMMAND ./bootstrap + COMMAND /configure ${AUTOTOOLS_P4EST_ARGS} + BUILD_COMMAND ${CMAKE_MAKE_PROGRAM} -j 8 + BUILD_IN_SOURCE 1 + LOG_DOWNLOAD 1 + LOG_CONFIGURE 1 + LOG_BUILD 1 + LOG_INSTALL 1) + endif() + + else() + # use CMAKE + if(${TOP_PROJECT_NAME}_P4EST_USE_GIT) + message( + "Building p4est with cmake using github repository with tag version ${DEP_${TOP_PROJECT_NAME}_P4EST_USE_GIT_P4EST_VERSION}" + ) + # cmake-format: off + ExternalProject_Add( + p4est_external + # GIT_REPOSITORY https://github.com/cburstedde/p4est.git + # GIT_TAG v${DEP_${TOP_PROJECT_NAME}_P4EST_USE_GIT_P4EST_VERSION} + GIT_REPOSITORY https://github.com/pkestene/p4est.git + GIT_TAG 2.8.5-ahead + CMAKE_ARGS ${CMAKE_P4EST_ARGS} + BUILD_COMMAND ${CMAKE_MAKE_PROGRAM} -j 8 + LOG_CONFIGURE 1 + LOG_BUILD 1 + LOG_INSTALL 1) + # cmake-format: on + else() + message( + "Building p4est with cmake using source archive ${DEP_${TOP_PROJECT_NAME}_P4EST_SOURCE_ARCHIVE}" + ) + ExternalProject_Add( + p4est_external + URL ${DEP_${TOP_PROJECT_NAME}_P4EST_SOURCE_ARCHIVE} + CMAKE_ARGS ${CMAKE_P4EST_ARGS} + BUILD_COMMAND ${CMAKE_MAKE_PROGRAM} -j 8 + LOG_CONFIGURE 1 + LOG_BUILD 1 + LOG_INSTALL 1) + endif() + endif() + + message("[kalypsso / p4est] p4est will be installed into ${P4EST_INSTALL_PREFIX}") + +endif(${TOP_PROJECT_NAME}_P4EST_BUILD) diff --git a/dependencies/cmake/modulefile.in b/dependencies/cmake/modulefile.in new file mode 100644 index 0000000..dcb2c9c --- /dev/null +++ b/dependencies/cmake/modulefile.in @@ -0,0 +1,41 @@ +#%Module1.0 +# -*- mode: Tcl;-*- +############################################################################################### +## Modulefile for using kalypsso dependencies (p4est, hdf5, ...) +############################################################################################### + +proc ModulesHelp { } { + puts stderr "Sets the environment for using kalypsso dependencies" +} + +module-whatis "sets the environment for using kalypsso dependencies" + +conflict kalypsso-dependencies + +# get module full name +set fullname [module-info name] + +# split fullname into name/version +set fullname_splitted [split $fullname /] + +# modulefile name +set product [lindex $fullname_splitted end-1] + +# module config +set version [lindex $fullname_splitted end] + +# for Tcl script use only +set topdir @CMAKE_INSTALL_PREFIX@ + +prepend-path PATH $topdir/@CMAKE_INSTALL_BINDIR@ +prepend-path LD_LIBRARY_PATH $topdir/lib +prepend-path LD_LIBRARY_PATH $topdir/@CMAKE_INSTALL_LIBDIR@ +prepend-path CMAKE_PREFIX_PATH $topdir/lib/cmake +prepend-path CMAKE_PREFIX_PATH $topdir/@CMAKE_INSTALL_LIBDIR@/cmake +prepend-path CMAKE_PREFIX_PATH $topdir/cmake +prepend-path PKG_CONFIG_PATH $topdir/lib/pkgconfig +prepend-path PKG_CONFIG_PATH $topdir/@CMAKE_INSTALL_LIBDIR@/pkgconfig + +@MODULEFILE_ZLIB_CONFIG@ + +unset version diff --git a/dependencies/cmake/modulefiles.cmake b/dependencies/cmake/modulefiles.cmake new file mode 100644 index 0000000..06971f2 --- /dev/null +++ b/dependencies/cmake/modulefiles.cmake @@ -0,0 +1,24 @@ +# gnu compatibility, see https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html this is used +# to retrieve portable install paths e.g. some platforms install libraries in lib, others in lib64 +include(GNUInstallDirs) + +if(NOT DEFINED TOP_PROJECT_NAME) + message(FATAL_ERROR "Variable TOP_PROJECT_NAME is not defined.") +endif() + +# +# if we build P4EST/SC, it also currently builds ZLIB, so we need to export ZLIB_ROOT +# +if(${TOP_PROJECT_NAME}_P4EST_BUILD) + set(MODULEFILE_ZLIB_CONFIG "setenv ZLIB_ROOT ${CMAKE_INSTALL_PREFIX}") +endif() + +set(MODULEFILE_DIR + "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/modulefiles/${TOP_PROJECT_NAME_LC}-dependencies" +) +file(MAKE_DIRECTORY ${MODULEFILE_DIR}) +configure_file(cmake/modulefile.in "${MODULEFILE_DIR}/1.0") + +# install( FILES modulefile DESTINATION +# "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/modulefiles/${TOP_PROJECT_NAME_LC}-dependencies" +# RENAME "1.0") diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 0000000..7c157fa --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1,3 @@ +mkdocs/mkdocs.yml +mkdocs/docs/index.md +mkdocs/site diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt new file mode 100644 index 0000000..43f5d3a --- /dev/null +++ b/doc/CMakeLists.txt @@ -0,0 +1,122 @@ +# +# We provide two type of documentation : +# +# * regular Doxygen for API +# * a static website (written in markdown) generated by MkDocs +# +# To generate the static webpage, you need to have mkdocs installed use the following lines to +# install it +# +# * conda create -n MkDoxy +# * conda activate MkDoxy +# * conda install pip +# * cd doc +# * pip install -r requirements.txt +# + +# +# Find Doxygen +# +find_package(Doxygen REQUIRED dot OPTIONAL_COMPONENTS mscgen dia) + +if(NOT DOXYGEN_FOUND) + message( + WARNING "Doxygen need to be installed to generate the doxygen documentation. Can't go further.") +endif(NOT DOXYGEN_FOUND) + +get_target_property(DOXY_DOT_PATH Doxygen::dot IMPORTED_LOCATION) +get_filename_component(DOXY_DOT_PATH "${DOXY_DOT_PATH}" DIRECTORY) +file(TO_NATIVE_PATH "${DOXY_DOT_PATH}" DOXY_DOT_PATH) + +set(DOC_SRC_DIR "${CMAKE_SOURCE_DIR}/doc") +set(DOC_BIN_DIR "${CMAKE_BINARY_DIR}/doc") +set(DOC_DEST_DIR "${CMAKE_BINARY_DIR}/doc/${KALYPSSO_DOC}") +set(DOXY_WARN_AS_ERR "NO") + +# ------------------------------------------------ +# configure documentation +# ------------------------------------------------- + +# if the target is doxygen build only doxygen HTML +if(KALYPSSO_DOC STREQUAL "doxygen") + + set(DOXY_BUILD_HTML "YES") + set(DOXY_BUILD_XML "NO") + set(DOXY_OUTPUT_DIR "${DOC_DEST_DIR}") + + # otherwise generate only XML doxygen output (used to generate MkDocs page) +else() + + set(DOXY_BUILD_HTML "NO") + set(DOXY_BUILD_XML "YES") + set(DOXY_OUTPUT_DIR "${DOC_BIN_DIR}/doxygen") + + # https://git.mel.vin/template/c/issues/50 https://bugzilla.gnome.org/show_bug.cgi?id=792676 + set(DOXY_WARN_AS_ERR "NO") + +endif() + +configure_file("${DOC_SRC_DIR}/doxyfile.in" "${DOC_BIN_DIR}/doxyfile" @ONLY) + +# ------------------------------------------------ +# create documentation targets +# ------------------------------------------------- +if(KALYPSSO_DOC STREQUAL "doxygen") + + # ================================ + # DOXYGEN html + # ================================ + + add_custom_target( + doc + COMMAND Doxygen::doxygen "${DOC_BIN_DIR}/doxyfile" + COMMENT "Generating HTML API documentation with Doxygen") + + add_custom_command( + TARGET doc + POST_BUILD + COMMAND ; + COMMENT "Output at ${DOC_DEST_DIR}/html/index.html") + + add_custom_target(clean_doc COMMAND ${CMAKE_COMMAND} -E remove_directory "${DOC_DEST_DIR}") + +elseif(KALYPSSO_DOC STREQUAL "mkdocs") + + # ================================ + # MkDocs + MkDoxy web site + # ================================ + + find_program( + MKDOCS_EXE + NAMES "mkdocs" + DOC "Path to mkdocs executable") + if(NOT MKDOCS_EXE) + message(FATAL_ERROR "mkdocs was not found, please install and make it available in your PATH") + endif() + + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/mkdocs/mkdocs.yml.in" + "${CMAKE_CURRENT_SOURCE_DIR}/mkdocs/mkdocs.yml") + + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/mkdocs/docs/index.md.in" + "${CMAKE_CURRENT_SOURCE_DIR}/mkdocs/docs/index.md") + + # set mkdocs command line + set(MKDOCS_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/mkdocs") + + add_custom_target( + mkdocs + COMMAND + "${CMAKE_COMMAND}" -E echo + "Generating mkdocs web site documentation with MkDocs/MkDoxy in ${MKDOCS_OUTPUT} from ${CMAKE_CURRENT_SOURCE_DIR}/mkdocs" + COMMAND ${MKDOCS_EXE} build -c -s -v --site-dir ${MKDOCS_OUTPUT} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/mkdocs) + + add_custom_target(clean_mkdocs COMMAND ${CMAKE_COMMAND} -E remove_directory "${MKDOCS_OUTPUT}") + +elseif() + + message( + WARNING + "The only valid values for DOC are : doxygen and mkdocs. No documentation will be generated") + +endif() diff --git a/doc/doxyfile.in b/doc/doxyfile.in new file mode 100644 index 0000000..45aa93f --- /dev/null +++ b/doc/doxyfile.in @@ -0,0 +1,27 @@ +DOT_MULTI_TARGETS = YES +DOT_PATH = "@DOXY_DOT_PATH@" +ENABLE_PREPROCESSING = YES +EXPAND_ONLY_PREDEF = YES +EXTRACT_LOCAL_CLASSES = NO +FULL_SIDEBAR = NO +GENERATE_HTML = @DOXY_BUILD_HTML@ +GENERATE_LATEX = @DOXY_BUILD_LATEX@ +GENERATE_TREEVIEW = YES +GENERATE_XML = @DOXY_BUILD_XML@ +HAVE_DOT = YES +INPUT = "@CMAKE_SOURCE_DIR@/src" "@CMAKE_SOURCE_DIR@/test" "@CMAKE_SOURCE_DIR@/app" +JAVADOC_AUTOBRIEF = YES +MACRO_EXPANSION = YES +MULTILINE_CPP_IS_BRIEF = YES +OUTPUT_DIRECTORY = "@DOXY_OUTPUT_DIR@" +PREDEFINED = restrict= +PROJECT_NAME = "@PROJECT_NAME@ API Documentation" +PROJECT_NUMBER = @PROJECT_VERSION@ +PROJECT_BRIEF = "A performance portable adaptive mesh refinement framework on top of p4est and kokkos." +QT_AUTOBRIEF = YES +QUIET = YES +RECURSIVE = YES +STRIP_FROM_PATH = "@CMAKE_SOURCE_DIR@" +USE_MDFILE_AS_MAINPAGE = README.md +WARN_AS_ERROR = @DOXY_WARN_AS_ERR@ +WARN_NO_PARAMDOC = YES diff --git a/doc/mkdocs/docs/index.md.in b/doc/mkdocs/docs/index.md.in new file mode 100644 index 0000000..9cf77e9 --- /dev/null +++ b/doc/mkdocs/docs/index.md.in @@ -0,0 +1,24 @@ +# @PROJECT_NAME@ + +**@PROJECT_NAME@ : Adaptive mesh refinement for CFD applications using performance portable kernels** + +## [Introduction](/introduction) + +!!! note + TODO + +## Feature list + +!!! note + TODO + +Resources +--------- + +**Online Documentation (TODO)** + +https://github.com/pkestene/@PROJECT_NAME@-doc ?? + +**Source Repo** + +https://github.com/pkestene/@PROJECT_NAME@ diff --git a/doc/mkdocs/docs/introduction.md b/doc/mkdocs/docs/introduction.md new file mode 100644 index 0000000..77b3157 --- /dev/null +++ b/doc/mkdocs/docs/introduction.md @@ -0,0 +1,75 @@ +# Solving compressible fluid flow equations on AMR grid + +## About Euler equations for compressible fluid dynamics + +Let's define the vector of conservative variables $U=(\rho, \rho u, \rho v, \rho w, E)$ and the corresponding vector of primitive variables $W=(\rho, u, v, w, p)$, where $\rho$ is the fluid density, $u,v,w$ are the three cartesian components of the velocity vector field, $E$ is the total energy per unit volume, $e$ is the specific internal energy and $p$ is the pressure. + +The 1d Euler system of equations written in conservative form reads: + +$$ +\begin{array}{ccccc} + \partial_t \rho & + & \partial_x(\rho u) & = & 0,\\ + \partial_t (\rho u) & + & \partial_x(\rho u^2+p) & = & 0,\\ + \partial_t E & + & \partial_x (u(E+p)) & = & 0,\\ + \end{array} +$$ + +or in short notations: + +$$ +\partial_t \mathbf{U} + \partial_x \mathbf{F(U)} = \mathbf{0} +$$ + +where the flux function is defined by + +$$ +\mathbf{F(U)} = \left [ + \begin{array}{c} + \rho u \\ + \rho u^2 + p \\ + u (E + p) + \end{array} \right] +$$ + +To close the system, one needs an additional relation, often given by an equation of state (EoS), that is a relation $e=e(\rho,p)$; that is internal energy is a function of density and pressure. + +In case of ideal gas EoS, one has + +- total (internal + kinetic) energy per unit volume $E = \rho \left( e + \frac{1}{2} u^2 \right) = \frac{p}{\gamma-1} + \frac{1}{2} \rho u^2$, +- specific (per mass unit) internal energy $e=\frac{p}{(\gamma-1)\rho}$. +- total enthalpy $H = (E + p)/\rho$ per mass unit (specific enthalpy) +- another useful relation $\frac{\gamma E}{\rho}=H+(\gamma-1)u^2/2$. +- Let $c=\sqrt{\left(\frac{\partial p}{\partial \rho}\right)_s}$ be the speed of sound. For ideal gas EoS, one can show that $c = \sqrt{\frac{\gamma p}{\rho}}$. + +## Euler equations in primitive variables + +Let's do a change of variable, and define $W=(\rho, u, \rho e)$. + +One aims at rewriting 1D Euler equations in quasi-linear form: + +$$ + Q_t + A(Q) Q_x = 0 +$$ + +before, doing that, lets rewritten 1D Euler equation without any assumption about EoS: + +$$ +\begin{array}{ccc} + \partial_t \rho + u \partial_x \rho + \rho \partial_x u & = & 0\\ + \partial_t u + u \partial_x u + \frac{1}{\rho} \partial_x p & = & 0\\ + \partial_t(\rho e) + u \partial_x(\rho e) + \rho e \partial_x u + P \partial_x u & = & 0 +\end{array} +$$ + +If one assumes ideal gas EoS, internal energy can be computed exactly as $\rho e = \frac{p}{\gamma-1}$, one can obtain the quasi-linear form above, where + +$$ +A(Q) = \left[ +\begin{array}{ccc} +u & \rho & 0\\ +0 & u & 1/rho\\ +0 & \rho c^2 & u +\end{array} \right] +$$ + +If one assumes stiffened gas Eos, the definition of $A(Q)$ still holds, we just need to adjust the definition of speed of sound $c$. diff --git a/doc/mkdocs/docs/javascripts/mathjax.js b/doc/mkdocs/docs/javascripts/mathjax.js new file mode 100644 index 0000000..7e48906 --- /dev/null +++ b/doc/mkdocs/docs/javascripts/mathjax.js @@ -0,0 +1,19 @@ +window.MathJax = { + tex: { + inlineMath: [["\\(", "\\)"]], + displayMath: [["\\[", "\\]"]], + processEscapes: true, + processEnvironments: true + }, + options: { + ignoreHtmlClass: ".*|", + processHtmlClass: "arithmatex" + } +}; + +document$.subscribe(() => { + MathJax.startup.output.clearCache() + MathJax.typesetClear() + MathJax.texReset() + MathJax.typesetPromise() +}) diff --git a/doc/mkdocs/mkdocs.yml.in b/doc/mkdocs/mkdocs.yml.in new file mode 100644 index 0000000..3cf18ca --- /dev/null +++ b/doc/mkdocs/mkdocs.yml.in @@ -0,0 +1,95 @@ +site_name: @PROJECT_NAME@ @PROJECT_VERSION@ Documentation + +#theme: 'readthedocs' +theme: + name: 'material' + features: + - navigation.tabs + - navigation.indexes + - navigation.top + - navigation.instant + - navigation.tracking + + icon: + repo: fontawesome/brands/github + + palette: + # Palette toggle for dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: amber + accent: amber + toggle: + icon: material/brightness-4 + name: Switch to light mode + + # Palette toggle for light mode + - media: "(prefers-color-scheme: light)" + scheme: default + primary: amber + accent: amber + toggle: + icon: material/brightness-7 + name: Switch to dark mode + +markdown_extensions: + - admonition + - codehilite: + linenums: true + guess_lang: false + - toc: + permalink: true + - pymdownx.arithmatex: + generic: true + - pymdownx.betterem: + smart_enable: all + - pymdownx.caret + - pymdownx.critic + - pymdownx.details + - pymdownx.emoji: + emoji_generator: !!python/name:pymdownx.emoji.to_svg + - pymdownx.inlinehilite + - pymdownx.magiclink + - pymdownx.mark + - pymdownx.smartsymbols + - pymdownx.superfences + - pymdownx.tasklist: + custom_checkbox: true + - pymdownx.tilde + +plugins: + - search + - glightbox + - open-in-new-tab + - mkdoxy: + projects: + @PROJECT_NAME@: + src-dirs: @PROJECT_SOURCE_DIR@/src @PROJECT_SOURCE_DIR@/test @PROJECT_SOURCE_DIR@/app + full-doc: True + doxy-cfg: # standard doxygen configuration (key: value) + ENABLE_PREPROCESSING: True + EXPAND_ONLY_PREDEF: True + FILE_PATTERNS: "*.cpp *.h*" + HAVE_DOT: True + JAVADOC_AUTOBRIEF: True + PROJECT_BRIEF: "A performance portable adaptive mesh refinement framework on top of p4est and kokkos." + RECURSIVE: True + +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/pkestene/kalypsso + +repo_name: pkestene/kalypsso +repo_url: https://github.com/pkestene/kalypsso +use_directory_urls: true # (optional) for better links without .html extension + +# required for MathJax +# see https://squidfunk.github.io/mkdocs-material/reference/math/#mathjax-mkdocsyml +extra_javascript: + - javascripts/mathjax.js + - https://polyfill.io/v3/polyfill.min.js?features=es6 + - https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js + +nav: + - "Home": index.md diff --git a/doc/requirements.txt b/doc/requirements.txt new file mode 100644 index 0000000..20826bd --- /dev/null +++ b/doc/requirements.txt @@ -0,0 +1,8 @@ +mkdocs +mkdocs-material +Jinja2 +ruamel.yaml +mkdoxy +mkdocs-glightbox +mkdocs-open-in-new-tab +mkdocs-git-revision-date-localized-plugin diff --git a/kalypsso-core-config.cmake.in b/kalypsso-core-config.cmake.in new file mode 100644 index 0000000..f8497cb --- /dev/null +++ b/kalypsso-core-config.cmake.in @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2025 kalypsso-core authors +# +# SPDX-License-Identifier: Unlicense + +@PACKAGE_INIT@ + +# +# we need to install dependencies that were part of the build +# +include(CMakeFindDependencyMacro) + +if(KALYPSSO_CORE_KOKKOS_BUILD) + find_dependency(Kokkos) +endif() + +if(KALYPSSO_CORE_BUILD_CNPY) + find_dependency(cnpy) +endif() + +find_dependency(HighFive) + +include(${CMAKE_CURRENT_LIST_DIR}/kalypsso-core-targets.cmake) +check_required_components(@PROJECT_NAME_SC@) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..36a8e29 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(kalypsso) diff --git a/src/kalypsso/CMakeLists.txt b/src/kalypsso/CMakeLists.txt new file mode 100644 index 0000000..13f6ae6 --- /dev/null +++ b/src/kalypsso/CMakeLists.txt @@ -0,0 +1,4 @@ +# config file reader / ConfigMap / monitoring timer +add_subdirectory(utils) + +add_subdirectory(core) diff --git a/src/kalypsso/core/AMRContext.h b/src/kalypsso/core/AMRContext.h new file mode 100644 index 0000000..84c79bb --- /dev/null +++ b/src/kalypsso/core/AMRContext.h @@ -0,0 +1,629 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file AMRContext.h + * + * Define data structure for applying AMR cycle (refine, coarsen, balance) using p4est API. + */ +#ifndef KALYPSSO_CORE_AMRCONTEXT_H_ +#define KALYPSSO_CORE_AMRCONTEXT_H_ + +#include // for KALYPSSO_ASSERT +#include +#include + +#include // for uint8_t + +namespace kalypsso +{ + +// forward declaration +template +int +refine_callback(typename p4est::Wrapper::forest_t * forest, + p4est::topidx_t which_tree, + typename p4est::Wrapper::quadrant_t * q); + +template +void +replace_on_refine_callback(typename p4est::Wrapper::forest_t * forest, + p4est::topidx_t which_tree, + int num_outgoing, + typename p4est::Wrapper::quadrant_t * outgoing[], + int num_incoming, + typename p4est::Wrapper::quadrant_t * incoming[]); + +template +int +coarsen_callback(typename p4est::Wrapper::forest_t * forest, + p4est::topidx_t which_tree, + typename kalypsso::p4est::Wrapper::quadrant_t * q[]); + +template +void +replace_on_coarsen_callback(typename p4est::Wrapper::forest_t * forest, + p4est::topidx_t which_tree, + int num_outgoing, + typename p4est::Wrapper::quadrant_t * outgoing[], + int num_incoming, + typename p4est::Wrapper::quadrant_t * incoming[]); + +template +void +replace_on_balance_callback(typename p4est::Wrapper::forest_t * forest, + p4est::topidx_t which_tree, + int num_outgoing, + typename p4est::Wrapper::quadrant_t * outgoing[], + int num_incoming, + typename p4est::Wrapper::quadrant_t * incoming[]); + + +// ======================================================= +// ======================================================= +/** + * A base class for AMRContext structure. + */ +struct AMRContextBase +{ + + //! amr flags type + using amrflag_t = int32_t; + + //!@{ + //! flags used in refine / coarsen callback functions + static constexpr amrflag_t KALYPSSO_FLAG_INIT = -2; + static constexpr amrflag_t KALYPSSO_DO_COARSEN = -1; + static constexpr amrflag_t KALYPSSO_DO_NOTHING = 0; + static constexpr amrflag_t KALYPSSO_DO_REFINE = 1; + //!@} + +}; // struct AMRContextBase + +// ======================================================= +// ======================================================= +struct AMRCycleData +{ + using amrflag_t = AMRContextBase::amrflag_t; + + //! raw pointer to AMR flags for refine (member of AMRContext, allocated in AMRContext) + //! must be allocated by AMRCycleData owner, size is p4est local number of quadrants + amrflag_t * flags; + + //! raw pointer to AMR flags for coarsen (member of AMRContext, allocated in AMRContext) + //! We strongly assumed coarsening happens AFTER refining. + //! must be allocated by AMRCycleData owner, size is p4est local number of quadrant, pls + //! additional newly added quadrants (from refining operation). + amrflag_t * tmp_flags; + + /** + * counter, determine the number of quadrants/octants to be refined. + * It is incremented in mark_refine, and decremented in mark_coarsen (if quadrant/octant + * has already been marked previously for refinement). + */ + p4est::locidx_t num_refine_flags; + + /** + * counter of visited quadrant/octant. + * + * incremented inside refine_callback (p4est_refine_ext), used for + * cross-checking afterwards, that the callback was actually called + * the right number of times. + */ + p4est::locidx_t inside_counter; + + /** + * actual number of coarsened quadrants; incremented inside coarsen_callback. + */ + p4est::locidx_t num_replaced; + + /** + * parameter that delays coarsening after adaptation. + * \sa https://github.com/cburstedde/p4est/blob/master/src/p4est_wrap.h#L60 + */ + int coarsen_delay; + + /** + * parameter that change the behavior of coarsening. Currently not used in kalypsso. + * \sa https://github.com/cburstedde/p4est/blob/master/src/p4est_wrap.h#L64 + */ + int coarsen_affect = 0; + + AMRCycleData(amrflag_t * _flags, amrflag_t * _tmp_flags) + : flags(_flags) + , tmp_flags(_tmp_flags) + , num_refine_flags(0) + , inside_counter(0) + , num_replaced(0) + , coarsen_delay(0) + {} + + void + reset() + { + num_refine_flags = 0; + inside_counter = 0; + num_replaced = 0; + } // reset + + void + reset_internal() + { + inside_counter = 0; + num_replaced = 0; + } // reset_internal + + void + set_coarsen_delay(int new_coarsen_delay) + { + coarsen_delay = new_coarsen_delay; + } + +}; // struct AMRCycleData + +// ====================================================================================== +// ====================================================================================== +/** + * A simple data structure holding minimal data needed to perform mesh refine/coarsen/balanced on + * host using wrapper p4est API. + * + * Purpose: + * - RAII for host and device AMR flags; the AMR flags are exposed to device; the user physics is + * responsible to compute the AMR flags (one per quadrant) to instruct p4est which + * quadrant must be refined, coarsened or stay untouched. + * - the main member is adapt_mesh: use the AMR flags to commit the mesh changes; in other words, + * make a call to three p4est API : p4est_refine, p4est_coarsen, and p4est_balance. + * Just as a reminder refine and coarsen are purely local to current MPI process, while + * p4est_balance involves MPI communications (internal to p4est). + * + * \note + * User data remapping (refine/coarsen/balance) will be done on device later. (TODO add here a link + * to the code where this is done). + * + * This class is a close companion to ARMmesh (p4est RAII) and MeshMap (for converting AMR key into + * a hash map). + * + * \TODO clarify if this must be a struct or a class + */ +template +struct AMRContext : public AMRContextBase +{ + + //! Kokkos execution space alias + using exec_space = typename device_t::execution_space; + + //! type alias for a kokkos view holding amr flags on device + using amrflags_view_t = typename Kokkos::View; + + //! type alias for a kokkos view holding amr flags on host + using amrflags_view_host_t = typename amrflags_view_t::HostMirror; + + using p4est_t = typename p4est::Wrapper; + using forest_t = typename p4est_t::forest_t; + + + // ======================================================= + /** + * \param[in] local_num_octant local number of octant in current MPI process + */ + AMRContext(int32_t local_num_octants) + : m_amrflags_d(Kokkos::view_alloc(Kokkos::WithoutInitializing, "AMRContext::amrflags"), + static_cast(local_num_octants)) + , m_amrflags_h(Kokkos::create_mirror_view(m_amrflags_d)) + {} + + // ======================================================= + //! resize amrflags view + void + resize_amrflags(int32_t new_size) + { + + // resize (actually just reallocate memory without initializing) + Kokkos::realloc( + Kokkos::view_alloc(Kokkos::WithoutInitializing), m_amrflags_d, static_cast(new_size)); + m_amrflags_h = Kokkos::create_mirror_view(m_amrflags_d); + + } // resize_amrflags + + // ======================================================= + //! reset amrflags view + void + reset_amrflags() + { + Kokkos::deep_copy(m_amrflags_d, AMRContextBase::KALYPSSO_FLAG_INIT); + } // reset_amrflags + + // ======================================================= + /** + * Apply refine / coarsen / 2:1 balance on the p4est mesh. + * + * Inside this routine, we assume the numerical scheme already filled the amrflags array using + * physics specific criterion, and deep copying the flags on the host mirror is up to date. + * + * \note heavy data / physics remap is done elsewhere. + * + * \return boolean indicating if mesh has changed + */ + bool + adapt_mesh(typename p4est_t::forest_t * forest) + { + + constexpr auto NB_CHILDREN = p4est_t::NB_CHILDREN; + + // store local number of quad before AMR cycle (will be used in assertions) + [[maybe_unused]] p4est::locidx_t local_num_quadrants = forest->local_num_quadrants; + + // compute the number of possible refined quadrants (used to allocate temp_flags host array) + int32_t num_refine_flags = 0; + Kokkos::Sum reducer(num_refine_flags); + + { + // view copy to avoid "Implicit capture of 'this' in extended lambda expression" + auto amrflags_d = m_amrflags_d; + + Kokkos::parallel_reduce( + "Compute_num_refined_flags", + Kokkos::RangePolicy(0, forest->local_num_quadrants), + KOKKOS_LAMBDA(const int32_t index, int32_t & local_sum) { + if (amrflags_d(index) == AMRContextBase::KALYPSSO_DO_REFINE) + local_sum++; + }, + reducer); + } + + KALYPSSO_ASSERT(num_refine_flags >= 0 && + num_refine_flags <= static_cast(forest->local_num_quadrants)); + + // This allocation is optimistic when not all refine requests are honored + amrflags_view_host_t temp_flags( + "temp_flags", + static_cast(forest->local_num_quadrants + (NB_CHILDREN - 1) * num_refine_flags)); + Kokkos::deep_copy(temp_flags, AMRContextBase::KALYPSSO_DO_REFINE); + + // Create and initialize context data for running AMR cycle (p4est mesh adapt) on host + AMRCycleData amr_cycle_data(m_amrflags_h.data(), temp_flags.data()); + amr_cycle_data.num_refine_flags = num_refine_flags; + + // change forest user_pointer + void * tmp_user_pointer = forest->user_pointer; + forest->user_pointer = static_cast(&amr_cycle_data); + + // store the number of quadrants before refinement + p4est::gloidx_t old_global_num_quadrants = forest->global_num_quadrants; + + bool mesh_changed = false; + + // + // Execute refinement + // + { + // TODO : design a class containing the ScopedRegion + some optional internal monitoring + // (Timer) + Kokkos::Profiling::ScopedRegion myprof("AMR_MESH::refine"); + + amr_cycle_data.reset_internal(); + + // let p4est perform the actual refinement + // - non-recursive + // - allowed level is the maxlevel (P4EST_QMAXLEVEL) + // - refine_callback is a function pointer (can't be nullptr), this is where the flag are + // checked, + // the callback must return true if the quadrant is to be refined. When non-recursive, only + // existing quadrant are checked, when recursive is activated then all quadrant are checked, + // existing ones and newly created ones. + // - init_fn will be non-nullptr only in the initial refinement (when computing the init + // condition) + // - replace_on_refine is specified in Solver::adapt to be + // wrap_replace_flags (initial condition) or quadrant_replace_fn (otherwise) + // note that wrap_replace_flags is defined here in SolverWrap + // while quadrant_replace_fn is define in Solver. + // + // Please note that quadrant_replace_fn is used to change quadrant's user data + // when we will refactor for external memory storage (e.g. using Kokkos library) + // this operation will be done is a separate call. + // + + constexpr int RECURSIVE_DISABLED = 0; + constexpr int ALL_LEVEL_ALLOWED = -1; // amrflags MUST check we stay on valid level range + + p4est_t::refine_ext(forest, + RECURSIVE_DISABLED, + ALL_LEVEL_ALLOWED, + refine_callback, + nullptr, + replace_on_refine_callback); + + KALYPSSO_ASSERT(amr_cycle_data.inside_counter == local_num_quadrants); + + KALYPSSO_ASSERT(((forest->local_num_quadrants - local_num_quadrants) == + amr_cycle_data.num_replaced * (NB_CHILDREN - 1))); + + + mesh_changed = old_global_num_quadrants != forest->global_num_quadrants; + } + + // + // Execute coarsening + // + { + Kokkos::Profiling::ScopedRegion myprof("AMR_MESH::coarsen"); + + amr_cycle_data.reset_internal(); + + // store the number of quadrants before coarsening + old_global_num_quadrants = forest->global_num_quadrants; + + // only used in debug (for assertion) + [[maybe_unused]] auto local_num_quadrants_before_coarsening = forest->local_num_quadrants; + + // let p4est perform the actual coarsening + // - non-recursive + // - callback_orphans=1 means coarsen_callback is also called when siblings are not + // "available" (i.e. ghost quadrants) + // - coarsen callback is a function pointer (that can't be nullptr), this is WHERE action is, + // i.e. + // where the flags of current quad and siblings are checked; if they all agree on + // coarsenning then they will disappear and be replaced by the coarse quadrant + // - init_fn will be non-nullptr only in the initial refinement (when computing the init + // condition) + // - replace_on_coarsen is where quadrant's user_data is changed + + constexpr int RECURSIVE_DISABLED = 0; + constexpr int CALLBACK_ORPHANS = 1; + p4est_t::coarsen_ext(forest, + RECURSIVE_DISABLED, + CALLBACK_ORPHANS, + coarsen_callback, + nullptr, + amr_cycle_data.coarsen_delay ? replace_on_coarsen_callback + : nullptr); + + KALYPSSO_ASSERT(amr_cycle_data.inside_counter == local_num_quadrants_before_coarsening); + KALYPSSO_ASSERT(local_num_quadrants_before_coarsening - forest->local_num_quadrants == + amr_cycle_data.num_replaced * (NB_CHILDREN - 1)); + mesh_changed = mesh_changed || old_global_num_quadrants != forest->global_num_quadrants; + } + + // + // Execute 2:1 balance + // + + // if mesh changed (refinement and/or coarsening actually happened), do also need to do 2:1 + // balance + if (mesh_changed) + { + Kokkos::Profiling::ScopedRegion myprof("AMR_MESH::2to1_balance"); + + p4est_t::balance_ext(forest, + p4est_t::CONNECT_FULL, + nullptr, + amr_cycle_data.coarsen_delay ? replace_on_balance_callback + : nullptr); + } + + // restore forest user_pointer + forest->user_pointer = tmp_user_pointer; + + return mesh_changed; + + } // adapt_mesh + + //! device amr flags. + //! array of flags (one per local quadrant, dynamic allocation) to indicate a cell + //! to refine or coarsen + amrflags_view_t m_amrflags_d; + + //! host amr flags. + //! host mirror of m_amrflags_h + amrflags_view_host_t m_amrflags_h; + +}; // struct AMRContext + +// ======================================================= +// ======================================================= +/** + * p4est refine callback used internally by p4est_refine_ext. + */ +template +int +refine_callback(typename p4est::Wrapper::forest_t * forest, + [[maybe_unused]] p4est::topidx_t which_tree, + [[maybe_unused]] typename p4est::Wrapper::quadrant_t * q) +{ + + using amrflag_t = AMRContextBase::amrflag_t; + + constexpr auto NB_CHILDREN = p4est::Wrapper::NB_CHILDREN; + + // get the AMR cycle internal data + AMRCycleData * amr_cycle_data = static_cast(forest->user_pointer); + + const p4est::locidx_t old_counter = amr_cycle_data->inside_counter++; + const amrflag_t flag = amr_cycle_data->flags[old_counter]; + + // check for invalid flag values + KALYPSSO_ASSERT(flag >= -1 && flag <= 1); + + KALYPSSO_ASSERT(amr_cycle_data->coarsen_delay >= 0); + KALYPSSO_ASSERT(0 <= old_counter); + KALYPSSO_ASSERT(0 <= amr_cycle_data->num_replaced && + amr_cycle_data->num_replaced <= amr_cycle_data->num_refine_flags); + + // refining WILL happen; the functor that created refine flags is responsible for checking + // if refinement if possible (i.e. max level allowed not reached yet) + amr_cycle_data->flags[old_counter] = AMRContextBase::KALYPSSO_DO_NOTHING; + amr_cycle_data->tmp_flags[old_counter + (NB_CHILDREN - 1) * amr_cycle_data->num_replaced] = + (flag == AMRContextBase::KALYPSSO_DO_REFINE) ? AMRContextBase::KALYPSSO_DO_NOTHING : flag; + + // increase quadrant's counter of most recent adaptation + // if refinement actually occurs, it will be reset to zero in all children + if (amr_cycle_data->coarsen_delay && q->p.user_int >= 0) + { + ++q->p.user_int; + } + + return flag == AMRContextBase::KALYPSSO_DO_REFINE ? 1 : 0; + +} // refine_callback + +// ======================================================= +// ======================================================= +/** + * p4est callback used when refinement actually happens. + */ +template +void +replace_on_refine_callback(typename p4est::Wrapper::forest_t * forest, + [[maybe_unused]] p4est::topidx_t which_tree, + [[maybe_unused]] int num_outgoing, + [[maybe_unused]] typename p4est::Wrapper::quadrant_t * outgoing[], + [[maybe_unused]] int num_incoming, + [[maybe_unused]] typename p4est::Wrapper::quadrant_t * incoming[]) +{ + + constexpr auto NB_CHILDREN = p4est::Wrapper::NB_CHILDREN; + + // get the AMR cycle internal data + AMRCycleData * amr_cycle_data = static_cast(forest->user_pointer); + + const p4est::locidx_t new_counter = + amr_cycle_data->inside_counter - 1 + (NB_CHILDREN - 1) * amr_cycle_data->num_replaced++; + + const auto flag = amr_cycle_data->tmp_flags[new_counter]; + + // this function is only called when refinement actually happens + KALYPSSO_ASSERT(num_outgoing == 1 && num_incoming == NB_CHILDREN); + KALYPSSO_ASSERT(flag == AMRContextBase::KALYPSSO_DO_NOTHING); + + // we have set the first flag in the refinement callback, do the others + for (int k = 1; k < NB_CHILDREN; ++k) + { + amr_cycle_data->tmp_flags[new_counter + k] = flag; + } + + // reset the counter for most recent adaptation + KALYPSSO_ASSERT(amr_cycle_data->coarsen_delay >= 0); + if (amr_cycle_data->coarsen_delay) + { + for (int k = 0; k < NB_CHILDREN; ++k) + { + incoming[k]->p.user_int = 0; + } + } + +} // replace_on_refine_callback + +// ======================================================= +// ======================================================= +/** + * p4est coarsen callback used internally by p4est_coarsen_ext. + */ +template +int +coarsen_callback(typename p4est::Wrapper::forest_t * forest, + [[maybe_unused]] p4est::topidx_t which_tree, + [[maybe_unused]] typename kalypsso::p4est::Wrapper::quadrant_t * q[]) +{ + constexpr auto NB_CHILDREN = p4est::Wrapper::NB_CHILDREN; + + // get the AMR cycle internal data + AMRCycleData * amr_cycle_data = static_cast(forest->user_pointer); + + KALYPSSO_ASSERT(amr_cycle_data->coarsen_delay >= 0); + + const p4est::locidx_t old_counter = amr_cycle_data->inside_counter++; + + // are we not coarsening at all, just counting ? TO BE CLARIFIED IF REALLY NEEDED + if (q[1] == nullptr) + { + return 0; + } + + // now we are possibly coarsening, check that all quadrant in family agree on coarsening + // if only one disagree, do not coarsen + for (int k = 0; k < NB_CHILDREN; ++k) + { + if (amr_cycle_data->tmp_flags[old_counter + k] != AMRContextBase::KALYPSSO_DO_COARSEN) + { + return 0; + } + if (amr_cycle_data->coarsen_delay && q[k]->p.user_int >= 0 && + q[k]->p.user_int <= amr_cycle_data->coarsen_delay) + { + // most recent adaptation has been too recent, do not coarsen + return 0; + } + } + + // we are definitely coarsening + amr_cycle_data->inside_counter += NB_CHILDREN - 1; + ++amr_cycle_data->num_replaced; + return 1; + +} // coarsen_callback + +// ======================================================= +// ======================================================= +/** + * p4est callback used when coarsening actually happens. + */ +template +void +replace_on_coarsen_callback(typename p4est::Wrapper::forest_t * forest, + [[maybe_unused]] p4est::topidx_t which_tree, + [[maybe_unused]] int num_outgoing, + [[maybe_unused]] typename p4est::Wrapper::quadrant_t * outgoing[], + [[maybe_unused]] int num_incoming, + [[maybe_unused]] typename p4est::Wrapper::quadrant_t * incoming[]) +{ + // using amrflag_t = AMRContextBase::amrflag_t; + [[maybe_unused]] constexpr auto NB_CHILDREN = p4est::Wrapper::NB_CHILDREN; + + // get the AMR cycle internal data + [[maybe_unused]] AMRCycleData * amr_cycle_data = + static_cast(forest->user_pointer); + + KALYPSSO_ASSERT(num_incoming == 1 && num_outgoing == NB_CHILDREN); + KALYPSSO_ASSERT(amr_cycle_data->coarsen_delay > 0); + + // reset most recent adaptation timer + incoming[0]->p.user_int = amr_cycle_data->coarsen_affect ? 0 : -1; + +} // replace_on_coarsen_callback + +// ======================================================= +// ======================================================= +/** + * p4est callback used when restoring 2:1 level balance. + */ +template +void +replace_on_balance_callback(typename p4est::Wrapper::forest_t * forest, + [[maybe_unused]] p4est::topidx_t which_tree, + [[maybe_unused]] int num_outgoing, + [[maybe_unused]] typename p4est::Wrapper::quadrant_t * outgoing[], + [[maybe_unused]] int num_incoming, + [[maybe_unused]] typename p4est::Wrapper::quadrant_t * incoming[]) +{ + + [[maybe_unused]] constexpr auto NB_CHILDREN = p4est::Wrapper::NB_CHILDREN; + + // get the AMR cycle internal data + [[maybe_unused]] AMRCycleData * amr_cycle_data = + static_cast(forest->user_pointer); + + KALYPSSO_ASSERT(num_outgoing == 1 && num_incoming == NB_CHILDREN); + KALYPSSO_ASSERT(amr_cycle_data->coarsen_delay > 0); + + // negative value means coarsening is allowed next time + for (int k = 0; k < NB_CHILDREN; ++k) + { + incoming[k]->p.user_int = -1; + } + +} // replace_on_balance_callback + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_AMRCONTEXT_H diff --git a/src/kalypsso/core/AMRCycleDriver.h b/src/kalypsso/core/AMRCycleDriver.h new file mode 100644 index 0000000..e1e01a8 --- /dev/null +++ b/src/kalypsso/core/AMRCycleDriver.h @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file AMRCycleDriver.h + * \brief A driver class to operate a forest through AMR cycle (refine, coarsen, balance). + * + * It is also the place where we store additional data to enable userdata remapping before/after AMR + * cycle (mesh change). + * + */ +#ifndef KALYPSSO_CORE_AMRCYCLEDRIVER_H_ +#define KALYPSSO_CORE_AMRCYCLEDRIVER_H_ + +#include + +#include +#include +#include + +#include +#include +#include + +namespace kalypsso +{ + +// ============================================================================= +// ============================================================================= +// ============================================================================= +// ============================================================================= +template +class AMRCycleDriver +{ +public: + using exec_space = typename device_t::execution_space; + + AMRCycleDriver() = delete; + AMRCycleDriver(const ParallelEnv & par_env, const ConfigMap & config_map) + : m_par_env(par_env) + , m_config_map(config_map) + , m_amr_mesh(new AMRmesh(par_env.mpi_comm(), config_map)) + , m_mesh_map(config_map, par_env) + { + + auto conn_name = m_config_map.getString("amr", "connectivity", "invalid_connectivity"); + if (conn_name == "brick") + { + m_brick_sizes[0] = + static_cast(m_config_map.getInteger("p4est_connectivity", "nbrick_x", 2)); + m_brick_sizes[1] = + static_cast(m_config_map.getInteger("p4est_connectivity", "nbrick_y", 3)); + if constexpr (dim == 3) + m_brick_sizes[2] = + static_cast(m_config_map.getInteger("p4est_connectivity", "nbrick_z", 4)); + } + } // AMRCycleDriver + + AMRmesh & + mesh() + { + return *m_amr_mesh; + } + + const MeshMap & + mesh_map() const + { + return m_mesh_map; + } + + Kokkos::Array + brick_sizes() const + { + return m_brick_sizes; + } + +private: + //! parallel environment + const ParallelEnv & m_par_env; + + //! config map (input parameter) + ConfigMap m_config_map; + + //! p4est resource (forest, ghost, ...) + std::shared_ptr> m_amr_mesh; + + //! a MeshMap object so that we can extract the orchard keys as a kokkos view or unordered map + MeshMap m_mesh_map; + + //! p4est connectivity sizes. + //! brick sizes are used to transform logical coordinates into vertex space (real space) + Kokkos::Array m_brick_sizes; + +}; // class AMRCycleDriver + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_AMRCYCLEDRIVER_H_ diff --git a/src/kalypsso/core/AMRMeshInfo.h b/src/kalypsso/core/AMRMeshInfo.h new file mode 100644 index 0000000..9a00e32 --- /dev/null +++ b/src/kalypsso/core/AMRMeshInfo.h @@ -0,0 +1,307 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file AMRMeshInfo.h + * + */ +#ifndef KALYPSSO_CORE_AMRMESHINFO_H_ +#define KALYPSSO_CORE_AMRMESHINFO_H_ + +#include +#include + +namespace kalypsso +{ + +/** + * A minimalist struct containing information about a AMR mesh: + * + * - number of owned quadrants inside domain (local to current MPI process) + * - sum of the number of owned quadrants (global, i.e. sum over all MPI process) + * - number of MPI ghost quadrants local to current MPI process + * - number of quadrants outside domain (local to current MPI process); may be zero + * - number of quadrants outside domain (global, i.e. sum over all MPI process); may be zero + * + */ +class AMRMeshInfo +{ + +private: + //! local (current MPI proc) number of owned quads/octs + int32_t m_local_num_quadrants; + + //! global accumulated (all MPI proc) number of owned quads/octs + int64_t m_global_num_quadrants; + + //! global index to the first quadrant in current MPI process (along the Morton curve) + int64_t m_global_first_quadrant; + + //! local number of MPI ghost quadrants + int32_t m_local_num_ghosts; + + //! local number of MPI owned quadrants that are mirrors (to be send to fill ghost quadrant in + //! some other MPI process) + int32_t m_local_num_mirrors; + + //! local (current MPI proc) number of quads/octs outside domain + int32_t m_local_num_quadrants_outside; + + //! global accumulated (all MPI proc) number of quads/octs outside domain + int64_t m_global_num_quadrants_outside; + + //! global index to the first outside quadrant in current MPI process + int64_t m_global_first_quadrant_outside; + + //! local (current MPI proc) number of quads/octs that are outside mirror of a ghost quadrant + int32_t m_local_num_quadrants_outside_ghost; + + //! mpi rank (so that we can use in inside kokkos parallel region) + int32_t m_mpi_rank; + +public: + //! constructor + KOKKOS_INLINE_FUNCTION + AMRMeshInfo() + : m_local_num_quadrants(0) + , m_global_num_quadrants(0) + , m_global_first_quadrant(0) + , m_local_num_ghosts(0) + , m_local_num_mirrors(0) + , m_local_num_quadrants_outside(0) + , m_global_num_quadrants_outside(0) + , m_global_first_quadrant_outside(0) + , m_local_num_quadrants_outside_ghost(0) + , m_mpi_rank(-1) + {} + + KOKKOS_DEFAULTED_FUNCTION + ~AMRMeshInfo() = default; + + KOKKOS_DEFAULTED_FUNCTION + AMRMeshInfo(AMRMeshInfo const & amr_mesh_info) = default; + + // ================================================================ + KOKKOS_INLINE_FUNCTION + auto + local_num_quadrants() const + { + return m_local_num_quadrants; + } + + KOKKOS_INLINE_FUNCTION + auto & + local_num_quadrants() + { + return m_local_num_quadrants; + } + + // ================================================================ + KOKKOS_INLINE_FUNCTION + auto + global_num_quadrants() const + { + return m_global_num_quadrants; + } + + KOKKOS_INLINE_FUNCTION + auto & + global_num_quadrants() + { + return m_global_num_quadrants; + } + + // ================================================================ + KOKKOS_INLINE_FUNCTION + auto + global_first_quadrant() const + { + return m_global_first_quadrant; + } + + KOKKOS_INLINE_FUNCTION + auto & + global_first_quadrant() + { + return m_global_first_quadrant; + } + + // ================================================================ + KOKKOS_INLINE_FUNCTION + auto + local_num_ghosts() const + { + return m_local_num_ghosts; + } + + KOKKOS_INLINE_FUNCTION + auto & + local_num_ghosts() + { + return m_local_num_ghosts; + } + + // ================================================================ + KOKKOS_INLINE_FUNCTION + auto + local_num_mirrors() const + { + return m_local_num_mirrors; + } + + KOKKOS_INLINE_FUNCTION + auto & + local_num_mirrors() + { + return m_local_num_mirrors; + } + + // ================================================================ + KOKKOS_INLINE_FUNCTION + auto + local_num_quadrants_outside() const + { + return m_local_num_quadrants_outside; + } + + KOKKOS_INLINE_FUNCTION + auto & + local_num_quadrants_outside() + { + return m_local_num_quadrants_outside; + } + + // ================================================================ + KOKKOS_INLINE_FUNCTION + auto + global_num_quadrants_outside() const + { + return m_global_num_quadrants_outside; + } + + KOKKOS_INLINE_FUNCTION + auto & + global_num_quadrants_outside() + { + return m_global_num_quadrants_outside; + } + + // ================================================================ + KOKKOS_INLINE_FUNCTION + auto + global_first_quadrant_outside() const + { + return m_global_first_quadrant_outside; + } + + KOKKOS_INLINE_FUNCTION + auto & + global_first_quadrant_outside() + { + return m_global_first_quadrant_outside; + } + + // ================================================================ + KOKKOS_INLINE_FUNCTION + auto + local_num_quadrants_outside_ghost() const + { + return m_local_num_quadrants_outside_ghost; + } + + KOKKOS_INLINE_FUNCTION + auto & + local_num_quadrants_outside_ghost() + { + return m_local_num_quadrants_outside_ghost; + } + + // ================================================================ + KOKKOS_INLINE_FUNCTION + auto + mpi_rank() const + { + return m_mpi_rank; + } + + KOKKOS_INLINE_FUNCTION + auto & + mpi_rank() + { + return m_mpi_rank; + } + + //! return the total number of quadrants in local MPI process. + //! Total number of quadrants means sum of number of owned, ghost, outside and outside_ghosts + //! quadrants. + KOKKOS_INLINE_FUNCTION + int32_t + local_num_quadrants_total() const + { + // clang-format off + return + m_local_num_quadrants + + m_local_num_ghosts + + m_local_num_quadrants_outside + + m_local_num_quadrants_outside_ghost; + // clang-format on + } // local_num_quadrants_total + + //! return the local index (local = inside current MPI process) to the first outside quadrants + KOKKOS_INLINE_FUNCTION + int32_t + first_outside_quad_local_id() const + { + return m_local_num_quadrants + m_local_num_ghosts; + } + + //! return the total number of outside quadrants (outside and ghost_outside) in local MPI process + KOKKOS_INLINE_FUNCTION + int32_t + total_local_number_of_outside_quads() const + { + return m_local_num_quadrants_outside + m_local_num_quadrants_outside_ghost; + } + + //! return the total number of quadrant other than owned quadrants; i.e. ghost + outside + + //! outside_ghost + KOKKOS_INLINE_FUNCTION + int32_t + local_num_quadrants_other() + { + // clang-format off + return + m_local_num_ghosts + + m_local_num_quadrants_outside + + m_local_num_quadrants_outside_ghost; + // clang-format on + } + + + //! print (host only) + void + print() const + { + // clang-format off + std::cout << "====================================================\n"; + std::cout << "AMRMeshInfo: \n"; + std::cout << "local_num_quadrants: " << m_local_num_quadrants << "\n"; + std::cout << "global_num_quadrants: " << m_global_num_quadrants << "\n"; + std::cout << "global_first_quadrant: " << m_global_first_quadrant << "\n"; + std::cout << "local_num_ghosts: " << m_local_num_ghosts << "\n"; + std::cout << "local_num_quadrants_outside: " << m_local_num_quadrants_outside << "\n"; + std::cout << "global_num_quadrants_outside: " << m_global_num_quadrants_outside << "\n"; + std::cout << "global_first_quadrant_outside: " << m_global_first_quadrant_outside << "\n"; + std::cout << "local_num_quadrants_outside_ghost: " << m_local_num_quadrants_outside_ghost << "\n"; + std::cout << "local_num_quadrants_total: " << local_num_quadrants_total() << "\n"; + std::cout << "first_outside_quad_local_id: " << first_outside_quad_local_id() << "\n"; + std::cout << "====================================================\n"; + // clang-format on + } // print + +}; // struct AMRMeshInfo + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_AMRMESHINFO_H_ diff --git a/src/kalypsso/core/AMRMeshMonitoring.cpp b/src/kalypsso/core/AMRMeshMonitoring.cpp new file mode 100644 index 0000000..3157053 --- /dev/null +++ b/src/kalypsso/core/AMRMeshMonitoring.cpp @@ -0,0 +1,123 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file AMRMeshMonitoring.cpp + */ +#include + +namespace kalypsso +{ + +// ============================================================================================ +// ============================================================================================ +template +void +AMRMeshMonitoring::print_level_info(ParallelEnv const & par_env, + MeshMap const & mesh_map) +{ + // all MPI procesus computes its local histogram + const auto level_histo = compute_level_histogram(par_env, mesh_map); + + const auto level_histo_host = + Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, level_histo); + + if (par_env.rank() == 0) + { + // compute total number of keys + int64_t total_num_keys = 0; + for (auto level = m_level_min; level <= m_level_max; ++level) + { + total_num_keys += level_histo_host(level - m_level_min); + } + + // total number of keys if the mesh was uniformly refined at max_level + [[maybe_unused]] int64_t total_num_keys_at_max_level_uniform = + m_num_trees * (1 << (static_cast(dim) * m_level_max)); + + KALYPSSO_INFO("AMR level histogram:"); + for (auto level = m_level_min; level <= m_level_max; ++level) + { + const auto num_keys = level_histo_host(level - m_level_min); + const int scaling = 1 << (static_cast(dim) * (m_level_max - level)); + const auto num_keys_at_max_level = num_keys * scaling; + + + KALYPSSO_INFO("level {:3d}: {:10d}/{:10d} ({: 6.2f}%) => {: 6.2f}% of volume ({:10d}/{:10d})", + level, + num_keys, + total_num_keys, + (100.0 * num_keys) / static_cast(total_num_keys), + (100.0 * num_keys_at_max_level) / + static_cast(total_num_keys_at_max_level_uniform), + num_keys_at_max_level, + total_num_keys_at_max_level_uniform); + } + + // compute sparsity index as the ratio of the total number of AMR keys divided + // by the number of keys obtained if the mesh was fully Cartesian at level=level_max + KALYPSSO_INFO("Sparsity index is the ratio of total number of AMR blocks ({}) over", + total_num_keys); + KALYPSSO_INFO("the number of blocks if the whole mesh was at level max ({}).", + total_num_keys_at_max_level_uniform); + KALYPSSO_INFO("Sparsity index : {: 3.2f}%", + 100.0 * static_cast(total_num_keys) / + static_cast(total_num_keys_at_max_level_uniform)); + } + +} // AMRMeshMonitoring::print_level_info + +// ============================================================================================ +// ============================================================================================ +template +auto +AMRMeshMonitoring::compute_level_histogram( + [[maybe_unused]] ParallelEnv const & par_env, + MeshMap const & mesh_map) -> level_histo_view_t +{ + + const auto num_octants = mesh_map.get_amr_mesh_info().local_num_quadrants(); + const auto orchard_keys = mesh_map.orchard_keys(); + auto level_histo = level_histo_view_t("level_histo", static_cast(m_num_levels)); + Kokkos::deep_copy(level_histo, 0); + + const auto level_min = m_level_min; + [[maybe_unused]] const auto level_max = m_level_max; + + Kokkos::parallel_for( + "compute_level_histogram", + Kokkos::RangePolicy(0, num_octants), + KOKKOS_LAMBDA(const int iOct) { + // get AMR level + const auto level = orchard_key_t::level(orchard_keys(iOct)); + KOKKOS_ASSERT(level >= level_min and level <= level_max and + "level wrong value: not in valid range."); + + Kokkos::atomic_add(&level_histo(level - level_min), 1); + }); + +// gather information from all MPI processes +#ifdef KALYPSSO_CORE_USE_MPI + auto level_histo_global = + level_histo_view_t("level_histo_global", static_cast(m_num_levels)); + par_env.comm().MPI_Reduce( + level_histo.data(), level_histo_global.data(), m_num_levels, 0); + + return level_histo_global; +#else + return level_histo; +#endif // KALYPSSO_CORE_USE_MPI + +} // AMRMeshMonitoring::compute_level_histogram + +// explicit template instantiation +template class AMRMeshMonitoring<2, DefaultDevice>; +template class AMRMeshMonitoring<3, DefaultDevice>; + +#ifdef KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE +template class AMRMeshMonitoring<2, HostDevice>; +template class AMRMeshMonitoring<3, HostDevice>; +#endif // KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE + +} // namespace kalypsso diff --git a/src/kalypsso/core/AMRMeshMonitoring.h b/src/kalypsso/core/AMRMeshMonitoring.h new file mode 100644 index 0000000..24ccd75 --- /dev/null +++ b/src/kalypsso/core/AMRMeshMonitoring.h @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file AMRMeshMonitoring.h + */ +#ifndef KALYPSSO_CORE_AMRMESHMONITORING_H_ +#define KALYPSSO_CORE_AMRMESHMONITORING_H_ + +#include +#include +#include // for KOKKOS_ENABLE_XXX + +#include +#include + +#include // for orchard_key_view_t and amr_map_t +#include +#include + +namespace kalypsso +{ + +/** + * A small class to compute, gather and print monitoring information about AMR mesh. + */ +template +class AMRMeshMonitoring +{ +public: + using exec_space = typename device_t::execution_space; + + using level_histo_view_t = Kokkos::View; + using MeshMap_t = MeshMap; + using orchard_key_view_t = typename orchard_key_base_t::view_t; + + AMRMeshMonitoring(ConfigMap const & config_map) + : m_level_min(config_map.getInteger("amr", "level_min", 0)) + , m_level_max(config_map.getInteger("amr", "level_max", 0)) + , m_num_levels(m_level_max - m_level_min + 1) + , m_num_trees(config_map.getInteger("p4est_connectivity", "nbrick_x", 1) * + config_map.getInteger("p4est_connectivity", "nbrick_y", 1) * + config_map.getInteger("p4est_connectivity", "nbrick_z", 1)) + {} + + ~AMRMeshMonitoring() = default; + + // ============================================================================================ + // ============================================================================================ + void + print_level_info(ParallelEnv const & par_env, MeshMap const & mesh_map); + + // ============================================================================================ + // ============================================================================================ + auto + compute_level_histogram([[maybe_unused]] ParallelEnv const & par_env, + MeshMap const & mesh_map) -> level_histo_view_t; + +private: + int m_level_min; + int m_level_max; + int m_num_levels; + int m_num_trees; + +}; // class AMRMeshMonitoring + +// explicit template instantiation +extern template class AMRMeshMonitoring<2, DefaultDevice>; +extern template class AMRMeshMonitoring<3, DefaultDevice>; + +#ifdef KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE +extern template class AMRMeshMonitoring<2, HostDevice>; +extern template class AMRMeshMonitoring<3, HostDevice>; +#endif // KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_AMRMESHMONITORING_H_ diff --git a/src/kalypsso/core/AMRmesh.h b/src/kalypsso/core/AMRmesh.h new file mode 100644 index 0000000..9d243f7 --- /dev/null +++ b/src/kalypsso/core/AMRmesh.h @@ -0,0 +1,479 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file AMRmesh.h + * \brief Container for p4est mesh data (p4est, ghost, connectivity). + * + */ +#ifndef KALYPSSO_CORE_AMRMESH_H_ +#define KALYPSSO_CORE_AMRMESH_H_ + +#include // for assertm, kalypsso_core_config.h +#include +#include +#include +#include +#include + +#include + +#ifdef KALYPSSO_CORE_USE_MPI +# include +#endif // KALYPSSO_CORE_USE_MPI + +namespace kalypsso +{ + +// forward declare +template +class AMRmesh; + +/** + * Callback to perform uniform refinement (input to p4est_refine). + * + * The one and only use of this callback is to feed p4est::Wrapper::new_forest with a callback + * function called one for every quadrant in the forest creation. + */ +template +static int +uniform_refine_fn(typename kalypsso::p4est::Wrapper::forest_t * forest, + [[maybe_unused]] typename kalypsso::p4est::topidx_t which_tree, + typename kalypsso::p4est::Wrapper::quadrant_t * quadrant) +{ + using p4est_userdata_t = typename AMRmesh::p4est_userdata_t; + + p4est_userdata_t * p4est_userdata = static_cast(forest->user_pointer); + const auto level_min = p4est_userdata->level_min; + + if (quadrant->level < level_min) + return 1; + + return 0; +} // uniform_refine_fn + +/** + * Default callback used when creating the forest. + * + * All such callback should initialize the delay memory parameter (see coarsen_delay in p4est + * sources). + * + * The one and only use of this callback is to feed p4est::Wrapper::new_forest with a callback + * function called one for every quadrant in the forest creation. + */ +template +static void +initial_quad_fn([[maybe_unused]] typename kalypsso::p4est::Wrapper::forest_t * forest, + [[maybe_unused]] typename kalypsso::p4est::topidx_t which_tree, + typename kalypsso::p4est::Wrapper::quadrant_t * quadrant) +{ + + // initialize delay memory in the quadrants' user field. + // user_int is a p4est internal metadata field (avail for each quadrant) + quadrant->p.user_int = 0; + +} // initial_quad_fn + + +/** + * \class AMRmesh + * + * This class is doing : + * - RAII, resources management (alloc / dealloc, resize) for p4est objects: connectivity, + * geometry, forest and ghost. + * + */ +template +class AMRmesh +{ +public: + //! type alias to access p4est C API (2D or 3D) + using p4est_t = typename p4est::Wrapper; + + using forest_t = typename p4est_t::forest_t; + using tree_t = typename p4est_t::tree_t; + using connectivity_t = typename p4est_t::connectivity_t; + using geometry_t = typename p4est_t::geometry_t; + using quadrant_t = typename p4est_t::quadrant_t; + using ghost_t = typename p4est_t::ghost_t; + + //! p4est user-defined meta data attached to each quadrant (allocated inside p4est) + //! currently not used, TODO: evaluate if we really need it, and for what purpose + // struct user_data_t + // { + // double x; + // }; + + //! Additional data structure type passed to p4est_new last argument. + //! there is only one instance per forest; it can be used to passed metadata can be used + //! inside p4est callback's + struct p4est_userdata_t + { + geometry_t * geom; + uint8_t level_min; + uint8_t level_max; + }; + + //! constructor : create/allocate a p4est object with connectivity and geometry + AMRmesh([[maybe_unused]] ParallelEnv const & par_env, const ConfigMap & config_map); + + //! destructor : destroy connectivity, geometry and p4est mesh + ~AMRmesh(); + + //! remove copy constructor + AMRmesh(const AMRmesh & other) = delete; + + //! remove copy assign constructor + AMRmesh & + operator=(const AMRmesh & other) = delete; + + //! reset p4est resource + void + reset_p4est_resources(); + + //! create p4est resources from file (created by p4est_save). + //! this is essentially a wrapper around p4est_load + void + create_p4est_resources_from_file(ParallelEnv const & par_env, + ConfigMap const & config_map, + std::string const & p4est_filename); + + forest_t * + forest() + { + return m_forest; + } + + const forest_t * + forest() const + { + return m_forest; + } + + connectivity_t * + connectivity() + { + return m_connectivity; + } + + const connectivity_t * + connectivity() const + { + return m_connectivity; + } + + std::string + connectivity_name() + { + return m_connectivity_name; + } + + const std::string & + connectivity_name() const + { + return m_connectivity_name; + } + + geometry_t * + geometry() + { + return m_geometry; + } + + const geometry_t * + geometry() const + { + return m_geometry; + } + + std::string + geometry_name() + { + return m_geometry_name; + } + + const std::string & + geometry_name() const + { + return m_geometry_name; + } + + ghost_t * + ghost() + { + return m_ghost; + } + + const ghost_t * + ghost() const + { + return m_ghost; + } + + void + reset_ghost() + { + if (m_ghost != nullptr) + { + p4est_t::ghost_destroy(m_ghost); + m_ghost = nullptr; + } + + m_ghost = p4est_t::ghost_new(m_forest, p4est_t::CONNECT_FULL); + } + + //! get number of owned quadrants / octants in current MPI process + int32_t + local_num_quadrants() const + { + return m_forest->local_num_quadrants; + } + + //! get number of quadrants / octants in current MPI process + int32_t + global_num_quadrants() const + { + return m_forest->global_num_quadrants; + } + + //! get the total number of ghost quadrants / octants in current MPI process + int32_t + local_num_ghosts() const + { + return static_cast(m_ghost->ghosts.elem_count); + } + + //! get total number of owned + ghost quadrants in current MPI process + //! WARNING/FYI: not counting outside quadrant here (this is something we added, not part of + //! p4est) + int32_t + local_num_quadrants_total() const + { + return m_forest->local_num_quadrants + static_cast(m_ghost->ghosts.elem_count); + } + + //! get the number of ghost quadrant in local MPI process to be filled with data received from + //! MPI process of rank 'iproc' \param[in] iproc is the MPI process rank from which we receive + //! data. + //! + //! Remainder proc_offsets is an array of size mpi size + 1, so no overflow here + int32_t + local_num_ghosts(int iproc) const + { + assertm(iproc >= 0 and iproc < m_forest->mpisize, + "[AMRmesh::local_num_ghosts] wrong MPI rank value."); + return m_ghost->proc_offsets[iproc + 1] - m_ghost->proc_offsets[iproc]; + } + + //! get number of owned mirror quadrants / octants in current MPI process + //! involved in MPI ghost communication. + //! Important \note + //! if a mirror quadrant is involved in several communication (e.g. at at corner where several MPI + //! sub-domains are neighbors) this quadrant is counted as many times. + //! As a remainder: ghost->mirrors.elem_count gives the number of unique mirror quadrants in + //! current MPI process not to be confused with ghost->mirror_proc_offsets[m_par_env.size()] which + //! is what we are looking for. + int32_t + local_num_mirrors() const + { + return m_ghost->mirror_proc_offsets[m_forest->mpisize]; + } + + //! number of local (owned by current MPI process) mirror quadrants involved in ghost + //! communication with MPI process of rank 'iproc' + //! + //! \param[in] iproc is the rank of the remote process where mirror quadrant are to be sent + int32_t + local_num_mirrors(int iproc) const + { + assertm(iproc >= 0 and iproc < m_forest->mpisize, + "[AMRmesh::local_num_mirrors] wrong MPI rank value."); + return m_ghost->mirror_proc_offsets[iproc + 1] - m_ghost->mirror_proc_offsets[iproc]; + } + + //! return the num of mirror quadrants in current process. + //! each quadrant is counted only once, not matter if quadrant is involved in several MPI + //! communications. + int32_t + local_num_mirrors_unique() const + { + return m_ghost->mirrors.elem_count; + } + +private: + //! the MAIN p4est object for accessing all mesh information + forest_t * m_forest; + + //! inter-tree connectivity. + //! see https://github.com/cburstedde/p4est/blob/master/src/p4est_connectivity.h#L152 + connectivity_t * m_connectivity; + + //! inter-tree connectivity name (only "brick" is supported by kalypsso currently) + std::string m_connectivity_name; + + //! additional geometrical mapping (x,y,z) => (X,Y,Z) from the connectivity space to another space + //! used to modify/deform a mesh + //! see https://github.com/cburstedde/p4est/blob/master/src/p4est_geometry.h#L59 + //! currently not used nor supported in kalypsso; we only use the brick connectivity without + //! additional mapping. + //! TODO: see any real application might need this. + geometry_t * m_geometry; + + //! geometry name + std::string m_geometry_name; + + //! the main p4est object for handling /transfer MPI ghost quadrant metadata. + //! ghost user data transfer is done elsewhere + ghost_t * m_ghost = nullptr; + + //! TODO: clarify if we need it in kalypsso. + p4est_userdata_t m_p4est_userdata; + + //! helper to initialize a p4est connectivity + connectivity_t * + create_connectivity(const ConfigMap & config_map) + { + m_connectivity_name = config_map.getString("amr", "connectivity", "invalid_connectivity"); + + // if connectivity name is not provided in input parameter file, + // then force it, or maybe we should abort - TBD + if (m_connectivity_name.compare("invalid_connectivity") == 0) + { + m_connectivity_name = dim == 2 ? "shell2d" : "shell"; + std::cout + << "KALYPSSO warning : p4est connectivity missing in input parameter file !!! Please " + "check !\n"; + } + + return p4est_t::my_connectivity_new_byname(m_connectivity_name.c_str(), config_map); + } + + //! helper to initialize a p4est geometry (can be nullptr) + geometry_t * + create_geometry(connectivity_t * conn, const ConfigMap & config_map) + { + m_geometry_name = config_map.getString("amr", "geometry", "no_geometry"); + + return p4est_t::my_geometry_new_byname(m_geometry_name.c_str(), conn, config_map); + } + +}; // class AMRmesh + +// ===================================================================== +// ===================================================================== +// constructor +template +AMRmesh::AMRmesh([[maybe_unused]] ParallelEnv const & par_env, const ConfigMap & config_map) +{ + m_connectivity = create_connectivity(config_map); + + m_geometry = create_geometry(m_connectivity, config_map); + + // initial refinement to reach at least level_min + m_p4est_userdata.level_min = static_cast(config_map.getInteger("amr", "level_min", 4)); + m_p4est_userdata.level_max = static_cast(config_map.getInteger("amr", "level_max", 4)); + m_p4est_userdata.geom = m_geometry; + + m_forest = p4est_t::new_forest( +#ifdef KALYPSSO_CORE_USE_MPI + par_env.mpi_comm(), // MPI communicator +#else + sc_MPI_COMM_WORLD, +#endif + m_connectivity, // tree connectivity + 0, // minimal initial quad per MPI proc + 0, // min level + 0, // fill uniform is false + 0, // sizeof(user_data_t), // currently not used + initial_quad_fn, // no user data init callback function + static_cast(&m_p4est_userdata)); // p4est user data + + p4est_t::refine(m_forest, 1, uniform_refine_fn, initial_quad_fn); + + reset_ghost(); + +} // AMRmesh + +// ===================================================================== +// ===================================================================== +template +AMRmesh::~AMRmesh() +{ + reset_p4est_resources(); +} // ~AMRmesh + +// ===================================================================== +// ===================================================================== +template +void +AMRmesh::reset_p4est_resources() +{ + if (m_ghost != nullptr) + { + p4est_t::ghost_destroy(m_ghost); + m_ghost = nullptr; + } + + // destroy p4est + if (m_forest != nullptr) + { + p4est_t::destroy(m_forest); + m_forest = nullptr; + } + + // destroy geometry + if (m_geometry != nullptr) + { + p4est_t::geometry_destroy(m_geometry); + m_geometry = nullptr; + } + + // destroy connectivity + if (m_connectivity != nullptr) + { + p4est_t::connectivity_destroy(m_connectivity); + m_connectivity = nullptr; + } + +} // AMRmesh::reset_p4est_resources + +// ===================================================================== +// ===================================================================== +template +void +AMRmesh::create_p4est_resources_from_file([[maybe_unused]] ParallelEnv const & par_env, + ConfigMap const & config_map, + std::string const & p4est_filename) +{ + reset_p4est_resources(); + + m_forest = p4est_t::load_ext(p4est_filename.c_str(), +#ifdef KALYPSSO_CORE_USE_MPI + par_env.mpi_comm(), +#else + sc_MPI_COMM_WORLD, +#endif + 0 /* data size */, + 0 /* load_data */, + 1 /* autopartition */, + 1 /* broadcast header */, + nullptr /* user pointer */, + &m_connectivity); + + m_geometry = create_geometry(m_connectivity, config_map); + + // initial refinement to reach at least level_min + m_p4est_userdata.level_min = static_cast(config_map.getInteger("amr", "level_min", 4)); + m_p4est_userdata.level_max = static_cast(config_map.getInteger("amr", "level_max", 4)); + m_p4est_userdata.geom = m_geometry; + + m_forest->user_pointer = static_cast(&m_p4est_userdata); + + reset_ghost(); + +} // AMRmesh::create_p4est_resources_from_file + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_AMRMESH_H_ diff --git a/src/kalypsso/core/AMRmesh_utils.h b/src/kalypsso/core/AMRmesh_utils.h new file mode 100644 index 0000000..1175990 --- /dev/null +++ b/src/kalypsso/core/AMRmesh_utils.h @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file AMRmesh_utils.h + * + */ +#ifndef KALYPSSO_CORE_AMRMESH_UTILS_H +#define KALYPSSO_CORE_AMRMESH_UTILS_H + +#include + +#include + +namespace kalypsso +{ + +// =========================================================== +// =========================================================== +/** + * Compute coordinates in vertex space (i.e. connectivity space) of a point + * from its logical coordinates in [0,1[^dim (given as double) relative to the + * tree it belongs to. + * + * This is only valid when no geometry (in p4est sense) is involved. + * + * \param[in] forest p4est mesh data + * \param[in] which_tree tree id in which octant is + * \param[in] eta logical coordinates vector, all coordinate are in [0,1], + * [0,1]^Dim maps the current tree. + * + * \return vector of coordinates in real space + */ +template +std::array +logical2vertex(typename p4est::Wrapper::forest_t * forest, + p4est::topidx_t which_tree, + std::array eta) +{ + using p4est_t = typename p4est::Wrapper; + + // return value : coordinates after mapping + // from logical space coord (eta) + // to connectivity space coord (xyz) + std::array xyz; + + // number of corners of a tree (4 in 2D, 8 in 3D) + constexpr auto NB_CHILDREN = p4est_t::NB_CHILDREN; + + // array of vertex index for all NB_CHILDREN vertices + // (corners of current tree) + p4est::topidx_t vt[NB_CHILDREN]; + + // vertices coordinates + const double * v = forest->connectivity->vertices; + const p4est::topidx_t * tree_to_vertex = forest->connectivity->tree_to_vertex; + + double & eta_x = eta[0]; + double & eta_y = eta[1]; + double & eta_z = eta[2]; + + // get vertex id for current tree + for (int k = 0; k < NB_CHILDREN; ++k) + { + vt[k] = tree_to_vertex[which_tree * NB_CHILDREN + k]; + } + + for (int j = 0; j < 3; ++j) + { + /* *INDENT-OFF* */ + // clang-format off + xyz[static_cast(j)] = + (1. - eta_z) * ((1. - eta_y) * ((1. - eta_x) * v[3 * vt[0] + j] + + eta_x * v[3 * vt[1] + j]) + + eta_y * ((1. - eta_x) * v[3 * vt[2] + j] + + eta_x * v[3 * vt[3] + j])); + if constexpr (dim == 3) + { + xyz[static_cast(j)] += + eta_z * ((1. - eta_y) * ((1. - eta_x) * v[3 * vt[4] + j] + + eta_x * v[3 * vt[5] + j]) + + eta_y * ((1. - eta_x) * v[3 * vt[6] + j] + + eta_x * v[3 * vt[7] + j])); + } + /* *INDENT-ON* */ + // clang-format on + } + + return xyz; + +} // logical2vertex + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_AMRMESH_UTILS_H diff --git a/src/kalypsso/core/AlternatingDirectionScheduler.cpp b/src/kalypsso/core/AlternatingDirectionScheduler.cpp new file mode 100644 index 0000000..e94a410 --- /dev/null +++ b/src/kalypsso/core/AlternatingDirectionScheduler.cpp @@ -0,0 +1,273 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +/** + * \file AlternatingDirectionScheduler.cpp + */ +#include + +namespace kalypsso +{ + +// ================================================================== +// ================================================================== +AlternatingDirectionType +get_alternating_direction_type(ConfigMap const & config_map) +{ + const auto alternating_direction_type_name = + config_map.getString("amr", "alternating_direction_type", "PERMUTATION"); + auto maybe_value = + AlternatingDirectionType::_from_string_nothrow(alternating_direction_type_name.c_str()); + if (maybe_value) + { + return *maybe_value; + } + else + { + Kokkos::abort("Wrong parameter for amr/alternating_direction_type."); + } + return AlternatingDirectionType::NONE; +} + + +// ================================================================== +// ================================================================== + +// ================================================================== +// ================================================================== +AlternatingDirectionScheduler::AlternatingDirectionScheduler(ConfigMap const & config_map, + size_t dim) + : m_alternating_direction_type(get_alternating_direction_type(config_map)) + , m_dim(dim) + , m_num_phases(0) + , m_num_sweeps(0) + , m_directions() + , m_weights() +{ + if (m_alternating_direction_type == +AlternatingDirectionType::NONE) + { + m_num_phases = 1; + m_num_sweeps = dim; + } + else if (m_alternating_direction_type == +AlternatingDirectionType::PERMUTATION) + { + m_num_phases = dim == 2 ? 2 : 6; + m_num_sweeps = dim; + } + else if (m_alternating_direction_type == +AlternatingDirectionType::RUTH_ORDER3) + { + m_num_phases = dim == 2 ? 2 : 6; + m_num_sweeps = dim == 2 ? 6 : 21; + } + + m_directions = direction_view_t("alternating directions", m_num_phases, m_num_sweeps); + m_weights = weight_view_t("integration weights", m_num_sweeps); + + init_directions(); + init_weights(); +} + +// ================================================================== +// ================================================================== +void +AlternatingDirectionScheduler::init_directions() +{ + + if (m_alternating_direction_type == +AlternatingDirectionType::NONE) + { + m_directions(0, 0) = IX; + m_directions(0, 1) = IY; + if (m_dim == 3) + m_directions(0, 2) = IZ; + } + else if (m_alternating_direction_type == +AlternatingDirectionType::PERMUTATION) + { + if (m_dim == 2) + { + m_directions(0, 0) = IX; + m_directions(0, 1) = IY; + + m_directions(1, 0) = IY; + m_directions(1, 1) = IX; + } + else if (m_dim == 3) + { + m_directions(0, 0) = IX; + m_directions(0, 1) = IY; + m_directions(0, 2) = IZ; + + m_directions(1, 0) = IZ; + m_directions(1, 1) = IY; + m_directions(1, 2) = IX; + + m_directions(2, 0) = IY; + m_directions(2, 1) = IZ; + m_directions(2, 2) = IX; + + m_directions(3, 0) = IX; + m_directions(3, 1) = IZ; + m_directions(3, 2) = IY; + + m_directions(4, 0) = IZ; + m_directions(4, 1) = IX; + m_directions(4, 2) = IY; + + m_directions(5, 0) = IY; + m_directions(5, 1) = IX; + m_directions(5, 2) = IZ; + } + } + else if (m_alternating_direction_type == +AlternatingDirectionType::RUTH_ORDER3) + { + if (m_dim == 2) + { + m_directions(0, 0) = IX; + m_directions(0, 1) = IY; + m_directions(0, 2) = IX; + m_directions(0, 3) = IY; + m_directions(0, 4) = IX; + m_directions(0, 5) = IY; + + m_directions(1, 0) = IY; + m_directions(1, 1) = IX; + m_directions(1, 2) = IY; + m_directions(1, 3) = IX; + m_directions(1, 4) = IY; + m_directions(1, 5) = IX; + } + else if (m_dim == 3) + { + ComponentIndex3D dir1, dir2, dir3; + for (size_t i_phase = 0; i_phase < m_num_phases; ++i_phase) + { + if (i_phase == 0) + { + dir1 = IX; + dir2 = IY; + dir3 = IZ; + } + else if (i_phase == 1) + { + dir1 = IX; + dir2 = IZ; + dir3 = IY; + } + else if (i_phase == 2) + { + dir1 = IY; + dir2 = IX; + dir3 = IZ; + } + else if (i_phase == 3) + { + dir1 = IY; + dir2 = IZ; + dir3 = IX; + } + else if (i_phase == 4) + { + dir1 = IZ; + dir2 = IX; + dir3 = IY; + } + else if (i_phase == 5) + { + dir1 = IZ; + dir2 = IY; + dir3 = IX; + } + m_directions(i_phase, 0) = dir1; + m_directions(i_phase, 1) = dir2; + m_directions(i_phase, 2) = dir1; + m_directions(i_phase, 3) = dir2; + m_directions(i_phase, 4) = dir1; + m_directions(i_phase, 5) = dir2; + m_directions(i_phase, 6) = dir3; + m_directions(i_phase, 7) = dir1; + m_directions(i_phase, 8) = dir2; + m_directions(i_phase, 9) = dir1; + m_directions(i_phase, 10) = dir2; + m_directions(i_phase, 11) = dir1; + m_directions(i_phase, 12) = dir2; + m_directions(i_phase, 13) = dir3; + m_directions(i_phase, 14) = dir1; + m_directions(i_phase, 15) = dir2; + m_directions(i_phase, 16) = dir1; + m_directions(i_phase, 17) = dir2; + m_directions(i_phase, 18) = dir1; + m_directions(i_phase, 19) = dir2; + m_directions(i_phase, 20) = dir3; + } + + } // end dim==3 + + } // end RUTH_ORDER3 + +} // AlternatingDirectionScheduler::init_directions + +// ================================================================== +// ================================================================== +void +AlternatingDirectionScheduler::init_weights() +{ + if (m_alternating_direction_type == +AlternatingDirectionType::NONE) + { + m_weights(0) = ONE_F; + m_weights(1) = ONE_F; + if (m_dim == 3) + m_weights(2) = ONE_F; + } + else if (m_alternating_direction_type == +AlternatingDirectionType::PERMUTATION) + { + m_weights(0) = ONE_F; + m_weights(1) = ONE_F; + if (m_dim == 3) + m_weights(2) = ONE_F; + } + else if (m_alternating_direction_type == +AlternatingDirectionType::RUTH_ORDER3) + { + constexpr real_t a1 = KALYPSSO_NUM(7.0) / KALYPSSO_NUM(24.0); + constexpr real_t a2 = KALYPSSO_NUM(3.0) / KALYPSSO_NUM(4.0); + constexpr real_t a3 = KALYPSSO_NUM(-1.0) / KALYPSSO_NUM(24.0); + + constexpr real_t b1 = KALYPSSO_NUM(2.0) / KALYPSSO_NUM(3.0); + constexpr real_t b2 = KALYPSSO_NUM(-2.0) / KALYPSSO_NUM(3.0); + constexpr real_t b3 = KALYPSSO_NUM(1.0); + + if (m_dim == 2) + { + m_weights(0) = a1; + m_weights(1) = b1; + m_weights(2) = a2; + m_weights(3) = b2; + m_weights(4) = a3; + m_weights(5) = b3; + } + else if (m_dim == 3) + { + m_weights(0) = a1 * a1; + m_weights(1) = a1 * b1; + m_weights(2) = a1 * a2; + m_weights(3) = a1 * b2; + m_weights(4) = a1 * a3; + m_weights(5) = a1 * b3; + m_weights(6) = b1; + m_weights(7) = a2 * a1; + m_weights(8) = a2 * b1; + m_weights(9) = a2 * a2; + m_weights(10) = a2 * b2; + m_weights(11) = a2 * a3; + m_weights(12) = a2 * b3; + m_weights(13) = b2; + m_weights(14) = a3 * a1; + m_weights(15) = a3 * b1; + m_weights(16) = a3 * a2; + m_weights(17) = a3 * b2; + m_weights(18) = a3 * a3; + m_weights(19) = a3 * b3; + m_weights(20) = b3; + } + } +} // AlternatingDirectionScheduler::init_weights + +} // namespace kalypsso diff --git a/src/kalypsso/core/AlternatingDirectionScheduler.h b/src/kalypsso/core/AlternatingDirectionScheduler.h new file mode 100644 index 0000000..f49b1de --- /dev/null +++ b/src/kalypsso/core/AlternatingDirectionScheduler.h @@ -0,0 +1,126 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +/** + * \file AlternatingDirectionScheduler.h + */ +#ifndef KALYPSSO_CORE_ALTERNATINGDIRECTIONSCHEDULER_H_ +#define KALYPSSO_CORE_ALTERNATINGDIRECTIONSCHEDULER_H_ + +#include +#include +#include // for math functions (max, min, ...) + +#include +#include + +#include <../better-enums/enum.h> + +namespace kalypsso +{ +// clang-format off +/** + * An enum type to represent all implemented time integration methods. + * + * - NONE : XY - XY - XY - .... + * - PERMUTATION : XY - YX - XY - .... + * - RUTH_ORDER3 : should provide third order convergence (when reachable) + */ +BETTER_ENUM(AlternatingDirectionType, uint8_t, + NONE = 0, + PERMUTATION = 1, + RUTH_ORDER3 = 2) +// clang-format on + +/** + * Read configuration map to initialize the alternating direction type. + */ +AlternatingDirectionType +get_alternating_direction_type(ConfigMap const & config_map); + +// =================================================================================== +// =================================================================================== +// =================================================================================== +/** + * Helper class for applying numerical scheme using an alternating direction method. + */ +class AlternatingDirectionScheduler +{ +public: + using direction_view_t = Kokkos::View; + using weight_view_t = Kokkos::View; + +private: + //! alternating direction type + AlternatingDirectionType m_alternating_direction_type; + + //! dimension + size_t m_dim; + + //! number of phases. + //! e.g when doing permutations, + //! in 2d there are 2 phases: XY and YZ + //! in 3d there are 6 phases: XYZ, ZXY, YZX, ZYX, XZY and YXZ + size_t m_num_phases; + + //! number of sweeps per phase. + //! currently there are dim steps per phases (but there could be more) + size_t m_num_sweeps; + + //! directions + direction_view_t m_directions; + + //! time integration weights + weight_view_t m_weights; + +public: + AlternatingDirectionScheduler(ConfigMap const & config_map, size_t dim); + + //! alternating direction type + auto + type() const + { + return m_alternating_direction_type; + } + + //! get number of phases + auto + num_phases() const + { + return m_num_phases; + } + + //! get number of sweeps per phase + auto + num_sweeps() const + { + return m_num_sweeps; + } + + //! get direction + ComponentIndex3D + get_direction(size_t iteration, size_t sweep) + { + const auto phase_id = iteration % m_num_phases; + return m_directions(phase_id, sweep); + } + + //! get weight + real_t + get_weight(size_t sweep) + { + return m_weights(sweep); + } + +private: + void + init_directions(); + + void + init_weights(); + +}; // class AlternatingDirectionScheduler + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_ALTERNATINGDIRECTIONSCHEDULER_H_ diff --git a/src/kalypsso/core/BinomialCoef.h b/src/kalypsso/core/BinomialCoef.h new file mode 100644 index 0000000..203f047 --- /dev/null +++ b/src/kalypsso/core/BinomialCoef.h @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file real_type.h + * \brief Define macros to switch single/double precision. + * + */ +#ifndef KALYPSSO_CORE_BINOMIAL_COEF_H_ +#define KALYPSSO_CORE_BINOMIAL_COEF_H_ + +#include +#include + +#include + +namespace kalypsso +{ + +/** + * Compute binomial coefficient. + */ +KOKKOS_INLINE_FUNCTION +constexpr int +binomial_coef(int n, int k) +{ + int res = 1; + if (k > n - k) + k = n - k; + for (int i = 0; i < k; ++i) + { + res *= (n - i); + res /= (i + 1); + } + + return res; + +} // binomial_coef + +/** + * Provide a constexpr storage for binomial coefficients of the Pascal triangle from N=1 up to Nmax. + */ +template +class BinomialCoef +{ +public: + constexpr KOKKOS_INLINE_FUNCTION + BinomialCoef() + : m_pascal_triangle() + { + int i = 0; + for (int N = 1; N <= Nmax; ++N) + { + for (int k = 0; k <= N; ++k) + { + m_pascal_triangle[i] = binomial_coef(N, k); + ++i; + } + } + } + + constexpr KOKKOS_INLINE_FUNCTION int + num_coef() const + { + return static_cast(NUM_COEFS); + } + + constexpr KOKKOS_INLINE_FUNCTION int + operator()(int N, int k) const + { + // static_assert(N <= Nmax, "N is too large"); + // static_assert(k >= 0 and k <= N, "k is invalid"); + + return m_pascal_triangle[(N + 2) * (N - 1) / 2 + k]; + } + +private: + static constexpr size_t NUM_COEFS = (3 + Nmax + 1) * (Nmax - 1) / 2; + int m_pascal_triangle[NUM_COEFS]; +}; // class BinomialCoef + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_BINOMIAL_COEF_H_ diff --git a/src/kalypsso/core/BitFieldHelper.h b/src/kalypsso/core/BitFieldHelper.h new file mode 100644 index 0000000..d5e80d9 --- /dev/null +++ b/src/kalypsso/core/BitFieldHelper.h @@ -0,0 +1,671 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file BitFieldHelper.h + * + * Adapted from svom eclairs software (original author F. Chateau) + */ +#ifndef KALYPSSO_CORE_BITFIELDHELPER_H +#define KALYPSSO_CORE_BITFIELDHELPER_H + +#include + +#include +#include +#include // for CHAR_BIT +#include + +#include + +KALYPSSO_DISABLE_CONVERSION_WARNINGS_PUSH() +KALYPSSO_DISABLE_NVCC_WARNINGS_PUSH() + +namespace kalypsso +{ + +using BitCount = uint8_t; +using BitIdx = uint8_t; + +/** + * This class provides template methods to manipulate bit-fields easily and efficiently. + * + * @note setField, setBit and clearBit do not modify their argument. + */ +template +class BitFieldHelper +{ +public: + using WordBasicType = std::remove_cv_t; + using WordSignedType = std::make_signed_t; + using WordUnsignedType = std::make_unsigned_t; + + /** + * Compile-time mapping that returns the signed or unsigned version of WordType + * depending on the sign of the specified template parameter. + */ + template + using WordTypeMatchingSignOf = + std::conditional_t::value, WordSignedType, WordUnsignedType>; + + static constexpr WordBasicType ZERO = 0; + static constexpr WordBasicType ONE = 1; + static constexpr WordBasicType ALL = ~0; + + static constexpr BitCount WORD_BYTECOUNT = static_cast(sizeof(WordBasicType)); + static constexpr BitCount WORD_BITCOUNT = static_cast(WORD_BYTECOUNT * CHAR_BIT); + + ///@name Compile-time bit-field manipulation. + ///@{ + template + KOKKOS_FUNCTION static constexpr bool + getBit(WordBasicType var); + + template + KOKKOS_FUNCTION static constexpr WordType + clearBit(WordType var); + + template + KOKKOS_FUNCTION static constexpr WordType + setBit(WordType var); + + template + KOKKOS_FUNCTION static constexpr WordType + setBit(WordType var, bool askSet); + + template + KOKKOS_FUNCTION static constexpr WordType + toggleBit(WordType var); + + template + KOKKOS_FUNCTION static auto + foobar(WordType var) -> WordType; + + template + KOKKOS_FUNCTION static constexpr auto + getField(WordBasicType var) -> WordBasicType; + + template + KOKKOS_FUNCTION static constexpr auto + getField(WordBasicType var) -> FieldType; + + template + KOKKOS_FUNCTION static constexpr void + getField(WordBasicType var, FieldType & result); + + template + KOKKOS_FUNCTION static constexpr WordType + setField(WordType var, WordBasicType value); + + template + KOKKOS_FUNCTION static constexpr WordType + toggleField(WordType var); + + template + KOKKOS_FUNCTION static constexpr auto + getReadMask() -> WordUnsignedType; + + template + KOKKOS_FUNCTION static constexpr auto + getClearMask() -> WordUnsignedType; + ///@} + + ///@name Run-time bit-field manipulation. + ///@{ + KOKKOS_FUNCTION static bool + getBit(WordBasicType var, BitIdx pos); + + KOKKOS_FUNCTION static WordType + clearBit(WordType var, BitIdx pos); + + KOKKOS_FUNCTION static WordType + setBit(WordType var, BitIdx pos); + + KOKKOS_FUNCTION static WordType + setBit(WordType var, BitIdx pos, bool askSet); + + KOKKOS_FUNCTION static WordType + toggleBit(WordType var, BitIdx pos); + + KOKKOS_FUNCTION static auto + getField(WordBasicType var, BitIdx pos, BitCount width) -> WordBasicType; + + template + KOKKOS_FUNCTION static FieldType + getField(WordBasicType var, BitIdx pos, BitCount width); + + template + KOKKOS_FUNCTION static void + getField(WordBasicType var, BitIdx pos, BitCount width, FieldType & result); + + KOKKOS_FUNCTION static WordType + setField(WordType var, BitIdx pos, BitCount width, WordBasicType value); + + KOKKOS_FUNCTION static WordType + toggleField(WordType var, BitIdx pos, BitCount width); + + KOKKOS_FUNCTION static auto + getReadMask(BitIdx pos, BitCount width) -> WordUnsignedType; + + KOKKOS_FUNCTION static auto + getClearMask(BitIdx pos, BitCount width) -> WordUnsignedType; + ///@} + + BitFieldHelper() = delete; + ~BitFieldHelper() = delete; +}; + +template +using NativeBitField = BitFieldHelper; + +/** + * Performs a bitwise NOT, or bit-toggling. + * @param value + * @return + */ +template +KOKKOS_INLINE_FUNCTION constexpr T +toggle(T value) +{ + return static_cast(~static_cast>(value)); +} + +/** + * Computes an integer exponential of base 2. + * The result essentially have the nth bit at 1 and the rest at 0. + */ +template +KOKKOS_INLINE_FUNCTION T +exponent2(BitIdx arg) +{ + assert(arg < sizeof(T) * CHAR_BIT); + return static_cast(static_cast(1) << arg); +} + +/** + * Computes an integer exponential of base 2 with compile-time argument. + * The result essentially have the nth bit at 1 and the rest at 0. + */ +template +KOKKOS_INLINE_FUNCTION constexpr T +exponent2() +{ + static_assert(ARG < sizeof(T) * CHAR_BIT, "exponent2 template argument too big for type"); + return static_cast(static_cast(1) << ARG); +} + +/** + * Binary left shift for unsigned values. + * @param value the value to shift + * @param nbits the number of bits to shift + * @return Returns value left-shifted by nbits. + */ +template +KOKKOS_INLINE_FUNCTION T +lhs(T value, std::enable_if_t::value, BitCount> nbits) +{ + assert(nbits <= sizeof(T) * CHAR_BIT); + return static_cast(value << nbits); +} + +/** + * Binary left shift for unsigned values, with compile-time parameter. + * @param value the value to shift + * @tparam NBITS the number of bits to shift + * @return Returns value left-shifted by NBITS. + */ +template ::value, T> = 0> +KOKKOS_INLINE_FUNCTION constexpr T +lhs(T value) +{ + static_assert(NBITS <= sizeof(T) * CHAR_BIT, "lhs template argument too big for type"); + return static_cast(value << NBITS); +} + +/** + * Binary left shift for signed values. + * @param value the value to shift + * @param nbits the number of bits to shift + * @return Returns value left-shifted by nbits. + */ +template +KOKKOS_INLINE_FUNCTION T +lhs(T value, std::enable_if_t::value, BitIdx> nbits) +{ + assert(nbits < sizeof(T) * CHAR_BIT); + return static_cast(static_cast>(value) << nbits); +} + +/** + * Binary left shift for signed values, with compile-time parameter. + * @param value the value to shift + * @tparam NBITS the number of bits to shift + * @return Returns value left-shifted by NBITS. + */ +template ::value, T> = 0> +KOKKOS_INLINE_FUNCTION constexpr T +lhs(T value) +{ + static_assert(NBITS < sizeof(T) * CHAR_BIT, "lhs template argument too big for type"); + return static_cast(static_cast>(value) << NBITS); +} + +/** + * Binary right shift. + * @param value the value to shift + * @param nbits the number of bits to shift + * @return Returns value right-shifted by nbits. + */ +template +KOKKOS_INLINE_FUNCTION T +rhs(T value, BitCount nbits) +{ + assert(nbits < sizeof(T) * CHAR_BIT); + // Here we suppose right shifting signed values will extend the sign. + return value >> nbits; +} + +/** + * Binary right shift, with compile-time parameter. + * @param value the value to shift + * @tparam NBITS the number of bits to shift + * @return Returns value right-shifted by NBITS. + */ +template +KOKKOS_INLINE_FUNCTION constexpr T +rhs(T value) +{ + static_assert(NBITS < sizeof(T) * CHAR_BIT, "rhs template argument too big for type"); + // Here we suppose right shifting signed values will extend the sign. + return value >> NBITS; +} + +/** + * Swaps the bytes of a 8-bits unsigned integer. + * Basically does nothing, only here for covering the whole set of integer types. + * @param value the value to swap. + * @return Returns the swapped integer. + */ +KOKKOS_INLINE_FUNCTION constexpr uint8_t +byteSwap(const uint8_t value) +{ + return value; +} + +/** + * Swaps the bytes of a 16-bits unsigned integer. + * @param value the value to swap. + * @return Returns the swapped integer. + */ +KOKKOS_INLINE_FUNCTION constexpr uint16_t +byteSwap(const uint16_t value) +{ + return NativeBitField::getField<8, 8>(value) | + lhs<8>(NativeBitField::getField<0, 8>(value)); +} + +/** + * Swaps the bytes of a 32-bits unsigned integer. + * @param value the value to swap. + * @return Returns the swapped integer. + */ +KOKKOS_INLINE_FUNCTION constexpr uint32_t +byteSwap(const uint32_t value) +{ + return NativeBitField::getField<24, 8>(value) | + lhs<8>(NativeBitField::getField<16, 8>(value)) | + lhs<16>(NativeBitField::getField<8, 8>(value)) | + lhs<24>(NativeBitField::getField<0, 8>(value)); +} + +/** + * Swaps the bytes of a 64-bits unsigned integer. + * @param value the value to swap. + * @return Returns the swapped integer. + */ +KOKKOS_INLINE_FUNCTION constexpr uint64_t +byteSwap(const uint64_t value) +{ + return NativeBitField::getField<56, 8>(value) | + lhs<8>(NativeBitField::getField<48, 8>(value)) | + lhs<16>(NativeBitField::getField<40, 8>(value)) | + lhs<24>(NativeBitField::getField<32, 8>(value)) | + lhs<32>(NativeBitField::getField<24, 8>(value)) | + lhs<40>(NativeBitField::getField<16, 8>(value)) | + lhs<48>(NativeBitField::getField<8, 8>(value)) | + lhs<56>(NativeBitField::getField<0, 8>(value)); +} + +/** + * Swaps the bytes of any signed integer. + * As the standard states that converting a negative number to unsigned uses the next congruent + * positive value, which changes nothing at the underlying twos-complement representation, we can + * just use the unsigned version of the byteSwap and performs casts to make the compiler happy. + * @param value + * @return + */ +template ::value, T> = 0> +KOKKOS_INLINE_FUNCTION constexpr T +byteSwap(T value) +{ + return static_cast(byteSwap(static_cast>(value))); +} + + +// ============================================================================= +// BitFieldHelper inline methods body +// ----------------------------------------------------------------------------- + +/** + * Returns the value of a bit of an integer variable (compile-time version). + */ +template +template +KOKKOS_FUNCTION constexpr bool +BitFieldHelper::getBit(WordBasicType var) +{ + return (var & exponent2()) != 0; +} + +/** + * Returns the value of a bit of an integer variable. + */ +template +KOKKOS_FUNCTION bool +BitFieldHelper::getBit(WordBasicType var, BitIdx pos) +{ + return (var & exponent2(pos)) != 0; +} + +/** + * Clears the bit at the specified compile-time position. + */ +template +template +KOKKOS_FUNCTION constexpr WordType +BitFieldHelper::clearBit(WordType var) +{ + return var & toggle(exponent2()); +} + +/** + * Clears the bit at the specified parameter position. + */ +template +KOKKOS_FUNCTION WordType +BitFieldHelper::clearBit(WordType var, BitIdx pos) +{ + return var & toggle(exponent2(pos)); +} + +/** + * Sets the bit at the specified compile-time position. + */ +template +template +KOKKOS_FUNCTION constexpr WordType +BitFieldHelper::setBit(WordType var) +{ + return var | exponent2(); +} + +/** + * Sets the bit at the specified position in parameter. + */ +template +KOKKOS_FUNCTION WordType +BitFieldHelper::setBit(WordType var, BitIdx pos) +{ + return var | exponent2(pos); +} + +/** + * Sets or clears a bit of a variable (compile-time version). + */ +template +template +KOKKOS_FUNCTION constexpr WordType +BitFieldHelper::setBit(WordType var, bool askSet) +{ + if (askSet) + { + return setBit(var); + } + else + { + return clearBit(var); + } +} + +template +template +KOKKOS_FUNCTION constexpr WordType +BitFieldHelper::toggleBit(WordType var) +{ + return var ^ exponent2(); +} + +/** + * Sets or clears a bit of a variable. + */ +template +KOKKOS_FUNCTION WordType +BitFieldHelper::setBit(WordType var, BitIdx pos, bool askSet) +{ + if (askSet) + { + return setBit(var, pos); + } + else + { + return clearBit(var, pos); + } +} + +template +KOKKOS_FUNCTION WordType +BitFieldHelper::toggleBit(WordType var, BitIdx pos) +{ + return var ^ exponent2(pos); +} + +/** + * Returns the value of a bit-field, with the same type as the input (compile-time version). + */ +template +template +KOKKOS_FUNCTION WordType +BitFieldHelper::foobar(WordType var) +{ + WordBasicType result = 0; // init should not be necessary but BCC2 requires it. + getField(var, result); + return result; +} + +/** + * Returns the value of a bit-field, with the same type as the input (compile-time version). + */ +template +template +KOKKOS_FUNCTION constexpr auto +BitFieldHelper::getField(WordBasicType var) -> WordBasicType +{ + WordBasicType result = 0; // init should not be necessary but BCC2 requires it. + getField(var, result); + return result; +} +/** + * Returns the value of a bit-field, with the same type as the input. + */ +template +KOKKOS_FUNCTION auto +BitFieldHelper::getField(WordBasicType var, BitIdx pos, BitCount width) -> WordBasicType +{ + WordBasicType result; + getField(var, pos, width, result); + return result; +} + +/** + * Returns the value of a bit-field, allowing the field to have a different type (compile-time + * version). + */ +template +template +KOKKOS_FUNCTION constexpr FieldType +BitFieldHelper::getField(WordBasicType var) +{ + FieldType result{}; // init should not be necessary but BCC2 requires it. + getField(var, result); + return result; +} + +/** + * Returns the value of a bit-field, allowing the field to have a different type. + */ +template +template +KOKKOS_FUNCTION FieldType +BitFieldHelper::getField(WordBasicType var, BitIdx pos, BitCount width) +{ + FieldType result; + getField(var, pos, width, result); + return result; +} + +/** + * Retrieves the value of a bit-field (compile-time version). + * The field does not need to have the same type (or same signed-ness) as the input. + */ +template +template +KOKKOS_FUNCTION constexpr void +BitFieldHelper::getField(WordBasicType var, FieldType & result) +{ + using SignCorrectWordType = WordTypeMatchingSignOf; + + const WordBasicType leftMost = lhs(var); + const SignCorrectWordType leftMostSignCorrect = static_cast(leftMost); + result = static_cast(rhs(leftMostSignCorrect)); +} + +/** + * Retrieves the value of a bit-field. + * The field does not need to have the same type (or same signed-ness) as the input. + */ +template +template +KOKKOS_FUNCTION void +BitFieldHelper::getField(WordBasicType var, + BitIdx pos, + BitCount width, + FieldType & result) +{ + using SignCorrectWordType = WordTypeMatchingSignOf; + + const WordBasicType leftMost = lhs(var, WORD_BITCOUNT - width - pos); + const SignCorrectWordType leftMostSignCorrect = static_cast(leftMost); + result = static_cast(rhs(leftMostSignCorrect, WORD_BITCOUNT - width)); +} + +/** + * Sets the value of a bit-field of a variable (compile-time version). + */ +template +template +KOKKOS_FUNCTION constexpr WordType +BitFieldHelper::setField(WordType var, WordBasicType value) +{ + const WordUnsignedType clearedOld = + static_cast(var) & getClearMask(); + const WordUnsignedType shiftedNew = + static_cast(lhs(value)) & getReadMask(); + return static_cast(clearedOld | shiftedNew); +} + +/** + * Sets the value of a bit-field of a variable. + */ +template +KOKKOS_FUNCTION WordType +BitFieldHelper::setField(WordType var, BitIdx pos, BitCount width, WordBasicType value) +{ + const WordUnsignedType clearedOld = static_cast(var) & getClearMask(pos, width); + const WordUnsignedType shiftedNew = + static_cast(lhs(value, pos)) & getReadMask(pos, width); + return static_cast(clearedOld | shiftedNew); +} + +template +template +KOKKOS_FUNCTION constexpr WordType +BitFieldHelper::toggleField(WordType var) +{ + const WordUnsignedType value = static_cast(var); + const WordUnsignedType toggled = value ^ getReadMask(); + return static_cast(toggled); +} + +template +KOKKOS_FUNCTION WordType +BitFieldHelper::toggleField(WordType var, BitIdx pos, BitCount width) +{ + const WordUnsignedType value = static_cast(var); + const WordUnsignedType toggled = value ^ getReadMask(pos, width); + return static_cast(toggled); +} + +/** + * Returns the mask used to isolate the bit-field range, in order to read it (compile-time + * version). Protection against the machine-dependent case (and seen at run-time) for which width + * == size of the word type + */ +template +template +KOKKOS_FUNCTION constexpr typename BitFieldHelper::WordUnsignedType +BitFieldHelper::getReadMask() +{ + return lhs(toggle(lhs(ALL))); +} + +/** + * Returns the mask used to isolate the bit-field range, in order to read it. + * Protection against the machine-dependent case (and seen at run-time) + * for which width == size of the word type + */ +template +KOKKOS_FUNCTION auto +BitFieldHelper::getReadMask(BitIdx pos, BitCount width) -> WordUnsignedType +{ + return lhs(toggle(lhs(ALL, width)), pos); +} + +/** + * Returns the mask used to exclude the bit-field range, in order to clear it (compile-time + * version). + */ +template +template +KOKKOS_FUNCTION constexpr auto +BitFieldHelper::getClearMask() -> WordUnsignedType +{ + return toggle(getReadMask()); +} + +/** + * Returns the mask used to exclude the bit-field range, in order to clear it. + */ +template +KOKKOS_FUNCTION auto +BitFieldHelper::getClearMask(BitIdx pos, BitCount width) -> WordUnsignedType +{ + return toggle(getReadMask(pos, width)); +} + +} // namespace kalypsso + +KALYPSSO_DISABLE_CONVERSION_WARNINGS_POP() +KALYPSSO_DISABLE_NVCC_WARNINGS_POP() + +#endif // KALYPSSO_CORE_BITFIELDHELPER_H diff --git a/src/kalypsso/core/BitFieldInteger.h b/src/kalypsso/core/BitFieldInteger.h new file mode 100644 index 0000000..955546e --- /dev/null +++ b/src/kalypsso/core/BitFieldInteger.h @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file BitFieldInteger.h + * + * Adapted from svom eclairs software (original author F. Chateau) + */ + +#ifndef KALYPSSO_CORE_BITFIELDINTEGER_H_ +#define KALYPSSO_CORE_BITFIELDINTEGER_H_ + +#include "BitFieldHelper.h" + +#include + +namespace kalypsso +{ + +/** + * This should be used as the base class of datatypes that implements a bit field semantics. + * + * It is designed to be used in conjunction with DECLARE_BIT and DECLARE_FIELD + * macros. These macro must be called in the subclass body. They generate + * accessors for each bit and fields. + */ +template +class BitFieldInteger +{ +public: + using WordType = T; + using WordBasicType = std::remove_cv_t; + + KOKKOS_DEFAULTED_FUNCTION constexpr BitFieldInteger() = default; +}; + +} // namespace kalypsso + +#define DECLARE_RO_BIT(name, pos) \ + static KOKKOS_INLINE_FUNCTION constexpr bool name(WordBasicType readValue) \ + { \ + return BitFieldHelper::getBit<(pos)>(readValue); \ + } + +#define DECLARE_WO_BIT(name, pos) \ + static KOKKOS_INLINE_FUNCTION constexpr WordBasicType cset_##name(WordBasicType readValue, \ + bool val) \ + { \ + return BitFieldHelper::setBit<(pos)>(readValue, val); \ + } \ + static KOKKOS_INLINE_FUNCTION void set_##name(WordBasicType & readValue, bool val) \ + { \ + readValue = BitFieldHelper::setBit<(pos)>(readValue, val); \ + } + +#define DECLARE_BIT(name, pos) \ + DECLARE_RO_BIT(name, (pos)) \ + DECLARE_WO_BIT(name, (pos)) + +#define DECLARE_CASTED_RO_FIELD(name, pos, width, type) \ + static KOKKOS_INLINE_FUNCTION constexpr type name(WordBasicType readValue) \ + { \ + return BitFieldHelper::getField<(pos), (width), type>(readValue); \ + } + +#define DECLARE_CASTED_WO_FIELD(name, pos, width, type) \ + static KOKKOS_INLINE_FUNCTION constexpr WordBasicType cset_##name(WordBasicType readValue, \ + type val) \ + { \ + return BitFieldHelper::setField<(pos), (width)>(readValue, \ + static_cast(val)); \ + } \ + static KOKKOS_INLINE_FUNCTION void set_##name(WordBasicType & readValue, type val) \ + { \ + readValue = BitFieldHelper::setField<(pos), (width)>( \ + readValue, static_cast(val)); \ + } + +#define DECLARE_CASTED_FIELD(name, pos, width, type) \ + DECLARE_CASTED_RO_FIELD(name, (pos), (width), type) \ + DECLARE_CASTED_WO_FIELD(name, (pos), (width), type) + +#define DECLARE_RO_FIELD(name, pos, width) \ + DECLARE_CASTED_RO_FIELD(name, (pos), (width), WordBasicType) + +#define DECLARE_WO_FIELD(name, pos, width) \ + DECLARE_CASTED_WO_FIELD(name, (pos), (width), WordBasicType) + +#define DECLARE_FIELD(name, pos, width) \ + DECLARE_RO_FIELD(name, (pos), (width)) \ + DECLARE_WO_FIELD(name, (pos), (width)) + +#endif /* KALYPSSO_CORE_BITFIELDINTEGER_H_ */ diff --git a/src/kalypsso/core/CMakeLists.txt b/src/kalypsso/core/CMakeLists.txt new file mode 100644 index 0000000..a664916 --- /dev/null +++ b/src/kalypsso/core/CMakeLists.txt @@ -0,0 +1,296 @@ +# ######################################################################################## +# ##### KALYPSSO_CORE_CONFIG ###### +# ######################################################################################## +add_library(kalypsso_core_config INTERFACE) + +# setup include path for including kalypsso_core_config.h +target_include_directories( + kalypsso_core_config + INTERFACE $ + $ + $) + +add_library(kalypsso::core_config ALIAS kalypsso_core_config) + +# ################################################################################################## +# ##### KALYPSSO_CORE ###### +# ################################################################################################## +add_library( + kalypsso_core + AlternatingDirectionScheduler.cpp + AlternatingDirectionScheduler.h + AMRContext.h + AMRCycleDriver.h + amr_hashmap.h + amr_key.h + AMRmesh.h + AMRMeshInfo.h + AMRMeshMonitoring.cpp + AMRMeshMonitoring.h + AMRmesh_utils.h + BinomialCoef.h + BitFieldHelper.h + BitFieldInteger.h + brick_base.h + brick_connectivity_utils.h + brick_utils.h + CheckEdgeSiblingsConnectivity.cpp + CheckEdgeSiblingsConnectivity.h + CheckFaceBorderCompatibility.cpp + CheckFaceBorderCompatibility.h + cmdline_utils.h + ComputeConformalFullStatus.h + ComputeConformalStatus.h + ComputeCurvature.cpp + ComputeCurvature.h + ComputeDivergence.cpp + ComputeDivergence.h + ComputeError.h + ComputeFilteredCurvature.cpp + ComputeFilteredCurvature.h + ComputeGhostedScalarField.cpp + ComputeGhostedScalarField.h + ComputeInterfaceNormalVector.cpp + ComputeInterfaceNormalVector.h + ComputeRefineFlags.h + ComputeRefineFlags.cpp + ComputeRefineFlags_utils.h + ComputeSchlieren.h + ComputeSchlieren.cpp + ComputeSmoothInterfaceFunction.cpp + ComputeSmoothInterfaceFunction.h + ComputeVolumeIntegralValue.h + ComputeVolumeIntegralValue.cpp + config_utils.h + ConformalFaceStatus.h + ConformalFullStatus.h + ConformalNeighborStatus.h + ConservativityCheck.cpp + ConservativityCheck.h + DataArray.h + DataArrayBlock.h + DataArrayBlockMultiVar.h + DataArrayBlock_utils.h + DataArrayGhostedBlock.h + DataArrayGhostedBlock_old.h + DataArrayUtils.cpp + DataArrayUtils.h + EdgeDataArrayBlock.h + EdgeDataArrayBlock_utils.h + enums.h + eos/eos_utils.h + eos/IdealGasEos.h + eos/IdealGasMixture.h + eos/StiffenedGasEos.h + eos/StiffenedGasMixture.h + eos/VanDerWaalsGasEos.h + ExtractNonGhostedArray.h + FaceDataArrayBlock.h + FaceDataArrayBlock_utils.h + FieldMap.h + FillBlockGhosts_common.h + FillBlockGhostCells.cpp + FillBlockGhostCells.h + FillBlockGhostCellsInPlace.cpp + FillBlockGhostCellsInPlace.h + FillBlockGhostCells_v2.cpp + FillBlockGhostCells_v2.h + FillBlockGhostFaces.cpp + FillBlockGhostFaces.h + FillBlockGhostFluxes.cpp + FillBlockGhostFluxes.h + FillBlockGhosts_common.h + FillOutside_utils.h + FiniteDifferenceData.h + FirstOrderDerivativeFiniteDifference.cpp + FirstOrderDerivativeFiniteDifference.h + geometry_utils.h + GravityField.h + GravityField.cpp + HDF5_IO_common.h + HDF5_Xdmf_Writer.h + HDF5_Xdmf_Writer_legacy.h + HydroParams.cpp + HydroParams.h + init_func.h + InterfaceNormalVectorAlgorithmParams.h + io_utils.cpp + io_utils.h + kalypsso_comm_config.h + kalypsso_core_base.h + kalypsso_core_build_info.h + kalypsso_core_build_info.cpp + kalypsso_core_git_info.h + kalypsso_core_git_info.cpp + kalypsso_data_container.h + kalypsso_macros.h + Kokkos_Array_extensions.h + Kokkos_extensions.h + kokkos_shared.h + LinearCombination.h + Locations.h + MaterialPresence.h + MeshMap.h + MeshMap.cpp + MeshPartitioner.h + MeshPartitioner_helper.h + mesh_utils.h + misc_utils.cpp + misc_utils.h + models/Hydro.cpp + models/Hydro.h + models/HydroSettings.cpp + models/HydroSettings.h + models/HydroState.h + models/MHD.cpp + models/MHD.h + models/MHDSettings.cpp + models/MHDSettings.h + models/MHDState.h + models/riemann_solver_types.h + models/RiemannSolvers.h + models/RiemannSolvers_MHD.h + models/mhd_utils.h + models/utils_hydro.h + morton_utils.h + multimaterial_utils.h + MultiMatFillBlockGhostCells.h + myKokkos_Sort.h + orchard_key_base.h + orchard_key.h + orchard_key_impl_2d.h + orchard_key_impl_3d.h + orchard_key_utils.h + OutsideQuadsInfo.h + p4est_utils.h + physical_constants.h + Point.h + problems/init_cond_utils.cpp + problems/init_cond_utils.h + problems/AlfvenParams.h + problems/BlastParams.h + problems/BreakingWaveParams.h + problems/DiamagCavityParams.h + problems/DoubleMachReflectionParams.h + problems/DropletAdvectionParams.h + problems/FieldLoopAdvectionParams.h + problems/FourQuadrantParams.h + problems/GreshoVortexParams.h + problems/ImplodeParams.h + problems/initRiemannConfig2d.cpp + problems/initRiemannConfig2d.h + problems/IsentropicVortexParams.h + problems/KHParams.h + problems/MHDShockTubeParams.h + problems/OrszagTangParams.h + problems/RayleighTaylorParams.h + problems/ShuOsherParams.h + problems/SodParams.h + problems/StaticDropletParams.h + problems/TriplePointParams.h + problems/TwoFluidShockTubeParams.h + problems/UnderwaterExplosionParams.h + problems/WoodwardParams.h + prolongation.h + real_type.h + region_utils.h + scan_utils.h + SimpleVTKIO.h + SmoothInterfaceFunctionData.cpp + SmoothInterfaceFunctionData.h + SolverBase.cpp + SolverBase.h + StencilHelper.h + StencilHelper.cpp + THINCParams.h + TimeIntegratorConfig.h + UserDataRemapper.cpp + UserDataRemapper.h + UserDataRemapperImplBCC.cpp + UserDataRemapperImplBCC.h + UserDataRemapperImplBFC.cpp + UserDataRemapperImplBFC.h + UserDataRemapperImplMD.cpp + UserDataRemapperImplMD.h + UserDataRemapperImplMP.cpp + UserDataRemapperImplMP.h + utils_block.h + vof/interface_tracking_utils.h + vof/interface_tracking_utils.cpp + vof/youngs.h) + +if(KALYPSSO_CORE_USE_MPI) + target_sources( + kalypsso_core + PRIVATE MaterialPresenceExchanger.cpp MaterialPresenceExchanger.h MeshGhostsExchanger.cpp + MeshGhostsExchanger.h MeshGhostsExchangerMultiVar.cpp MeshGhostsExchangerMultiVar.h) +endif(KALYPSSO_CORE_USE_MPI) + +if(KALYPSSO_CORE_USE_CPPTRACE) + target_sources(kalypsso_core PRIVATE cpptrace_utils.h cpptrace_utils.cpp) +endif(KALYPSSO_CORE_USE_CPPTRACE) + +if(KALYPSSO_CORE_USE_HDF5) + target_sources(kalypsso_core PRIVATE HDF5_IO_common.cpp) +endif(KALYPSSO_CORE_USE_HDF5) + +if(KALYPSSO_CORE_USE_CNPY) + target_sources(kalypsso_core PRIVATE ComputeDataSliceAlongLine.cpp ComputeDataSliceAlongLine.h) + + target_sources(kalypsso_core PUBLIC $ + $) +endif(KALYPSSO_CORE_USE_CNPY) + +target_link_libraries(kalypsso_core PRIVATE kalypsso_warnings) + +target_link_libraries( + kalypsso_core PUBLIC kalypsso_core_config better-enums::better-enums kalypsso::monitoring + kalypsso::log Kokkos::kokkos HighFive::HighFive) + +if(KALYPSSO_CORE_USE_MPI) + target_link_libraries(kalypsso_core PUBLIC kalypsso::p4est_wrap kalypsso::mpiUtils) +endif(KALYPSSO_CORE_USE_MPI) + +if(KALYPSSO_CORE_USE_HDF5) + target_link_libraries(kalypsso_core PUBLIC ${HDF5_LIBRARIES}) +endif(KALYPSSO_CORE_USE_HDF5) + +if(KALYPSSO_CORE_USE_CNPY) + target_link_libraries(kalypsso_core PUBLIC cnpy::cnpy) +endif(KALYPSSO_CORE_USE_CNPY) + +if(KALYPSSO_CORE_USE_CPPTRACE) + target_link_libraries(kalypsso_core PUBLIC cpptrace::cpptrace) + + add_executable(kalypsso_stack_printer kalypsso_stack_printer.cpp) + target_link_libraries(kalypsso_stack_printer PUBLIC cpptrace::cpptrace) +endif(KALYPSSO_CORE_USE_CPPTRACE) + +# declare library alias +add_library(kalypsso::core ALIAS kalypsso_core) + +# +# Get prepared for install +# +include(${PROJECT_SOURCE_DIR}/cmake/setup_install.cmake) + +# +# install libraries and headers +# +install( + TARGETS kalypsso_core kalypsso_core_config + EXPORT kalypsso-targets + LIBRARY DESTINATION ${INSTALL_LIBDIR} + ARCHIVE DESTINATION ${INSTALL_LIBDIR} + RUNTIME DESTINATION ${INSTALL_BINDIR}) + +# +# install headers (exclude cpp files) +# +install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DESTINATION ${INSTALL_INCLUDEDIR}/kalypsso + FILES_MATCHING + PATTERN "*.h" + PATTERN "*.cpp" EXCLUDE + PATTERN "*.in" EXCLUDE) diff --git a/src/kalypsso/core/CheckEdgeSiblingsConnectivity.cpp b/src/kalypsso/core/CheckEdgeSiblingsConnectivity.cpp new file mode 100644 index 0000000..7adac00 --- /dev/null +++ b/src/kalypsso/core/CheckEdgeSiblingsConnectivity.cpp @@ -0,0 +1,339 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file CheckEdgeSiblingsConnectivity.cpp + */ +#include + +namespace kalypsso +{ + +namespace core +{ + +/*******************************************************************************/ +/*******************************************************************************/ +/*******************************************************************************/ +template +CheckEdgeSiblingsConnectivity::CheckEdgeSiblingsConnectivity( + block_size_t bSize, + StencilHelper_t stencil_helper, + orchard_key_view_t orchard_keys, + AMRMeshInfo amr_mesh_info, + ConfigMap const & config_map) + : m_block_sizes(bSize) + , m_stencil_helper(stencil_helper) + , m_orchard_keys_device(orchard_keys) + , m_amr_mesh_info(amr_mesh_info) + , m_scaling_factor(get_scaling_factor(config_map)) + , m_xyz_min(get_xyz_min(config_map)) + , m_edge_flat_index_offsets(compute_edge_flat_index_offsets_emf(bSize)) +{} // constructor + +// ============================================================== +// ============================================================== +template +void +CheckEdgeSiblingsConnectivity::apply(block_size_t bSize, + amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + AMRMeshInfo amr_mesh_info, + ConfigMap const & config_map, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic) +{ + + auto stencil_helper = + StencilHelper_t(amr_hashmap, orchard_keys, bSize, brick_sizes, is_brick_periodic); + + CheckEdgeSiblingsConnectivity functor( + bSize, stencil_helper, orchard_keys, amr_mesh_info, config_map); + + // number of owned quadrant x total number of edges (along Z only in 2d, or along all dir in 3D) + // be careful emf is sized upon block_size + 1 in all direction, but + // actually it is only required in the edge transverse directions, not in the edge longitudinal + // direction + const auto edge_flat_index_offsets = compute_edge_flat_index_offsets_emf(bSize); + const auto nbIterations = amr_mesh_info.local_num_quadrants() * edge_flat_index_offsets[3]; + + // launch computation + Kokkos::parallel_for("kalypsso::core::CheckEdgeSiblingsConnectivity", + Kokkos::RangePolicy(0, nbIterations), + functor); + +} // apply + +// ==================================================================== +// ==================================================================== +template +KOKKOS_INLINE_FUNCTION bool +CheckEdgeSiblingsConnectivity::are_location_not_near( + Kokkos::Array const & xyz0, + Kokkos::Array const & xyz1) const +{ + bool problem = (fabs(xyz0[IX] - xyz1[IX]) > SMALL_F) or (fabs(xyz0[IY] - xyz1[IY]) > SMALL_F); + if constexpr (dim == 3) + { + problem = problem or (fabs(xyz0[IZ] - xyz1[IZ]) > SMALL_F); + } + + return problem; +} + +// ==================================================================== +// ==================================================================== +template +KOKKOS_INLINE_FUNCTION void +CheckEdgeSiblingsConnectivity::expect_near_xyz( + EdgeLocation const & edge_loc0, + Kokkos::Array const & xyz0, + Kokkos::Array const & xyz1) const +{ + if (are_location_not_near(xyz0, xyz1)) + { + if constexpr (dim == 2) + { + KOKKOS_IF_ON_HOST((KALYPSSO_ERROR("INVALID edge sibling location found at : i={} j={} " + "edge_dir={} iOct={} | xyz0={} {} | xyz1={} {}", + edge_loc0.ijk[IX], + edge_loc0.ijk[IY], + edge_loc0.ijk[dim], + edge_loc0.iOct, + xyz0[IX], + xyz0[IY], + xyz1[IX], + xyz1[IY]);)) + KOKKOS_IF_ON_DEVICE((Kokkos::printf("INVALID edge sibling location found at: i=%d j=%d " + "edge_dir=%d iOct=%d | xyz0=%f %f | xyz1=%f %f\n", + edge_loc0.ijk[IX], + edge_loc0.ijk[IY], + edge_loc0.ijk[dim], + edge_loc0.iOct, + xyz0[IX], + xyz0[IY], + xyz1[IX], + xyz1[IY]);)) + } + else if constexpr (dim == 3) + { + KOKKOS_IF_ON_HOST((KALYPSSO_ERROR("INVALID edge sibling location found at : i={} j={} k={} " + "edge_dir={} iOct={} | xyz0={} {} {} | xyz1={} {} {}", + edge_loc0.ijk[IX], + edge_loc0.ijk[IY], + edge_loc0.ijk[IZ], + edge_loc0.ijk[dim], + edge_loc0.iOct, + xyz0[IX], + xyz0[IY], + xyz0[IZ], + xyz1[IX], + xyz1[IY], + xyz1[IZ]);)) + KOKKOS_IF_ON_DEVICE((Kokkos::printf("INVALID edge sibling location found at: i=%d j=%d k=%d " + "edge_dir=%d iOct=%d | xyz0=%f %f %f | xyz1=%f %f %f\n", + edge_loc0.ijk[IX], + edge_loc0.ijk[IY], + edge_loc0.ijk[IZ], + edge_loc0.ijk[dim], + edge_loc0.iOct, + xyz0[IX], + xyz0[IY], + xyz0[IZ], + xyz1[IX], + xyz1[IY], + xyz1[IZ]);)) + } + } +} // expect_near_xyz + +// ==================================================================== +// ==================================================================== +template +KOKKOS_INLINE_FUNCTION void +CheckEdgeSiblingsConnectivity::expect_near_same_level( + EdgeLocation const & edge_loc0, + EdgeLocation const & edge_loc1) const +{ + + const auto key0 = m_stencil_helper.key(edge_loc0.iOct); + const auto key1 = m_stencil_helper.key(edge_loc1.iOct); + + const auto xyz0 = orchard_key_to_edgecenter_real_space( + key0, edge_loc0.ijk, m_block_sizes, m_scaling_factor, m_xyz_min); + const auto xyz1 = orchard_key_to_edgecenter_real_space( + key1, edge_loc1.ijk, m_block_sizes, m_scaling_factor, m_xyz_min); + + expect_near_xyz(edge_loc0, xyz0, xyz1); + +} // expect_near_same_level + +// ==================================================================== +// ==================================================================== +template +KOKKOS_INLINE_FUNCTION void +CheckEdgeSiblingsConnectivity::expect_near_finer_level( + EdgeLocation const & edge_loc, + EdgeLocation const & edge_loc0) const +{ + if constexpr (dim == 3) + { + + const auto key = m_stencil_helper.key(edge_loc.iOct); + const auto xyz = orchard_key_to_edgecenter_real_space( + key, edge_loc.ijk, m_block_sizes, m_scaling_factor, m_xyz_min); + + // get xyz in the 2 small edges + const auto key0 = m_stencil_helper.key(edge_loc0.iOct); + + KOKKOS_ASSERT((edge_loc0.level() > edge_loc.level()) && "edge_loc0 must be at finer AMR level"); + + const auto xyz0a = orchard_key_to_edgecenter_real_space( + key0, edge_loc0.ijk, m_block_sizes, m_scaling_factor, m_xyz_min); + + // unit vector along edge direction + const auto v2 = get_unit_vector(edge_loc0.ijk[dim]); + + auto edge_loc0b = edge_loc0; + edge_loc0b.ijk[IX] += v2[IX]; + edge_loc0b.ijk[IY] += v2[IY]; + edge_loc0b.ijk[IZ] += v2[IZ]; // legal since in we are in 3d + const auto xyz0b = orchard_key_to_edgecenter_real_space( + key0, edge_loc0b.ijk, m_block_sizes, m_scaling_factor, m_xyz_min); + // middle of the 2 small edges + const auto xyz0 = 0.5 * (xyz0a + xyz0b); + + // current and and the middle of the 2 small edges should be near + expect_near_xyz(edge_loc, xyz, xyz0); + } +} // expect_near_finer_level + +// ==================================================================== +// ==================================================================== +template +KOKKOS_INLINE_FUNCTION void +CheckEdgeSiblingsConnectivity::check(index_t const & edge_flatindex, + int32_t const & iOct) const +{ + + auto const edge_indexes = + edge_flat_index_unravel_emf(edge_flatindex, m_block_sizes, m_edge_flat_index_offsets); + + if constexpr (dim == 2) + { + KOKKOS_ASSERT(edge_indexes[dim] == IZ && "EMF can only be along Z in 2D"); + } + + // current block AMR key + const auto key_cur = m_stencil_helper.key(iOct); + + // create an edge location for current edge's cell (just round down edge index in transverse + // direction) + const EdgeLocation_t edge_loc{ edge_indexes, key_cur, iOct, false }; + const auto level = edge_loc.level(); + + // get sibling edge locations + const auto edge_loc0 = m_stencil_helper.getEdgeSiblingLoc(edge_loc, 0); + const auto edge_loc1 = m_stencil_helper.getEdgeSiblingLoc(edge_loc, 1); + const auto edge_loc2 = m_stencil_helper.getEdgeSiblingLoc(edge_loc, 2); + + const auto v = + get_edge_outside_unit_vector(edge_indexes, m_block_sizes, EdgeNormalType::DIAGONAL); + + auto norm_v = v[IX] * v[IX] + v[IY] * v[IY]; + if constexpr (dim == 3) + norm_v += v[IZ] * v[IZ]; + + // determine if emf at current edge location must be corrected + // i.e. if neighbor is at finer AMR level + // + // NB: an outside neighbor is always at same AMR level as current block + + const bool is_not_at_domain_border = + !m_stencil_helper.is_edge_location_at_domain_border(edge_loc); + + if (norm_v > 0 and is_not_at_domain_border) + { + const bool isNotOutside0 = + edge_loc0.iOct < (m_amr_mesh_info.local_num_quadrants() + m_amr_mesh_info.local_num_ghosts()); + + if (edge_loc0.is_valid and isNotOutside0) + { + if (edge_loc0.level() == level) + { + expect_near_same_level(edge_loc, edge_loc0); + } + if constexpr (dim == 3) + { + if (edge_loc0.level() > level) + { + expect_near_finer_level(edge_loc, edge_loc0); + } + } + } + + const bool isNotOutside1 = + edge_loc1.iOct < (m_amr_mesh_info.local_num_quadrants() + m_amr_mesh_info.local_num_ghosts()); + + if (edge_loc1.is_valid and isNotOutside1) + { + if (edge_loc1.level() == level) + { + expect_near_same_level(edge_loc, edge_loc1); + } + if constexpr (dim == 3) + { + if (edge_loc1.level() > level) + { + expect_near_finer_level(edge_loc, edge_loc1); + } + } + } + + const bool isNotOutside2 = + edge_loc2.iOct < (m_amr_mesh_info.local_num_quadrants() + m_amr_mesh_info.local_num_ghosts()); + + if (edge_loc2.is_valid and isNotOutside2) + { + if (edge_loc2.level() == level) + { + expect_near_same_level(edge_loc, edge_loc2); + } + if constexpr (dim == 3) + { + if (edge_loc2.level() > level) + { + expect_near_finer_level(edge_loc, edge_loc2); + } + } + } + + } // end norm_v > 0 + +} // check + +// ==================================================================== +// ==================================================================== +template +KOKKOS_INLINE_FUNCTION void +CheckEdgeSiblingsConnectivity::operator()(const index_t & global_index) const +{ + + const auto num_edges = m_edge_flat_index_offsets[3]; + + // retrieve local octant index + auto const iOct_local = global_index / num_edges; + auto const edge_flatindex = global_index - iOct_local * num_edges; + + check(edge_flatindex, iOct_local); + +} // operator () + +// explicit template instantiation +template class CheckEdgeSiblingsConnectivity<2, kalypsso::DefaultDevice>; +template class CheckEdgeSiblingsConnectivity<3, kalypsso::DefaultDevice>; + +} // namespace core + +} // namespace kalypsso diff --git a/src/kalypsso/core/CheckEdgeSiblingsConnectivity.h b/src/kalypsso/core/CheckEdgeSiblingsConnectivity.h new file mode 100644 index 0000000..4546573 --- /dev/null +++ b/src/kalypsso/core/CheckEdgeSiblingsConnectivity.h @@ -0,0 +1,181 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file CheckEdgeSiblingsConnectivity.h + * + * + */ +#ifndef KALYPSSO_CORE_CHECKEDGESIBLINGSCONNECTIVITY_H_ +#define KALYPSSO_CORE_CHECKEDGESIBLINGSCONNECTIVITY_H_ + +#include // for assertm +#include +#include +#include // for DataArrayBlock +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace kalypsso +{ + +namespace core +{ + +/*************************************************/ +/*************************************************/ +/*************************************************/ +/** + * \class CheckEdgeSiblingsConnectivity + * \brief Check that edge siblings at block border are valid. + * + * The purpose of this functor is to check that give an edge location, all siblings edge locations + * as computed by StencilHelper::getEdgeSiblingLoc are valid. This functor cross-check a posteriori + * at run-time the functionality of StencilHelper (instead of writing an exhaustive unit test which + * is quite complex). + * + * Let's remind that given an edge location, when requesting all edge siblings (i.e. edge locations + * of all neighbor cells sharing the same edge), there are three possible situations: + * + * - either there are only two valid edge siblings; that happens when edge is at a non-conformal + * interface and lies in the middle of a large face (of the coarse cell neighbor). + * - either there are four valid edge siblings that maybe live at different AMR level: + * - all at the level "l" + * - some at level "l", some at level "l+1" (when there are cell neighbors at finer AMR level) + * - some at level "l", some at level "l-1" (when there is a coarser cell neighbor) + * + * The cross-check here simply consist in computing in physical space the coordinates of the edge + * center and testing if we have the exact same value for all siblings. + */ +template +class CheckEdgeSiblingsConnectivity +{ + +public: + using exec_space = typename device_t::execution_space; + using index_t = int32_t; + + // hashmap related type aliases + using amr_hashmap_t = typename hashmap_base_t::map_t; + using orchard_key_view_t = typename orchard_key_base_t::view_t; + + // data array related type aliases + using DataArrayBlock_t = DataArrayBlock; + + template + using offsets_t = coord_t<_dim, real_t>; + + using CellLocation_t = CellLocation; + using EdgeLocation_t = EdgeLocation; + using StencilHelper_t = StencilHelper; + +private: + //! block sizes (no ghost) + const block_size_t m_block_sizes; + + //! helper to compute neighbor cell location + StencilHelper_t m_stencil_helper; + + //! list of orchard key of the mesh + orchard_key_view_t m_orchard_keys_device; + + //! AMR mesh info (number of owned, MPI ghost, outside quadrants) + const AMRMeshInfo m_amr_mesh_info; + + //! get geometrical scaling factor + const real_t m_scaling_factor; + + //! get domain lower left corner + const Kokkos::Array m_xyz_min; + + //! edge flat index offsets + const edge_flat_index_offset_t m_edge_flat_index_offsets; + + //! threshold + KALYPSSO_STATIC_MATH_CONSTANT(SMALL, 1e-13); + +public: + /** + * \param[in] stencil_helper A stencil helper object. + * \param[in] orchard_keys Orchard keys. + * \param[in] amr_mesh_info AMR mesh info. + * \param[in] config_map A config map. + * + */ + CheckEdgeSiblingsConnectivity(block_size_t bSize, + StencilHelper_t stencil_helper, + orchard_key_view_t orchard_keys, + AMRMeshInfo amr_mesh_info, + ConfigMap const & config_map); + + // ============================================================== + // ============================================================== + //! static method which does it all: create and execute functor with range policy + //! + //! Use this member when computing primitive in a group of octant + static void + apply(block_size_t bSize, + amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + AMRMeshInfo amr_mesh_info, + ConfigMap const & config_map, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic); + + // ==================================================================== + // ==================================================================== + KOKKOS_INLINE_FUNCTION bool + are_location_not_near(Kokkos::Array const & xyz0, + Kokkos::Array const & xyz1) const; + + // ==================================================================== + // ==================================================================== + KOKKOS_INLINE_FUNCTION void + expect_near_xyz(EdgeLocation const & edge_loc0, + Kokkos::Array const & xyz0, + Kokkos::Array const & xyz1) const; + + // ==================================================================== + // ==================================================================== + KOKKOS_INLINE_FUNCTION void + expect_near_same_level(EdgeLocation const & edge_loc0, + EdgeLocation const & edge_loc1) const; + + // ==================================================================== + // ==================================================================== + KOKKOS_INLINE_FUNCTION void + expect_near_finer_level(EdgeLocation const & edge_loc, + EdgeLocation const & edge_loc0) const; + + // ==================================================================== + // ==================================================================== + KOKKOS_INLINE_FUNCTION void + check(index_t const & edge_flatindex, int32_t const & iOct) const; + + // ==================================================================== + // ==================================================================== + KOKKOS_INLINE_FUNCTION + void + operator()(const index_t & global_index) const; + +}; // CheckEdgeSiblingsConnectivity + +// explicit template instantiation +extern template class CheckEdgeSiblingsConnectivity<2, kalypsso::DefaultDevice>; +extern template class CheckEdgeSiblingsConnectivity<3, kalypsso::DefaultDevice>; + +} // namespace core + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_CHECKEDGESIBLINGSCONNECTIVITY_H_ diff --git a/src/kalypsso/core/CheckFaceBorderCompatibility.cpp b/src/kalypsso/core/CheckFaceBorderCompatibility.cpp new file mode 100644 index 0000000..adb890c --- /dev/null +++ b/src/kalypsso/core/CheckFaceBorderCompatibility.cpp @@ -0,0 +1,273 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file CheckFaceBorderCompatibility.cpp + */ +#include + +namespace kalypsso +{ + +namespace core +{ + +/*******************************************************************************/ +/*******************************************************************************/ +/*******************************************************************************/ +template +CheckFaceBorderCompatibility::CheckFaceBorderCompatibility( + FaceDataArrayBlock_t const & facedata, + StencilHelper_t const & stencil_helper, + orchard_key_view_t const & orchard_keys, + AMRMeshInfo const & amr_mesh_info, + const int mpi_comm_rank, + ConfigMap const & config_map) + : m_facedata(facedata) + , m_stencil_helper(stencil_helper) + , m_orchard_keys_device(orchard_keys) + , m_amr_mesh_info(amr_mesh_info) + , m_scaling_factor(get_scaling_factor(config_map)) + , m_mpi_comm_rank(mpi_comm_rank) +{} // constructor + +// ============================================================== +// ============================================================== +template +void +CheckFaceBorderCompatibility::apply( + FaceDataArrayBlock_t const & facedata, + amr_hashmap_t const & amr_hashmap, + orchard_key_view_t const & orchard_keys, + AMRMeshInfo const & amr_mesh_info, + ConfigMap const & config_map, + brick_size_t const & brick_sizes, + Kokkos::Array const & is_brick_periodic, + ParallelEnv const & par_env) +{ + + auto stencil_helper = StencilHelper_t( + amr_hashmap, orchard_keys, facedata.cell_block_size_inner(), brick_sizes, is_brick_periodic); + + CheckFaceBorderCompatibility functor( + facedata, stencil_helper, orchard_keys, amr_mesh_info, par_env.rank(), config_map); + + // number of owned quadrant x number of surface cells + const auto num_surface_cells = get_number_of_surface_cells(facedata.cell_block_size_inner()); + const auto nbIterations = amr_mesh_info.local_num_quadrants() * num_surface_cells; + + // launch computation + Kokkos::parallel_for("kalypsso::core::CheckFaceBorderCompatibility", + Kokkos::RangePolicy(0, nbIterations), + functor); + +} // apply + +// ==================================================================== +// ==================================================================== +template +KOKKOS_INLINE_FUNCTION void +CheckFaceBorderCompatibility::check(index_t const & surface_flatindex, + int32_t const & iOct) const +{ + // auto const ijk = + // surface_flatindex_unravel_to_cell_ijk(surface_flatindex, m_facedata.cell_block_size_inner()); + + auto const normal = + surface_flatindex_to_normal_vector(surface_flatindex, m_facedata.cell_block_size_inner()); + + auto const ijk_face = + surface_flatindex_to_face_multiindex(surface_flatindex, m_facedata.cell_block_size_inner()); + + // current block AMR key + const auto key_cur = m_stencil_helper.key(iOct); + + // create a face location for current face + const FaceLocation_t face_loc{ ijk_face, key_cur, iOct, false }; + + // create a face location for neighbor + auto face_loc_neigh = m_stencil_helper.getNeighLoc(face_loc, normal); + auto const & iOct_neigh = face_loc_neigh.iOct; + + // if octant id is above local_num_quadrants, it means neigh octant is not an owned quadrant, so + // we don't check it (check for it would require applying border conditions beforehand, but we + // can't enforce that here.) + // if (iOct_neigh >= m_amr_mesh_info.local_num_quadrants()) + // return; + + const auto face_dir = ijk_face[dim]; + + // get a face location at neighbor location + // we only explore 2 cases: + // - when neighbor is at same AMR level + // - when neighbor is at finer AMR level + // In any of these two cases, we need to "symmetrize" the face normal coordinate + face_loc_neigh.ijk[face_dir] = m_facedata.cell_block_size_inner()[face_dir] - ijk_face[face_dir]; + + // only check when neighbor is at same AMR level or at finer level + if (face_loc_neigh.level() == face_loc.level()) + { + + if (fabs(m_facedata(ijk_face, iOct) - m_facedata(face_loc_neigh.ijk, iOct_neigh)) > SMALL_F) + { + if constexpr (dim == 2) + { + KOKKOS_IF_ON_HOST( + (KALYPSSO_ERROR( + "[Conformal face]: INVALID value at face location : mpi_rank={} i={} j={} " + "var={} iOct={} iOct_neigh={} current_value={} neighbor_value={} diff={}", + m_mpi_comm_rank, + ijk_face[IX], + ijk_face[IY], + ijk_face[dim], + iOct, + iOct_neigh, + m_facedata(ijk_face, iOct), + m_facedata(face_loc_neigh.ijk, iOct_neigh), + fabs(m_facedata(ijk_face, iOct) - m_facedata(face_loc_neigh.ijk, iOct_neigh)));)) + KOKKOS_IF_ON_DEVICE( + (Kokkos::printf( + "[Conformal face]: INVALID value at face location : mpi_rank=%d i=%d j=%d " + "var=%d iOct=%d iOct_neigh=%d current_value=%f neighbor_value=%f diff=%f\n", + m_mpi_comm_rank, + ijk_face[IX], + ijk_face[IY], + ijk_face[dim], + iOct, + iOct_neigh, + m_facedata(ijk_face, iOct), + m_facedata(face_loc_neigh.ijk, iOct_neigh), + fabs(m_facedata(ijk_face, iOct) - m_facedata(face_loc_neigh.ijk, iOct_neigh)));)) + } + else if constexpr (dim == 3) + { + KOKKOS_IF_ON_HOST( + (KALYPSSO_ERROR( + "[Conformal face]: INVALID value at face location : mpi_rank={} i={} j={} k={} " + "var={} iOct={} iOct_neigh={} current_value={} neighbor_value={} diff={}", + m_mpi_comm_rank, + ijk_face[IX], + ijk_face[IY], + ijk_face[IZ], + ijk_face[dim], + iOct, + iOct_neigh, + m_facedata(ijk_face, iOct), + m_facedata(face_loc_neigh.ijk, iOct_neigh), + fabs(m_facedata(ijk_face, iOct) - m_facedata(face_loc_neigh.ijk, iOct_neigh)));)) + KOKKOS_IF_ON_DEVICE( + (Kokkos::printf( + "[Conformal face]: INVALID value at face location : mpi_rank=%d i=%d j=%d k=%d " + "var=%d iOct=%d iOct_neigh=%d current_value=%f neighbor_value=%f diff=%f\n", + m_mpi_comm_rank, + ijk_face[IX], + ijk_face[IY], + ijk_face[IZ], + ijk_face[dim], + iOct, + iOct_neigh, + m_facedata(ijk_face, iOct), + m_facedata(face_loc_neigh.ijk, iOct_neigh), + fabs(m_facedata(ijk_face, iOct) - m_facedata(face_loc_neigh.ijk, iOct_neigh)));)) + } + } + } + else if (face_loc_neigh.level() == face_loc.level() + 1) + { + // neighbor is at finer scale + const auto fine_value = + m_stencil_helper.compute_face_siblings_average(face_loc_neigh, m_facedata); + const auto current_value = m_facedata(ijk_face, iOct); + + if (fabs(current_value - fine_value) > SMALL_F) + { + if constexpr (dim == 2) + { + KOKKOS_IF_ON_HOST( + (KALYPSSO_ERROR( + "[Non-conformal face]: INVALID value at face location : mpi_rank={} i={} j={} var={} " + "iOct={} iOct_neigh={} current_value={} fine_value={} diff={}", + m_mpi_comm_rank, + ijk_face[IX], + ijk_face[IY], + ijk_face[dim], + iOct, + iOct_neigh, + current_value, + fine_value, + fabs(current_value - fine_value));)) + KOKKOS_IF_ON_DEVICE( + (Kokkos::printf( + "[Non-conformal face]: INVALID value at face location : mpi_rank=%d i=%d j=%d var=%d " + "iOct=%d iOct_neigh=%d current_value=%f fine_value=%f diff=%f\n", + m_mpi_comm_rank, + ijk_face[IX], + ijk_face[IY], + ijk_face[dim], + iOct, + iOct_neigh, + current_value, + fine_value, + fabs(current_value - fine_value));)) + } + else if constexpr (dim == 3) + { + KOKKOS_IF_ON_HOST( + (KALYPSSO_ERROR( + "[Non-conformal face]: INVALID value at face location : mpi_rank={} i={} j={} k={} " + "var={} iOct={} iOct_neigh={} current_value={} fine_value={} diff={}", + m_mpi_comm_rank, + ijk_face[IX], + ijk_face[IY], + ijk_face[IZ], + ijk_face[dim], + iOct, + iOct_neigh, + current_value, + fine_value, + fabs(current_value - fine_value));)) + KOKKOS_IF_ON_DEVICE( + (Kokkos::printf( + "[Non-conformal face]: INVALID value at face location : mpi_rank=%d i=%d j=%d k=%d " + "var=%d iOct=%d iOct_neigh=%d current_value=%f fine_value=%f diff=%f\n", + m_mpi_comm_rank, + ijk_face[IX], + ijk_face[IY], + ijk_face[IZ], + ijk_face[dim], + iOct, + iOct_neigh, + current_value, + fine_value, + fabs(current_value - fine_value));)) + } + } + } + +} // check + +// ==================================================================== +// ==================================================================== +template +KOKKOS_INLINE_FUNCTION void +CheckFaceBorderCompatibility::operator()(const index_t & global_index) const +{ + + const auto num_surface_cells = get_number_of_surface_cells(m_facedata.cell_block_size_inner()); + + // retrieve local octant index + auto const iOct_local = global_index / num_surface_cells; + auto const surface_cell_index = global_index - iOct_local * num_surface_cells; + + check(surface_cell_index, iOct_local); + +} // operator () + +// explicit template instantiation +template class CheckFaceBorderCompatibility<2, kalypsso::DefaultDevice>; +template class CheckFaceBorderCompatibility<3, kalypsso::DefaultDevice>; + +} // namespace core + +} // namespace kalypsso diff --git a/src/kalypsso/core/CheckFaceBorderCompatibility.h b/src/kalypsso/core/CheckFaceBorderCompatibility.h new file mode 100644 index 0000000..ed1e4ba --- /dev/null +++ b/src/kalypsso/core/CheckFaceBorderCompatibility.h @@ -0,0 +1,164 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file CheckFaceBorderCompatibility.h + * + * The purpose of this functor is to check that a FaceDataArrayBlock is continuous at block border. + * + * There are two possible situations: + * + * - when two neighbor blocks live at the same AMR level, we just check equality of the face data + * array values on the interface. + * - when two neighbor blocks are at different AMR levels, the interface is non-conform (see drawing + * below), then we check that the values on the large block face (Block A) is equal to the average + * of the values on the small faces of block B. + * + * + * Block A + * ____________________________ + * | | | | | + * | | | | | + * | | | | | + * |______|______|______|______| + * | | | | | + * | | | | | + * | | | | | Block B + * |______|______|______|______| ________________ + * | | | | | ____| | | | | + * | | | | |______ / |___|___|___|___| + * | | | | | \_____| | | | | + * |______|______|______|______| |___|___|___|___| + * | | | | | | | | | | + * | | | | | |___|___|___|___| + * | | | | | | | | | | + * |______|______|______|______| |___|___|___|___| + * + * + */ +#ifndef KALYPSSO_CORE_CHECKFACEBORDERCOMPATIBILITY_H_ +#define KALYPSSO_CORE_CHECKFACEBORDERCOMPATIBILITY_H_ + +#include // for assertm +#include +#include // for DataArrayBlock +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace kalypsso +{ + +namespace core +{ + +/*************************************************/ +/*************************************************/ +/*************************************************/ +/** + * Check that a FaceDataArrayBlock is continuous at block border. + * + */ +template +class CheckFaceBorderCompatibility +{ + +public: + using exec_space = typename device_t::execution_space; + using index_t = int32_t; + + // hashmap related type aliases + using amr_hashmap_t = typename hashmap_base_t::map_t; + using orchard_key_view_t = typename orchard_key_base_t::view_t; + + // data array related type aliases + using DataArrayBlock_t = DataArrayBlock; + using FaceDataArrayBlock_t = FaceDataArrayBlock; + + using CellLocation_t = CellLocation; + using FaceLocation_t = FaceLocation; + using StencilHelper_t = StencilHelper; + +private: + //! some face array (in) + FaceDataArrayBlock_t m_facedata; + + //! helper to compute neighbor cell location + StencilHelper_t m_stencil_helper; + + //! list of orchard key of the mesh + orchard_key_view_t m_orchard_keys_device; + + //! AMR mesh info (number of owned, MPI ghost, outside quadrants) + const AMRMeshInfo m_amr_mesh_info; + + //! get geometrical scaling factor + const real_t m_scaling_factor; + + //! threshold + KALYPSSO_STATIC_MATH_CONSTANT(SMALL, 1e-13); + + //! MPI comm rank from parallel environment + const int m_mpi_comm_rank; + +public: + /** + * \param[in] facedata A FaceDataArrayBlock to check. + * \param[in] config_map A config map. + * \param[in] stencil_helper A stencil helper object. + * \param[in] orchard_keys Orchard keys. + * \param[in] amr_mesh_info AMR mesh info. + * + */ + CheckFaceBorderCompatibility(FaceDataArrayBlock_t const & facedata, + StencilHelper_t const & stencil_helper, + orchard_key_view_t const & orchard_keys, + AMRMeshInfo const & amr_mesh_info, + const int mpi_comm_rank, + ConfigMap const & config_map); + + // ============================================================== + // ============================================================== + //! static method which does it all: create and execute functor with range policy + //! + //! Use this member when computing primitive in a group of octant + static void + apply(FaceDataArrayBlock_t const & facedata, + amr_hashmap_t const & amr_hashmap, + orchard_key_view_t const & orchard_keys, + AMRMeshInfo const & amr_mesh_info, + ConfigMap const & config_map, + brick_size_t const & brick_sizes, + Kokkos::Array const & is_brick_periodic, + ParallelEnv const & par_env); + + // ==================================================================== + // ==================================================================== + KOKKOS_INLINE_FUNCTION void + check(index_t const & surface_flatindex, int32_t const & iOct) const; + + // ==================================================================== + // ==================================================================== + KOKKOS_INLINE_FUNCTION + void + operator()(const index_t & global_index) const; + +}; // CheckFaceBorderCompatibility + +// explicit template instantiation +extern template class CheckFaceBorderCompatibility<2, kalypsso::DefaultDevice>; +extern template class CheckFaceBorderCompatibility<3, kalypsso::DefaultDevice>; + +} // namespace core + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_CHECKFACEBORDERCOMPATIBILITY_H_ diff --git a/src/kalypsso/core/ComputeConformalFullStatus.h b/src/kalypsso/core/ComputeConformalFullStatus.h new file mode 100644 index 0000000..0935fb5 --- /dev/null +++ b/src/kalypsso/core/ComputeConformalFullStatus.h @@ -0,0 +1,591 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ComputeConformalFullStatus.h + * + * Kokkos functor which computes conformal face status for each owned + MPI quadrants. + */ +#ifndef KALYPSSO_CORE_COMPUTECONFORMALFULLSTATUS_H_ +#define KALYPSSO_CORE_COMPUTECONFORMALFULLSTATUS_H_ + +#include +#include +#include // for DataArray, DataArrayHost, DataArrayGhostedBlock +#include + +#include +#include +#include +#include +#include // for definition of Face::XMIN, etc... + +namespace kalypsso +{ + +/** + * \class ComputeConformalFullStatusFunctor + */ +template +class ComputeConformalFullStatusFunctor +{ +public: + using exec_space = typename device_t::execution_space; + using index_t = int32_t; + + using amr_hashmap_t = typename hashmap_base_t::map_t; + using orchard_key_view_t = typename orchard_key_base_t::view_t; + using conformal_full_status_view_type = conformal_full_status_view_t; + +private: + //! AMR unordered map which maps orchard keys to quadrant number for all key in the mesh + //! (owned quadrants and ghost quadrants) + amr_hashmap_t m_amr_hashmap_device; + + //! list of orchard key of the mesh + orchard_key_view_t m_orchard_keys_device; + + //! AMR mesh info (number of owned, MPI ghost, outside quadrants) + AMRMeshInfo m_amr_mesh_info; + + //! p4est brick connectivity sizes + const brick_size_t m_brick_sizes; + + //! is p4est connectivity periodic ? + const Kokkos::Array m_is_brick_periodic; + + //! conformal status array + conformal_full_status_view_type m_conformal_full_status_view; + +public: + /** + * Constructor. + * + * \param[in] amr_hashmap unordered map from orchard key to memory index for owned and ghost + * quadrants + * \param[in] orchard_keys array of orchard key ordered by Morton order + * \param[in] amr_mesh_info (number of owned, MPI ghosts and outside quadrants) + * \param[in] brick_sizes is an array of p4est brick connectivity sizes (number of trees in + * each dimension) + * \param[in] is_brick_periodic array of boolean value indicating if the p4est + * brick connectivity is periodic in the given dimension + * \param[out] conformal_status_view + */ + ComputeConformalFullStatusFunctor(amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + AMRMeshInfo amr_mesh_info, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + conformal_full_status_view_type conformal_full_status_view) + : m_amr_hashmap_device(amr_hashmap) + , m_orchard_keys_device(orchard_keys) + , m_amr_mesh_info(amr_mesh_info) + , m_brick_sizes(brick_sizes) + , m_is_brick_periodic(is_brick_periodic) + , m_conformal_full_status_view(conformal_full_status_view) + { + // make sure conformal_status_view has the right size + KOKKOS_ASSERT(conformal_full_status_view.extent(0) == + static_cast( + (amr_mesh_info.local_num_quadrants() + amr_mesh_info.local_num_ghosts())) && + "conformal status view has wrong size"); + } + + // ============================================================== + // ============================================================== + //! static method which does it all: create and execute functor with range policy + static void + apply(amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + AMRMeshInfo amr_mesh_info, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + conformal_full_status_view_type conformal_full_status_view) + { + ComputeConformalFullStatusFunctor functor(amr_hashmap, + orchard_keys, + amr_mesh_info, + brick_sizes, + is_brick_periodic, + conformal_full_status_view); + + const auto num_iterations = + amr_mesh_info.local_num_quadrants() + amr_mesh_info.local_num_ghosts(); + + Kokkos::parallel_for("kalypsso::core::ComputeConformalFullStatusFunctor", + Kokkos::RangePolicy(0, num_iterations), + functor); + + } // apply + + // ============================================================== + // ============================================================== + //! get outside normal direction to a face + //! + //! \param[in] face + KOKKOS_INLINE_FUNCTION + auto + get_direction(Face::face_t face) const + { + if constexpr (dim == 2) + { + if (face == Face::XMIN) + { + return Kokkos::Array{ -1, 0 }; + } + else if (face == Face::XMAX) + { + return Kokkos::Array{ 1, 0 }; + } + else if (face == Face::YMIN) + { + return Kokkos::Array{ 0, -1 }; + } + else if (face == Face::YMAX) + { + return Kokkos::Array{ 0, 1 }; + } + else + { + return Kokkos::Array{ 0, 0 }; + } + } + else if constexpr (dim == 3) + { + if (face == Face::XMIN) + { + return Kokkos::Array{ -1, 0, 0 }; + } + else if (face == Face::XMAX) + { + return Kokkos::Array{ 1, 0, 0 }; + } + else if (face == Face::YMIN) + { + return Kokkos::Array{ 0, -1, 0 }; + } + else if (face == Face::YMAX) + { + return Kokkos::Array{ 0, 1, 0 }; + } + else if (face == Face::ZMIN) + { + return Kokkos::Array{ 0, 0, -1 }; + } + else if (face == Face::ZMAX) + { + return Kokkos::Array{ 0, 0, 1 }; + } + else + { + return Kokkos::Array{ 0, 0, 0 }; + } + } + } // get_direction - face normal + + // ============================================================== + // ============================================================== + //! get outside normal direction to an edge. + //! + //! An edge is identified by the intersection of two faces. + //! + //! \param[in] face0 + //! \param[in] face1 + KOKKOS_INLINE_FUNCTION + auto + get_direction(Face::face_t face0, Face::face_t face1) const + { + if constexpr (dim == 2) + { + if (face0 == Face::XMIN and face1 == Face::YMIN) + { + return Kokkos::Array{ -1, -1 }; + } + else if (face0 == Face::XMAX and face1 == Face::YMIN) + { + return Kokkos::Array{ 1, -1 }; + } + else if (face0 == Face::XMIN and face1 == Face::YMAX) + { + return Kokkos::Array{ -1, 1 }; + } + else if (face0 == Face::XMAX and face1 == Face::YMAX) + { + return Kokkos::Array{ 1, 1 }; + } + else + { + return Kokkos::Array{ 0, 0 }; + } + } + else if constexpr (dim == 3) + { + if (face0 == Face::XMIN and face1 == Face::YMIN) + { + return Kokkos::Array{ -1, -1, 0 }; + } + else if (face0 == Face::XMAX and face1 == Face::YMIN) + { + return Kokkos::Array{ 1, -1, 0 }; + } + else if (face0 == Face::XMIN and face1 == Face::YMAX) + { + return Kokkos::Array{ -1, 1, 0 }; + } + else if (face0 == Face::XMAX and face1 == Face::YMAX) + { + return Kokkos::Array{ 1, 1, 0 }; + } + else if (face0 == Face::YMIN and face1 == Face::ZMIN) + { + return Kokkos::Array{ 0, -1, -1 }; + } + else if (face0 == Face::YMAX and face1 == Face::ZMIN) + { + return Kokkos::Array{ 0, 1, -1 }; + } + else if (face0 == Face::YMIN and face1 == Face::ZMAX) + { + return Kokkos::Array{ 0, -1, 1 }; + } + else if (face0 == Face::YMAX and face1 == Face::ZMAX) + { + return Kokkos::Array{ 0, 1, 1 }; + } + else if (face0 == Face::XMIN and face1 == Face::ZMIN) + { + return Kokkos::Array{ -1, 0, -1 }; + } + else if (face0 == Face::XMAX and face1 == Face::ZMIN) + { + return Kokkos::Array{ 1, 0, -1 }; + } + else if (face0 == Face::XMIN and face1 == Face::ZMAX) + { + return Kokkos::Array{ -1, 0, 1 }; + } + else if (face0 == Face::XMAX and face1 == Face::ZMAX) + { + return Kokkos::Array{ 1, 0, 1 }; + } + else + { + return Kokkos::Array{ 0, 0, 0 }; + } + } + } // get_direction - edge normal + + // ============================================================== + // ============================================================== + //! get outside normal direction to a corner (3d). + //! + //! An corner in 3d is identified by the intersection of three faces. + //! + //! \param[in] face0 + //! \param[in] face1 + //! \param[in] face2 + KOKKOS_INLINE_FUNCTION + auto + get_direction(Face::face_t face0, Face::face_t face1, Face::face_t face2) const + { + if constexpr (dim == 3) + { + if (face0 == Face::XMIN and face1 == Face::YMIN and face2 == Face::ZMIN) + { + return Kokkos::Array{ -1, -1, -1 }; + } + else if (face0 == Face::XMAX and face1 == Face::YMIN and face2 == Face::ZMIN) + { + return Kokkos::Array{ 1, -1, -1 }; + } + else if (face0 == Face::XMIN and face1 == Face::YMAX and face2 == Face::ZMIN) + { + return Kokkos::Array{ -1, 1, -1 }; + } + else if (face0 == Face::XMAX and face1 == Face::YMAX and face2 == Face::ZMIN) + { + return Kokkos::Array{ 1, 1, -1 }; + } + else if (face0 == Face::XMIN and face1 == Face::YMIN and face2 == Face::ZMAX) + { + return Kokkos::Array{ -1, -1, 1 }; + } + else if (face0 == Face::XMAX and face1 == Face::YMIN and face2 == Face::ZMAX) + { + return Kokkos::Array{ 1, -1, 1 }; + } + else if (face0 == Face::XMIN and face1 == Face::YMAX and face2 == Face::ZMAX) + { + return Kokkos::Array{ -1, 1, 1 }; + } + else if (face0 == Face::XMAX and face1 == Face::YMAX and face2 == Face::ZMAX) + { + return Kokkos::Array{ 1, 1, 1 }; + } + else + { + return Kokkos::Array{ 0, 0, 0 }; + } + } + } // get_direction - corner normal (3d only) + + // ============================================================== + // ============================================================== + KOKKOS_INLINE_FUNCTION void + set_face_status(Face::face_t face, + key_t key_cur, + typename conformal_full_status_t::status_t & status) const + { + const auto dir = get_direction(face); + + // get neighbor key at same level + const auto key_neigh_same_level = orchard_key_t::get_neighbor_key_same_level( + key_cur, dir, m_brick_sizes, m_is_brick_periodic); + + // check if neighbor key exists in hash map + auto key_neigh_hashindex = m_amr_hashmap_device.find(key_neigh_same_level); + + const auto is_at_same_level = m_amr_hashmap_device.valid_at(key_neigh_hashindex); + + // if key is valid, it means neighbor is actually at the same level + if (is_at_same_level) + { + conformal_full_status_t::set_status( + face, conformal_neighbor_status::NEIGHBOR_IS_AT_SAME_LEVEL, status); + } + else + { + // check if father exist (coarser level) + const auto key_neigh_coarser = orchard_key_t::father(key_neigh_same_level); + + key_neigh_hashindex = m_amr_hashmap_device.find(key_neigh_coarser); + + const auto is_at_coarser_level = m_amr_hashmap_device.valid_at(key_neigh_hashindex); + + if (is_at_coarser_level) + { + conformal_full_status_t::set_status( + face, conformal_neighbor_status::NEIGHBOR_IS_COARSER, status); + } + else + { + // test if a neighbor at finer scale exists + const auto child_id = orchard_key_t::get_face_neighbor_smallest_child_id(face); + const auto key_neigh_finer = orchard_key_t::child(key_neigh_same_level, child_id); + + key_neigh_hashindex = m_amr_hashmap_device.find(key_neigh_finer); + const auto is_at_finer_level = m_amr_hashmap_device.valid_at(key_neigh_hashindex); + + if (is_at_finer_level) + { + conformal_full_status_t::set_status( + face, conformal_neighbor_status::NEIGHBOR_IS_FINER, status); + } + else + { + // unavailable (should be in a ghost octant) + conformal_full_status_t::set_status( + face, conformal_neighbor_status::NEIGHBOR_IS_UNAVAILABLE, status); + } + } + } + + } // set_face_status + + // ============================================================== + // ============================================================== + KOKKOS_INLINE_FUNCTION void + set_edge_status(Face::face_t face0, + Face::face_t face1, + key_t key_cur, + typename conformal_full_status_t::status_t & status) const + { + const auto dir = get_direction(face0, face1); + + // get neighbor key at same level + const auto key_neigh_same_level = orchard_key_t::get_neighbor_key_same_level( + key_cur, dir, m_brick_sizes, m_is_brick_periodic); + + // check if neighbor key exists in hash map + auto key_neigh_hashindex = m_amr_hashmap_device.find(key_neigh_same_level); + + const auto is_at_same_level = m_amr_hashmap_device.valid_at(key_neigh_hashindex); + + // if key is valid, it means neighbor is actually at the same level + if (is_at_same_level) + { + conformal_full_status_t::set_status( + face0, face1, conformal_neighbor_status::NEIGHBOR_IS_AT_SAME_LEVEL, status); + } + else + { + // check if father exist (coarser level) + const auto key_neigh_coarser = orchard_key_t::father(key_neigh_same_level); + + key_neigh_hashindex = m_amr_hashmap_device.find(key_neigh_coarser); + + const auto is_at_coarser_level = m_amr_hashmap_device.valid_at(key_neigh_hashindex); + + if (is_at_coarser_level) + { + conformal_full_status_t::set_status( + face0, face1, conformal_neighbor_status::NEIGHBOR_IS_COARSER, status); + } + else + { + // test if a neighbor at finer scale exists + const auto child_id = orchard_key_t::get_edge_neighbor_smallest_child_id(face0, face1); + const auto key_neigh_finer = orchard_key_t::child(key_neigh_same_level, child_id); + + key_neigh_hashindex = m_amr_hashmap_device.find(key_neigh_finer); + const auto is_at_finer_level = m_amr_hashmap_device.valid_at(key_neigh_hashindex); + + if (is_at_finer_level) + { + conformal_full_status_t::set_status( + face0, face1, conformal_neighbor_status::NEIGHBOR_IS_FINER, status); + } + else + { + // unavailable (should be in a ghost octant) + conformal_full_status_t::set_status( + face0, face1, conformal_neighbor_status::NEIGHBOR_IS_UNAVAILABLE, status); + } + } + } + + } // set_edge_status + + // ============================================================== + // ============================================================== + KOKKOS_INLINE_FUNCTION void + set_corner_status(Face::face_t face0, + Face::face_t face1, + Face::face_t face2, + key_t key_cur, + typename conformal_full_status_t::status_t & status) const + { + if constexpr (dim == 3) + { + const auto dir = get_direction(face0, face1, face2); + + // get neighbor key at same level + const auto key_neigh_same_level = orchard_key_t::get_neighbor_key_same_level( + key_cur, dir, m_brick_sizes, m_is_brick_periodic); + + // check if neighbor key exists in hash map + auto key_neigh_hashindex = m_amr_hashmap_device.find(key_neigh_same_level); + + const auto is_at_same_level = m_amr_hashmap_device.valid_at(key_neigh_hashindex); + + // if key is valid, it means neighbor is actually at the same level + if (is_at_same_level) + { + conformal_full_status_t::set_status( + face0, face1, face2, conformal_neighbor_status::NEIGHBOR_IS_AT_SAME_LEVEL, status); + } + else + { + // check if father exist (coarser level) + const auto key_neigh_coarser = orchard_key_t::father(key_neigh_same_level); + + key_neigh_hashindex = m_amr_hashmap_device.find(key_neigh_coarser); + + const auto is_at_coarser_level = m_amr_hashmap_device.valid_at(key_neigh_hashindex); + + if (is_at_coarser_level) + { + conformal_full_status_t::set_status( + face0, face1, face2, conformal_neighbor_status::NEIGHBOR_IS_COARSER, status); + } + else + { + // test if a neighbor at finer scale exists + const auto child_id = + orchard_key_t::get_corner_neighbor_smallest_child_id(face0, face1, face2); + const auto key_neigh_finer = orchard_key_t::child(key_neigh_same_level, child_id); + + key_neigh_hashindex = m_amr_hashmap_device.find(key_neigh_finer); + const auto is_at_finer_level = m_amr_hashmap_device.valid_at(key_neigh_hashindex); + + if (is_at_finer_level) + { + conformal_full_status_t::set_status( + face0, face1, face2, conformal_neighbor_status::NEIGHBOR_IS_FINER, status); + } + else + { + // unavailable (should be in a ghost octant) + conformal_full_status_t::set_status( + face0, face1, face2, conformal_neighbor_status::NEIGHBOR_IS_UNAVAILABLE, status); + } + } + } + } + } // set_corner_status + + // ============================================================== + // ============================================================== + /** + * range policy functor + */ + KOKKOS_INLINE_FUNCTION void + operator()(const index_t & iOct) const + { + + // get orchard key of current octant + auto key_cur = m_orchard_keys_device(iOct); + + typename conformal_full_status_t::status_t status = 0; + + // + // set face status + // + for (Face::face_t face = 0; face < Face::num_faces(); ++face) + { + set_face_status(face, key_cur, status); + } // end for face + + // + // set edge status + // + set_edge_status(Face::XMIN, Face::YMIN, key_cur, status); + set_edge_status(Face::XMAX, Face::YMIN, key_cur, status); + set_edge_status(Face::XMIN, Face::YMAX, key_cur, status); + set_edge_status(Face::XMAX, Face::YMAX, key_cur, status); + if constexpr (dim == 3) + { + set_edge_status(Face::YMIN, Face::ZMIN, key_cur, status); + set_edge_status(Face::YMAX, Face::ZMIN, key_cur, status); + set_edge_status(Face::YMIN, Face::ZMAX, key_cur, status); + set_edge_status(Face::YMAX, Face::ZMAX, key_cur, status); + + set_edge_status(Face::XMIN, Face::ZMIN, key_cur, status); + set_edge_status(Face::XMAX, Face::ZMIN, key_cur, status); + set_edge_status(Face::XMIN, Face::ZMAX, key_cur, status); + set_edge_status(Face::XMAX, Face::ZMAX, key_cur, status); + } + + if constexpr (dim == 3) + { + set_corner_status(Face::XMIN, Face::YMIN, Face::ZMIN, key_cur, status); + set_corner_status(Face::XMAX, Face::YMIN, Face::ZMIN, key_cur, status); + set_corner_status(Face::XMIN, Face::YMAX, Face::ZMIN, key_cur, status); + set_corner_status(Face::XMAX, Face::YMAX, Face::ZMIN, key_cur, status); + set_corner_status(Face::XMIN, Face::YMIN, Face::ZMAX, key_cur, status); + set_corner_status(Face::XMAX, Face::YMIN, Face::ZMAX, key_cur, status); + set_corner_status(Face::XMIN, Face::YMAX, Face::ZMAX, key_cur, status); + set_corner_status(Face::XMAX, Face::YMAX, Face::ZMAX, key_cur, status); + } + + // status is up to date, just write it now + m_conformal_full_status_view(iOct) = status; + + } // operator() + +}; // class ComputeConformalFullStatusFunctor + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_COMPUTECONFORMALFULLSTATUS_H_ diff --git a/src/kalypsso/core/ComputeConformalStatus.h b/src/kalypsso/core/ComputeConformalStatus.h new file mode 100644 index 0000000..5d2e696 --- /dev/null +++ b/src/kalypsso/core/ComputeConformalStatus.h @@ -0,0 +1,265 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ComputeConformalStatus.h + * + * Kokkos functor which computes conformal face status for each owned + MPI quadrants. + */ +#ifndef KALYPSSO_CORE_COMPUTECONFORMALSTATUS_H_ +#define KALYPSSO_CORE_COMPUTECONFORMALSTATUS_H_ + +#include +#include +#include // for DataArray, DataArrayHost, DataArrayGhostedBlock +#include + +#include +#include +#include +#include +#include // for definition of Face::XMIN, etc... + +namespace kalypsso +{ + +/** + * \class ComputeConformalStatusFunctor + */ +template +class ComputeConformalStatusFunctor +{ +public: + using exec_space = typename device_t::execution_space; + using index_t = int32_t; + + using amr_hashmap_t = typename hashmap_base_t::map_t; + using orchard_key_view_t = typename orchard_key_base_t::view_t; + using conformal_status_view_type = conformal_status_view_t; + +private: + //! AMR unordered map which maps orchard keys to quadrant number for all key in the mesh + //! (owned quadrants and ghost quadrants) + amr_hashmap_t m_amr_hashmap_device; + + //! list of orchard key of the mesh + orchard_key_view_t m_orchard_keys_device; + + //! AMR mesh info (number of owned, MPI ghost, outside quadrants) + AMRMeshInfo m_amr_mesh_info; + + //! p4est brick connectivity sizes + const brick_size_t m_brick_sizes; + + //! is p4est connectivity periodic ? + const Kokkos::Array m_is_brick_periodic; + + //! conformal status array + conformal_status_view_type m_conformal_status_view; + +public: + /** + * Constructor. + * + * \param[in] amr_hashmap unordered map from orchard key to memory index for owned and ghost + * quadrants + * \param[in] orchard_keys array of orchard key ordered by Morton order + * \param[in] amr_mesh_info (number of owned, MPI ghosts and outside quadrants) + * \param[in] brick_sizes is an array of p4est brick connectivity sizes (number of trees in + * each dimension) + * \param[in] is_brick_periodic array of boolean value indicating if the p4est + * brick connectivity is periodic in the given dimension + * \param[out] conformal_status_view + */ + ComputeConformalStatusFunctor(amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + AMRMeshInfo amr_mesh_info, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + conformal_status_view_type conformal_status_view) + : m_amr_hashmap_device(amr_hashmap) + , m_orchard_keys_device(orchard_keys) + , m_amr_mesh_info(amr_mesh_info) + , m_brick_sizes(brick_sizes) + , m_is_brick_periodic(is_brick_periodic) + , m_conformal_status_view(conformal_status_view) + { + // make sure conformal_status_view has the right size + KOKKOS_ASSERT(conformal_status_view.extent(0) == + static_cast( + (amr_mesh_info.local_num_quadrants() + amr_mesh_info.local_num_ghosts())) && + "conformal status view has wrong size"); + } + + // ============================================================== + // ============================================================== + //! static method which does it all: create and execute functor with range policy + static void + apply(amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + AMRMeshInfo amr_mesh_info, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + conformal_status_view_type conformal_status_view) + { + ComputeConformalStatusFunctor functor(amr_hashmap, + orchard_keys, + amr_mesh_info, + brick_sizes, + is_brick_periodic, + conformal_status_view); + + const auto num_iterations = + amr_mesh_info.local_num_quadrants() + amr_mesh_info.local_num_ghosts(); + + Kokkos::parallel_for("kalypsso::core::ComputeConformalStatusFunctor", + Kokkos::RangePolicy(0, num_iterations), + functor); + + } // apply + + // ============================================================== + // ============================================================== + KOKKOS_INLINE_FUNCTION + auto + get_direction(Face::face_t face) const + { + if constexpr (dim == 2) + { + if (face == Face::XMIN) + { + return Kokkos::Array{ -1, 0 }; + } + else if (face == Face::XMAX) + { + return Kokkos::Array{ 1, 0 }; + } + else if (face == Face::YMIN) + { + return Kokkos::Array{ 0, -1 }; + } + else if (face == Face::YMAX) + { + return Kokkos::Array{ 0, 1 }; + } + else + { + return Kokkos::Array{ 0, 0 }; + } + } + else if constexpr (dim == 3) + { + if (face == Face::XMIN) + { + return Kokkos::Array{ -1, 0, 0 }; + } + else if (face == Face::XMAX) + { + return Kokkos::Array{ 1, 0, 0 }; + } + else if (face == Face::YMIN) + { + return Kokkos::Array{ 0, -1, 0 }; + } + else if (face == Face::YMAX) + { + return Kokkos::Array{ 0, 1, 0 }; + } + else if (face == Face::ZMIN) + { + return Kokkos::Array{ 0, 0, -1 }; + } + else if (face == Face::ZMAX) + { + return Kokkos::Array{ 0, 0, 1 }; + } + else + { + return Kokkos::Array{ 0, 0, 0 }; + } + } + } // get_direction + + // ============================================================== + // ============================================================== + /** + * range policy functor + */ + KOKKOS_INLINE_FUNCTION void + operator()(const index_t & iOct) const + { + + // get orchard key of current octant + auto key_cur = m_orchard_keys_device(iOct); + + typename conformal_face_status_t::status_t status = 0; + + for (Face::face_t face = 0; face < Face::num_faces(); ++face) + { + const auto dir = get_direction(face); + + // get neighbor key at same level + const auto key_neigh_same_level = orchard_key_t::get_neighbor_key_same_level( + key_cur, dir, m_brick_sizes, m_is_brick_periodic); + + // check if neighbor key exists in hash map + auto key_neigh_hashindex = m_amr_hashmap_device.find(key_neigh_same_level); + + const auto is_at_same_level = m_amr_hashmap_device.valid_at(key_neigh_hashindex); + + // if key is valid, it means neighbor is actually at the same level + if (is_at_same_level) + { + conformal_face_status_t::set_status( + face, conformal_neighbor_status::NEIGHBOR_IS_AT_SAME_LEVEL, status); + } + else + { + // check if father exist (coarser level) + const auto key_neigh_coarser = orchard_key_t::father(key_neigh_same_level); + + key_neigh_hashindex = m_amr_hashmap_device.find(key_neigh_coarser); + + const auto is_at_coarser_level = m_amr_hashmap_device.valid_at(key_neigh_hashindex); + + if (is_at_coarser_level) + { + conformal_face_status_t::set_status( + face, conformal_neighbor_status::NEIGHBOR_IS_COARSER, status); + } + else + { + // test if a neighbor at finer scale exists + const auto child_id = orchard_key_t::get_face_neighbor_smallest_child_id(face); + const auto key_neigh_finer = orchard_key_t::child(key_neigh_same_level, child_id); + + key_neigh_hashindex = m_amr_hashmap_device.find(key_neigh_finer); + const auto is_at_finer_level = m_amr_hashmap_device.valid_at(key_neigh_hashindex); + + if (is_at_finer_level) + { + conformal_face_status_t::set_status( + face, conformal_neighbor_status::NEIGHBOR_IS_FINER, status); + } + else + { + // unavailable (should be in a ghost octant) + conformal_face_status_t::set_status( + face, conformal_neighbor_status::NEIGHBOR_IS_UNAVAILABLE, status); + } + } + } + + } // end for face + + // status is up to date, just write it now + m_conformal_status_view(iOct) = status; + + } // operator() + +}; // class ComputeConformalStatusFunctor + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_COMPUTECONFORMALSTATUS_H_ diff --git a/src/kalypsso/core/ComputeCurvature.cpp b/src/kalypsso/core/ComputeCurvature.cpp new file mode 100644 index 0000000..6c50aeb --- /dev/null +++ b/src/kalypsso/core/ComputeCurvature.cpp @@ -0,0 +1,121 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ComputeCurvature.cpp + */ +#include + +namespace kalypsso +{ + +/*************************************************/ +/*************************************************/ +/*************************************************/ +template +ComputeCurvature::ComputeCurvature(DataArrayGhostedBlock_t normal_vector, + DataArrayGhostedBlock_t curvature, + const OrchardKeys & keys, + const int32_t iOct_first, + const int32_t num_quads, + ConfigMap const & config_map) + : m_normal_vector(normal_vector) + , m_curvature(curvature) + , m_keys(keys) + , m_iOct_first(iOct_first) + , m_num_quads(num_quads) + , m_scaling_factor(get_scaling_factor(config_map)) + +{} // constructor + +// ============================================================== +// ============================================================== +template +void +ComputeCurvature::apply(DataArrayGhostedBlock_t normal_vector, + DataArrayGhostedBlock_t curvature, + const OrchardKeys & keys, + const int32_t iOct_first, + const int32_t num_quads, + ConfigMap const & config_map) +{ + + ComputeCurvature functor( + normal_vector, curvature, keys, iOct_first, num_quads, config_map); + + const auto nbIterations = num_quads * curvature.num_cells(); + + // launch computation + Kokkos::parallel_for( + "kalypsso::core::ComputeCurvature", Kokkos::RangePolicy(0, nbIterations), functor); + +} // ComputeCurvature::apply + +// ==================================================================== +// ==================================================================== +template +KOKKOS_INLINE_FUNCTION void +ComputeCurvature::operator()(const index_t & global_index) const +{ + const auto block_size = m_curvature.block_size(); + + // retrieve local octant index in range [0, num_quads_to_process [ + auto const i_oct = static_cast(global_index / m_curvature.num_cells()); + auto const cell_index = static_cast(global_index - i_oct * m_curvature.num_cells()); + + // compute cell length + const auto level = orchard_key_t::level(m_keys(i_oct)); + const auto dx = compute_cell_length(level, block_size[IX]) * m_scaling_factor; + + + // compute cartesian coordinates inside ghosted block + const auto coord = + cellindex_to_coord(cell_index, m_curvature.ghosted_block_size(), m_curvature.shift()); + + if constexpr (dim == 2) + { + auto const & i = coord[IX]; + auto const & j = coord[IY]; + + // clang-format off + const auto dn_dx = + (m_normal_vector(i + 1, j, IX, i_oct) - + m_normal_vector(i - 1, j, IX, i_oct)) / 2; + const auto dn_dy = + (m_normal_vector(i, j + 1, IY, i_oct) - + m_normal_vector(i, j - 1, IY, i_oct)) / 2; + // clang-format on + + // compute divergence + m_curvature(i, j, 0, i_oct) = -(dn_dx + dn_dy) / dx; + } + else if constexpr (dim == 3) + { + auto const & i = coord[IX]; + auto const & j = coord[IY]; + auto const & k = coord[IZ]; + + // clang-format off + const auto dn_dx = + (m_normal_vector(i + 1, j, k, IX, i_oct) - + m_normal_vector(i - 1, j, k, IX, i_oct)) / 2; + const auto dn_dy = + (m_normal_vector(i, j + 1, k, IY, i_oct) - + m_normal_vector(i, j - 1, k, IY, i_oct)) / 2; + const auto dn_dz = + (m_normal_vector(i, j, k + 1, IZ, i_oct) - + m_normal_vector(i, j, k - 1, IZ, i_oct)) / 2; + // clang-format on + + // compute divergence + m_curvature(i, j, k, 0, i_oct) = -(dn_dx + dn_dy + dn_dz) / dx; + } + +} // ComputeCurvature::operator () + +// explicit template instantiation +template class ComputeCurvature<2, kalypsso::DefaultDevice>; +template class ComputeCurvature<3, kalypsso::DefaultDevice>; + +} // namespace kalypsso diff --git a/src/kalypsso/core/ComputeCurvature.h b/src/kalypsso/core/ComputeCurvature.h new file mode 100644 index 0000000..bf0b3d6 --- /dev/null +++ b/src/kalypsso/core/ComputeCurvature.h @@ -0,0 +1,103 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ComputeCurvature.h + */ +#ifndef KALYPSSO_CORE_COMPUTE_CURVATURE_H_ +#define KALYPSSO_CORE_COMPUTE_CURVATURE_H_ + +#include // for assertm +#include +#include // for DataArrayBlock +#include + +namespace kalypsso +{ + +/*************************************************/ +/*************************************************/ +/*************************************************/ +/** + * Compute interface curvature as the divergence of the normal vector using a + * second order central difference scheme. + * + * References: + * + * - A finite-volume HLLC-based scheme for compressible interfacial flows with surface tension, + * Garrick Owkes and Regele, Journal of Computational Physics Volume 339, 15 June 2017, Pages 46-67. + * https://doi.org/10.1016/j.jcp.2017.03.007 + * - An interface capturing scheme for modeling atomization in compressible flows, Garrick, Hagen + * and Regele, Journal of Computational Physics, Volume 344, 1 September 2017, Pages 260-280. + * https://doi.org/10.1016/j.jcp.2017.04.079 + */ +template +class ComputeCurvature +{ + +public: + using exec_space = typename device_t::execution_space; + using index_t = int64_t; + + // data array related type aliases + using DataArrayGhostedBlock_t = DataArrayGhostedBlock; + using OrchardKeys = typename orchard_key_base_t::view_t; + +private: + //! normal vector + DataArrayGhostedBlock_t m_normal_vector; + + //! curvature + DataArrayGhostedBlock_t m_curvature; + + //! Orchard keys + OrchardKeys m_keys; + + //! offset to first octant + const int32_t m_iOct_first; + + //! number of quadrants to process + const int32_t m_num_quads; + + //! Tree scaling factor (used for computing local metric) + const real_t m_scaling_factor; + + +public: + // ============================================================== + // ============================================================== + ComputeCurvature(DataArrayGhostedBlock_t normal_vector, + DataArrayGhostedBlock_t curvature, + const OrchardKeys & keys, + const int32_t iOct_first, + const int32_t num_quads, + ConfigMap const & config_map); + + // ============================================================== + // ============================================================== + //! static method which does it all: create and execute functor with range policy + //! + static void + apply(DataArrayGhostedBlock_t normal_vector, + DataArrayGhostedBlock_t curvature, + const OrchardKeys & keys, + const int32_t iOct_first, + const int32_t num_quads, + ConfigMap const & config_map); + + // ==================================================================== + // ==================================================================== + KOKKOS_INLINE_FUNCTION + void + operator()(const index_t & global_index) const; + +}; // ComputeCurvature + +// explicit template instantiation +extern template class ComputeCurvature<2, kalypsso::DefaultDevice>; +extern template class ComputeCurvature<3, kalypsso::DefaultDevice>; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_COMPUTE_CURVATURE_H_ diff --git a/src/kalypsso/core/ComputeDataSliceAlongLine.cpp b/src/kalypsso/core/ComputeDataSliceAlongLine.cpp new file mode 100644 index 0000000..d349687 --- /dev/null +++ b/src/kalypsso/core/ComputeDataSliceAlongLine.cpp @@ -0,0 +1,629 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ComputeDataSliceAlongLine.cpp + * + * \brief Contains the definition of ComputeDataSliceAlongLine. + */ + +#include + +#include +#include + +namespace kalypsso +{ + +namespace core +{ + +// ================================================================================================ +// ================================================================================================ +template +ComputeDataSliceAlongLine::ComputeDataSliceAlongLine( + const DataArrayBlock & data, + const OrchardKeys & keys, + const int32_t var, + const int32_t start_index, + const int32_t end_index, + const Kokkos::Array start_point, + const Kokkos::Array end_point, + const ConfigMap & config_map) + : m_data_cell(data) + , m_keys(keys) + , m_scaling_factor(get_scaling_factor(config_map)) + , m_var(var) + , m_corner(get_xyz_min(config_map)) + , m_start_index(start_index) + , m_offsets("Offsets", static_cast(end_index - start_index)) + , m_positions("Position", 0) + , m_amr("AMR", 0) + , m_data_slice("Data", 0) + , m_start(start_point) + , m_end(end_point) +{ + bool on_dir[dim]; + on_dir[IX] = !ISFUZZYNULL(start_point[IX] - end_point[IX]); + on_dir[IY] = !ISFUZZYNULL(start_point[IY] - end_point[IY]); + if constexpr (dim == 3) + on_dir[IZ] = !ISFUZZYNULL(start_point[IZ] - end_point[IZ]); + + if constexpr (dim == 2) + { + assertm(((on_dir[IX] + on_dir[IY]) == 1), "Points must be on a single axis"); + } + else if constexpr (dim == 3) + { + assertm(((on_dir[IX] + on_dir[IY] + on_dir[IZ]) == 1), "Points must be on a single axis"); + } + + for (uint8_t dir = IX; dir < dim; dir++) + m_dir = on_dir[dir] ? ComponentIndex3D(dir) : m_dir; +} // ComputeDataSliceAlongLine::ComputeDataSliceAlongLine + +// ================================================================================================ +// ================================================================================================ +template +ComputeDataSliceAlongLine::ComputeDataSliceAlongLine( + const DataArrayBlock & data_cell, + const FaceDataArrayBlock & data_face, + const OrchardKeys & keys, + const int32_t var, + const int32_t start_index, + const int32_t end_index, + const Kokkos::Array start_point, + const Kokkos::Array end_point, + const ConfigMap & config_map) + : m_data_cell(data_cell) + , m_data_face(data_face) + , m_keys(keys) + , m_scaling_factor(get_scaling_factor(config_map)) + , m_var(var) + , m_corner(get_xyz_min(config_map)) + , m_start_index(start_index) + , m_offsets("Offsets", static_cast(end_index - start_index)) + , m_positions("Position", 0) + , m_amr("AMR", 0) + , m_data_slice("Data", 0) + , m_start(start_point) + , m_end(end_point) +{ + bool on_dir[dim]; + on_dir[IX] = !ISFUZZYNULL(start_point[IX] - end_point[IX]); + on_dir[IY] = !ISFUZZYNULL(start_point[IY] - end_point[IY]); + if constexpr (dim == 3) + on_dir[IZ] = !ISFUZZYNULL(start_point[IZ] - end_point[IZ]); + + if constexpr (dim == 2) + { + assertm(((on_dir[IX] + on_dir[IY]) == 1), "Points must be on a single axis"); + } + else if constexpr (dim == 3) + { + assertm(((on_dir[IX] + on_dir[IY] + on_dir[IZ]) == 1), "Points must be on a single axis"); + } + + for (uint8_t dir = IX; dir < dim; dir++) + m_dir = on_dir[dir] ? ComponentIndex3D(dir) : m_dir; +} // ComputeDataSliceAlongLine::ComputeDataSliceAlongLine + +// ================================================================================================ +// ================================================================================================ +template +void +ComputeDataSliceAlongLine::apply(const DataArrayBlock & data, + const int32_t start_octant, + const int32_t end_octant, + const Kokkos::Array start_point, + const Kokkos::Array end_point, + const OrchardKeys & keys, + const int32_t var, + const std::string & file_prefix, + const ParallelEnv & parallel_env, + const ConfigMap & config_map) +{ + const auto nb_cells = data.num_cells(); + const auto start_index = start_octant * nb_cells; + const auto end_index = end_octant * nb_cells; + ComputeDataSliceAlongLine functor( + data, keys, var, start_index, end_index, start_point, end_point, config_map); + + Kokkos::RangePolicy policy0(start_index, end_index); + Kokkos::parallel_for("kalypsso::core::ComputeDataSliceAlongLine::MarkCells", policy0, functor); + + Kokkos::RangePolicy policy1(0, end_index - start_index); + int32_t total_num_cells = 0; + Kokkos::parallel_scan( + "kalypsso::core::ComputeDataSliceAlongLine::Offsets", + policy1, + KOKKOS_LAMBDA(const int32_t i, int32_t & offset, bool is_final) { + if (functor.m_offsets(i) == -1) + return; + + if (is_final) + functor.m_offsets(i) = offset; + offset += 1; + }, + total_num_cells); + + Kokkos::realloc(functor.m_positions, static_cast(total_num_cells)); + Kokkos::realloc(functor.m_amr, static_cast(total_num_cells)); + Kokkos::realloc(functor.m_data_slice, static_cast(total_num_cells)); + + Kokkos::RangePolicy policy2(start_index, end_index); + Kokkos::parallel_for("kalypsso::core::ComputeDataSliceAlongLine::WriteData", policy2, functor); + + auto positions_host = + Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, functor.m_positions); + auto amr_host = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, functor.m_amr); + auto data_host = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, functor.m_data_slice); + + if (parallel_env.nRanks() == 1) + { + save_cnpy(positions_host, file_prefix + "_positions"); + save_cnpy(amr_host, file_prefix + "_level"); + save_cnpy(data_host, file_prefix + "_data"); + } + else + { + const auto rank = std::to_string(parallel_env.rank()); + save_cnpy(positions_host, file_prefix + "_" + rank + "_positions"); + save_cnpy(amr_host, file_prefix + "_" + rank + "_level"); + save_cnpy(data_host, file_prefix + "_" + rank + "_data"); + } +} // ComputeDataSliceAlongLine::apply + +// ================================================================================================ +// ================================================================================================ +template +void +ComputeDataSliceAlongLine::apply(const DataArrayBlock & data, + const int32_t start_octant, + const int32_t end_octant, + const int32_t direction, + const OrchardKeys & keys, + const int32_t var, + const std::string & file_prefix, + const ParallelEnv & parallel_env, + const ConfigMap & config_map) +{ + const Kokkos::Array start_point = get_mid_box_start_point(config_map, direction); + const Kokkos::Array end_point = get_mid_box_end_point(config_map, direction); + + ComputeDataSliceAlongLine::apply(data, + start_octant, + end_octant, + start_point, + end_point, + keys, + var, + file_prefix, + parallel_env, + config_map); + +} // ComputeDataSliceAlongLine::apply + +// ================================================================================================ +// ================================================================================================ +template +void +ComputeDataSliceAlongLine::apply(const DataArrayBlock & data, + const int32_t start_octant, + const int32_t end_octant, + const int32_t direction, + const OrchardKeys & keys, + const std::vector vars, + const std::vector var_names, + const std::string & file_prefix, + const ParallelEnv & parallel_env, + const ConfigMap & config_map) +{ + const Kokkos::Array start_point = get_mid_box_start_point(config_map, direction); + const Kokkos::Array end_point = get_mid_box_end_point(config_map, direction); + + const auto nb_cells = data.num_cells(); + const auto start_index = start_octant * nb_cells; + const auto end_index = end_octant * nb_cells; + ComputeDataSliceAlongLine functor( + data, keys, vars[0], start_index, end_index, start_point, end_point, config_map); + + Kokkos::RangePolicy policy0(start_index, end_index); + Kokkos::parallel_for("kalypsso::core::ComputeDataSliceAlongLine::MarkCells", policy0, functor); + + Kokkos::RangePolicy policy1(0, end_index - start_index); + int32_t total_num_cells = 0; + Kokkos::parallel_scan( + "kalypsso::core::ComputeDataSliceAlongLine::Offsets", + policy1, + KOKKOS_LAMBDA(const int32_t i, int32_t & offset, bool is_final) { + if (functor.m_offsets(i) == -1) + return; + + if (is_final) + functor.m_offsets(i) = offset; + offset += 1; + }, + total_num_cells); + + Kokkos::realloc(functor.m_positions, static_cast(total_num_cells)); + Kokkos::realloc(functor.m_amr, static_cast(total_num_cells)); + Kokkos::realloc(functor.m_data_slice, static_cast(total_num_cells)); + + for (size_t iv = 0; iv < vars.size(); ++iv) + { + + functor.m_var = vars[iv]; + + Kokkos::RangePolicy policy2(start_index, end_index); + Kokkos::parallel_for("kalypsso::core::ComputeDataSliceAlongLine::WriteData", policy2, functor); + + auto positions_host = + Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, functor.m_positions); + auto amr_host = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, functor.m_amr); + auto data_host = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, functor.m_data_slice); + + if (parallel_env.nRanks() == 1) + { + if (iv == 0) + { + save_cnpy(positions_host, file_prefix + "_positions"); + save_cnpy(amr_host, file_prefix + "_level"); + } + save_cnpy(data_host, file_prefix + "_" + var_names[iv]); + } + else + { + const auto rank = std::to_string(parallel_env.rank()); + if (iv == 0) + { + save_cnpy(positions_host, file_prefix + "_" + rank + "_positions"); + save_cnpy(amr_host, file_prefix + "_" + rank + "_level"); + } + save_cnpy(data_host, file_prefix + "_" + rank + "_" + var_names[iv]); + } + } +} // ComputeDataSliceAlongLine::apply + +// ================================================================================================ +// ================================================================================================ +template +void +ComputeDataSliceAlongLine::apply( + const DataArrayBlock & data_cell, + const FaceDataArrayBlock & data_face, + const int32_t start_octant, + const int32_t end_octant, + const int32_t direction, + const OrchardKeys & keys, + const std::vector cell_vars, + const std::vector cell_var_names, + const std::vector face_vars, + const std::vector face_var_names, + const std::string & file_prefix, + const ParallelEnv & parallel_env, + const ConfigMap & config_map) +{ + const Kokkos::Array start_point = get_mid_box_start_point(config_map, direction); + const Kokkos::Array end_point = get_mid_box_end_point(config_map, direction); + + const auto nb_cells = data_cell.num_cells(); + const auto start_index = start_octant * nb_cells; + const auto end_index = end_octant * nb_cells; + ComputeDataSliceAlongLine functor(data_cell, + data_face, + keys, + cell_vars[0], + start_index, + end_index, + start_point, + end_point, + config_map); + + Kokkos::RangePolicy policy0(start_index, end_index); + Kokkos::parallel_for("kalypsso::core::ComputeDataSliceAlongLine::MarkCells", policy0, functor); + + Kokkos::RangePolicy policy1(0, end_index - start_index); + int32_t total_num_cells = 0; + Kokkos::parallel_scan( + "kalypsso::core::ComputeDataSliceAlongLine::Offsets", + policy1, + KOKKOS_LAMBDA(const int32_t i, int32_t & offset, bool is_final) { + if (functor.m_offsets(i) == -1) + return; + + if (is_final) + functor.m_offsets(i) = offset; + offset += 1; + }, + total_num_cells); + + Kokkos::realloc(functor.m_positions, static_cast(total_num_cells)); + Kokkos::realloc(functor.m_amr, static_cast(total_num_cells)); + Kokkos::realloc(functor.m_data_slice, static_cast(total_num_cells)); + + // cell variables + for (size_t iv = 0; iv < cell_vars.size(); ++iv) + { + + functor.m_var = cell_vars[iv]; + + Kokkos::RangePolicy policy2(start_index, end_index); + Kokkos::parallel_for("kalypsso::core::ComputeDataSliceAlongLine::WriteData", policy2, functor); + + auto positions_host = + Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, functor.m_positions); + auto amr_host = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, functor.m_amr); + auto data_host = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, functor.m_data_slice); + + if (parallel_env.nRanks() == 1) + { + if (iv == 0) + { + save_cnpy(positions_host, file_prefix + "_positions"); + save_cnpy(amr_host, file_prefix + "_level"); + } + save_cnpy(data_host, file_prefix + "_" + cell_var_names[iv]); + } + else + { + const auto rank = std::to_string(parallel_env.rank()); + if (iv == 0) + { + save_cnpy(positions_host, file_prefix + "_" + rank + "_positions"); + save_cnpy(amr_host, file_prefix + "_" + rank + "_level"); + } + save_cnpy(data_host, file_prefix + "_" + rank + "_" + cell_var_names[iv]); + } + } + + // face variables + for (size_t iv = 0; iv < face_vars.size(); ++iv) + { + + functor.m_var = face_vars[iv]; + + Kokkos::RangePolicy policy2(start_index, end_index); + Kokkos::parallel_for("kalypsso::core::ComputeDataSliceAlongLine::WriteData", policy2, functor); + + auto positions_host = + Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, functor.m_positions); + auto amr_host = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, functor.m_amr); + auto data_host = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, functor.m_data_slice); + + if (parallel_env.nRanks() == 1) + { + save_cnpy(data_host, file_prefix + "_" + face_var_names[iv]); + } + else + { + const auto rank = std::to_string(parallel_env.rank()); + save_cnpy(data_host, file_prefix + "_" + rank + "_" + face_var_names[iv]); + } + } + +} // ComputeDataSliceAlongLine::apply + +// ================================================================================================ +// ================================================================================================ +template +Kokkos::Array +ComputeDataSliceAlongLine::get_mid_box_start_point(const ConfigMap & config_map, + const int32_t direction) +{ + const auto xmin = config_map.getReal("mesh", "xmin", KALYPSSO_NUM(0.0)); + const auto ymin = config_map.getReal("mesh", "ymin", KALYPSSO_NUM(0.0)); + const auto zmin = config_map.getReal("mesh", "zmin", KALYPSSO_NUM(0.0)); + const auto nbrick_x = config_map.getInteger("p4est_connectivity", "nbrick_x", 1); + const auto nbrick_y = config_map.getInteger("p4est_connectivity", "nbrick_y", 1); + const auto nbrick_z = config_map.getInteger("p4est_connectivity", "nbrick_z", 1); + const auto scaling_factor = config_map.getReal("mesh", "scaling_factor", KALYPSSO_NUM(1.0)); + + Kokkos::Array xyz; + + xyz[IX] = direction == IX + ? xmin + : xmin + static_cast(nbrick_x) * scaling_factor * HALF_F + KALYPSSO_NUM(1e-5); + + xyz[IY] = direction == IY + ? ymin + : ymin + static_cast(nbrick_y) * scaling_factor * HALF_F + KALYPSSO_NUM(1e-5); + + if constexpr (dim == 3) + { + xyz[IZ] = direction == IZ ? zmin + : zmin + static_cast(nbrick_z) * scaling_factor * HALF_F + + KALYPSSO_NUM(1e-5); + } + + return xyz; + +} // ComputeDataSliceAlongLine::get_mid_box_start_point + +// ================================================================================================ +// ================================================================================================ +template +Kokkos::Array +ComputeDataSliceAlongLine::get_mid_box_end_point(const ConfigMap & config_map, + const int32_t direction) +{ + const auto xmin = config_map.getReal("mesh", "xmin", KALYPSSO_NUM(0.0)); + const auto ymin = config_map.getReal("mesh", "ymin", KALYPSSO_NUM(0.0)); + const auto zmin = config_map.getReal("mesh", "zmin", KALYPSSO_NUM(0.0)); + const auto nbrick_x = config_map.getInteger("p4est_connectivity", "nbrick_x", 1); + const auto nbrick_y = config_map.getInteger("p4est_connectivity", "nbrick_y", 1); + const auto nbrick_z = config_map.getInteger("p4est_connectivity", "nbrick_z", 1); + const auto scaling_factor = config_map.getReal("mesh", "scaling_factor", KALYPSSO_NUM(1.0)); + + Kokkos::Array xyz; + + xyz[IX] = direction == IX + ? xmin + static_cast(nbrick_x) * scaling_factor + : xmin + static_cast(nbrick_x) * scaling_factor * HALF_F + KALYPSSO_NUM(1e-5); + + xyz[IY] = direction == IY + ? ymin + static_cast(nbrick_y) * scaling_factor + : ymin + static_cast(nbrick_y) * scaling_factor * HALF_F + KALYPSSO_NUM(1e-5); + + if constexpr (dim == 3) + { + xyz[IZ] = direction == IZ ? zmin + static_cast(nbrick_z) * scaling_factor + : zmin + static_cast(nbrick_z) * scaling_factor * HALF_F + + KALYPSSO_NUM(1e-5); + } + + return xyz; + +} // ComputeDataSliceAlongLine::get_mid_box_end_point + +// ================================================================================================ +// ================================================================================================ +template +void +ComputeDataSliceAlongLine::operator()(const TagMarkCells, + const int32_t & i_global) const +{ + const auto block_size = m_data_cell.block_size(); + + const auto nb_cells = m_data_cell.num_cells(); + const auto i_oct = i_global / nb_cells; + const auto i_cell = i_global - nb_cells * i_oct; + + const auto i_coord = cellindex_to_coord(i_cell, m_data_cell.block_size()); + + const auto key = m_keys(i_oct); + const auto level = orchard_key_t::level(m_keys(i_oct)); + const auto xyz_vertex = + orchard_key_to_cell_coord(key, i_coord, m_data_cell.block_size()[IX]); + const auto xyz = vertex_coord_to_real_space(xyz_vertex, m_scaling_factor, m_corner); + const auto dx = compute_cell_length(level, block_size[IX]) * m_scaling_factor; + + // signed dim + constexpr auto sdim = static_cast(dim); + + const auto dir0 = (m_dir + 0) % sdim; // Segment axis + const auto dir1 = (m_dir + 1) % sdim; + const auto dir2 = (m_dir + 2) % sdim; + + // Check if segment crosses cell + bool cross = false; + if constexpr (dim == 3) + cross = xyz[dir1] - dx / 2 < m_start[dir1] && xyz[dir1] + dx / 2 > m_end[dir1] && + xyz[dir2] - dx / 2 < m_start[dir2] && xyz[dir2] + dx / 2 > m_end[dir2] && + xyz[dir0] > m_start[dir0] && xyz[dir0] < m_end[dir0]; + else if constexpr (dim == 2) + cross = xyz[dir1] - dx / 2 < m_start[dir1] && xyz[dir1] + dx / 2 > m_end[dir1] && + xyz[dir0] > m_start[dir0] && xyz[dir0] < m_end[dir0]; + + m_offsets(i_global - m_start_index) = cross ? 1 : -1; +} + +// ================================================================================================ +// ================================================================================================ +template +void +ComputeDataSliceAlongLine::operator()(const TagWriteCellData, + const int32_t & i_global) const +{ + const auto offset = m_offsets(i_global - m_start_index); + if (offset == -1) + return; + + const auto nb_cells = m_data_cell.num_cells(); + const auto i_oct = i_global / nb_cells; + const auto i_cell = i_global - nb_cells * i_oct; + + const auto i_coord = cellindex_to_coord(i_cell, m_data_cell.block_size()); + + const auto key = m_keys(i_oct); + const auto level = orchard_key_t::level(m_keys(i_oct)); + const auto xyz_vertex = + orchard_key_to_cell_coord(key, i_coord, m_data_cell.block_size()[IX]); + const auto xyz = vertex_coord_to_real_space(xyz_vertex, m_scaling_factor, m_corner); + + m_positions(offset) = xyz[m_dir]; + m_amr(offset) = level; + m_data_slice(offset) = m_data_cell(i_cell, m_var, i_oct); +} // ComputeDataSliceAlongLine::operator + +// ================================================================================================ +// ================================================================================================ +template +void +ComputeDataSliceAlongLine::operator()(const TagWriteFaceData, + const int32_t & i_global) const +{ + const auto offset = m_offsets(i_global - m_start_index); + if (offset == -1) + return; + + const auto nb_cells = m_data_cell.num_cells(); + const auto i_oct = i_global / nb_cells; + const auto i_cell = i_global - nb_cells * i_oct; + + const auto i_coord = cellindex_to_coord(i_cell, m_data_cell.block_size()); + + const auto key = m_keys(i_oct); + const auto level = orchard_key_t::level(m_keys(i_oct)); + const auto xyz_vertex = + orchard_key_to_cell_coord(key, i_coord, m_data_cell.block_size()[IX]); + const auto xyz = vertex_coord_to_real_space(xyz_vertex, m_scaling_factor, m_corner); + + m_positions(offset) = xyz[m_dir]; + m_amr(offset) = level; + + if constexpr (dim == 2) + { + if (m_var == IX) + { + m_data_slice(offset) = (m_data_face(i_coord[IX], i_coord[IY], m_var, i_oct) + + m_data_face(i_coord[IX] + 1, i_coord[IY], m_var, i_oct)) / + 2; + } + else if (m_var == IY) + { + m_data_slice(offset) = (m_data_face(i_coord[IX], i_coord[IY], m_var, i_oct) + + m_data_face(i_coord[IX], i_coord[IY] + 1, m_var, i_oct)) / + 2; + } + else if (m_var == IZ) + { + m_data_slice(offset) = m_data_face(i_coord[IX], i_coord[IY], m_var, i_oct); + } + } + else if constexpr (dim == 3) + { + if (m_var == IX) + { + m_data_slice(offset) = + (m_data_face(i_coord[IX], i_coord[IY], i_coord[IZ], m_var, i_oct) + + m_data_face(i_coord[IX] + 1, i_coord[IY], i_coord[IZ], m_var, i_oct)) / + 2; + } + else if (m_var == IY) + { + m_data_slice(offset) = + (m_data_face(i_coord[IX], i_coord[IY], i_coord[IZ], m_var, i_oct) + + m_data_face(i_coord[IX], i_coord[IY] + 1, i_coord[IZ], m_var, i_oct)) / + 2; + } + else if (m_var == IY) + { + m_data_slice(offset) = + (m_data_face(i_coord[IX], i_coord[IY], i_coord[IZ], m_var, i_oct) + + m_data_face(i_coord[IX], i_coord[IY], i_coord[IZ] + 1, m_var, i_oct)) / + 2; + } + } + +} // ComputeDataSliceAlongLine::operator + +// ================================================================================================ +// ================================================================================================ +template class ComputeDataSliceAlongLine<2, kalypsso::DefaultDevice>; +template class ComputeDataSliceAlongLine<3, kalypsso::DefaultDevice>; + +} // namespace core + +} // namespace kalypsso diff --git a/src/kalypsso/core/ComputeDataSliceAlongLine.h b/src/kalypsso/core/ComputeDataSliceAlongLine.h new file mode 100644 index 0000000..df4f464 --- /dev/null +++ b/src/kalypsso/core/ComputeDataSliceAlongLine.h @@ -0,0 +1,315 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ComputeDataSliceAlongLine.h + * + * \brief Contains the functor that extracts a data slice along a line. + * + * This functor is able to slice a DataArrayBlock along a line across domain decomposition + * and save data in a file using Numpy file format. This is currently limited to lines which + * direction is axis aligned. + * + * \todo refactor for supporting non-axis aligned slice (even more complex shape defined by + * equation of type f(x,y,z)=0) + * + * \note The functionality is similar to Paraview filter "slice". + */ + +#ifndef KALYPSSO_CORE_COMPUTE_DATA_SLICE_ALONG_LINE_H_ +#define KALYPSSO_CORE_COMPUTE_DATA_SLICE_ALONG_LINE_H_ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace kalypsso +{ + +namespace core +{ + +template +class ComputeDataSliceAlongLine +{ +public: + //! Array of Orchard keys + using OrchardKeys = typename orchard_key_base_t::view_t; + + //! Tag used in marking cells + struct TagMarkCells + {}; + + //! Tag used to write in final array of cell-centered data + struct TagWriteCellData + {}; + + //! Tag used to write in final array of face-centered data + struct TagWriteFaceData + {}; + + /** + * \brief Computes a slice along a line and saves it in cnpy format. + * + * \param data Data array to slice. + * \param start_octant The first owned octant. + * \param end_octant The last owned octant, excluded. + * \param start_point The segment's start. + * \param end_point The segments' end. + * \param keys Orchard keys. + * \param fm Hydro variables to index mapper. + * \param var Variable to save. + * \param file_prefix Files prefix. + * \param parallel_env Parallel environment. + * \param config_map Inputted config map. + */ + static void + apply(const DataArrayBlock & data, + const int32_t start_octant, + const int32_t end_octant, + const Kokkos::Array start_point, + const Kokkos::Array end_point, + const OrchardKeys & keys, + const int32_t var, + const std::string & file_prefix, + const ParallelEnv & parallel_env, + const ConfigMap & config_map); + + /** + * \brief Computes a slice along a line and saves it in cnpy format. + * + * Same as above, but the start and end point are automatically determined to be the middle box + * axis along a given direction. + * + * \param data Data array to slice. + * \param start_octant The first owned octant. + * \param end_octant The last owned octant, excluded. + * \param direction The line direction. + * \param keys Orchard keys. + * \param fm Hydro variables to index mapper. + * \param var Variable to save. + * \param file_prefix Files prefix. + * \param parallel_env Parallel environment. + * \param config_map Inputted config map. + */ + static void + apply(const DataArrayBlock & data, + const int32_t start_octant, + const int32_t end_octant, + const int32_t direction, + const OrchardKeys & keys, + const int32_t var, + const std::string & file_prefix, + const ParallelEnv & parallel_env, + const ConfigMap & config_map); + + /** + * \brief Computes a slice along a line and saves it in cnpy format. + * + * Same as above, but writes multiple scalar values. + * + * \param data Data array to slice. + * \param start_octant The first owned octant. + * \param end_octant The last owned octant, excluded. + * \param direction The line direction. + * \param keys Orchard keys. + * \param fm Hydro variables to index mapper. + * \param vars Vector of variables to save. + * \param var_names Vector of variables names. + * \param file_prefix Files prefix. + * \param parallel_env Parallel environment. + * \param config_map Inputted config map. + */ + static void + apply(const DataArrayBlock & data, + const int32_t start_octant, + const int32_t end_octant, + const int32_t direction, + const OrchardKeys & keys, + const std::vector vars, + const std::vector var_names, + const std::string & file_prefix, + const ParallelEnv & parallel_env, + const ConfigMap & config_map); + + /** + * \brief Computes a slice along a line and saves it in cnpy format. + * + * Same as above, but writes multiple scalar values. + * + * \param data_cell Cell-centered data array to slice. + * \param data_face Face-centered data array to slice. + * \param start_octant The first owned octant. + * \param end_octant The last owned octant, excluded. + * \param direction The line direction. + * \param keys Orchard keys. + * \param fm Hydro variables to index mapper. + * \param cell_vars Vector of cell-centered variables to save. + * \param cell_var_names Vector of cell-centered variables names. + * \param face_vars Vector of face-centered variables to save. + * \param face_var_names Vector of face-centered variables names. + * \param file_prefix Files prefix. + * \param parallel_env Parallel environment. + * \param config_map Inputted config map. + */ + static void + apply(const DataArrayBlock & data_cell, + const FaceDataArrayBlock & data_face, + const int32_t start_octant, + const int32_t end_octant, + const int32_t direction, + const OrchardKeys & keys, + const std::vector cell_vars, + const std::vector cell_var_names, + const std::vector face_vars, + const std::vector face_var_names, + const std::string & file_prefix, + const ParallelEnv & parallel_env, + const ConfigMap & config_map); + + static Kokkos::Array + get_mid_box_start_point(const ConfigMap & config_map, const int32_t direction); + + static Kokkos::Array + get_mid_box_end_point(const ConfigMap & config_map, const int32_t direction); + + void + set_var(int32_t var) + { + m_var = var; + } + + /** + * \brief Kokkos kernel that will mark cells the segment crosses. + * + * \param i_global The global index of the cell. + */ + KOKKOS_FUNCTION void + operator()(const TagMarkCells, const int32_t & i_global) const; + + /** + * \brief Kokkos kernel that will write cell-centered data into the final array. + * + * \param i_global The global index of the cell. + */ + KOKKOS_FUNCTION void + operator()(const TagWriteCellData, const int32_t & i_global) const; + + /** + * \brief Kokkos kernel that will write face-centered data into the final array. + * + * \param i_global The global index of the cell. + */ + KOKKOS_FUNCTION void + operator()(const TagWriteFaceData, const int32_t & i_global) const; + +private: + //! Kokkos execution space + using ExecutionSpace = typename device_t::execution_space; + + /** + * \brief Create and fills in internal data used for the computation. + * + * \param data Data array to slice. + * \param keys Orchard keys. + * \param var Variable to save. + * \param start_index The first owned cell. + * \param end_index The last owned cell, excluded. + * \param start_point The segment's start. + * \param end_point The segments' end. + * \param config_map Inputted config map. + */ + ComputeDataSliceAlongLine(const DataArrayBlock & data, + const OrchardKeys & keys, + const int32_t var, + const int32_t start_index, + const int32_t end_index, + const Kokkos::Array start_point, + const Kokkos::Array end_point, + const ConfigMap & config_map); + + /** + * \brief Create and fills in internal data used for the computation. + * + * \note face-data will be converted into cell centered values. + * + * \param data_cell Cell-centered data array to slice. + * \param data_face Face-centered data array to slice. + * \param keys Orchard keys. + * \param var Variable to save. + * \param start_index The first owned cell. + * \param end_index The last owned cell, excluded. + * \param start_point The segment's start. + * \param end_point The segments' end. + * \param config_map Inputted config map. + */ + ComputeDataSliceAlongLine(const DataArrayBlock & data_cell, + const FaceDataArrayBlock & data_face, + const OrchardKeys & keys, + const int32_t var, + const int32_t start_index, + const int32_t end_index, + const Kokkos::Array start_point, + const Kokkos::Array end_point, + const ConfigMap & config_map); + + //! Cell-centered data array to slice + DataArrayBlock m_data_cell; + + //! Face-centered data array to slice (optional) + FaceDataArrayBlock m_data_face; + + //! The Orchard keys + OrchardKeys m_keys; + + //! Tree scaling factor + real_t m_scaling_factor; + + //! Variable id + int32_t m_var; + + //! Minimum corner of the mesh in real space + Kokkos::Array m_corner; + + //! Start octant + int32_t m_start_index; + + //! Offsets for output + Kokkos::View m_offsets; + + //! Positions + Kokkos::View m_positions; + + //! AMR data + Kokkos::View m_amr; + + //! sliced data + Kokkos::View m_data_slice; + + //! slice axis + ComponentIndex3D m_dir; + + //! Start point + Kokkos::Array m_start; + + //! End point + Kokkos::Array m_end; +}; + +extern template class ComputeDataSliceAlongLine<2, kalypsso::DefaultDevice>; +extern template class ComputeDataSliceAlongLine<3, kalypsso::DefaultDevice>; + +} // namespace core + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_COMPUTE_DATA_SLICE_ALONG_LINE_H_ diff --git a/src/kalypsso/core/ComputeDivergence.cpp b/src/kalypsso/core/ComputeDivergence.cpp new file mode 100644 index 0000000..10ac8a5 --- /dev/null +++ b/src/kalypsso/core/ComputeDivergence.cpp @@ -0,0 +1,157 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ComputeDivergence.cpp + */ +#include + +namespace kalypsso +{ + +// ==================================================================== +// ==================================================================== +template +auto +ComputeDivergence::run(amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + int32_t local_num_octants, + block_size_t block_sizes, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + real_t scaling_factor, + DataArrayBlock_t userdata, + Kokkos::Array field_index) -> DataArrayBlock_t +{ + auto divergence = DataArrayBlock_t("DivergenceB", userdata.block_size(), 1, local_num_octants); + + ComputeDivergence functor(amr_hashmap, + orchard_keys, + local_num_octants, + block_sizes, + brick_sizes, + is_brick_periodic, + scaling_factor, + userdata, + divergence, + field_index); + + const auto nbCellsPerLeaf = Kokkos::dim_prod(block_sizes); + const auto nbCellsTotal = local_num_octants * nbCellsPerLeaf; + + Kokkos::parallel_for( + "compute_divergence_B", Kokkos::RangePolicy(0, nbCellsTotal), functor); + + return divergence; + +} // ComputeDivergence::run + +// ==================================================================== +// ==================================================================== +template +KOKKOS_FUNCTION real_t +ComputeDivergence::getNeighborDataSameLevel(CellLocation const & cell_loc, + int32_t cell_index, + shift_t shift, + int const & ivar) const +{ + const auto cell_loc_neigh = m_helper.getNeighLoc(cell_loc, shift); + const auto cell_index_neigh = coord_to_cellindex(cell_loc_neigh.ijk, m_block_sizes); + + if (cell_loc_neigh.is_outside_domain) + { + // return value in current cell + return m_userdata_in(cell_index, ivar, cell_loc.iOct); + } + else + { + // if neighbor is at the same level or is coarser, juste use the value + if ((cell_loc_neigh.level() == cell_loc.level()) or + (cell_loc_neigh.level() == cell_loc.level() - 1)) + { + KOKKOS_ASSERT(static_cast(cell_loc_neigh.iOct) < m_userdata_in.num_quadrants() && + "userdata has wrong size. You probability forgot to update/resize it."); + return m_userdata_in(cell_index_neigh, ivar, cell_loc_neigh.iOct); + } + // if neighbor is finer, average small cell values + else if (cell_loc_neigh.level() == cell_loc.level() + 1) + { + return m_helper.compute_siblings_average(cell_loc_neigh, m_block_sizes, ivar, m_userdata_in); + } + else + { + // we shouldn't be here + return ZERO_F; + } + } + +} // ComputeDivergence::getNeighborDataSameLevel + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION real_t +ComputeDivergence::compute_divergence(CellLocation_t const & cell_loc, + int32_t const & cell_index) const +{ + + auto data_L = + getNeighborDataSameLevel(cell_loc, cell_index, get_shift_left(), m_field_index[IX]); + + auto data_R = + getNeighborDataSameLevel(cell_loc, cell_index, get_shift_right(), m_field_index[IX]); + + auto div = (data_R - data_L); + + + data_L = + getNeighborDataSameLevel(cell_loc, cell_index, get_shift_left(), m_field_index[IY]); + + data_R = + getNeighborDataSameLevel(cell_loc, cell_index, get_shift_right(), m_field_index[IY]); + + div += (data_R - data_L); + + if constexpr (dim == 3) + { + data_L = + getNeighborDataSameLevel(cell_loc, cell_index, get_shift_left(), m_field_index[IZ]); + + data_R = + getNeighborDataSameLevel(cell_loc, cell_index, get_shift_right(), m_field_index[IZ]); + + div += (data_R - data_L); + } + + return div; + +} // ComputeDivergence::compute_divergence + +// ==================================================================== +// ==================================================================== +template +KOKKOS_FUNCTION void +ComputeDivergence::operator()(const index_t & global_index) const +{ + const auto iOct_global = global_index / m_nbCellsPerLeaf; + const auto cell_index = global_index - iOct_global * m_nbCellsPerLeaf; + + const auto coord = cellindex_to_coord(cell_index, m_block_sizes); + const auto key_cur = m_orchard_keys_device(iOct_global); + CellLocation_t cell_loc{ coord, key_cur, iOct_global, false }; + + auto const level = orchard_key_t::level(m_orchard_keys_device(iOct_global)); + auto const dx = compute_cell_length(level, m_block_sizes[IX]) * m_scaling_factor; + + + auto div = compute_divergence(cell_loc, cell_index) / dx; + + m_userdata_out(cell_index, 0, iOct_global) = div; + +} // ComputeDivergence::operator() + +template class ComputeDivergence<2, kalypsso::DefaultDevice>; +template class ComputeDivergence<3, kalypsso::DefaultDevice>; + +} // namespace kalypsso diff --git a/src/kalypsso/core/ComputeDivergence.h b/src/kalypsso/core/ComputeDivergence.h new file mode 100644 index 0000000..20966f7 --- /dev/null +++ b/src/kalypsso/core/ComputeDivergence.h @@ -0,0 +1,207 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ComputeDivergence.h + * + * Compute divergence of a vector field represented by cell-centered values. + * + * Divergence is evaluated by a simple first order finite difference approximation. + */ +#ifndef KALYPSSO_CORE_COMPUTEDIVERGENCE_H_ +#define KALYPSSO_CORE_COMPUTEDIVERGENCE_H_ + +#include +#include +#include // for math functions (max, min, ...) + +#include // for DataArray, DataArrayHost, DataArrayGhostedBlock +#include + +#include +#include +#include +#include + +#include + +#include // for definition of function cellindex_to_coord and coord_to_cellindex + +#include + +namespace kalypsso +{ + +/** + * \class ComputeDivergence + */ +template +class ComputeDivergence +{ +public: + using exec_space = typename device_t::execution_space; + using index_t = int32_t; + using amr_hashmap_t = typename hashmap_base_t::map_t; + using orchard_key_view_t = typename orchard_key_base_t::view_t; + using CellLocation_t = CellLocation; + + using DataArrayBlock_t = DataArrayBlock; + using DataArrayLeaf_t = DataArrayLeaf; + + // ==================================================================== + // ==================================================================== + //! constructor. + //! + //! All variables are inputs, except flags which will be computed + //! + //! \param[in] amr_hashmap unordered map from orchard key to memory index for owned and ghost + //! quadrants + //! + //! \param[in] orchard_keys array of orchard key ordered by Morton order + //! + //! \param[in] local_num_quadrants number of octants (p4est ghosts excluded) owned by current + //! MPI process + //! + //! \param[in] block_sizes sizes of the local cartesian grid at leaf level + //! + //! \param[in] brick_sizes is an array of p4est brick connectivity sizes (number of trees in + //! each dimension) + //! + //! \param[in] is_brick_periodic array of boolean value indicating if the p4est brick + //! connectivity is periodic in the given dimension + //! + //! \param[in] userdata input block array (owned + MPI ghost blocks) used to compute Schlieren + //! on the first scalar field. + //! + //! \param[out] userdata output block array (owned) containing Schlieren of input array + //! + ComputeDivergence(amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + int32_t local_num_octants, + block_size_t block_sizes, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + real_t scaling_factor, + DataArrayBlock_t userdata_in, + DataArrayBlock_t userdata_out, + Kokkos::Array field_index) + : m_helper(amr_hashmap, orchard_keys, block_sizes, brick_sizes, is_brick_periodic) + , m_amr_hashmap_device(amr_hashmap) + , m_orchard_keys_device(orchard_keys) + , m_local_num_octants(local_num_octants) + , m_nbCellsPerLeaf(Kokkos::dim_prod(block_sizes)) + , m_block_sizes(block_sizes) + , m_brick_sizes(brick_sizes) + , m_is_brick_periodic(is_brick_periodic) + , m_scaling_factor(scaling_factor) + , m_userdata_in(userdata_in) + , m_userdata_out(userdata_out) + , m_field_index(field_index) + {} + + // ==================================================================== + // ==================================================================== + //! destructor. + ~ComputeDivergence() = default; + + // ==================================================================== + // ==================================================================== + //! run the functor. + //! + //! \param[in] userdata is a cell-center data array which may contains many field + //! \param[in] field_index is an array of dim integer to specify which scalar field must be used + //! to compute divergence + static auto + run(amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + int32_t local_num_octants, + block_size_t block_sizes, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + real_t scaling_factor, + DataArrayBlock_t userdata, + Kokkos::Array field_index) -> DataArrayBlock_t; + + // ============================================================== + // ============================================================== + /** + * Get, compute or average data in neighbor cell defined as a shift from current cell. + * + * \param[in] cell_loc is cell location of current cell + * \param[in] cell_index is cell index of current cell inside current block grid + * \param[in] shift defines a translation (integer number of cell away from current cell) + * \param[in] which variable to get + */ + KOKKOS_FUNCTION real_t + getNeighborDataSameLevel(CellLocation const & cell_loc, + int32_t cell_index, + shift_t shift, + int const & ivar) const; + + + // ============================================================== + // ============================================================== + /** + * Compute simple gradient norm + * + * \param[in] cell_loc is cell location of current cell + * \param[in] cell_index is cell index of current cell inside current block grid + */ + KOKKOS_FUNCTION real_t + compute_divergence(CellLocation_t const & cell_loc, int32_t const & cell_index) const; + + // ============================================================== + // ============================================================== + /** + * range policy functor body + */ + KOKKOS_FUNCTION void + operator()(const index_t & global_index) const; + +private: + //! help to compute cell location + StencilHelper m_helper; + + //! AMR unordered map which maps orchard keys to quadrant number for all key in the mesh + //! (owned quadrants and ghost quadrants) + amr_hashmap_t m_amr_hashmap_device; + + //! list of orchard key of the mesh + orchard_key_view_t m_orchard_keys_device; + + //! total number of octants in the current MPI process (ghost block excluded) + const int32_t m_local_num_octants; + + //! number of cells per leaf block + const int32_t m_nbCellsPerLeaf; + + //! block sizes + const block_size_t m_block_sizes; + + //! p4est brick connectivity sizes + const brick_size_t m_brick_sizes; + + //! is p4est connectivity periodic ? + const Kokkos::Array m_is_brick_periodic; + + //! scaling factor + real_t m_scaling_factor; + + //! a block data array (no ghosts, sizes= bx,by,bz) + DataArrayBlock_t m_userdata_in; + + //! a block data array (no ghosts, sizes= bx,by,bz) + DataArrayBlock_t m_userdata_out; + + //! array of index to use to specify x,y and z components + Kokkos::Array m_field_index; + +}; // class ComputeDivergence + +extern template class ComputeDivergence<2, kalypsso::DefaultDevice>; +extern template class ComputeDivergence<3, kalypsso::DefaultDevice>; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_COMPUTEDIVERGENCE_H_ diff --git a/src/kalypsso/core/ComputeError.h b/src/kalypsso/core/ComputeError.h new file mode 100644 index 0000000..9d88110 --- /dev/null +++ b/src/kalypsso/core/ComputeError.h @@ -0,0 +1,220 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ComputeError.h + * + * Just compute L1 or L2 norm of the difference between two data array. + */ +#ifndef KALYPSSO_CORE_COMPUTEERROR_H_ +#define KALYPSSO_CORE_COMPUTEERROR_H_ + +#include +#include +#include // for math functions (max, min, ...) +#include // for DataArray, DataArrayHost, DataArrayGhostedBlock +#include + +#include <../better-enums/enum.h> + +namespace kalypsso +{ + +// clang-format off +BETTER_ENUM(NormType, int, L1, L2) +BETTER_ENUM(NormId, int, ERR, REF) +// clang-format on + +//! a simple data structure to hold two values +//! - an error norm +//! - a reference norm +//! +//! this struct is used in a custom sum reduction +struct Norms +{ + real_t values[2]; + + KOKKOS_INLINE_FUNCTION + Norms() + { + values[0] = 0; + values[1] = 0; + } + + KOKKOS_INLINE_FUNCTION + Norms(const Norms & rhs) + { + values[0] = rhs.values[0]; + values[1] = rhs.values[1]; + } + + KOKKOS_INLINE_FUNCTION + Norms & + operator+=(const Norms & src) + { + values[0] += src.values[0]; + values[1] += src.values[1]; + return *this; + } +}; // struct Norms +} // namespace kalypsso + +namespace Kokkos +{ +template <> +struct reduction_identity +{ + KOKKOS_FORCEINLINE_FUNCTION static kalypsso::Norms + sum() + { + return kalypsso::Norms(); + } +}; +} // namespace Kokkos + +namespace kalypsso +{ +/** + * \class ComputeError + */ +template +class ComputeError +{ +public: + //! our kokkos execution space + using exec_space = typename device_t::execution_space; + + //! type alias for a data array at block level (see kalypsso_data_container.h) + using DataArrayBlock_t = DataArrayBlock; + + using index_t = int64_t; + +private: + //! heavy data + DataArrayBlock_t m_data1, m_data2; + + //! identify which variable is used to compute error + int m_varId; + + //! identify which norm is used to compute error + NormType m_norm_type; + + //! flag to indicate if we want to divide (normalize) values + bool m_divideByVar0; + +public: + // ==================================================================== + // ==================================================================== + //! constructor. + //! + //! \param[in] varId identify which variable to reduce (ID, IE, IU, ...) + ComputeError(DataArrayBlock_t data1, + DataArrayBlock_t data2, + int varId, + NormType norm_type, + bool divideByVar0) + : m_data1(data1) + , m_data2(data2) + , m_varId(varId) + , m_norm_type(norm_type) + , m_divideByVar0(divideByVar0) + {} + + // ==================================================================== + // ==================================================================== + //! destructor. + ~ComputeError() = default; + + // ==================================================================== + // ==================================================================== + //! perform reduction. + //! + //! \return return relative error: |U-Uref| / |Uref| + //! norm used can be either L1 or L2 + static auto + apply([[maybe_unused]] const ParallelEnv & par_env, + DataArrayBlock_t data1, + DataArrayBlock_t data2, + int varId, + NormType norm_type, + bool divideByVar0) + { + ComputeError functor(data1, data2, varId, norm_type, divideByVar0); + + // check that data1 and data2 have same sizes + // clang-format off + assertm(data1.num_cells() == data2.num_cells() and + data1.num_vars() == data2.num_vars() and + data1.num_quadrants() == data2.num_quadrants(), + "[ComputeError] data and data2 array sizes don't match !"); + // clang-format on + + const auto nbCellsPerLeaf = data1.num_cells(); + const auto local_num_octants = data1.num_quadrants(); + const auto nbCellsTotal = local_num_octants * nbCellsPerLeaf; + + Norms norms; + Kokkos::Sum reducer(norms); + Kokkos::parallel_reduce( + "ComputeError", Kokkos::RangePolicy(0, nbCellsTotal), functor, reducer); + + real_t total_error = 0; + real_t total_ref = 0; + +#ifdef KALYPSSO_CORE_USE_MPI + par_env.comm().MPI_Reduce(&norms.values[NormId::ERR], &total_error, 1, 0); + par_env.comm().MPI_Reduce(&norms.values[NormId::REF], &total_ref, 1, 0); +#else + total_error = norms.values[NormId::ERR]; + total_ref = norms.values[NormId::REF]; +#endif + + if (norm_type == +NormType::L2) + { + total_error = sqrt(total_error); + total_ref = sqrt(total_ref); + } + + // return relative error + return total_error / total_ref; + + } // apply + + // ==================================================================== + // ==================================================================== + KOKKOS_INLINE_FUNCTION + void + operator()(const index_t & global_index, Norms & norms) const + { + // convert global index into + // - octant id + // - cell_index inside block (from 0 to nbCellsPerLeaf-1) + const auto iOct = global_index / m_data1.num_cells(); + const auto cell_index = global_index - iOct * m_data1.num_cells(); + + const auto value1 = m_divideByVar0 + ? m_data1(cell_index, m_varId, iOct) / m_data1(cell_index, 0, iOct) + : m_data1(cell_index, m_varId, iOct); + const auto value2 = m_divideByVar0 + ? m_data2(cell_index, m_varId, iOct) / m_data2(cell_index, 0, iOct) + : m_data2(cell_index, m_varId, iOct); + + if (m_norm_type == +NormType::L1) + { + norms.values[NormId::ERR] += fabs(value1 - value2); + norms.values[NormId::REF] += fabs(value2); + } + else if (m_norm_type == +NormType::L2) + { + norms.values[NormId::ERR] += (value1 - value2) * (value1 - value2); + norms.values[NormId::REF] += (value2 * value2); + } + + } // operator () + +}; // class ComputeError + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_COMPUTEERROR_H_ diff --git a/src/kalypsso/core/ComputeFilteredCurvature.cpp b/src/kalypsso/core/ComputeFilteredCurvature.cpp new file mode 100644 index 0000000..ac03f3b --- /dev/null +++ b/src/kalypsso/core/ComputeFilteredCurvature.cpp @@ -0,0 +1,191 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ComputeFilteredCurvature.cpp + */ +#include + +namespace kalypsso +{ + +/*************************************************/ +/*************************************************/ +/*************************************************/ +template +ComputeFilteredCurvature::ComputeFilteredCurvature( + DataArrayGhostedBlock_t const & qdata, + const int32_t iphi_id, + DataArrayGhostedBlock_t const & unfiltered_curvature, + DataArrayGhostedBlock_t const & curvature_weights, + DataArrayGhostedBlock_t const & curvature, + const int32_t iOct_first, + const int32_t num_quads, + ConfigMap const & config_map) + : m_q(qdata) + , m_iphi(iphi_id) + , m_unfiltered_curvature(unfiltered_curvature) + , m_weights(curvature_weights) + , m_curvature(curvature) + , m_iOct_first(iOct_first) + , m_num_quads(num_quads) + , m_num_filt_iter(config_map.getInteger("smooth_interface_function", "filter_iterations", 3)) + , m_filt_threshold(config_map.getReal("smooth_interface_function", "filter_threshold", ONE_EM6_F)) + + +{} // constructor + +// ============================================================== +// ============================================================== +template +void +ComputeFilteredCurvature::apply(DataArrayGhostedBlock_t const & qdata, + const int32_t iphi_id, + DataArrayGhostedBlock_t const & unfiltered_curvature, + DataArrayGhostedBlock_t const & curvature_weights, + DataArrayGhostedBlock_t const & curvature, + const int32_t iOct_first, + const int32_t num_quads, + ConfigMap const & config_map) +{ + + ComputeFilteredCurvature functor(qdata, + iphi_id, + unfiltered_curvature, + curvature_weights, + curvature, + iOct_first, + num_quads, + config_map); + + // compute weights everywhere + const auto nbIterations = num_quads * curvature.num_cells(); + + // apply filter only on the inner part of the block of cells + const auto nbIterations_filter = num_quads * curvature.num_cells_inner(); + + // first compute weights + Kokkos::parallel_for("kalypsso::core::ComputeFilteredCurvature - compute weights", + Kokkos::RangePolicy(0, nbIterations), + functor); + + auto const & num_filt_iter = functor.m_num_filt_iter; + + // apply filter + for (int iter = 0; iter < num_filt_iter; iter++) + { + Kokkos::parallel_for("kalypsso::core::ComputeFilteredCurvature - apply filter", + Kokkos::RangePolicy(0, nbIterations_filter), + functor); + + // swap array to get prepared for the next iteration + if (iter < num_filt_iter - 1) + { + my_swap(functor.m_unfiltered_curvature, functor.m_curvature); + } + } + +} // ComputeFilteredCurvature::apply + +// ==================================================================== +// ==================================================================== +template +KOKKOS_INLINE_FUNCTION void +ComputeFilteredCurvature::operator()(TagComputeWeights const &, + const index_t & global_index) const +{ + // retrieve local octant index in range [0, num_quads_to_process [ + auto const i_oct = static_cast(global_index / m_curvature.num_cells()); + auto const cell_index = static_cast(global_index - i_oct * m_curvature.num_cells()); + + const auto phi = m_q(cell_index, m_iphi, i_oct); + + m_weights(cell_index, 0, i_oct) = phi * phi * (ONE_F - phi) * (ONE_F - phi); +} + +// ==================================================================== +// ==================================================================== +template +KOKKOS_INLINE_FUNCTION void +ComputeFilteredCurvature::operator()(TagApplyFilter const &, + const index_t & global_index) const +{ + // retrieve local octant index in range [0, num_quads_to_process [ + auto const i_oct = static_cast(global_index / m_curvature.num_cells_inner()); + auto const cell_index = + static_cast(global_index - i_oct * m_curvature.num_cells_inner()); + + // compute cartesian coordinates inside ghosted block + const auto coord = cellindex_to_coord(cell_index, m_curvature.block_size()); + + if constexpr (dim == 2) + { + auto const & i = coord[IX]; + auto const & j = coord[IY]; + + // only apply filter where needed + const auto w = m_weights(i, j, 0, i_oct); + + if (w > m_filt_threshold) + { + auto sum_w = ZERO_F; + auto sum_w_k = ZERO_F; + + for (int dj = -1; dj < 2; ++dj) + { + for (int di = -1; di < 2; ++di) + { + const auto ii = i + di; + const auto jj = j + dj; + + sum_w += m_weights(ii, jj, 0, i_oct); + sum_w_k += m_weights(ii, jj, 0, i_oct) * m_unfiltered_curvature(ii, jj, 0, i_oct); + } + } + + m_curvature(i, j, 0, i_oct) = sum_w_k / sum_w; + } + } + else if constexpr (dim == 3) + { + auto const & i = coord[IX]; + auto const & j = coord[IY]; + auto const & k = coord[IZ]; + + // only apply filter where needed + const auto w = m_weights(i, j, k, 0, i_oct); + + if (w > m_filt_threshold) + { + auto sum_w = ZERO_F; + auto sum_w_k = ZERO_F; + + for (int dk = -1; dk < 2; ++dk) + { + for (int dj = -1; dj < 2; ++dj) + { + for (int di = -1; di < 2; ++di) + { + const auto ii = i + di; + const auto jj = j + dj; + const auto kk = k + dk; + + sum_w += m_weights(ii, jj, kk, 0, i_oct); + sum_w_k += + m_weights(ii, jj, kk, 0, i_oct) * m_unfiltered_curvature(ii, jj, kk, 0, i_oct); + } + } + } + + m_curvature(i, j, k, 0, i_oct) = sum_w_k / sum_w; + } + } + +} // ComputeFilteredCurvature::operator () + +// explicit template instantiation +template class ComputeFilteredCurvature<2, kalypsso::DefaultDevice>; +template class ComputeFilteredCurvature<3, kalypsso::DefaultDevice>; + +} // namespace kalypsso diff --git a/src/kalypsso/core/ComputeFilteredCurvature.h b/src/kalypsso/core/ComputeFilteredCurvature.h new file mode 100644 index 0000000..eafd53c --- /dev/null +++ b/src/kalypsso/core/ComputeFilteredCurvature.h @@ -0,0 +1,128 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ComputeFilteredCurvature.h + */ +#ifndef KALYPSSO_CORE_COMPUTE_FILTERED_CURVATURE_H_ +#define KALYPSSO_CORE_COMPUTE_FILTERED_CURVATURE_H_ + +#include // for assertm +#include +#include // for DataArrayBlock +#include + +namespace kalypsso +{ + +/*************************************************/ +/*************************************************/ +/*************************************************/ +/** + * Compute interface curvature as the divergence of the normal vector using a + * second order central difference scheme. + * + * References: + * + * - A finite-volume HLLC-based scheme for compressible interfacial flows with surface tension, + * Garrick Owkes and Regele, Journal of Computational Physics Volume 339, 15 June 2017, Pages 46-67. + * https://doi.org/10.1016/j.jcp.2017.03.007 + * - An interface capturing scheme for modeling atomization in compressible flows, Garrick, Hagen + * and Regele, Journal of Computational Physics, Volume 344, 1 September 2017, Pages 260-280. + * https://doi.org/10.1016/j.jcp.2017.04.079 + */ +template +class ComputeFilteredCurvature +{ + +public: + using exec_space = typename device_t::execution_space; + using index_t = int64_t; + + // data array related type aliases + using DataArrayGhostedBlock_t = DataArrayGhostedBlock; + using OrchardKeys = typename orchard_key_base_t::view_t; + + struct TagComputeWeights + {}; + struct TagApplyFilter + {}; + +private: + //! Conservative variable (to access volume fractions) + DataArrayGhostedBlock_t m_q; + + //! index to volume fraction + int32_t m_iphi; + + //! Unfiltered curvature + DataArrayGhostedBlock_t m_unfiltered_curvature; + + //! Curvature weights + DataArrayGhostedBlock_t m_weights; + + //! Filtered curvature + DataArrayGhostedBlock_t m_curvature; + + //! offset to first octant + const int32_t m_iOct_first; + + //! Number of quadrants to process + const int32_t m_num_quads; + + //! Number of filter iterations + const int32_t m_num_filt_iter; + + //! filter threshold in unit of phi^2*(1-phi)^2 + const real_t m_filt_threshold; + + KALYPSSO_STATIC_MATH_CONSTANT(ONE_EM6, 1e-6); + +public: + // ============================================================== + // ============================================================== + ComputeFilteredCurvature(DataArrayGhostedBlock_t const & qdata, + const int32_t iphi_id, + DataArrayGhostedBlock_t const & unfiltered_curvature, + DataArrayGhostedBlock_t const & curvature_weights, + DataArrayGhostedBlock_t const & curvature, + const int32_t iOct_first, + const int32_t num_quads, + ConfigMap const & config_map); + + // ============================================================== + // ============================================================== + //! static method which does it all: create and execute functor with range policy + //! + static void + apply(DataArrayGhostedBlock_t const & qdata, + const int32_t iphi_id, + DataArrayGhostedBlock_t const & unfiltered_curvature, + DataArrayGhostedBlock_t const & curvature_weights, + DataArrayGhostedBlock_t const & curvature, + const int32_t iOct_first, + const int32_t num_quads, + ConfigMap const & config_map); + + // ==================================================================== + // ==================================================================== + KOKKOS_INLINE_FUNCTION + void + operator()(TagComputeWeights const &, const index_t & global_index) const; + + // ==================================================================== + // ==================================================================== + KOKKOS_INLINE_FUNCTION + void + operator()(TagApplyFilter const &, const index_t & global_index) const; + +}; // ComputeFilteredCurvature + +// explicit template instantiation +extern template class ComputeFilteredCurvature<2, kalypsso::DefaultDevice>; +extern template class ComputeFilteredCurvature<3, kalypsso::DefaultDevice>; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_COMPUTE_FILTERED_CURVATURE_H_ diff --git a/src/kalypsso/core/ComputeGhostedScalarField.cpp b/src/kalypsso/core/ComputeGhostedScalarField.cpp new file mode 100644 index 0000000..9a30584 --- /dev/null +++ b/src/kalypsso/core/ComputeGhostedScalarField.cpp @@ -0,0 +1,452 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ComputeGhostedScalarField.cpp + */ +#include + +namespace kalypsso +{ + +// ============================================================== +// ============================================================== +template +ComputeGhostedScalarField::ComputeGhostedScalarField( + StencilHelper_t stencil_helper, + AMRMeshInfo amr_mesh_info, + int32_t iOct_begin, + DataArrayBlock_t userdata_in, + DataArrayGhostedBlock_t userdata_out, + int32_t ivar, + CellCenteredProlongationType prolongation) + : m_stencil_helper(stencil_helper) + , m_mirror_orchard_keys_device() + , m_amr_mesh_info(amr_mesh_info) + , m_iOct_begin(iOct_begin) + , m_userdata_in(userdata_in) + , m_userdata_out(userdata_out) + , m_ivar(ivar) + , m_prolongation(prolongation) + +{ + KOKKOS_ASSERT(userdata_in.block_size() == userdata_out.block_size() && + "userdata_in and userdata_out must have the same block sizes."); +} + +// ============================================================== +// ============================================================== +// same as above, but specifying also the mirror keys array +template +ComputeGhostedScalarField::ComputeGhostedScalarField( + StencilHelper_t stencil_helper, + orchard_key_view_t mirror_orchard_keys, + AMRMeshInfo amr_mesh_info, + DataArrayBlock_t userdata_in, + DataArrayGhostedBlock_t userdata_out, + int32_t ivar, + CellCenteredProlongationType prolongation) + : m_stencil_helper(stencil_helper) + , m_mirror_orchard_keys_device(mirror_orchard_keys) + , m_amr_mesh_info(amr_mesh_info) + , m_iOct_begin(0) // not used when processing mirrors quad + , m_userdata_in(userdata_in) + , m_userdata_out(userdata_out) + , m_ivar(ivar) + , m_prolongation(prolongation) +{} + +// ============================================================== +// ============================================================== +template +void +ComputeGhostedScalarField::apply_on_group(amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + AMRMeshInfo amr_mesh_info, + int32_t iOct_begin, + int32_t num_octants_in_group, + DataArrayBlock_t userdata_in, + DataArrayGhostedBlock_t userdata_out, + int32_t ivar, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic) +{ + // make sure the range of octants to process is valid + assertm((iOct_begin + num_octants_in_group) <= amr_mesh_info.local_num_quadrants(), + "Invalid range of octants to process"); + + auto stencil_helper = StencilHelper_t( + amr_hashmap, orchard_keys, userdata_in.block_size(), brick_sizes, is_brick_periodic); + + const auto prolongation_type = CellCenteredProlongationType::EXTRAPOLATE_LINEAR_MINMOD; + + ComputeGhostedScalarField functor( + stencil_helper, amr_mesh_info, iOct_begin, userdata_in, userdata_out, ivar, prolongation_type); + + const auto nbCellsPerGhostedLeaf = userdata_out.num_cells(); + const auto nbCellsTotal = num_octants_in_group * nbCellsPerGhostedLeaf; + + // for AMR tree leaf, explore the neighbor block + Kokkos::parallel_for("ComputeGhostedScalarField", + Kokkos::RangePolicy(0, nbCellsTotal), + functor); + +} // apply_on_group + +// ============================================================== +// ============================================================== +template +void +ComputeGhostedScalarField::apply_in_mirrors( + amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + orchard_key_view_t mirror_orchard_keys, + AMRMeshInfo amr_mesh_info, + DataArrayBlock_t userdata_in, + DataArrayGhostedBlock_t userdata_out, + int32_t ivar, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic) +{ + + auto stencil_helper = StencilHelper_t( + amr_hashmap, orchard_keys, userdata_in.block_size(), brick_sizes, is_brick_periodic); + + const auto prolongation_type = CellCenteredProlongationType::EXTRAPOLATE_LINEAR_MINMOD; + + ComputeGhostedScalarField functor(stencil_helper, + mirror_orchard_keys, + amr_mesh_info, + userdata_in, + userdata_out, + ivar, + prolongation_type); + + const auto num_mirrors = mirror_orchard_keys.extent(0); + const auto nbCellsPerGhostedLeaf = userdata_out.num_cells(); + const auto nbCellsTotal = num_mirrors * static_cast(nbCellsPerGhostedLeaf); + + assertm(num_mirrors == static_cast(amr_mesh_info.local_num_mirrors()), + "wrong number of mirror quads."); + + // for AMR tree leaf, explore the neighbor block + Kokkos::parallel_for("ComputeGhostedScalarField", + Kokkos::RangePolicy(0, nbCellsTotal), + functor); + +} // apply_in_mirrors + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION real_t +ComputeGhostedScalarField::get_var(CellLocation_t const & cell_loc) const +{ + const auto cellindex_in = cell_loc.cellindex(m_userdata_in.block_size()); + const auto & iOct_in = cell_loc.iOct; + + return m_userdata_in(cellindex_in, m_ivar, iOct_in); +} + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION real_t +ComputeGhostedScalarField::get_var_restriction(CellLocation_t const & cell_loc) const +{ + + auto const & block_size = m_userdata_in.block_size(); + + return m_stencil_helper.compute_siblings_average(cell_loc, block_size, m_ivar, m_userdata_in); + +} // get_var_restriction + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +ComputeGhostedScalarField::fill_inner(int32_t cellindex_in, + int32_t cellindex_out, + iOct_t iOct_global, + iOct_t iOct_out) const +{ + // read variable + const auto value = m_userdata_in(cellindex_in, m_ivar, iOct_global); + + // write variable + m_userdata_out(cellindex_out, 0, iOct_out) = value; + +} // fill_inner + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +ComputeGhostedScalarField::fill_ghost_copy(CellLocation_t const & cell_loc_out, + CellLocation_t const & cell_loc_in, + index_t const & cellindex_out, + iOct_t const & iOct_out) const +{ + + const bool do_restriction = cell_loc_in.level() == (cell_loc_out.level() + 1); + + // read variable + const auto value = do_restriction ? get_var_restriction(cell_loc_in) : get_var(cell_loc_in); + + // write variable + m_userdata_out(cellindex_out, 0, iOct_out) = value; + +} // fill_ghost_copy + +// ============================================================== +// ============================================================== +template +template > +KOKKOS_INLINE_FUNCTION void +ComputeGhostedScalarField::linear_extrapolate_using_limited_slopes( + CellLocation<2> const & cell_loc_neigh, + coord_t<2> const & coord_in, + [[maybe_unused]] iOct_t const & iOct_global, + index_t const & cellindex_out, + iOct_t const & iOct_out) const +{ + const real_t slope_type = 1; + + const auto cell_loc_left_x = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(-XDIR)); + const auto cell_loc_right_x = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(+XDIR)); + + const auto cell_loc_left_y = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(-YDIR)); + const auto cell_loc_right_y = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(+YDIR)); + + // determine local position of current cell inside virtual parent cell using integer coordinates + // in -1, +1 + const int ix = 2 * (coord_in[IX] - 2 * (coord_in[IX] / 2)) - 1; + const int iy = 2 * (coord_in[IY] - 2 * (coord_in[IY] / 2)) - 1; + + // compute limited slopes + auto const dudx = m_stencil_helper.compute_minmod_slopes( + cell_loc_neigh, cell_loc_right_x, cell_loc_left_x, m_ivar, m_userdata_in, slope_type); + auto const dudy = m_stencil_helper.compute_minmod_slopes( + cell_loc_neigh, cell_loc_right_y, cell_loc_left_y, m_ivar, m_userdata_in, slope_type); + + // extrapolate conservative variables + real_t value = + m_userdata_in( + cell_loc_neigh.cellindex(m_userdata_in.block_size()), m_ivar, cell_loc_neigh.iOct) + + ONE_FOURTH_F * static_cast(ix) * dudx + ONE_FOURTH_F * static_cast(iy) * dudy; + + m_userdata_out(cellindex_out, 0, iOct_out) = value; + +} // linear_extrapolate_using_limited_slopes - 2d + +// ============================================================== +// ============================================================== +template +template > +KOKKOS_INLINE_FUNCTION void +ComputeGhostedScalarField::linear_extrapolate_using_limited_slopes( + CellLocation<3> const & cell_loc_neigh, + coord_t<3> const & coord_in, + iOct_t const & iOct_global, + index_t const & cellindex_out, + [[maybe_unused]] iOct_t const & iOct_out) const +{ + + const real_t slope_type = 1; // TODO : investigate if a better value should be searched for + + const auto cell_loc_left_x = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(-XDIR)); + const auto cell_loc_right_x = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(+XDIR)); + + const auto cell_loc_left_y = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(-YDIR)); + const auto cell_loc_right_y = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(+YDIR)); + + const auto cell_loc_left_z = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(-ZDIR)); + const auto cell_loc_right_z = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(+ZDIR)); + + // determine local position of current cell inside virtual parent cell using integer coordinates + // in -1, +1 + const int ix = 2 * (coord_in[IX] - 2 * (coord_in[IX] / 2)) - 1; + const int iy = 2 * (coord_in[IY] - 2 * (coord_in[IY] / 2)) - 1; + const int iz = 2 * (coord_in[IZ] - 2 * (coord_in[IZ] / 2)) - 1; + + // compute limited slopes + auto const dudx = m_stencil_helper.compute_minmod_slopes( + cell_loc_neigh, cell_loc_right_x, cell_loc_left_x, m_ivar, m_userdata_in, slope_type); + auto const dudy = m_stencil_helper.compute_minmod_slopes( + cell_loc_neigh, cell_loc_right_y, cell_loc_left_y, m_ivar, m_userdata_in, slope_type); + auto const dudz = m_stencil_helper.compute_minmod_slopes( + cell_loc_neigh, cell_loc_right_z, cell_loc_left_z, m_ivar, m_userdata_in, slope_type); + + // extrapolate conservative variables + real_t value = + m_userdata_in( + cell_loc_neigh.cellindex(m_userdata_in.block_size()), m_ivar, cell_loc_neigh.iOct) + + ONE_FOURTH_F * static_cast(ix) * dudx + ONE_FOURTH_F * static_cast(iy) * dudy + + ONE_FOURTH_F * static_cast(iz) * dudz; + + m_userdata_out(cellindex_out, 0, iOct_global - m_iOct_begin) = value; + +} // linear_extrapolate_using_limited_slopes - 3d + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +ComputeGhostedScalarField::fill_ghosts(index_t const & cellindex_out, + coord_t const & coord_out, + iOct_t const & iOct_global, + iOct_t const & iOct_out) const +{ + + const auto & b = m_userdata_in.block_size(); + + // coordinates of source cell (where to read data) + coord_t coord_in; + const auto dir = ghosted_coords_to_inner_coords(coord_in, coord_out, b); + + int32_t cellindex_in = coord_to_cellindex(coord_in, m_userdata_in.block_size()); + + auto dir_norm = dir[IX] * dir[IX] + dir[IY] * dir[IY]; + if constexpr (dim == 3) + dir_norm += dir[IZ] * dir[IZ]; + + if (dir_norm == 0) + { + // current cell is inside current block + fill_inner(cellindex_in, cellindex_out, iOct_global, iOct_out); + } + else + { + // current cell is a ghost cell (thus belonging to a neighbor block) + + /* + * fill actual ghosts with data from a neighbor block. + */ + + // get orchard key of current octant + auto key_cur = m_stencil_helper.key(iOct_global); + + shift_t shift; + shift[IX] = b[IX] * dir[IX]; + shift[IY] = b[IY] * dir[IY]; + if constexpr (dim == 3) + { + shift[IZ] = b[IZ] * dir[IZ]; + } + + const CellLocation_t cell_loc_cur{ coord_in, key_cur, iOct_global, false }; + const auto cell_loc_neigh = m_stencil_helper.getNeighLoc(cell_loc_cur, shift); + + /* + * Dealing with the 3 possibilities: + * - neighbor octant is at same AMR level : doing a simple copy + * - neighbor octant is at finer AMR level : doing a restriction (average values) + * - neighbor octant is at coarser AMR level : doing a prolongation + */ + if (cell_loc_neigh.level() >= cell_loc_cur.level()) + { + // doing a simple copy or doing a restriction when neighbor is at higher AMR level + fill_ghost_copy(cell_loc_cur, cell_loc_neigh, cellindex_out, iOct_out); + } + else if (cell_loc_neigh.level() + 1 == cell_loc_cur.level()) + { + // doing a prolongation because neighbor is coarser + + if (m_prolongation == +CellCenteredProlongationType::SIMPLE_COPY) + { + // simple copy of the coarse value + fill_ghost_copy(cell_loc_cur, cell_loc_neigh, cellindex_out, iOct_out); + } + else if (m_prolongation == +CellCenteredProlongationType::EXTRAPOLATE_LINEAR_MINMOD) + { + linear_extrapolate_using_limited_slopes( + cell_loc_neigh, coord_in, iOct_global, cellindex_out, iOct_out); + } + } + else + { + KOKKOS_ASSERT(false && "Logic error: neighbor octant not found (Kernel Panic !)"); + } + + } // end if (dir_norm ==0) + +} // fill_ghosts + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +ComputeGhostedScalarField::operator()(TagComputeAllQuad const &, + const index_t & global_index) const +{ + + const auto nbCellsPerGhostedLeaf = m_userdata_out.num_cells(); + + // retrieve local octant index (this is where we want to write data) + const auto iOct_local = global_index / nbCellsPerGhostedLeaf; + const auto cell_index_out = global_index - iOct_local * nbCellsPerGhostedLeaf; + + // retrieve global octant index + const auto iOct_global = m_iOct_begin + iOct_local; + + // compute cartesian coordinates inside ghosted block + const auto coord_out = cellindex_to_coord( + cell_index_out, m_userdata_out.ghosted_block_size(), m_userdata_out.shift()); + + fill_ghosts(cell_index_out, coord_out, iOct_global, iOct_local); + +} // operator() - TagComputeAllQuad + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +ComputeGhostedScalarField::operator()(TagComputeMirrorQuad const &, + const index_t & global_index) const +{ + + const auto nbCellsPerGhostedLeaf = m_userdata_out.num_cells(); + + // retrieve mirror index + const auto iMirror = global_index / nbCellsPerGhostedLeaf; + const auto cell_index_out = global_index - iMirror * nbCellsPerGhostedLeaf; + + // retrieve key associated to that mirror index + const auto mirror_key = m_mirror_orchard_keys_device(iMirror); + + // make sure the key is in the hashmap and retrieve value + const auto mirror_hashindex = m_stencil_helper.m_amr_hashmap_device.find(mirror_key); + [[maybe_unused]] const auto valid = + m_stencil_helper.m_amr_hashmap_device.valid_at(mirror_hashindex); + + KOKKOS_ASSERT( + valid && "(mirror quadrant) key doesn't exist in hashmap (this is in principle not possible, " + "since mirror keys are computed from p4est ghosts.)"); + + // retrieve iOct associated to that mirror quadrant + const auto iOct_global = m_stencil_helper.m_amr_hashmap_device.value_at(mirror_hashindex); + + // compute cartesian coordinates inside ghosted block + const auto coord_out = cellindex_to_coord( + cell_index_out, m_userdata_out.ghosted_block_size(), m_userdata_out.shift()); + + fill_ghosts(cell_index_out, coord_out, iOct_global, iMirror); + +} // operator() - TagComputeMirrorQuad + +// explicit template instantiation +template class ComputeGhostedScalarField<2, kalypsso::DefaultDevice>; +template class ComputeGhostedScalarField<3, kalypsso::DefaultDevice>; + +} // namespace kalypsso diff --git a/src/kalypsso/core/ComputeGhostedScalarField.h b/src/kalypsso/core/ComputeGhostedScalarField.h new file mode 100644 index 0000000..e9ab31d --- /dev/null +++ b/src/kalypsso/core/ComputeGhostedScalarField.h @@ -0,0 +1,335 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ComputeGhostedScalarField.h + */ +#ifndef KALYPSSO_CORE_COMPUTEGHOSTEDSCALARFIELD_H_ +#define KALYPSSO_CORE_COMPUTEGHOSTEDSCALARFIELD_H_ + +#include +#include +#include +#include +#include +#include + +namespace kalypsso +{ + +/** + * \class ComputeGhostedScalarField + * + * This class is a direct adaptation of FillBlockGhostsFunctor: + * - given in input a DataArrayBlock, extract one scalar field and fill ghost cells around all + * blocks + * - to make sure MPI ghost block are properly filled, one needs to compute first the mirror octant + * (in the sens defined by p4est) and perform a MPI communication (this must be done in the calling + * routine). + * + * \note No prolongation operator is applied to fill ghost cells when the neighbor block is at + * coarser AMR level. + * + */ +template +class ComputeGhostedScalarField +{ + +public: + using exec_space = typename device_t::execution_space; + using index_t = int32_t; + + using amr_hashmap_t = typename hashmap_base_t::map_t; + using orchard_key_view_t = typename orchard_key_base_t::view_t; + + using DataArrayBlock_t = DataArrayBlock; + using DataArrayGhostedBlock_t = DataArrayGhostedBlock; + + using CellLocation_t = CellLocation; + using StencilHelper_t = StencilHelper; + +private: + //! helper to compute neighbor cell location + StencilHelper_t m_stencil_helper; + + //! list of orchard keys that are "mirrors" (in the p4est sense). + //! only used when we want to solely computed mirror quadrants. + orchard_key_view_t m_mirror_orchard_keys_device; + + //! AMR mesh info (number of owned, MPI ghost, outside quadrants) + AMRMeshInfo m_amr_mesh_info; + + //! starting octant id. + //! this is a global octant id offset to the first octant to be processed when processing a group + //! of owned quadrants. + //! + //! \note it is not used when processing mirrors + //! quadrants, because mirror quadrants are process all at once. + const int32_t m_iOct_begin; + + //! cell-centered variables (no ghosts, sizes= block_x,block_y,block_z) + DataArrayBlock_t m_userdata_in; + + //! a ghosted data array (which block ghost cells need to be filled) - scalar field (only one + //! variable) + DataArrayGhostedBlock_t m_userdata_out; + + //! var id to be extracted from the input userdata + int32_t m_ivar; + + //! prolongation type + const CellCenteredProlongationType m_prolongation; + +public: + struct TagComputeMirrorQuad + {}; + struct TagComputeAllQuad + {}; + + /** + * Copy and fill ghost cells in a group of owned quadrants. + * + * \param[in] stencil helper + * \param[in] amr_mesh_info number of octants (owned, ghost, outside, ...) + * \param[in] iOct_begin is the first octant to process + * \param[in] userdata_in data array used to fill ghost of userdata_out + * \param[in,out] userdata_out data array which we want to fill the block ghosts cells + * + */ + ComputeGhostedScalarField(StencilHelper_t stencil_helper, + AMRMeshInfo amr_mesh_info, + int32_t iOct_begin, + DataArrayBlock_t userdata_in, + DataArrayGhostedBlock_t userdata_out, + int32_t ivar, + CellCenteredProlongationType prolongation); + + //! same as above, but specifying also the mirror keys array + ComputeGhostedScalarField(StencilHelper_t stencil_helper, + orchard_key_view_t mirror_orchard_keys, + AMRMeshInfo amr_mesh_info, + DataArrayBlock_t userdata_in, + DataArrayGhostedBlock_t userdata_out, + int32_t ivar, + CellCenteredProlongationType prolongation); + + // ============================================================== + // ============================================================== + //! static method which does it all: create and execute functor with range policy + //! + //! Use this member when processing in a group of octant + static void + apply_on_group(amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + AMRMeshInfo amr_mesh_info, + int32_t iOct_begin, + int32_t num_octants_in_group, + DataArrayBlock_t userdata_in, + DataArrayGhostedBlock_t userdata_out, + int32_t ivar, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic); + + // ============================================================== + // ============================================================== + //! static method which does it all: create and execute functor with range policy. + //! + //! Use this member when processing only in mirror quadrants. + static void + apply_in_mirrors(amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + orchard_key_view_t mirror_orchard_keys, + AMRMeshInfo amr_mesh_info, + DataArrayBlock_t userdata_in, + DataArrayGhostedBlock_t userdata_out, + int32_t ivar, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic); + + // ============================================================== + // ============================================================== + KOKKOS_INLINE_FUNCTION + real_t + get_var(CellLocation_t const & cell_loc) const; + + // ============================================================== + // ============================================================== + //! Perform restriction (average value over all sibling) + KOKKOS_INLINE_FUNCTION + real_t + get_var_restriction(CellLocation_t const & cell_loc) const; + + // ============================================================== + // ============================================================== + /** + * fill interior of ghosted block. + * + * \param[in] cellindex_out is the cell index of the ghost cell to fill + * \param[in] iOct_global is the octant id among all octant owned by current MPI process. + * \param[in] iOct_out index where to write data + * + */ + KOKKOS_INLINE_FUNCTION void + fill_inner(int32_t cellindex_in, + int32_t cellindex_out, + iOct_t iOct_global, + iOct_t iOct_out) const; + + // ============================================================== + // ============================================================== + /** + * Fill a ghost cell data by copying data from a neighbor either at same level, or finer level + * (doing actually a restriction) a neighbor octant in case neighbor is at the same AMR level. + * + * \param[in] cell_loc_out is the cell location where to write data + * \param[in] cell_loc_in is the cell location where to read data + * + */ + KOKKOS_INLINE_FUNCTION + void + fill_ghost_copy(CellLocation_t const & cell_loc_out, + CellLocation_t const & cell_loc_in, + index_t const & cellindex_out, + iOct_t const & iOct_out) const; + + // ============================================================== + // ============================================================== + /** + * Do linear extrapolation using limited slopes. + */ + template = true> + KOKKOS_INLINE_FUNCTION void + linear_extrapolate_using_limited_slopes(CellLocation<2> const & cell_loc_neigh, + coord_t<2> const & coord_in, + iOct_t const & iOct_global, + index_t const & cellindex_out, + iOct_t const & iOct_out) const; + + // ============================================================== + // ============================================================== + /** + * Do linear extrapolation using limited slopes. + */ + template = true> + KOKKOS_INLINE_FUNCTION void + linear_extrapolate_using_limited_slopes(CellLocation<3> const & cell_loc_neigh, + coord_t<3> const & coord_in, + iOct_t const & iOct_global, + index_t const & cellindex_out, + iOct_t const & iOct_out) const; + + // ============================================================== + // ============================================================== + /** + * Fill (copy) ghost cell data of current octant (iOct_global) from + * a neighbor octant in case neighbor is at coarser level. + * + * \param[in] key is current octant orchard key + * \param[in] key_neigh is neighbor octant orchard key + * \param[in] child_id is the child id of neighbor (at same level) wrt actual neighbor (coarser + * level) + * \param[in] iOct_global index to current octant + * \param[in] iOct_out index where to write data + * \param[in] iOct_neigh index to neighbor octant + * \param[in] cellindex integer used to map the ghost cell to fill + * \param[in] dir is direction to neighbor (in a 3x3 neighborhood) + * + * In 2d, for face neighbors, there are 2 distinct situations : + * ______ ______ __ + * | | | | X | + * | | __ or | | X__| + * | | X | | | + * |______| X__| |______| + * + * In this function, we want to fill the "X" ghost cells using data from + * the (larger) neighbor octant. + * + */ + KOKKOS_INLINE_FUNCTION + void + fill_ghost_coarser_level(key_t key, + key_t key_neigh, + uint8_t child_id, + int32_t iOct_global, + iOct_t iOct_out, + iOct_t iOct_neigh, + index_t cellindex, + Kokkos::Array const & dir) const; + + // ============================================================== + // ============================================================== + /** + * Fill (copy) ghost cell data of current octant (iOct_global) from + * a neighbor octant in case neighbor is at finer level. + * + * \param[in] key_neigh_same_level is orchard key of neighbor in direction dir at same AMR level + * \param[in] iOct_global is index to current octant + * \param[in] iOct_out index where to write data + * \param[in] cellindex integer used to map the ghost cell to fill + * \param[in] dir is direction to neighbor (in a 3x3 neighborhood) + * + * current octant (large one) is on the right + * neighbor octant (small one) is on the left + * + * In 2d, for face neighbor, there are 2 distinct situations : + * _______ __ _______ + * | | | | X | + * __ | | or |__| X | + * | | X | | | + * |__| x_______| |_______| + * + * In 3d, for face neighbor, there are 4 distinct situations : + */ + KOKKOS_INLINE_FUNCTION + bool + fill_ghost_finer_level(key_t key_neigh_same_level, + iOct_t iOct_global, + iOct_t iOct_out, + index_t cellindex, + Kokkos::Array const & dir) const; + + // ============================================================== + // ============================================================== + /** + * Fill (copy) ghost cell data all around current octant (iOct_global). + * + * This is (almost) the main entry point of the functor, i.e. directly called inside operator(). + * + * \param[in] cellindex integer used to map the ghost cell to fill + * \param[in] coord cartesian coordinates of current cell inside block + * \param[in] iOct_global is index to current octant + * \param[in] iOct_out is index to where to write data + * + */ + KOKKOS_INLINE_FUNCTION void + fill_ghosts(index_t const & cellindex, + coord_t const & coord, + iOct_t const & iOct_global, + iOct_t const & iOct_out) const; + + // ============================================================== + // ============================================================== + /** + * range policy functor when computing in all group quadrants. + */ + KOKKOS_INLINE_FUNCTION void + operator()(TagComputeAllQuad const &, const index_t & global_index) const; + + // ============================================================== + // ============================================================== + /** + * range policy functor when computing only mirror quadrant + */ + KOKKOS_INLINE_FUNCTION void + operator()(TagComputeMirrorQuad const &, const index_t & global_index) const; + +}; // class ComputeGhostedScalarField + +// explicit template instantiation +extern template class ComputeGhostedScalarField<2, kalypsso::DefaultDevice>; +extern template class ComputeGhostedScalarField<3, kalypsso::DefaultDevice>; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_COMPUTEGHOSTEDSCALARFIELD_H_ diff --git a/src/kalypsso/core/ComputeInterfaceNormalVector.cpp b/src/kalypsso/core/ComputeInterfaceNormalVector.cpp new file mode 100644 index 0000000..7f8c518 --- /dev/null +++ b/src/kalypsso/core/ComputeInterfaceNormalVector.cpp @@ -0,0 +1,132 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ComputeInterfaceNormalVector.cpp + */ +#include + +namespace kalypsso +{ + +/*************************************************/ +/*************************************************/ +/*************************************************/ +template +ComputeInterfaceNormalVector::ComputeInterfaceNormalVector( + DataArrayGhostedBlock_t smooth_interface_function, + DataArrayGhostedBlock_t normal_vector, + const int32_t iOct_first, + const int32_t num_quads) + : m_sif(smooth_interface_function) + , m_normal_vector(normal_vector) + , m_iOct_first(iOct_first) + , m_num_quads(num_quads) +{} // constructor + +// ============================================================== +// ============================================================== +template +void +ComputeInterfaceNormalVector::apply( + DataArrayGhostedBlock_t smooth_interface_function, + DataArrayGhostedBlock_t normal_vector, + const int32_t iOct_first, + const int32_t num_quads) +{ + + ComputeInterfaceNormalVector functor( + smooth_interface_function, normal_vector, iOct_first, num_quads); + + const auto nbIterations = num_quads * normal_vector.num_cells(); + + // launch computation + Kokkos::parallel_for("kalypsso::core::ComputeInterfaceNormalVector", + Kokkos::RangePolicy(0, nbIterations), + functor); + +} // ComputeInterfaceNormalVector::apply + +// ==================================================================== +// ==================================================================== +template +KOKKOS_INLINE_FUNCTION void +ComputeInterfaceNormalVector::operator()(const index_t & global_index) const +{ + + // retrieve local octant index in range [0, num_quads_to_process [ + auto const iOct_local = static_cast(global_index / m_normal_vector.num_cells()); + auto const cell_index = + static_cast(global_index - iOct_local * m_normal_vector.num_cells()); + + // compute cartesian coordinates inside ghosted block + const auto coord = cellindex_to_coord( + cell_index, m_normal_vector.ghosted_block_size(), m_normal_vector.shift()); + + if constexpr (dim == 2) + { + auto const & i = coord[IX]; + auto const & j = coord[IY]; + + // clang-format off + const auto dsif_dx = ( m_sif(i - 2, j, 0, iOct_local) + - 8 * m_sif(i - 1, j, 0, iOct_local) + + 8 * m_sif(i + 1, j, 0, iOct_local) + - m_sif(i + 2, j, 0, iOct_local)) / 12; + const auto dsif_dy = ( m_sif(i, j - 2, 0, iOct_local) + - 8 * m_sif(i, j - 1, 0, iOct_local) + + 8 * m_sif(i, j + 1, 0, iOct_local) + - m_sif(i, j + 2, 0, iOct_local)) / 12; + // clang-format on + + auto norm = sqrt(dsif_dx * dsif_dx + dsif_dy * dsif_dy); + + // if norm is very small, it means the sif is almost uniform + // avoid division by zero + if (norm < KALYPSSO_NUM(1e-13)) + norm = 1.0; + + m_normal_vector(i, j, IX, iOct_local) = dsif_dx / norm; + m_normal_vector(i, j, IY, iOct_local) = dsif_dy / norm; + } + else if constexpr (dim == 3) + { + auto const & i = coord[IX]; + auto const & j = coord[IY]; + auto const & k = coord[IZ]; + + // clang-format off + const auto dsif_dx = ( m_sif(i - 2, j , k , 0, iOct_local) + - 8 * m_sif(i - 1, j , k , 0, iOct_local) + + 8 * m_sif(i + 1, j , k , 0, iOct_local) + - m_sif(i + 2, j , k , 0, iOct_local)) / 12; + const auto dsif_dy = ( m_sif(i , j - 2, k , 0, iOct_local) + - 8 * m_sif(i , j - 1, k , 0, iOct_local) + + 8 * m_sif(i , j + 1, k , 0, iOct_local) + - m_sif(i , j + 2, k , 0, iOct_local)) / 12; + const auto dsif_dz = ( m_sif(i , j , k - 2, 0, iOct_local) + - 8 * m_sif(i , j , k - 1, 0, iOct_local) + + 8 * m_sif(i , j , k + 1, 0, iOct_local) + - m_sif(i , j , k + 2, 0, iOct_local)) / 12; + // clang-format on + + auto norm = sqrt(dsif_dx * dsif_dx + dsif_dy * dsif_dy + dsif_dz * dsif_dz); + + // if norm is very small, it means the sif is almost uniform + // avoid division by zero + if (norm < KALYPSSO_NUM(1e-13)) + norm = 1.0; + + m_normal_vector(i, j, k, IX, iOct_local) = dsif_dx / norm; + m_normal_vector(i, j, k, IY, iOct_local) = dsif_dy / norm; + m_normal_vector(i, j, k, IZ, iOct_local) = dsif_dz / norm; + } + +} // ComputeInterfaceNormalVector::operator () + +// explicit template instantiation +template class ComputeInterfaceNormalVector<2, kalypsso::DefaultDevice>; +template class ComputeInterfaceNormalVector<3, kalypsso::DefaultDevice>; + +} // namespace kalypsso diff --git a/src/kalypsso/core/ComputeInterfaceNormalVector.h b/src/kalypsso/core/ComputeInterfaceNormalVector.h new file mode 100644 index 0000000..7fda1e8 --- /dev/null +++ b/src/kalypsso/core/ComputeInterfaceNormalVector.h @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ComputeInterfaceNormalVector.h + */ +#ifndef KALYPSSO_CORE_COMPUTE_INTERFACE_NORMAL_VECTOR_H_ +#define KALYPSSO_CORE_COMPUTE_INTERFACE_NORMAL_VECTOR_H_ + +#include // for assertm +#include +#include // for DataArrayBlock +#include + +namespace kalypsso +{ + +/*************************************************/ +/*************************************************/ +/*************************************************/ +/** + * Compute interface normal vector as the gradient of psi, the smooth interface function using a + * fourth order central difference scheme. + * + * References: + * + * - A finite-volume HLLC-based scheme for compressible interfacial flows with surface tension, + * Garrick Owkes and Regele, Journal of Computational Physics Volume 339, 15 June 2017, Pages 46-67. + * https://doi.org/10.1016/j.jcp.2017.03.007 + * - An interface capturing scheme for modeling atomization in compressible flows, Garrick, Hagen + * and Regele, Journal of Computational Physics, Volume 344, 1 September 2017, Pages 260-280. + * https://doi.org/10.1016/j.jcp.2017.04.079 + */ +template +class ComputeInterfaceNormalVector +{ + +public: + using exec_space = typename device_t::execution_space; + using index_t = int64_t; + + // data array related type aliases + using DataArrayGhostedBlock_t = DataArrayGhostedBlock; + +private: + //! smooth interface function + DataArrayGhostedBlock_t m_sif; + + //! normal vector + DataArrayGhostedBlock_t m_normal_vector; + + //! offset to first octant + const int32_t m_iOct_first; + + //! number of quadrants to process + const int32_t m_num_quads; + +public: + // ============================================================== + // ============================================================== + ComputeInterfaceNormalVector(DataArrayGhostedBlock_t smooth_interface_function, + DataArrayGhostedBlock_t normal_vector, + const int32_t iOct_first, + const int32_t num_quads); + + // ============================================================== + // ============================================================== + //! static method which does it all: create and execute functor with range policy + //! + static void + apply(DataArrayGhostedBlock_t smooth_interface_function, + DataArrayGhostedBlock_t normal_vector, + const int32_t iOct_first, + const int32_t num_quads); + + // ==================================================================== + // ==================================================================== + KOKKOS_INLINE_FUNCTION + void + operator()(const index_t & global_index) const; + +}; // ComputeInterfaceNormalVector + +// explicit template instantiation +extern template class ComputeInterfaceNormalVector<2, kalypsso::DefaultDevice>; +extern template class ComputeInterfaceNormalVector<3, kalypsso::DefaultDevice>; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_COMPUTE_INTERFACE_NORMAL_VECTOR_H_ diff --git a/src/kalypsso/core/ComputeRefineFlags.cpp b/src/kalypsso/core/ComputeRefineFlags.cpp new file mode 100644 index 0000000..7f7d765 --- /dev/null +++ b/src/kalypsso/core/ComputeRefineFlags.cpp @@ -0,0 +1,404 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ComputeRefineFlags.cpp + */ +#include + +namespace kalypsso +{ + +// ==================================================================== +// ==================================================================== +template +void +ComputeRefineFlags::run(amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + int32_t local_num_octants, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + DataArrayBlock_t userdata, + amrflags_view_t flags, + RefineIndicatorData refineParams) +{ + + ComputeRefineFlags functor(amr_hashmap, + orchard_keys, + local_num_octants, + brick_sizes, + is_brick_periodic, + userdata, + flags, + refineParams); + + const auto nbCellsPerLeaf = userdata.num_cells(); + const auto nbCellsTotal = local_num_octants * nbCellsPerLeaf; + + if (refineParams.indicator == +Indicator::LOHNER_SPLIT) + { + Kokkos::parallel_for("ComputeRefineFlags", + Kokkos::RangePolicy(0, nbCellsTotal), + functor); + } + else if (refineParams.indicator == +Indicator::LOHNER_UNSPLIT) + { + Kokkos::parallel_for("ComputeRefineFlags", + Kokkos::RangePolicy(0, nbCellsTotal), + functor); + } + else if (refineParams.indicator == +Indicator::SIMPLE_GRADIENT) + { + Kokkos::parallel_for("ComputeRefineFlags", + Kokkos::RangePolicy(0, nbCellsTotal), + functor); + } + else if (refineParams.indicator == +Indicator::THRESHOLD_AFFINE) + { + Kokkos::parallel_for("ComputeRefineFlags", + Kokkos::RangePolicy(0, nbCellsTotal), + functor); + } + else + { + KALYPSSO_ERROR("Unknown value for refine indicator method."); + } + +}; // ComputeRefineFlags::run + +// ==================================================================== +// ==================================================================== +template +KOKKOS_FUNCTION real_t +ComputeRefineFlags::getNeighborDataSameLevel(CellLocation const & cell_loc, + int32_t cell_index, + shift_t shift, + int ivar) const +{ + + const auto cell_loc_neigh = m_helper.getNeighLoc(cell_loc, shift); + const auto cell_index_neigh = + coord_to_cellindex(cell_loc_neigh.ijk, m_userdata.block_size()); + + if (cell_loc_neigh.is_outside_domain) + { + // return value in current cell + return m_userdata(cell_index, ivar, cell_loc.iOct); + } + else + { + // if neighbor is at same level or is coarser, just use the value + if ((cell_loc_neigh.level() == cell_loc.level()) or + (cell_loc_neigh.level() == cell_loc.level() - 1)) + { + KOKKOS_ASSERT(static_cast(cell_loc_neigh.iOct) < m_userdata.num_quadrants() && + "userdata has wrong size. You probability forgot to update/resize it."); + return m_userdata(cell_index_neigh, ivar, cell_loc_neigh.iOct); + } + // if neighbor is finer, average small cell values + else if (cell_loc_neigh.level() == cell_loc.level() + 1) + { + return m_helper.compute_siblings_average( + cell_loc_neigh, m_userdata.block_size(), ivar, m_userdata); + } + else + { + // we shouldn't be here + return ZERO_F; + } + } + +} // ComputeRefineFlags::getNeighborDataSameLevel + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION real_t +ComputeRefineFlags::normalized_gradient(real_t const & v1, real_t const & v2) const +{ + + auto vmax = fmax(fabs(v1), fabs(v2)); + + if (vmax < KALYPSSO_NUM(0.001)) + { + return ZERO_F; + } + + vmax = fabs(v1 - v2) / vmax; + return fmax(fmin(vmax, ONE_F), ZERO_F); + +} // ComputeRefineFlags::normalized_gradient + +// ============================================================== +// ============================================================== +template +template +KOKKOS_FUNCTION real_t +ComputeRefineFlags::compute_lohner_split(CellLocation_t const & cell_loc, + int32_t const & cell_index, + int const & ivar) const +{ + + constexpr auto shift_L = get_shift_left(); + constexpr auto shift_R = get_shift_right(); + + const auto data_L = getNeighborDataSameLevel(cell_loc, cell_index, shift_L, ivar); + const auto data_R = getNeighborDataSameLevel(cell_loc, cell_index, shift_R, ivar); + const auto data_C = m_userdata(cell_index, ivar, cell_loc.iOct); + + return abs(data_L - 2 * data_C + data_R) / + (abs(data_R - data_C) + abs(data_C - data_L) + + m_refineParams.epsilon * (abs(data_L) + 2 * abs(data_C) + abs(data_R))); + +} // ComputeRefineFlags::compute_lohner_split + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION real_t +ComputeRefineFlags::compute_lohner_unsplit(CellLocation_t const & cell_loc, + int32_t const & cell_index, + int const & ivar) const +{ + + const auto data_C = m_userdata(cell_index, ivar, cell_loc.iOct); + + const auto data_Lx = + getNeighborDataSameLevel(cell_loc, cell_index, get_shift_left(), ivar); + const auto data_Rx = + getNeighborDataSameLevel(cell_loc, cell_index, get_shift_right(), ivar); + + const auto data_Ly = + getNeighborDataSameLevel(cell_loc, cell_index, get_shift_left(), ivar); + const auto data_Ry = + getNeighborDataSameLevel(cell_loc, cell_index, get_shift_right(), ivar); + + auto num = (data_Lx - 2 * data_C + data_Rx) * (data_Lx - 2 * data_C + data_Rx) + + (data_Ly - 2 * data_C + data_Ry) * (data_Ly - 2 * data_C + data_Ry); + + auto tmp = abs(data_Rx - data_C) + abs(data_C - data_Lx) + + m_refineParams.epsilon * (abs(data_Lx) + 2 * abs(data_C) + abs(data_Rx)); + auto denom = tmp * tmp; + + tmp = abs(data_Ry - data_C) + abs(data_C - data_Ly) + + m_refineParams.epsilon * (abs(data_Ly) + 2 * abs(data_C) + abs(data_Ry)); + denom += tmp * tmp; + + + if constexpr (dim == 3) + { + const auto data_Lz = + getNeighborDataSameLevel(cell_loc, cell_index, get_shift_left(), ivar); + const auto data_Rz = + getNeighborDataSameLevel(cell_loc, cell_index, get_shift_right(), ivar); + + num += (data_Lz - 2 * data_C + data_Rz) * (data_Lz - 2 * data_C + data_Rz); + + tmp = abs(data_Rz - data_C) + abs(data_C - data_Lz) + + m_refineParams.epsilon * (abs(data_Lz) + 2 * abs(data_C) + abs(data_Rz)); + denom += tmp * tmp; + } + + return sqrt(num / denom); + +} // ComputeRefineFlags::compute_lohner_unsplit + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION real_t +ComputeRefineFlags::compute_simple_gradient(CellLocation_t const & cell_loc, + int32_t const & cell_index, + int const & ivar) const +{ + + auto res = Kokkos::reduction_identity::max(); + + const auto data_C = m_userdata(cell_index, ivar, cell_loc.iOct); + + const auto data_Lx = + getNeighborDataSameLevel(cell_loc, cell_index, get_shift_left(), ivar); + const auto data_Rx = + getNeighborDataSameLevel(cell_loc, cell_index, get_shift_right(), ivar); + + res = fmax(res, normalized_gradient(data_C, data_Lx)); + res = fmax(res, normalized_gradient(data_C, data_Rx)); + + const auto data_Ly = + getNeighborDataSameLevel(cell_loc, cell_index, get_shift_left(), ivar); + const auto data_Ry = + getNeighborDataSameLevel(cell_loc, cell_index, get_shift_right(), ivar); + + res = fmax(res, normalized_gradient(data_C, data_Ly)); + res = fmax(res, normalized_gradient(data_C, data_Ry)); + + if constexpr (dim == 3) + { + const auto data_Lz = + getNeighborDataSameLevel(cell_loc, cell_index, get_shift_left(), ivar); + const auto data_Rz = + getNeighborDataSameLevel(cell_loc, cell_index, get_shift_right(), ivar); + + res = fmax(res, normalized_gradient(data_C, data_Lz)); + res = fmax(res, normalized_gradient(data_C, data_Rz)); + } + + return res; + +} // ComputeRefineFlags::compute_simple_gradient + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION real_t +ComputeRefineFlags::compute_threshold_affine( + CellLocation_t const & cell_loc, + int32_t const & cell_index, + int const & ivar, + ThresholdAffineParams const & th_aff_params) const +{ + + const auto data_C = m_userdata(cell_index, ivar, cell_loc.iOct); + + return th_aff_params.a * data_C + th_aff_params.b; + +} // ComputeRefineFlags::compute_threshold_affine + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION void +ComputeRefineFlags::update_flag(amrflag_t & flag, + real_t const & indicator, + uint8_t const & level) const +{ + if (level < m_refineParams.level_max and indicator > m_refineParams.refine_th) + flag = KokkosExt::max_val(flag, AMRContextBase::KALYPSSO_DO_REFINE); + else if (level > m_refineParams.level_min and indicator < m_refineParams.coarsen_th) + flag = KokkosExt::max_val(flag, AMRContextBase::KALYPSSO_DO_COARSEN); + else + flag = KokkosExt::max_val(flag, AMRContextBase::KALYPSSO_DO_NOTHING); + +} // ComputeRefineFlags::update_flag + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION void +ComputeRefineFlags::operator()(TagLohnerSplit const &, + const index_t & global_index) const +{ + const auto iOct_global = global_index / m_userdata.num_cells(); + const auto cell_index = global_index - iOct_global * m_userdata.num_cells(); + + const auto coord = cellindex_to_coord(cell_index, m_userdata.block_size()); + const auto key_cur = m_orchard_keys_device(iOct_global); + CellLocation_t cell_loc{ coord, key_cur, iOct_global, false }; + + const auto level = orchard_key_t::level(key_cur); + auto flag = AMRContextBase::KALYPSSO_FLAG_INIT; + + { + auto indicator = compute_lohner_split(cell_loc, cell_index, m_refineParams.ivar); + update_flag(flag, indicator, level); + } + + { + auto indicator = compute_lohner_split(cell_loc, cell_index, m_refineParams.ivar); + update_flag(flag, indicator, level); + } + + if constexpr (dim == 3) + { + auto indicator = compute_lohner_split(cell_loc, cell_index, m_refineParams.ivar); + update_flag(flag, indicator, level); + } + + Kokkos::atomic_max(&m_flags(iOct_global), flag); + +} // ComputeRefineFlags::operator() + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION void +ComputeRefineFlags::operator()(TagLohnerUnsplit const &, + const index_t & global_index) const +{ + const auto iOct_global = global_index / m_userdata.num_cells(); + const auto cell_index = global_index - iOct_global * m_userdata.num_cells(); + + const auto coord = cellindex_to_coord(cell_index, m_userdata.block_size()); + const auto key_cur = m_orchard_keys_device(iOct_global); + CellLocation_t cell_loc{ coord, key_cur, iOct_global, false }; + + const auto level = orchard_key_t::level(key_cur); + auto flag = AMRContextBase::KALYPSSO_FLAG_INIT; + const auto indicator = compute_lohner_unsplit(cell_loc, cell_index, m_refineParams.ivar); + + update_flag(flag, indicator, level); + + Kokkos::atomic_max(&m_flags(iOct_global), flag); + +} // ComputeRefineFlags::operator() + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION void +ComputeRefineFlags::operator()(TagSimpleGradient const &, + const index_t & global_index) const +{ + const auto iOct_global = global_index / m_userdata.num_cells(); + const auto cell_index = global_index - iOct_global * m_userdata.num_cells(); + + const auto coord = cellindex_to_coord(cell_index, m_userdata.block_size()); + const auto key_cur = m_orchard_keys_device(iOct_global); + CellLocation_t cell_loc{ coord, key_cur, iOct_global, false }; + + const auto level = orchard_key_t::level(key_cur); + auto flag = AMRContextBase::KALYPSSO_FLAG_INIT; + const auto indicator = compute_simple_gradient(cell_loc, cell_index, m_refineParams.ivar); + + update_flag(flag, indicator, level); + + Kokkos::atomic_max(&m_flags(iOct_global), flag); + +} // ComputeRefineFlags::operator() + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION void +ComputeRefineFlags::operator()(TagThresholdAffine const &, + const index_t & global_index) const +{ + const auto iOct_global = global_index / m_userdata.num_cells(); + const auto cell_index = global_index - iOct_global * m_userdata.num_cells(); + + const auto coord = cellindex_to_coord(cell_index, m_userdata.block_size()); + const auto key_cur = m_orchard_keys_device(iOct_global); + CellLocation_t cell_loc{ coord, key_cur, iOct_global, false }; + + const auto level = orchard_key_t::level(key_cur); + auto flag = AMRContextBase::KALYPSSO_FLAG_INIT; + const auto indicator = + compute_threshold_affine(cell_loc, cell_index, m_refineParams.ivar, m_refineParams.th_aff_par); + + if (level < m_refineParams.level_max and indicator > m_refineParams.th_aff_par.threshold) + flag = KokkosExt::max_val(flag, AMRContextBase::KALYPSSO_DO_REFINE); + else if (level > m_refineParams.level_min and indicator < m_refineParams.th_aff_par.threshold) + flag = KokkosExt::max_val(flag, AMRContextBase::KALYPSSO_DO_COARSEN); + else + flag = KokkosExt::max_val(flag, AMRContextBase::KALYPSSO_DO_NOTHING); + + Kokkos::atomic_max(&m_flags(iOct_global), flag); + +} // ComputeRefineFlags::operator() + +// explicit template instantiation +template class ComputeRefineFlags<2, kalypsso::DefaultDevice>; +template class ComputeRefineFlags<3, kalypsso::DefaultDevice>; + +} // namespace kalypsso diff --git a/src/kalypsso/core/ComputeRefineFlags.h b/src/kalypsso/core/ComputeRefineFlags.h new file mode 100644 index 0000000..8a5235e --- /dev/null +++ b/src/kalypsso/core/ComputeRefineFlags.h @@ -0,0 +1,290 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ComputeRefineFlags.h + * + * Implement classic refine indicator compute algorithm, e.g. : + * + * - Lohner: [R. Lohner, An adaptive finite element scheme for transient problems in CFD, Comp. + * Meth. App. Mech. Eng. 61, 323 (1987)] + */ +#ifndef KALYPSSO_CORE_COMPUTEREFINEFLAGS_H_ +#define KALYPSSO_CORE_COMPUTEREFINEFLAGS_H_ + +#include +#include +#include // for math functions (max, min, ...) + +#include + +#include // for DataArray, DataArrayHost, DataArrayGhostedBlock +#include +#include + +#include +#include +#include +#include + +#include + +#include // for definition of function cellindex_to_coord and coord_to_cellindex, and shift_left/shift_right + +#include + +namespace kalypsso +{ + +/** + * \class ComputeRefineFlags + */ +template +class ComputeRefineFlags +{ +public: + using exec_space = typename device_t::execution_space; + using index_t = int32_t; + using amr_hashmap_t = typename hashmap_base_t::map_t; + using orchard_key_view_t = typename orchard_key_base_t::view_t; + using CellLocation_t = CellLocation; + + using amrflag_t = AMRContextBase::amrflag_t; + using amrflags_view_t = typename AMRContext::amrflags_view_t; + + using DataArrayBlock_t = DataArrayBlock; + using DataArrayLeaf_t = DataArrayLeaf; + + struct TagLohnerSplit + {}; + struct TagLohnerUnsplit + {}; + struct TagSimpleGradient + {}; + struct TagThresholdAffine + {}; + + // ==================================================================== + // ==================================================================== + //! constructor. + //! + //! All variables are inputs, except flags which will be computed + //! + //! \param[in] amr_hashmap unordered map from orchard key to memory index for owned and ghost + //! quadrants + //! \param[in] orchard_keys array of orchard key ordered by Morton order + //! \param[in] local_num_quadrants number of octants (p4est ghosts excluded) owned by current + //! MPI process + //! \param[in] brick_sizes is an array of p4est brick connectivity sizes (number of trees in + //! each dimension) + //! \param[in] is_brick_periodic array of boolean value indicating if the p4est brick + //! connectivity is periodic in the given dimension + //! \param[in] userdata if a block array (owned + MPI ghost blocks) used to compute refinement + //! flags + //! \param[out] flags result of the refinement flags computation + //! \param[in] refineParams Refinement parameters + ComputeRefineFlags(amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + int32_t local_num_octants, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + DataArrayBlock_t userdata, + amrflags_view_t flags, + RefineIndicatorData refineParams) + : m_helper(amr_hashmap, orchard_keys, userdata.block_size(), brick_sizes, is_brick_periodic) + , m_amr_hashmap_device(amr_hashmap) + , m_orchard_keys_device(orchard_keys) + , m_local_num_octants(local_num_octants) + , m_brick_sizes(brick_sizes) + , m_is_brick_periodic(is_brick_periodic) + , m_userdata(userdata) + , m_flags(flags) + , m_refineParams(refineParams) + {} + + // ==================================================================== + // ==================================================================== + //! destructor. + ~ComputeRefineFlags() = default; + + // ==================================================================== + // ==================================================================== + //! run the functor. + //! + //! Important note: the caller is responsible for resetting the flags + //! + //! \sa ComputeRefineFlags + static void + run(amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + int32_t local_num_octants, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + DataArrayBlock_t userdata, + amrflags_view_t flags, + RefineIndicatorData refineParams); + + // ============================================================== + // ============================================================== + /** + * Get, compute or average data in neighbor cell defined as a shift from current cell. + * + * \param[in] cell_loc is cell location of current cell + * \param[in] cell_index is cell index of current cell inside current block grid + * \param[in] shift defines a translation (integer number of cell away from current cell) + * \param[in] which variable to get + */ + KOKKOS_FUNCTION real_t + getNeighborDataSameLevel(CellLocation const & cell_loc, + int32_t cell_index, + shift_t shift, + int ivar) const; + + // ============================================================== + // ============================================================== + KOKKOS_FUNCTION real_t + normalized_gradient(real_t const & v1, real_t const & v2) const; + + // ============================================================== + // ============================================================== + /** + * Compute Lohner indicator (split version) + * + * \param[in] cell_loc is cell location of current cell + * \param[in] cell_index is cell index of current cell inside current block grid + * \param[in] which variable to get + * + * \tparam dir direction along which second partial derivative is computed + */ + template + KOKKOS_FUNCTION real_t + compute_lohner_split(CellLocation_t const & cell_loc, + int32_t const & cell_index, + int const & ivar) const; + + // ============================================================== + // ============================================================== + /** + * Compute Lohner indicator (unsplit version) + * + * \param[in] cell_loc is cell location of current cell + * \param[in] cell_index is cell index of current cell inside current block grid + * \param[in] which variable to get + * + * \tparam dir direction along which second partial derivative is computed + */ + KOKKOS_FUNCTION real_t + compute_lohner_unsplit(CellLocation_t const & cell_loc, + int32_t const & cell_index, + int const & ivar) const; + + // ============================================================== + // ============================================================== + /** + * Compute simple gradient refine criterion + * + * \param[in] cell_loc is cell location of current cell + * \param[in] cell_index is cell index of current cell inside current block grid + * \param[in] which variable to get + * + * \tparam dir direction along which second partial derivative is computed + */ + KOKKOS_FUNCTION real_t + compute_simple_gradient(CellLocation_t const & cell_loc, + int32_t const & cell_index, + int const & ivar) const; + + // ============================================================== + // ============================================================== + /** + * Compute "threshold affine" refine criterion + * + * \param[in] cell_loc is cell location of current cell + * \param[in] cell_index is cell index of current cell inside current block grid + * \param[in] which variable to get + * \param[in] threshold affine parameters + * + * \tparam dir direction along which second partial derivative is computed + */ + KOKKOS_FUNCTION real_t + compute_threshold_affine(CellLocation_t const & cell_loc, + int32_t const & cell_index, + int const & ivar, + ThresholdAffineParams const & th_aff_params) const; + + // ============================================================== + // ============================================================== + KOKKOS_FUNCTION void + update_flag(amrflag_t & flag, real_t const & indicator, uint8_t const & level) const; + + // ============================================================== + // ============================================================== + /** + * range policy functor body - Lohner split version + */ + KOKKOS_FUNCTION void + operator()(TagLohnerSplit const &, const index_t & global_index) const; + + // ============================================================== + // ============================================================== + /** + * range policy functor body - Lohner unsplit version + */ + KOKKOS_FUNCTION void + operator()(TagLohnerUnsplit const &, const index_t & global_index) const; + + // ============================================================== + // ============================================================== + /** + * range policy functor body - simple gradient version + */ + KOKKOS_FUNCTION void + operator()(TagSimpleGradient const &, const index_t & global_index) const; + + // ============================================================== + // ============================================================== + /** + * range policy functor body - threshold affine version + */ + KOKKOS_FUNCTION void + operator()(TagThresholdAffine const &, const index_t & global_index) const; + +private: + //! help to compute cell location + StencilHelper m_helper; + + //! AMR unordered map which maps orchard keys to quadrant number for all key in the mesh + //! (owned quadrants and ghost quadrants) + amr_hashmap_t m_amr_hashmap_device; + + //! list of orchard key of the mesh + orchard_key_view_t m_orchard_keys_device; + + //! total number of octants in the current MPI process (ghost block excluded) + const int32_t m_local_num_octants; + + //! p4est brick connectivity sizes + const brick_size_t m_brick_sizes; + + //! is p4est connectivity periodic ? + const Kokkos::Array m_is_brick_periodic; + + //! a block data array (no ghosts, sizes= bx,by,bz) + DataArrayBlock_t m_userdata; + + //! a leaf data array (one "value" per block) + amrflags_view_t m_flags; + + //! refine flags algorithm parameters + RefineIndicatorData m_refineParams; + +}; // class ComputeRefineFlags + +// explicit template instantiation +extern template class ComputeRefineFlags<2, kalypsso::DefaultDevice>; +extern template class ComputeRefineFlags<3, kalypsso::DefaultDevice>; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_COMPUTEREFINEFLAGS_H_ diff --git a/src/kalypsso/core/ComputeRefineFlags_utils.h b/src/kalypsso/core/ComputeRefineFlags_utils.h new file mode 100644 index 0000000..ebe21d6 --- /dev/null +++ b/src/kalypsso/core/ComputeRefineFlags_utils.h @@ -0,0 +1,104 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ComputeRefineFlags_utils.h + */ +#ifndef KALYPSSO_CORE_COMPUTEREFINEFLAGS_UTILS_H_ +#define KALYPSSO_CORE_COMPUTEREFINEFLAGS_UTILS_H_ + +#include +#include +#include // for math functions (max, min, ...) + +#include +#include <../better-enums/enum.h> + +namespace kalypsso +{ + +// clang-format off +/** + * An enum type to represent all possible refine indicator types. + */ +BETTER_ENUM(Indicator, uint8_t, + LOHNER_SPLIT = 0, + LOHNER_UNSPLIT = 1, + SIMPLE_GRADIENT = 2, + THRESHOLD_AFFINE = 3) +// clang-format on + +/** + * A parameter to use when THRESHOLD_AFFINE indicator is activated. + */ +struct ThresholdAffineParams +{ + //! multiplicative factor + real_t a; + + //! offset + real_t b; + + // threshold + real_t threshold; + + ThresholdAffineParams() + : a(KALYPSSO_NUM(1.0)) + , b(KALYPSSO_NUM(0.0)) + , threshold(KALYPSSO_NUM(0.0)) + {} + + ThresholdAffineParams(real_t _a, real_t _b, real_t _threshold) + : a(_a) + , b(_b) + , threshold(_threshold) + {} + +}; // struct ThresholdAffineParams + +/** + * A companion data structure for class ComputeRefineFlags holding parameters used to + * compute refine flags. + */ +struct RefineIndicatorData +{ + static Indicator + get_indicator(ConfigMap const & config_map) + { + auto indicator_name = config_map.getString("amr", "refine_criterion", "LOHNER_UNSPLIT"); + auto maybe_value = Indicator::_from_string_nothrow(indicator_name.c_str()); + if (maybe_value) + return *maybe_value; + return Indicator::LOHNER_UNSPLIT; + } + + //! minimal AMR level allowed + int level_min; + + //! maximum AMR level allowed + int level_max; + + //! type of indicator + Indicator indicator; + + //! threshold to decide refinement + real_t refine_th; + + //! threshold to decide coarsening + real_t coarsen_th; + + //! variable id used to compute refine flags + int ivar; + + //! parameter used in Lohner criterion + real_t epsilon; + + //! parameters only use when refine_criterion is THRESHOLD_AFFINE + ThresholdAffineParams th_aff_par{}; + +}; // struct RefineIndicatorData + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_COMPUTEREFINEFLAGS_UTILS_H_ diff --git a/src/kalypsso/core/ComputeSchlieren.cpp b/src/kalypsso/core/ComputeSchlieren.cpp new file mode 100644 index 0000000..46b7b5e --- /dev/null +++ b/src/kalypsso/core/ComputeSchlieren.cpp @@ -0,0 +1,235 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ComputeSchlieren.cpp + */ +#include + +namespace kalypsso +{ + +namespace core +{ + +// ==================================================================== +// ==================================================================== +template +auto +ComputeSchlieren::run(ConfigMap const & config_map, + [[maybe_unused]] const ParallelEnv & par_env, + amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + int32_t local_num_octants, + block_size_t block_sizes, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + DataArrayBlock_t userdata) -> DataArrayBlock_t +{ + KOKKOS_ASSERT(userdata.shape()[IX] > 2 && "userdata has a wrong shape, block size too small"); + KOKKOS_ASSERT(userdata.shape()[IY] > 2 && "userdata has a wrong shape, block size too small"); + if constexpr (dim == 3) + { + KOKKOS_ASSERT(userdata.shape()[IZ] > 2 && "userdata has a wrong shape, block size too small"); + } + + auto schlieren = DataArrayBlock_t("Schlieren", userdata.block_size(), 1, local_num_octants); + + ComputeSchlieren functor(amr_hashmap, + orchard_keys, + local_num_octants, + block_sizes, + brick_sizes, + is_brick_periodic, + get_scaling_factor(config_map), + userdata, + schlieren); + + const auto nbCellsPerLeaf = Kokkos::dim_prod(block_sizes); + const auto nbCellsTotal = local_num_octants * nbCellsPerLeaf; + + real_t local_max_norm_grad = 0; + Kokkos::Max reducer(local_max_norm_grad); + + // compute max of norm of gradient + Kokkos::parallel_reduce("compute_max_norm_of_gradient", + Kokkos::RangePolicy(0, nbCellsTotal), + functor, + reducer); + + real_t max_norm_grad = 0; +#ifdef KALYPSSO_CORE_USE_MPI + par_env.comm().MPI_Allreduce(&local_max_norm_grad, &max_norm_grad, 1); +#else + max_norm_grad = local_max_norm_grad; +#endif + + // divide by max norm of gradient, and apply scaling if required + const auto schlieren_scaling_type = read_schlieren_scaling(config_map); + + if (schlieren_scaling_type == +SCHLIEREN_SCALING::SQRT) + { + Kokkos::parallel_for( + "compute_schlieren", + Kokkos::RangePolicy(0, nbCellsTotal), + KOKKOS_LAMBDA(int32_t global_index) { + const auto iOct = global_index / nbCellsPerLeaf; + const auto cell_index = global_index - iOct * nbCellsPerLeaf; + + schlieren(cell_index, 0, iOct) = sqrt(schlieren(cell_index, 0, iOct) / max_norm_grad); + }); + } + else if (schlieren_scaling_type == +SCHLIEREN_SCALING::LOG) + { + Kokkos::parallel_for( + "compute_schlieren", + Kokkos::RangePolicy(0, nbCellsTotal), + KOKKOS_LAMBDA(int32_t global_index) { + const auto iOct = global_index / nbCellsPerLeaf; + const auto cell_index = global_index - iOct * nbCellsPerLeaf; + + schlieren(cell_index, 0, iOct) = schlieren(cell_index, 0, iOct) > 0 + ? log(schlieren(cell_index, 0, iOct) / max_norm_grad) + : ZERO_F; + }); + } + else + { + Kokkos::parallel_for( + "compute_schlieren", + Kokkos::RangePolicy(0, nbCellsTotal), + KOKKOS_LAMBDA(int32_t global_index) { + const auto iOct = global_index / nbCellsPerLeaf; + const auto cell_index = global_index - iOct * nbCellsPerLeaf; + + schlieren(cell_index, 0, iOct) /= max_norm_grad; + }); + } + + return schlieren; + +}; // ComputeSchlieren::run + +// ==================================================================== +// ==================================================================== +template +KOKKOS_FUNCTION real_t +ComputeSchlieren::getNeighborDataSameLevel(CellLocation const & cell_loc, + int32_t cell_index, + shift_t shift, + int ivar, + bool & is_neighbor_not_owned) const +{ + + const auto cell_loc_neigh = m_helper.getNeighLoc(cell_loc, shift); + const auto cell_index_neigh = coord_to_cellindex(cell_loc_neigh.ijk, m_block_sizes); + + if (cell_loc_neigh.is_outside_domain or cell_loc_neigh.iOct >= m_local_num_octants) + { + // neighbor is not a locally owned quadrant + is_neighbor_not_owned = true; + + // return value in current cell + return m_userdata_in(cell_index, ivar, cell_loc.iOct); + } + else + { + // if neighbor is at same level or is coarser, just use the value + if ((cell_loc_neigh.level() == cell_loc.level()) or + (cell_loc_neigh.level() == cell_loc.level() - 1)) + { + KOKKOS_ASSERT(static_cast(cell_loc_neigh.iOct) < m_userdata_in.num_quadrants() && + "userdata has wrong size. You probability forgot to update/resize it."); + return m_userdata_in(cell_index_neigh, ivar, cell_loc_neigh.iOct); + } + // if neighbor is finer, average small cell values + else if (cell_loc_neigh.level() == cell_loc.level() + 1) + { + return m_helper.compute_siblings_average(cell_loc_neigh, m_block_sizes, ivar, m_userdata_in); + } + else + { + // we shouldn't be here + return ZERO_F; + } + } + +} // ComputeSchlieren::getNeighborDataSameLevel + +// ============================================================== +// ============================================================== +template +template +KOKKOS_FUNCTION real_t +ComputeSchlieren::compute_gradient_along_dir(CellLocation_t const & cell_loc, + int32_t const & cell_index, + int const & ivar) const +{ + const auto & b = m_userdata_in.block_size(); + + bool is_neighbor_not_owned = false; + + const auto data_L = getNeighborDataSameLevel( + cell_loc, cell_index, get_shift_left(), ivar, is_neighbor_not_owned); + + const auto data_R = getNeighborDataSameLevel( + cell_loc, cell_index, get_shift_right(), ivar, is_neighbor_not_owned); + + const auto dx = compute_cell_length(cell_loc.key, b[dir]) * m_scaling_factor; + + return is_neighbor_not_owned ? (data_R - data_L) / dx : (data_R - data_L) / (2 * dx); +} + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION real_t +ComputeSchlieren::compute_norm_of_gradient(CellLocation_t const & cell_loc, + int32_t const & cell_index, + int const & ivar) const +{ + const auto gradx = compute_gradient_along_dir(cell_loc, cell_index, ivar); + const auto grady = compute_gradient_along_dir(cell_loc, cell_index, ivar); + + auto grad_norm = gradx * gradx + grady * grady; + + if constexpr (dim == 3) + { + const auto gradz = compute_gradient_along_dir(cell_loc, cell_index, ivar); + grad_norm += gradz * gradz; + } + + return sqrt(grad_norm); + +} // ComputeSchlieren::compute_norm_of_gradient + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION void +ComputeSchlieren::operator()(const index_t & global_index, + real_t & max_norm_grad) const +{ + const auto iOct_global = global_index / m_nbCellsPerLeaf; + const auto cell_index = global_index - iOct_global * m_nbCellsPerLeaf; + + const auto coord = cellindex_to_coord(cell_index, m_block_sizes); + const auto key_cur = m_orchard_keys_device(iOct_global); + CellLocation_t cell_loc{ coord, key_cur, iOct_global, false }; + + auto norm_grad = compute_norm_of_gradient(cell_loc, cell_index, 0); + + max_norm_grad = fmax(max_norm_grad, norm_grad); + + m_userdata_out(cell_index, 0, iOct_global) = norm_grad; + +} // ComputeSchlieren::operator() + +// explicit template instantiation +template class ComputeSchlieren<2, kalypsso::DefaultDevice>; +template class ComputeSchlieren<3, kalypsso::DefaultDevice>; + +} // namespace core + +} // namespace kalypsso diff --git a/src/kalypsso/core/ComputeSchlieren.h b/src/kalypsso/core/ComputeSchlieren.h new file mode 100644 index 0000000..dde1dae --- /dev/null +++ b/src/kalypsso/core/ComputeSchlieren.h @@ -0,0 +1,261 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ComputeSchlieren.h + * + * Compute schlieren of a scalar field. + * + */ +#ifndef KALYPSSO_CORE_COMPUTESCHLIEREN_H_ +#define KALYPSSO_CORE_COMPUTESCHLIEREN_H_ + +#include +#include +#include // for math functions (max, min, ...) + +#include // for DataArray, DataArrayHost, DataArrayGhostedBlock +#include + +#include +#include +#include +#include + +#include + +#include // for definition of function cellindex_to_coord and coord_to_cellindex + +#include + +namespace kalypsso +{ + +namespace core +{ + +// clang-format off +BETTER_ENUM(SCHLIEREN_SCALING, uint32_t, + NONE, + SQRT, + LOG +) +// clang-format on + + +/** + * \class ComputeSchlieren + */ +template +class ComputeSchlieren +{ +public: + using exec_space = typename device_t::execution_space; + using index_t = int32_t; + using amr_hashmap_t = typename hashmap_base_t::map_t; + using orchard_key_view_t = typename orchard_key_base_t::view_t; + using CellLocation_t = CellLocation; + + using DataArrayBlock_t = DataArrayBlock; + using DataArrayLeaf_t = DataArrayLeaf; + + // ==================================================================== + // ==================================================================== + //! constructor. + //! + //! All variables are inputs, except flags which will be computed + //! + //! \param[in] amr_hashmap unordered map from orchard key to memory index for owned and ghost + //! quadrants + //! + //! \param[in] orchard_keys array of orchard key ordered by Morton order + //! + //! \param[in] local_num_quadrants number of octants (p4est ghosts excluded) owned by current + //! MPI process + //! + //! \param[in] block_sizes sizes of the local cartesian grid at leaf level + //! + //! \param[in] brick_sizes is an array of p4est brick connectivity sizes (number of trees in + //! each dimension) + //! + //! \param[in] is_brick_periodic array of boolean value indicating if the p4est brick + //! connectivity is periodic in the given dimension + //! + //! \param[in] userdata input block array (owned + MPI ghost blocks) used to compute Schlieren + //! on the first scalar field. + //! + //! \param[out] userdata output block array (owned) containing Schlieren of input array + //! + ComputeSchlieren(amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + int32_t local_num_octants, + block_size_t block_sizes, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + real_t scaling_factor, + DataArrayBlock_t userdata_in, + DataArrayBlock_t userdata_out) + : m_helper(amr_hashmap, orchard_keys, block_sizes, brick_sizes, is_brick_periodic) + , m_amr_hashmap_device(amr_hashmap) + , m_orchard_keys_device(orchard_keys) + , m_local_num_octants(local_num_octants) + , m_nbCellsPerLeaf(Kokkos::dim_prod(block_sizes)) + , m_block_sizes(block_sizes) + , m_brick_sizes(brick_sizes) + , m_is_brick_periodic(is_brick_periodic) + , m_scaling_factor(scaling_factor) + , m_userdata_in(userdata_in) + , m_userdata_out(userdata_out) + {} + + // ==================================================================== + // ==================================================================== + //! destructor. + ~ComputeSchlieren() = default; + + // ==================================================================== + // ==================================================================== + //! run the functor. + //! + static auto + run(ConfigMap const & config_map, + const ParallelEnv & par_env, + amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + int32_t local_num_octants, + block_size_t block_sizes, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + DataArrayBlock_t userdata) -> DataArrayBlock_t; + + // ==================================================================== + // ==================================================================== + static SCHLIEREN_SCALING + read_schlieren_scaling(ConfigMap const & config_map) + { + + const auto schlieren_type_str = config_map.getString("output", "schlieren_scaling", "NONE"); + + // check if schlieren_type_str is a valid value + auto maybe_value = SCHLIEREN_SCALING::_from_string_nothrow(schlieren_type_str.c_str()); + if (!maybe_value) + { + return SCHLIEREN_SCALING::NONE; + } + else + { + return *maybe_value; + } + } // read_schlieren_scaling + + // ==================================================================== + // ==================================================================== + /** + * Get, compute or average data in neighbor cell defined as a shift from current cell. + * + * \param[in] cell_loc is cell location of current cell + * \param[in] cell_index is cell index of current cell inside current block grid + * \param[in] shift defines a translation (integer number of cell away from current cell) + * \param[in] which variable to get + * \param[out] is_neighbor_owned is status to inform caller + */ + KOKKOS_FUNCTION real_t + getNeighborDataSameLevel(CellLocation const & cell_loc, + int32_t cell_index, + shift_t shift, + int ivar, + bool & is_neighbor_not_owned) const; + + // ============================================================== + // ============================================================== + /** + * Compute first derivative along direction dir using a 3 point stencil. + * + * Derivative is estimated with big O(h^2) approximation. + */ + template + KOKKOS_FUNCTION real_t + compute_first_derivative_3_points(CellLocation const & cell_loc, int ivar) const; + + // ============================================================== + // ============================================================== + /** + * Compute gradient along dir + */ + template + KOKKOS_FUNCTION real_t + compute_gradient_along_dir(CellLocation_t const & cell_loc, + int32_t const & cell_index, + int const & ivar) const; + + // ============================================================== + // ============================================================== + /** + * Compute simple gradient norm + * + * \param[in] cell_loc is cell location of current cell + * \param[in] cell_index is cell index of current cell inside current block grid + * \param[in] ivar which variable to get + * + * \tparam dir direction along which second partial derivative is computed + */ + KOKKOS_FUNCTION real_t + compute_norm_of_gradient(CellLocation_t const & cell_loc, + int32_t const & cell_index, + int const & ivar) const; + + // ============================================================== + // ============================================================== + /** + * range policy functor body + */ + KOKKOS_FUNCTION void + operator()(const index_t & global_index, real_t & max_norm_grad) const; + +private: + //! help to compute cell location + StencilHelper m_helper; + + //! AMR unordered map which maps orchard keys to quadrant number for all key in the mesh + //! (owned quadrants and ghost quadrants) + amr_hashmap_t m_amr_hashmap_device; + + //! list of orchard key of the mesh + orchard_key_view_t m_orchard_keys_device; + + //! total number of octants in the current MPI process (ghost block excluded) + const int32_t m_local_num_octants; + + //! number of cells per leaf block + const int32_t m_nbCellsPerLeaf; + + //! block sizes + const block_size_t m_block_sizes; + + //! p4est brick connectivity sizes + const brick_size_t m_brick_sizes; + + //! is p4est connectivity periodic ? + const Kokkos::Array m_is_brick_periodic; + + //! scaling factor + real_t m_scaling_factor; + + //! a block data array (no ghosts, sizes= bx,by,bz) + DataArrayBlock_t m_userdata_in; + + //! a block data array (no ghosts, sizes= bx,by,bz) + DataArrayBlock_t m_userdata_out; + +}; // class ComputeSchlieren + +// explicit template instantiation +extern template class ComputeSchlieren<2, kalypsso::DefaultDevice>; +extern template class ComputeSchlieren<3, kalypsso::DefaultDevice>; + +} // namespace core + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_COMPUTESCHLIEREN_H_ diff --git a/src/kalypsso/core/ComputeSmoothInterfaceFunction.cpp b/src/kalypsso/core/ComputeSmoothInterfaceFunction.cpp new file mode 100644 index 0000000..8d61bc4 --- /dev/null +++ b/src/kalypsso/core/ComputeSmoothInterfaceFunction.cpp @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ComputeSmoothInterfaceFunction.cpp + */ +#include + +namespace kalypsso +{ + +/*************************************************/ +/*************************************************/ +/*************************************************/ +template +ComputeSmoothInterfaceFunction::ComputeSmoothInterfaceFunction( + DataArrayBlock_t userdata, + DataArrayGhostedBlock_t smooth_interface_function, + int32_t ivar, + const int32_t iOct_first, + const int32_t num_quads, + const real_t alpha) + : m_U(userdata) + , m_sif(smooth_interface_function) + , m_ivar(ivar) + , m_iOct_first(iOct_first) + , m_num_quads(num_quads) + , m_alpha(alpha) +{} // constructor + +// ============================================================== +// ============================================================== +template +void +ComputeSmoothInterfaceFunction::apply( + ConfigMap const & config_map, + DataArrayBlock_t userdata, + DataArrayGhostedBlock_t smooth_interface_function, + const int32_t ivar, + const int32_t iOct_first, + const int32_t num_quads) +{ + + ComputeSmoothInterfaceFunction functor( + userdata, + smooth_interface_function, + ivar, + iOct_first, + num_quads, + config_map.getReal("smooth_interface_function", "alpha", KALYPSSO_NUM(0.1))); + + const auto nbIterations = num_quads * userdata.num_cells(); + + // launch computation + Kokkos::parallel_for("kalypsso::core::ComputeSmoothInterfaceFunction", + Kokkos::RangePolicy(0, nbIterations), + functor); + +} // apply + +// ==================================================================== +// ==================================================================== +template +KOKKOS_INLINE_FUNCTION void +ComputeSmoothInterfaceFunction::operator()(const index_t & global_index) const +{ + + // retrieve local octant index in range [0, num_quads_to_process [ + auto const iOct_local = static_cast(global_index / m_U.num_cells()); + auto const cell_index = static_cast(global_index - iOct_local * m_U.num_cells()); + + KOKKOS_ASSERT(iOct_local < m_U.num_quadrants()); + KOKKOS_ASSERT(iOct_local < m_sif.num_quadrants()); + + const auto phi = m_U(cell_index, m_ivar, iOct_local); + + // we use ijk here because m_sif is a ghosted array + const auto ijk = cellindex_to_coord(cell_index, m_U.block_size()); + + m_sif(ijk, 0, iOct_local) = pow(phi, m_alpha) / (pow(phi, m_alpha) + pow(ONE_F - phi, m_alpha)); + +} // operator () + +// explicit template instantiation +template class ComputeSmoothInterfaceFunction<2, kalypsso::DefaultDevice>; +template class ComputeSmoothInterfaceFunction<3, kalypsso::DefaultDevice>; + +} // namespace kalypsso diff --git a/src/kalypsso/core/ComputeSmoothInterfaceFunction.h b/src/kalypsso/core/ComputeSmoothInterfaceFunction.h new file mode 100644 index 0000000..2fa14f5 --- /dev/null +++ b/src/kalypsso/core/ComputeSmoothInterfaceFunction.h @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ComputeSmoothInterfaceFunction.h + */ +#ifndef KALYPSSO_CORE_COMPUTE_SMOOTH_INTERFACE_FUNCTION_H_ +#define KALYPSSO_CORE_COMPUTE_SMOOTH_INTERFACE_FUNCTION_H_ + +#include // for assertm +#include +#include // for DataArrayBlock +#include + +namespace kalypsso +{ + +/*************************************************/ +/*************************************************/ +/*************************************************/ +/** + * Compute smooth interface function defined as \f$ \psi = + * \frac{\phi_0^\alpha}{\phi_0^\alpha+\phi_1^\alpha}\f$ + * + * \f$ \psi \f$ must have same block size as the conservative/primitive variables array but can have + * a different ghost width + */ +template +class ComputeSmoothInterfaceFunction +{ + +public: + using exec_space = typename device_t::execution_space; + using index_t = int64_t; + + // data array related type aliases + using DataArrayBlock_t = DataArrayBlock; + using DataArrayGhostedBlock_t = DataArrayGhostedBlock; + +private: + //! a block array (typically of conservative variables) + DataArrayBlock_t m_U; + + //! smooth interface function + DataArrayGhostedBlock_t m_sif; + + //! variable index to address m_q identifying a scalar used for computing the + //! smooth interface function; e.g. for the godunov_five_eq, it should fm[Hydro::IPHI] + int32_t m_ivar; + + //! offset to first octant + const int32_t m_iOct_first; + + //! number of quadrants to process + const int32_t m_num_quads; + + //! alpha + const real_t m_alpha; + +public: + // ============================================================== + // ============================================================== + ComputeSmoothInterfaceFunction(DataArrayBlock_t userdata, + DataArrayGhostedBlock_t smooth_interface_function, + int32_t ivar, + const int32_t iOct_first, + const int32_t num_quads, + const real_t alpha); + + // ============================================================== + // ============================================================== + //! static method which does it all: create and execute functor with range policy + //! + static void + apply(ConfigMap const & config_map, + DataArrayBlock_t userdata, + DataArrayGhostedBlock_t smooth_interface_function, + int32_t ivar, + const int32_t iOct_first, + const int32_t num_quads); + + // ==================================================================== + // ==================================================================== + KOKKOS_INLINE_FUNCTION + void + operator()(const index_t & global_index) const; + +}; // ComputeSmoothInterfaceFunction + +// explicit template instantiation +extern template class ComputeSmoothInterfaceFunction<2, kalypsso::DefaultDevice>; +extern template class ComputeSmoothInterfaceFunction<3, kalypsso::DefaultDevice>; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_COMPUTE_SMOOTH_INTERFACE_FUNCTION_H_ diff --git a/src/kalypsso/core/ComputeVolumeIntegralValue.cpp b/src/kalypsso/core/ComputeVolumeIntegralValue.cpp new file mode 100644 index 0000000..0069e23 --- /dev/null +++ b/src/kalypsso/core/ComputeVolumeIntegralValue.cpp @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ComputeVolumeIntegralValue.cpp + * + * \brief Contains the definition of ComputeVolumeIntegralValue. + */ + +#include + +#include + +namespace kalypsso +{ + +namespace core +{ + +// ================================================================================================ +// ================================================================================================ +template +ComputeVolumeIntegralValue::ComputeVolumeIntegralValue( + const DataArrayBlock & data, + const OrchardKeys & keys, + const int32_t var_index, + const ConfigMap & config_map) + : m_data(data) + , m_keys(keys) + , m_var_index(var_index) + , m_scaling_factor(get_scaling_factor(config_map)) +{} + +// ================================================================================================ +// ================================================================================================ +template +real_t +ComputeVolumeIntegralValue::apply(const DataArrayBlock & data, + const int32_t start_octant, + const int32_t end_octant, + const OrchardKeys & keys, + const int32_t var_index, + const ConfigMap & config_map, + [[maybe_unused]] const ParallelEnv & par_env) +{ + ComputeVolumeIntegralValue functor(data, keys, var_index, config_map); + + const int32_t nb_cells = data.num_cells(); + const int32_t start = start_octant * nb_cells; + const int32_t end = end_octant * nb_cells; + Kokkos::RangePolicy policy(start, end); + + real_t local_total = 0; + Kokkos::Sum reducer(local_total); + Kokkos::parallel_reduce("kalypsso::core::ComputeVolumeIntegralValue", policy, functor, reducer); + + real_t global_total = local_total; +#ifdef KALYPSSO_CORE_USE_MPI + par_env.comm().template MPI_Allreduce(&local_total, &global_total, 1); +#endif // KALYPSSO_CORE_USE_MPI + + return global_total; +} + +// ================================================================================================ +// ================================================================================================ +template +KOKKOS_FUNCTION void +ComputeVolumeIntegralValue::operator()(const int32_t i_global, real_t & total) const +{ + const auto block_size = m_data.block_size(); + const auto nb_cells = m_data.num_cells(); + const auto i_oct = i_global / nb_cells; + const auto i_cell = i_global - nb_cells * i_oct; + + const auto level = orchard_key_t::level(m_keys(i_oct)); + const auto dx = compute_cell_length(level, block_size[IX]) * m_scaling_factor; + + real_t vol = dx * dx; + if constexpr (dim == 3) + vol *= dx; + + total += vol * m_data(i_cell, m_var_index, i_oct); +} + +// ================================================================================================ +// ================================================================================================ +template class ComputeVolumeIntegralValue<2, kalypsso::DefaultDevice>; +template class ComputeVolumeIntegralValue<3, kalypsso::DefaultDevice>; + +} // namespace core + +} // namespace kalypsso diff --git a/src/kalypsso/core/ComputeVolumeIntegralValue.h b/src/kalypsso/core/ComputeVolumeIntegralValue.h new file mode 100644 index 0000000..7c3346f --- /dev/null +++ b/src/kalypsso/core/ComputeVolumeIntegralValue.h @@ -0,0 +1,115 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ComputeVolumeIntegralValue.h + * + * \brief Contains the functor used to compute a volume integral value. + */ + +#ifndef KALYPSSO_CORE_VOLUME_COMPUTE_INTEGRAL_VALUE_H_ +#define KALYPSSO_CORE_VOLUME_COMPUTE_INTEGRAL_VALUE_H_ + +#include +#include +#include // for math functions (max, min, ...) +#include // for DataArray, DataArrayHost, DataArrayGhostedBlock +#include +#include +#include + +namespace kalypsso +{ + +namespace core +{ + +/** + * \class ComputeVolumeIntegralValue + * \brief Functor wrapper that computes a volume integral value over a data array. + * + * \tparam dim The dimension of the problem (must be 2 or 3). + * \tparam device_t On which Kokkos device to run the underlying functor. + * + */ +template +class ComputeVolumeIntegralValue +{ +public: + using OrchardKeys = typename orchard_key_base_t::view_t; + + /** + * \brief Computes a volume integral value over a data array. + * + * Volume integral value is simply defined as the sum of all values of an array (can be a + * DataArrayBlock), but it could be any other integral value if needed. + * + * \param data Values array of the simulation. + * \param start_octant The first octant to compute. + * \param end_octant The last octant to compute, excluded. + * \param keys Orchard keys. + * \param var_index The variable to sum on. + * \param config_map Inputted config map. + * \param par_env Parallel environment. + * + * \note par_env is only used when the number of MPI processes is strictly larger than 1. + * + * \returns The volume integral value. + */ + static real_t + apply(const DataArrayBlock & data, + const int32_t start_octant, + const int32_t end_octant, + const OrchardKeys & keys, + const int32_t var_index, + const ConfigMap & config_map, + [[maybe_unused]] const ParallelEnv & par_env); + + /** + * \brief Kokkos kernel. + * + * \param i_global The global index of the cell. + * \param total The summed total. + */ + KOKKOS_FUNCTION void + operator()(const int32_t i_global, real_t & total) const; + +private: + //! Kokkos execution space + using ExecutionSpace = typename device_t::execution_space; + + /** + * \brief Constructor. + * + * \param data Values array of the simulation. + * \param keys Orchard keys. + * \param var_index The variable to sum on. + * \param config_map Inputted config map. + */ + ComputeVolumeIntegralValue(const DataArrayBlock & data, + const OrchardKeys & keys, + const int32_t var_index, + const ConfigMap & config_map); + + //! Some input data + DataArrayBlock m_data; + + //! The Orchard keys + OrchardKeys m_keys; + + //! The variable index to use for integral value + int32_t m_var_index; + + //! Tree scaling factor (used for computing local metric) + real_t m_scaling_factor; +}; + +extern template class ComputeVolumeIntegralValue<2, kalypsso::DefaultDevice>; +extern template class ComputeVolumeIntegralValue<3, kalypsso::DefaultDevice>; + +} // namespace core + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_VOLUME_COMPUTE_INTEGRAL_VALUE_H_ diff --git a/src/kalypsso/core/ConformalFaceStatus.h b/src/kalypsso/core/ConformalFaceStatus.h new file mode 100644 index 0000000..6143de3 --- /dev/null +++ b/src/kalypsso/core/ConformalFaceStatus.h @@ -0,0 +1,191 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ConformalFaceStatus.h + * + */ +#ifndef KALYPSSO_CORE_CONFORMALFACESTATUS_H_ +#define KALYPSSO_CORE_CONFORMALFACESTATUS_H_ + +#include + +#include // for Kokkos::View +#include // for KOKKOS_ENABLE_XXX +#include // for Face::XMIN, etc... +#include // for conformal_neighbor_status +#include // for macro KALYPSSO_DISABLE_NVCC_WARNINGS_PUSH + +KALYPSSO_DISABLE_NVCC_WARNINGS_PUSH() + +namespace kalypsso +{ + +/** + * \struct conformal_face_status_t + * + * For each face of a tree leaf, encode the conformal status, more precisely we want to store the + * level difference between a given AMR leaf, and its neighbors. + * + * Encoding values (in binary, 2 bits): + * 00 : neighbor is at same AMR level, face is conform + * 01 : neighbor is finer than current cell (higher AMR level), face is non-conform + * 10 : neighbor is coarser than current cell (lower AMR level), face is no-conform + * 11 : neighbor status is not available (this may happens when visiting a MPI ghost leaf). + * + * Implementation note: + * - in 2D, we only need 2 bits per face, that is 8 bits in total per leaf/block. + * - in 3D, we need 6x2bits = 12 bits => rounded-up to 16 bits + */ +template +struct conformal_face_status_t; + +// ==================================================================== +// ==================================================================== +// ==================================================================== +template <> +struct conformal_face_status_t<2> : public BitFieldInteger +{ + using status_t = uint8_t; + + conformal_face_status_t(const conformal_face_status_t &) = default; + + conformal_face_status_t(conformal_face_status_t &&) = default; + + conformal_face_status_t & + operator=(const conformal_face_status_t &) = default; + + conformal_face_status_t & + operator=(conformal_face_status_t &&) = default; + + using BitFieldInteger::BitFieldInteger; + DECLARE_CASTED_FIELD(face_xmin, 0, 2, uint8_t) + DECLARE_CASTED_FIELD(face_xmax, 2, 2, uint8_t) + DECLARE_CASTED_FIELD(face_ymin, 4, 2, uint8_t) + DECLARE_CASTED_FIELD(face_ymax, 6, 2, uint8_t) + + KOKKOS_INLINE_FUNCTION static void + set_status(Face::face_t const & face, uint8_t face_status, status_t & conformal_status) + { + if (face == Face::XMIN) + set_face_xmin(conformal_status, face_status); + else if (face == Face::XMAX) + set_face_xmax(conformal_status, face_status); + else if (face == Face::YMIN) + set_face_ymin(conformal_status, face_status); + else if (face == Face::YMAX) + set_face_ymax(conformal_status, face_status); + } // set_status + + KOKKOS_INLINE_FUNCTION static bool + is_face_neighbor_finer(Face::face_t const & face, status_t const & conformal_status) + { + if (face == Face::XMIN) + return conformal_face_status_t<2>::face_xmin(conformal_status) == + conformal_neighbor_status::NEIGHBOR_IS_FINER; + else if (face == Face::XMAX) + return conformal_face_status_t<2>::face_xmax(conformal_status) == + conformal_neighbor_status::NEIGHBOR_IS_FINER; + else if (face == Face::YMIN) + return conformal_face_status_t<2>::face_ymin(conformal_status) == + conformal_neighbor_status::NEIGHBOR_IS_FINER; + else if (face == Face::YMAX) + return conformal_face_status_t<2>::face_ymax(conformal_status) == + conformal_neighbor_status::NEIGHBOR_IS_FINER; + + return false; + } // is_face_neighbor_finer + +}; // conformal_face_status_t<2> + +// ==================================================================== +// ==================================================================== +// ==================================================================== +template <> +struct conformal_face_status_t<3> : public BitFieldInteger +{ + using status_t = uint16_t; + + conformal_face_status_t(const conformal_face_status_t &) = default; + + conformal_face_status_t(conformal_face_status_t &&) = default; + + conformal_face_status_t & + operator=(const conformal_face_status_t &) = default; + + conformal_face_status_t & + operator=(conformal_face_status_t &&) = default; + + using BitFieldInteger::BitFieldInteger; + DECLARE_CASTED_FIELD(face_xmin, 0, 2, uint8_t) + DECLARE_CASTED_FIELD(face_xmax, 2, 2, uint8_t) + DECLARE_CASTED_FIELD(face_ymin, 4, 2, uint8_t) + DECLARE_CASTED_FIELD(face_ymax, 6, 2, uint8_t) + DECLARE_CASTED_FIELD(face_zmin, 8, 2, uint8_t) + DECLARE_CASTED_FIELD(face_zmax, 10, 2, uint8_t) + + KOKKOS_INLINE_FUNCTION static void + set_status(Face::face_t const & face, uint8_t face_status, status_t & conformal_status) + { + if (face == Face::XMIN) + set_face_xmin(conformal_status, face_status); + else if (face == Face::XMAX) + set_face_xmax(conformal_status, face_status); + else if (face == Face::YMIN) + set_face_ymin(conformal_status, face_status); + else if (face == Face::YMAX) + set_face_ymax(conformal_status, face_status); + else if (face == Face::ZMIN) + set_face_zmin(conformal_status, face_status); + else if (face == Face::ZMAX) + set_face_zmax(conformal_status, face_status); + } // set_status + + KOKKOS_INLINE_FUNCTION static bool + is_face_neighbor_finer(Face::face_t const & face, status_t const & conformal_status) + { + if (face == Face::XMIN) + return conformal_face_status_t<3>::face_xmin(conformal_status) == + conformal_neighbor_status::NEIGHBOR_IS_FINER; + else if (face == Face::XMAX) + return conformal_face_status_t<3>::face_xmax(conformal_status) == + conformal_neighbor_status::NEIGHBOR_IS_FINER; + else if (face == Face::YMIN) + return conformal_face_status_t<3>::face_ymin(conformal_status) == + conformal_neighbor_status::NEIGHBOR_IS_FINER; + else if (face == Face::YMAX) + return conformal_face_status_t<3>::face_ymax(conformal_status) == + conformal_neighbor_status::NEIGHBOR_IS_FINER; + else if (face == Face::ZMIN) + return conformal_face_status_t<3>::face_zmin(conformal_status) == + conformal_neighbor_status::NEIGHBOR_IS_FINER; + else if (face == Face::ZMAX) + return conformal_face_status_t<3>::face_zmax(conformal_status) == + conformal_neighbor_status::NEIGHBOR_IS_FINER; + + return false; + } // is_face_neighbor_finer + +}; // conformal_face_status_t<3> + +//! data type alias to store conformal face status (one value by AMR leaf) +template +using conformal_status_t = typename conformal_face_status_t::status_t; + +//! conformal face status array type alias (device) +template +using conformal_status_view_t = typename Kokkos::View *, device_t>; + +//! conformal face status array type alias (host) +template +using conformal_status_view_host_t = + typename Kokkos::View *, + typename conformal_status_view_t::host_mirror_space>; + + +} // namespace kalypsso + +KALYPSSO_DISABLE_NVCC_WARNINGS_POP() + +#endif // KALYPSSO_CORE_CONFORMALFACESTATUS_H_ diff --git a/src/kalypsso/core/ConformalFullStatus.h b/src/kalypsso/core/ConformalFullStatus.h new file mode 100644 index 0000000..1a560a9 --- /dev/null +++ b/src/kalypsso/core/ConformalFullStatus.h @@ -0,0 +1,365 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ConformalFullStatus.h + * + */ +#ifndef KALYPSSO_CORE_CONFORMALFULLSTATUS_H_ +#define KALYPSSO_CORE_CONFORMALFULLSTATUS_H_ + +#include + +#include // for Kokkos::View +#include // for KOKKOS_ENABLE_XXX +#include // for Face::XMIN, etc... +#include // for conformal_neighbor_status +#include // for macro KALYPSSO_DISABLE_NVCC_WARNINGS_PUSH + +KALYPSSO_DISABLE_NVCC_WARNINGS_PUSH() + +namespace kalypsso +{ + +/** + * \struct conformal_full_status_t + * + * For each face, corner or edge (in 3d) of a tree leaf, encode the conformal status, + * more precisely we want to store the level difference between a given AMR leaf, and + * its neighbors. + * + * Encoding values (in binary, 2 bits): + * 00 : neighbor is at same AMR level, face is conform + * 01 : neighbor is finer than current cell (higher AMR level), face is non-conform + * 10 : neighbor is coarser than current cell (lower AMR level), face is no-conform + * 11 : neighbor status is not available (this may happens when visiting a MPI ghost leaf). + * + * Implementation note: + * - in 2D, we only need 2 bits per face and corner, that is 16 bits in total per leaf/block. + * - in 3D, we need 26x2bits = 52 bits => rounded-up to 64 bits + * + * \note this a more general version of conformal_face_status_t; + * sometimes conformal_face_status_t is enough, sometimes not (e.g. in MHD we may need the full + * conformal status) + * + */ +template +struct conformal_full_status_t; + +// ==================================================================== +// ==================================================================== +// ==================================================================== +template <> +struct conformal_full_status_t<2> : public BitFieldInteger +{ + using status_t = uint16_t; + + conformal_full_status_t(const conformal_full_status_t &) = default; + + conformal_full_status_t(conformal_full_status_t &&) = default; + + conformal_full_status_t & + operator=(const conformal_full_status_t &) = default; + + conformal_full_status_t & + operator=(conformal_full_status_t &&) = default; + + using BitFieldInteger::BitFieldInteger; + // clang-format off + DECLARE_CASTED_FIELD(face_xmin, 0, 2, uint8_t) + DECLARE_CASTED_FIELD(face_xmax, 2, 2, uint8_t) + DECLARE_CASTED_FIELD(face_ymin, 4, 2, uint8_t) + DECLARE_CASTED_FIELD(face_ymax, 6, 2, uint8_t) + DECLARE_CASTED_FIELD(edge_xmin_ymin, 8, 2, uint8_t) + DECLARE_CASTED_FIELD(edge_xmax_ymin, 10, 2, uint8_t) + DECLARE_CASTED_FIELD(edge_xmin_ymax, 12, 2, uint8_t) + DECLARE_CASTED_FIELD(edge_xmax_ymax, 14, 2, uint8_t) + // clang-format on + + //! set a face status + KOKKOS_INLINE_FUNCTION static void + set_status(Face::face_t face, uint8_t face_status, status_t & conformal_status) + { + if (face == Face::XMIN) + set_face_xmin(conformal_status, face_status); + else if (face == Face::XMAX) + set_face_xmax(conformal_status, face_status); + else if (face == Face::YMIN) + set_face_ymin(conformal_status, face_status); + else if (face == Face::YMAX) + set_face_ymax(conformal_status, face_status); + } // set_status + + //! get a face status + KOKKOS_INLINE_FUNCTION static uint8_t + get_status(Face::face_t face, status_t const & conformal_status) + { + if (face == Face::XMIN) + return face_xmin(conformal_status); + else if (face == Face::XMAX) + return face_xmax(conformal_status); + else if (face == Face::YMIN) + return face_ymin(conformal_status); + else if (face == Face::YMAX) + return face_ymax(conformal_status); + return conformal_neighbor_status::NEIGHBOR_IS_UNAVAILABLE; + } // get_status + + //! set an edge status + KOKKOS_INLINE_FUNCTION static void + set_status(Face::face_t face_x, + Face::face_t face_y, + uint8_t edge_status, + status_t & conformal_status) + { + if (face_x == Face::XMIN and face_y == Face::YMIN) + set_edge_xmin_ymin(conformal_status, edge_status); + else if (face_x == Face::XMAX and face_y == Face::YMIN) + set_edge_xmax_ymin(conformal_status, edge_status); + else if (face_x == Face::XMIN and face_y == Face::YMAX) + set_edge_xmin_ymax(conformal_status, edge_status); + else if (face_x == Face::XMAX and face_y == Face::YMAX) + set_edge_xmax_ymax(conformal_status, edge_status); + } // set_status + + //! get an edge status + KOKKOS_INLINE_FUNCTION static uint8_t + get_status(Face::face_t face_x, Face::face_t face_y, status_t const & conformal_status) + { + if (face_x == Face::XMIN and face_y == Face::YMIN) + return edge_xmin_ymin(conformal_status); + else if (face_x == Face::XMAX and face_y == Face::YMIN) + return edge_xmax_ymin(conformal_status); + else if (face_x == Face::XMIN and face_y == Face::YMAX) + return edge_xmin_ymax(conformal_status); + else if (face_x == Face::XMAX and face_y == Face::YMAX) + return edge_xmax_ymax(conformal_status); + return conformal_neighbor_status::NEIGHBOR_IS_UNAVAILABLE; + } // get_status + +}; // conformal_full_status_t<2> + +// ==================================================================== +// ==================================================================== +// ==================================================================== +template <> +struct conformal_full_status_t<3> : public BitFieldInteger +{ + using status_t = uint64_t; + + conformal_full_status_t(const conformal_full_status_t &) = default; + + conformal_full_status_t(conformal_full_status_t &&) = default; + + conformal_full_status_t & + operator=(const conformal_full_status_t &) = default; + + conformal_full_status_t & + operator=(conformal_full_status_t &&) = default; + + using BitFieldInteger::BitFieldInteger; + // clang-format off + DECLARE_CASTED_FIELD(face_xmin, 0, 2, uint8_t) + DECLARE_CASTED_FIELD(face_xmax, 2, 2, uint8_t) + DECLARE_CASTED_FIELD(face_ymin, 4, 2, uint8_t) + DECLARE_CASTED_FIELD(face_ymax, 6, 2, uint8_t) + DECLARE_CASTED_FIELD(face_zmin, 8, 2, uint8_t) + DECLARE_CASTED_FIELD(face_zmax, 10, 2, uint8_t) + DECLARE_CASTED_FIELD(edge_xmin_ymin, 12, 2, uint8_t) + DECLARE_CASTED_FIELD(edge_xmax_ymin, 14, 2, uint8_t) + DECLARE_CASTED_FIELD(edge_xmin_ymax, 16, 2, uint8_t) + DECLARE_CASTED_FIELD(edge_xmax_ymax, 18, 2, uint8_t) + DECLARE_CASTED_FIELD(edge_ymin_zmin, 20, 2, uint8_t) + DECLARE_CASTED_FIELD(edge_ymax_zmin, 22, 2, uint8_t) + DECLARE_CASTED_FIELD(edge_ymin_zmax, 24, 2, uint8_t) + DECLARE_CASTED_FIELD(edge_ymax_zmax, 26, 2, uint8_t) + DECLARE_CASTED_FIELD(edge_xmin_zmin, 28, 2, uint8_t) + DECLARE_CASTED_FIELD(edge_xmax_zmin, 30, 2, uint8_t) + DECLARE_CASTED_FIELD(edge_xmin_zmax, 32, 2, uint8_t) + DECLARE_CASTED_FIELD(edge_xmax_zmax, 34, 2, uint8_t) + DECLARE_CASTED_FIELD(corner_xmin_ymin_zmin, 36, 2, uint8_t) + DECLARE_CASTED_FIELD(corner_xmax_ymin_zmin, 38, 2, uint8_t) + DECLARE_CASTED_FIELD(corner_xmin_ymax_zmin, 40, 2, uint8_t) + DECLARE_CASTED_FIELD(corner_xmax_ymax_zmin, 42, 2, uint8_t) + DECLARE_CASTED_FIELD(corner_xmin_ymin_zmax, 44, 2, uint8_t) + DECLARE_CASTED_FIELD(corner_xmax_ymin_zmax, 46, 2, uint8_t) + DECLARE_CASTED_FIELD(corner_xmin_ymax_zmax, 48, 2, uint8_t) + DECLARE_CASTED_FIELD(corner_xmax_ymax_zmax, 50, 2, uint8_t) + // clang-format on + + //! set a face status + KOKKOS_INLINE_FUNCTION static void + set_status(Face::face_t face, uint8_t face_status, status_t & conformal_status) + { + if (face == Face::XMIN) + set_face_xmin(conformal_status, face_status); + else if (face == Face::XMAX) + set_face_xmax(conformal_status, face_status); + else if (face == Face::YMIN) + set_face_ymin(conformal_status, face_status); + else if (face == Face::YMAX) + set_face_ymax(conformal_status, face_status); + else if (face == Face::ZMIN) + set_face_zmin(conformal_status, face_status); + else if (face == Face::ZMAX) + set_face_zmax(conformal_status, face_status); + } // set_status - face + + //! get a face status + KOKKOS_INLINE_FUNCTION static uint8_t + get_status(Face::face_t face, status_t const & conformal_status) + { + if (face == Face::XMIN) + return face_xmin(conformal_status); + else if (face == Face::XMAX) + return face_xmax(conformal_status); + else if (face == Face::YMIN) + return face_ymin(conformal_status); + else if (face == Face::YMAX) + return face_ymax(conformal_status); + else if (face == Face::ZMIN) + return face_zmin(conformal_status); + else if (face == Face::ZMAX) + return face_zmax(conformal_status); + return conformal_neighbor_status::NEIGHBOR_IS_UNAVAILABLE; + } // set_status - face + + //! set an edge status + KOKKOS_INLINE_FUNCTION static void + set_status(Face::face_t face_0, + Face::face_t face_1, + uint8_t edge_status, + status_t & conformal_status) + { + if (face_0 == Face::XMIN and face_1 == Face::YMIN) + set_edge_xmin_ymin(conformal_status, edge_status); + else if (face_0 == Face::XMAX and face_1 == Face::YMIN) + set_edge_xmax_ymin(conformal_status, edge_status); + else if (face_0 == Face::XMIN and face_1 == Face::YMAX) + set_edge_xmin_ymax(conformal_status, edge_status); + else if (face_0 == Face::XMAX and face_1 == Face::YMAX) + set_edge_xmax_ymax(conformal_status, edge_status); + else if (face_0 == Face::YMIN and face_1 == Face::ZMIN) + set_edge_ymin_zmin(conformal_status, edge_status); + else if (face_0 == Face::YMAX and face_1 == Face::ZMIN) + set_edge_ymax_zmin(conformal_status, edge_status); + else if (face_0 == Face::YMIN and face_1 == Face::ZMAX) + set_edge_ymin_zmax(conformal_status, edge_status); + else if (face_0 == Face::YMAX and face_1 == Face::ZMAX) + set_edge_ymax_zmax(conformal_status, edge_status); + else if (face_0 == Face::XMIN and face_1 == Face::ZMIN) + set_edge_xmin_zmin(conformal_status, edge_status); + else if (face_0 == Face::XMAX and face_1 == Face::ZMIN) + set_edge_xmax_zmin(conformal_status, edge_status); + else if (face_0 == Face::XMIN and face_1 == Face::ZMAX) + set_edge_xmin_zmax(conformal_status, edge_status); + else if (face_0 == Face::XMAX and face_1 == Face::ZMAX) + set_edge_xmax_zmax(conformal_status, edge_status); + } // set_status - edge + + //! get an edge status + KOKKOS_INLINE_FUNCTION static uint8_t + get_status(Face::face_t face_0, Face::face_t face_1, status_t const & conformal_status) + { + if (face_0 == Face::XMIN and face_1 == Face::YMIN) + return edge_xmin_ymin(conformal_status); + else if (face_0 == Face::XMAX and face_1 == Face::YMIN) + return edge_xmax_ymin(conformal_status); + else if (face_0 == Face::XMIN and face_1 == Face::YMAX) + return edge_xmin_ymax(conformal_status); + else if (face_0 == Face::XMAX and face_1 == Face::YMAX) + return edge_xmax_ymax(conformal_status); + else if (face_0 == Face::YMIN and face_1 == Face::ZMIN) + return edge_ymin_zmin(conformal_status); + else if (face_0 == Face::YMAX and face_1 == Face::ZMIN) + return edge_ymax_zmin(conformal_status); + else if (face_0 == Face::YMIN and face_1 == Face::ZMAX) + return edge_ymin_zmax(conformal_status); + else if (face_0 == Face::YMAX and face_1 == Face::ZMAX) + return edge_ymax_zmax(conformal_status); + else if (face_0 == Face::XMIN and face_1 == Face::ZMIN) + return edge_xmin_zmin(conformal_status); + else if (face_0 == Face::XMAX and face_1 == Face::ZMIN) + return edge_xmax_zmin(conformal_status); + else if (face_0 == Face::XMIN and face_1 == Face::ZMAX) + return edge_xmin_zmax(conformal_status); + else if (face_0 == Face::XMAX and face_1 == Face::ZMAX) + return edge_xmax_zmax(conformal_status); + return conformal_neighbor_status::NEIGHBOR_IS_UNAVAILABLE; + } // get_status - edge + + //! set a corner status + KOKKOS_INLINE_FUNCTION static void + set_status(Face::face_t face_0, + Face::face_t face_1, + Face::face_t face_2, + uint8_t corner_status, + status_t & conformal_status) + { + if (face_0 == Face::XMIN and face_1 == Face::YMIN and face_2 == Face::ZMIN) + set_corner_xmin_ymin_zmin(conformal_status, corner_status); + else if (face_0 == Face::XMAX and face_1 == Face::YMIN and face_2 == Face::ZMIN) + set_corner_xmax_ymin_zmin(conformal_status, corner_status); + else if (face_0 == Face::XMIN and face_1 == Face::YMAX and face_2 == Face::ZMIN) + set_corner_xmin_ymax_zmin(conformal_status, corner_status); + else if (face_0 == Face::XMAX and face_1 == Face::YMAX and face_2 == Face::ZMIN) + set_corner_xmax_ymax_zmin(conformal_status, corner_status); + else if (face_0 == Face::XMIN and face_1 == Face::YMIN and face_2 == Face::ZMAX) + set_corner_xmin_ymin_zmax(conformal_status, corner_status); + else if (face_0 == Face::XMAX and face_1 == Face::YMIN and face_2 == Face::ZMAX) + set_corner_xmax_ymin_zmax(conformal_status, corner_status); + else if (face_0 == Face::XMIN and face_1 == Face::YMAX and face_2 == Face::ZMAX) + set_corner_xmin_ymax_zmax(conformal_status, corner_status); + else if (face_0 == Face::XMAX and face_1 == Face::YMAX and face_2 == Face::ZMAX) + set_corner_xmax_ymax_zmax(conformal_status, corner_status); + } // set status - corner + + //! get a corner status + KOKKOS_INLINE_FUNCTION static uint8_t + get_status(Face::face_t face_0, + Face::face_t face_1, + Face::face_t face_2, + status_t const & conformal_status) + { + if (face_0 == Face::XMIN and face_1 == Face::YMIN and face_2 == Face::ZMIN) + return corner_xmin_ymin_zmin(conformal_status); + else if (face_0 == Face::XMAX and face_1 == Face::YMIN and face_2 == Face::ZMIN) + return corner_xmax_ymin_zmin(conformal_status); + else if (face_0 == Face::XMIN and face_1 == Face::YMAX and face_2 == Face::ZMIN) + return corner_xmin_ymax_zmin(conformal_status); + else if (face_0 == Face::XMAX and face_1 == Face::YMAX and face_2 == Face::ZMIN) + return corner_xmax_ymax_zmin(conformal_status); + else if (face_0 == Face::XMIN and face_1 == Face::YMIN and face_2 == Face::ZMAX) + return corner_xmin_ymin_zmax(conformal_status); + else if (face_0 == Face::XMAX and face_1 == Face::YMIN and face_2 == Face::ZMAX) + return corner_xmax_ymin_zmax(conformal_status); + else if (face_0 == Face::XMIN and face_1 == Face::YMAX and face_2 == Face::ZMAX) + return corner_xmin_ymax_zmax(conformal_status); + else if (face_0 == Face::XMAX and face_1 == Face::YMAX and face_2 == Face::ZMAX) + return corner_xmax_ymax_zmax(conformal_status); + return conformal_neighbor_status::NEIGHBOR_IS_UNAVAILABLE; + } // set status - corner + +}; // conformal_full_status_t<3> + +//! data type alias to store full conformal status (one value by AMR leaf) +template +using conformal_full_status_value_t = typename conformal_full_status_t::status_t; + +//! conformal face status array type alias (device) +template +using conformal_full_status_view_t = + typename Kokkos::View *, device_t>; + +//! conformal face status array type alias (host) +template +using conformal_full_status_view_host_t = + typename Kokkos::View *, + typename conformal_full_status_view_t::host_mirror_space>; + +} // namespace kalypsso + +KALYPSSO_DISABLE_NVCC_WARNINGS_POP() + +#endif // KALYPSSO_CORE_CONFORMALFULLSTATUS_H_ diff --git a/src/kalypsso/core/ConformalNeighborStatus.h b/src/kalypsso/core/ConformalNeighborStatus.h new file mode 100644 index 0000000..9c43c7f --- /dev/null +++ b/src/kalypsso/core/ConformalNeighborStatus.h @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ConformalNeighborStatus.h + * + */ +#ifndef KALYPSSO_CORE_CONFORMALNEIGHBORSTATUS_H_ +#define KALYPSSO_CORE_CONFORMALNEIGHBORSTATUS_H_ + +#include // for KOKKOS_ENABLE_XXX +#include // for Face::XMIN, etc... + +namespace kalypsso +{ + +struct conformal_neighbor_status +{ + + enum neighbor_status : uint8_t + { + NEIGHBOR_IS_AT_SAME_LEVEL = 0, + NEIGHBOR_IS_FINER = 1, + NEIGHBOR_IS_COARSER = 2, + NEIGHBOR_IS_UNAVAILABLE = 3 + }; + + KOKKOS_INLINE_FUNCTION static bool + is_at_same_level(uint8_t const & status) + { + return status == NEIGHBOR_IS_AT_SAME_LEVEL; + } + + KOKKOS_INLINE_FUNCTION static bool + is_finer(uint8_t const & status) + { + return status == NEIGHBOR_IS_FINER; + } + + KOKKOS_INLINE_FUNCTION static bool + is_coarser(uint8_t const & status) + { + return status == NEIGHBOR_IS_COARSER; + } + + KOKKOS_INLINE_FUNCTION static bool + is_conformal(uint8_t const & status) + { + return is_at_same_level(status); + } + + KOKKOS_INLINE_FUNCTION static bool + is_non_conformal(uint8_t const & status) + { + return is_finer(status) or is_coarser(status); + } + + KOKKOS_INLINE_FUNCTION static bool + is_non_available(uint8_t const & status) + { + return status == NEIGHBOR_IS_UNAVAILABLE; + } +}; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_CONFORMALNEIGHBORSTATUS_H_ diff --git a/src/kalypsso/core/ConservativityCheck.cpp b/src/kalypsso/core/ConservativityCheck.cpp new file mode 100644 index 0000000..4d8fa22 --- /dev/null +++ b/src/kalypsso/core/ConservativityCheck.cpp @@ -0,0 +1,159 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ConservativityCheck.cpp + * + * \brief Contains a helper class for checking conservativity. + */ + +#include +#include + +#include +#include + +namespace kalypsso +{ + +namespace core +{ + +// ================================================================================================ +// ================================================================================================ +template +void +ConservativityCheck::register_value( + const DataArrayBlock & data, + const int32_t start_octant, + const int32_t end_octant, + const OrchardKeys & keys, + const int32_t var_index, + const std::string var_name, + const ConfigMap & config_map, + const ParallelEnv & par_env, + bool is_reference) +{ + + const auto value = ComputeVolumeIntegralValue::apply( + data, start_octant, end_octant, keys, var_index, config_map, par_env); + + insert_or_update_value(var_name, value, is_reference); + +} // ConservativityCheck::register_value + +// ================================================================================================ +// ================================================================================================ +template +void +ConservativityCheck::print_report(const ParallelEnv & par_env) const +{ + if (m_reference_values.size() > 0) + { + if (par_env.rank() == 0) + { + std::cout << "========================================\n"; + std::cout << "Conservativity check report \n"; + std::cout << "========================================\n"; + } + } + + for (auto ref : m_reference_values) + { + auto const & var_name = ref.first; + auto const & ref_value = ref.second; + + auto it = m_test_values.find(var_name); + + if (it != m_test_values.end()) + { + // we found a value that exists in both reference and test map + auto const & test_value = it->second; + + const auto diff = fabs(test_value - ref_value); + const auto refv = fabs(ref_value); + + if (par_env.rank() == 0) + { + +#ifdef KALYPSSO_CORE_USE_SPDLOG + fmt::print("Conservativity check of {}\n", var_name); + fmt::print("{: >30} {}\n", "Initial volume integral: ", ref_value); + fmt::print("{: >30} {}\n", "Final volume integral: ", test_value); + fmt::print("{: >30} {}\n", "L1 absolute error: ", diff); + if (refv > SMALL_VALUE) + { + fmt::print("{: >30} {}\n", "L1 relative error: ", diff / refv); + } + fmt::print("-------------------------------------\n"); +#else + // clang-format off + std::cout << "Conservativity check of " << var_name << "\n"; + std::cout << std::setprecision(10) + << std::setw(30) << "Initial volume integral: " << ref_value << "\n" + << std::setw(30) << "Final volume integral: " << test_value << "\n" + << std::setw(30) << "L1 absolute error: " << diff << "\n"; + if (refv > SMALL_VALUE) + { + std::cout << std::setw(30) << "L1 relative error: " << diff / refv << "\n"; + } + std::cout << "-------------------------------------\n"; + // clang-format on +#endif + } // end rank == 0 + } + else + { + // we should print some error message, since a value exist in reference map but no + // corresponding value in test map + if (par_env.rank() == 0) + { + std::cerr << "variable " << var_name + << " was not found in test values map; check you code for a missing call to " + "register_value\n"; + } + } + } // end for variables + + if (m_reference_values.size() > 0) + { + if (par_env.rank() == 0) + { + std::cout << "========================================\n"; + } + } + +} // ConservativityCheck::print_report + +// ================================================================================================ +// ================================================================================================ +template +void +ConservativityCheck::insert_or_update_value(std::string var_name, + real_t value, + bool is_reference) +{ + auto & value_map = is_reference ? m_reference_values : m_test_values; + + auto it = value_map.find(var_name); + + if (it != value_map.end()) + { + // entry already exists, just update value + value_map.at(var_name) = value; + } + else + { + value_map.insert({ var_name, value }); + } +} // ConservativityCheck::insert_or_update_value + +// ================================================================================================ +// ================================================================================================ +template class ConservativityCheck<2, kalypsso::DefaultDevice>; +template class ConservativityCheck<3, kalypsso::DefaultDevice>; + +} // namespace core + +} // namespace kalypsso diff --git a/src/kalypsso/core/ConservativityCheck.h b/src/kalypsso/core/ConservativityCheck.h new file mode 100644 index 0000000..136d2aa --- /dev/null +++ b/src/kalypsso/core/ConservativityCheck.h @@ -0,0 +1,110 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ConservativityCheck.h + * + * \brief Contains a helper class for checking conservativity. + * + * This class is only really useful when solving a system of conservation laws (like Euler) using + * either periodic or wall border conditions for which we know that all volume integral value of + * conservative variables should remain constant in time. This class register the volume integral + * values at initial and final time and provide a method to display relative error. + */ +#ifndef KALYPSSO_CORE_CONSERVATIVITY_CHECK_H_ +#define KALYPSSO_CORE_CONSERVATIVITY_CHECK_H_ + +#include +#include +#include // for math functions (max, min, ...) +#include // for DataArray, DataArrayHost, DataArrayGhostedBlock +#include +#include +#include + +#include +#include + +namespace kalypsso +{ + +namespace core +{ + +/** + * \class ComputeVolumeIntegralValue + * \brief Functor wrapper that computes a volume integral value over a data array. + * + * \tparam dim The dimension of the problem (must be 2 or 3). + * \tparam device_t On which Kokkos device to run the underlying functor. + * + */ +template +class ConservativityCheck +{ +public: + using OrchardKeys = typename orchard_key_base_t::view_t; + + static constexpr real_t SMALL_VALUE = KALYPSSO_NUM(1e-13); + + /** + * \brief ConservativityCheck constructor. + * + */ + ConservativityCheck() + : m_reference_values() + , m_test_values(){}; + + /** + * \brief Register a reference or test value. + * + * \param data Values array of the simulation. + * \param start_octant The first octant to compute. + * \param end_octant The last octant to compute, excluded. + * \param keys Orchard keys. + * \param var_index The variable to sum on. + * \param config_map Inputted config map. + * \param is_reference Set to true if you want to register a reference value else a test value. + */ + void + register_value(const DataArrayBlock & data, + const int32_t start_octant, + const int32_t end_octant, + const OrchardKeys & keys, + const int32_t var_index, + const std::string var_name, + const ConfigMap & config_map, + const ParallelEnv & par_env, + bool is_reference); + + /** + * \brief Print conservativity check report. + * + * Compare reference and test values; then display relative errors. + */ + void + print_report(const ParallelEnv & par_env) const; + +private: + //! volume integral reference values (usually computed at initial time) + std::map m_reference_values; + + //! volume integral test values to be compared with reference values (usually computed at final + //! time) + std::map m_test_values; + + //! helper method to insert or update (var_name,value) in either reference or test map + void + insert_or_update_value(std::string var_name, real_t value, bool is_reference); + +}; // class ConservativityCheck + +extern template class ConservativityCheck<2, kalypsso::DefaultDevice>; +extern template class ConservativityCheck<3, kalypsso::DefaultDevice>; + +} // namespace core + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_CONSERVATIVITY_CHECK_H_ diff --git a/src/kalypsso/core/DataArray.h b/src/kalypsso/core/DataArray.h new file mode 100644 index 0000000..9870920 --- /dev/null +++ b/src/kalypsso/core/DataArray.h @@ -0,0 +1,273 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file DataArray.h + */ +#ifndef KALYPSSO_CORE_DATAARRAY_H_ +#define KALYPSSO_CORE_DATAARRAY_H_ + +#include +#include // for KOKKOS_ENABLE_XXX + +#include +#include +#include +#include + +#include +#include + +#include + +#include // for std::conditional + + +namespace kalypsso +{ + +// ============================================================================================ +// ============================================================================================ +// ============================================================================================ +/** + * \class DataArray + * + * A helper class wrapping a 1d Kokkos::View with specific memory allocation strategy (capacity). + */ +template +class DataArray +{ +public: + using DataArray_t = DataArray; + + using FlatArrayOwned_t = Kokkos::View; + using FlatArrayUnmanaged_t = Kokkos::View>; + + using FlatArray_t = typename std:: + conditional::type; + + using view_t = FlatArray_t; + using value_type = typename FlatArray_t::value_type; + + using host_mirror_t = typename FlatArray_t::host_mirror_type; + +private: + //! number of elements + size_t m_num_elements; + + //! storage capacity. + //! actual (physical) number of elements in m_storage + //! it is increased upon resizing only when necessary + size_t m_storage_capacity; + + //! storage array + FlatArray_t m_storage; + +public: + DataArray() = default; + + ~DataArray() = default; + + //! this constructor is only allowed when we want a class that does own data + template = true> + DataArray(std::string name, size_t num_elements) + : m_num_elements(num_elements) + , m_storage_capacity(DataArrayUtils::allocated_capacity(num_elements)) + , m_storage(Kokkos::view_alloc(Kokkos::WithoutInitializing, name), m_storage_capacity) + {} + + //! this constructor is only allowed when we want a class that doesn't own data + template = true> + DataArray(T * ptr, size_t num_elements) + : m_num_elements(num_elements) + , m_storage_capacity(DataArrayUtils::allocated_capacity(static_cast(num_elements))) + , m_storage(ptr, m_storage_capacity) + {} + + //! move constructor + template = true> + DataArray(size_t num_elements, FlatArray_t && some_data) + : m_num_elements(num_elements) + , m_storage_capacity(some_data.size()) + , m_storage(some_data) + {} + + DataArray(const DataArray & other) = default; + + DataArray(DataArray && other) = default; + + DataArray & + operator=(const DataArray & other) = default; + + DataArray & + operator=(DataArray && other) = default; + + + // ================================================================================== + /** + * create host mirror. + */ + static auto + create_host_mirror_view(DataArray_t src) + { + + return DataArray(src.num_elements(), + Kokkos::create_mirror_view(src.physical_view())); + } + + // ================================================================================== + /** + * create host mirror and copy. + */ + static auto + create_host_mirror_view_and_copy(DataArray_t src) + { + + // auto storage_res = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, + // src.storage_ref()); + + auto res = DataArray( + src.num_elements(), + Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, src.physical_view_ref())); + return res; + } + + // ================================================================================== + //! memory access operator() + template + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(I0 i0) const + { +#ifdef KALYPSSO_CORE_DEBUG_BOUNDS_CHECK + KOKKOS_ASSERT((static_cast(i0) < m_num_elements) && "Wrong value for i0"); +#endif + + return m_storage(i0); + } + + // ================================================================================== + auto + label() const + { + return m_storage.label(); + } + + // ================================================================================== + KOKKOS_FORCEINLINE_FUNCTION + auto + num_elements() const + { + return m_num_elements; + } + + // ================================================================================== + KOKKOS_FORCEINLINE_FUNCTION + auto + size() const + { + return static_cast(this->num_elements()); + } + + // ================================================================================== + //! Access to raw pointer + KOKKOS_FORCEINLINE_FUNCTION + auto + data() const + { + return m_storage.data(); + } + + // ================================================================================== + //! return internal Kokkos::View used for raw storage. + //! A regular user should probably never have to use the physical, but most surely the + //! logical_view (with logical sizes). + auto + physical_view() -> decltype(m_storage) + { + return m_storage; + } + + // ================================================================================== + auto + physical_view_ref() -> decltype(m_storage) & + { + return m_storage; + } + + // ================================================================================== + //! return logical size (total number of elements). Not to be confused with capacity (physical + //! size) + auto + logical_size_in_elements() const + { + return m_num_elements; + } + + // ================================================================================== + auto + logical_view() const + { + const auto logical_range = + std::pair(0, this->logical_size_in_elements()); + return Kokkos::subview(m_storage, logical_range); + } + + // ================================================================================== + void + resize(size_t num_elements) + { + + if constexpr (st == StorageType::OWNED) + { + m_num_elements = num_elements; + + // only resize when the requested new size is larger than capacity + if (logical_size_in_elements() > m_storage_capacity) + { + size_t new_storage_capacity = + DataArrayUtils::allocated_capacity(static_cast(num_elements)); + + Kokkos::resize( + Kokkos::view_alloc(Kokkos::WithoutInitializing), m_storage, new_storage_capacity); + + m_storage_capacity = new_storage_capacity; + } + } + else + { + // resizing is not allowed when the class doesn't own its data + KALYPSSO_WARN("Attempting to resize an unmanaged DataArray. This is not allowed."); + } + + } // DataArray::resize + + // ================================================================================== + //! resize (by re-allocating) and initialize to zero + void + resize_and_reset(size_t num_elements) + { + + resize(num_elements); + Kokkos::deep_copy(m_storage, 0); + + } // resize_and_reset + + // ================================================================================== + //! Return the total allocated memory in bytes. + auto + allocated_size_in_bytes() const + { + if constexpr (st == StorageType::OWNED) + { + uint64_t size = m_storage.extent(0) * sizeof(T); + return size; + } + return static_cast(0); + } + +}; // class DataArray + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_DATAARRAY_H_ diff --git a/src/kalypsso/core/DataArrayBlock.h b/src/kalypsso/core/DataArrayBlock.h new file mode 100644 index 0000000..4ca5921 --- /dev/null +++ b/src/kalypsso/core/DataArrayBlock.h @@ -0,0 +1,697 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file DataArrayBlock.h + */ +#ifndef KALYPSSO_CORE_DATAARRAYBLOCK_H_ +#define KALYPSSO_CORE_DATAARRAYBLOCK_H_ + +#include +#include // for KOKKOS_ENABLE_XXX + +#include +#include +#include +#include + +#include // for definition of type block_size_t and shift_t +#include +#include + +#include + +#include // for std::conditional + + +namespace kalypsso +{ + +enum KokkosLayout +{ + KOKKOS_LAYOUT_LEFT, + KOKKOS_LAYOUT_RIGHT +}; + +/** + * DataArrayLeafSoA is used to store data attached to the leaves of the octrees. + * + * first index is leaf id (curvilinear index along the Morton curve) + * last index is some scalar index + * + * The class name is suffixed by SoA (Structure of Array) because the "fast" index is the first + * index, that is, the leaf id (structure of array of leaf index) + */ +template +using DataArrayLeafSoA = Kokkos::View; + +template +using DataArrayLeafSoAUnmanaged = + Kokkos::View>; + +template +using DataArrayHostLeafSoA = typename DataArrayLeafSoA::host_mirror_type; + +/** + * DataArrayLeafAoS is used to store data attached to the leaves of the octrees. + * + * first index is leaf id (curvilinear index along the Morton curve) + * last index is some scalar index + * + * The class name is suffixed by AoS (Array of Structure) because the "fast" index is the last + * index, that is, is variable index (array indexed by leaf index of a structure containing nvar + * scalar values). + */ +template +using DataArrayLeafAoS = Kokkos::View; + +template +using DataArrayLeafAoSUnmanaged = Kokkos::View>; + +template +using DataArrayHostLeafAoS = typename DataArrayLeafAoS::host_mirror_type; + +/** + * DataArrayLeaf mostly used when using one cell per octree leaf. + * + * first index is leaf id (curvilinear index along the Morton curve) + * last index is hydro variable + */ +template +using DataArrayLeaf = DataArrayLeafAoS; + +template +using DataArrayLeafUnmanaged = DataArrayLeafAoSUnmanaged; + +template +using DataArrayLeafHost = typename DataArrayLeaf::host_mirror_type; + +/** + * DataArrayBlockLegacy used when designing a solver with a block of + * data per leaf of the octree. + * + * \note this class is deprecated, please use DataArrayBlock instead. + * + * first index identifies a cell inside a block (left layout, from 0 to bx by -1) + * second index identifies the variable (rho, momentum, energy, ...) + * third index is the leaf id (curvilinear index along the Morton curve) + * + * Note that we enforce Left layout here, since we plan to the Kokkos TeamPolicy with one team per + * leaf, so we favor memory locality inside a block. + */ +template +using DataArrayBlockLegacy = Kokkos::View; + +template +using DataArrayBlockLegacyUnmanaged = + Kokkos::View>; + +template +using DataArrayBlockLegacyHost = typename DataArrayBlockLegacy::host_mirror_type; + +// ============================================================================================ +// ============================================================================================ +/** + * Decode i,j,k from a flat cell index. + */ +template +KOKKOS_FORCEINLINE_FUNCTION auto +cell_index_unravel(int32_t cell_index, block_size_t const & shape) +{ + coord_t ijk; + + if constexpr (dim == 2) + { + // cell_index = i + shape[IX] * j + ijk[IY] = cell_index / shape[IX]; + ijk[IX] = cell_index - shape[IX] * ijk[IY]; + } + else if (dim == 3) + { + // cell_index = i + shape[IX] * j + shape[IX] * shape[IY] * k + ijk[IZ] = cell_index / (shape[IX] * shape[IY]); + cell_index -= ijk[IZ] * shape[IX] * shape[IY]; + + ijk[IY] = cell_index / shape[IX]; + ijk[IX] = cell_index - shape[IX] * ijk[IY]; + } + + return ijk; + +} // cell_index_unravel + +// ============================================================================================ +// ============================================================================================ +/** + * Decode i,j,k,ivar,iOct from a flat index (into a DataBlockArray) + */ +template +KOKKOS_FORCEINLINE_FUNCTION auto +flat_index_unravel(int64_t flat_index, block_size_t const & shape, int32_t const & num_vars) +{ + + block_multiindex_t index; + + const auto num_cells = Kokkos::dim_prod(shape); + + // iOct + index[dim + 1] = flat_index / (num_cells * num_vars); + flat_index -= index[dim + 1] * (num_cells * num_vars); + + // ivar + index[dim] = flat_index / num_cells; + + // cell index + flat_index -= index[dim] * num_cells; + + if constexpr (dim == 2) + { + index[IY] = flat_index / shape[IX]; + index[IX] = flat_index - shape[IX] * index[IY]; + } + else if constexpr (dim == 3) + { + index[IZ] = flat_index / (shape[IY] * shape[IX]); + flat_index -= index[IZ] * shape[IY] * shape[IX]; + + index[IY] = flat_index / shape[IX]; + index[IX] = flat_index - shape[IX] * index[IY]; + } + + return index; + +} // flat_index_unravel + +// ============================================================================================ +// ============================================================================================ +// ============================================================================================ +/** + * \class DataArrayBlock + * + * A helper class around a 1d Kokkos::View providing different left-layout multi-dimensional data + * access. + */ +template +class DataArrayBlock +{ +public: + using DataArrayBlock_t = DataArrayBlock; + + using FlatArrayOwned_t = Kokkos::View; + using FlatArrayUnmanaged_t = Kokkos::View>; + + using FlatArray_t = typename std:: + conditional::type; + + using host_mirror_t = typename FlatArray_t::host_mirror_type; + +private: + //! block size (used for memory allocation). Can only be changed by resizing (re-allocating + //! without copy) + block_size_t m_bSize; + + //! array shape (used for memory addressing). Initially and after resizing, m_shape and m_bSize + //! are equal, but shape can be change by reshaping (no memory reallocation, no data modification, + //! just changing index linearization to access memory differently). + //! Example application: a 2d array of sizes N x (N+1) can be reshaped into (N+1) x N + //! current limitation: we only support reshaping the block size (not the number of variables, or + //! the number of quadrants. => TODO if it can be useful to some application). + block_size_t m_shape; + + //! num cells + int32_t m_num_cells; + + //! num of physical values + int32_t m_num_vars; + + //! number of quadrants + int32_t m_num_quadrants; + + //! storage capacity (should be larger or equal to num_cells * num_vars * num_quadrants). + //! actual (physical) number of elements in m_storage + //! it is increased upon resizing only when necessary + size_t m_storage_capacity; + + //! storage array + FlatArray_t m_storage; + +public: + DataArrayBlock() = default; + + //! this constructor is only allowed when we want a class that does own data + template = true> + DataArrayBlock(std::string name, block_size_t bSize, int32_t num_vars, int32_t num_quadrants) + : m_bSize(bSize) + , m_shape(bSize) + , m_num_cells(Kokkos::dim_prod(bSize)) + , m_num_vars(num_vars) + , m_num_quadrants(num_quadrants) + , m_storage_capacity(DataArrayUtils::allocated_capacity( + static_cast(Kokkos::dim_prod(bSize) * num_vars * num_quadrants))) + , m_storage(Kokkos::view_alloc(Kokkos::WithoutInitializing, name), m_storage_capacity) + {} + + //! this constructor is only allowed when we want a class that doesn't own data + template = true> + DataArrayBlock(T * ptr, block_size_t bSize, int32_t num_vars, int32_t num_quadrants) + : m_bSize(bSize) + , m_shape(bSize) + , m_num_cells(Kokkos::dim_prod(bSize)) + , m_num_vars(num_vars) + , m_num_quadrants(num_quadrants) + , m_storage_capacity(DataArrayUtils::allocated_capacity( + static_cast(Kokkos::dim_prod(bSize) * num_vars * num_quadrants))) + , m_storage(ptr, m_storage_capacity) + {} + + //! move constructor + template = true> + DataArrayBlock(block_size_t bSize, + int32_t num_vars, + int32_t num_quadrants, + FlatArray_t && some_data) + : m_bSize(bSize) + , m_shape(bSize) + , m_num_cells(Kokkos::dim_prod(bSize)) + , m_num_vars(num_vars) + , m_num_quadrants(num_quadrants) + , m_storage_capacity(some_data.size()) + , m_storage(some_data) + {} + + // ================================================================================== + /** + * create host mirror. + */ + static auto + create_host_mirror_view(DataArrayBlock_t src) + { + + return DataArrayBlock(src.block_size(), + src.num_vars(), + src.num_quadrants(), + Kokkos::create_mirror_view(src.physical_view())); + } + + // ================================================================================== + /** + * create host mirror and copy. + */ + static auto + create_host_mirror_view_and_copy(DataArrayBlock_t src) + { + + // auto storage_res = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, + // src.storage_ref()); + + auto res = DataArrayBlock( + src.block_size(), + src.num_vars(), + src.num_quadrants(), + Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, src.physical_view_ref())); + return res; + } + + // ================================================================================== + /** + * Return true when current shape is equal to size set by constructor. + */ + KOKKOS_FORCEINLINE_FUNCTION bool + are_shape_and_size_equal() const + { + return m_bSize == m_shape; + } + + /** + * Reshape DataArrayBlock without reallocating. + * + * We only accept reshaping when the new shape (block sizes) is compatible current memory + * allocation. + * + * \return true when DataArrayBlock is actually reshaped. + */ + KOKKOS_FORCEINLINE_FUNCTION bool + reshape(block_size_t const & new_shape) + { + // when reshaping, as we don't want to reallocate, we need to make sure the new + // shape is not larger than current allocation + // KOKKOS_ASSERT(Kokkos::dim_prod(new_shape) <= Kokkos::dim_prod(m_bSize) && + // "Can't reshape DataArrayBlock, new shape is too large."); + + // only reshape when possible + if (Kokkos::dim_prod(new_shape) <= Kokkos::dim_prod(m_bSize)) + { + m_shape = new_shape; + m_num_cells = Kokkos::dim_prod(new_shape); + return true; + } + return false; + } + + /** + * Restore shape to be equal to the original shape set by constructor. + */ + KOKKOS_FORCEINLINE_FUNCTION void + shape_reset() + { + m_shape = m_bSize; + m_num_cells = Kokkos::dim_prod(m_shape); + } + + // ================================================================================== + /** + * convert a logical multi-index (icell, ivar, iOct) into a flat index to storage (2D). + */ + template + KOKKOS_FORCEINLINE_FUNCTION int64_t + flat_index(ICell icell, IVar ivar, IOct iOct) const + { + return icell + m_num_cells * (ivar + m_num_vars * iOct); + } + + // ================================================================================== + /** + * convert a logical multi-index (i, j, ivar, iOct) into a flat index to storage (2D). + */ + template = true> + KOKKOS_FORCEINLINE_FUNCTION int64_t + flat_index(I0 i0, I1 i1, IVar ivar, IOct iOct) const + { + return i0 + m_shape[IX] * (i1 + m_shape[IY] * (ivar + m_num_vars * iOct)); + } + + // ================================================================================== + /** + * convert a logical multi-index (i, j, k, ivar, iOct) into a flat index to storage (3D). + */ + template = true> + KOKKOS_FORCEINLINE_FUNCTION int64_t + flat_index(I0 i0, I1 i1, I2 i2, IVar ivar, IOct iOct) const + { + return i0 + m_shape[IX] * (i1 + m_shape[IY] * (i2 + m_shape[IZ] * (ivar + m_num_vars * iOct))); + } + + // ================================================================================== + //! memory access operator() as if dealing with a regular Kokkos::View - 2d case + template = true> + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(I0 i0, I1 i1, IVar ivar, IOct iOct) const + { +#ifdef KALYPSSO_CORE_DEBUG_BOUNDS_CHECK + KOKKOS_ASSERT((static_cast(i0) < m_shape[IX]) && "Wrong value for i0"); + KOKKOS_ASSERT((static_cast(i1) < m_shape[IY]) && "Wrong value for i1"); + KOKKOS_ASSERT((static_cast(ivar) < m_num_vars) && "Wrong value for ivar"); + KOKKOS_ASSERT((static_cast(iOct) < m_num_quadrants) && "Wrong value for iOct"); +#endif + + return m_storage(flat_index(i0, i1, ivar, iOct)); + } + + // ================================================================================== + //! memory access operator() as if dealing with a regular Kokkos::View - 3d case + template = true> + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(I0 i0, I1 i1, I2 i2, IVar ivar, IOct iOct) const + { +#ifdef KALYPSSO_CORE_DEBUG_BOUNDS_CHECK + KOKKOS_ASSERT((static_cast(i0) < m_shape[IX]) && "Wrong value for i0"); + KOKKOS_ASSERT((static_cast(i1) < m_shape[IY]) && "Wrong value for i1"); + KOKKOS_ASSERT((static_cast(i2) < m_shape[IZ]) && "Wrong value for i2"); + KOKKOS_ASSERT((static_cast(ivar) < m_num_vars) && "Wrong value for ivar"); + KOKKOS_ASSERT((static_cast(iOct) < m_num_quadrants) && "Wrong value for iOct"); +#endif + + return m_storage(flat_index(i0, i1, i2, ivar, iOct)); + } + + // ================================================================================== + //! memory access operator() using a flat cell index (2d and 3d) + template + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(ICell icell, IVar ivar, IOct iOct) const + { +#ifdef KALYPSSO_CORE_DEBUG_BOUNDS_CHECK + KOKKOS_ASSERT((static_cast(icell) < m_num_cells) && "Wrong value for icell"); + KOKKOS_ASSERT((static_cast(ivar) < m_num_vars) && "Wrong value for ivar"); + KOKKOS_ASSERT((static_cast(iOct) < m_num_quadrants) && "Wrong value for iOct"); +#endif + + return m_storage(flat_index(icell, ivar, iOct)); + } + + // ================================================================================== + //! memory access operator() using a multi-index (2d and 3d) + template + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(coord_t ijk, IVar ivar, IOct iOct) const + { +#ifdef KALYPSSO_CORE_DEBUG_BOUNDS_CHECK + KOKKOS_ASSERT((static_cast(ijk[IX]) < m_shape[IX]) && "Wrong value for ijk[IX]"); + KOKKOS_ASSERT((static_cast(ijk[IY]) < m_shape[IY]) && "Wrong value for ijk[IY]"); + if constexpr (dim == 3) + { + KOKKOS_ASSERT((static_cast(ijk[IZ]) < m_shape[IZ]) && "Wrong value for ijk[IZ]"); + } + KOKKOS_ASSERT((static_cast(ivar) < m_num_vars) && "Wrong value for ivar"); + KOKKOS_ASSERT((static_cast(iOct) < m_num_quadrants) && "Wrong value for iOct"); +#endif + + if constexpr (dim == 2) + { + return this->operator()(ijk[IX], ijk[IY], ivar, iOct); + } + else if constexpr (dim == 3) + { + return this->operator()(ijk[IX], ijk[IY], ijk[IZ], ivar, iOct); + } + } + + // ================================================================================== + //! return true this DataArrayBlock can be considered a flux array. + //! + //! being a flux array means current shape has one extra sizes in one dimension when compared to + //! input bSizes. + KOKKOS_FORCEINLINE_FUNCTION bool + is_flux_array(block_size_t const & bSizes, int dir) const + { + + if constexpr (dim == 2) + { + KOKKOS_ASSERT((dir == IX or dir == IY) && "Wrong direction"); + + return ((dir == IX) and (m_shape[IX] == bSizes[IX] + 1) and (m_shape[IY] == bSizes[IY])) or + ((dir == IY) and (m_shape[IY] == bSizes[IY] + 1) and (m_shape[IX] == bSizes[IX])); + } + else if constexpr (dim == 3) + { + KOKKOS_ASSERT((dir == IX or dir == IY or dir == IZ) && "Wrong direction"); + // clang-format off + return ((dir == IX) and (m_shape[IX] == bSizes[IX] + 1) and + (m_shape[IY] == bSizes[IY] ) and + (m_shape[IZ] == bSizes[IZ] ) ) or + ((dir == IY) and (m_shape[IX] == bSizes[IX] ) and + (m_shape[IY] == bSizes[IY] + 1) and + (m_shape[IZ] == bSizes[IZ] ) ) or + ((dir == IZ) and (m_shape[IX] == bSizes[IX] ) and + (m_shape[IY] == bSizes[IY] ) and + (m_shape[IZ] == bSizes[IZ] + 1) ); + // clang-format on + } + return false; + + } // is_flux_array + + // ================================================================================== + auto + label() const + { + return m_storage.label(); + } + + // ================================================================================== + KOKKOS_FORCEINLINE_FUNCTION + auto const & + block_size() const + { + return m_bSize; + } + + // ================================================================================== + KOKKOS_FORCEINLINE_FUNCTION + auto const & + shape() const + { + return m_shape; + } + + // ================================================================================== + KOKKOS_FORCEINLINE_FUNCTION + auto + num_cells() const + { + return m_num_cells; + } + + // ================================================================================== + KOKKOS_FORCEINLINE_FUNCTION + auto + num_vars() const + { + return m_num_vars; + } + + // ================================================================================== + KOKKOS_FORCEINLINE_FUNCTION + auto + num_quadrants() const + { + return m_num_quadrants; + } + + // ================================================================================== + //! Access to raw pointer + KOKKOS_FORCEINLINE_FUNCTION + auto + data() const + { + return m_storage.data(); + } + + // ================================================================================== + //! return internal Kokkos::View used for raw storage. + //! A regular user should probably never have to use the physical, but most surely the + //! logical_view (with logical sizes). + auto + physical_view() -> decltype(m_storage) + { + return m_storage; + } + + // ================================================================================== + auto + physical_view_ref() -> decltype(m_storage) & + { + return m_storage; + } + + // ================================================================================== + //! return logical size (total number of elements). Not to be confused with capacity (physical + //! size) + auto + logical_size_in_elements() const + { + return static_cast(m_num_cells * m_num_vars * m_num_quadrants); + } + + // ================================================================================== + auto + logical_view() const + { + const auto logical_range = + std::pair(0, this->logical_size_in_elements()); + return Kokkos::subview(m_storage, logical_range); + } + + // ================================================================================== + void + resize(block_size_t bSize, int32_t num_vars, int32_t num_quadrants) + { + if constexpr (st == StorageType::OWNED) + { + m_bSize = bSize; + m_shape = bSize; + m_num_cells = Kokkos::dim_prod(bSize); + m_num_vars = num_vars; + m_num_quadrants = num_quadrants; + + // only resize when the requested new size is larger than capacity + if (logical_size_in_elements() > m_storage_capacity) + { + size_t new_storage_capacity = DataArrayUtils::allocated_capacity( + static_cast(Kokkos::dim_prod(bSize) * num_vars * num_quadrants)); + + Kokkos::resize( + Kokkos::view_alloc(Kokkos::WithoutInitializing), m_storage, new_storage_capacity); + + m_storage_capacity = new_storage_capacity; + } + } + else + { + // resizing is not allowed when the class doesn't own its data + KALYPSSO_WARN("Attempting to resize an unmanaged DataArrayBlock. This is not allowed."); + } + } + + // ================================================================================== + //! resize (by re-allocating) but do not initialize + void + resize(int32_t num_quadrants) + { + m_num_quadrants = num_quadrants; + + // only resize when the requested new size is larger than capacity + if (logical_size_in_elements() > m_storage_capacity) + { + size_t new_storage_capacity = DataArrayUtils::allocated_capacity( + static_cast(m_num_cells * m_num_vars * num_quadrants)); + + Kokkos::resize( + Kokkos::view_alloc(Kokkos::WithoutInitializing), m_storage, new_storage_capacity); + m_storage_capacity = new_storage_capacity; + } + } // resize + + // ================================================================================== + //! resize (by re-allocating) and initialize to zero + void + resize_and_reset(int32_t num_quadrants) + { + resize(num_quadrants); + Kokkos::deep_copy(m_storage, 0); + } // resize_and_reset + + // ================================================================================== + //! Return the total allocated memory in bytes. + auto + allocated_size_in_bytes() const + { + if constexpr (st == StorageType::OWNED) + { + uint64_t size = m_storage.extent(0) * sizeof(T); + return size; + } + return static_cast(0); + } + +}; // class DataArrayBlock + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_DATAARRAYBLOCK_H_ diff --git a/src/kalypsso/core/DataArrayBlockMultiVar.h b/src/kalypsso/core/DataArrayBlockMultiVar.h new file mode 100644 index 0000000..0143c25 --- /dev/null +++ b/src/kalypsso/core/DataArrayBlockMultiVar.h @@ -0,0 +1,985 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file DataArrayBlockMultiVar.h + */ + +#ifndef KALYPSSO_CORE_DATA_ARRAY_BLOCK_MULTI_VAR_H_ +#define KALYPSSO_CORE_DATA_ARRAY_BLOCK_MULTI_VAR_H_ + +#include +#include +#include +#include + +namespace kalypsso +{ + +// ================================================================================================ +// ================================================================================================ + +//! Wide index { i_var, i_oct } +using MVBlockIndex_t = Kokkos::Array; + +/** + * \brief Gets the block's index of an octant's variable + */ +template +KOKKOS_FORCEINLINE_FUNCTION int64_t +flat_mv_block_index(const IVar i_var, + const IOct i_oct, + const DataArray & offsets) +{ + return static_cast(offsets(i_oct)) + i_var; +} + +/** + * \brief Gets the octant's variable from a block's index (reverse of flat_mv_block_index) + */ +template +KOKKOS_FORCEINLINE_FUNCTION auto +flat_mv_block_index_unravel(const int64_t index, const DataArray & offsets) +{ + uint32_t i_oct = 0; + while (i_oct + 1 < offsets.size() and offsets(i_oct + 1) <= index) + i_oct++; + + return MVBlockIndex_t{ static_cast(index) - offsets(i_oct), i_oct }; +} + +// ================================================================================================ +// ================================================================================================ + +/** + * \class DataArrayBlockMultiVar + * + * \brief Wrapper around a DataArrayBlock that can handle multiple variables per octant + * + * It is essentially a wrapper around a regular DataArrayBlock. It works along an "offset" view that + * indicates where does the octant starts in the main array. + * + * For example, if we have the following setup: + * + * | + * Oct 2 | Oct 3 + * 2 Vars | 1 Var + * | + * ----------+---------- + * | + * Oct 0 | Oct 1 + * 2 Vars | 1 Var + * | + * + * Then the offset view will be: [0, 2, 3, 5, 6] (running sum of the number of variables) + * and the corresponding view will be: + * + * Oct 0 || Oct 1 || Oct 2 || Oct 3 + * -------+-------++-------++-------+-------++------- + * Var 0 | Var 1 || Var 0 || Var 0 | Var 1 || Var 0 + * + * In the end, this is a wrapper around a 1-variable DataArrayBlock with the following + * transformation where the variable and octant indices are fused to have full control on the blocks + * arrangement: + * + * DataArrayBlockMultiVar(ijk, i_var, i_oct) <=> DataArrayBlock(ijk, 0, offset[i_oct] + i_var) + * + * This lets us keep the same interface as the usual DataArrayBlock. + * + * \tparam dim Dimension of the block + * \tparam T Value of inner type + * \tparam device_t Location of the data + */ +template +class DataArrayBlockMultiVar +{ +public: + // using Offsets_t = Kokkos::View; + using Offsets_t = DataArray; + using NumVars_t = Kokkos::View; + using DataArrayBlock_t = DataArrayBlock; + using DataArrayBlockMultiVar_t = DataArrayBlockMultiVar; + using MatPresView_t = MaterialPresenceView; + + // ============================================================================================== + // ============================================================================================== + + //! Default constructor. Empty offset and empty storage + DataArrayBlockMultiVar() = default; + + /** + * \brief Constructs an empty DataArrayBlockMultiVar + * + * \param name Views' labels + * \param block_size Block size + */ + DataArrayBlockMultiVar(const std::string & name, const block_size_t block_size) + : m_storage(name, block_size, 1, 0) + , m_offsets(name + " [offsets]", 0) + {} + + /** + * \brief Constructs a new DataArrayBlockMultiVar + * + * \param name Views' labels + * \param block_size Block size + * \param num_vars Array indicating how many variables an octant has + */ + DataArrayBlockMultiVar(const std::string & name, + const block_size_t block_size, + const NumVars_t num_vars) + : DataArrayBlockMultiVar(name, block_size) + { + reorganize(num_vars); + } + + //! Move constructor + DataArrayBlockMultiVar(Offsets_t && offsets, DataArrayBlock_t && storage) + : m_storage(storage) + , m_offsets(offsets) + {} + + // ============================================================================================== + // ============================================================================================== + + /** + * \brief Recomputes the offsets and resizes the internal storage. Replacement for `resize`. + */ + void + reorganize(const NumVars_t num_vars) + { + const auto num_blocks = compute_offsets(num_vars); + m_storage.resize(num_blocks); + } + + /** + * \brief Recomputes the offsets and resizes the internal storage from a 'material presence view'. + */ + void + reorganize(const MatPresView_t mat_pres, const uint32_t num_vars_per_mat) + { + const auto num_blocks = compute_offsets(mat_pres, num_vars_per_mat); + m_storage.resize(num_blocks); + } + + /** + * \brief Recomputes the offsets, resizes the internal storage and resets the data. Replacement + * for `resize_and_reset`. + */ + void + reorganize_and_reset(const NumVars_t num_vars) + { + const auto num_blocks = compute_offsets(num_vars); + m_storage.resize_and_reset(num_blocks); + } + + /** + * \brief Updates the object with another's offsets + */ + template + void + align_with(const DataArrayBlockMultiVar & other) + { + m_offsets = other.offsets(); + m_storage.resize(other.storage().num_quadrants()); + } + + // ============================================================================================== + // ============================================================================================== + + /** + * \brief Gets the internal DataArrayBlock + */ + KOKKOS_INLINE_FUNCTION auto const + storage() const + { + return m_storage; + } + + /** + * \brief Gets the internal DataArrayBlock + */ + KOKKOS_INLINE_FUNCTION auto + storage() + { + return m_storage; + } + + /** + * \brief Gets the internal offset array + */ + KOKKOS_INLINE_FUNCTION auto const + offsets() const + { + return m_offsets; + } + + /** + * \brief Gets the internal offset array + */ + KOKKOS_INLINE_FUNCTION auto + offsets() + { + return m_offsets; + } + + // ============================================================================================== + // ============================================================================================== + // DataArrayBlock proxy functions + // ============================================================================================== + // ============================================================================================== + + /** + * \brief Creates host mirror + */ + static auto + create_host_mirror_view(DataArrayBlockMultiVar_t src) + { + return DataArrayBlockMultiVar( + Offsets_t::create_host_mirror_view(src.m_offsets), + DataArrayBlock_t::create_host_mirror_view(src.m_storage)); + } + + /** + * \brief Creates host mirror and copy offsets + */ + static auto + create_host_mirror_view_and_copy_offsets(DataArrayBlockMultiVar_t src) + { + return DataArrayBlockMultiVar( + Offsets_t::create_host_mirror_view_and_copy(src.m_offsets), + DataArrayBlock_t::create_host_mirror_view(src.m_storage)); + } + + /** + * \brief Creates host mirror and copy + */ + static auto + create_host_mirror_view_and_copy(DataArrayBlockMultiVar_t src) + { + return DataArrayBlockMultiVar( + Offsets_t::create_host_mirror_view_and_copy(src.m_offsets), + DataArrayBlock_t::create_host_mirror_view_and_copy(src.m_storage)); + } + + // ============================================================================================== + // ============================================================================================== + + KOKKOS_FORCEINLINE_FUNCTION bool + are_shape_and_size_equal() const + { + return m_storage.are_shape_and_size_equal(); + } + + /** + * \brief Reshapes the inner block + */ + KOKKOS_FORCEINLINE_FUNCTION void + reshape(block_size_t const & new_shape) + { + m_storage.reshape(new_shape); + } + + /** + * \brief Resets the inner block back to its original shape + */ + KOKKOS_FORCEINLINE_FUNCTION void + shape_reset() + { + m_storage.shape_reset(); + } + + // ============================================================================================== + // ============================================================================================== + + /** + * \brief Converts a logical multi-index (cell, var, oct) to a flat index storage + */ + template + KOKKOS_FORCEINLINE_FUNCTION auto + flat_index(ICell i_cell, IVar i_var, IOct i_oct) const + { + return m_storage.flat_index(i_cell, 0, flat_mv_block_index(i_var, i_oct, m_offsets)); + } + + /** + * \brief Converts a logical multi-index (i, j, var, oct) to a flat index storage + */ + template = true> + KOKKOS_FORCEINLINE_FUNCTION auto + flat_index(I0 i_0, I1 i_1, IVar i_var, IOct i_oct) const + { + return m_storage.flat_index(i_0, i_1, 0, flat_mv_block_index(i_var, i_oct, m_offsets)); + } + + /** + * \brief Converts a logical multi-index (i, j, k, var, oct) to a flat index storage + */ + template = true> + KOKKOS_FORCEINLINE_FUNCTION auto + flat_index(I0 i_0, I1 i_1, I2 i_2, IVar i_var, IOct i_oct) const + { + return m_storage.flat_index(i_0, i_1, i_2, 0, flat_mv_block_index(i_var, i_oct, m_offsets)); + } + + /** + * \brief Converts a flat index storage to a logical multi-index (i, j, [k,] var, oct) + */ + KOKKOS_FORCEINLINE_FUNCTION auto + flat_index_unravel(int64_t flat_index) const + { + auto indices = + ::kalypsso::flat_index_unravel(flat_index, m_storage.shape(), m_storage.num_vars()); + const auto octant_unraveled = flat_mv_block_index_unravel(indices[dim + 1], m_offsets); + indices[dim] = octant_unraveled[0]; + indices[dim + 1] = octant_unraveled[1]; + return indices; + } + + // ============================================================================================== + // ============================================================================================== + + /** + * \brief Memory access operator() with indices + */ + template = true> + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(I0 i_0, I1 i_1, IVar i_var, IOct i_oct) const + { +#ifdef KALYPSSO_CORE_DEBUG_BOUNDS_CHECK + KOKKOS_ASSERT((static_cast(i_oct) < m_offsets.size() - 1) && "Wrong value for i_oct"); + KOKKOS_ASSERT((static_cast(i_var) < num_vars(i_oct)) && "Wrong value for i_var"); +#endif + + return m_storage(i_0, i_1, 0, flat_mv_block_index(i_var, i_oct, m_offsets)); + } + + /** + * \brief Memory access operator() with indices + */ + template = true> + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(I0 i_0, I1 i_1, I2 i_2, IVar i_var, IOct i_oct) const + { +#ifdef KALYPSSO_CORE_DEBUG_BOUNDS_CHECK + KOKKOS_ASSERT((static_cast(i_oct) < m_offsets.size() - 1) && "Wrong value for i_oct"); + KOKKOS_ASSERT((static_cast(i_var) < num_vars(i_oct)) && "Wrong value for i_var"); +#endif + + return m_storage(i_0, i_1, i_2, 0, flat_mv_block_index(i_var, i_oct, m_offsets)); + } + + /** + * \brief Memory access operator() with cell index + */ + template + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(ICell i_cell, IVar i_var, IOct i_oct) const + { +#ifdef KALYPSSO_CORE_DEBUG_BOUNDS_CHECK + KOKKOS_ASSERT((static_cast(i_oct) < m_offsets.size() - 1) && "Wrong value for i_oct"); + KOKKOS_ASSERT((static_cast(i_var) < num_vars(i_oct)) && "Wrong value for i_var"); +#endif + + return m_storage(i_cell, 0, flat_mv_block_index(i_var, i_oct, m_offsets)); + } + + /** + * \brief Memory access operator() with cell index and block index + */ + template + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(ICell i_cell, IBlk i_blk) const + { +#ifdef KALYPSSO_CORE_DEBUG_BOUNDS_CHECK + KOKKOS_ASSERT((static_cast(i_blk) < m_storage.num_quadrants()) && + "Wrong value for i_blk"); +#endif + + return m_storage(i_cell, 0, i_blk); + } + + // ============================================================================================== + // ============================================================================================== + + KOKKOS_FORCEINLINE_FUNCTION bool + is_flux_array(block_size_t const & b_sizes, int dir) const + { + return m_storage.is_flux_array(b_sizes, dir); + } + + auto + label() const + { + return m_storage.label(); + } + + KOKKOS_FORCEINLINE_FUNCTION + auto const & + block_size() const + { + return m_storage.block_size(); + } + + KOKKOS_FORCEINLINE_FUNCTION + auto const & + shape() const + { + return m_storage.shape(); + } + + KOKKOS_FORCEINLINE_FUNCTION + auto + num_cells() const + { + return m_storage.num_cells(); + } + + template + KOKKOS_FORCEINLINE_FUNCTION auto + num_vars(IOct i_oct) const + { +#ifdef KALYPSSO_CORE_DEBUG_BOUNDS_CHECK + KOKKOS_ASSERT((static_cast(i_oct) < m_offsets.size() - 1) && "Wrong value for i_oct"); +#endif + + return static_cast(m_offsets(i_oct + 1) - m_offsets(i_oct)); + } + + KOKKOS_FORCEINLINE_FUNCTION + auto + num_quadrants() const + { + return static_cast(m_offsets.size()) - 1; + } + + KOKKOS_FORCEINLINE_FUNCTION + auto + num_blocks() const + { + return m_storage.num_quadrants(); + } + + auto + allocated_size_in_bytes() const + { + return m_offsets.size() * sizeof(typename Offsets_t::value_type) + + m_storage.allocated_size_in_bytes(); + } + + /** + * \brief Computes the offset view from an array of number of variables. It simply computes a + * postfix sum with the total sum at the end. + */ + int32_t + compute_offsets(const NumVars_t num_vars) + { + m_offsets.resize(num_vars.size() + 1); + + // avoid capturing class member in lambda + auto offsets = m_offsets; + + Kokkos::RangePolicy policy(0, m_offsets.size()); + uint32_t num_blocks = 0; + Kokkos::parallel_scan( + "kalypsso::DataArrayBlockMultiVar::offsets", + policy, + KOKKOS_LAMBDA(const uint32_t i_var, uint32_t & offset, const bool is_final) { + if (is_final) + offsets(i_var) = offset; + if (i_var != num_vars.size()) + offset += num_vars(i_var); + }, + num_blocks); + + return static_cast(num_blocks); + } + + /** + * \brief Computes the offset view from a MaterialPresenceView. A "number of variables per + * material" is still needed but it simply computes a postfix sum with the total sum at the end. + */ + int32_t + compute_offsets(const MatPresView_t mat_pres, const uint32_t num_vars_per_mat) + { + m_offsets.resize(mat_pres.size() + 1); + + // avoid capturing class member in lambda + auto offsets = m_offsets; + + Kokkos::RangePolicy policy(0, m_offsets.size()); + uint32_t num_blocks = 0; + Kokkos::parallel_scan( + "kalypsso::DataArrayBlockMultiVar::offsets", + policy, + KOKKOS_LAMBDA(const uint32_t i_var, uint32_t & offset, const bool is_final) { + if (is_final) + offsets(i_var) = offset; + if (i_var != mat_pres.size()) + offset += static_cast(mat_pres.num_materials(static_cast(i_var))) * + num_vars_per_mat; + }, + num_blocks); + + return static_cast(num_blocks); + } + +private: + //! Contains the actual data. Essentially, the handling of an octant's variables is done via the + //! m_offsets member. So this structure has a single variable per block and an index flattener + //! picks the correct block from the variable and octant indices. + DataArrayBlock_t m_storage; + + //! Octant's variables are located at block index [ m_offsets(i) : m_offsets(i+1) ). + Offsets_t m_offsets; + +}; // class DataArrayBlockMultiVar + +// ============================================================================================== +// ============================================================================================== +// ============================================================================================== +// ============================================================================================== +// ============================================================================================== + +// ============================================================================================== +// ============================================================================================== +// ============================================================================================== +// ============================================================================================== +// ============================================================================================== + +/** + * \class DataArrayGhostedBlockMultiVar + * + * \brief Wrapper around a DataArrayGhostedBlock that can handle multiple variables per octant + * + * \tparam dim Dimension of the block + * \tparam T Value of inner type + * \tparam device_t Location of the data + */ +template +class DataArrayGhostedBlockMultiVar +{ +public: + using Offsets_t = DataArray; + using NumVars_t = Kokkos::View; + using DataArrayGhostedBlock_t = DataArrayGhostedBlock; + using DataArrayGhostedBlockMultiVar_t = DataArrayGhostedBlockMultiVar; + using MatPresView_t = MaterialPresenceView; + + // ============================================================================================== + // ============================================================================================== + + //! Default constructor. Empty offset and empty storage + DataArrayGhostedBlockMultiVar() = default; + + /** + * \brief Constructs an empty DataArrayGhostedBlockMultiVar + * + * \param name Views' labels + * \param block_size Block size + * \param ghost_size Ghosted size + * \param shift Shift of original block + */ + DataArrayGhostedBlockMultiVar(const std::string & name, + const block_size_t block_size, + const block_size_t ghost_size, + const shift_t shift) + : m_storage(block_size, ghost_size, shift, name, 1, 0) + , m_offsets(name + " [offsets]", 0) + {} + + /** + * \brief Constructs a new DataArrayGhostedBlockMultiVar + * + * \param name Views' labels + * \param block_size Block size + * \param ghost_size Ghosted size + * \param shift Shift of original block + * \param num_vars Array indicating how many variables an octant has + */ + DataArrayGhostedBlockMultiVar(const std::string & name, + const block_size_t block_size, + const block_size_t ghost_size, + const shift_t shift, + const NumVars_t num_vars) + : DataArrayGhostedBlockMultiVar(name, block_size, ghost_size, shift) + { + reorganize(num_vars); + } + + //! Move constructor + DataArrayGhostedBlockMultiVar(Offsets_t && offsets, DataArrayGhostedBlock_t && storage) + : m_storage(storage) + , m_offsets(offsets) + {} + + // ============================================================================================== + // ============================================================================================== + + /** + * \brief Recomputes the offsets and resizes the internal storage. Replacement for `resize`. + */ + void + reorganize(const NumVars_t num_vars) + { + const auto num_blocks = compute_offsets(num_vars); + m_storage.resize(num_blocks); + } + + /** + * \brief Recomputes the offsets and resizes the internal storage from a 'material presence view'. + */ + void + reorganize(const MatPresView_t mat_pres, const uint32_t num_vars_per_mat) + { + const auto num_blocks = compute_offsets(mat_pres, num_vars_per_mat); + m_storage.resize(num_blocks); + } + + /** + * \brief Recomputes the offsets, resizes the internal storage and resets the data. Replacement + * for `resize_and_reset`. + */ + void + reorganize_and_reset(const NumVars_t num_vars) + { + const auto num_blocks = compute_offsets(num_vars); + m_storage.resize_and_reset(num_blocks); + } + + /** + * \brief Updates the object with another's offsets + */ + template + void + align_with(const DataArrayGhostedBlockMultiVar & other) + { + m_offsets = other.offsets(); + m_storage.resize(other.storage().num_quadrants()); + } + + // ============================================================================================== + // ============================================================================================== + + /** + * \brief Gets the internal DataArrayGhostedBlock + */ + KOKKOS_INLINE_FUNCTION auto const + storage() const + { + return m_storage; + } + + /** + * \brief Gets the internal DataArrayGhostedBlock + */ + KOKKOS_INLINE_FUNCTION auto + storage() + { + return m_storage; + } + + /** + * \brief Gets the internal DataArrayGhostedBlock + */ + KOKKOS_INLINE_FUNCTION auto & + storage_ref() + { + return m_storage; + } + + /** + * \brief Gets the internal offset array + */ + KOKKOS_INLINE_FUNCTION auto const + offsets() const + { + return m_offsets; + } + + /** + * \brief Gets the internal offset array + */ + KOKKOS_INLINE_FUNCTION auto + offsets() + { + return m_offsets; + } + + /** + * \brief Gets the internal offset array + */ + KOKKOS_INLINE_FUNCTION auto & + offsets_ref() + { + return m_offsets; + } + + // ============================================================================================== + // ============================================================================================== + // DataArrayGhostedBlock proxy functions + // ============================================================================================== + // ============================================================================================== + + /** + * \brief Reshapes the inner block + */ + KOKKOS_FORCEINLINE_FUNCTION void + reshape(block_size_t const & new_ghosted_size, shift_t const & new_shift) + { + m_storage.reshape(new_ghosted_size, new_shift); + } + + /** + * \brief Resets the inner block back to its original shape + */ + KOKKOS_FORCEINLINE_FUNCTION void + shape_reset() + { + m_storage.shape_reset(); + } + + // ============================================================================================== + // ============================================================================================== + + /** + * \brief Memory access operator() with indices + */ + template = true> + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(I0 i_0, I1 i_1, IVar i_var, IOct i_oct) const + { +#ifdef KALYPSSO_CORE_DEBUG_BOUNDS_CHECK + KOKKOS_ASSERT((static_cast(i_oct) < m_offsets.size() - 1) && "Wrong value for i_oct"); + KOKKOS_ASSERT((static_cast(i_var) < num_vars(i_oct)) && "Wrong value for i_var"); +#endif + + return m_storage(i_0, i_1, 0, flat_mv_block_index(i_var, i_oct, m_offsets)); + } + + /** + * \brief Memory access operator() with indices + */ + template = true> + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(I0 i_0, I1 i_1, I2 i_2, IVar i_var, IOct i_oct) const + { +#ifdef KALYPSSO_CORE_DEBUG_BOUNDS_CHECK + KOKKOS_ASSERT((static_cast(i_oct) < m_offsets.size() - 1) && "Wrong value for i_oct"); + KOKKOS_ASSERT((static_cast(i_var) < num_vars(i_oct)) && "Wrong value for i_var"); +#endif + + return m_storage(i_0, i_1, i_2, 0, flat_mv_block_index(i_var, i_oct, m_offsets)); + } + + /** + * \brief Memory access operator() with cell index + */ + template + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(ICell i_cell, IVar i_var, IOct i_oct) const + { +#ifdef KALYPSSO_CORE_DEBUG_BOUNDS_CHECK + KOKKOS_ASSERT((static_cast(i_oct) < m_offsets.size() - 1) && "Wrong value for i_oct"); + KOKKOS_ASSERT((static_cast(i_var) < num_vars(i_oct)) && "Wrong value for i_var"); +#endif + + return m_storage(i_cell, 0, flat_mv_block_index(i_var, i_oct, m_offsets)); + } + + /** + * \brief Memory access operator() with cell index and block index + */ + template + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(ICell i_cell, IBlk i_blk) const + { +#ifdef KALYPSSO_CORE_DEBUG_BOUNDS_CHECK + KOKKOS_ASSERT((static_cast(i_blk) < m_storage.num_quadrants()) && + "Wrong value for i_blk"); +#endif + + return m_storage(i_cell, 0, i_blk); + } + + // ============================================================================================== + // ============================================================================================== + + auto + label() const + { + return m_storage.label(); + } + + KOKKOS_FORCEINLINE_FUNCTION + auto const + block_size() const + { + return m_storage.block_size(); + } + + KOKKOS_FORCEINLINE_FUNCTION + auto const + ghost_block_size() const + { + return m_storage.allocated_ghosted_block_size(); + } + + KOKKOS_FORCEINLINE_FUNCTION + auto const + shape() const + { + return m_storage.shape(); + } + + KOKKOS_FORCEINLINE_FUNCTION + auto const + shift() const + { + return m_storage.shift(); + } + + KOKKOS_FORCEINLINE_FUNCTION + auto + num_cells() const + { + return m_storage.num_cells(); + } + + template + KOKKOS_FORCEINLINE_FUNCTION auto + num_vars(IOct i_oct) const + { +#ifdef KALYPSSO_CORE_DEBUG_BOUNDS_CHECK + KOKKOS_ASSERT((static_cast(i_oct) < m_offsets.size() - 1) && "Wrong value for i_oct"); +#endif + + return static_cast(m_offsets(i_oct + 1) - m_offsets(i_oct)); + } + + KOKKOS_FORCEINLINE_FUNCTION + auto + num_quadrants() const + { + return static_cast(m_offsets.size()) - 1; + } + + KOKKOS_FORCEINLINE_FUNCTION + auto + num_blocks() const + { + return m_storage.num_quadrants(); + } + + auto + allocated_size_in_bytes() const + { + return m_offsets.size() * sizeof(typename Offsets_t::value_type) + + m_storage.allocated_size_in_bytes(); + } + + /** + * \brief Computes the offset view from an array of number of variables. It simply computes a + * postfix sum with the total sum at the end. + */ + int32_t + compute_offsets(const NumVars_t num_vars) + { + m_offsets.resize(num_vars.size() + 1); + + // avoid capturing class member in lambda + auto offsets = m_offsets; + + Kokkos::RangePolicy policy(0, m_offsets.size()); + uint32_t num_blocks = 0; + Kokkos::parallel_scan( + "kalypsso::DataArrayBlockMultiVar::offsets", + policy, + KOKKOS_LAMBDA(const uint32_t i_var, uint32_t & offset, const bool is_final) { + if (is_final) + offsets(i_var) = offset; + if (i_var != num_vars.size()) + offset += num_vars(i_var); + }, + num_blocks); + + return static_cast(num_blocks); + } + + /** + * \brief Computes the offset view from a MaterialPresenceView. A "number of variables per + * material" is still needed but it simply computes a postfix sum with the total sum at the end. + */ + int32_t + compute_offsets(const MatPresView_t mat_pres, const uint32_t num_vars_per_mat) + { + m_offsets.resize(mat_pres.size() + 1); + + // avoid capturing class member in lambda + auto offsets = m_offsets; + + Kokkos::RangePolicy policy(0, m_offsets.size()); + uint32_t num_blocks = 0; + Kokkos::parallel_scan( + "kalypsso::DataArrayBlockMultiVar::offsets", + policy, + KOKKOS_LAMBDA(const uint32_t i_var, uint32_t & offset, const bool is_final) { + if (is_final) + offsets(i_var) = offset; + if (i_var != mat_pres.size()) + offset += static_cast(mat_pres.num_materials(static_cast(i_var))) * + num_vars_per_mat; + }, + num_blocks); + + return static_cast(num_blocks); + } + +private: + //! Contains the actual data. Essentially, the handling of an octant's variables is done via the + //! m_offsets member. So this structure has a single variable per block and an index flattener + //! picks the correct block from the variable and octant indices. + DataArrayGhostedBlock_t m_storage; + + //! Octant's variables are located at block index [ m_offsets(i) : m_offsets(i+1) ). + Offsets_t m_offsets; + +}; // class DataArrayGhostedBlockMultiVar + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_DATA_ARRAY_BLOCK_MULTI_VAR_H_ diff --git a/src/kalypsso/core/DataArrayBlock_utils.h b/src/kalypsso/core/DataArrayBlock_utils.h new file mode 100644 index 0000000..9906737 --- /dev/null +++ b/src/kalypsso/core/DataArrayBlock_utils.h @@ -0,0 +1,409 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file DataArrayBlock_utils.h + */ +#ifndef KALYPSSO_CORE_DATAARRAYBLOCK_UTILS_H_ +#define KALYPSSO_CORE_DATAARRAYBLOCK_UTILS_H_ + +#include +#include // for KOKKOS_ENABLE_XXX + +#include +#include +#include + +#include // for definition of type block_size_t and shift_t +#include + +namespace kalypsso +{ + +// ============================================================================================ +// ============================================================================================ +/** + * Compute the number of cells that are at surface of a block of cells in a given face. + * + * \param[in] bSize Sizes of a block of cells. + * + * \tparam[in] dim Dimension (2 or 3). + * \tparam[in] dir Normal direction (identifying a face). + * + * \return the number of surface cells. + */ +template +KOKKOS_FORCEINLINE_FUNCTION int32_t +get_number_of_cells_on_face(block_size_t const & bSize) +{ + if constexpr (dim == 2) + { + if constexpr (dir == IX) + return bSize[IY]; + else if constexpr (dir == IY) + return bSize[IX]; + } + else if constexpr (dim == 3) + { + if constexpr (dir == IX) + { + return bSize[IY] * bSize[IZ]; + } + else if constexpr (dir == IY) + { + return bSize[IZ] * bSize[IX]; + } + else if constexpr (dir == IZ) + { + return bSize[IX] * bSize[IY]; + } + } +} // get_number_of_cells_on_face + +// ============================================================================================ +// ============================================================================================ +/** + * Compute the number of cells that are at surface of a block of cells. + * + * \param[in] bSize Sizes of a block of cells. + * + * \tparam[in] dim Dimension (2 or 3). + * + * \return the number of surface cells. + */ +template +KOKKOS_FORCEINLINE_FUNCTION int32_t +get_number_of_surface_cells(block_size_t const & bSize) +{ + if constexpr (dim == 2) + { + return 2 * (get_number_of_cells_on_face(bSize) + + get_number_of_cells_on_face(bSize)); + } + else if constexpr (dim == 3) + { + return 2 * (get_number_of_cells_on_face(bSize) + + get_number_of_cells_on_face(bSize) + + get_number_of_cells_on_face(bSize)); + } +} // get_number_of_surface_cells + +// ============================================================================================ +// ============================================================================================ +/** + * Unravel cell indexes from a flat index corresponding to a "surface" cell (i.e. a cell at the + * border of a block of cells). + * + * \param[in] flat_index A surface cell flat index. + * \param[in] bSize Sizes of a block of cells. + * + * \tparam[in] dim Dimension (2 or 3). + * + * \return coordinates of the surface cell identified by its flat index. + */ +template +KOKKOS_FORCEINLINE_FUNCTION coord_t +surface_flatindex_unravel_to_cell_ijk(int32_t flat_index, block_size_t const & bSize) +{ + KOKKOS_ASSERT(flat_index >= 0 and flat_index < get_number_of_surface_cells(bSize)); + + coord_t res; + + if constexpr (dim == 2) + { + if (flat_index < 2 * get_number_of_cells_on_face<2, IX>(bSize)) + { + bool is_left = flat_index < get_number_of_cells_on_face<2, IX>(bSize); + + res[IX] = is_left ? 0 : bSize[IX] - 1; + res[IY] = is_left ? flat_index : flat_index - get_number_of_cells_on_face<2, IX>(bSize); + } + else + { + flat_index -= 2 * get_number_of_cells_on_face<2, IX>(bSize); + + bool is_left = flat_index < get_number_of_cells_on_face<2, IY>(bSize); + + res[IX] = is_left ? flat_index : flat_index - get_number_of_cells_on_face<2, IY>(bSize); + res[IY] = is_left ? 0 : bSize[IY] - 1; + } + } // dim == 2 + else if constexpr (dim == 3) + { + if (flat_index < 2 * get_number_of_cells_on_face<3, IX>(bSize)) + { + bool is_left = flat_index < get_number_of_cells_on_face<3, IX>(bSize); + + if (!is_left) + flat_index -= get_number_of_cells_on_face<3, IX>(bSize); + + res[IZ] = flat_index / bSize[IY]; + res[IY] = flat_index - bSize[IY] * res[IZ]; + res[IX] = is_left ? 0 : bSize[IX] - 1; + } + else if (flat_index < 2 * (get_number_of_cells_on_face<3, IX>(bSize) + + get_number_of_cells_on_face<3, IY>(bSize))) + { + flat_index -= 2 * get_number_of_cells_on_face<3, IX>(bSize); + + bool is_left = flat_index < get_number_of_cells_on_face<3, IY>(bSize); + + if (!is_left) + flat_index -= get_number_of_cells_on_face<3, IY>(bSize); + + res[IZ] = flat_index / bSize[IX]; + res[IX] = flat_index - bSize[IX] * res[IZ]; + res[IY] = is_left ? 0 : bSize[IY] - 1; + } + else + { + flat_index -= + 2 * (get_number_of_cells_on_face<3, IX>(bSize) + get_number_of_cells_on_face<3, IY>(bSize)); + + bool is_left = flat_index < get_number_of_cells_on_face<3, IZ>(bSize); + + if (!is_left) + flat_index -= get_number_of_cells_on_face<3, IZ>(bSize); + + res[IY] = flat_index / bSize[IX]; + res[IX] = flat_index - bSize[IX] * res[IY]; + res[IZ] = is_left ? 0 : bSize[IZ] - 1; + } + } // dim == 3 + + return res; + +} // surface_flatindex_unravel_to_cell_ijk + +// ============================================================================================ +// ============================================================================================ +/** + * Unravel cell indexes from a flat index corresponding to a "surface" cell (i.e. a cell at the + * border of a block of cells). + * + * \param[in] flat_index A surface cell flat index. + * \param[in] bSize Sizes of a block of cells. + * + * \tparam[in] dim Dimension (2 or 3). + * + * \return face multi-index identified by a given flat index. + */ +template +KOKKOS_FORCEINLINE_FUNCTION face_multiindex_t +surface_flatindex_unravel_to_face_ijk(int32_t flat_index, block_size_t const & bSize) +{ + KOKKOS_ASSERT(flat_index >= 0 and flat_index < get_number_of_surface_cells(bSize)); + + face_multiindex_t res; + + if constexpr (dim == 2) + { + if (flat_index < 2 * get_number_of_cells_on_face<2, IX>(bSize)) + { + bool is_left = flat_index < get_number_of_cells_on_face<2, IX>(bSize); + + res[IX] = is_left ? 0 : bSize[IX]; + res[IY] = is_left ? flat_index : flat_index - get_number_of_cells_on_face<2, IX>(bSize); + res[dim] = IX; + } + else + { + flat_index -= 2 * get_number_of_cells_on_face<2, IX>(bSize); + + bool is_left = flat_index < get_number_of_cells_on_face<2, IY>(bSize); + + res[IX] = is_left ? flat_index : flat_index - get_number_of_cells_on_face<2, IY>(bSize); + res[IY] = is_left ? 0 : bSize[IY]; + res[dim] = IY; + } + } // dim == 2 + else if constexpr (dim == 3) + { + if (flat_index < 2 * get_number_of_cells_on_face<3, IX>(bSize)) + { + bool is_left = flat_index < get_number_of_cells_on_face<3, IX>(bSize); + + if (!is_left) + flat_index -= get_number_of_cells_on_face<3, IX>(bSize); + + res[IZ] = flat_index / bSize[IY]; + res[IY] = flat_index - bSize[IY] * res[IZ]; + res[IX] = is_left ? 0 : bSize[IX]; + res[dim] = IX; + } + else if (flat_index < 2 * (get_number_of_cells_on_face<3, IX>(bSize) + + get_number_of_cells_on_face<3, IY>(bSize))) + { + flat_index -= 2 * get_number_of_cells_on_face<3, IX>(bSize); + + bool is_left = flat_index < get_number_of_cells_on_face<3, IY>(bSize); + + if (!is_left) + flat_index -= get_number_of_cells_on_face<3, IY>(bSize); + + res[IZ] = flat_index / bSize[IX]; + res[IX] = flat_index - bSize[IX] * res[IZ]; + res[IY] = is_left ? 0 : bSize[IY]; + res[dim] = IY; + } + else + { + flat_index -= + 2 * (get_number_of_cells_on_face<3, IX>(bSize) + get_number_of_cells_on_face<3, IY>(bSize)); + + bool is_left = flat_index < get_number_of_cells_on_face<3, IZ>(bSize); + + if (!is_left) + flat_index -= get_number_of_cells_on_face<3, IZ>(bSize); + + res[IY] = flat_index / bSize[IX]; + res[IX] = flat_index - bSize[IX] * res[IY]; + res[IZ] = is_left ? 0 : bSize[IZ]; + res[dim] = IZ; + } + } // dim == 3 + + return res; + +} // surface_flatindex_unravel_to_face_ijk + +// ============================================================================================ +// ============================================================================================ +/** + * Compute face normal vector from a surface cell indexes. + * + * \param[in] flat_index A surface cell flat index. + * \param[in] bSize Sizes of a block of cells. + * + * \tparam[in] dim Dimension (2 or 3). + * + * \return coordinates of the normal vector to a block of cells face at a given cell identified by + * its flat index. + */ +template +KOKKOS_FORCEINLINE_FUNCTION coord_t +surface_flatindex_to_normal_vector(int32_t flat_index, block_size_t const & bSize) +{ + KOKKOS_ASSERT(flat_index >= 0 and flat_index < get_number_of_surface_cells(bSize)); + + coord_t res; + res[IX] = 0; + res[IY] = 0; + if constexpr (dim == 3) + res[IZ] = 0; + + if (flat_index < get_number_of_cells_on_face(bSize)) + { + res[IX] = -1; + } + else if (flat_index < 2 * get_number_of_cells_on_face(bSize)) + { + res[IX] = 1; + } + else if (flat_index < 2 * get_number_of_cells_on_face(bSize) + + get_number_of_cells_on_face(bSize)) + { + res[IY] = -1; + } + else if (flat_index < 2 * get_number_of_cells_on_face(bSize) + + 2 * get_number_of_cells_on_face(bSize)) + { + res[IY] = 1; + } + else + { + if constexpr (dim == 3) + { + flat_index -= 2 * get_number_of_cells_on_face(bSize) + + 2 * get_number_of_cells_on_face(bSize); + + if (flat_index < get_number_of_cells_on_face(bSize)) + { + res[IZ] = -1; + } + else + { + res[IZ] = 1; + } + } + } + + return res; + +} // surface_flatindex_to_normal_vector + +// ============================================================================================ +// ============================================================================================ +/** + * Compute face normal vector from a surface cell indexes. + * + * \param[in] flat_index A surface cell flat index. + * \param[in] bSize Sizes of a block of cells. + * + * \tparam[in] dim Dimension (2 or 3). + * + * \return coordinates of the normal vector to a block of cells face at a given cell identified by + * its flat index. + */ +template +KOKKOS_FORCEINLINE_FUNCTION face_multiindex_t +surface_flatindex_to_face_multiindex(int32_t flat_index, block_size_t const & bSize) +{ + KOKKOS_ASSERT(flat_index >= 0 and flat_index < get_number_of_surface_cells(bSize)); + + const auto ijk = surface_flatindex_unravel_to_cell_ijk(flat_index, bSize); + + face_multiindex_t res; + res[IX] = ijk[IX]; + res[IY] = ijk[IY]; + if constexpr (dim == 3) + res[IZ] = ijk[IZ]; + res[dim] = IX; // default value to avoid uninitialized value warning (in 2d) + + if (flat_index < get_number_of_cells_on_face(bSize)) + { + res[dim] = IX; + } + else if (flat_index < 2 * get_number_of_cells_on_face(bSize)) + { + res[IX] += 1; + res[dim] = IX; + } + else if (flat_index < 2 * get_number_of_cells_on_face(bSize) + + get_number_of_cells_on_face(bSize)) + { + res[dim] = IY; + } + else if (flat_index < 2 * get_number_of_cells_on_face(bSize) + + 2 * get_number_of_cells_on_face(bSize)) + { + res[IY] += 1; + res[dim] = IY; + } + else + { + if constexpr (dim == 3) + { + flat_index -= 2 * get_number_of_cells_on_face(bSize) + + 2 * get_number_of_cells_on_face(bSize); + + if (flat_index < get_number_of_cells_on_face(bSize)) + { + res[dim] = IZ; + } + else + { + res[IZ] += 1; + res[dim] = IZ; + } + } + } + + return res; + +} // surface_flatindex_to_face_multiindex + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_DATAARRAYBLOCK_UTILS_H_ diff --git a/src/kalypsso/core/DataArrayGhostedBlock.h b/src/kalypsso/core/DataArrayGhostedBlock.h new file mode 100644 index 0000000..0bc4af1 --- /dev/null +++ b/src/kalypsso/core/DataArrayGhostedBlock.h @@ -0,0 +1,682 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file DataArrayGhostedBlock.h + */ +#ifndef KALYPSSO_CORE_DATAARRAYGHOSTEDBLOCK_H_ +#define KALYPSSO_CORE_DATAARRAYGHOSTEDBLOCK_H_ + +#include +#include // for KOKKOS_ENABLE_XXX + +#include +#include +#include + +#include // for definition of type block_size_t +#include +#include // for the definition of shift_t + +#include + +#include + +namespace kalypsso +{ + +// ============================================================================================ +// ============================================================================================ +// ============================================================================================ +/** + * \class DataArrayGhostedBlock is data container similar to DataArrayBlock for storing user data + * where each block may contain ghost cells. + * + * For each original block of cells from a DataArrayBlock, we associate a new block of cells (that + * may be larger, same size, smaller than the original block, and shifted in space). For example, + * when the shift is negative along the x axis, it means the shifted block of cells will overlap + * with a neighbor block, and we will need to either copy, prolongate or restrict data from the + * neighbor block. This copy/interpolation operation is the job of class FillBlockGhostCells. + * + * Here we only design a container that we ease the job of FillBlockGhostCells. That is why we store + * the block size (of the original block of cells, the one use in our AMR mesh), and also the block + * size of the shifted block, as well as the vector shift. One important constraint we enforce here + * is that the shift (space displacement) can't larger than the block size divided by two in each + * direction. This constraint will ensure that we will be able to fill ghost cells using data from + * the adjacent block cells, where the worst case is when the neighbor block is finer than current + * one. + * + * A typical practical situation is when one want to apply a stencil operator to a DataArrayBlock, + * e.g. compute a centered estimation of a gradient: \f$ \partial_x f \simeq + * \frac{f(i-1,j)-f(i+1,j)}{2\delta_x} \f$. When visiting a cell at block border, one needs data + * from a cell that belongs to a neighbor block which may live at finer, same or coarser AMR level. + * Usually the stencil size (data access pattern) is known in advance (by design), so here we + * provide, as a helper class, a data container where all block (associated to an octant) may be + * equipped with ghost cells; ghost cells overlap neighbor blocks, so ghost cells data are filled + * with either a prolongation, restriction or copy operator. + * + * Simple example where the ghosted block is made with one ghost cell all around. + * + * ________________________ + * | | | | | | | + * |___|___|___|___|___|___| + * | | x | x | x | x | | + * |___|___|___|___|___|___| + * | | x | x | x | x | | + * |___|___|___|___|___|___| + * | | x | x | x | x | | + * |___|___|___|___|___|___| + * | |x0 | x | x | x | | + * |___|___|___|___|___|___| + * | X0| | | | | | + * |___|___|___|___|___|___| + * + * In the more general case, we may need to have only ghost cells along the x-axis, or the y-axis. + * The ghosted block is seen as a block of cells that has a different block size compared to the + * original non-ghosted block size, and that is shifted in space. + * + * So this class is designed so that the user specifies: + * - a space shift (in number of cell units), it may be positive or negative + * - a block size: it may be smaller of larger than the original block size + * + * We provide two ways to access cell data: + * - either by using signed integers coordinates that are relative to the lower left cell of the + * original non-ghosted block (see cell marked with label "x0" in the drawing above) + * - either by using positive integers coordinates that relative to the lower left cell of the + * ghosted block (see label "X0"); this is called "direct access" in the class implementation. + * + * \note the ghost width on a given side can't be larger than block width divided by two. This + * constraint is necessary for AMR: indeed when filling ghost cells, we need to access data from + * neighbor block, and if ghost width where larger than block width over two, then we would need to + * access data from neighbor block that are not direct neighbor but neighbor of neighbor which is + * not currently possible with p4est, nor desirable (the neighbor of neighbor may has 2 AMR level + * difference with current block level.) + */ +template +class DataArrayGhostedBlock +{ +public: + using DataArrayBlock_t = DataArrayBlock; + + //! tag dispatch class to use when using non-signed index coordinates (i.e. relative to the + //! shifted block origin); when using direct access, valid index must be in the following ranges: + //! i in [0, m_gSize[IX]-1] + //! j in [0, m_gSize[IY]-1] + //! k in [0, m_gSize[IZ]-1] + //! + //! \note if you reshape a DataArrayGhostedBlock, valid index are constraint by m_data.shape() + struct DirectAccess + {}; + +private: + //! non-ghosted block size + block_size_t m_bSize; + + //! space shift + shift_t m_shift; + + //! storage array + DataArrayBlock_t m_data; + + //! overlapping block size : sizes of the zones where the shifted block and the original block + //! overlaps + block_size_t m_bSize_overlap; + +public: + DataArrayGhostedBlock() = default; + + //! DataArrayGhostedBlock constructor. + //! + //! \param[in] bSize is the non-ghosted block size. + //! \param[in] gSize is the size of the ghosted block (used for memory allocation) + //! \param[in] shift is the vector of shift (in unit signed number of cell) to locate the origin + //! of the shifted ghosted block + //! \param[in] name is the ghosted block label + //! \param[in] num_vars is the number of scalar variables + //! \param[in] num_quadrants is the number of quadrants(2d) or octants(3d) + DataArrayGhostedBlock(block_size_t bSize, + block_size_t gSize, + shift_t shift, + std::string name, + int num_vars, + int num_quadrants) + : m_bSize(bSize) + , m_shift(shift) + , m_data(name, gSize, num_vars, num_quadrants) + , m_bSize_overlap(get_block_size_overlap(gSize, shift)) + { + KOKKOS_ASSERT((shift[IX] + bSize[IX] / 2 >= 0) && "shift[IX] is too large"); + KOKKOS_ASSERT((shift[IY] + bSize[IY] / 2 >= 0) && "shift[IY] is too large"); + if constexpr (dim == 3) + { + KOKKOS_ASSERT((shift[IZ] + bSize[IZ] / 2 >= 0) && "shift[IZ] is too large"); + } + + KOKKOS_ASSERT((m_data.block_size()[IX] <= 2 * bSize[IX]) && + "m_data.block_size()[IX] is too large"); + KOKKOS_ASSERT((m_data.block_size()[IY] <= 2 * bSize[IY]) && + "m_data.block_size()[IY] is too large"); + if constexpr (dim == 3) + { + KOKKOS_ASSERT((m_data.block_size()[IZ] <= 2 * bSize[IZ]) && + "m_data.block_size()[IZ] is too large"); + } + } + + auto + flat_view() + { + return m_data.logical_view(); + } + + auto & + flat_view_ref() + { + return m_data.logical_view(); + } + + auto + data() + { + return m_data; + } + + auto & + data_ref() + { + return m_data; + } + + // ================================================================================== + /** + * Default memory access operator() - 2d case. + * + * \param[in] i0 cell coordinate inside block/octant along X axis + * \param[in] i1 cell coordinate inside block/octant along Y axis + * \param[in] ivar variable index + * \param[in] iOct octant/block index + * + * \note index i0,i1,i2 must be coordinates relative to the non-ghosted block (signed values) + */ + template = true> + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(I0 i0, I1 i1, IVar ivar, IOct iOct) const + { + KOKKOS_ASSERT((i0 - m_shift[IX] >= 0) && "Invalid index value"); + KOKKOS_ASSERT((i1 - m_shift[IY] >= 0) && "Invalid index value"); + KOKKOS_ASSERT((i0 - m_shift[IX] < static_cast(m_data.shape()[IX])) && + "Invalid index value"); + KOKKOS_ASSERT((i1 - m_shift[IY] < static_cast(m_data.shape()[IY])) && + "Invalid index value"); + + KOKKOS_ASSERT((ivar >= 0) && "invalid ivar index value"); + KOKKOS_ASSERT((ivar < m_data.num_vars()) && "invalid ivar index value"); + + KOKKOS_ASSERT((iOct >= 0) && "invalid iOct index value"); + KOKKOS_ASSERT((iOct < m_data.num_quadrants()) && "invalid iOct index value"); + + return m_data(i0 - m_shift[IX], i1 - m_shift[IY], ivar, iOct); + } + + // ================================================================================== + /** + * Direct memory access operator() - 2d case. + * + * \param[in] i0 cell coordinate inside block/octant along X axis + * \param[in] i1 cell coordinate inside block/octant along Y axis + * \param[in] ivar variable index + * \param[in] iOct octant/block index + * + * \note index i0,i1,i2 must be coordinates relative to the ghosted block (positive values) + */ + template = true> + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(I0 i0, I1 i1, IVar ivar, IOct iOct, DirectAccess const &) const + { + KOKKOS_ASSERT((i0 >= 0) && "Invalid index value"); + KOKKOS_ASSERT((i1 >= 0) && "Invalid index value"); + KOKKOS_ASSERT((i0 < m_data.shape()[IX]) && "Invalid index value"); + KOKKOS_ASSERT((i1 < m_data.shape()[IY]) && "Invalid index value"); + + KOKKOS_ASSERT((ivar >= 0) && "invalid ivar index value"); + KOKKOS_ASSERT((ivar < m_data.num_vars()) && "invalid ivar index value"); + + KOKKOS_ASSERT((iOct >= 0) && "invalid iOct index value"); + KOKKOS_ASSERT((iOct < m_data.num_quadrants()) && "invalid iOct index value"); + + return m_data(i0, i1, ivar, iOct); + } + + // ================================================================================== + /** + * Default memory access operator() - 3d case. + * + * \param[in] i0 cell coordinate inside block/octant along X axis + * \param[in] i1 cell coordinate inside block/octant along Y axis + * \param[in] i2 cell coordinate inside block/octant along Z axis + * \param[in] ivar variable index + * \param[in] iOct octant/block index + * + * \note index i0,i1,i2 must be coordinates relative to the non-ghosted block (signed values) + */ + template = true> + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(I0 i0, I1 i1, I2 i2, IVar ivar, IOct iOct) const + { + KOKKOS_ASSERT((i0 - m_shift[IX] >= 0) && "Invalid index value"); + KOKKOS_ASSERT((i1 - m_shift[IY] >= 0) && "Invalid index value"); + KOKKOS_ASSERT((i2 - m_shift[IZ] >= 0) && "Invalid index value"); + KOKKOS_ASSERT((i0 - m_shift[IX] < m_data.shape()[IX]) && "Invalid index value"); + KOKKOS_ASSERT((i1 - m_shift[IY] < m_data.shape()[IY]) && "Invalid index value"); + KOKKOS_ASSERT((i2 - m_shift[IZ] < m_data.shape()[IZ]) && "Invalid index value"); + + KOKKOS_ASSERT((ivar >= 0) && "invalid ivar index value"); + KOKKOS_ASSERT((ivar < m_data.num_vars()) && "invalid ivar index value"); + + KOKKOS_ASSERT((iOct >= 0) && "invalid iOct index value"); + KOKKOS_ASSERT((iOct < m_data.num_quadrants()) && "invalid iOct index value"); + + return m_data(i0 - m_shift[IX], i1 - m_shift[IY], i2 - m_shift[IZ], ivar, iOct); + } + + // ================================================================================== + /** + * Direct memory access operator() - 3d case. + * + * \param[in] i0 cell coordinate inside block/octant along X axis + * \param[in] i1 cell coordinate inside block/octant along Y axis + * \param[in] i2 cell coordinate inside block/octant along Z axis + * \param[in] ivar variable index + * \param[in] iOct octant/block index + * + * \note index i0,i1,i2 must be coordinates relative to the ghosted block (positive values) + */ + template = true> + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(I0 i0, I1 i1, I2 i2, IVar ivar, IOct iOct, DirectAccess const &) const + { + KOKKOS_ASSERT((i0 >= 0) && "Invalid index value"); + KOKKOS_ASSERT((i1 >= 0) && "Invalid index value"); + KOKKOS_ASSERT((i2 >= 0) && "Invalid index value"); + KOKKOS_ASSERT((i0 < m_data.shape()[IX]) && "Invalid index value"); + KOKKOS_ASSERT((i1 < m_data.shape()[IY]) && "Invalid index value"); + KOKKOS_ASSERT((i2 < m_data.shape()[IZ]) && "Invalid index value"); + + KOKKOS_ASSERT((ivar >= 0) && "invalid ivar index value"); + KOKKOS_ASSERT((ivar < m_data.num_vars()) && "invalid ivar index value"); + + KOKKOS_ASSERT((iOct >= 0) && "invalid iOct index value"); + KOKKOS_ASSERT((iOct < m_data.num_quadrants()) && "invalid iOct index value"); + + return m_data(i0, i1, i2, ivar, iOct); + } + + // ================================================================================== + /** + * Memory access operator() using a flat cell index (2d and 3d). + * + * \param[in] icell cell index inside block/octant + * \param[in] ivar variable index + * \param[in] iOct octant/block index + * + * \note cell index icell must have been computed using coordinates relative to the ghosted block + * (positive values). + */ + template + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(ICell icell, IVar ivar, IOct iOct) const + { + return m_data(icell, ivar, iOct); + } + + // ================================================================================== + /** Memory access operator() using vector of coordinates. + * + * \param[in] ijk cell coordinates vector identifyin a cell inside block/octant + * \param[in] ivar variable index + * \param[in] iOct octant/block index + * + * \note indexes ijk[IX],...,ijk[dim-1] must be coordinates relative to the non-ghosted block + * (signed values) + */ + template + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(coord_t ijk, IVar ivar, IOct iOct) const + { + KOKKOS_ASSERT((ijk[IX] - m_shift[IX] >= 0) && "Invalid index value"); + KOKKOS_ASSERT((ijk[IY] - m_shift[IY] >= 0) && "Invalid index value"); + if constexpr (dim == 3) + { + KOKKOS_ASSERT((ijk[IZ] - m_shift[IZ] >= 0) && "Invalid index value"); + } + KOKKOS_ASSERT((ijk[IX] - m_shift[IX] < m_data.shape()[IX]) && "Invalid index value"); + KOKKOS_ASSERT((ijk[IY] - m_shift[IY] < m_data.shape()[IY]) && "Invalid index value"); + if constexpr (dim == 3) + { + KOKKOS_ASSERT((ijk[IZ] - m_shift[IZ] < m_data.shape()[IZ]) && "Invalid index value"); + } + + if constexpr (dim == 2) + { + return this->operator()(ijk[IX], ijk[IY], ivar, iOct); + } + else if constexpr (dim == 3) + { + return this->operator()(ijk[IX], ijk[IY], ijk[IZ], ivar, iOct); + } + } + + // ================================================================================== + /** Direct memory access operator() using vector of coordinates. + * + * \param[in] ijk cell coordinates vector identifyin a cell inside block/octant + * \param[in] ivar variable index + * \param[in] iOct octant/block index + * + * \note indexes ijk[IX],...,ijk[dim-1] must be coordinates relative to the ghosted block + * (positive values) + */ + template + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(coord_t ijk, IVar ivar, IOct iOct, DirectAccess const &) const + { + KOKKOS_ASSERT((ijk[IX] >= 0) && "Invalid index value"); + KOKKOS_ASSERT((ijk[IY] >= 0) && "Invalid index value"); + if constexpr (dim == 3) + { + KOKKOS_ASSERT((ijk[IZ] >= 0) && "Invalid index value"); + } + KOKKOS_ASSERT((ijk[IX] < m_data.shape()[IX]) && "Invalid index value"); + KOKKOS_ASSERT((ijk[IY] < m_data.shape()[IY]) && "Invalid index value"); + if constexpr (dim == 3) + { + KOKKOS_ASSERT((ijk[IZ] < m_data.shape()[IZ]) && "Invalid index value"); + } + + if constexpr (dim == 2) + { + return this->operator()(ijk[IX], ijk[IY], ivar, iOct, DirectAccess{}); + } + else if constexpr (dim == 3) + { + return this->operator()(ijk[IX], ijk[IY], ijk[IZ], ivar, iOct, DirectAccess{}); + } + } + + // ================================================================================== + auto + label() const + { + return m_data.label(); + } + + //! Reallocate by changing the number of quadrants/octants (do not initialize memory). + void + resize(int32_t num_quads) + { + m_data.resize(num_quads); + } + + //! Reallocate by changing the number of quadrants/octants and reset memory. + void + resize_and_reset(int32_t num_quads) + { + m_data.resize_and_reset(num_quads); + } + + /** + * Change ghosted block size, and shift without reallocating. + * + * We only require that the new shape corresponds to a smaller number of cells. + * + * \return true when the new shape is accepted (i.e. compatible with current memory allocation) + */ + KOKKOS_FORCEINLINE_FUNCTION bool + reshape(block_size_t const & new_ghosted_size, shift_t const & new_shift) + { + // when reshaping, as we don't want to reallocate, we need to make sure the new + // ghosted size is not larger than current allocation + + // only reshape when possible + if (Kokkos::dim_prod(new_ghosted_size) <= Kokkos::dim_prod(m_data.block_size())) + { + + m_data.reshape(new_ghosted_size); + + // just make sure the new shift is valid + KOKKOS_ASSERT((new_shift[IX] + m_bSize[IX] / 2 >= 0) && "shift[IX] is too large (negative)"); + KOKKOS_ASSERT((new_shift[IY] + m_bSize[IY] / 2 >= 0) && "shift[IY] is too large (negative)"); + if constexpr (dim == 3) + { + KOKKOS_ASSERT((new_shift[IZ] + m_bSize[IZ] / 2 >= 0) && + "shift[IZ] is too large (negative)"); + } + + KOKKOS_ASSERT((new_shift[IX] <= m_bSize[IX] / 2) && "shift[IX] is too large"); + KOKKOS_ASSERT((new_shift[IY] <= m_bSize[IY] / 2) && "shift[IY] is too large"); + if constexpr (dim == 3) + { + KOKKOS_ASSERT((new_shift[IZ] <= m_bSize[IZ] / 2) && "shift[IZ] is too large"); + } + + // update shift + m_shift = new_shift; + + // update block size of the overlapping zone + m_bSize_overlap = get_block_size_overlap(new_ghosted_size, new_shift); + + return true; + } + + // reshaping can't happen, don't modify neither shape, neither shift + return false; + + } // reshape + + /** + * Change ghosted block size, inner size and shift without reallocating. + * + * We only require that the new shape corresponds to a smaller number of cells. + * + * This variant of reshape is only useful for the corner case when + * - the inner block is actually a flux array (i.e. slightly larger is in one direction) + * - reshaping need the reshape both the inner shape and ghosted shape + * + * \return true when the new shape is accepted (i.e. compatible with current memory allocation) + */ + KOKKOS_FORCEINLINE_FUNCTION bool + reshape(block_size_t const & new_inner_size, + block_size_t const & new_ghosted_size, + shift_t const & new_shift) + { + auto bSize_tmp = m_bSize; + m_bSize = new_inner_size; + + auto valid = reshape(new_ghosted_size, new_shift); + + if (!valid) + { + // restore old bSize + m_bSize = bSize_tmp; + } + return valid; + } // reshape + + //! reset ghosted block shape to the original one used for memory allocation + //! TODO: does this necessarily need to be available on device ? + KOKKOS_FORCEINLINE_FUNCTION void + shape_reset() + { + m_data.shape_reset(); + } + + //! Return total number of cells of the ghosted block. + KOKKOS_FORCEINLINE_FUNCTION + auto + num_cells() const + { + return m_data.num_cells(); + } + + //! Return total number of cells of the original non-ghosted block. + KOKKOS_FORCEINLINE_FUNCTION + auto + num_cells_inner() const + { + return Kokkos::dim_prod(m_bSize); + } + + //! Return the number of scalar variables. + KOKKOS_FORCEINLINE_FUNCTION + auto + num_vars() const + { + return m_data.num_vars(); + } + + //! Return the number of quadrants/octants. + KOKKOS_FORCEINLINE_FUNCTION + auto + num_quadrants() const + { + return m_data.num_quadrants(); + } + + //! Return the block size (shape) of the non-ghosted block. + KOKKOS_FORCEINLINE_FUNCTION + auto + block_size() const + { + return m_bSize; + } + + //! return the shifted block size as used in allocation by constructor; this won't are reshaping + //! are only allowed using a smaller shape + auto + allocated_ghosted_block_size() const + { + return m_data.block_size(); + } + + //! return the block sizes of the shifted block + KOKKOS_FORCEINLINE_FUNCTION + auto + ghosted_block_size() const + { + return m_data.shape(); + } + + //! return the block sizes of the shifted block (same as ghosted_block_size) + KOKKOS_FORCEINLINE_FUNCTION + auto + shape() const + { + return m_data.shape(); + } + + //! return the shift (space displacement) + KOKKOS_FORCEINLINE_FUNCTION + auto + shift() const + { + return m_shift; + } + + //! return block size of the block of cells where original block and shifted block + //! overlap + KOKKOS_FORCEINLINE_FUNCTION + auto + get_block_size_overlap() const + { + return m_bSize_overlap; + } + + //! compute and return block size of the block of cells where original block and shifted block + //! overlap + KOKKOS_FORCEINLINE_FUNCTION + auto + get_block_size_overlap(block_size_t ghosted_size, shift_t shift) const + { + block_size_t bSize_overlap; + + coord_t start; + start[IX] = shift[IX] < 0 ? 0 : shift[IX]; + start[IY] = shift[IY] < 0 ? 0 : shift[IY]; + if constexpr (dim == 3) + { + start[IZ] = shift[IZ] < 0 ? 0 : shift[IZ]; + } + + coord_t end; + end[IX] = + shift[IX] + ghosted_size[IX] >= m_bSize[IX] ? m_bSize[IX] : shift[IX] + ghosted_size[IX]; + end[IY] = + shift[IY] + ghosted_size[IY] >= m_bSize[IY] ? m_bSize[IY] : shift[IY] + ghosted_size[IY]; + if constexpr (dim == 3) + { + end[IZ] = + shift[IZ] + ghosted_size[IZ] >= m_bSize[IZ] ? m_bSize[IZ] : shift[IZ] + ghosted_size[IZ]; + } + + bSize_overlap[IX] = end[IX] - start[IX]; + bSize_overlap[IY] = end[IY] - start[IY]; + if constexpr (dim == 3) + { + bSize_overlap[IZ] = end[IZ] - start[IZ]; + } + + return bSize_overlap; + } + + //! return lower left corner coordinate of the block where original block and shifted block + //! overlap + KOKKOS_FORCEINLINE_FUNCTION + auto + get_start_overlap() const + { + + coord_t start; + start[IX] = m_shift[IX] < 0 ? 0 : m_shift[IX]; + start[IY] = m_shift[IY] < 0 ? 0 : m_shift[IY]; + if constexpr (dim == 3) + { + start[IZ] = m_shift[IZ] < 0 ? 0 : m_shift[IZ]; + } + return start; + } + + //! Return the total allocated memory in bytes. + auto + allocated_size_in_bytes() const + { + return m_data.allocated_size_in_bytes(); + } + +}; // DataArrayGhostedBlock + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_DATAARRAYBLOCK_H_ diff --git a/src/kalypsso/core/DataArrayGhostedBlock_old.h b/src/kalypsso/core/DataArrayGhostedBlock_old.h new file mode 100644 index 0000000..13b9ff5 --- /dev/null +++ b/src/kalypsso/core/DataArrayGhostedBlock_old.h @@ -0,0 +1,238 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file DataArrayGhostedBlock.h + */ +#ifndef KALYPSSO_CORE_DATAARRAYGHOSTEDBLOCK_OLD_H_ +#define KALYPSSO_CORE_DATAARRAYGHOSTEDBLOCK_OLD_H_ + +#include +#include // for KOKKOS_ENABLE_XXX + +#include +#include +#include + +#include // for definition of type block_size_t +#include +#include // for the definition of shift_t + +#include + +#include + +namespace kalypsso +{ + +// ============================================================================================ +// ============================================================================================ +// ============================================================================================ +/** + * \class DataArrayGhostedBlock is data container similar to DataArrayBlock for storing user data + * where each block contains ghost cells. + * + * In current implementation, the ghost zone is symmetric (durection by direction, it has the same + * width on the left and on the right). + * + * \note that the ghost width on a given side can't be larger than block width divided by two. This + * constraint is necessary for AMR: indeed when filling ghost cells, we need to access data from + * neighbor block, and if ghost width where larger than block width over two, then we would need to + * access data from neighbor block that are not direct neighbor but neighbor of neighbor which is + * not currently possible with p4est, nor desirable (the neighbor of neighbor may has 2 AMR level + * difference with current block level.) + */ +template +class KALYPSSO_DEPRECATED_WITH_COMMENT( + "This implementation is deprecated. Please refactor your code " + "and use impl from header DataArrayGhostedBlock.h") DataArrayGhostedBlockOld +{ +public: + using DataArrayBlock_t = DataArrayBlock; + +private: + //! block size + block_size_t m_bSize; + + //! ghost width + block_size_t m_gWidth; + + //! total size (block size + 2 * ghost width) + block_size_t m_tSize; + + //! storage array + DataArrayBlock_t m_data; + +public: + DataArrayGhostedBlockOld() = default; + + DataArrayGhostedBlockOld(block_size_t bSize, + block_size_t gWidth, + std::string name, + int num_vars, + int num_quadrants) + : m_bSize(bSize) + , m_gWidth(gWidth) + , m_tSize(bSize + 2 * gWidth) + , m_data(name, m_tSize, num_vars, num_quadrants) + { + KOKKOS_ASSERT((gWidth[IX] <= bSize[IX] / 2) && "Ghost width is too large"); + KOKKOS_ASSERT((gWidth[IY] <= bSize[IY] / 2) && "Ghost width is too large"); + if constexpr (dim == 3) + { + KOKKOS_ASSERT((gWidth[IZ] <= bSize[IZ] / 2) && "Ghost width is too large"); + } + } + + auto + flat_view() + { + return m_data.storage(); + } + + auto & + flat_view_ref() + { + return m_data.storage(); + } + + auto + data() + { + return m_data; + } + + auto & + data_ref() + { + return m_data; + } + + // ================================================================================== + //! memory access operator() as if dealing with a regular Kokkos::View - 2d case + template = true> + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(I0 i0, I1 i1, IVar ivar, IOct iOct) const + { + return m_data(i0, i1, ivar, iOct); + } + + // ================================================================================== + //! memory access operator() as if dealing with a regular Kokkos::View - 3d case + template = true> + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(I0 i0, I1 i1, I2 i2, IVar ivar, IOct iOct) const + { + return m_data(i0, i1, i2, ivar, iOct); + } + + // ================================================================================== + //! memory access operator() using a flat cell index (2d and 3d) + template + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(ICell icell, IVar ivar, IOct iOct) const + { + return m_data(icell, ivar, iOct); + } + + // ================================================================================== + auto + label() const + { + return m_data.label(); + } + + //! Reallocate by changing the number of quadrants/octants (do not initialize memory). + void + resize(int32_t num_quads) + { + m_data.resize(num_quads); + } + + //! Reallocate by changing the number of quadrants/octants and reset memory. + void + resize_and_reset(int32_t num_quads) + { + m_data.resize_and_reset(num_quads); + } + + KOKKOS_FORCEINLINE_FUNCTION + auto + num_cells() const + { + return m_data.num_cells(); + } + + //! Return total number of cells of the original non-ghosted block. + KOKKOS_FORCEINLINE_FUNCTION + auto + num_cells_inner() const + { + return Kokkos::dim_prod(m_bSize); + } + + //! Return the number of scalar variables. + KOKKOS_FORCEINLINE_FUNCTION + auto + num_vars() const + { + return m_data.num_vars(); + } + + //! Return the number of quadrants/octants. + KOKKOS_FORCEINLINE_FUNCTION + auto + num_quadrants() const + { + return m_data.num_quadrants(); + } + + //! Return the block size (shape) of the non-ghosted block. + KOKKOS_FORCEINLINE_FUNCTION + auto + block_size() const + { + return m_bSize; + } + + KOKKOS_FORCEINLINE_FUNCTION + auto + ghost_size() const + { + return m_gWidth; + } + + KOKKOS_FORCEINLINE_FUNCTION + auto + total_block_size() const + { + return m_tSize; + } + + //! Return the total allocated memory in bytes. + auto + allocated_size_in_bytes() const + { + uint64_t size = + static_cast(m_data.num_cells() * m_data.num_vars() * m_data.num_quadrants()) * + sizeof(T); + return size; + } + +}; // DataArrayGhostedBlockOld + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_DATAARRAYBLOCK_OLD_H_ diff --git a/src/kalypsso/core/DataArrayUtils.cpp b/src/kalypsso/core/DataArrayUtils.cpp new file mode 100644 index 0000000..792a608 --- /dev/null +++ b/src/kalypsso/core/DataArrayUtils.cpp @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file DataArrayUtils.h + */ + +#include + +#include + +namespace kalypsso +{ + +// default value is 1.0 (no growth rate) +real_t DataArrayUtils::m_capacity_growth_rate = m_capacity_growth_rate_default; + +// ============================================================================================ +// ============================================================================================ +void +DataArrayUtils::set_growth_rate(ConfigMap const & config_map) +{ + // a growth rate of 1.2 seems reasonable + m_capacity_growth_rate = + config_map.getReal("data_array", "capacity_growth_rate", KALYPSSO_NUM(1.2)); + + KOKKOS_ASSERT(m_capacity_growth_rate >= KALYPSSO_NUM(1.0) && + "Memory capacity growth rate must be larger than one."); +} + +// ============================================================================================ +// ============================================================================================ +void +DataArrayUtils::set_growth_rate(real_t growth_rate) +{ + m_capacity_growth_rate = growth_rate; + + KOKKOS_ASSERT(m_capacity_growth_rate >= KALYPSSO_NUM(1.0) && + "Memory capacity growth rate must be larger than one."); +} + +} // namespace kalypsso diff --git a/src/kalypsso/core/DataArrayUtils.h b/src/kalypsso/core/DataArrayUtils.h new file mode 100644 index 0000000..671d942 --- /dev/null +++ b/src/kalypsso/core/DataArrayUtils.h @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file DataArrayUtils.h + */ +#ifndef KALYPSSO_CORE_DATAARRAYUTILS_H_ +#define KALYPSSO_CORE_DATAARRAYUTILS_H_ + +#include +#include // for KOKKOS_ENABLE_XXX + +#include +#include +#include + +namespace kalypsso +{ + +// forward declaration necessary for defining DataArrayBase +class ConfigMap; + +//! enum used as a template parameter to DataArray and DataArrayBlock +enum class StorageType +{ + OWNED = 0, + UNMANAGED = 1 +}; + +// ============================================================================================ +// ============================================================================================ +// ============================================================================================ +/** + * \class DataArrayUtils + * + * This is to parametrize all DataArray-like class. + * It is meant to store common parameters; currently only parameter: the capacity growth rate + * + */ +struct DataArrayUtils +{ + static constexpr real_t m_capacity_growth_rate_default = KALYPSSO_NUM(1.0); + static real_t m_capacity_growth_rate; + + static void + set_growth_rate(ConfigMap const & config_map); + + static void + set_growth_rate(real_t growth_rate); + + //! compute allocated capacity given initial size. + static size_t + allocated_capacity(size_t wanted_size) + { + return static_cast( + std::floor(static_cast(wanted_size) * m_capacity_growth_rate)); + } +}; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_DATAARRAYUTILS_H_ diff --git a/src/kalypsso/core/EdgeDataArrayBlock.h b/src/kalypsso/core/EdgeDataArrayBlock.h new file mode 100644 index 0000000..4643cab --- /dev/null +++ b/src/kalypsso/core/EdgeDataArrayBlock.h @@ -0,0 +1,506 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file EdgeDataArrayBlock.h + */ +#ifndef KALYPSSO_CORE_EDGEDATAARRAYBLOCK_H_ +#define KALYPSSO_CORE_EDGEDATAARRAYBLOCK_H_ + +#include +#include +#include + +namespace kalypsso +{ + +// ============================================================================================ +// ============================================================================================ +// ============================================================================================ +/** + * A Data container helper to store d-dimensional vector field edge-staggered components. + * + * More precisely, on edges, we only stored longitudinal components. + * The aim is to be able to compute a face-centered vector field as the curl of an edge-centered + * vector field. + * + * Taking as example magnetic vector potential A (a vector field such that magnetic field B = + * curl(A)): + * - Ax is center on x-edges + * - Ay is center on y-edges + * - Az is center on z-edges + * + * \note Currently in kalypsso blocks can only be of equal size in each direction. + * So, for example, in 3D, each component is stored using an array of N x (N+1) x (N+1) elements. + * Later, if needed, we could add support for block of independent sizes along each direction. + * + * \note In 2d, only Az is actually used and stored. + * + */ +template +class EdgeDataArrayBlock +{ +public: + using DataArrayBlock_t = DataArrayBlock; + using FaceDataArrayBlock_t = FaceDataArrayBlock; + using EdgeDataArrayBlock_t = EdgeDataArrayBlock; + + using EdgeFlatArray_t = Kokkos::View; + using EdgeFlatArrayUnmanaged_t = + Kokkos::View>; + + using ExecutionSpace = typename device_t::execution_space; + +private: + //! block size : number of cells in each direction (without counting ghost cells). + block_size_t m_bSize; + + //! ghost size : number of ghosts cells all around block of cells. + //! m_gSize must be even because prologation operator would be too complex to implement + //! defaulted value is zero. + int32_t m_gSize; + + //! block size : number of cells in each direction (with ghost cells included). + block_size_t m_bSize_total; + + //! number of x-edges in each direction - used for flat index computation + block_size_t m_bSize_edge_x; + + //! number of y-edges in each direction - used for flat index computation + block_size_t m_bSize_edge_y; + + //! number of z-edges in each direction - used for flat index computation + block_size_t m_bSize_edge_z; + + //! offset used as starting address of each component of the field inside a block + //! - m_offsets[1] number of x-edges elements per octant + //! - m_offsets[3] number of x-edges and y-edges elements per octant + //! - m_offsets[3] total num of edges elements (x,y and z) per octant + edge_flat_index_offset_t m_offsets; + + //! total number of quadrants (owned, MPI ghost, outside, outside_ghosts) + //! may changed when resizing + int m_num_quadrants; + + //! 1D storage array, it should contain enough space to hold m_data_x, m_data_y and m_data_z + //! may be reallocated when the number of octants changes + EdgeFlatArray_t m_storage_data; + +public: + EdgeDataArrayBlock() = default; + + /** + * Constructor without ghost cells. + */ + EdgeDataArrayBlock(std::string name, block_size_t bSize, int num_quadrants) + : m_bSize(bSize) + , m_gSize(0) + , m_bSize_total(bSize) + , m_bSize_edge_x(get_edge_block_size(bSize, IX)) + , m_bSize_edge_y(get_edge_block_size(bSize, IY)) + , m_bSize_edge_z(get_edge_block_size(bSize, IZ)) + , m_offsets(compute_edge_flat_index_offsets(bSize)) + , m_num_quadrants(num_quadrants) + , m_storage_data(Kokkos::view_alloc(Kokkos::WithoutInitializing, name), + static_cast(m_offsets[3] * num_quadrants)) + {} // EdgeDataArrayBlock + + /** + * Constructor with ghost cells. + */ + EdgeDataArrayBlock(std::string name, + block_size_t bSize, + int32_t ghostwidth, + int num_quadrants) + : m_bSize(bSize) + , m_gSize(ghostwidth) + , m_bSize_total(bSize + 2 * ghostwidth) + , m_bSize_edge_x(get_edge_block_size(bSize + 2 * ghostwidth, IX)) + , m_bSize_edge_y(get_edge_block_size(bSize + 2 * ghostwidth, IY)) + , m_bSize_edge_z(get_edge_block_size(bSize + 2 * ghostwidth, IZ)) + , m_offsets(compute_edge_flat_index_offsets(bSize + 2 * ghostwidth)) + , m_num_quadrants(num_quadrants) + , m_storage_data(Kokkos::view_alloc(Kokkos::WithoutInitializing, name), + static_cast(m_offsets[3] * num_quadrants)) + {} // EdgeDataArrayBlock + + /** + * accumulate over all directions the number of edge elements in that direction per octant. + */ + KOKKOS_FORCEINLINE_FUNCTION auto + offsets() const + { + return m_offsets; + } // offsets + + /** + * convert a logical multi-index (i, j, iOct) into a flat index to storage. + * + * \tparam dir is used to determine which type of edge (along which direction: IX, IY or IZ) + */ + template + KOKKOS_FORCEINLINE_FUNCTION int64_t + flat_index(I0 i0, I1 i1, IOct iOct) const + { + if constexpr (dir == IX) + { + KOKKOS_ASSERT(static_cast(i0) < m_bSize_edge_x[IX] and + "[EdgeDataArrayBlock::flat_index] wrong index i0"); + KOKKOS_ASSERT(static_cast(i1) < m_bSize_edge_x[IY] and + "[EdgeDataArrayBlock::flat_index] wrong index i1"); + + return m_offsets[0] + i0 + m_bSize_edge_x[IX] * i1 + iOct * m_offsets[3]; + } + else if constexpr (dir == IY) + { + KOKKOS_ASSERT(static_cast(i0) < m_bSize_edge_y[IX] and + "[EdgeDataArrayBlock::flat_index] wrong index i0"); + KOKKOS_ASSERT(static_cast(i1) < m_bSize_edge_y[IY] and + "[EdgeDataArrayBlock::flat_index] wrong index i1"); + + return m_offsets[1] + i0 + m_bSize_edge_y[IX] * i1 + iOct * m_offsets[3]; + } + else if constexpr (dir == IZ) + { + KOKKOS_ASSERT(static_cast(i0) < m_bSize_edge_z[IX] and + "[EdgeDataArrayBlock::flat_index] wrong index i0"); + KOKKOS_ASSERT(static_cast(i1) < m_bSize_edge_z[IY] and + "[EdgeDataArrayBlock::flat_index] wrong index i1"); + + return m_offsets[2] + i0 + m_bSize_edge_z[IX] * i1 + iOct * m_offsets[3]; + } + return 0; + } // flat_index + + /** + * convert a logical multi-index (i, j, k, iOct) into a flat index to storage. + * + * \tparam dir is used to determine which type of edge (along which direction: IX, IY or IZ) + */ + template + KOKKOS_FORCEINLINE_FUNCTION int64_t + flat_index(I0 i0, I1 i1, I2 i2, IOct iOct) const + { + if constexpr (dir == IX) + { + KOKKOS_ASSERT(static_cast(i0) < m_bSize_edge_x[IX] and + "[EdgeDataArrayBlock::flat_index] wrong index i0"); + KOKKOS_ASSERT(static_cast(i1) < m_bSize_edge_x[IY] and + "[EdgeDataArrayBlock::flat_index] wrong index i1"); + KOKKOS_ASSERT(static_cast(i2) < m_bSize_edge_x[IZ] and + "[EdgeDataArrayBlock::flat_index] wrong index i2"); + + return m_offsets[0] + i0 + m_bSize_edge_x[IX] * (i1 + m_bSize_edge_x[IY] * i2) + + iOct * m_offsets[3]; + } + else if constexpr (dir == IY) + { + KOKKOS_ASSERT(static_cast(i0) < m_bSize_edge_y[IX] and + "[EdgeDataArrayBlock::flat_index] wrong index i0"); + KOKKOS_ASSERT(static_cast(i1) < m_bSize_edge_y[IY] and + "[EdgeDataArrayBlock::flat_index] wrong index i1"); + KOKKOS_ASSERT(static_cast(i2) < m_bSize_edge_y[IZ] and + "[EdgeDataArrayBlock::flat_index] wrong index i2"); + + return m_offsets[1] + i0 + m_bSize_edge_y[IX] * (i1 + m_bSize_edge_y[IY] * i2) + + iOct * m_offsets[3]; + } + else if constexpr (dir == IZ) + { + KOKKOS_ASSERT(static_cast(i0) < m_bSize_edge_z[IX] and + "[EdgeDataArrayBlock::flat_index] wrong index i0"); + KOKKOS_ASSERT(static_cast(i1) < m_bSize_edge_z[IY] and + "[EdgeDataArrayBlock::flat_index] wrong index i1"); + KOKKOS_ASSERT(static_cast(i2) < m_bSize_edge_z[IZ] and + "[EdgeDataArrayBlock::flat_index] wrong index i2"); + + return m_offsets[2] + i0 + m_bSize_edge_z[IX] * (i1 + m_bSize_edge_z[IY] * i2) + + iOct * m_offsets[3]; + } + return 0; + } // flat_index + + //! memory access operator() as if dealing with a regular Kokkos::View - 2d case + template + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(I0 i0, I1 i1, IDir dir, IOct iOct) const + { + KOKKOS_ASSERT((dir == IX or dir == IY or dir == IZ) and "EdgeDataArrayBlock: wrong direction"); + KOKKOS_ASSERT((static_cast(iOct) < m_storage_data.extent(0)) and + "EdgeDataArrayBlock: index iOct too large"); + + if (dir == IX) + return m_storage_data(flat_index(i0, i1, iOct)); + else if (dir == IY) + return m_storage_data(flat_index(i0, i1, iOct)); + else if (dir == IZ) + return m_storage_data(flat_index(i0, i1, iOct)); + else // shouldn't be here + return m_storage_data(flat_index(i0, i1, iOct)); + } + + //! memory access operator() as if dealing with a regular Kokkos::View - 3d case + template + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(I0 i0, I1 i1, I2 i2, IDir dir, IOct iOct) const + { + KOKKOS_ASSERT((dir == IX or dir == IY or dir == IZ) and "EdgeDataArrayBlock: wrong direction"); + KOKKOS_ASSERT((static_cast(iOct) < m_storage_data.extent(0)) and + "EdgeDataArrayBlock: index iOct too large"); + + if (dir == IX) + return m_storage_data(flat_index(i0, i1, i2, iOct)); + else if (dir == IY) + return m_storage_data(flat_index(i0, i1, i2, iOct)); + else if (dir == IZ) + return m_storage_data(flat_index(i0, i1, i2, iOct)); + else // shouldn't be here + return m_storage_data(flat_index(i0, i1, i2, iOct)); + } + + //! memory access operator() as if dealing with a regular Kokkos::View - 2d/3d case + template + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(edge_multiindex_t indexes, IOct iOct) const + { + if constexpr (dim == 2) + return this->operator()(indexes[IX], indexes[IY], indexes[dim], iOct); + else if constexpr (dim == 3) + return this->operator()(indexes[IX], indexes[IY], indexes[IZ], indexes[dim], iOct); + } + + auto + storage() -> decltype(m_storage_data) + { + return m_storage_data; + } + + auto + storage_ref() -> decltype(m_storage_data) & + { + return m_storage_data; + } + + //! Resize m_storage data using new number of octant. + //! + //! This function resizes data without copying old data, without even initializing the new array. + //! This is enough especially when doing load balancing; data will be initialized by MPI + //! communications. + void + resize(int32_t num_quads) + { + // update number of quadrants + m_num_quadrants = num_quads; + + Kokkos::resize( + Kokkos::view_alloc(Kokkos::WithoutInitializing), m_storage_data, m_offsets[3] * num_quads); + + } // resize + + //! Resize m_storage data using new number of octant. + //! This function resizes data; it doesn't copy old data, but default initialize to zero. + void + resize_and_reset(int32_t num_quads) + { + resize(num_quads); + Kokkos::deep_copy(m_storage_data, 0); + + } // resize_and_reset + + KOKKOS_FORCEINLINE_FUNCTION + auto + num_quadrants() const + { + return m_num_quadrants; + } + + KOKKOS_FORCEINLINE_FUNCTION + int32_t + num_elements_per_octant() const + { + return m_offsets[3]; + } + + KOKKOS_FORCEINLINE_FUNCTION + auto + cell_block_size() const + { + return m_bSize_total; + } + + KOKKOS_FORCEINLINE_FUNCTION + auto + cell_block_size_inner() const + { + return m_bSize; + } + + KOKKOS_FORCEINLINE_FUNCTION + auto + edge_block_size(int dir) const + { + if (dir == IX) + return m_bSize_edge_x; + else if (dir == IY) + return m_bSize_edge_y; + else if (dir == IZ) + return m_bSize_edge_z; + + return m_bSize_edge_x; + } + + KOKKOS_FORCEINLINE_FUNCTION + auto + ghostwidth() const + { + return m_gSize; + } + + KOKKOS_FORCEINLINE_FUNCTION + auto + is_ghosted() const + { + return m_gSize > 0; + } + + auto + label() const + { + return m_storage_data.label(); + } + + auto + allocated_size_in_bytes() const + { + auto size = m_storage_data.extent(0) * sizeof(T); + return size; + } + + /** + * \return maximum number of edges inside a block (ghost cells included). + */ + KOKKOS_FORCEINLINE_FUNCTION + auto + max_num_edges_per_leaf() const + { + return maximum_number_edges_per_leaf(m_bSize_total); + } + +public: + /** + * Compute curl (rotationnel) of an EdgeDataArrayBlock. + * + * This was design to ease computing B = rot(A) where + * - B is magnetic field (a FaceDataArrayBlock) + * - A is vector potential (a EdgeDataArrayBlock) + * + * \param[in] edata is the edge data array + * \param[in] orchard_keys + * + * \return a FaceDataArrayBlock that is the curl of input edge array + */ + static FaceDataArrayBlock_t + compute_curl(EdgeDataArrayBlock_t edata, + typename orchard_key_base_t::view_t orchard_keys) + { + const std::string curl("curl_of_"); + const auto label = curl + edata.label(); + + const auto block_size = edata.cell_block_size(); + auto res = FaceDataArrayBlock_t(label, block_size, edata.num_quadrants()); + const int64_t total_num_faces = res.num_elements_per_octant() * edata.num_quadrants(); + const auto nbFacesPerLeaf = res.num_elements_per_octant(); + + Kokkos::parallel_for( + "compute curl of an EdgeDataArrayBlock", + Kokkos::RangePolicy(0, total_num_faces), + KOKKOS_LAMBDA(const int64_t & global_index) { + // convert global index into + // - octant id + // - face_index inside block (from 0 to num_elements_per_octant-1) + const auto iOct = global_index / nbFacesPerLeaf; + const auto face_index = static_cast(global_index - iOct * nbFacesPerLeaf); + + // get block level + const auto level = orchard_key_t::level(orchard_keys(iOct)); + + // the only reason of the following dummy code to be here, is that cuda nvcc compile + // doesn't support capturing variables inside the inner lambda inside a constexpr if + // + // strangely, nvc++ is ok and don't need that + // TODO: remove theses lines when nvcc will have support for this. +#ifdef __NVCC__ + [[maybe_unused]] int dummy = 0; + if (edata.num_quadrants() == 0 or res.num_quadrants() == 0 or block_size[IX] == 0) + dummy++; +#endif + + if constexpr (dim == 2) + { + auto const face_indexes = + face_flat_index_unravel<2>(face_index, block_size, res.offsets(), res.shift()); + auto const & i = face_indexes[IX]; + auto const & j = face_indexes[IY]; + auto const & ivar = face_indexes[dim]; + + const auto dx = compute_cell_length(level, block_size[IX]); + const auto & dy = dx; // assuming square cells + + if (ivar == IX) + { + res(i, j, ivar, iOct) = (edata(i, j + 1, IZ, iOct) - edata(i, j, IZ, iOct)) / dy; + } + else if (ivar == IY) + { + res(i, j, ivar, iOct) = -(edata(i + 1, j, IZ, iOct) - edata(i, j, IZ, iOct)) / dx; + } + else if (ivar == IZ) + { + res(i, j, ivar, iOct) = (edata(i + 1, j, IY, iOct) - edata(i, j, IY, iOct)) / dx - + (edata(i, j + 1, IX, iOct) - edata(i, j, IX, iOct)) / dy; + } + } + else if constexpr (dim == 3) + { + auto const face_indexes = + face_flat_index_unravel<3>(face_index, block_size, res.offsets(), res.shift()); + auto const & i = face_indexes[IX]; + auto const & j = face_indexes[IY]; + auto const & k = face_indexes[IZ]; + auto const & ivar = face_indexes[dim]; + + const auto dx = compute_cell_length(level, block_size[IX]); + const auto & dy = dx; // assuming square cells + const auto & dz = dx; // assuming square cells + + if (ivar == IX) + { + res(i, j, k, ivar, iOct) = + (edata(i, j + 1, k, IZ, iOct) - edata(i, j, k, IZ, iOct)) / dy - + (edata(i, j, k + 1, IY, iOct) - edata(i, j, k, IY, iOct)) / dz; + } + if (ivar == IY) + { + res(i, j, k, ivar, iOct) = + (edata(i, j, k + 1, IX, iOct) - edata(i, j, k, IX, iOct)) / dz - + (edata(i + 1, j, k, IZ, iOct) - edata(i, j, k, IZ, iOct)) / dx; + } + else if (ivar == IZ) + { + res(i, j, k, ivar, iOct) = + (edata(i + 1, j, k, IY, iOct) - edata(i, j, k, IY, iOct)) / dx - + (edata(i, j + 1, k, IX, iOct) - edata(i, j, k, IX, iOct)) / dy; + } + } + }); + + return res; + + } // EdgeDataArrayBlock::compute_curl + +}; // EdgeDataArrayBlock + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_EDGEDATAARRAYBLOCK_H_ diff --git a/src/kalypsso/core/EdgeDataArrayBlock_utils.h b/src/kalypsso/core/EdgeDataArrayBlock_utils.h new file mode 100644 index 0000000..75ebe45 --- /dev/null +++ b/src/kalypsso/core/EdgeDataArrayBlock_utils.h @@ -0,0 +1,705 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file EdgeDataArrayBlock_utils.h + * + * This header is similar to FaceDataArrayBlock_utils.h but we do not completely define a new data + * array type, just adapt the logic to enumerate edges in both 2d / 3d, and ease mapping + * Kokkos parallel_for iterations to edges. + * + * \note in 2d, edges (along Z axis) are identified to cell nodes, but should think of infinitely + * long edge in the Z direction. + * + * \note Just to be clear, + * - in a 3d (bx,by,bz) block of cells, there are (bx+1)*(by+1)*bz edges along + * - in 3d the total number of edges is the sum of edges along X, Y and Z + */ +#ifndef KALYPSSO_CORE_EDGEDATAARRAYBLOCK_UTILS_H_ +#define KALYPSSO_CORE_EDGEDATAARRAYBLOCK_UTILS_H_ + +#include +#include // for KOKKOS_ENABLE_XXX + +#include // for definition of assertm +#include +#include + +#include // for edge_multiindex_t definition +#include // for definition of type block_size_t +#include +#include // for Face::face_t definition + +#include <../better-enums/enum.h> + +namespace kalypsso +{ + +/** + * A type alias used to store index offsets indicating where the different type of edge (edges along + * X, Y or Z ) start in a flat edge index. + */ +using edge_flat_index_offset_t = Kokkos::Array; + +// clang-format off +/** + * Enum used to distinguish the 3 possible neighbors of a block across an edge. + * + * X ----> X1 + * | \ + * | \ + * \/ X diagonal + * X2 + * + * if an edge is along direction DIR0, then DIR1 and DIR2 designated / can be used to enumerate the + * two orthogonal directions computed as + * DIR1 = (DIR0+1) % 3 + * DIR2 = (DIR1+2) % 3 + */ +BETTER_ENUM(EdgeNormalType, int, DIR1, DIR2, DIAGONAL) +// clang-format on + + +// ============================================================================================ +// ============================================================================================ +/** + * For a given edge direction, compute the logical edge array sizes associated + * to a block of cells of size bSize. + * + * \param[in] cell block size (number of cells per direction) + * + * \return block size (number of edges in direction "dir") per direction + * + * \tparam dim is dimension (2 or 3) + * \tparam dir is the edge direction + * + * \note in 2d, only edges along Z will be used in practice, but just for the sake of symmetry, we + * allow edge along IX and IY, knowing that in practice, these are identical to "faces" + */ +template +KOKKOS_FORCEINLINE_FUNCTION block_size_t + get_edge_block_size(block_size_t bSize, int dir) +{ + KOKKOS_ASSERT((dir == IX or dir == IY or dir == IZ) && "Wrong direction index."); + + block_size_t res = bSize; + + // enlarge sizes in edge transverse directions + if constexpr (dim == 2) + { + res[IX] += (dir == IX) ? 0 : 1; + res[IY] += (dir == IY) ? 0 : 1; + } + if constexpr (dim == 3) + { + res[IX] += (dir == IX) ? 0 : 1; + res[IY] += (dir == IY) ? 0 : 1; + res[IZ] += (dir == IZ) ? 0 : 1; + } + + return res; +} // get_edge_block_size + +// ============================================================================================ +// ============================================================================================ +//! compute the maximum number of edges (among all direction) in a block of cell of size bSize. +//! +//! - when the block is square (equal dimension in all direction), the result is exact +//! - when the block is rectangular, the value is rounded up +//! +//! Purpose: allocate an array that can be addressed with the same i,j,k bounds for x,y,z faces +//! +//! \param[in] bSize cell block size (number of cells per direction) +template +KOKKOS_FORCEINLINE_FUNCTION auto +maximum_number_edges_per_leaf(block_size_t bSize) +{ + + // optimization for the case block has equal sizes in all direction + if constexpr (dim == 2) + { + if (bSize[IX] == bSize[IY]) + { + return (bSize[IX] + 1) * bSize[IX]; + } + } + else if constexpr (dim == 3) + { + if ((bSize[IX] == bSize[IY]) and (bSize[IX] == bSize[IZ])) + { + return (bSize[IX] + 1) * (bSize[IX] + 1) * bSize[IX]; + } + } + + // general case : a rectangular block + return dim == 2 ? (bSize[IX] + 1) * (bSize[IY] + 1) + : (bSize[IX] + 1) * (bSize[IY] + 1) * (bSize[IZ] + 1); + +} // maximum_number_faces_per_leaf + +// ============================================================================================ +// ============================================================================================ +/** + * accumulate over direction the number of edges in that direction per octant. + * + * \param[in] bSize cell block size (number of cells per direction) + * + * \todo evaluate if it wouldn't be better to use to co-dimension to enumerate edge directions + * because as seen below it would better + * + * \note in 2d, this is only valid when one only care about edges along Z (for MHD emfs) + */ +template +KOKKOS_FORCEINLINE_FUNCTION auto +compute_edge_flat_index_offsets_emf(block_size_t bSize) -> edge_flat_index_offset_t +{ + edge_flat_index_offset_t offset; + + if constexpr (dim == 2) + { + offset[0] = 0; + offset[1] = offset[0]; // no edges along X + offset[2] = offset[1]; // no edges along Y + offset[3] = offset[2] + Kokkos::dim_prod(get_edge_block_size(bSize, IZ)); + } + else if constexpr (dim == 3) + { + offset[0] = 0; + offset[1] = offset[0] + Kokkos::dim_prod(get_edge_block_size(bSize, IX)); + offset[2] = offset[1] + Kokkos::dim_prod(get_edge_block_size(bSize, IY)); + offset[3] = offset[2] + Kokkos::dim_prod(get_edge_block_size(bSize, IZ)); + } + + return offset; +} // compute_edge_flat_index_offsets_emf + +// ============================================================================================ +// ============================================================================================ +/** + * accumulate over direction the number of edges in that direction per octant. + * + * \see compute_edge_flat_index_offsets + */ +template +KOKKOS_FORCEINLINE_FUNCTION auto +compute_edge_flat_index_offsets(block_size_t bSize) -> edge_flat_index_offset_t +{ + edge_flat_index_offset_t offset; + + if constexpr (dim == 2) + { + offset[0] = 0; + offset[1] = offset[0] + Kokkos::dim_prod(get_edge_block_size(bSize, IX)); + offset[2] = offset[1] + Kokkos::dim_prod(get_edge_block_size(bSize, IY)); + offset[3] = offset[2] + Kokkos::dim_prod(get_edge_block_size(bSize, IZ)); + } + else if constexpr (dim == 3) + { + offset[0] = 0; + offset[1] = offset[0] + Kokkos::dim_prod(get_edge_block_size(bSize, IX)); + offset[2] = offset[1] + Kokkos::dim_prod(get_edge_block_size(bSize, IY)); + offset[3] = offset[2] + Kokkos::dim_prod(get_edge_block_size(bSize, IZ)); + } + + return offset; +} // compute_edge_flat_index_offsets + +// ============================================================================================ +// ============================================================================================ +/** + * Decode i,j,k, edge_type from an edge flat index + * + * Only use this one when dealing with MHD emfs (electromotive forces). + */ +template +KOKKOS_FORCEINLINE_FUNCTION auto +edge_flat_index_unravel_emf(int32_t flat_index, + block_size_t const & block_size, + edge_flat_index_offset_t const & edge_flat_index_offsets) +{ + KOKKOS_ASSERT(flat_index >= 0 and "flat index must be positive !"); + KOKKOS_ASSERT(flat_index < edge_flat_index_offsets[3] and "flat index invalid (too large) !"); + + edge_multiindex_t index; + + // determine edge type + index[dim] = flat_index < edge_flat_index_offsets[1] ? IX + : flat_index < edge_flat_index_offsets[2] ? IY + : IZ; + + if constexpr (dim == 2) + { + KOKKOS_ASSERT(index[dim] == IZ and "Wrong edge index, should be IZ"); + // flat_index = i + (block_size[IX]+1) * j + index[IY] = flat_index / (block_size[IX] + 1); + index[IX] = flat_index - index[IY] * (block_size[IX] + 1); + } + else if constexpr (dim == 3) + { + if (index[dim] == IX) + { + // flat_index = edge_flat_index_offsets[0] + i + (block_size[IX]) * j + + // (block_size[IX])*(block_size[IY]+1) * k + // remember that flat_index_offsets[0] = 0 by design + index[IZ] = flat_index / ((block_size[IX]) * (block_size[IY] + 1)); + flat_index -= (block_size[IX]) * (block_size[IY] + 1) * index[IZ]; + index[IY] = flat_index / (block_size[IX]); + index[IX] = flat_index - index[IY] * (block_size[IX]); + } + else if (index[dim] == IY) + { + // flat_index = flat_index_offsets[1] + i + (block_size[IX]+1)*j + (block_size[IX]+1) * + // (block_size[IY]) * k + flat_index -= edge_flat_index_offsets[1]; + index[IZ] = flat_index / ((block_size[IX] + 1) * (block_size[IY])); + flat_index -= (block_size[IX] + 1) * (block_size[IY]) * index[IZ]; + index[IY] = flat_index / (block_size[IX] + 1); + index[IX] = flat_index - index[IY] * (block_size[IX] + 1); + } + else if (index[dim] == IZ) // IZ + { + // flat_index = flat_index_offsets[2] + i + (block_size[IX]+1)*j + (block_size[IX]+1) * + // (block_size[IY]+1) * k + flat_index -= edge_flat_index_offsets[2]; + index[IZ] = flat_index / ((block_size[IX] + 1) * (block_size[IY] + 1)); + flat_index -= (block_size[IX] + 1) * (block_size[IY] + 1) * index[IZ]; + index[IY] = flat_index / (block_size[IX] + 1); + index[IX] = flat_index - index[IY] * (block_size[IX] + 1); + } + } + + return index; +} // edge_flat_index_unravel_emf + +// ============================================================================================ +// ============================================================================================ +/** + * Decode i,j,k, edge_type from an edge flat index + */ +template +KOKKOS_FORCEINLINE_FUNCTION auto +edge_flat_index_unravel(int32_t flat_index, + block_size_t const & block_size, + edge_flat_index_offset_t const & edge_flat_index_offsets) +{ + KOKKOS_ASSERT(flat_index >= 0 and "flat index must be positive !"); + KOKKOS_ASSERT(flat_index < edge_flat_index_offsets[3] and "flat index invalid (too large) !"); + + edge_multiindex_t index; + + // determine edge type + index[dim] = flat_index < edge_flat_index_offsets[1] ? IX + : flat_index < edge_flat_index_offsets[2] ? IY + : IZ; + + if constexpr (dim == 2) + { + // flat_index = i + (edge_bsize[IX]) * j + const auto edge_bsize = get_edge_block_size(block_size, index[dim]); + flat_index -= edge_flat_index_offsets[index[dim]]; + + index[IY] = flat_index / edge_bsize[IX]; + index[IX] = flat_index - index[IY] * edge_bsize[IX]; + } + else if constexpr (dim == 3) + { + // flat_index = i + edge_bsize[IX] * j + const auto edge_bsize = get_edge_block_size(block_size, index[dim]); + flat_index -= edge_flat_index_offsets[index[dim]]; + + index[IZ] = flat_index / (edge_bsize[IX] * edge_bsize[IY]); + flat_index -= edge_bsize[IX] * edge_bsize[IY] * index[IZ]; + index[IY] = flat_index / edge_bsize[IX]; + index[IX] = flat_index - index[IY] * edge_bsize[IX]; + } + + return index; +} // edge_flat_index_unravel + +// ============================================================================================ +// ============================================================================================ +/** + * edge coordinates to cell coordinates. + */ +template +KOKKOS_INLINE_FUNCTION coord_t +edge_to_cell_coords(edge_multiindex_t const & edge_indexes, block_size_t const & bSizes) +{ + if constexpr (dim == 2) + { + // clang-format off + const coord_t<2> ijk{ edge_indexes[IX] == bSizes[IX] ? edge_indexes[IX] - 1 : edge_indexes[IX], + edge_indexes[IY] == bSizes[IY] ? edge_indexes[IY] - 1 : edge_indexes[IY] }; + // clang-format on + return ijk; + } + else if constexpr (dim == 3) + { + // clang-format off + const coord_t<3> ijk{ edge_indexes[IX] == bSizes[IX] ? edge_indexes[IX] - 1 : edge_indexes[IX], + edge_indexes[IY] == bSizes[IY] ? edge_indexes[IY] - 1 : edge_indexes[IY], + edge_indexes[IZ] == bSizes[IZ] ? edge_indexes[IZ] - 1 : edge_indexes[IZ] }; + // clang-format on + return ijk; + } +} // edge_to_cell_coords + +// ============================================================================================ +// ============================================================================================ +/** + * is edge on the far right ? + */ +template +KOKKOS_INLINE_FUNCTION bool +is_edge_on_the_right(edge_multiindex_t const & edge_indexes, block_size_t const & bSizes) +{ + if constexpr (dim == 2) + { + return edge_indexes[IX] == bSizes[IX] or edge_indexes[IY] == bSizes[IY]; + } + else if constexpr (dim == 3) + { + return edge_indexes[IX] == bSizes[IX] or edge_indexes[IY] == bSizes[IY] or + edge_indexes[IZ] == bSizes[IZ]; + } +} // is_edge_on_the_right + +// ============================================================================================ +// ============================================================================================ +/** + * Returns true if edge "touches" block border. + * + * Said differently edge touches border when it belongs to a cell at border. + */ +template +KOKKOS_INLINE_FUNCTION bool +is_edge_at_block_border(edge_multiindex_t const & ijk, + block_size_t const & bSize, + Face::face_t border_loc) +{ + const int dir = border_loc / 2; + KOKKOS_ASSERT((dir == IX or dir == IY or dir == IZ) && "Wrong direction index."); + + const auto edge_block_size = get_edge_block_size(bSize, dir); + + if constexpr (dim == 2) + { + return (border_loc == Face::XMIN and ijk[IX] == 0) or + (border_loc == Face::XMAX and ijk[IX] == edge_block_size[IX] - 1) or + (border_loc == Face::YMIN and ijk[IY] == 0) or + (border_loc == Face::YMAX and ijk[IY] == edge_block_size[IY] - 1); + } + else if constexpr (dim == 3) + { + return (border_loc == Face::XMIN and ijk[IX] == 0) or + (border_loc == Face::XMAX and ijk[IX] == edge_block_size[IX] - 1) or + (border_loc == Face::YMIN and ijk[IY] == 0) or + (border_loc == Face::YMAX and ijk[IY] == edge_block_size[IY] - 1) or + (border_loc == Face::ZMIN and ijk[IZ] == 0) or + (border_loc == Face::ZMAX and ijk[IZ] == edge_block_size[IZ] - 1); + } + + return false; + +} // is_edge_at_block_border + +// ============================================================================================ +// ============================================================================================ +/** + * Returns true if edge is contained in block surface. + * + * In 2d, this is equivalent to is_at_block_border + */ +template +KOKKOS_INLINE_FUNCTION bool +is_edge_at_block_surface(edge_multiindex_t const & ijk, + block_size_t const & bSize, + Face::face_t border_loc) +{ + if constexpr (dim == 2) + { + const int dir = border_loc / 2; + KOKKOS_ASSERT((dir == IX or dir == IY or dir == IZ) && "Wrong direction index."); + + const auto edge_block_size = get_edge_block_size(bSize, dir); + auto const & edge_dir = ijk[dim]; + + if (edge_dir == IZ) + return (border_loc == Face::XMIN and ijk[IX] == 0) or + (border_loc == Face::XMAX and ijk[IX] == edge_block_size[IX] - 1) or + (border_loc == Face::YMIN and ijk[IY] == 0) or + (border_loc == Face::YMAX and ijk[IY] == edge_block_size[IY] - 1); + else if (edge_dir == IX) + { + return (border_loc == Face::YMIN and ijk[IY] == 0) or + (border_loc == Face::YMAX and ijk[IY] == edge_block_size[IY] - 1); + } + else if (edge_dir == IY) + { + return (border_loc == Face::XMIN and ijk[IX] == 0) or + (border_loc == Face::XMAX and ijk[IX] == edge_block_size[IX] - 1); + } + } + else if constexpr (dim == 3) + { + const int dir = border_loc / 2; + KOKKOS_ASSERT((dir == IX or dir == IY or dir == IZ) && "Wrong direction index."); + + const auto edge_block_size = get_edge_block_size(bSize, dir); + auto const & edge_dir = ijk[dim]; + + if (edge_dir == IZ) + return (border_loc == Face::XMIN and ijk[IX] == 0) or + (border_loc == Face::XMAX and ijk[IX] == edge_block_size[IX] - 1) or + (border_loc == Face::YMIN and ijk[IY] == 0) or + (border_loc == Face::YMAX and ijk[IY] == edge_block_size[IY] - 1); + else if (edge_dir == IX) + { + return (border_loc == Face::YMIN and ijk[IY] == 0) or + (border_loc == Face::YMAX and ijk[IY] == edge_block_size[IY] - 1) or + (border_loc == Face::ZMIN and ijk[IZ] == 0) or + (border_loc == Face::ZMAX and ijk[IZ] == edge_block_size[IZ] - 1); + } + else if (edge_dir == IY) + { + return (border_loc == Face::ZMIN and ijk[IZ] == 0) or + (border_loc == Face::ZMAX and ijk[IZ] == edge_block_size[IZ] - 1) or + (border_loc == Face::XMIN and ijk[IX] == 0) or + (border_loc == Face::XMAX and ijk[IX] == edge_block_size[IX] - 1); + } + } + + return false; + +} // is_at_block_surface + +// ============================================================================================ +// ============================================================================================ +template +KOKKOS_INLINE_FUNCTION bool +is_edge_in_the_middle_of_a_face_impl(edge_multiindex_t const & ijk, + block_size_t const & bSize, + ComponentIndex3D dir1, + ComponentIndex3D dir2) +{ + + if ((ijk[dir1] == 0 or ijk[dir1] == bSize[dir1]) and ijk[dir2] == bSize[dir2] / 2) + return true; + + if ((ijk[dir2] == 0 or ijk[dir2] == bSize[dir2]) and ijk[dir1] == bSize[dir1] / 2) + return true; + + return false; + +} // is_edge_in_the_middle_of_a_face_impl + +// ============================================================================================ +// ============================================================================================ +/** + * Returns true if edge is in the middle of a face (see drawing). + * + * In 2d, this is simple, this corresponds exactly to four location marked with an "x" + * + * _______x________ + * | | | | | + * |___|___|___|___| + * | | | | | + * x___|___|___|___x + * | | | | | + * |___|___|___|___| + * | | | | | + * |___|___x___|___| + * + * In 3d, this a bit more complex, let look at a face in the (x,y) plane, it corresponds to all + * the edges strictly contained in the face (edge along X or along Y axis not on the face border) + * and aligned with one of the two lines passing through the face center that are axis aligned. + * + * In the drawing below: + * - edges marked with an "x" are aligned with X axis and passing through the face center, + * - edges marked with an "y" are aligned with Y axis and passing through the face center, + * + * ________________ + * | | y | | + * |___|___y___|___| + * | | y | | + * |xxx|xxx xxx|xxx| + * | | y | | + * |___|___y___|___| + * | | y | | + * |___|___y___|___| + * + * \note this function is useful when dealing with computation that need to access both a given + * edge location in current current and corresponding edge location (same physical location) in + * the face-neighbor octant. + * + * There are two situations: + * - if the face is non-hanging, there isn't any problem, there is a 1-to-1 correspondence between + * the edge in current octant and an edge in the neighbor; + * - if the face is hanging (i.e. non-conform) then all edges at the middle of the large face have + * 2 neighbor edges the fine neighbor octants. + * + * \note Important: this routine is only valid when cell block sizes are all even integers (which + * we assume at multiple locations). + */ +template +KOKKOS_INLINE_FUNCTION bool +is_edge_in_the_middle_of_a_face(edge_multiindex_t const & ijk, block_size_t const & bSize) +{ + auto const & edge_dir = ijk[dim]; + + if constexpr (dim == 2) + { + if (edge_dir == IZ) + { + const auto dir1 = IX; + const auto dir2 = IY; + return is_edge_in_the_middle_of_a_face_impl(ijk, bSize, dir1, dir2); + } + } + else if constexpr (dim == 3) + { + if (edge_dir == IZ) + { + const auto dir1 = IX; + const auto dir2 = IY; + return is_edge_in_the_middle_of_a_face_impl(ijk, bSize, dir1, dir2); + } + else if (edge_dir == IX) + { + const auto dir1 = IY; + const auto dir2 = IZ; + return is_edge_in_the_middle_of_a_face_impl(ijk, bSize, dir1, dir2); + } + else if (edge_dir == IY) + { + const auto dir1 = IZ; + const auto dir2 = IX; + return is_edge_in_the_middle_of_a_face_impl(ijk, bSize, dir1, dir2); + } + } + + return false; + +} // is_edge_in_the_middle_of_a_face + +// ============================================================================================ +// ============================================================================================ +/** + * Returns true if cell edge is also an edge of the block itself. + * + * Edges marked with an "x" are also block edges. + * + * x______________x + * | | | | | + * |___|___|___|___| + * | | | | | + * |___|___|___|___| + * | | | | | + * |___|___|___|___| + * | | | | | + * x___|___|___|___x + * + * \note a corner is identified by the intersection of two faces. + * + * \param[in] ijk identifies an edge location + * \param[in] bSize cell block size + * \param[in] border_loc0 identifies a border + * \param[in] border_loc1 identifies another border that must be orthogonal to border_loc0 + */ +template +KOKKOS_INLINE_FUNCTION bool +is_edge_at_block_edge(edge_multiindex_t const & ijk, + block_size_t const & bSize, + Face::face_t border_loc0, + Face::face_t border_loc1) +{ + [[maybe_unused]] const int dir0 = border_loc0 / 2; + KOKKOS_ASSERT((dir0 == IX or dir0 == IY or dir0 == IZ) && "Wrong direction index."); + + [[maybe_unused]] const int dir1 = border_loc1 / 2; + KOKKOS_ASSERT((dir1 == IX or dir1 == IY or dir1 == IZ) && "Wrong direction index."); + + // border_loc0 and border_loc1 must be orthogonal + KOKKOS_ASSERT((dir0 != dir1) && "dir0 and dir1 must be orthogonal"); + + return is_edge_at_block_surface(ijk, bSize, border_loc0) and + is_edge_at_block_surface(ijk, bSize, border_loc1); + +} // is_edge_at_block_edge + +// ============================================================================================ +// ============================================================================================ +template +KOKKOS_INLINE_FUNCTION auto +get_edge_outside_unit_vector(Face::face_t border_loc0, + Face::face_t border_loc1) -> Kokkos::Array +{ + Kokkos::Array v; + for (int i = 0; i < dim; ++i) + v[i] = 0; + + const auto dir0 = border_loc0 / 2; + const auto dir1 = border_loc1 / 2; + + { + const auto faces = Face::get_pair_of_faces(dir0); + v[dir0] = border_loc0 == faces[0] ? -1 : border_loc0 == faces[1] ? 1 : 0; + } + { + const auto faces = Face::get_pair_of_faces(dir1); + v[dir1] = border_loc1 == faces[0] ? -1 : border_loc1 == faces[1] ? 1 : 0; + } + + return v; + +} // get_unit_outside_vector + +// ============================================================================================ +// ============================================================================================ +/** + * if edge is strictly inside block, this vector is zero (square norm is 0). + * if edge is a border on a face, return the face outside normal (square norm is 1) + * if edge is also a block edge return outside normal (square norm is 2) + * + * in all other cases, return null vector + */ +template +KOKKOS_INLINE_FUNCTION auto +get_edge_outside_unit_vector(edge_multiindex_t const & ijk, + block_size_t const & cell_block_size, + EdgeNormalType edge_normal_type) -> shift_t +{ + shift_t v; + for (size_t i = 0; i < dim; ++i) + v[i] = 0; + + auto const & edge_dir = ijk[dim]; + + // compute normal directions to current edge + const auto dir1 = (edge_dir + 1) % 3; + const auto dir2 = (edge_dir + 2) % 3; + + if (edge_normal_type == +EdgeNormalType::DIAGONAL or edge_normal_type == +EdgeNormalType::DIR1) + { + if (ijk[dir1] == 0) + v[dir1] = -1; + else if (ijk[dir1] == cell_block_size[dir1]) + v[dir1] = 1; + } + + if (edge_normal_type == +EdgeNormalType::DIAGONAL or edge_normal_type == +EdgeNormalType::DIR2) + { + if (ijk[dir2] == 0) + v[dir2] = -1; + else if (ijk[dir2] == cell_block_size[dir2]) + v[dir2] = 1; + } + + return v; + +} // get_unit_outside_vector + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_EDGEDATAARRAYBLOCK_UTILS_H_ diff --git a/src/kalypsso/core/ExtractNonGhostedArray.h b/src/kalypsso/core/ExtractNonGhostedArray.h new file mode 100644 index 0000000..d83daeb --- /dev/null +++ b/src/kalypsso/core/ExtractNonGhostedArray.h @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ExtractNonGhostedArray.h + */ +#ifndef KALYPSSO_CORE_EXTRACT_NON_GHOSTED_ARRAY_H_ +#define KALYPSSO_CORE_EXTRACT_NON_GHOSTED_ARRAY_H_ + +#include // for assertm +#include +#include // for DataArrayBlock, DataArrayGhostedBlock + +namespace kalypsso +{ + +// ======================================================== +// ======================================================== +// ======================================================== +/** + * A simple helper structure to convert a DataArrayGhostedBlock into a DataArrayBlock (non-ghosted). + */ +template +struct ExtractNonGhostedArray +{ + + //! type alias for cell-centered data array at block level (see kalypsso_data_container.h) + using DataArrayBlock_t = DataArrayBlock; + using DataArrayGhostedBlock_t = DataArrayGhostedBlock; + + //! our kokkos execution space + using ExecutionSpace = typename device_t::execution_space; + + // ========================================================================== + // ========================================================================== + static DataArrayBlock_t + run(DataArrayGhostedBlock_t data, int32_t ivar) + { + KALYPSSO_ASSERT(ivar < data.num_vars()); + + const auto label = std::string("extracted array var=") + std::to_string(ivar); + + // data inner block size to allocate result + auto res = DataArrayBlock_t(label, data.block_size(), 1, data.num_quadrants()); + + const auto nbCellsPerLeaf = res.num_cells(); + const int64_t total_num_cells = nbCellsPerLeaf * data.num_quadrants(); + + Kokkos::parallel_for( + label, + Kokkos::RangePolicy(0, total_num_cells), + KOKKOS_LAMBDA(const int64_t & global_index) { + /// convert global index into + // - octant id + // - cell_index inside block (from 0 to nbCellsPerLeaf-1) + const auto i_oct = global_index / nbCellsPerLeaf; + const auto i_cell = static_cast(global_index - i_oct * nbCellsPerLeaf); + + const auto & bSize = res.block_size(); + + auto ijk = cellindex_to_coord(i_cell, bSize); + + // copy value from inner block to res + res(ijk, 0, i_oct) = data(ijk, ivar, i_oct); + }); + + return res; + + } // run + +}; // struct ExtractNonGhostedArray + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_EXTRACT_NON_GHOSTED_ARRAY_H_ diff --git a/src/kalypsso/core/FaceDataArrayBlock.h b/src/kalypsso/core/FaceDataArrayBlock.h new file mode 100644 index 0000000..63dc50e --- /dev/null +++ b/src/kalypsso/core/FaceDataArrayBlock.h @@ -0,0 +1,932 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file FaceDataArrayBlock.h + */ +#ifndef KALYPSSO_CORE_FACEDATAARRAYBLOCK_H_ +#define KALYPSSO_CORE_FACEDATAARRAYBLOCK_H_ + +#include +#include + +namespace kalypsso +{ + +// ============================================================================================ +// ============================================================================================ +// ============================================================================================ +/** + * A Data container helper to store d-dimensional vector field face-staggered components. + * + * More precisely, taking as example magnetic field used in constraint transport methods: + * - Bx is center on x-faces + * - By is center on y-faces + * - Bz is center on z-faces + * + * \note Currently in kalypsso blocks can only be of equal size in each direction. + * So, for example, in 3D, each component is stored using an array of N x N x (N+1) elements. + * Later, if needed, we could add support for block of independent sizes along each direction. + * + * \note Bz is actually used in 2d. + * + */ +template +class FaceDataArrayBlock +{ +public: + using DataArrayBlock_t = DataArrayBlock; + using FaceDataArrayBlock_t = FaceDataArrayBlock; + + using FaceFlatArray_t = Kokkos::View; + using FaceFlatArrayUnmanaged_t = + Kokkos::View>; + + using ExecutionSpace = typename device_t::execution_space; + + //! tag dispatch class to use when using non-signed index coordinates (i.e. relative to the + //! shifted block origin); when using direct access, valid index must be in the following ranges: + //! i in [shift[IX], shift[IX]+m_bSize_total[IX]] + //! j in [shift[IY], shift[IY]+m_bSize_total[IY]] + //! k in [shift[IZ], shift[IZ]+m_bSize_total[IZ]] + //! + struct DirectAccess + {}; + +private: + //! block size : number of cells in each direction (without counting ghost cells). + block_size_t m_bSize; + + //! space shift in unit of number of cells + //! in each direction, shift must be even if you plan to use prologation operator (using odd + //! integer would be too complex to implement). + //! + //! \note Using a negative shift means we want to have ghost cells on the left of + //! the block of cells. + //! + //! See class DataArrayGhostedBlock which uses the same semantic + shift_t m_shift; + + //! block size : number of cells in each direction (ghost cells included). + //! \note this parameter combined with m_shift defines a window that may completely or partially + //! overlap with the original block of cells (from the AMR mesh). + block_size_t m_bSize_total; + + //! block size used for memory allocation + block_size_t m_bSize_total_allocated; + + //! number of x-faces in each direction - used for flat index computation + block_size_t m_bSize_face_x; + + //! number of y-faces in each direction - used for flat index computation + block_size_t m_bSize_face_y; + + //! number of z-faces in each direction - used for flat index computation + block_size_t m_bSize_face_z; + + //! offset used as starting address of each component of the field inside a block + //! - m_offsets[1] number of x-faces elements per octant + //! - m_offsets[2] number of x-faces and y-faces elements per octant + //! - m_offsets[3] total num of faces elements (x,y and z) per octant + face_flat_index_offset_t m_offsets; + + //! total number of quadrants (owned, MPI ghost, outside, outside_ghosts) + //! may changed when resizing + int m_num_quadrants; + + //! storage capacity (should be larger or equal to m_offsets[3] * num_quads). + //! actual (physical) number of elements in m_storage + //! it is increased upon resizing only when necessary + size_t m_storage_capacity; + + //! 1D storage array, it should contain enough space to hold m_data_x, m_data_y and m_data_z + //! may be reallocated when the number of octants changes + FaceFlatArray_t m_storage_data; + +public: + FaceDataArrayBlock() = default; + + /** + * Constructor without ghost cells. + * + * \param[in] name is a label used to name the underlying Kokkos::View + * \param[in] bSize is the cell block size + * \param[in] num_quadrants number of quadrant (used for memory allocation) + */ + FaceDataArrayBlock(std::string name, block_size_t bSize, int num_quadrants) + : m_bSize(bSize) + , m_shift(get_shift(0)) + , m_bSize_total(bSize) + , m_bSize_total_allocated(bSize) + , m_bSize_face_x(get_face_block_size(bSize)) + , m_bSize_face_y(get_face_block_size(bSize)) + , m_bSize_face_z(get_face_block_size(bSize)) + , m_offsets(compute_face_flat_index_offsets(bSize)) + , m_num_quadrants(num_quadrants) + , m_storage_capacity( + DataArrayUtils::allocated_capacity(static_cast(m_offsets[3] * num_quadrants))) + , m_storage_data(Kokkos::view_alloc(Kokkos::WithoutInitializing, name), m_storage_capacity) + {} // FaceDataArrayBlock + + /** + * Constructor with a uniform layer of ghost cells all around. + * + * \param[in] name is a label used to name the underlying Kokkos::View + * \param[in] bSize is the cell block size + * \param[in] ghostwidth is the ghost width (thickness) in unit of cell + * \param[in] num_quadrants number of quadrant (used for memory allocation) + */ + FaceDataArrayBlock(std::string name, + block_size_t bSize, + int32_t ghostwidth, + int num_quadrants) + : m_bSize(bSize) + , m_shift(get_shift(-ghostwidth)) + , m_bSize_total(bSize + 2 * ghostwidth) + , m_bSize_total_allocated(bSize + 2 * ghostwidth) + , m_bSize_face_x(get_face_block_size(bSize + 2 * ghostwidth)) + , m_bSize_face_y(get_face_block_size(bSize + 2 * ghostwidth)) + , m_bSize_face_z(get_face_block_size(bSize + 2 * ghostwidth)) + , m_offsets(compute_face_flat_index_offsets(bSize + 2 * ghostwidth)) + , m_num_quadrants(num_quadrants) + , m_storage_capacity( + DataArrayUtils::allocated_capacity(static_cast(m_offsets[3] * num_quadrants))) + , m_storage_data(Kokkos::view_alloc(Kokkos::WithoutInitializing, name), m_storage_capacity) + { + KOKKOS_ASSERT(ghostwidth >= 0 and + "[FaceDataArrayBlock::FaceDataArrayBlock] ghostwidth has wrong value"); + } // FaceDataArrayBlock + + /** + * Constructor with a custom layer of ghost cells all around. + * + * Useful e.g. when one want to have ghost faces only in one direction. + * + * \param[in] name is a label used to name the underlying Kokkos::View + * \param[in] bSize is the cell block size (ghosts non-included) + * \param[in] bSize_total is the cell block size (ghosts included) + * \param[in] shift in unit of cell + * \param[in] num_quadrants number of quadrant (used for memory allocation) + */ + FaceDataArrayBlock(std::string name, + block_size_t bSize, + block_size_t bSize_total, + shift_t shift, + int num_quadrants) + : m_bSize(bSize) + , m_shift(shift) + , m_bSize_total(bSize_total) + , m_bSize_total_allocated(bSize_total) + , m_bSize_face_x(get_face_block_size(bSize_total)) + , m_bSize_face_y(get_face_block_size(bSize_total)) + , m_bSize_face_z(get_face_block_size(bSize_total)) + , m_offsets(compute_face_flat_index_offsets(bSize_total)) + , m_num_quadrants(num_quadrants) + , m_storage_capacity( + DataArrayUtils::allocated_capacity(static_cast(m_offsets[3] * num_quadrants))) + , m_storage_data(Kokkos::view_alloc(Kokkos::WithoutInitializing, name), m_storage_capacity) + {} // FaceDataArrayBlock + + /** + * accumulate over all direction the number of face elements in that direction per octant. + */ + KOKKOS_FORCEINLINE_FUNCTION auto + offsets() const + { + return m_offsets; + } // offsets + + /** + * convert a logical multi-index (i, j, iOct) into a flat index to storage. + * + * \note IMPORTANT multi-index must be non-shifted positive integer. + */ + template + KOKKOS_FORCEINLINE_FUNCTION int64_t + flat_index(I0 i0, I1 i1, IOct iOct) const + { + if constexpr (dir == IX) + { + KOKKOS_ASSERT((static_cast(i0) < m_bSize_face_x[IX]) and + (static_cast(i0) >= 0) and + "[FaceDataArrayBlock::flat_index] wrong index i0"); + KOKKOS_ASSERT((static_cast(i1) < m_bSize_face_x[IY]) and + (static_cast(i1) >= 0) and + "[FaceDataArrayBlock::flat_index] wrong index i1"); + KOKKOS_ASSERT((static_cast(iOct) < m_num_quadrants) and + "[FaceDataArrayBlock::flat_index] wrong index iOct"); + + return m_offsets[0] + i0 + m_bSize_face_x[IX] * i1 + iOct * m_offsets[3]; + } + else if constexpr (dir == IY) + { + KOKKOS_ASSERT((static_cast(i0) < m_bSize_face_y[IX]) and + (static_cast(i0) >= 0) and + "[FaceDataArrayBlock::flat_index] wrong index i0"); + KOKKOS_ASSERT((static_cast(i1) < m_bSize_face_y[IY]) and + (static_cast(i1) >= 0) and + "[FaceDataArrayBlock::flat_index] wrong index i1"); + KOKKOS_ASSERT(static_cast(iOct) < m_num_quadrants and + "[FaceDataArrayBlock::flat_index] wrong index iOct"); + + return m_offsets[1] + i0 + m_bSize_face_y[IX] * i1 + iOct * m_offsets[3]; + } + else if constexpr (dir == IZ) + { + KOKKOS_ASSERT((static_cast(i0) < m_bSize_face_z[IX]) and + (static_cast(i0) >= 0) and + "[FaceDataArrayBlock::flat_index] wrong index i0"); + KOKKOS_ASSERT((static_cast(i1) < m_bSize_face_z[IY]) and + (static_cast(i1) >= 0) and + "[FaceDataArrayBlock::flat_index] wrong index i1"); + KOKKOS_ASSERT(static_cast(iOct) < m_num_quadrants and + "[FaceDataArrayBlock::flat_index] wrong index iOct"); + + return m_offsets[2] + i0 + m_bSize_face_z[IX] * i1 + iOct * m_offsets[3]; + } + return 0; + } // flat_index + + /** + * convert a logical multi-index (i, j, k, iOct) into a flat index to storage. + * + * \note IMPORTANT multi-index must be non-shifted positive integer. + */ + template + KOKKOS_FORCEINLINE_FUNCTION int64_t + flat_index(I0 i0, I1 i1, I2 i2, IOct iOct) const + { + if constexpr (dir == IX) + { + KOKKOS_ASSERT((static_cast(i0) < m_bSize_face_x[IX]) and + (static_cast(i0) >= 0) and + "[FaceDataArrayBlock::flat_index] wrong index i0"); + KOKKOS_ASSERT((static_cast(i1) < m_bSize_face_x[IY]) and + (static_cast(i1) >= 0) and + "[FaceDataArrayBlock::flat_index] wrong index i1"); + KOKKOS_ASSERT((static_cast(i2) < m_bSize_face_x[IZ]) and + (static_cast(i2) >= 0) and + "[FaceDataArrayBlock::flat_index] wrong index i2"); + KOKKOS_ASSERT(static_cast(iOct) < m_num_quadrants and + "[FaceDataArrayBlock::flat_index] wrong index iOct"); + + return m_offsets[0] + i0 + m_bSize_face_x[IX] * (i1 + m_bSize_face_x[IY] * i2) + + iOct * m_offsets[3]; + } + else if constexpr (dir == IY) + { + KOKKOS_ASSERT((static_cast(i0) < m_bSize_face_y[IX]) and + (static_cast(i0) >= 0) and + "[FaceDataArrayBlock::flat_index] wrong index i0"); + KOKKOS_ASSERT((static_cast(i1) < m_bSize_face_y[IY]) and + (static_cast(i1) >= 0) and + "[FaceDataArrayBlock::flat_index] wrong index i1"); + KOKKOS_ASSERT((static_cast(i2) < m_bSize_face_y[IZ]) and + (static_cast(i2) >= 0) and + "[FaceDataArrayBlock::flat_index] wrong index i2"); + KOKKOS_ASSERT(static_cast(iOct) < m_num_quadrants and + "[FaceDataArrayBlock::flat_index] wrong index iOct"); + + return m_offsets[1] + i0 + m_bSize_face_y[IX] * (i1 + m_bSize_face_y[IY] * i2) + + iOct * m_offsets[3]; + } + else if constexpr (dir == IZ) + { + KOKKOS_ASSERT((static_cast(i0) < m_bSize_face_z[IX]) and + (static_cast(i0) >= 0) and + "[FaceDataArrayBlock::flat_index] wrong index i0"); + KOKKOS_ASSERT((static_cast(i1) < m_bSize_face_z[IY]) and + (static_cast(i1) >= 0) and + "[FaceDataArrayBlock::flat_index] wrong index i1"); + KOKKOS_ASSERT((static_cast(i2) < m_bSize_face_z[IZ]) and + (static_cast(i2) >= 0) and + "[FaceDataArrayBlock::flat_index] wrong index i2"); + KOKKOS_ASSERT(static_cast(iOct) < m_num_quadrants and + "[FaceDataArrayBlock::flat_index] wrong index iOct"); + + return m_offsets[2] + i0 + m_bSize_face_z[IX] * (i1 + m_bSize_face_z[IY] * i2) + + iOct * m_offsets[3]; + } + return 0; + } // flat_index + + // ================================================================================== + //! + //! Regular memory access operator() - 2d case. + //! + template + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(I0 i0, I1 i1, IDir dir, IOct iOct) const + { + KOKKOS_ASSERT((dir == IX or dir == IY or dir == IZ) and "FaceDataArrayBlock: wrong direction"); + KOKKOS_ASSERT((static_cast(iOct) < m_num_quadrants) and + "FaceDataArrayBlock: index iOct too large"); + + if (dir == IX) + return m_storage_data(flat_index(i0 - m_shift[IX], i1 - m_shift[IY], iOct)); + else if (dir == IY) + return m_storage_data(flat_index(i0 - m_shift[IX], i1 - m_shift[IY], iOct)); + else if (dir == IZ) + return m_storage_data(flat_index(i0 - m_shift[IX], i1 - m_shift[IY], iOct)); + else // shouldn't be here + return m_storage_data(flat_index(i0 - m_shift[IX], i1 - m_shift[IY], iOct)); + } + + // ================================================================================== + //! + //! Direct memory access operator() - 2d case. + //! + template + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(I0 i0, I1 i1, IDir dir, IOct iOct, DirectAccess const &) const + { + KOKKOS_ASSERT((dir == IX or dir == IY or dir == IZ) and "FaceDataArrayBlock: wrong direction"); + KOKKOS_ASSERT((static_cast(iOct) < m_num_quadrants) and + "FaceDataArrayBlock: index iOct too large"); + + if (dir == IX) + return m_storage_data(flat_index(i0, i1, iOct)); + else if (dir == IY) + return m_storage_data(flat_index(i0, i1, iOct)); + else if (dir == IZ) + return m_storage_data(flat_index(i0, i1, iOct)); + else // shouldn't be here + return m_storage_data(flat_index(i0, i1, iOct)); + } + + // ================================================================================== + //! + //! Regular memory access operator() - 3d case. + //! + template + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(I0 i0, I1 i1, I2 i2, IDir dir, IOct iOct) const + { + KOKKOS_ASSERT((dir == IX or dir == IY or dir == IZ) and "FaceDataArrayBlock: wrong direction"); + KOKKOS_ASSERT((static_cast(iOct) < m_num_quadrants) and + "FaceDataArrayBlock: index iOct too large"); + + if (dir == IX) + return m_storage_data( + flat_index(i0 - m_shift[IX], i1 - m_shift[IY], i2 - m_shift[IZ], iOct)); + else if (dir == IY) + return m_storage_data( + flat_index(i0 - m_shift[IX], i1 - m_shift[IY], i2 - m_shift[IZ], iOct)); + else if (dir == IZ) + return m_storage_data( + flat_index(i0 - m_shift[IX], i1 - m_shift[IY], i2 - m_shift[IZ], iOct)); + else // shouldn't be here + return m_storage_data( + flat_index(i0 - m_shift[IX], i1 - m_shift[IY], i2 - m_shift[IZ], iOct)); + } + + // ================================================================================== + //! + //! Direct memory access operator() - 3d case. + //! + template + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(I0 i0, I1 i1, I2 i2, IDir dir, IOct iOct, DirectAccess const &) const + { + KOKKOS_ASSERT((dir == IX or dir == IY or dir == IZ) and "FaceDataArrayBlock: wrong direction"); + KOKKOS_ASSERT((static_cast(iOct) < m_num_quadrants) and + "FaceDataArrayBlock: index iOct too large"); + + if (dir == IX) + return m_storage_data(flat_index(i0, i1, i2, iOct)); + else if (dir == IY) + return m_storage_data(flat_index(i0, i1, i2, iOct)); + else if (dir == IZ) + return m_storage_data(flat_index(i0, i1, i2, iOct)); + else // shouldn't be here + return m_storage_data(flat_index(i0, i1, i2, iOct)); + } + + // ================================================================================== + //! + //! Regular memory access operator() - 2d and 3d case. + //! + template + KOKKOS_FORCEINLINE_FUNCTION T & + operator()(face_multiindex_t const & face_index, IOct iOct) const + { + + if constexpr (dim == 2) + { + return this->operator()(face_index[IX], face_index[IY], face_index[dim], iOct); + } + else if constexpr (dim == 3) + { + return this->operator()( + face_index[IX], face_index[IY], face_index[IZ], face_index[dim], iOct); + } + } + + // ================================================================================== + //! Access to raw pointer + KOKKOS_FORCEINLINE_FUNCTION + auto + data() const + { + return m_storage_data.data(); + } + + // ================================================================================== + //! return internal Kokkos::View used for raw storage. + //! A regular user should probably never have to use the physical, but most surely the + //! logical_view (with logical sizes). + auto + physical_view() -> decltype(m_storage_data) + { + return m_storage_data; + } + + auto + physical_view_ref() -> decltype(m_storage_data) & + { + return m_storage_data; + } + +public: + // ================================================================================== + //! return logical size (total number of elements). Not to be confused with capacity (physical + //! size) + auto + logical_size_in_elements() const + { + return static_cast(m_offsets[3] * m_num_quadrants); + } + + // ================================================================================== + auto + logical_view() const + { + const auto logical_range = + std::pair(0, this->logical_size_in_elements()); + return Kokkos::subview(m_storage_data, logical_range); + } + + //! Resize m_storage data using a new number of octants. + //! + //! This function resizes data without copying old data, without even initializing the new array. + //! This is enough especially when doing load balancing; data will be initialized by MPI + //! communications. + void + resize(int32_t num_quads) + { + // update number of quadrants + m_num_quadrants = num_quads; + + const size_t new_size = static_cast(m_offsets[3] * m_num_quadrants); + + // only resize when the requested new size is larger than capacity + if (new_size > m_storage_capacity) + { + // new storage capacity + size_t new_storage_capacity = + DataArrayUtils::allocated_capacity(static_cast(m_offsets[3] * num_quads)); + + Kokkos::resize( + Kokkos::view_alloc(Kokkos::WithoutInitializing), m_storage_data, new_storage_capacity); + + m_storage_capacity = new_storage_capacity; + } + + } // resize + + //! Resize m_storage data using a new number of octant. + //! This function resizes data; it doesn't copy old data, but default initialize to zero. + void + resize_and_reset(int32_t num_quads) + { + resize(num_quads); + Kokkos::deep_copy(m_storage_data, 0); + + } // resize_and_reset + + /** + * Change ghosted block size, and shift without reallocating. + * + * We only require that the new shape corresponds to a smaller number of cells. + * + * \return true when the new shape is accepted (i.e. compatible with current memory allocation) + */ + KOKKOS_FORCEINLINE_FUNCTION bool + reshape(block_size_t const & new_bSize_total, shift_t const & new_shift) + { + // when reshaping, as we don't want to reallocate, we need to make sure the new + // ghosted size is not larger than current allocation + // KOKKOS_ASSERT(Kokkos::dim_prod(new_ghosted_size) <= Kokkos::dim_prod(m_data.block_size()) && + // "Can't reshape DataArrayGhostedBlock, new ghosted size is too large."); + + // only reshape when possible + if (compute_total_number_of_faces(new_bSize_total) <= + compute_total_number_of_faces(m_bSize_total_allocated)) + { + + // just make sure the new shift is valid + KOKKOS_ASSERT((new_shift[IX] + m_bSize[IX] / 2 >= 0) && "shift[IX] is too large (negative)"); + KOKKOS_ASSERT((new_shift[IY] + m_bSize[IY] / 2 >= 0) && "shift[IY] is too large (negative)"); + if constexpr (dim == 3) + { + KOKKOS_ASSERT((new_shift[IZ] + m_bSize[IZ] / 2 >= 0) && + "shift[IZ] is too large (negative)"); + } + + KOKKOS_ASSERT((new_shift[IX] <= m_bSize[IX] / 2) && "shift[IX] is too large"); + KOKKOS_ASSERT((new_shift[IY] <= m_bSize[IY] / 2) && "shift[IY] is too large"); + if constexpr (dim == 3) + { + KOKKOS_ASSERT((new_shift[IZ] <= m_bSize[IZ] / 2) && "shift[IZ] is too large"); + } + + // recompute face block sizes and offsets + m_bSize_total = new_bSize_total; + m_bSize_face_x = get_face_block_size(m_bSize_total); + m_bSize_face_y = get_face_block_size(m_bSize_total); + m_bSize_face_z = get_face_block_size(m_bSize_total); + m_offsets = compute_face_flat_index_offsets(m_bSize_total); + + // update shift + m_shift = new_shift; + + return true; + } + + // reshaping can't happen, don't modify neither shape, neither shift + return false; + + } // reshape + + KOKKOS_FORCEINLINE_FUNCTION + auto + num_quadrants() const + { + return m_num_quadrants; + } + + KOKKOS_FORCEINLINE_FUNCTION + int32_t + num_elements_per_octant() const + { + return m_offsets[3]; + } + + KOKKOS_FORCEINLINE_FUNCTION + auto + cell_block_size() const + { + return m_bSize_total; + } + + KOKKOS_FORCEINLINE_FUNCTION + auto + cell_block_size_inner() const + { + return m_bSize; + } + + KOKKOS_FORCEINLINE_FUNCTION + auto + shift() const + { + return m_shift; + } + + KOKKOS_FORCEINLINE_FUNCTION + auto + face_block_size(int dir) const + { + if (dir == IX) + return m_bSize_face_x; + else if (dir == IY) + return m_bSize_face_y; + else if (dir == IZ) + return m_bSize_face_z; + + return m_bSize_face_x; + } + + KOKKOS_FORCEINLINE_FUNCTION + auto + is_non_ghosted() const + { + if constexpr (dim == 2) + { + return m_shift[IX] == 0 and m_shift[IY] == 0 and m_bSize_total == m_bSize; + } + else if constexpr (dim == 3) + { + return m_shift[IX] == 0 and m_shift[IY] == 0 and m_shift[IZ] == 0 and + m_bSize_total == m_bSize; + } + } + + KOKKOS_FORCEINLINE_FUNCTION + auto + is_ghosted() const + { + return !is_non_ghosted(); + } + + auto + label() const + { + return m_storage_data.label(); + } + + auto + allocated_size_in_bytes() const + { + uint64_t size = m_storage_data.extent(0) * sizeof(T); + return size; + } + + /** + * \return maximum number of faces inside a block (ghost cells included). + */ + KOKKOS_FORCEINLINE_FUNCTION + auto + max_num_faces_per_leaf() const + { + return maximum_number_faces_per_leaf(m_bSize_total); + } + + /** + * For a given component (e.g. Bx, By or Bz), get the last valid index in given direction. + * + * \param[in] component (IX, IY, or IZ for Bx, By or Bz) + * \param[in] direction : IX, IY or IZ + * + */ + KOKKOS_FORCEINLINE_FUNCTION + auto + last_valid_index_in_direction(int component, int dir) const + { + // in 2D, dir can only by IX or IY + if constexpr (dim == 2) + { + KOKKOS_ASSERT((dir == IX or dir == IY) and "FaceDataArrayBlock: wrong direction in 2D"); + } + else if constexpr (dim == 3) + { + KOKKOS_ASSERT((dir == IX or dir == IY or dir == IZ) and + "FaceDataArrayBlock: wrong direction in 3D"); + } + + return (dir == component) ? m_bSize_total[dir] : m_bSize_total[dir] - 1; + + } // last_valid_index + + /** + * Just for symmetry with last_valid_index_in_direction + */ + KOKKOS_FORCEINLINE_FUNCTION + auto + first_valid_index_in_direction(int component, int dir) const + { + return 0; + } + +public: + /** + * A static utility to convert a FaceDataArrayBlock_t into a DataArrayBlock_t separating left from + * right faces. + * + * The resulting DataArrayBlock has 6 components: 3 for left faces and 3 for right faces. + */ + static DataArrayBlock_t + to_DataArrayBlock(FaceDataArrayBlock_t fdata) + { + const auto label = fdata.label(); + + const auto bSize_total = fdata.cell_block_size(); + const auto nbCellsPerLeaf = Kokkos::dim_prod(bSize_total); + const int64_t total_num_cells = nbCellsPerLeaf * fdata.num_quadrants(); + // - 2d: 2 left + 2 right + 1 for IZ direction + // - 3d: 3 left + 3 right + const auto num_vars = dim == 3 ? 6 : 5; + + auto res = DataArrayBlock_t(label, bSize_total, num_vars, fdata.num_quadrants()); + + Kokkos::parallel_for( + "to_DataArrayBlock", + Kokkos::RangePolicy(0, total_num_cells), + KOKKOS_LAMBDA(const int64_t & global_index) { + // convert global index into + // - octant id + // - cell_index inside block (from 0 to nbCellsPerLeaf-1) + const auto iOct = global_index / nbCellsPerLeaf; + const auto cell_index = static_cast(global_index - iOct * nbCellsPerLeaf); + + const auto iCoord = cellindex_to_coord(cell_index, bSize_total) + fdata.shift(); + + // the only reason of the following dummy code to be here, is that cuda nvcc compile + // doesn't support capturing variables inside the inner lambda inside a constexpr if + // + // strangely, nvc++ is ok and don't need that + // TODO: remove theses lines when nvcc will have support for this. +#ifdef __NVCC__ + [[maybe_unused]] int dummy = 0; + if ((fdata.num_quadrants() == 0) or (res.num_cells() == 0)) + dummy++; +#endif + + if constexpr (dim == 2) + { + auto const & i = iCoord[IX]; + auto const & j = iCoord[IY]; + + // clang-format off + res(cell_index, 0, iOct) = fdata(i , j , IX, iOct); + res(cell_index, 1, iOct) = fdata(i + 1, j , IX, iOct); + res(cell_index, 2, iOct) = fdata(i , j , IY, iOct); + res(cell_index, 3, iOct) = fdata(i , j + 1, IY, iOct); + res(cell_index, 4, iOct) = fdata(i , j , IZ, iOct); + // clang-format on + } + else + { + auto const & i = iCoord[IX]; + auto const & j = iCoord[IY]; + auto const & k = iCoord[IZ]; + + // clang-format off + res(cell_index, 0, iOct) = fdata(i , j , k , IX, iOct); + res(cell_index, 1, iOct) = fdata(i + 1, j , k , IX, iOct); + res(cell_index, 2, iOct) = fdata(i , j , k , IY, iOct); + res(cell_index, 3, iOct) = fdata(i , j + 1, k , IY, iOct); + res(cell_index, 4, iOct) = fdata(i , j , k , IZ, iOct); + res(cell_index, 5, iOct) = fdata(i , j , k + 1, IZ, iOct); + // clang-format on + } + }); + + return res; + } // FaceDataArrayBlock::to_DataArrayBlock + + /** + * A static utility to convert one component to cell centered data. + * + * \note This should probably only be used when input FacaDataArrayBlock_t is non-ghosted. + */ + static DataArrayBlock_t + to_DataArrayBlockCentered(FaceDataArrayBlock_t fdata, int dir) + { + const std::string type_str("_cell_centered_"); + const auto label = fdata.label() + type_str + std::to_string(dir); + + const auto bSize_total = fdata.cell_block_size(); + const auto nbCellsPerLeaf = Kokkos::dim_prod(bSize_total); + const int64_t total_num_cells = nbCellsPerLeaf * fdata.num_quadrants(); + const auto num_vars = 1; // only one value per cell + + auto res = DataArrayBlock_t(label, bSize_total, num_vars, fdata.num_quadrants()); + + // compute unit vector associated to direction + const auto unit_vector = [](int direction) { + Kokkos::Array v; + for (int i = 0; i < static_cast(dim); ++i) + { + v[i] = i == direction ? 1 : 0; + } + return v; + }(dir); + + Kokkos::parallel_for( + "to_DataArrayBlock", + Kokkos::RangePolicy(0, total_num_cells), + KOKKOS_LAMBDA(const int64_t & global_index) { + // convert global index into + // - octant id + // - cell_index inside block (from 0 to nbCellsPerLeaf-1) + const auto iOct = global_index / nbCellsPerLeaf; + const auto cell_index = global_index - iOct * nbCellsPerLeaf; + + const auto iCoord = + cellindex_to_coord(static_cast(cell_index), bSize_total) + fdata.shift(); + + // the only reason of the following dummy code to be here, is that cuda nvcc compile + // doesn't support capturing variables inside the inner lambda inside a constexpr if + // + // strangely, nvc++ is ok and don't need that + // TODO: remove theses lines when nvcc will have support for this. +#ifdef __NVCC__ + [[maybe_unused]] int dummy = 0; + if ((fdata.num_quadrants() == 0) or (res.num_cells() == 0) or dir != dir or + unit_vector[0] != unit_vector[0]) + dummy++; +#endif + + if constexpr (dim == 2) + { + auto const & i = iCoord[IX]; + auto const & j = iCoord[IY]; + + res(cell_index, 0, iOct) = + HALF_F * + (fdata(i, j, dir, iOct) + fdata(i + unit_vector[IX], j + unit_vector[IY], dir, iOct)); + } + else + { + auto const & i = iCoord[IX]; + auto const & j = iCoord[IY]; + auto const & k = iCoord[IZ]; + + res(cell_index, 0, iOct) = + HALF_F * + (fdata(i, j, k, dir, iOct) + + fdata(i + unit_vector[IX], j + unit_vector[IY], k + unit_vector[IZ], dir, iOct)); + } + }); + + return res; + } // FaceDataArrayBlock::to_DataArrayBlockCentered + + /** + * Compute divergence of a FaceDataArrayBlock. + * + * \param[in] fdata is the face data array + * \param[in] orchard_keys + * + * \note This should probably only be used when input FacaDataArrayBlock_t is non-ghosted. + * + */ + static DataArrayBlock_t + compute_divergence(FaceDataArrayBlock_t fdata, + typename orchard_key_base_t::view_t orchard_keys) + { + const std::string div("divergence_of_"); + const auto label = div + fdata.label(); + + const auto bSize_total = fdata.cell_block_size(); + const auto nbCellsPerLeaf = Kokkos::dim_prod(bSize_total); + const int64_t total_num_cells = nbCellsPerLeaf * fdata.num_quadrants(); + const auto num_vars = 1; // only one value per cell ( divergence is a scalar ) + + auto res = DataArrayBlock_t(label, bSize_total, num_vars, fdata.num_quadrants()); + + Kokkos::parallel_for( + "compute divergence of FaceDataArrayBlock", + Kokkos::RangePolicy(0, total_num_cells), + KOKKOS_LAMBDA(const int64_t & global_index) { + // convert global index into + // - octant id + // - cell_index inside block (from 0 to nbCellsPerLeaf-1) + const auto iOct = global_index / nbCellsPerLeaf; + const auto cell_index = global_index - iOct * nbCellsPerLeaf; + + // get block level + const auto level = orchard_key_t::level(orchard_keys(iOct)); + + const auto iCoord = + cellindex_to_coord(static_cast(cell_index), bSize_total) + fdata.shift(); + + // the only reason of the following dummy code to be here, is that cuda nvcc compile + // doesn't support capturing variables inside the inner lambda inside a constexpr if + // + // strangely, nvc++ is ok and don't need that + // TODO: remove theses lines when nvcc will have support for this. +#ifdef __NVCC__ + [[maybe_unused]] int dummy = 0; + if (fdata.num_quadrants() == 0 or res.num_cells() == 0) + dummy++; +#endif + + if constexpr (dim == 2) + { + auto const & i = iCoord[IX]; + auto const & j = iCoord[IY]; + const auto dx = compute_cell_length(level, bSize_total[IX]); + const auto & dy = dx; // assuming square cells + + res(cell_index, 0, iOct) = (fdata(i + 1, j, IX, iOct) - fdata(i, j, IX, iOct)) / dx + + (fdata(i, j + 1, IY, iOct) - fdata(i, j, IY, iOct)) / dy; + } + else if constexpr (dim == 3) + { + auto const & i = iCoord[IX]; + auto const & j = iCoord[IY]; + auto const & k = iCoord[IZ]; + + const auto dx = compute_cell_length(level, bSize_total[IX]); + const auto & dy = dx; // assuming square cells + const auto & dz = dx; // assuming square cells + + res(cell_index, 0, iOct) = + (fdata(i + 1, j, k, IX, iOct) - fdata(i, j, k, IX, iOct)) / dx + + (fdata(i, j + 1, k, IY, iOct) - fdata(i, j, k, IY, iOct)) / dy + + (fdata(i, j, k + 1, IZ, iOct) - fdata(i, j, k, IZ, iOct)) / dz; + } + }); + + return res; + + } // FaceDataArrayBlock::compute_divergence + +}; // FaceDataArrayBlock + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_FACEDATAARRAYBLOCK_H_ diff --git a/src/kalypsso/core/FaceDataArrayBlock_utils.h b/src/kalypsso/core/FaceDataArrayBlock_utils.h new file mode 100644 index 0000000..26fe8e5 --- /dev/null +++ b/src/kalypsso/core/FaceDataArrayBlock_utils.h @@ -0,0 +1,299 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file FaceDataArrayBlock_utils.h + * + * Some utility functions used by FaceDataArrayBlock + */ +#ifndef KALYPSSO_CORE_FACEDATAARRAYBLOCK_UTILS_H_ +#define KALYPSSO_CORE_FACEDATAARRAYBLOCK_UTILS_H_ + +#include +#include // for KOKKOS_ENABLE_XXX + +#include // for definition of assertm +#include +#include + +#include // for definition of type block_size_t +#include + +#include +#include + +namespace kalypsso +{ + +/** + * A type alias used to store index offset indicating where the different type of face (X, Y or Z + * faces) start in a FaceDataArrayBlock. + */ +using face_flat_index_offset_t = Kokkos::Array; + +// ============================================================================================ +// ============================================================================================ +/** + * For a given face direction, compute the logical face array sizes associated + * to a block of cells of size bSize. + * + * \param[in] cell block size (number of cells per direction) + * + * \return block size (number of faces in direction "dir") per direction + * + * \tparam dim is dimension (2 or 3) + * \tparam dir is the face direction + */ +template +KOKKOS_FORCEINLINE_FUNCTION block_size_t + get_face_block_size(block_size_t bSize) +{ + block_size_t res = bSize; + + res[IX] += (dir == IX) ? 1 : 0; + res[IY] += (dir == IY) ? 1 : 0; + + if constexpr (dim == 3) + { + res[IZ] += (dir == IZ) ? 1 : 0; + } + + return res; +} // get_face_block_size + +// ============================================================================================ +// ============================================================================================ +//! compute the maximum number of faces (among all direction) in a block of cell of size bSize. +//! +//! - when the block is square (equal dimension in all direction), the result is exact +//! - when the block is rectangular, the value is rounded up +//! +//! Purpose: allocate an array that can be addressed with the same i,j,k bounds for x,y,z faces +//! +//! \param[in] bSize cell block size (number of cells per direction) +template +KOKKOS_FORCEINLINE_FUNCTION auto +maximum_number_faces_per_leaf(block_size_t bSize) +{ + + // optimization for the case block has equal sizes in all direction + if constexpr (dim == 2) + { + if (bSize[IX] == bSize[IY]) + { + return (bSize[IX] + 1) * bSize[IX]; + } + } + else if constexpr (dim == 3) + { + if ((bSize[IX] == bSize[IY]) and (bSize[IX] == bSize[IZ])) + { + return (bSize[IX] + 1) * bSize[IX] * bSize[IX]; + } + } + + // general case : a rectangular block + return dim == 2 ? (bSize[IX] + 1) * (bSize[IY] + 1) + : (bSize[IX] + 1) * (bSize[IY] + 1) * (bSize[IZ] + 1); + +} // maximum_number_faces_per_leaf + +// ============================================================================================ +// ============================================================================================ +/** + * accumulate over direction the number of face elements in that direction per octant. + * + * \param[in] bSize cell block size (number of cells per direction) + * + */ +template +KOKKOS_FORCEINLINE_FUNCTION auto +compute_face_flat_index_offsets(block_size_t bSize) -> face_flat_index_offset_t +{ + face_flat_index_offset_t offset; + + offset[0] = 0; + offset[1] = offset[0] + Kokkos::dim_prod(get_face_block_size(bSize)); + offset[2] = offset[1] + Kokkos::dim_prod(get_face_block_size(bSize)); + offset[3] = offset[2] + Kokkos::dim_prod(get_face_block_size(bSize)); + + return offset; +} // compute_face_flat_index_offsets + +// ============================================================================================ +// ============================================================================================ +/** + * Compute total number of faces over all directions. + * + * \param[in] bSize cell block size (number of cells per direction) + * + */ +template +KOKKOS_FORCEINLINE_FUNCTION auto +compute_total_number_of_faces(block_size_t bSize) +{ + int32_t res = 0; + + res += Kokkos::dim_prod(get_face_block_size(bSize)); + res += Kokkos::dim_prod(get_face_block_size(bSize)); + res += Kokkos::dim_prod(get_face_block_size(bSize)); + + return res; +} // compute_face_flat_index_offsets + +// ============================================================================================ +// ============================================================================================ +/** + * Decode i,j,k, face_type from a flat index (result are positive integers). + * + * \param[in] flat_index is a face index in range [0, face_flat_index_offset[3]) + * \param[in] block_size is cell block size + * \param[in] face_flat_index_offsets array of cumulative number of faces over direction + * + * \return face_multiindex (i,j,k,face_type) corresponding to a given flat index + */ +template +KOKKOS_FORCEINLINE_FUNCTION auto +face_flat_index_unravel_no_shift(int32_t flat_index, + block_size_t const & block_size, + face_flat_index_offset_t const & face_flat_index_offsets) + -> face_multiindex_t +{ + KOKKOS_ASSERT(flat_index >= 0 and "flat_index must be positive"); + KOKKOS_ASSERT(flat_index < face_flat_index_offsets[3] and "flat index invalid (too large) !"); + + face_multiindex_t index; + + // determine face type + index[dim] = flat_index < face_flat_index_offsets[1] ? IX + : flat_index < face_flat_index_offsets[2] ? IY + : IZ; + + if constexpr (dim == 2) + { + if (index[dim] == IX) + { + // flat_index = flat_index_offsets[0] + i + (block_size[IX]+1)*j + // remember that flat_index_offsets[0] = 0 by design + index[IY] = flat_index / (block_size[IX] + 1); + index[IX] = flat_index - index[IY] * (block_size[IX] + 1); + } + else if (index[dim] == IY) + { + // flat_index = flat_index_offsets[1] + i + block_size[IX]*j + flat_index -= face_flat_index_offsets[1]; + index[IY] = flat_index / (block_size[IX]); + index[IX] = flat_index - index[IY] * (block_size[IX]); + } + else + { + // flat_index = flat_index_offsets[2] + i + block_size[IX] * j + flat_index -= face_flat_index_offsets[2]; + index[IY] = flat_index / (block_size[IX]); + index[IX] = flat_index - index[IY] * (block_size[IX]); + } + } + else if constexpr (dim == 3) + { + if (index[dim] == IX) + { + // flat_index = face_flat_index_offsets[0] + i + (block_size[IX]+1) * j + + // (block_size[IX]+1)*block_size[IY] * k + // remember that flat_index_offsets[0] = 0 by design + index[IZ] = flat_index / ((block_size[IX] + 1) * block_size[IY]); + flat_index -= (block_size[IX] + 1) * block_size[IY] * index[IZ]; + index[IY] = flat_index / (block_size[IX] + 1); + index[IX] = flat_index - index[IY] * (block_size[IX] + 1); + } + else if (index[dim] == IY) + { + // flat_index = flat_index_offsets[1] + i + block_size[IX]*j + block_size[IX] * + // (block_size[IY]+1) * k + flat_index -= face_flat_index_offsets[1]; + index[IZ] = flat_index / (block_size[IX] * (block_size[IY] + 1)); + flat_index -= block_size[IX] * (block_size[IY] + 1) * index[IZ]; + index[IY] = flat_index / block_size[IX]; + index[IX] = flat_index - index[IY] * (block_size[IX]); + } + else + { + // flat_index = flat_index_offsets[2] + i + block_size[IX]*j + block_size[IX] * + // block_size[IY] * k + flat_index -= face_flat_index_offsets[2]; + index[IZ] = flat_index / (block_size[IX] * block_size[IY]); + flat_index -= block_size[IX] * block_size[IY] * index[IZ]; + index[IY] = flat_index / block_size[IX]; + index[IX] = flat_index - index[IY] * block_size[IX]; + } + } + + return index; +} // face_flat_index_unravel_no_shift + +// ============================================================================================ +// ============================================================================================ +/** + * Decode i,j,k, face_type from a flat index (results are shifted). + * + * \param[in] flat_index is a face index in range [0, face_flat_index_offset[3]) + * \param[in] block_size is cell block size + * \param[in] face_flat_index_offsets array of cumulative number of faces over direction + * \param[in] shift + * + * \return face_multiindex (i,j,k,face_type) corresponding to a given flat index + */ +template +KOKKOS_FORCEINLINE_FUNCTION auto +face_flat_index_unravel(int32_t flat_index, + block_size_t const & block_size, + face_flat_index_offset_t const & face_flat_index_offsets, + coord_t const shift) -> face_multiindex_t +{ + auto res = face_flat_index_unravel_no_shift(flat_index, block_size, face_flat_index_offsets); + + res[IX] += shift[IX]; + res[IY] += shift[IY]; + if constexpr (dim == 3) + { + res[IZ] += shift[IZ]; + } + return res; +} + +// ============================================================== +// ============================================================== +/** + * face coordinates to cell coordinates. + * + * \param[in] face_indexes a face multi-index (i,j,k,face_type) + * \param[in] block_size is cell block size + * + * \return cell multi-index (i,j,k) + */ +template +KOKKOS_INLINE_FUNCTION coord_t +face_to_cell_coords(face_multiindex_t const & face_indexes, block_size_t const & bSizes) +{ + if constexpr (dim == 2) + { + // clang-format off + const coord_t<2> ijk{ face_indexes[IX] == bSizes[IX] ? face_indexes[IX] - 1 : face_indexes[IX], + face_indexes[IY] == bSizes[IY] ? face_indexes[IY] - 1 : face_indexes[IY] }; + // clang-format on + return ijk; + } + else if constexpr (dim == 3) + { + // clang-format off + const coord_t<3> ijk{ face_indexes[IX] == bSizes[IX] ? face_indexes[IX] - 1 : face_indexes[IX], + face_indexes[IY] == bSizes[IY] ? face_indexes[IY] - 1 : face_indexes[IY], + face_indexes[IZ] == bSizes[IZ] ? face_indexes[IZ] - 1 : face_indexes[IZ] }; + // clang-format on + return ijk; + } +} // face_to_cell_coords + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_FACEDATAARRAYBLOCK_UTILS_H_ diff --git a/src/kalypsso/core/FieldMap.h b/src/kalypsso/core/FieldMap.h new file mode 100644 index 0000000..7bc1117 --- /dev/null +++ b/src/kalypsso/core/FieldMap.h @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file FieldMap.h + * \brief Define class FieldMap + * + * \date July, 11th 2022 + */ +#ifndef KALYPSSO_CORE_FIELDMAP_H_ +#define KALYPSSO_CORE_FIELDMAP_H_ + +#include +#include + +#include +#include + +namespace kalypsso +{ + +//! a convenience alias to map variable names to id +using names2id_t = std::unordered_map; + +//! same as above but mapping memory index (instead of enum) +using names2index_t = std::unordered_map; + +//! a convenience alias to map id to variable names +using id2names_t = std::unordered_map; + +/** + * A simple class to map id (enum) to index for a given model. + * + * the actual model is responsible for activating / deactivating some scalar fields. + * This map is used to access user data in DataArray, by "Id" instead of index. + * + * The main purpose of this class is to provide operator[]. + * eg: + * Kokkos::View data; + * Hydro::Settings setting(...); + * Hydro hydro_model(settings); + * auto fm = + * + * To clarify again: + * - id is an enum value + * - index is an integer between 0 and scalarFieldNb-1, + * used to access a DataArray + * + * \tparam Model must define an enum called VarId + * + * This class can be used inside a Kokkos parallel region. + */ +template +class FieldMap +{ + +public: + using VarId = typename Model::VarId; + using Id_t = typename Model::Id_t; + static constexpr Id_t VARCOUNT = VarId::VARID_COUNT; + +private: + Kokkos::Array id2index{}; + Kokkos::Array field_enabled{}; + int m_nbfields = 0; + +public: + void + enable(VarId id) + { + id2index[id] = m_nbfields; + + // we add field only if field is not already enabled + if (!field_enabled[id]) + { + field_enabled[id] = true; + m_nbfields++; + } + } + + std::set + enabled_fields() const + { + std::set res; + for (int i = 0; i < VARCOUNT; ++i) + if (field_enabled[i]) + res.insert(static_cast(i)); + return res; + } + + KOKKOS_INLINE_FUNCTION + int + nbfields() const + { + return m_nbfields; + } + + KOKKOS_INLINE_FUNCTION + int + operator[](VarId id) const + { + assert(field_enabled[id]); // This variable is not active + return id2index[id]; + } + + KOKKOS_INLINE_FUNCTION + bool + enabled(VarId id) const + { + return field_enabled[id]; + } + +}; // class FieldMap + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_FIELDMAP_H_ diff --git a/src/kalypsso/core/FillBlockGhostCells.cpp b/src/kalypsso/core/FillBlockGhostCells.cpp new file mode 100644 index 0000000..2a8ede7 --- /dev/null +++ b/src/kalypsso/core/FillBlockGhostCells.cpp @@ -0,0 +1,640 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file FillBlockGhostCells.cpp + * \brief \copybrief FillBlockGhostCells.h + */ +#include + +namespace kalypsso +{ +// ================================================================================================ +// ================================================================================================ +template +void +FillBlockGhostCellsFunctor::check_args_validity( + DataArrayBlock_t userdata_in, + CellCenteredProlongationType prolongation_type) +{ + if (prolongation_type == +CellCenteredProlongationType::CONSERVATIVE_INTERPOLATION_ORDER_2) + { + { + const auto & b = userdata_in.block_size(); + bool invalid_size = b[IX] < 4 or b[IY] < 4; + if constexpr (dim == 3) + { + invalid_size = invalid_size or b[IZ] < 5; + } + if (invalid_size) + Kokkos::abort("Userdata block size is must have at least 4 cells in direction"); + } + } + if (prolongation_type == +CellCenteredProlongationType::CONSERVATIVE_INTERPOLATION_ORDER_4) + { + { + const auto & b = userdata_in.block_size(); + bool invalid_size = b[IX] < 6 or b[IY] < 6; + if constexpr (dim == 3) + { + invalid_size = invalid_size or b[IZ] < 6; + } + if (invalid_size) + Kokkos::abort("Userdata block size is must have at least 6 cells in direction"); + } + } +} // FillBlockGhostCellsFunctor::check_args_validity + +// ============================================================== +// ============================================================== +template +void +FillBlockGhostCellsFunctor::apply(ConfigMap const & config_map, + amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + [[maybe_unused]] int32_t local_num_octants, + int32_t iOct_begin, + int32_t num_octants, + DataArrayBlock_t userdata_in, + DataArrayGhostedBlock_t userdata_out, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic) +{ + + // make sure the range of octants to process is valid + KOKKOS_ASSERT(iOct_begin + num_octants <= local_num_octants && + "Invalid range of octants to process"); + + auto stencil_helper = StencilHelper_t( + amr_hashmap, orchard_keys, userdata_in.block_size(), brick_sizes, is_brick_periodic); + + const auto prolongation_type = get_cell_prolongation_type(config_map); + + check_args_validity(userdata_in, prolongation_type); + + FillBlockGhostCellsFunctor functor( + stencil_helper, iOct_begin, num_octants, userdata_in, userdata_out, prolongation_type); + + const auto nbCellsPerGhostedLeaf = userdata_out.num_cells(); + const auto nbCellsTotal = num_octants * nbCellsPerGhostedLeaf; + + // for AMR tree leaf, explore the neighbor block + Kokkos::parallel_for( + "FillBlockGhostCellsFunctor", Kokkos::RangePolicy(0, nbCellsTotal), functor); + +} // FillBlockGhostCellsFunctor::apply + +// ============================================================== +// ============================================================== +template +template > +KOKKOS_INLINE_FUNCTION void +FillBlockGhostCellsFunctor::linear_extrapolate_using_limited_slopes( + CellLocation<2> const & cell_loc_neigh, + coord_t<2> const & coord_in, + index_t const & cellindex_out, + int32_t const & iOct_global) const +{ + const real_t slope_type = 1; // TODO : investigate if a better value should be searched for + const auto nbvar = m_userdata_in.num_vars(); + + const auto cell_loc_left_x = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(-XDIR)); + const auto cell_loc_right_x = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(+XDIR)); + + const auto cell_loc_left_y = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(-YDIR)); + const auto cell_loc_right_y = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(+YDIR)); + + // determine local position of current cell inside virtual parent cell using integer coordinates + // in {-1, +1} + // + // -------------------------- + // | | | + // | | | + // | -1,1 | 1,1 | + // | | | + // |___________|____________| + // | | | + // | | | + // | -1,-1 | 1,-1 | + // | | | + // |___________|____________| + const int ix = 2 * (coord_in[IX] - 2 * (coord_in[IX] / 2)) - 1; + const int iy = 2 * (coord_in[IY] - 2 * (coord_in[IY] / 2)) - 1; + + for (int32_t ivar = 0; ivar < nbvar; ++ivar) + { + // compute limited slopes + auto const dudx = m_stencil_helper.compute_minmod_slopes( + cell_loc_neigh, cell_loc_right_x, cell_loc_left_x, ivar, m_userdata_in, slope_type); + auto const dudy = m_stencil_helper.compute_minmod_slopes( + cell_loc_neigh, cell_loc_right_y, cell_loc_left_y, ivar, m_userdata_in, slope_type); + + // extrapolate + m_userdata_out(cellindex_out, ivar, iOct_global - m_iOct_begin) = + m_userdata_in( + cell_loc_neigh.cellindex(m_userdata_in.block_size()), ivar, cell_loc_neigh.iOct) + + ONE_FOURTH_F * static_cast(ix) * dudx + ONE_FOURTH_F * static_cast(iy) * dudy; + } +} // FillBlockGhostCellsFunctor::linear_extrapolate_using_limited_slopes - 2d + +// ============================================================== +// ============================================================== +template +template > +KOKKOS_INLINE_FUNCTION void +FillBlockGhostCellsFunctor::linear_extrapolate_using_limited_slopes( + CellLocation<3> const & cell_loc_neigh, + coord_t<3> const & coord_in, + index_t const & cellindex_out, + int32_t const & iOct_global) const +{ + const real_t slope_type = 1; // TODO : investigate if a better value should be searched for + const auto nbvar = m_userdata_in.num_vars(); + + const auto cell_loc_left_x = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(-XDIR)); + const auto cell_loc_right_x = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(+XDIR)); + + const auto cell_loc_left_y = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(-YDIR)); + const auto cell_loc_right_y = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(+YDIR)); + + const auto cell_loc_left_z = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(-ZDIR)); + const auto cell_loc_right_z = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(+ZDIR)); + + // determine local position of current cell inside virtual parent cell using integer coordinates + // in {-1, +1} + // + // -------------------------- + // | | | + // | | | + // | -1,1 | 1,1 | + // | | | + // |___________|____________| + // | | | + // | | | + // | -1,-1 | 1,-1 | + // | | | + // |___________|____________| + const int ix = 2 * (coord_in[IX] - 2 * (coord_in[IX] / 2)) - 1; + const int iy = 2 * (coord_in[IY] - 2 * (coord_in[IY] / 2)) - 1; + const int iz = 2 * (coord_in[IZ] - 2 * (coord_in[IZ] / 2)) - 1; + + for (int32_t ivar = 0; ivar < nbvar; ++ivar) + { + // compute limited slopes + auto const dudx = m_stencil_helper.compute_minmod_slopes( + cell_loc_neigh, cell_loc_right_x, cell_loc_left_x, ivar, m_userdata_in, slope_type); + auto const dudy = m_stencil_helper.compute_minmod_slopes( + cell_loc_neigh, cell_loc_right_y, cell_loc_left_y, ivar, m_userdata_in, slope_type); + auto const dudz = m_stencil_helper.compute_minmod_slopes( + cell_loc_neigh, cell_loc_right_z, cell_loc_left_z, ivar, m_userdata_in, slope_type); + + // extrapolate + m_userdata_out(cellindex_out, ivar, iOct_global - m_iOct_begin) = + m_userdata_in( + cell_loc_neigh.cellindex(m_userdata_in.block_size()), ivar, cell_loc_neigh.iOct) + + ONE_FOURTH_F * static_cast(ix) * dudx + + ONE_FOURTH_F * static_cast(iy) * dudy + ONE_FOURTH_F * static_cast(iz) * dudz; + } + +} // FillBlockGhostCellsFunctor::linear_extrapolate_using_limited_slopes - 3d + +// ============================================================== +// ============================================================== +template +template > +KOKKOS_INLINE_FUNCTION void +FillBlockGhostCellsFunctor::conservative_interpolation_order2( + CellLocation<2> const & cell_loc_neigh, + coord_t<2> const & coord_out, + index_t const & cellindex_out, + int32_t const & iOct_global) const +{ + const auto nbvar = m_userdata_in.num_vars(); + + // determine local position of current cell inside virtual parent cell using integer + // coordinates in {0, 1} + // + // -------------------------- + // | | | + // | | | + // | 0,1 | 1,1 | + // | | | + // |___________|____________| + // | | | + // | | | + // | 0,0 | 1,0 | + // | | | + // |___________|____________| + const int ix = (coord_out[IX] - 2 * (coord_out[IX] / 2)); + const int iy = (coord_out[IY] - 2 * (coord_out[IY] / 2)); + + for (int32_t ivar = 0; ivar < nbvar; ++ivar) + { + real_t val[3]; + + for (int j = -1; j < 2; ++j) + { + // interpolate along X + auto cell_loc_x_m1 = m_stencil_helper.getNeighLoc(cell_loc_neigh, shift_t<2>{ -1, j }); + auto cell_loc_x_0 = m_stencil_helper.getNeighLoc(cell_loc_neigh, shift_t<2>{ 0, j }); + auto cell_loc_x_p1 = m_stencil_helper.getNeighLoc(cell_loc_neigh, shift_t<2>{ 1, j }); + + val[j + 1] = m_stencil_helper.compute_linear_combination(cell_loc_x_m1, + cell_loc_x_0, + cell_loc_x_p1, + cell_loc_neigh.level(), + ivar, + m_userdata_in, + ix == 0 ? m_cons_interpol.COEFS2_L + : m_cons_interpol.COEFS2_R); + } + + m_userdata_out(cellindex_out, ivar, iOct_global - m_iOct_begin) = + m_cons_interpol.order2(val[0], val[1], val[2], iy == 0); + + } // end for ivar + +} // FillBlockGhostCellsFunctor::conservative_interpolation_order2 - 2d + +// ============================================================== +// ============================================================== +template +template > +KOKKOS_INLINE_FUNCTION void +FillBlockGhostCellsFunctor::conservative_interpolation_order2( + CellLocation<3> const & cell_loc_neigh, + coord_t<3> const & coord_out, + index_t const & cellindex_out, + int32_t const & iOct_global) const +{ + const auto nbvar = m_userdata_in.num_vars(); + + // determine local position of current cell inside virtual parent cell using integer coordinates + // in {0, 1} + // -------------------------- + // | | | + // | | | + // | 0,1 | 1,1 | + // | | | + // |___________|____________| + // | | | + // | | | + // | 0,0 | 1,0 | + // | | | + // |___________|____________| + const int ix = (coord_out[IX] - 2 * (coord_out[IX] / 2)); + const int iy = (coord_out[IY] - 2 * (coord_out[IY] / 2)); + const int iz = (coord_out[IZ] - 2 * (coord_out[IZ] / 2)); + + for (int32_t ivar = 0; ivar < nbvar; ++ivar) + { + real_t valx[3][3]; + + for (int k = -1; k < 2; ++k) + { + for (int j = -1; j < 2; ++j) + { + // interpolate along X + auto cell_loc_x_m1 = m_stencil_helper.getNeighLoc(cell_loc_neigh, shift_t<3>{ -1, j, k }); + auto cell_loc_x_0 = m_stencil_helper.getNeighLoc(cell_loc_neigh, shift_t<3>{ 0, j, k }); + auto cell_loc_x_p1 = m_stencil_helper.getNeighLoc(cell_loc_neigh, shift_t<3>{ 1, j, k }); + + valx[k + 1][j + 1] = m_stencil_helper.compute_linear_combination( + cell_loc_x_m1, + cell_loc_x_0, + cell_loc_x_p1, + cell_loc_neigh.level(), + ivar, + m_userdata_in, + ix == 0 ? m_cons_interpol.COEFS2_L : m_cons_interpol.COEFS2_R); + } + } + real_t valy[3]; + + valy[0] = m_cons_interpol.order2(valx[0][0], valx[0][1], valx[0][2], iy == 0); + valy[1] = m_cons_interpol.order2(valx[1][0], valx[1][1], valx[1][2], iy == 0); + valy[2] = m_cons_interpol.order2(valx[2][0], valx[2][1], valx[2][2], iy == 0); + + m_userdata_out(cellindex_out, ivar, iOct_global - m_iOct_begin) = + m_cons_interpol.order2(valy[0], valy[1], valy[2], iz == 0); + + } // end for ivar + +} // FillBlockGhostCellsFunctor::conservative_interpolation_order2 - 3d + +// ============================================================== +// ============================================================== +template +template > +KOKKOS_INLINE_FUNCTION void +FillBlockGhostCellsFunctor::conservative_interpolation_order4( + CellLocation<2> const & cell_loc_neigh, + coord_t<2> const & coord_out, + index_t const & cellindex_out, + int32_t const & iOct_global) const +{ + const auto nbvar = m_userdata_in.num_vars(); + + // determine local position of current cell inside virtual parent cell using integer + // coordinates in {0, 1} + // + // -------------------------- + // | | | + // | | | + // | 0,1 | 1,1 | + // | | | + // |___________|____________| + // | | | + // | | | + // | 0,0 | 1,0 | + // | | | + // |___________|____________| + const int ix = (coord_out[IX] - 2 * (coord_out[IX] / 2)); + const int iy = (coord_out[IY] - 2 * (coord_out[IY] / 2)); + + for (int32_t ivar = 0; ivar < nbvar; ++ivar) + { + real_t val[5]; + + for (int j = -2; j < 3; ++j) + { + // interpolate along X + auto cell_loc_x_m2 = m_stencil_helper.getNeighLoc(cell_loc_neigh, shift_t<2>{ -2, j }); + auto cell_loc_x_m1 = m_stencil_helper.getNeighLoc(cell_loc_neigh, shift_t<2>{ -1, j }); + auto cell_loc_x_0 = m_stencil_helper.getNeighLoc(cell_loc_neigh, shift_t<2>{ 0, j }); + auto cell_loc_x_p1 = m_stencil_helper.getNeighLoc(cell_loc_neigh, shift_t<2>{ 1, j }); + auto cell_loc_x_p2 = m_stencil_helper.getNeighLoc(cell_loc_neigh, shift_t<2>{ 2, j }); + + val[j + 2] = m_stencil_helper.compute_linear_combination(cell_loc_x_m2, + cell_loc_x_m1, + cell_loc_x_0, + cell_loc_x_p1, + cell_loc_x_p2, + cell_loc_neigh.level(), + ivar, + m_userdata_in, + ix == 0 ? m_cons_interpol.COEFS4_L + : m_cons_interpol.COEFS4_R); + } + + m_userdata_out(cellindex_out, ivar, iOct_global - m_iOct_begin) = + m_cons_interpol.order4(val[0], val[1], val[2], val[3], val[4], iy == 0); + + } // end for ivar + +} // FillBlockGhostCellsFunctor::conservative_interpolation_order4 - 2d + +// ============================================================== +// ============================================================== +template +template > +KOKKOS_INLINE_FUNCTION void +FillBlockGhostCellsFunctor::conservative_interpolation_order4( + CellLocation<3> const & cell_loc_neigh, + coord_t<3> const & coord_out, + index_t const & cellindex_out, + int32_t const & iOct_global) const +{ + const auto nbvar = m_userdata_in.num_vars(); + + // determine local position of current cell inside virtual parent cell using integer coordinates + // in {0, 1} + // -------------------------- + // | | | + // | | | + // | 0,1 | 1,1 | + // | | | + // |___________|____________| + // | | | + // | | | + // | 0,0 | 1,0 | + // | | | + // |___________|____________| + const int ix = (coord_out[IX] - 2 * (coord_out[IX] / 2)); + const int iy = (coord_out[IY] - 2 * (coord_out[IY] / 2)); + const int iz = (coord_out[IZ] - 2 * (coord_out[IZ] / 2)); + + for (int32_t ivar = 0; ivar < nbvar; ++ivar) + { + real_t valx[5][5]; + + for (int k = -2; k < 3; ++k) + { + for (int j = -2; j < 3; ++j) + { + // interpolate along X + auto cell_loc_x_m2 = m_stencil_helper.getNeighLoc(cell_loc_neigh, shift_t<3>{ -2, j, k }); + auto cell_loc_x_m1 = m_stencil_helper.getNeighLoc(cell_loc_neigh, shift_t<3>{ -1, j, k }); + auto cell_loc_x_0 = m_stencil_helper.getNeighLoc(cell_loc_neigh, shift_t<3>{ 0, j, k }); + auto cell_loc_x_p1 = m_stencil_helper.getNeighLoc(cell_loc_neigh, shift_t<3>{ 1, j, k }); + auto cell_loc_x_p2 = m_stencil_helper.getNeighLoc(cell_loc_neigh, shift_t<3>{ 2, j, k }); + + valx[k + 2][j + 2] = m_stencil_helper.compute_linear_combination( + cell_loc_x_m2, + cell_loc_x_m1, + cell_loc_x_0, + cell_loc_x_p1, + cell_loc_x_p2, + cell_loc_neigh.level(), + ivar, + m_userdata_in, + ix == 0 ? m_cons_interpol.COEFS4_L : m_cons_interpol.COEFS4_R); + } + } + real_t valy[5]; + + valy[0] = + m_cons_interpol.order4(valx[0][0], valx[0][1], valx[0][2], valx[0][3], valx[0][4], iy == 0); + valy[1] = + m_cons_interpol.order4(valx[1][0], valx[1][1], valx[1][2], valx[1][3], valx[1][4], iy == 0); + valy[2] = + m_cons_interpol.order4(valx[2][0], valx[2][1], valx[2][2], valx[2][3], valx[2][4], iy == 0); + valy[3] = + m_cons_interpol.order4(valx[3][0], valx[3][1], valx[3][2], valx[3][3], valx[3][4], iy == 0); + valy[4] = + m_cons_interpol.order4(valx[4][0], valx[4][1], valx[4][2], valx[4][3], valx[4][4], iy == 0); + + m_userdata_out(cellindex_out, ivar, iOct_global - m_iOct_begin) = + m_cons_interpol.order4(valy[0], valy[1], valy[2], valy[3], valy[4], iz == 0); + + } // end for ivar + +} // FillBlockGhostCellsFunctor::conservative_interpolation_order4 - 3d + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +FillBlockGhostCellsFunctor::fill_inner(int32_t cellindex_in, + int32_t cellindex_out, + int32_t iOct_global) const +{ + + const auto nbvar = m_userdata_in.num_vars(); + + for (int32_t ivar = 0; ivar < nbvar; ++ivar) + m_userdata_out(cellindex_out, ivar, iOct_global - m_iOct_begin) = + m_userdata_in(cellindex_in, ivar, iOct_global); + +} // FillBlockGhostCellsFunctor::fill_inner + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +FillBlockGhostCellsFunctor::fill_ghosts(index_t const & cellindex_out, + coord_t const & coord_out, + int32_t const & iOct_global) const +{ + + const auto & b = m_userdata_in.block_size(); + + coord_t coord_in; + const auto dir = ghosted_coords_to_inner_coords(coord_in, coord_out, b); + + int32_t cellindex_in = coord_to_cellindex(coord_in, m_userdata_in.block_size()); + + auto dir_norm = dir[IX] * dir[IX] + dir[IY] * dir[IY]; + if constexpr (dim == 3) + dir_norm += dir[IZ] * dir[IZ]; + + if (dir_norm == 0) + { + // current cell is inside current block + fill_inner(cellindex_in, cellindex_out, iOct_global); + } + else + { + // current cell is a ghost cell (thus belonging to a neighbor block) + + /* + * fill ghosts all around + */ + + // get orchard key of current octant + auto key_cur = m_stencil_helper.key(iOct_global); + + shift_t shift; + shift[IX] = b[IX] * dir[IX]; + shift[IY] = b[IY] * dir[IY]; + if constexpr (dim == 3) + { + shift[IZ] = b[IZ] * dir[IZ]; + } + + const CellLocation_t cell_loc_cur{ coord_in, key_cur, iOct_global, false }; + const auto cell_loc_neigh = m_stencil_helper.getNeighLoc(cell_loc_cur, shift); + + const auto nbvar = m_userdata_in.num_vars(); + + /* + * Dealing with the 3 possibilities: + * - neighbor octant is at same AMR level : doing a simple copy + * - neighbor octant is at finer AMR level : doing a restriction (average values) + * - neighbor octant is at coarser AMR level : doing a prolongation + */ + const auto iOct_in = cell_loc_neigh.iOct; + + if (cell_loc_neigh.level() == cell_loc_cur.level()) + { + + // doing a simple copy (this is probably not need, cellindex_in computed above is ok) + cellindex_in = cell_loc_neigh.cellindex(m_userdata_in.block_size()); + + for (int32_t ivar = 0; ivar < nbvar; ++ivar) + { + m_userdata_out(cellindex_out, ivar, iOct_global - m_iOct_begin) = + m_userdata_in(cellindex_in, ivar, iOct_in); + } + } + else if (cell_loc_neigh.level() + 1 == cell_loc_cur.level()) + { + // doing a prolongation because neighbor is coarser + + if (m_prolongation == +CellCenteredProlongationType::SIMPLE_COPY) + { + // simple copy of the coarse value + + cellindex_in = cell_loc_neigh.cellindex(m_userdata_in.block_size()); + + for (int32_t ivar = 0; ivar < nbvar; ++ivar) + { + m_userdata_out(cellindex_out, ivar, iOct_global - m_iOct_begin) = + m_userdata_in(cellindex_in, ivar, iOct_in); + } + } + else if (m_prolongation == +CellCenteredProlongationType::EXTRAPOLATE_LINEAR_MINMOD) + { + linear_extrapolate_using_limited_slopes( + cell_loc_neigh, coord_in, cellindex_out, iOct_global); + } + else if (m_prolongation == +CellCenteredProlongationType::CONSERVATIVE_INTERPOLATION_ORDER_2) + { + conservative_interpolation_order2(cell_loc_neigh, coord_out, cellindex_out, iOct_global); + } + else if (m_prolongation == +CellCenteredProlongationType::CONSERVATIVE_INTERPOLATION_ORDER_4) + { + conservative_interpolation_order4(cell_loc_neigh, coord_out, cellindex_out, iOct_global); + } + else + { + // we shouldn't be here + KOKKOS_ASSERT(false && "Unsupported prolongation type"); + } + } + else if (cell_loc_neigh.level() == cell_loc_cur.level() + 1) + { + // doing a restriction + for (int32_t ivar = 0; ivar < nbvar; ++ivar) + { + m_userdata_out(cellindex_out, ivar, iOct_global - m_iOct_begin) = + m_stencil_helper.compute_siblings_average( + cell_loc_neigh, m_userdata_in.block_size(), ivar, m_userdata_in); + + } // for ivar + } + else + { + KOKKOS_ASSERT(false && "Logic error: neighbor octant not found (Kernel Panic !)"); + } + + } // end if (dir_norm == 0) + +} // FillBlockGhostCellsFunctor::fill_ghosts + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +FillBlockGhostCellsFunctor::operator()(const index_t & global_index) const +{ + + const auto nbCellsPerGhostedLeaf = m_userdata_out.num_cells(); + + const auto iOct_local = global_index / nbCellsPerGhostedLeaf; + const auto cell_index_out = global_index - iOct_local * nbCellsPerGhostedLeaf; + + const auto iOct_global = m_iOct_begin + iOct_local; + + const auto coord_out = cellindex_to_coord( + cell_index_out, m_userdata_out.ghosted_block_size(), m_userdata_out.shift()); + + fill_ghosts(cell_index_out, coord_out, iOct_global); + +} // FillBlockGhostCellsFunctor::operator() + +template class FillBlockGhostCellsFunctor<2, kalypsso::DefaultDevice>; +template class FillBlockGhostCellsFunctor<3, kalypsso::DefaultDevice>; + +} // namespace kalypsso diff --git a/src/kalypsso/core/FillBlockGhostCells.h b/src/kalypsso/core/FillBlockGhostCells.h new file mode 100644 index 0000000..9fd3199 --- /dev/null +++ b/src/kalypsso/core/FillBlockGhostCells.h @@ -0,0 +1,251 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file FillBlockGhostCells.h + */ +#ifndef KALYPSSO_CORE_FILLBLOCKGHOSTCELLS_H_ +#define KALYPSSO_CORE_FILLBLOCKGHOSTCELLS_H_ + +#include +#include // for orchard_key_view_t alias +#include +#include +#include + +namespace kalypsso +{ + +/** + * \class FillBlockGhostCellsFunctor + * + * This is a kokkos functor class that takes as input a DataArrayGhostedBlock + * instance, and for each quadrant, fill block ghosts cells (across faces, edges and corners). + * The input array is non-ghosted, output array is ghosted. + * + * This class only supports block sizes that are even integers. + * + * This class can be used in a piecewise loop over leaf octant. + * + * Just to illustrate, let's consider 4x4 blocks, with 1 ghost cell all around. + * Below are 2 blocks (same ARM level); the right column of the left block (symbol "o", non-ghosted + * block) is copied in the ghost e.g. right face ("o" symbol, ghost cells) + * + * . . . . . . + * x x x o o x x x x . + * x x x o ==> o x x x x . + * x x x o o x x x x . + * x x x o o x x x x . + * . . . . . . + * + * The main difficulty here is to deploy the entire combinatorics of + * geometrical possibilities in terms of + * - size of neighbor octant, i.e. + * is neighbor octant small, same size or larger than current octant, + * - direction : face along X, Y or Z behave slightly differently, need to + * efficiently take symetries into account + * - 2d/3d + * + * So we need to be careful, have good testing code. + * See file test_AMRmesh_fill_block_ghosts.cpp + * + */ +template +class FillBlockGhostCellsFunctor +{ + +public: + using exec_space = typename device_t::execution_space; + using index_t = int32_t; + + using amr_hashmap_t = typename hashmap_base_t::map_t; + using orchard_key_view_t = typename orchard_key_base_t::view_t; + + using DataArrayBlock_t = DataArrayBlock; + using DataArrayGhostedBlock_t = DataArrayGhostedBlock; + + using CellLocation_t = CellLocation; + using StencilHelper_t = StencilHelper; + + /** + * + * \param[in] stencil helper + * \param[in] iOct_begin is the first octant to process + * \param[in] num_octants is the number of octant to process + * \param[in] userdata_in data array used to fill ghost of userdata_out + * \param[in,out] userdata_out data array which we want to fill the block ghosts + * cells + * \param[in] prolongation selects how coarse neighbor must be prolongated to fill ghost cells + */ + FillBlockGhostCellsFunctor(StencilHelper_t stencil_helper, + int32_t iOct_begin, + int32_t num_octants, + DataArrayBlock_t userdata_in, + DataArrayGhostedBlock_t userdata_out, + CellCenteredProlongationType prolongation) + : m_stencil_helper(stencil_helper) + , m_iOct_begin(iOct_begin) + , m_num_octants(num_octants) + , m_userdata_in(userdata_in) + , m_userdata_out(userdata_out) + , m_prolongation(prolongation) + , m_cons_interpol() + { + KOKKOS_ASSERT(userdata_out.block_size() == userdata_in.block_size() && + "userdata_in and userdata_out must have the same block sizes."); + } + + // ==================================================================== + // ==================================================================== + static void + check_args_validity(DataArrayBlock_t userdata_in, CellCenteredProlongationType prolongation_type); + + // ============================================================== + // ============================================================== + //! static method which does it all: create and execute functor with range policy + static void + apply(ConfigMap const & config_map, + amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + [[maybe_unused]] int32_t local_num_octants, + int32_t iOct_begin, + int32_t num_octants, + DataArrayBlock_t userdata_in, + DataArrayGhostedBlock_t userdata_out, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic); + + // ============================================================== + // ============================================================== + /** + * Do linear extrapolation using limited slopes. + */ + template = true> + KOKKOS_INLINE_FUNCTION void + linear_extrapolate_using_limited_slopes(CellLocation<2> const & cell_loc_neigh, + coord_t<2> const & coord_in, + index_t const & cellindex_out, + int32_t const & iOct_global) const; + + // ============================================================== + // ============================================================== + /** + * Do linear extrapolation using limited slopes. + */ + template = true> + KOKKOS_INLINE_FUNCTION void + linear_extrapolate_using_limited_slopes(CellLocation<3> const & cell_loc_neigh, + coord_t<3> const & coord_in, + index_t const & cellindex_out, + int32_t const & iOct_global) const; + + // ============================================================== + // ============================================================== + /** + * Do second order conservative interpolation from coarse to fine cells. + */ + template = true> + KOKKOS_INLINE_FUNCTION void + conservative_interpolation_order2(CellLocation<2> const & cell_loc_neigh, + coord_t<2> const & coord_out, + index_t const & cellindex_out, + int32_t const & iOct_global) const; + + // ============================================================== + // ============================================================== + /** + * Do second order conservative interpolation from coarse to fine cells. + */ + template = true> + KOKKOS_INLINE_FUNCTION void + conservative_interpolation_order2(CellLocation<3> const & cell_loc_neigh, + coord_t<3> const & coord_out, + index_t const & cellindex_out, + int32_t const & iOct_global) const; + + // ============================================================== + // ============================================================== + /** + * Do fourth order conservative interpolation from coarse to fine cells. + */ + template = true> + KOKKOS_INLINE_FUNCTION void + conservative_interpolation_order4(CellLocation<2> const & cell_loc_neigh, + coord_t<2> const & coord_out, + index_t const & cellindex_out, + int32_t const & iOct_global) const; + + // ============================================================== + // ============================================================== + /** + * Do fourth order conservative interpolation from coarse to fine cells. + */ + template = true> + KOKKOS_INLINE_FUNCTION void + conservative_interpolation_order4(CellLocation<3> const & cell_loc_neigh, + coord_t<3> const & coord_out, + index_t const & cellindex_out, + int32_t const & iOct_global) const; + + // ============================================================== + // ============================================================== + /** + * fill interior of ghosted block. + * + * \param[in] cellindex_in is the cell index of the cell to read data from + * \param[in] cellindex_out is the cell index of the ghost cell to fill + * \param[in] iOct_global is the octant id among all octant owned by current MPI process. + * + * Just to be clear iOct_global - m_iOct_begin is the local octant id inside the group of octant + * being processed. + */ + KOKKOS_INLINE_FUNCTION + void + fill_inner(int32_t cellindex_in, int32_t cellindex_out, int32_t iOct_global) const; + + // ============================================================== + // ============================================================== + KOKKOS_INLINE_FUNCTION void + fill_ghosts(index_t const & cellindex_out, + coord_t const & coord_out, + int32_t const & iOct_global) const; + + // ============================================================== + // ============================================================== + /** + * range policy functor + */ + KOKKOS_INLINE_FUNCTION void + operator()(const index_t & global_index) const; + +private: + //! helper to compute neighbor cell location + StencilHelper_t m_stencil_helper; + + //! starting octant id + const int32_t m_iOct_begin; + + //! number of octant to process, starting at m_iOct_begin + const int32_t m_num_octants; + + //! a block data array (no ghosts, sizes= bx,by,bz) + DataArrayBlock_t m_userdata_in; + + //! a ghosted data array (which block ghost cells need to be filled) + DataArrayGhostedBlock_t m_userdata_out; + + //! prolongation type + const CellCenteredProlongationType m_prolongation; + + //! Conservative interpolation data + const ConservativeInterpolation m_cons_interpol; + +}; // class FillBlockGhostCellsFunctor + +extern template class FillBlockGhostCellsFunctor<2, kalypsso::DefaultDevice>; +extern template class FillBlockGhostCellsFunctor<3, kalypsso::DefaultDevice>; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_FILLBLOCKGHOSTCELLS_H_ diff --git a/src/kalypsso/core/FillBlockGhostCellsInPlace.cpp b/src/kalypsso/core/FillBlockGhostCellsInPlace.cpp new file mode 100644 index 0000000..187f639 --- /dev/null +++ b/src/kalypsso/core/FillBlockGhostCellsInPlace.cpp @@ -0,0 +1,258 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file FillBlockGhostCellsInPlace.cpp + * \brief \copybrief FillBlockGhostCellsInPlace.h + */ +#include + +namespace kalypsso +{ + +// ============================================================== +// ============================================================== +template +void +FillBlockGhostCellsInPlaceFunctor::apply(ConfigMap const & config_map, + amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + DataArrayGhostedBlock_t userdata, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic) +{ + + auto stencil_helper = StencilHelper_t( + amr_hashmap, orchard_keys, userdata.block_size(), brick_sizes, is_brick_periodic); + + FillBlockGhostCellsInPlaceFunctor functor( + stencil_helper, userdata, get_cell_prolongation_type(config_map)); + + const auto nbCellsPerGhostedLeaf = userdata.num_cells(); + const auto nbCellsTotal = userdata.num_quadrants() * nbCellsPerGhostedLeaf; + + // for AMR tree leaf, explore the neighbor block + Kokkos::parallel_for( + "FillBlockGhostCellsInPlaceFunctor", Kokkos::RangePolicy(0, nbCellsTotal), functor); + +} // FillBlockGhostCellsInPlaceFunctor::apply + +// ============================================================== +// ============================================================== +template +template > +KOKKOS_INLINE_FUNCTION void +FillBlockGhostCellsInPlaceFunctor::linear_extrapolate_using_limited_slopes( + CellLocation<2> const & cell_loc_neigh, + coord_t<2> const & coord_in, + index_t const & cellindex_out, + int32_t const & iOct) const +{ + const real_t slope_type = 1; // TODO : investigate if a better value should be searched for + const auto nbvar = m_userdata.num_vars(); + + const auto cell_loc_left_x = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(-XDIR)); + const auto cell_loc_right_x = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(+XDIR)); + + const auto cell_loc_left_y = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(-YDIR)); + const auto cell_loc_right_y = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(+YDIR)); + + // determine local position of current cell inside virtual parent cell using integer coordinates + // in -1, +1 + const int ix = 2 * (coord_in[IX] - 2 * (coord_in[IX] / 2)) - 1; + const int iy = 2 * (coord_in[IY] - 2 * (coord_in[IY] / 2)) - 1; + + for (int32_t ivar = 0; ivar < nbvar; ++ivar) + { + // compute limited slopes + auto const dudx = m_stencil_helper.compute_minmod_slopes( + cell_loc_neigh, cell_loc_right_x, cell_loc_left_x, ivar, m_userdata, slope_type); + auto const dudy = m_stencil_helper.compute_minmod_slopes( + cell_loc_neigh, cell_loc_right_y, cell_loc_left_y, ivar, m_userdata, slope_type); + + // extrapolate + m_userdata(cellindex_out, ivar, iOct) = + m_userdata(cell_loc_neigh.ijk, ivar, cell_loc_neigh.iOct) + + ONE_FOURTH_F * static_cast(ix) * dudx + ONE_FOURTH_F * static_cast(iy) * dudy; + } +} // FillBlockGhostCellsInPlaceFunctor::linear_extrapolate_using_limited_slopes - 2d + +// ============================================================== +// ============================================================== +template +template > +KOKKOS_INLINE_FUNCTION void +FillBlockGhostCellsInPlaceFunctor::linear_extrapolate_using_limited_slopes( + CellLocation<3> const & cell_loc_neigh, + coord_t<3> const & coord_in, + index_t const & cellindex_out, + int32_t const & iOct) const +{ + const real_t slope_type = 1; // TODO : investigate if a better value should be searched for + const auto nbvar = m_userdata.num_vars(); + + const auto cell_loc_left_x = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(-XDIR)); + const auto cell_loc_right_x = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(+XDIR)); + + const auto cell_loc_left_y = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(-YDIR)); + const auto cell_loc_right_y = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(+YDIR)); + + const auto cell_loc_left_z = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(-ZDIR)); + const auto cell_loc_right_z = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(+ZDIR)); + + // determine local position of current cell inside virtual parent cell using integer coordinates + // in -1, +1 + const int ix = 2 * (coord_in[IX] - 2 * (coord_in[IX] / 2)) - 1; + const int iy = 2 * (coord_in[IY] - 2 * (coord_in[IY] / 2)) - 1; + const int iz = 2 * (coord_in[IZ] - 2 * (coord_in[IZ] / 2)) - 1; + + for (int32_t ivar = 0; ivar < nbvar; ++ivar) + { + // compute limited slopes + auto const dudx = m_stencil_helper.compute_minmod_slopes( + cell_loc_neigh, cell_loc_right_x, cell_loc_left_x, ivar, m_userdata, slope_type); + auto const dudy = m_stencil_helper.compute_minmod_slopes( + cell_loc_neigh, cell_loc_right_y, cell_loc_left_y, ivar, m_userdata, slope_type); + auto const dudz = m_stencil_helper.compute_minmod_slopes( + cell_loc_neigh, cell_loc_right_z, cell_loc_left_z, ivar, m_userdata, slope_type); + + // extrapolate + m_userdata(cellindex_out, ivar, iOct) = + m_userdata(cell_loc_neigh.ijk, ivar, cell_loc_neigh.iOct) + + ONE_FOURTH_F * static_cast(ix) * dudx + + ONE_FOURTH_F * static_cast(iy) * dudy + ONE_FOURTH_F * static_cast(iz) * dudz; + } + +} // FillBlockGhostCellsInPlaceFunctor::linear_extrapolate_using_limited_slopes - 3d + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +FillBlockGhostCellsInPlaceFunctor::fill_ghosts(index_t const & cellindex_out, + coord_t const & coord_out, + int32_t const & iOct) const +{ + + const auto & b = m_userdata.block_size(); + + coord_t coord_in; + const auto dir = ghosted_coords_to_inner_coords(coord_in, coord_out, b); + + // int32_t cellindex_in = coord_to_cellindex(coord_in, m_userdata.block_size()); + + auto dir_norm = dir[IX] * dir[IX] + dir[IY] * dir[IY]; + if constexpr (dim == 3) + dir_norm += dir[IZ] * dir[IZ]; + + if (dir_norm != 0) + { + // current cell is a ghost cell (thus overlapping with a neighbor block) + + /* + * fill ghosts all around + */ + + // get orchard key of current octant + auto key_cur = m_stencil_helper.key(iOct); + + shift_t shift; + shift[IX] = b[IX] * dir[IX]; + shift[IY] = b[IY] * dir[IY]; + if constexpr (dim == 3) + { + shift[IZ] = b[IZ] * dir[IZ]; + } + + const CellLocation_t cell_loc_cur{ coord_in, key_cur, iOct, false }; + const auto cell_loc_neigh = m_stencil_helper.getNeighLoc(cell_loc_cur, shift); + + const auto nbvar = m_userdata.num_vars(); + + /* + * Dealing with the 3 possibilities: + * - neighbor octant is at same AMR level : doing a simple copy + * - neighbor octant is at finer AMR level : doing a restriction (average values) + * - neighbor octant is at coarser AMR level : doing a prolongation + */ + const auto iOct_in = cell_loc_neigh.iOct; + + if (cell_loc_neigh.level() == cell_loc_cur.level()) + { + + // doing a simple copy (this is probably not need, cellindex_in computed above is ok) + for (int32_t ivar = 0; ivar < nbvar; ++ivar) + { + m_userdata(cellindex_out, ivar, iOct) = m_userdata(cell_loc_neigh.ijk, ivar, iOct_in); + } + } + else if (cell_loc_neigh.level() + 1 == cell_loc_cur.level()) + { + // doing a prolongation because neighbor is coarser + + if (m_prolongation == +CellCenteredProlongationType::SIMPLE_COPY) + { + // simple copy of the coarse value + for (int32_t ivar = 0; ivar < nbvar; ++ivar) + { + m_userdata(cellindex_out, ivar, iOct) = m_userdata(cell_loc_neigh.ijk, ivar, iOct_in); + } + } + else if (m_prolongation == +CellCenteredProlongationType::EXTRAPOLATE_LINEAR_MINMOD) + { + linear_extrapolate_using_limited_slopes(cell_loc_neigh, coord_in, cellindex_out, iOct); + } + } + else if (cell_loc_neigh.level() == cell_loc_cur.level() + 1) + { + // doing a restriction + for (int32_t ivar = 0; ivar < nbvar; ++ivar) + { + m_userdata(cellindex_out, ivar, iOct) = + m_stencil_helper.compute_siblings_average(cell_loc_neigh, ivar, m_userdata); + + } // for ivar + } + else + { + KOKKOS_ASSERT(false && "Logic error: neighbor octant not found (Kernel Panic !)"); + } + + } // end if (dir_norm == 0) + +} // FillBlockGhostCellsInPlaceFunctor::fill_ghosts + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +FillBlockGhostCellsInPlaceFunctor::operator()(const index_t & global_index) const +{ + + const auto nbCellsPerGhostedLeaf = m_userdata.num_cells(); + + const auto iOct = global_index / nbCellsPerGhostedLeaf; + const auto cell_index_out = global_index - iOct * nbCellsPerGhostedLeaf; + + const auto coord_out = + cellindex_to_coord(cell_index_out, m_userdata.ghosted_block_size(), m_userdata.shift()); + + fill_ghosts(cell_index_out, coord_out, iOct); + +} // FillBlockGhostCellsInPlaceFunctor::operator() + +template class FillBlockGhostCellsInPlaceFunctor<2, kalypsso::DefaultDevice>; +template class FillBlockGhostCellsInPlaceFunctor<3, kalypsso::DefaultDevice>; + +} // namespace kalypsso diff --git a/src/kalypsso/core/FillBlockGhostCellsInPlace.h b/src/kalypsso/core/FillBlockGhostCellsInPlace.h new file mode 100644 index 0000000..9f166d3 --- /dev/null +++ b/src/kalypsso/core/FillBlockGhostCellsInPlace.h @@ -0,0 +1,148 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file FillBlockGhostCellsInPlace.h + */ +#ifndef KALYPSSO_CORE_FILLBLOCKGHOSTCELLSINPLACE_H_ +#define KALYPSSO_CORE_FILLBLOCKGHOSTCELLSINPLACE_H_ + +#include +#include // for orchard_key_view_t alias +#include +#include +#include + +namespace kalypsso +{ + +/** + * \class FillBlockGhostCellsInPlaceFunctor + * + * \sa FillBlockGhostCellsFunctor + * + * Same functionality as FillBlockGhostCellsFunctor but here the ghosted data array is used in input + * (for the inner block of cells) and in output (to fill the ghost cells). + * + * This is a kokkos functor class that takes as input a DataArrayGhostedBlock + * instance, and for each quadrant, fill block ghosts cells (across faces, edges and corners). + * + * This class only supports block sizes that are even integers. + * + * This class can be used in a piecewise loop over all octants. + * + * Just to illustrate, let's consider 4x4 blocks, with 1 ghost cell all around. + * Below are 2 blocks (same ARM level); the right column of the left block (symbol "o") is copied + * into the ghost of the right block, and vice-versa with symbol 'r' + * + * . . . . . . . . . . . . + * . x x x o r o r x x x . + * . x x x o r <==> o r x x x . + * . x x x o r o r x x x . + * . x x x o r o r x x x . + * . . . . . . . . . . . . + * + * + */ +template +class FillBlockGhostCellsInPlaceFunctor +{ + +public: + using exec_space = typename device_t::execution_space; + using index_t = int32_t; + + using amr_hashmap_t = typename hashmap_base_t::map_t; + using orchard_key_view_t = typename orchard_key_base_t::view_t; + + using DataArrayGhostedBlock_t = DataArrayGhostedBlock; + + using CellLocation_t = CellLocation; + using StencilHelper_t = StencilHelper; + + /** + * + * \param[in] stencil helper + * \param[in] iOct_begin is the first octant to process + * \param[in] num_octants is the number of octant to process + * \param[in,out] userdata data array which we want to fill the block ghosts + * cells + * \param[in] prolongation selects how coarse neighbor must be prolongated to fill ghost cells + */ + FillBlockGhostCellsInPlaceFunctor(StencilHelper_t stencil_helper, + DataArrayGhostedBlock_t userdata, + CellCenteredProlongationType prolongation) + : m_stencil_helper(stencil_helper) + , m_userdata(userdata) + , m_prolongation(prolongation) + {} + + // ============================================================== + // ============================================================== + //! static method which does it all: create and execute functor with range policy + static void + apply(ConfigMap const & config_map, + amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + DataArrayGhostedBlock_t userdata, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic); + + // ============================================================== + // ============================================================== + /** + * Do linear extrapolation using limited slopes. + */ + template = true> + KOKKOS_INLINE_FUNCTION void + linear_extrapolate_using_limited_slopes(CellLocation<2> const & cell_loc_neigh, + coord_t<2> const & coord_in, + index_t const & cellindex_out, + int32_t const & iOct) const; + + // ============================================================== + // ============================================================== + /** + * Do linear extrapolation using limited slopes. + */ + template = true> + KOKKOS_INLINE_FUNCTION void + linear_extrapolate_using_limited_slopes(CellLocation<3> const & cell_loc_neigh, + coord_t<3> const & coord_in, + index_t const & cellindex_out, + int32_t const & iOct) const; + + // ============================================================== + // ============================================================== + KOKKOS_INLINE_FUNCTION void + fill_ghosts(index_t const & cellindex_out, + coord_t const & coord_out, + int32_t const & iOct) const; + + // ============================================================== + // ============================================================== + /** + * range policy functor + */ + KOKKOS_INLINE_FUNCTION void + operator()(const index_t & global_index) const; + +private: + //! helper to compute neighbor cell location + StencilHelper_t m_stencil_helper; + + //! a ghosted data array (which block ghost cells need to be filled) + DataArrayGhostedBlock_t m_userdata; + + //! prolongation type + const CellCenteredProlongationType m_prolongation; + +}; // class FillBlockGhostCellsInPlaceFunctor + +extern template class FillBlockGhostCellsInPlaceFunctor<2, kalypsso::DefaultDevice>; +extern template class FillBlockGhostCellsInPlaceFunctor<3, kalypsso::DefaultDevice>; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_FILLBLOCKGHOSTCELLSINPLACE_H_ diff --git a/src/kalypsso/core/FillBlockGhostCells_v2.cpp b/src/kalypsso/core/FillBlockGhostCells_v2.cpp new file mode 100644 index 0000000..105f8b3 --- /dev/null +++ b/src/kalypsso/core/FillBlockGhostCells_v2.cpp @@ -0,0 +1,403 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file FillBlockGhostCells_v2.cpp + * \brief \copybrief FillBlockGhostCells_v2.h + */ + +#include + +namespace kalypsso +{ + +// ============================================================== +// ============================================================== +template +FillBlockGhostCellsFunctorV2::FillBlockGhostCellsFunctorV2( + amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + int32_t iOct_begin, + int32_t num_octants, + DataArrayBlock_t userdata_in, + DataArrayGhostedBlock_t userdata_out, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic) + : m_amr_hashmap_device(amr_hashmap) + , m_orchard_keys_device(orchard_keys) + , m_iOct_begin(iOct_begin) + , m_num_octants(num_octants) + , m_userdata_in(userdata_in) + , m_userdata_out(userdata_out) + , m_brick_sizes(brick_sizes) + , m_is_brick_periodic(is_brick_periodic) +{ + KOKKOS_ASSERT(userdata_out.block_size() == userdata_in.block_size() && + "userdata_in and userdata_out must have the same block sizes."); +} + +// ============================================================== +// ============================================================== +template +void +FillBlockGhostCellsFunctorV2::apply(amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + [[maybe_unused]] int32_t local_num_octants, + int32_t iOct_begin, + int32_t num_octants, + DataArrayBlock_t userdata_in, + DataArrayGhostedBlock_t userdata_out, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic) +{ + + // make sure the range of octants to process is valid + KOKKOS_ASSERT(iOct_begin + num_octants <= local_num_octants && + "Invalid range of octants to process"); + + FillBlockGhostCellsFunctorV2 functor(amr_hashmap, + orchard_keys, + iOct_begin, + num_octants, + userdata_in, + userdata_out, + brick_sizes, + is_brick_periodic); + + const auto nbCellsPerGhostedLeaf = userdata_out.num_cells(); + const auto nbCellsTotal = num_octants * nbCellsPerGhostedLeaf; + + // for AMR tree leaf, explore the neighbor block + Kokkos::parallel_for( + "FillBlockGhostCellsFunctorV2", Kokkos::RangePolicy(0, nbCellsTotal), functor); + +} // apply + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION real_t +FillBlockGhostCellsFunctorV2::compute_siblings_average(coord_t const & coords, + int32_t const & ivar, + int32_t const & iOct) const +{ + const auto & b = m_userdata_in.block_size(); + + // number of siblings : + // - 4 in 2d + // - 8 in 3d + // that is 2^dim + constexpr int num_siblings = 1 << dim; + + // average child octant values + real_t value = 0; + + if constexpr (dim == 2) + { + for (int32_t jj = 0; jj < 2; ++jj) + { + for (int32_t ii = 0; ii < 2; ++ii) + { + + KOKKOS_ASSERT((coords[IX] + ii < b[IX]) && "Wrong values for coords[IX]"); + KOKKOS_ASSERT((coords[IY] + jj < b[IY]) && "Wrong values for coords[IY]"); + + // compute corresponding index in the ghosted block (current octant) + const auto coord_sibling = coord_t<2>{ coords[IX] + ii, coords[IY] + jj }; + const auto cellindex_sibling = coord_to_cellindex(coord_sibling, b); + + value += m_userdata_in(cellindex_sibling, ivar, iOct); + } // for ii + } // for jj + } + else if constexpr (dim == 3) + { + for (int32_t kk = 0; kk < 2; ++kk) + { + for (int32_t jj = 0; jj < 2; ++jj) + { + for (int32_t ii = 0; ii < 2; ++ii) + { + + KOKKOS_ASSERT((coords[IX] + ii < b[IX]) && "Wrong values for coords[IX]"); + KOKKOS_ASSERT((coords[IY] + jj < b[IY]) && "Wrong values for coords[IY]"); + KOKKOS_ASSERT((coords[IZ] + kk < b[IZ]) && "Wrong values for coords[IZ]"); + + // compute corresponding index in the ghosted block (current octant) + const auto coord_sibling = + coord_t<3>{ coords[IX] + ii, coords[IY] + jj, coords[IZ] + kk }; + const auto cellindex_sibling = coord_to_cellindex(coord_sibling, b); + + value += m_userdata_in(cellindex_sibling, ivar, iOct); + } // for ii + } // for jj + } // for kk + } + + return value / num_siblings; +} // compute_siblings_average + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +FillBlockGhostCellsFunctorV2::fill_inner(int32_t cellindex_in, + int32_t cellindex_out, + int32_t iOct_global) const +{ + const auto nbvar = m_userdata_in.num_vars(); + + for (int32_t ivar = 0; ivar < nbvar; ++ivar) + m_userdata_out(cellindex_out, ivar, iOct_global - m_iOct_begin) = + m_userdata_in(cellindex_in, ivar, iOct_global); + +} // fill_inner + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +FillBlockGhostCellsFunctorV2::fill_ghost_same_level(int32_t iOct_global, + int32_t iOct_neigh, + index_t cellindex_in, + index_t cellindex_out) const +{ + + const auto nbvar = m_userdata_in.num_vars(); + + for (int32_t ivar = 0; ivar < nbvar; ++ivar) + m_userdata_out(cellindex_out, ivar, iOct_global - m_iOct_begin) = + m_userdata_in(cellindex_in, ivar, iOct_neigh); + +} // fill_ghost_same_level + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +FillBlockGhostCellsFunctorV2::fill_ghost_coarser_level(uint8_t child_id, + int32_t iOct_global, + int32_t iOct_neigh, + coord_t coord_in, + index_t cellindex_out) const +{ + + const auto & b = m_userdata_in.block_size(); + + coord_in[IX] = (coord_in[IX] + ((child_id & 0x1) >> 0) * b[IX]) / 2; + coord_in[IY] = (coord_in[IY] + ((child_id & 0x2) >> 1) * b[IY]) / 2; + if constexpr (dim == 3) + { + coord_in[IZ] = (coord_in[IZ] + ((child_id & 0x4) >> 2) * b[IZ]) / 2; + } + + auto cellindex_neigh = coord_to_cellindex(coord_in, m_userdata_in.block_size()); + + KOKKOS_ASSERT((coord_in[IX] < b[IX]) && "Wrong values for coord_in[IX]"); + KOKKOS_ASSERT((coord_in[IY] < b[IY]) && "Wrong values for coord_in[IY]"); + if constexpr (dim == 3) + { + KOKKOS_ASSERT((coord_in[IZ] < b[IZ]) && "Wrong values for coord_in[IZ]"); + } + + const auto nbvar = m_userdata_in.num_vars(); + + for (int32_t ivar = 0; ivar < nbvar; ++ivar) + m_userdata_out(cellindex_out, ivar, iOct_global - m_iOct_begin) = + m_userdata_in(cellindex_neigh, ivar, iOct_neigh); + +} // fill_ghost_coarser_level + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION bool +FillBlockGhostCellsFunctorV2::fill_ghost_finer_level(key_t key_neigh_same_level, + int32_t iOct_global, + coord_t coord_in, + index_t cellindex_out) const +{ + auto status = false; + + const auto & b = m_userdata_in.block_size(); + + coord_in = coord_in * 2; + + // determine in which finer octant we need to use (i.e. determine child id) + uint8_t child_id = 0; + if (coord_in[IX] >= b[IX]) + { + coord_in[IX] -= b[IX]; + child_id += 1; + } + if (coord_in[IY] >= b[IY]) + { + coord_in[IY] -= b[IY]; + child_id += 2; + } + if constexpr (dim == 3) + { + if (coord_in[IZ] >= b[IZ]) + { + coord_in[IZ] -= b[IZ]; + child_id += 4; + } + } + + // now coord_in contain the coordinates of the lower left finer cell to use + // we need to average this cell with its siblings + + // lookup for this child in the hashmap + const auto key_neigh_fine = orchard_key_t::child(key_neigh_same_level, child_id); + + const auto key_neigh_hashindex = m_amr_hashmap_device.find(key_neigh_fine); + const auto valid = m_amr_hashmap_device.valid_at(key_neigh_hashindex); + + if (valid) + { + + const auto iOct_neigh = + static_cast(m_amr_hashmap_device.value_at(key_neigh_hashindex)); + + // fill ghost cell with values at finer level + + const auto nbvar = m_userdata_in.num_vars(); + + for (int32_t ivar = 0; ivar < nbvar; ++ivar) + { + m_userdata_out(cellindex_out, ivar, iOct_global - m_iOct_begin) = + compute_siblings_average(coord_in, ivar, iOct_neigh); + + } // for ivar + + status = true; + } + else + { + status = false; + } + + return status; +} // fill_ghost_finer_level + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +FillBlockGhostCellsFunctorV2::fill_ghosts(index_t const & cellindex_out, + coord_t const & coord_out, + int32_t const & iOct_global) const +{ + const auto & b = m_userdata_in.block_size(); + + coord_t coord_in; + const auto dir = ghosted_coords_to_inner_coords(coord_in, coord_out, b); + + int32_t cellindex_in = coord_to_cellindex(coord_in, m_userdata_in.block_size()); + + auto dir_norm = dir[IX] * dir[IX] + dir[IY] * dir[IY]; + if constexpr (dim == 3) + dir_norm += dir[IZ] * dir[IZ]; + + if (dir_norm == 0) + { + // current cell is inside current block + fill_inner(cellindex_in, cellindex_out, iOct_global); + } + else + { + /* + * fill ghosts + */ + + // get orchard key of current octant + auto key_cur = m_orchard_keys_device(iOct_global); + + // get neighbor key at same level + auto key_neigh_same_level = orchard_key_t::get_neighbor_key_same_level( + key_cur, dir, m_brick_sizes, m_is_brick_periodic); + + // check if neighbor key exists in hash map + auto key_neigh_hashindex = m_amr_hashmap_device.find(key_neigh_same_level); + + auto is_at_same_level = m_amr_hashmap_device.valid_at(key_neigh_hashindex); + + // if key is valid, it means neighbor is actually at the same level + if (is_at_same_level) + { + const auto iOct_neigh = + static_cast(m_amr_hashmap_device.value_at(key_neigh_hashindex)); + + fill_ghost_same_level(iOct_global, iOct_neigh, cellindex_in, cellindex_out); + } + else + { + + // check if father exist (coarser level) + auto key_neigh_coarser = orchard_key_t::father(key_neigh_same_level); + + key_neigh_hashindex = m_amr_hashmap_device.find(key_neigh_coarser); + + auto is_at_coarser_level = m_amr_hashmap_device.valid_at(key_neigh_hashindex); + + if (is_at_coarser_level) + { + const auto iOct_neigh = + static_cast(m_amr_hashmap_device.value_at(key_neigh_hashindex)); + const auto child_id = orchard_key_t::child_id(key_neigh_same_level); + + fill_ghost_coarser_level(child_id, iOct_global, iOct_neigh, coord_in, cellindex_out); + } + else + { + // try to fill ghost cell with data at finer level + auto fill_ok = + fill_ghost_finer_level(key_neigh_same_level, iOct_global, coord_in, cellindex_out); + + // if not successful, it means neighbor octant doesn't exist in hashmap + // this can only happen if the ghost cell is outside domain border and we are not using + // periodic brick connectivity + if (!fill_ok) + { + if (orchard_key_t::is_at_any_domain_border(key_cur, m_brick_sizes)) + { + orchard_key_t::print(key_cur); + // TODO: provides default ways to fill ghost cells + // or let the numerical scheme decide + } + else + { + KOKKOS_ASSERT(false && "Logic error: neighbor not found"); + } + } + } + } + } + +} // fill_ghosts + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +FillBlockGhostCellsFunctorV2::operator()(const index_t & global_index) const +{ + + const auto nbCellsPerGhostedLeaf = m_userdata_out.num_cells(); + + const auto iOct_local = global_index / nbCellsPerGhostedLeaf; + const auto cell_index_out = global_index - iOct_local * nbCellsPerGhostedLeaf; + + const auto iOct_global = m_iOct_begin + iOct_local; + const auto coord_out = cellindex_to_coord( + cell_index_out, m_userdata_out.ghosted_block_size(), m_userdata_out.shift()); + + fill_ghosts(cell_index_out, coord_out, iOct_global); + +} // operator() + +template class FillBlockGhostCellsFunctorV2<2, kalypsso::DefaultDevice>; +template class FillBlockGhostCellsFunctorV2<3, kalypsso::DefaultDevice>; + +} // namespace kalypsso diff --git a/src/kalypsso/core/FillBlockGhostCells_v2.h b/src/kalypsso/core/FillBlockGhostCells_v2.h new file mode 100644 index 0000000..114bafd --- /dev/null +++ b/src/kalypsso/core/FillBlockGhostCells_v2.h @@ -0,0 +1,279 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file FillBlockGhostCells_v2.h + */ +#ifndef KALYPSSO_CORE_FILLBLOCKGHOSTCELLS_V2_H_ +#define KALYPSSO_CORE_FILLBLOCKGHOSTCELLS_V2_H_ + +#include +#include // for orchard_key_view_t alias +#include + +namespace kalypsso +{ + +/** + * \class FillBlockGhostCellsFunctorV2 + * + * This is a kokkos functor class that takes as input a DataArrayGhostedBlock + * instance, and for each quadrant, fill block ghosts cells (across faces, edges and corners). + * The input array is non-ghosted, output array is ghosted. + * + * This class only supports block sizes that are even integers. + * + * This class can be used in a piecewise loop over leaf octant. + * + * Just to illustrate, let's consider 4x4 blocks, with 1 ghost cell all around. + * Below are 2 blocks (same ARM level); the right column of the left block (symbol "o", non-ghosted + * block) is copied in the ghost e.g. right face ("o" symbol, ghosted blocked) + * + * . . . . . . . . . . . . + * . x x x o . o x x x x . + * . x x x o . ==> o x x x x . + * . x x x o . o x x x x . + * . x x x o . o x x x x . + * . . . . . . . . . . . . + * + * The main difficulty here is to deploy the entire combinatorics of + * geometrical possibilities in terms of + * - size of neighbor octant, i.e. + * is neighbor octant small, same size or larger than current octant, + * - direction : face along X, Y or Z behave slightly differently, need to + * efficiently take symetries into account + * - 2d/3d + * + * So we need to be careful, have good testing code. + * See file test_AMRmesh_fill_block_ghosts.cpp + * + */ +template +class FillBlockGhostCellsFunctorV2 +{ + +public: + using exec_space = typename device_t::execution_space; + using index_t = int32_t; + + using amr_hashmap_t = typename hashmap_base_t::map_t; + using orchard_key_view_t = typename orchard_key_base_t::view_t; + + using DataArrayBlock_t = DataArrayBlock; + using DataArrayGhostedBlock_t = DataArrayGhostedBlock; + + /** + * + * \param[in] amr_hashmap unordered map from orchard key to memory index for owned and ghost + * quadrants + * \param[in] orchard_keys array of orchard key ordered by Morton order + * \param[in] iOct_begin is the first octant to process + * \param[in] num_octants is the number of octant to process + * \param[in] userdata_in data array used to fill ghost of userdata_out + * \param[in,out] userdata_out data array which we want to fill the block ghosts + * cells + * \param[in] brick_sizes is an array of p4est brick connectivity sizes (number of trees in + * each dimension) + * \param[in] is_brick_periodic array of boolean value indicating if the p4est + * brick connectivity is periodic in the given dimension + * + */ + FillBlockGhostCellsFunctorV2(amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + int32_t iOct_begin, + int32_t num_octants, + DataArrayBlock_t userdata_in, + DataArrayGhostedBlock_t userdata_out, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic); + + // ============================================================== + // ============================================================== + //! static method which does it all: create and execute functor with range policy + static void + apply(amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + int32_t local_num_octants, + int32_t iOct_begin, + int32_t num_octants, + DataArrayBlock_t userdata_in, + DataArrayGhostedBlock_t userdata_out, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic); + + // ============================================================== + // ============================================================== + /** + * compute siblings average. + * + * In other words, given the coordinate of a given cell (all even integer, so that it + * corresponds to the eldest sibling of a block 2^dim cells), then we perform the average of a + * given field of m_userdata_in over all siblings. + * ____________ + * | | | + * | | | + * |_____|_____| + * | | | + * | X | | + * |_____|_____| + * + * + * \param[in] coordinates of the eldest sibling (must be all even integers). + * \param[in] ivar variable id + * \param[in] iOct octant id + * + * \return average value + */ + KOKKOS_INLINE_FUNCTION real_t + compute_siblings_average(coord_t const & coords, + int32_t const & ivar, + int32_t const & iOct) const; + + // ============================================================== + // ============================================================== + /** + * fill interior of ghosted block. + * + * \param[in] cellindex_out is the cell index of the ghost cell to fill + * \param[in] iOct_global is the octant id among all octant owned by current MPI process. + * + * Just to be clear iOct_global - m_iOct_begin is the local octant id inside the group of octant + * being processed. + */ + KOKKOS_INLINE_FUNCTION + void + fill_inner(int32_t cellindex_in, int32_t cellindex_out, int32_t iOct_global) const; + + // ============================================================== + // ============================================================== + /** + * Fill (copy) ghost cell data of current octant (iOct_global) from + * a neighbor octant in case neighbor is at the same AMR level. + * + * \param[in] iOct_global index to current octant + * \param[in] iOct_neigh index to neighbor octant + * \param[in] cell index integer used to map the ghost cell to fill + * \param[in] dir is array of direction pointing to neighbor + * + */ + KOKKOS_INLINE_FUNCTION + void + fill_ghost_same_level(int32_t iOct_global, + int32_t iOct_neigh, + index_t cellindex_in, + index_t cellindex_out) const; + + // ============================================================== + // ============================================================== + /** + * Fill (copy) ghost cell data of current octant (iOct_global) from + * a neighbor octant in case neighbor is at coarser level. + * + * \param[in] key is current octant orchard key + * \param[in] key_neigh is neighbor octant orchard key + * \param[in] child_id is the child id of neighbor (at same level) wrt actual neighbor (coarser + * level) + * \param[in] iOct_global index to current octant + * \param[in] iOct_neigh index to neighbor octant + * \param[in] coord_in + * \param[in] cellindex_out integer used to map the ghost cell to fill + * \param[in] dir is direction to neighbor (in a 3x3 neighborhood) + * + * In 2d, for face neighbors, there are 2 typical situations : + * ______ ______ __ + * | | | | X | + * | | __ or | | X__| + * | | X | | | + * |______| X__| |______| + * + * In this function, we want to fill the "X" ghost cells using data from + * the (larger) neighbor octant. + * + */ + KOKKOS_INLINE_FUNCTION + void + fill_ghost_coarser_level(uint8_t child_id, + int32_t iOct_global, + int32_t iOct_neigh, + coord_t coord_in, + index_t cellindex_out) const; + + // ============================================================== + // ============================================================== + /** + * Fill (copy) ghost cell data of current octant (iOct_global) from + * a neighbor octant in case neighbor is at finer level. + * + * \param[in] key_neigh_same_level is orchard key of neighbor in direction dir at same AMR level + * \param[in] iOct_global is index to current octant + * \param[in] cellindex integer used to map the ghost cell to fill + * \param[in] dir is direction to neighbor (in a 3x3 neighborhood) + * + * current octant (large one) is on the right + * neighbor octant (small one) is on the left + * + * In 2d, for face neighbor, there are 2 typical situations : + * _______ __ _______ + * | | | | X | + * __ | | or |__| X | + * | | X | | | + * |__| x_______| |_______| + * + */ + KOKKOS_INLINE_FUNCTION + bool + fill_ghost_finer_level(key_t key_neigh_same_level, + int32_t iOct_global, + coord_t coord_in, + index_t cellindex_out) const; + + // ============================================================== + // ============================================================== + KOKKOS_INLINE_FUNCTION void + fill_ghosts(index_t const & cellindex_out, + coord_t const & coord_out, + int32_t const & iOct_global) const; + + // ============================================================== + // ============================================================== + /** + * range policy functor + */ + KOKKOS_INLINE_FUNCTION void + operator()(const index_t & global_index) const; + +private: + //! AMR unordered map which maps orchard keys to quadrant number for all key in the mesh + //! (owned quadrants and ghost quadrants) + amr_hashmap_t m_amr_hashmap_device; + + //! list of orchard key of the mesh + orchard_key_view_t m_orchard_keys_device; + + //! starting octant id + const int32_t m_iOct_begin; + + //! number of octant to process, starting at m_iOct_begin + const int32_t m_num_octants; + + //! a block data array (no ghosts, sizes= bx,by,bz) + DataArrayBlock_t m_userdata_in; + + //! a ghosted data array (which block ghost cells need to be filled) + DataArrayGhostedBlock_t m_userdata_out; + + //! p4est brick connectivity sizes + const brick_size_t m_brick_sizes; + + //! is p4est connectivity periodic ? + const Kokkos::Array m_is_brick_periodic; + +}; // class FillBlockGhostCellsFunctorV2 + +extern template class FillBlockGhostCellsFunctorV2<2, kalypsso::DefaultDevice>; +extern template class FillBlockGhostCellsFunctorV2<3, kalypsso::DefaultDevice>; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_FILLBLOCKGHOSTCELLS_V2_H_ diff --git a/src/kalypsso/core/FillBlockGhostFaces.cpp b/src/kalypsso/core/FillBlockGhostFaces.cpp new file mode 100644 index 0000000..26f10d8 --- /dev/null +++ b/src/kalypsso/core/FillBlockGhostFaces.cpp @@ -0,0 +1,902 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file FillBlockGhostFaces.cpp + */ +#include + + +namespace kalypsso +{ + +// ============================================================== +// ============================================================== +template +void +FillBlockGhostFacesFunctor::apply(ConfigMap const & config_map, + amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + [[maybe_unused]] int32_t local_num_octants, + int32_t iOct_begin, + int32_t num_octants, + FaceDataArrayBlock_t facedata_in, + FaceDataArrayBlock_t facedata_out, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + AMRMeshInfo amr_mesh_info) +{ + + // make sure the range of octants to process is valid + KOKKOS_ASSERT(iOct_begin + num_octants <= local_num_octants && + "Invalid range of octants to process"); + + // input array must be non-ghosted + KOKKOS_ASSERT(facedata_in.is_ghosted() == false && "Input array must be non ghosted."); + + // output array ghost size must be even + KOKKOS_ASSERT(((facedata_out.shift()[IX] & 0x1) == 0) && + "Output array must have a ghost width to be an even integer."); + KOKKOS_ASSERT(((facedata_out.shift()[IY] & 0x1) == 0) && + "Output array must have a ghost width to be an even integer."); + if constexpr (dim == 3) + { + KOKKOS_ASSERT(((facedata_out.shift()[IZ] & 0x1) == 0) && + "Output array must have a ghost width to be an even integer."); + } + + auto stencil_helper = StencilHelper_t( + amr_hashmap, orchard_keys, facedata_in.cell_block_size_inner(), brick_sizes, is_brick_periodic); + + FillBlockGhostFacesFunctor functor(stencil_helper, + iOct_begin, + num_octants, + facedata_in, + facedata_out, + ProlongationParam(config_map), + amr_mesh_info); + + const auto num_elements_per_octant = facedata_out.num_elements_per_octant(); + + Kokkos::parallel_for("FillBlockGhostFacesFunctor - AllButInternalFaces", + Kokkos::RangePolicy( + 0, num_octants * num_elements_per_octant), + functor); + + Kokkos::parallel_for( + "FillBlockGhostFacesFunctor - InternalFaces", + Kokkos::RangePolicy(0, num_octants * num_elements_per_octant), + functor); + +} // apply + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION bool +FillBlockGhostFacesFunctor::is_face_ambiguous( + face_multiindex_t const & face_indexes, + block_size_t const & block_sizes) const +{ + auto const & ivar = face_indexes[dim]; + + if (ivar == IX and (face_indexes[IX] == 0 or face_indexes[IX] == block_sizes[IX])) + { + return true; + } + if (ivar == IY and (face_indexes[IY] == 0 or face_indexes[IY] == block_sizes[IY])) + { + return true; + } + if constexpr (dim == 3) + { + if (ivar == IZ and (face_indexes[IZ] == 0 or face_indexes[IZ] == block_sizes[IZ])) + { + return true; + } + } + return false; +} + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION Kokkos::Array + FillBlockGhostFacesFunctor::convert_face_indexes_out_to_in( + face_multiindex_t & face_indexes_in, + face_multiindex_t const & face_indexes_out) const +{ + + const auto & ivar = face_indexes_out[dim]; + const auto & fb = m_facedata_in.face_block_size(ivar); + const auto & b = m_block_sizes; + // const auto & g = m_ghost_sizes; + + auto dir = []() { + if constexpr (dim == 2) + return Kokkos::Array{ 0, 0 }; + else if constexpr (dim == 3) + return Kokkos::Array{ 0, 0, 0 }; + }(); + + // check if output cell is inside inner block, or in neighbor block + if (face_indexes_out[IX] < 0) + { + dir[IX] = -1; + face_indexes_in[IX] = face_indexes_out[IX] + b[IX]; + } + else if (face_indexes_out[IX] >= fb[IX]) + { + dir[IX] = 1; + face_indexes_in[IX] = face_indexes_out[IX] - b[IX]; + } + else + { + face_indexes_in[IX] = face_indexes_out[IX]; + } + + if (face_indexes_out[IY] < 0) + { + dir[IY] = -1; + face_indexes_in[IY] = face_indexes_out[IY] + b[IY]; + } + else if (face_indexes_out[IY] >= fb[IY]) + { + dir[IY] = 1; + face_indexes_in[IY] = face_indexes_out[IY] - b[IY]; + } + else + { + face_indexes_in[IY] = face_indexes_out[IY]; + } + + if constexpr (dim == 3) + { + if (face_indexes_out[IZ] < 0) + { + dir[IZ] = -1; + face_indexes_in[IZ] = face_indexes_out[IZ] + b[IZ]; + } + else if (face_indexes_out[IZ] >= fb[IZ]) + { + dir[IZ] = 1; + face_indexes_in[IZ] = face_indexes_out[IZ] - b[IZ]; + } + else + { + face_indexes_in[IZ] = face_indexes_out[IZ]; + } + } + + face_indexes_in[dim] = face_indexes_out[dim]; + + return dir; + +} // convert_face_indexes_out_to_in + +// ============================================================== +// ============================================================== +template +template > +KOKKOS_INLINE_FUNCTION void +FillBlockGhostFacesFunctor::linear_extrapolate_using_limited_slopes( + FaceLocation<2> const & face_loc_neigh, + FaceLocation<2> const & face_loc_out, + int32_t const & iOct_local) const +{ + // doing a prolongation: the neighbor must be at coarse level + KOKKOS_ASSERT((face_loc_neigh.level() + 1 == face_loc_out.level()) && + "Invalid face location for doing a prolongation"); + + auto const & ivar = face_loc_neigh.ijk[dim]; + + // get transverse slope + const auto face_loc_left = + ivar == IX ? m_stencil_helper.getNeighLoc(face_loc_neigh, m_stencil_helper.unit_shift(-YDIR)) + : m_stencil_helper.getNeighLoc(face_loc_neigh, m_stencil_helper.unit_shift(-XDIR)); + const auto face_loc_right = + ivar == IX ? m_stencil_helper.getNeighLoc(face_loc_neigh, m_stencil_helper.unit_shift(+YDIR)) + : m_stencil_helper.getNeighLoc(face_loc_neigh, m_stencil_helper.unit_shift(+XDIR)); + + // determine local position of current output face inside virtual parent cell using integer + // coordinates in -1, +1 + auto const & ii = ivar == IX ? face_loc_out.ijk[IY] : face_loc_out.ijk[IX]; + const int i = 2 * (ii - 2 * (ii / 2)) - 1; + + // compute limited slopes + auto const dfdx = m_stencil_helper.compute_minmod_slopes_prolongation( + face_loc_neigh, face_loc_right, face_loc_left, m_facedata_in); + + // extrapolate + m_facedata_out(face_loc_out.ijk[IX], face_loc_out.ijk[IY], ivar, iOct_local) = + m_facedata_in(face_loc_neigh.ijk[IX], face_loc_neigh.ijk[IY], ivar, face_loc_neigh.iOct) + + ONE_FOURTH_F * static_cast(i) * dfdx; + +} // linear_extrapolate_using_limited_slopes - 2d + +// ============================================================== +// ============================================================== +template +template > +KOKKOS_INLINE_FUNCTION void +FillBlockGhostFacesFunctor::linear_extrapolate_using_limited_slopes( + FaceLocation<3> const & face_loc_neigh, + FaceLocation<3> const & face_loc_out, + int32_t const & iOct_local) const +{ + // doing a prolongation: the neighbor must be at coarse level + KOKKOS_ASSERT((face_loc_neigh.level() + 1 == face_loc_out.level()) && + "Invalid face location for doing a prolongation"); + + auto const & ivar = face_loc_neigh.ijk[dim]; + + if (ivar == IX) + { + // get transverse slopes + const auto face_loc_left_y = + m_stencil_helper.getNeighLoc(face_loc_neigh, m_stencil_helper.unit_shift(-YDIR)); + + const auto face_loc_right_y = + m_stencil_helper.getNeighLoc(face_loc_neigh, m_stencil_helper.unit_shift(+YDIR)); + + const auto face_loc_left_z = + m_stencil_helper.getNeighLoc(face_loc_neigh, m_stencil_helper.unit_shift(-ZDIR)); + + const auto face_loc_right_z = + m_stencil_helper.getNeighLoc(face_loc_neigh, m_stencil_helper.unit_shift(+ZDIR)); + + // determine local position of current output face inside virtual parent cell using integer + // coordinates in -1, +1 + auto const & jj = face_loc_out.ijk[IY]; + const int j = 2 * (jj - 2 * (jj / 2)) - 1; + + auto const & kk = face_loc_out.ijk[IZ]; + const int k = 2 * (kk - 2 * (kk / 2)) - 1; + + // compute limited slopes + auto const dfdy = m_stencil_helper.compute_minmod_slopes_prolongation( + face_loc_neigh, face_loc_right_y, face_loc_left_y, m_facedata_in); + + auto const dfdz = m_stencil_helper.compute_minmod_slopes_prolongation( + face_loc_neigh, face_loc_right_z, face_loc_left_z, m_facedata_in); + + // extrapolate + m_facedata_out( + face_loc_out.ijk[IX], face_loc_out.ijk[IY], face_loc_out.ijk[IZ], ivar, iOct_local) = + m_facedata_in(face_loc_neigh.ijk[IX], + face_loc_neigh.ijk[IY], + face_loc_neigh.ijk[IZ], + ivar, + face_loc_neigh.iOct) + + ONE_FOURTH_F * static_cast(j) * dfdy + ONE_FOURTH_F * static_cast(k) * dfdz; + } + else if (ivar == IY) + { + // get transverse slopes + const auto face_loc_left_x = + m_stencil_helper.getNeighLoc(face_loc_neigh, m_stencil_helper.unit_shift(-XDIR)); + + const auto face_loc_right_x = + m_stencil_helper.getNeighLoc(face_loc_neigh, m_stencil_helper.unit_shift(+XDIR)); + + const auto face_loc_left_z = + m_stencil_helper.getNeighLoc(face_loc_neigh, m_stencil_helper.unit_shift(-ZDIR)); + + const auto face_loc_right_z = + m_stencil_helper.getNeighLoc(face_loc_neigh, m_stencil_helper.unit_shift(+ZDIR)); + + // determine local position of current output face inside virtual parent cell using integer + // coordinates in -1, +1 + auto const & ii = face_loc_out.ijk[IX]; + const int i = 2 * (ii - 2 * (ii / 2)) - 1; + + auto const & kk = face_loc_out.ijk[IZ]; + const int k = 2 * (kk - 2 * (kk / 2)) - 1; + + // compute limited slopes + auto const dfdx = m_stencil_helper.compute_minmod_slopes_prolongation( + face_loc_neigh, face_loc_right_x, face_loc_left_x, m_facedata_in); + + auto const dfdz = m_stencil_helper.compute_minmod_slopes_prolongation( + face_loc_neigh, face_loc_right_z, face_loc_left_z, m_facedata_in); + + // extrapolate + m_facedata_out( + face_loc_out.ijk[IX], face_loc_out.ijk[IY], face_loc_out.ijk[IZ], ivar, iOct_local) = + m_facedata_in(face_loc_neigh.ijk[IX], + face_loc_neigh.ijk[IY], + face_loc_neigh.ijk[IZ], + ivar, + face_loc_neigh.iOct) + + ONE_FOURTH_F * static_cast(i) * dfdx + ONE_FOURTH_F * static_cast(k) * dfdz; + } + else if (ivar == IZ) + { + // get transverse slopes + const auto face_loc_left_x = + m_stencil_helper.getNeighLoc(face_loc_neigh, m_stencil_helper.unit_shift(-XDIR)); + + const auto face_loc_right_x = + m_stencil_helper.getNeighLoc(face_loc_neigh, m_stencil_helper.unit_shift(+XDIR)); + + const auto face_loc_left_y = + m_stencil_helper.getNeighLoc(face_loc_neigh, m_stencil_helper.unit_shift(-YDIR)); + + const auto face_loc_right_y = + m_stencil_helper.getNeighLoc(face_loc_neigh, m_stencil_helper.unit_shift(+YDIR)); + + // determine local position of current output face inside virtual parent cell using integer + // coordinates in -1, +1 + auto const & ii = face_loc_out.ijk[IX]; + const int i = 2 * (ii - 2 * (ii / 2)) - 1; + + auto const & jj = face_loc_out.ijk[IY]; + const int j = 2 * (jj - 2 * (jj / 2)) - 1; + + // compute limited slopes + auto const dfdx = m_stencil_helper.compute_minmod_slopes_prolongation( + face_loc_neigh, face_loc_right_x, face_loc_left_x, m_facedata_in); + + auto const dfdy = m_stencil_helper.compute_minmod_slopes_prolongation( + face_loc_neigh, face_loc_right_y, face_loc_left_y, m_facedata_in); + + // extrapolate + m_facedata_out( + face_loc_out.ijk[IX], face_loc_out.ijk[IY], face_loc_out.ijk[IZ], ivar, iOct_local) = + m_facedata_in(face_loc_neigh.ijk[IX], + face_loc_neigh.ijk[IY], + face_loc_neigh.ijk[IZ], + ivar, + face_loc_neigh.iOct) + + ONE_FOURTH_F * static_cast(i) * dfdx + ONE_FOURTH_F * static_cast(j) * dfdy; + } + +} // linear_extrapolate_using_limited_slopes - 3d + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +FillBlockGhostFacesFunctor::internal_faces_prolongation_by_toth_and_roe( + face_multiindex_t const & face_indexes_out, + int32_t const & iOct_local) const +{ + const auto & ivar = face_indexes_out[dim]; + + if constexpr (dim == 2) + { + auto const & i_new = face_indexes_out[IX]; + auto const & j_new = face_indexes_out[IY]; + + // "eldest" face : all coords even + const auto i_e = (i_new / 2) * 2; + const auto j_e = (j_new / 2) * 2; + + // reminder: we MUST use m_facedata_out for accessing external faces + + // note : when ivar = IZ (in 2D) there are no "internal" faces + // in other words, ivar = IZ is already taken into account when filling external faces + + real_t Uxx = ZERO_F; + real_t Vyy = ZERO_F; + for (int j = -1; j <= 1; j += 2) + for (int i = -1; i <= 1; i += 2) + { + // clang-format off + Uxx += static_cast(i * j) * m_facedata_out(i_e + (i + 1) / 2, j_e + (j + 1) , IY, iOct_local); + Vyy += static_cast(i * j) * m_facedata_out(i_e + (i + 1) , j_e + (j + 1) / 2, IX, iOct_local); + // clang-format on + } + Uxx /= 4; + Vyy /= 4; + + if (ivar == IX) + { + auto mid_value = HALF_F * (m_facedata_out(i_new - 1, j_new, IX, iOct_local) + + m_facedata_out(i_new + 1, j_new, IX, iOct_local)); + + m_facedata_out(i_new, j_new, ivar, iOct_local) = mid_value + Uxx; + } + else if (ivar == IY) + { + auto mid_value = HALF_F * (m_facedata_out(i_new, j_new - 1, IY, iOct_local) + + m_facedata_out(i_new, j_new + 1, IY, iOct_local)); + + m_facedata_out(i_new, j_new, ivar, iOct_local) = mid_value + Vyy; + } + } + else if constexpr (dim == 3) + { + auto const & i_new = face_indexes_out[IX]; + auto const & j_new = face_indexes_out[IY]; + auto const & k_new = face_indexes_out[IZ]; + + // "eldest" face : all coords even + const auto i_e = (i_new / 2) * 2; + const auto j_e = (j_new / 2) * 2; + const auto k_e = (k_new / 2) * 2; + + // compute local face index among group of 4 co-planar faces + const int ii = 2 * (i_new - i_e) - 1; + const int jj = 2 * (j_new - j_e) - 1; + const int kk = 2 * (k_new - k_e) - 1; + + real_t Uxx = ZERO_F; + real_t Vyy = ZERO_F; + real_t Wzz = ZERO_F; + + real_t Uxyz = ZERO_F; + real_t Vxyz = ZERO_F; + real_t Wxyz = ZERO_F; + + // reminder: we must use m_facedata_out for accessing external faces + + for (int k = -1; k <= 1; k += 2) + for (int j = -1; j <= 1; j += 2) + for (int i = -1; i <= 1; i += 2) + { + // clang-format off + Uxx += + static_cast(i * j) * + m_facedata_out(i_e + (i + 1) / 2, j_e + (j + 1) , k_e + (k + 1) / 2, IY, iOct_local); + Uxx += + static_cast(i * k) * + m_facedata_out(i_e + (i + 1) / 2, j_e + (j + 1) / 2, k_e + (k + 1) , IZ, iOct_local); + + Vyy += + static_cast(j * k) * + m_facedata_out(i_e + (i + 1) / 2, j_e + (j + 1) / 2, k_e + (k + 1) , IZ, iOct_local); + Vyy += + static_cast(j * i) * + m_facedata_out(i_e + (i + 1) , j_e + (j + 1) / 2, k_e + (k + 1) / 2, IX, iOct_local); + + Wzz += + static_cast(k * i) * + m_facedata_out(i_e + (i + 1) , j_e + (j + 1) / 2, k_e + (k + 1) / 2, IX, iOct_local); + Wzz += + static_cast(k * j) * + m_facedata_out(i_e + (i + 1) / 2, j_e + (j + 1) , k_e + (k + 1) / 2, IY, iOct_local); + + Uxyz += + static_cast(i * j * k) * + m_facedata_out(i_e + (i + 1) , j_e + (j + 1) / 2, k_e + (k + 1) / 2, IX, iOct_local); + + Vxyz += + static_cast(i * j * k) * + m_facedata_out(i_e + (i + 1) / 2, j_e + (j + 1) , k_e + (k + 1) / 2, IY, iOct_local); + + Wxyz += + static_cast(i * j * k) * + m_facedata_out(i_e + (i + 1) / 2, j_e + (j + 1) / 2, k_e + (k + 1) , IZ, iOct_local); + // clang-format on + } + Uxx /= 8; + Vyy /= 8; + Wzz /= 8; + Uxyz /= 16; // because Delta x = Delta y = Delta z + Vxyz /= 16; // because Delta x = Delta y = Delta z + Wxyz /= 16; // because Delta x = Delta y = Delta z + + if (ivar == IX) + { + auto mid_value = HALF_F * (m_facedata_out(i_new - 1, j_new, k_new, IX, iOct_local) + + m_facedata_out(i_new + 1, j_new, k_new, IX, iOct_local)); + + m_facedata_out(i_new, j_new, k_new, ivar, iOct_local) = + mid_value + Uxx + static_cast(kk) * Vxyz + static_cast(jj) * Wxyz; + } + if (ivar == IY) + { + auto mid_value = HALF_F * (m_facedata_out(i_new, j_new - 1, k_new, IY, iOct_local) + + m_facedata_out(i_new, j_new + 1, k_new, IY, iOct_local)); + + m_facedata_out(i_new, j_new, k_new, ivar, iOct_local) = + mid_value + Vyy + static_cast(ii) * Wxyz + static_cast(kk) * Uxyz; + } + if (ivar == IZ) + { + auto mid_value = HALF_F * (m_facedata_out(i_new, j_new, k_new - 1, IZ, iOct_local) + + m_facedata_out(i_new, j_new, k_new + 1, IZ, iOct_local)); + + m_facedata_out(i_new, j_new, k_new, ivar, iOct_local) = + mid_value + Wzz + static_cast(jj) * Uxyz + static_cast(ii) * Vxyz; + } + + } // end 3D + +} // internal_faces_prolongation_by_toth_and_roe + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +FillBlockGhostFacesFunctor::fill_inner( + face_multiindex_t const & face_indexes_in, + face_multiindex_t const & face_indexes_out, + int32_t iOct_local) const +{ + + // clang-format off + if constexpr (dim == 2) + { + m_facedata_out(face_indexes_out[IX], + face_indexes_out[IY], + face_indexes_out[dim], + iOct_local) = + m_facedata_in(face_indexes_in[IX], + face_indexes_in[IY], + face_indexes_in[dim], + iOct_local + m_iOct_begin); + } + else if constexpr (dim == 3) + { + m_facedata_out(face_indexes_out[IX], + face_indexes_out[IY], + face_indexes_out[IZ], + face_indexes_out[dim], + iOct_local) = + m_facedata_in(face_indexes_in[IX], + face_indexes_in[IY], + face_indexes_in[IZ], + face_indexes_in[dim], + iOct_local + m_iOct_begin); + } + // clang-format on + +} // fill_inner + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +FillBlockGhostFacesFunctor::fill_all_faces_but_internal( + face_multiindex_t const & face_indexes_out, + int32_t const & iOct_local) const +{ + auto const & ivar = face_indexes_out[dim]; + + // coordinates of source face + face_multiindex_t face_indexes_in; + + // compute source face coordinates + const auto dir = convert_face_indexes_out_to_in(face_indexes_in, face_indexes_out); + + auto dir_norm = dir[IX] * dir[IX] + dir[IY] * dir[IY]; + if constexpr (dim == 3) + dir_norm += dir[IZ] * dir[IZ]; + + // check if current quadrant is ghost or outside; in that case, we only fill inner faces + // if ((dir_norm == 0) and (iOct_local >= (m_amr_mesh_info.local_num_quadrants() + + // m_amr_mesh_info.local_num_ghosts()))) + if ((dir_norm == 0) and (iOct_local >= (m_amr_mesh_info.local_num_quadrants()))) + { + fill_inner(face_indexes_in, face_indexes_out, iOct_local); + return; + } + + // deal with all other quadrants (owned and ghost) + if (dir_norm == 0) + { + // current faces is inside current inner block + fill_inner(face_indexes_in, face_indexes_out, iOct_local); + } + else + { + // current face belongs to a ghost cell (thus belonging to a neighbor block) + + /* + * fill ghost faces all around + */ + + // get orchard key of current octant + auto key_cur = m_stencil_helper.key(iOct_local + m_iOct_begin); + + const auto & b = m_block_sizes; + + shift_t shift; + shift[IX] = b[IX] * dir[IX]; + shift[IY] = b[IY] * dir[IY]; + if constexpr (dim == 3) + { + shift[IZ] = b[IZ] * dir[IZ]; + } + + // coordinates of source cell (where to read data) + // const auto cell_coord_in = face_to_cell_coords(face_indexes_in, + // m_block_sizes); + + // const CellLocation_t cell_loc_cur{ cell_coord_in, key_cur, iOct_local + m_iOct_begin, false + // }; const auto cell_loc_neigh = m_stencil_helper.getNeighLoc(cell_loc_cur, shift); + + const FaceLocation_t face_loc_cur{ face_indexes_in, key_cur, iOct_local + m_iOct_begin, false }; + auto face_loc_neigh = m_stencil_helper.getNeighLoc(face_loc_cur, shift); + + /* + * Dealing with the 3 possibilities: + * - neighbor octant is at same AMR level : doing a simple copy + * - neighbor octant is at finer AMR level : doing a restriction (average values) + * - neighbor octant is at coarser AMR level : doing a prolongation + */ + + if (face_loc_neigh.level() == face_loc_cur.level()) + { + // doing a simple copy + + // clang-format off + if constexpr (dim == 2) + { + m_facedata_out(face_indexes_out[IX], + face_indexes_out[IY], ivar, iOct_local) = + m_facedata_in(face_indexes_in[IX], + face_indexes_in[IY], ivar, face_loc_neigh.iOct); + } + else if constexpr (dim == 3) + { + m_facedata_out(face_indexes_out[IX], + face_indexes_out[IY], + face_indexes_out[IZ], ivar, iOct_local) = + m_facedata_in(face_indexes_in[IX], + face_indexes_in[IY], + face_indexes_in[IZ], ivar, face_loc_neigh.iOct); + } + // clang-format on + } + else if (face_loc_neigh.level() + 1 == face_loc_cur.level()) + { + + // + // if face is ambiguous, we need to check if the symmetric neighbor must be used instead + // + if (is_face_ambiguous(face_indexes_out, m_block_sizes)) + { + const auto face_loc_neigh2 = m_stencil_helper.getBorderFaceLocSymmetric(face_loc_neigh); + + if (face_loc_neigh2.level() == face_loc_cur.level()) + { + face_loc_neigh = face_loc_neigh2; + + // we also need to adjust face coords since face_loc_neigh2 has been obtained by + // shifting from a coarser neighbor + if constexpr (dim == 2) + { + if (face_indexes_out[dim] == IX) + { + if (face_indexes_out[IY] & 0x1) + face_loc_neigh.ijk[IY] += 1; + } + else if (face_indexes_out[dim] == IY) + { + if (face_indexes_out[IX] & 0x1) + face_loc_neigh.ijk[IX] += 1; + } + } + else if constexpr (dim == 3) + { + if (face_indexes_out[dim] == IX) + { + if (face_indexes_out[IY] & 0x1) + face_loc_neigh.ijk[IY] += 1; + if (face_indexes_out[IZ] & 0x1) + face_loc_neigh.ijk[IZ] += 1; + } + else if (face_indexes_out[dim] == IY) + { + if (face_indexes_out[IX] & 0x1) + face_loc_neigh.ijk[IX] += 1; + if (face_indexes_out[IZ] & 0x1) + face_loc_neigh.ijk[IZ] += 1; + } + else if (face_indexes_out[dim] == IZ) + { + if (face_indexes_out[IX] & 0x1) + face_loc_neigh.ijk[IX] += 1; + if (face_indexes_out[IY] & 0x1) + face_loc_neigh.ijk[IY] += 1; + } + } + } + } // end ambiguous face special treatment + + // + // doing a PROLONGATION because neighbor is coarser + // only fill external faces (the ones that are co-localized with mother cells) + // + + if (is_external_face(face_indexes_out)) + { + if (m_prolongation.m_face_external == +FaceCenteredProlongationExternalType::SIMPLE_COPY) + { + // simple copy of the coarse value + + // clang-format off + if constexpr (dim == 2) + { + m_facedata_out(face_indexes_out[IX], + face_indexes_out[IY], ivar, iOct_local) = + m_facedata_in(face_loc_neigh.ijk[IX], + face_loc_neigh.ijk[IY], ivar, face_loc_neigh.iOct); + } + else if constexpr (dim == 3) + { + m_facedata_out(face_indexes_out[IX], + face_indexes_out[IY], + face_indexes_out[IZ], ivar, iOct_local) = + m_facedata_in(face_loc_neigh.ijk[IX], + face_loc_neigh.ijk[IY], + face_loc_neigh.ijk[IZ], ivar, face_loc_neigh.iOct); + } + // clang-format on + } + else if (m_prolongation.m_face_external == + +FaceCenteredProlongationExternalType::EXTRAPOLATE_LINEAR_MINMOD) + { + // not entirely sure this is a good idea to use this; we should probably stick with + // simple copy. + + // this only valid for ivar=IX,IY not IZ in 2d + + const FaceLocation_t face_loc_out{ + face_indexes_out, key_cur, iOct_local + m_iOct_begin, false + }; + + if constexpr (dim == 2) + { + if (ivar == IZ) + { + // do a simple copy + m_facedata_out(face_indexes_out[IX], face_indexes_out[IY], ivar, iOct_local) = + m_facedata_in( + face_loc_neigh.ijk[IX], face_loc_neigh.ijk[IY], ivar, face_loc_neigh.iOct); + } + else + { + linear_extrapolate_using_limited_slopes(face_loc_neigh, face_loc_out, iOct_local); + } + } + else if constexpr (dim == 3) + { + linear_extrapolate_using_limited_slopes(face_loc_neigh, face_loc_out, iOct_local); + } + } + } + } + else if (face_loc_neigh.level() == face_loc_cur.level() + 1) + { + // doing a RESTRICTION + + // clang-format off + if constexpr (dim == 2) + { + m_facedata_out(face_indexes_out[IX], + face_indexes_out[IY], ivar, iOct_local) = + m_stencil_helper.compute_face_siblings_average( + face_loc_neigh, m_facedata_in); + } + else if constexpr (dim == 3) + { + m_facedata_out(face_indexes_out[IX], + face_indexes_out[IY], + face_indexes_out[IZ], ivar, iOct_local) = + m_stencil_helper.compute_face_siblings_average( + face_loc_neigh, m_facedata_in); + } + // clang-format on + } + else + { + KOKKOS_ASSERT(false && "Logic error: neighbor octant not found (Kernel Panic !)"); + } + + } // end if (dir_norm == 0) + +} // fill_all_faces_but_internal + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +FillBlockGhostFacesFunctor::fill_internal_faces_ghost( + face_multiindex_t const & face_indexes_out, + int32_t const & iOct_local) const +{ + [[maybe_unused]] auto const & ivar = face_indexes_out[dim]; + + // coordinates of source face + face_multiindex_t face_indexes_in; + + // compute source face coordinates + const auto dir = convert_face_indexes_out_to_in(face_indexes_in, face_indexes_out); + + auto dir_norm = dir[IX] * dir[IX] + dir[IY] * dir[IY]; + if constexpr (dim == 3) + dir_norm += dir[IZ] * dir[IZ]; + + // make sure we are outside inner block + if (dir_norm > 0) + { + // get orchard key of current octant + auto key_cur = m_stencil_helper.key(iOct_local + m_iOct_begin); + + const auto & b = m_block_sizes; + + shift_t shift; + shift[IX] = b[IX] * dir[IX]; + shift[IY] = b[IY] * dir[IY]; + if constexpr (dim == 3) + { + shift[IZ] = b[IZ] * dir[IZ]; + } + + const FaceLocation_t face_loc_cur{ face_indexes_in, key_cur, iOct_local + m_iOct_begin, false }; + const auto face_loc_neigh = m_stencil_helper.getNeighLoc(face_loc_cur, shift); + + if (face_loc_neigh.level() + 1 == face_loc_cur.level()) + { + // doing a prolongation because neighbor is coarser + // only fill internal faces (not the ones that are co-localized with mother cells) + + // beware that the following can only happen after all external faces are filled since + // up-to-date external faces are required to fill internal faces. + if (is_internal_face(face_indexes_out)) + { + if (m_prolongation.m_face_internal == +FaceCenteredProlongationInternalType::TOTH_AND_ROE) + { + internal_faces_prolongation_by_toth_and_roe(face_indexes_out, iOct_local); + } + } // end internal faces + } // end prolongation + } // end dir_norm > 0 + +} // fill_internal_faces_ghost + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +FillBlockGhostFacesFunctor::operator()(TagFillAllFacesButInternal const &, + const index_t & global_index) const +{ + + const auto num_elements_per_octant = m_facedata_out.num_elements_per_octant(); + + const int32_t iOct_local = static_cast(global_index / num_elements_per_octant); + const int32_t face_flat_index = + static_cast(global_index - iOct_local * num_elements_per_octant); + + // compute ix,iy,iz,ivar of local face inside + // block from a face flat-index + const auto face_indexes_out = face_flat_index_unravel( + face_flat_index, m_total_sizes, m_facedata_out.offsets(), m_facedata_out.shift()); + + fill_all_faces_but_internal(face_indexes_out, iOct_local); + +} // operator() - TagFillAllFacesButInternal + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +FillBlockGhostFacesFunctor::operator()(TagFillInternalFaces const &, + const index_t & global_index) const +{ + + const auto num_elements_per_octant = m_facedata_out.num_elements_per_octant(); + + const int32_t iOct_local = static_cast(global_index / num_elements_per_octant); + const int32_t face_flat_index = + static_cast(global_index - iOct_local * num_elements_per_octant); + + // compute ix,iy,iz,ivar of local face inside + // block from a face flat-index + const auto face_indexes_out = face_flat_index_unravel( + face_flat_index, m_total_sizes, m_facedata_out.offsets(), m_facedata_out.shift()); + + fill_internal_faces_ghost(face_indexes_out, iOct_local); + +} // operator() - TagFillInternalFaces + +template class FillBlockGhostFacesFunctor<2, kalypsso::DefaultDevice>; +template class FillBlockGhostFacesFunctor<3, kalypsso::DefaultDevice>; + +} // namespace kalypsso diff --git a/src/kalypsso/core/FillBlockGhostFaces.h b/src/kalypsso/core/FillBlockGhostFaces.h new file mode 100644 index 0000000..392f577 --- /dev/null +++ b/src/kalypsso/core/FillBlockGhostFaces.h @@ -0,0 +1,298 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file FillBlockGhostFaces.h + */ +#ifndef KALYPSSO_CORE_FILLBLOCKGHOSTFACES_H_ +#define KALYPSSO_CORE_FILLBLOCKGHOSTFACES_H_ + +#include +#include // for orchard_key_view_t alias +#include +#include +#include +#include + +namespace kalypsso +{ + +/** + * \class FillBlockGhostFacesFunctor + * + * This is a kokkos functor class that takes as input a FaceDataArrayBlock and as + * output another FaceDataArrayBlock (with larger block size), and for each + * quadrant, fill block ghost faces. + * Again, just to be clear, the input array is non-ghosted, output array is ghosted. + * + * Limitations: + * - this class only supports block sizes that are even integers. + * - this class only supports ghost sizes that are even integers. + * + * As a reminder: + * - external faces are faces with even coordinate in the normal direction + * - internal faces are faces with odd coordinate in the normal direction + * + * \note the main difference with class FillBlockGhostCellsFunctor is that here we do things in two + * steps (hence the definition of two tagged operator() ). + * + * - First step computes all faces except news faces (called internal faces) when doing + * prolongation. + * - Second step computes new internal faces in the ghost zones when doing prolongation. + * + * Prolongating external faces is done using either: + * - simple copy + * - linear extrapolation using limited slopes (when neighbor block is coarser than current block) + * + * Prolongation internal faces is done using a divergence preserving algorithm + * - currently only Toth and Roe (2002) is implemented, reference + * Toth and Roe, JCP, 180, 746-759, 2002: Divergence- and curl-preserving + * prolongation and restriction formulas. https://doi.org/10.1006/jcph.2002.7120 + * + * + * - on the left, we have a coarse cell that has 4 faces + * - on the right, we have 4 fine cells, 8 external faces, and 4 internal faces (labeled with "+") + * + * AMR level l AMR level l+1 + * ________________ ________ ________ + * | | | + | + * | | prolongation | + | + * | | ====> | + | + * | | +++++++ +++++++ + * | | <==== | + | + * | | restriction | + | + * |______________| |_______+______ | + * + * + * See file test_AMRmesh_fill_block_ghosts_faces.cpp for testing + * + * \note Important note: which quadrant must be filled ? + * - MPI owned quadrants : inner part and ghost cells + * - MPI ghost quadrants : inner part and ghost cells + * - outside quadrants : only the inner part which will be used when computing primitive variables + */ +template +class FillBlockGhostFacesFunctor +{ + +public: + using exec_space = typename device_t::execution_space; + using index_t = int32_t; + + using amr_hashmap_t = typename hashmap_base_t::map_t; + using orchard_key_view_t = typename orchard_key_base_t::view_t; + + using DataArrayBlock_t = DataArrayBlock; + using FaceDataArrayBlock_t = FaceDataArrayBlock; + + using CellLocation_t = CellLocation; + using FaceLocation_t = FaceLocation; + using StencilHelper_t = StencilHelper; + + //! Compute everything: fill ghost faces (inner block + external faces in ghost zone) + struct TagFillAllFacesButInternal + {}; + + //! Finalize filling ghost faces (internal faces in ghost zone) + struct TagFillInternalFaces + {}; + + /** + * + * \param[in] stencil helper + * \param[in] iOct_begin is the first octant to process + * \param[in] num_octants is the number of octant to process + * \param[in] facedata_in non-ghosted face data array + * \param[in,out] facedata_out ghosted face data array + * \param[in] prolongation selects how coarse neighbor must be prolongated to fill ghost faces + */ + FillBlockGhostFacesFunctor(StencilHelper_t stencil_helper, + int32_t iOct_begin, + int32_t num_octants, + FaceDataArrayBlock_t facedata_in, + FaceDataArrayBlock_t facedata_out, + ProlongationParam prolongation, + AMRMeshInfo amr_mesh_info) + : m_stencil_helper(stencil_helper) + , m_iOct_begin(iOct_begin) + , m_num_octants(num_octants) + , m_facedata_in(facedata_in) + , m_facedata_out(facedata_out) + , m_block_sizes(facedata_out.cell_block_size_inner()) + , m_total_sizes(facedata_out.cell_block_size()) + , m_prolongation(prolongation) + , m_amr_mesh_info(amr_mesh_info) + {} + + // ============================================================== + // ============================================================== + //! static method which does it all: create and execute functor with range policy + static void + apply(ConfigMap const & config_map, + amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + [[maybe_unused]] int32_t local_num_octants, + int32_t iOct_begin, + int32_t num_octants, + FaceDataArrayBlock_t facedata_in, + FaceDataArrayBlock_t facedata_out, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + AMRMeshInfo amr_mesh_info); + + // ============================================================== + // ============================================================== + /** + * Given a face multi-index identifying a face in the ghost zone, determine is face is ambiguous, + * i.e. shared by two neighbor blocks. + * + * Faces that are in the ghost zones and aligned with the block border are said "ambiguous" + * because those faces are colocated with two neighbor blocks (top and below in the following + * drawing). + * + * Two situations may happen: + * - if top and below blocks are at the same AMR level, no problem, they both agree on the value + * attached to the ambiguous face. + * - if "top block" (direct face neighbor) is at level l-1 (i.e. coarser than current block) and + * "below block" is at level l, we must use face value from "below block" (if not, we may have + * artefacts in prolongating values from the "top block", that can be seen e.g. when computing + * divergence). + * + * | ghost zone | + * __|____________|__ + * | |__ + * | Block of |__ + * | cells at |__ neighbor block top at level l-1 + * | AMR level |__ <== non-ambiguous face /\ + * | l |__ || + * __|____________|__ <== ambiguous face ___||_________ + * | | || + * | | \/ + * neighbor block below at level l + */ + KOKKOS_INLINE_FUNCTION + bool + is_face_ambiguous(face_multiindex_t const & face_indexes, + block_size_t const & block_sizes) const; + + // ============================================================== + // ============================================================== + /** + * Convert face indexes out (ghosted) in face indexes in (non ghost) + * + * \return relative direction to neighbor block where to look for data to copy + */ + KOKKOS_INLINE_FUNCTION + Kokkos::Array + convert_face_indexes_out_to_in(face_multiindex_t & face_indexes_in, + face_multiindex_t const & face_indexes_out) const; + + // ============================================================== + // ============================================================== + /** + * Do a prolongation by linear extrapolation using limited slopes. + */ + template = true> + KOKKOS_INLINE_FUNCTION void + linear_extrapolate_using_limited_slopes(FaceLocation<2> const & face_loc_neigh, + FaceLocation<2> const & face_loc_out, + int32_t const & iOct_local) const; + + // ============================================================== + // ============================================================== + /** + * Do a prolongation by linear extrapolation using limited slopes. + */ + template = true> + KOKKOS_INLINE_FUNCTION void + linear_extrapolate_using_limited_slopes(FaceLocation<3> const & face_loc_neigh, + FaceLocation<3> const & face_loc_out, + int32_t const & iOct_local) const; + + // ============================================================== + // ============================================================== + KOKKOS_INLINE_FUNCTION void + internal_faces_prolongation_by_toth_and_roe(face_multiindex_t const & face_indexes_out, + int32_t const & iOct_local) const; + + // ============================================================== + // ============================================================== + /** + * fill interior of ghosted block. + * + * \param[in] cellindex_out is the cell index of the ghost cell to fill + * \param[in] iOct_global is the octant id among all octant owned by current MPI process. + * + * Just to be clear iOct_global - m_iOct_begin is the local octant id inside the group of octant + * being processed. + */ + KOKKOS_INLINE_FUNCTION void + fill_inner(face_multiindex_t const & face_indexes_in, + face_multiindex_t const & face_indexes_out, + int32_t iOct_local) const; + + // ============================================================== + // ============================================================== + KOKKOS_INLINE_FUNCTION void + fill_all_faces_but_internal(face_multiindex_t const & face_indexes_out, + int32_t const & iOct_local) const; + + // ============================================================== + // ============================================================== + KOKKOS_INLINE_FUNCTION void + fill_internal_faces_ghost(face_multiindex_t const & face_indexes_out, + int32_t const & iOct_local) const; + + // ============================================================== + // ============================================================== + /** + * range policy functor + */ + KOKKOS_INLINE_FUNCTION void + operator()(TagFillAllFacesButInternal const &, const index_t & global_index) const; + + // ============================================================== + // ============================================================== + /** + * range policy functor + */ + KOKKOS_INLINE_FUNCTION void + operator()(TagFillInternalFaces const &, const index_t & global_index) const; + +private: + //! helper to compute neighbor cell location + StencilHelper_t m_stencil_helper; + + //! starting octant id + const int32_t m_iOct_begin; + + //! number of octant to process, starting at m_iOct_begin + const int32_t m_num_octants; + + //! a block data array (no ghosts, sizes= bx,by,bz) + FaceDataArrayBlock_t m_facedata_in; + + //! a ghosted data array (which block ghost cells need to be filled) + FaceDataArrayBlock_t m_facedata_out; + + //! block sizes (inner size of ghost block, in unit of cells, not faces) + const block_size_t m_block_sizes; + + //! total sizes (ghosted block size, in unit of cells, not faces) + const block_size_t m_total_sizes; + + //! prolongation type + const ProlongationParam m_prolongation; + + //! AMR mesh info (useful for knowing where outside quadrants are) + const AMRMeshInfo m_amr_mesh_info; + +}; // class FillBlockGhostFacesFunctor + +extern template class FillBlockGhostFacesFunctor<2, kalypsso::DefaultDevice>; +extern template class FillBlockGhostFacesFunctor<3, kalypsso::DefaultDevice>; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_FILLBLOCKGHOSTFACES_H_ diff --git a/src/kalypsso/core/FillBlockGhostFluxes.cpp b/src/kalypsso/core/FillBlockGhostFluxes.cpp new file mode 100644 index 0000000..ab13a17 --- /dev/null +++ b/src/kalypsso/core/FillBlockGhostFluxes.cpp @@ -0,0 +1,363 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file FillBlockGhostFluxes.cpp + * \brief \copybrief FillBlockGhostFluxes.h + */ +#include + +namespace kalypsso +{ +// ================================================================================================ +// ================================================================================================ +template +void +FillBlockGhostFluxesFunctor::check_args_validity( + DataArrayBlock_t const & fluxes_in, + DataArrayGhostedBlock_t const & fluxes_out, + block_size_t const & cell_block_size, + ComponentIndex3D direction) +{ + // check that input is a flux array in given direction + if (fluxes_in.shape()[direction] != (cell_block_size[direction] + 1)) + { + KALYPSSO_ERROR("fluxes_in shape={} and cell_bloc_size={}", + fluxes_in.shape()[direction], + cell_block_size[direction]); + Kokkos::abort("Input has invalid shape"); + } + + // check fluxes_out shape + if (fluxes_out.shift()[IX] + cell_block_size[IX] / 2 < 0) + { + Kokkos::abort("shift[IX] is too large (negative)"); + } + if (fluxes_out.shift()[IY] + cell_block_size[IY] / 2 < 0) + { + Kokkos::abort("shift[IY] is too large (negative)"); + } + + if constexpr (dim == 3) + { + if (fluxes_out.shift()[IZ] + cell_block_size[IZ] / 2 < 0) + { + Kokkos::abort("shift[IZ] is too large (negative)"); + } + } + + + if ((fluxes_out.shift()[IX] + fluxes_out.shape()[IX] - fluxes_out.block_size()[IX]) > + cell_block_size[IX] / 2) + { + Kokkos::abort("shift[IX] is too large (negative)"); + } + if ((fluxes_out.shift()[IY] + fluxes_out.shape()[IY] - fluxes_out.block_size()[IY]) > + cell_block_size[IY] / 2) + { + Kokkos::abort("shift[IY] is too large (negative)"); + } + if constexpr (dim == 3) + { + if ((fluxes_out.shift()[IZ] + fluxes_out.shape()[IZ] - fluxes_out.block_size()[IZ]) > + cell_block_size[IZ] / 2) + { + Kokkos::abort("shift[IZ] is too large (negative)"); + } + } + +} // FillBlockGhostFluxesFunctor::check_args_validity + +// ============================================================== +// ============================================================== +template +void +FillBlockGhostFluxesFunctor::apply(amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + [[maybe_unused]] int32_t local_num_octants, + int32_t iOct_begin, + int32_t num_octants, + block_size_t const & cell_block_size, + ComponentIndex3D direction, + DataArrayBlock_t fluxes_in, + DataArrayGhostedBlock_t fluxes_out, + int var_index_in, + int var_index_out, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic) +{ + + // make sure the range of octants to process is valid + KOKKOS_ASSERT(iOct_begin + num_octants <= local_num_octants && + "Invalid range of octants to process"); + + auto stencil_helper = + StencilHelper_t(amr_hashmap, orchard_keys, cell_block_size, brick_sizes, is_brick_periodic); + + check_args_validity(fluxes_in, fluxes_out, cell_block_size, direction); + + FillBlockGhostFluxesFunctor functor(stencil_helper, + iOct_begin, + num_octants, + direction, + cell_block_size, + fluxes_in, + fluxes_out, + var_index_in, + var_index_out); + + const auto nbFluxesPerGhostedLeaf = fluxes_out.num_cells(); + const auto nbFluxesTotal = num_octants * nbFluxesPerGhostedLeaf; + + // for AMR tree leaf, explore the neighbor block + Kokkos::parallel_for( + "FillBlockGhostFluxesFunctor", Kokkos::RangePolicy(0, nbFluxesTotal), functor); + +} // FillBlockGhostFluxesFunctor::apply + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION int32_t +FillBlockGhostFluxesFunctor::to_flat_index( + face_multiindex_t const & face_multiindex, + block_size_t const & flux_shapes) const +{ + int32_t index = 0; + + if constexpr (dim == 2) + { + return face_multiindex[IX] + flux_shapes[IX] * face_multiindex[IY]; + } + else if constexpr (dim == 3) + { + return face_multiindex[IX] + flux_shapes[IX] * face_multiindex[IY] + + flux_shapes[IX] * flux_shapes[IY] * face_multiindex[IZ]; + } + + return index; + +} // FillBlockGhostFluxesFunctor::to_flat_index + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION face_multiindex_t + FillBlockGhostFluxesFunctor::to_face_multiindex( + int32_t const & flat_index, + block_size_t const & flux_shapes) const +{ + KOKKOS_ASSERT(flat_index >= 0); + + face_multiindex_t res; + + if constexpr (dim == 2) + { + const auto & bx = flux_shapes[IX]; + res[IY] = (flat_index / bx); + res[IX] = (flat_index - bx * res[IY]); + } + else if constexpr (dim == 3) + { + const auto & bx = flux_shapes[IX]; + const auto & by = flux_shapes[IY]; + + res[IZ] = (flat_index / (bx * by)); + int32_t flat_index2 = flat_index - bx * by * res[IZ]; + res[IY] = (flat_index2 / bx); + res[IX] = (flat_index2 - bx * res[IY]); + } + + res[dim] = m_direction; + + return res; + +} // FillBlockGhostFluxesFunctor::to_face_multiindex + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION face_multiindex_t + FillBlockGhostFluxesFunctor::to_face_multiindex( + int32_t const & flat_index, + block_size_t const & flux_shapes, + shift_t const & shift) const +{ + auto res = this->to_face_multiindex(flat_index, flux_shapes); + + res[IX] += shift[IX]; + res[IY] += shift[IY]; + if constexpr (dim == 3) + { + res[IZ] += shift[IZ]; + } + + return res; + +} // FillBlockGhostFluxesFunctor::to_face_multiindex + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +FillBlockGhostFluxesFunctor::fill_inner(int32_t flat_index_in, + int32_t flat_index_out, + int32_t iOct_global) const +{ + + m_fluxes_out(flat_index_out, m_var_index_out, iOct_global - m_iOct_begin) = + m_fluxes_in(flat_index_in, m_var_index_in, iOct_global); + +} // FillBlockGhostFluxesFunctor::fill_inner + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +FillBlockGhostFluxesFunctor::fill_ghosts( + index_t const & flat_index_out, + face_multiindex_t const & face_multiindex_out, + int32_t const & iOct_global) const +{ + + auto face_multiindex_in = face_multiindex_out; + + // direction to neighbor octant where we need to fetch data to fill ghost flux + // dir = 0 means we stay in current octant + int dir = 0; + if (face_multiindex_out[m_direction] < 0) + { + dir = -1; + face_multiindex_in[m_direction] += m_cell_block_size[m_direction]; + } + else if (face_multiindex_out[m_direction] > m_cell_block_size[m_direction]) + { + dir = 1; + face_multiindex_in[m_direction] -= m_cell_block_size[m_direction]; + } + + // + // there is a strong assumption here: + // we assume the flux that are on the block edge have already been corrected when non-conformal + // interface is detected (this is the case in lagrange-remap scheme, where the star variables have + // been corrected in the Lagrange phase.) + // + if (dir == 0) + { + // flux is located inside current block => just copy + const auto flat_index_in = this->to_flat_index(face_multiindex_in, m_fluxes_in.shape()); + + fill_inner(flat_index_in, flat_index_out, iOct_global); + } + else + { + // flux is ghost (thus belonging to a neighbor octant) + + // get orchard key of current octant + auto key_cur = m_stencil_helper.key(iOct_global); + + shift_t shift = get_shift(0); + shift[m_direction] = m_cell_block_size[m_direction] * dir; + + const FaceLocation_t face_loc_cur{ face_multiindex_in, key_cur, iOct_global, false }; + const auto face_loc_neigh = m_stencil_helper.getNeighLoc(face_loc_cur, shift); + + /* + * Dealing with the 3 possibilities: + * - neighbor octant is at same AMR level : doing a simple copy + * - neighbor octant is at finer AMR level : doing a restriction (average values) + * - neighbor octant is at coarser AMR level : doing a prolongation + * + * There is an extra case when neighbor is outside domain; in that case neighbor is by design + * at same AMR level and we need border conditions to get involved. We provide here simple + * border conditions: + * - wall (copy with sign inversion) + * - outflow (simple copy) + * + * Note that if the mesh is periodic, nothing is needed, by design the neighbor will be inside + * domain. + * + * If a user needs additional border condition for this fluxes, he will have to re-implement + * this functor. + */ + const auto iOct_in = face_loc_neigh.iOct; + + + if (face_loc_neigh.level() == face_loc_cur.level()) + { + + if (face_loc_neigh.is_outside_domain and + !m_stencil_helper.is_brick_periodic(face_multiindex_in[dim])) + { + // just copy the value from the block border (that is also the domain border) + // into the ghost + // TODO: see if we really need more than that + + auto ijk = face_multiindex_in; + ijk[m_direction] = dir == -1 ? 0 : m_cell_block_size[m_direction]; + const auto flat_index_in = this->to_flat_index(face_multiindex_in, m_fluxes_in.shape()); + + m_fluxes_out(flat_index_out, m_var_index_out, iOct_global - m_iOct_begin) = + m_fluxes_in(flat_index_in, m_var_index_in, iOct_global); + } + else + { + // doing a simple copy (this is probably not need, cellindex_in computed above is ok) + const auto flat_index_in = this->to_flat_index(face_loc_neigh.ijk, m_fluxes_in.shape()); + + m_fluxes_out(flat_index_out, m_var_index_out, iOct_global - m_iOct_begin) = + m_fluxes_in(flat_index_in, m_var_index_in, iOct_in); + } + } + else if (face_loc_neigh.level() + 1 == face_loc_cur.level()) + { + // doing a prolongation because neighbor is coarser + // simple copy of the coarse value + + const auto flat_index_in = this->to_flat_index(face_loc_neigh.ijk, m_fluxes_in.shape()); + + m_fluxes_out(flat_index_out, m_var_index_out, iOct_global - m_iOct_begin) = + m_fluxes_in(flat_index_in, m_var_index_in, iOct_in); + } + else if (face_loc_neigh.level() == face_loc_cur.level() + 1) + { + // doing a restriction + m_fluxes_out(flat_index_out, m_var_index_out, iOct_global - m_iOct_begin) = + m_stencil_helper.compute_face_siblings_average( + face_loc_neigh, m_cell_block_size, m_var_index_in, m_fluxes_in); + } + else + { + KOKKOS_ASSERT(false && "Logic error: neighbor octant not found (Kernel Panic !)"); + } + + } // end if (dir == 0) + +} // FillBlockGhostFluxesFunctor::fill_ghosts + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +FillBlockGhostFluxesFunctor::operator()(const index_t & global_index) const +{ + + const auto nbFluxesPerGhostedLeaf = m_fluxes_out.num_cells(); + + const auto iOct_local = global_index / nbFluxesPerGhostedLeaf; + const auto flat_index_out = global_index - iOct_local * nbFluxesPerGhostedLeaf; + const auto iOct_global = m_iOct_begin + iOct_local; + + // get face coordinates in the ghost block + const auto face_multiindex_out = + to_face_multiindex(flat_index_out, m_fluxes_out.ghosted_block_size(), m_fluxes_out.shift()); + + fill_ghosts(flat_index_out, face_multiindex_out, iOct_global); + +} // FillBlockGhostFluxesFunctor::operator() + +template class FillBlockGhostFluxesFunctor<2, kalypsso::DefaultDevice>; +template class FillBlockGhostFluxesFunctor<3, kalypsso::DefaultDevice>; + +} // namespace kalypsso diff --git a/src/kalypsso/core/FillBlockGhostFluxes.h b/src/kalypsso/core/FillBlockGhostFluxes.h new file mode 100644 index 0000000..7d50f1b --- /dev/null +++ b/src/kalypsso/core/FillBlockGhostFluxes.h @@ -0,0 +1,226 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file FillBlockGhostFluxes.h + */ +#ifndef KALYPSSO_CORE_FILLBLOCKGHOSTFLUXES_H_ +#define KALYPSSO_CORE_FILLBLOCKGHOSTFLUXES_H_ + +#include +#include // for orchard_key_view_t alias +#include +#include +#include + +namespace kalypsso +{ + +/** + * \class FillBlockGhostFluxesFunctor + * + * Just consider an AMR mesh where the leaves are populated with block of cells of size (bx,by,bz), + * and consider an input flux array fluxes_in. + * If the input corresponds to flux along X direction, then fluxes_in must be a DataArrayBlock of + * size (bx+1, by, bz). + * + * The purpose of this functor is to output a DataArrayGhostedBlock of size + * (bx+1+2+ghost_width, by, bz), that contains ghost elements that are filled by either + * - copying data from a neighbor octant at same AMR level + * - prolongating data from a neighbor octant at coarser AMR level + * - restricting data from a neighbor octant at finer AMR level + * + * Just to clear: + * - the input array is non-ghosted, output array is ghosted. + * - this class only supports cell block sizes that are even integers. + * - the ghost_width can not be strictly larger than bx/2 (resp. by/2 or bz/2); if not we would need + * to potentially access data from neighbor of neighbor octant which is not allowed since our MPI + * ghost exchange operator only transfer a ghost region of one octant all around current MPI + * sub-domain. + * + * This class can be used in a piecewise loop over leaf octant. + * + * Just to illustrate, let's consider 4x4 blocks of cells, an input flux array along X direction + * must have shape 5x4. Assume we want to fill 2 ghost flux along the X direction. + * + * Input flux block Output ghost flux array (2 ghosts on each side) + * 5x4 9x4 + * + * x x x x x o o x x x x x o o + * x x x x x ==> o o x x x x x o o + * x x x x x o o x x x x x o o + * x x x x x o o x x x x x o o + * + * + * The main difficulty here is to deploy the entire combinatorics of + * geometrical possibilities in terms of + * - size of neighbor octant, i.e. + * is neighbor octant smaller, same size or larger than current octant, + * - 2d/3d + * + * So we need to be careful, have good testing code. + * See file test_AMRmesh_fill_block_ghosts_flux.cpp + * + */ +template +class FillBlockGhostFluxesFunctor +{ + +public: + using exec_space = typename device_t::execution_space; + using index_t = int32_t; + + using amr_hashmap_t = typename hashmap_base_t::map_t; + using orchard_key_view_t = typename orchard_key_base_t::view_t; + + using DataArrayBlock_t = DataArrayBlock; + using DataArrayGhostedBlock_t = DataArrayGhostedBlock; + + using FaceLocation_t = FaceLocation; + using StencilHelper_t = StencilHelper; + + /** + * + * \param[in] stencil helper + * \param[in] iOct_begin is the first octant to process + * \param[in] num_octants is the number of octant to process + * \param[in] userdata_in data array used to fill ghost of userdata_out + * \param[in,out] userdata_out data array which we want to fill the block ghosts + * cells + * \param[in] prolongation selects how coarse neighbor must be prolongated to fill ghost cells + */ + FillBlockGhostFluxesFunctor(StencilHelper_t stencil_helper, + int32_t iOct_begin, + int32_t num_octants, + ComponentIndex3D direction, + block_size_t cell_block_size, + DataArrayBlock_t fluxes_in, + DataArrayGhostedBlock_t fluxes_out, + int var_index_in, + int var_index_out) + : m_stencil_helper(stencil_helper) + , m_iOct_begin(iOct_begin) + , m_num_octants(num_octants) + , m_direction(direction) + , m_cell_block_size(cell_block_size) + , m_fluxes_in(fluxes_in) + , m_fluxes_out(fluxes_out) + , m_var_index_in(var_index_in) + , m_var_index_out(var_index_out) + {} + + // ==================================================================== + // ==================================================================== + static void + check_args_validity(DataArrayBlock_t const & fluxes_in, + DataArrayGhostedBlock_t const & fluxes_out, + block_size_t const & cell_block_size, + ComponentIndex3D direction); + + // ============================================================== + // ============================================================== + //! static method which does it all: create and execute functor with range policy + static void + apply(amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + [[maybe_unused]] int32_t local_num_octants, + int32_t iOct_begin, + int32_t num_octants, + block_size_t const & cell_block_size, + ComponentIndex3D direction, + DataArrayBlock_t fluxes_in, + DataArrayGhostedBlock_t fluxes_out, + int var_index_in, + int var_index_out, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic); + + // ============================================================== + // ============================================================== + //! compute flat index inside octant + KOKKOS_INLINE_FUNCTION int32_t + to_flat_index(face_multiindex_t const & face_multiindex, + block_size_t const & flux_shapes) const; + + // ============================================================== + // ============================================================== + //! compute face multi-index inside octant + KOKKOS_INLINE_FUNCTION face_multiindex_t + to_face_multiindex(int32_t const & flat_index, block_size_t const & flux_shapes) const; + + // ============================================================== + // ============================================================== + //! compute face multi-index inside octant + KOKKOS_INLINE_FUNCTION face_multiindex_t + to_face_multiindex(int32_t const & flat_index, + block_size_t const & flux_shapes, + shift_t const & shift) const; + + // ============================================================== + // ============================================================== + /** + * fill interior of ghosted block. + * + * \param[in] flat_index_in is the cell index of the cell to read data from + * \param[in] flat_index_out is the cell index of the ghost cell to fill + * \param[in] iOct_global is the octant id among all octant owned by current MPI process. + * + * Just to be clear iOct_global - m_iOct_begin is the local octant id inside the group of octant + * being processed. + */ + KOKKOS_INLINE_FUNCTION + void + fill_inner(int32_t flat_index_in, int32_t flat_index_out, int32_t iOct_global) const; + + // ============================================================== + // ============================================================== + KOKKOS_INLINE_FUNCTION void + fill_ghosts(index_t const & flat_index_out, + face_multiindex_t const & facecoord_out, + int32_t const & iOct_global) const; + + // ============================================================== + // ============================================================== + /** + * range policy functor + */ + KOKKOS_INLINE_FUNCTION void + operator()(const index_t & global_index) const; + +private: + //! helper to compute neighbor cell location + StencilHelper_t m_stencil_helper; + + //! starting octant id + const int32_t m_iOct_begin; + + //! number of octant to process, starting at m_iOct_begin + const int32_t m_num_octants; + + //! direction associated to flux + const ComponentIndex3D m_direction; + + //! cell block size (bx,by,bz) + const block_size_t m_cell_block_size; + + //! a flux data array (no ghosts, sizes must cell block size plus one in a given direction) + DataArrayBlock_t m_fluxes_in; + + //! a ghosted data array (which block ghost cells need to be filled) + DataArrayGhostedBlock_t m_fluxes_out; + + //! variable index in input array + const int m_var_index_in; + + //! variable index in output array + const int m_var_index_out; + +}; // class FillBlockGhostFluxesFunctor + +extern template class FillBlockGhostFluxesFunctor<2, kalypsso::DefaultDevice>; +extern template class FillBlockGhostFluxesFunctor<3, kalypsso::DefaultDevice>; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_FILLBLOCKGHOSTFLUXES_H_ diff --git a/src/kalypsso/core/FillBlockGhosts_common.h b/src/kalypsso/core/FillBlockGhosts_common.h new file mode 100644 index 0000000..db2bb55 --- /dev/null +++ b/src/kalypsso/core/FillBlockGhosts_common.h @@ -0,0 +1,635 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file FillBlockGhosts_common.h + */ +#ifndef KALYPSSO_CORE_FILLBLOCKGHOSTS_COMMON_H_ +#define KALYPSSO_CORE_FILLBLOCKGHOSTS_COMMON_H_ + +#include +#include +#include + +#include // for DataArray, DataArrayHost, DataArrayGhostedBlock +#include +#include +#include +#include + +#include + +#include // for definition of function cellindex_to_coord and coord_to_cellindex + +namespace kalypsso +{ + +// ======================================================= +// ======================================================= +/** + * return true if a face is a face on the left + */ +template +KOKKOS_INLINE_FUNCTION bool +is_left_face(const Face::face_t & face) +{ + if constexpr (dim == 2) + return (face == Face::XMIN) or (face == Face::YMIN); + if constexpr (dim == 3) + return (face == Face::XMIN) or (face == Face::YMIN) or (face == Face::ZMIN); +} + +// ======================================================= +// ======================================================= +/** + * return true if a face is a face on the right + */ +template +KOKKOS_INLINE_FUNCTION bool +is_right_face(const Face::face_t & face) +{ + if constexpr (dim == 2) + return (face == Face::XMAX) or (face == Face::YMAX); + if constexpr (dim == 3) + return (face == Face::XMAX) or (face == Face::YMAX) or (face == Face::ZMAX); +} + +/** + * input : 2 faces identifying a corner + * output : cornerId + * + * Corners are enumerated in Morton order + * + * 2 --- 3 + * | | + * | | + * 0 --- 1 + */ +KOKKOS_INLINE_FUNCTION uint8_t +compute_corner_id(const Face::face_t & face_x, const Face::face_t & face_y) +{ + return (face_x & 0x1) + ((face_y & 0x1) << 1); +} + +KOKKOS_INLINE_FUNCTION uint8_t +compute_corner_id(const Face::face_t & face_x, + const Face::face_t & face_y, + const Face::face_t & face_z) +{ + return static_cast((face_x & 0x1) + ((face_y & 0x1) << 1) + ((face_z & 0x1) << 2)); +} + +template +KOKKOS_INLINE_FUNCTION uint8_t +compute_corner_id(Kokkos::Array const & faces) +{ + if constexpr (dim == 2) + { + return static_cast((faces[IX] & 0x1) + ((faces[IY] & 0x1) << 1)); + } + else if constexpr (dim == 3) + { + return static_cast((faces[IX] & 0x1) + ((faces[IY] & 0x1) << 1) + + ((faces[IZ] & 0x1) << 2)); + } +} + +// =========================================================== +// =========================================================== +template +KOKKOS_INLINE_FUNCTION bool +is_edge_along(Edge::edge_t edge) +{ + if constexpr (dir == Dir::Z) + if (edge < 4) + return true; + + if constexpr (dir == Dir::X) + if (edge < 8 and edge >= 4) + return true; + + if constexpr (dir == Dir::Y) + if (edge < 12 and edge >= 8) + return true; + + return false; +} // is_edge_along + +// ============================================================== +// ============================================================== +KOKKOS_INLINE_FUNCTION Edge::edge_t + compute_edge_id(const Face::face_t & face0, const Face::face_t & face1) +{ + KOKKOS_ASSERT(face0 / 2 < face1 / 2 && + "WRONG VALUE, faces must be orthogonal and Morton ordered."); + + Edge::edge_t res = (face0 & 0x1) + ((face1 & 0x1) << 1); + + if (((face1 / 2) == Dir::Z) and ((face0 / 2) == Dir::Y)) + res += 4; + + if (((face1 / 2) == Dir::Z) and ((face0 / 2) == Dir::X)) + res += 8; + + return res; +} + +// ============================================================== +// ============================================================== +/** + * Shift (geometric translation) the coordinates of cell (inside a block), moving from right to left + * face. + * + * \param[in,out] coord coordinates of a cell in a face, to be shifted to the left most face + * \param[in] block sizes + * \param[in] ghost sizes + * \param[in] face id + * + */ +template +KOKKOS_INLINE_FUNCTION void +shift_coord_face(coord_t & coord, + block_size_t const & b, + block_size_t const & g, + Face::face_t face) +{ + + const Dir::dir_t dir = face / 2; + + if constexpr (dim == 2) + { + if (dir == Dir::X) + { + if (is_right_face(face)) + { + coord[IX] -= (b[IX] + g[IX]); + } + coord[IY] -= g[IY]; + } + + if (dir == Dir::Y) + { + if (is_right_face(face)) + { + coord[IY] -= (b[IY] + g[IY]); + } + coord[IX] -= g[IX]; + } + } // dim == 2 + + else if constexpr (dim == 3) + { + if (dir == Dir::X) + { + if (is_right_face(face)) + { + coord[IX] -= (b[IX] + g[IX]); + } + coord[IY] -= g[IY]; + coord[IZ] -= g[IZ]; + } + + if (dir == Dir::Y) + { + coord[IX] -= g[IX]; + if (is_right_face(face)) + { + coord[IY] -= (b[IY] + g[IY]); + } + coord[IZ] -= g[IZ]; + } + + if (dir == Dir::Z) + { + coord[IX] -= g[IX]; + coord[IY] -= g[IY]; + if (is_right_face(face)) + { + coord[IZ] -= (b[IZ] + g[IZ]); + } + } + } // dim == 3 + +} // shift_coord_face + +// ============================================================== +// ============================================================== +/** + * Compute neighbor orchard key of neighbor octant across a given face, at same level. + * + * when neighbor is at finer level (i.e. larger level), there multiple neighbors, so we only + * return the smallest key (the other neighbor keys can be deduced). + * + * \param[in] key is current octant key + * \param[in] iface identifies a face (XMIN, XMAX, ...) + */ +template +KOKKOS_INLINE_FUNCTION key_t +compute_face_neighbor_key(key_t key, Face::face_t iface, brick_size_t const & brick_sizes) +{ + + // clang-format off + KOKKOS_ASSERT(((iface == Face::XMIN) or + (iface == Face::XMAX) or + (iface == Face::YMIN) or + (iface == Face::YMAX) or + (iface == Face::ZMIN) or + (iface == Face::ZMAX)) && + "Wrong Face Id"); + // clang-format on + + return orchard_key_t::get_face_neighbor_key(key, iface, brick_sizes); + +} // compute_face_neighbor_key + +// ============================================================== +// ============================================================== +/** + * Compute neighbor orchard keys of all neighbor octants across a given face, at finer level. + * + * when neighbor is at finer level (i.e. larger level), there multiple neighbors, so we only + * return the smallest key that touches the faces (the other neighbor keys can be deduced). + * + * \param[in] key is neigh octant key (at same level as current octant) + * \param[in] iface identifies a face (XMIN, XMAX, ...) + * + * implementation note: in input we give the neighbor key at same level as current octant, because + * we assume, we already check if neighbor at same level exists, so this key has already been + * computed. The fine neighbors are children of the neighbor same level. + * + * In the drawing, we want to access the left neighbor at finer level. + * + * left neighbors current octant + * _______________ ______________ + * | | | | | + * | | x | | | + * | | | | | + * |_______|_______| | | + * | | | | | + * | | x | | | + * | | | | | + * |_______|_______| |______________| + * + * + * + * \return fine neighbor key + */ +template +KOKKOS_INLINE_FUNCTION Kokkos::Array::NB_FACE_NEIGHBORS_FINE> +compute_face_neighbor_key_finer(key_t key_neigh_same_level, Face::face_t iface) +{ + + // clang-format off + KOKKOS_ASSERT(((iface == Face::XMIN) or + (iface == Face::XMAX) or + (iface == Face::YMIN) or + (iface == Face::YMAX) or + (iface == Face::ZMIN) or + (iface == Face::ZMAX)) && + "Wrong Face Id"); + // clang-format on + + if constexpr (dim == 2) + { + if (iface == Face::XMIN) + { + return { orchard_key_t::child(key_neigh_same_level, 1), + orchard_key_t::child(key_neigh_same_level, 3) }; + } + else if (iface == Face::XMAX) + { + return { orchard_key_t::child(key_neigh_same_level, 0), + orchard_key_t::child(key_neigh_same_level, 2) }; + } + else if (iface == Face::YMIN) + { + return { orchard_key_t::child(key_neigh_same_level, 2), + orchard_key_t::child(key_neigh_same_level, 3) }; + } + else if (iface == Face::YMAX) + { + return { orchard_key_t::child(key_neigh_same_level, 0), + orchard_key_t::child(key_neigh_same_level, 1) }; + } + else + { + // we shouldn't be here, return invalid values + return { key_neigh_same_level, key_neigh_same_level }; + } + } + else if constexpr (dim == 3) + { + if (iface == Face::XMIN) + { + return { orchard_key_t::child(key_neigh_same_level, 1), + orchard_key_t::child(key_neigh_same_level, 3), + orchard_key_t::child(key_neigh_same_level, 5), + orchard_key_t::child(key_neigh_same_level, 7) }; + } + else if (iface == Face::XMAX) + { + return { orchard_key_t::child(key_neigh_same_level, 0), + orchard_key_t::child(key_neigh_same_level, 2), + orchard_key_t::child(key_neigh_same_level, 4), + orchard_key_t::child(key_neigh_same_level, 6) }; + } + else if (iface == Face::YMIN) + { + return { orchard_key_t::child(key_neigh_same_level, 2), + orchard_key_t::child(key_neigh_same_level, 3), + orchard_key_t::child(key_neigh_same_level, 6), + orchard_key_t::child(key_neigh_same_level, 7) }; + } + else if (iface == Face::YMAX) + { + return { orchard_key_t::child(key_neigh_same_level, 0), + orchard_key_t::child(key_neigh_same_level, 1), + orchard_key_t::child(key_neigh_same_level, 4), + orchard_key_t::child(key_neigh_same_level, 5) }; + } + else if (iface == Face::ZMIN) + { + return { orchard_key_t::child(key_neigh_same_level, 4), + orchard_key_t::child(key_neigh_same_level, 5), + orchard_key_t::child(key_neigh_same_level, 6), + orchard_key_t::child(key_neigh_same_level, 7) }; + } + else if (iface == Face::ZMAX) + { + return { orchard_key_t::child(key_neigh_same_level, 0), + orchard_key_t::child(key_neigh_same_level, 1), + orchard_key_t::child(key_neigh_same_level, 2), + orchard_key_t::child(key_neigh_same_level, 3) }; + } + else + { + // we shouldn't be here, return invalid values + return { + key_neigh_same_level, key_neigh_same_level, key_neigh_same_level, key_neigh_same_level + }; + } + } + +} // compute_face_neighbor_key_finer + +// ============================================================== +// ============================================================== +/** + * Compute neighbor orchard key of neighbor octant across a given corner, at same level - 2d. + * + * need 2 orthogonal faces to identify a corner. + */ +KOKKOS_INLINE_FUNCTION key_t +compute_corner_neighbor_key(key_t key, + Face::face_t iface_x, + Face::face_t iface_y, + brick_size_t<2> const & brick_sizes) +{ + + // clang-format off + KOKKOS_ASSERT( ( ( (iface_x == Face::XMIN) or (iface_x == Face::XMAX) ) and + ( (iface_y == Face::YMIN) or (iface_y == Face::YMAX) ) ) && "Wrong corner ids"); + // clang-format on + + return orchard_key_t<2>::get_corner_neighbor_key(key, iface_x, iface_y, brick_sizes); + +} // compute_corner_neighbor_key + +template +KOKKOS_INLINE_FUNCTION key_t +compute_corner_neighbor_key(key_t key, + Kokkos::Array faces, + brick_size_t const & brick_sizes) +{ + + // clang-format off + if constexpr (dim==2) { + KOKKOS_ASSERT( ( ( (faces[IX] == Face::XMIN) or (faces[IX] == Face::XMAX) ) and + ( (faces[IY] == Face::YMIN) or (faces[IY] == Face::YMAX) ) ) && "Wrong corner ids"); + } + if constexpr (dim==3) { + KOKKOS_ASSERT( ( ( (faces[IX] == Face::XMIN) or (faces[IX] == Face::XMAX) ) and + ( (faces[IY] == Face::YMIN) or (faces[IY] == Face::YMAX) ) and + ( (faces[IZ] == Face::ZMIN) or (faces[IZ] == Face::ZMAX) ) ) && "Wrong corner ids"); + } + // clang-format on + + if constexpr (dim == 2) + return orchard_key_t<2>::get_corner_neighbor_key(key, faces[IX], faces[IY], brick_sizes); + + if constexpr (dim == 3) + return orchard_key_t<3>::get_corner_neighbor_key( + key, faces[IX], faces[IY], faces[IZ], brick_sizes); + +} // compute_corner_neighbor_key + +// ============================================================== +// ============================================================== +/** + * Compute neighbor orchard key of neighbor octant across a given corner, at same level - 3d. + * + * need 3 orthogonal faces to identify a corner. + */ +KOKKOS_INLINE_FUNCTION key_t +compute_corner_neighbor_key(key_t key, + Face::face_t iface_x, + Face::face_t iface_y, + Face::face_t iface_z, + brick_size_t<3> const & brick_sizes) +{ + + // clang-format off + KOKKOS_ASSERT( ( ( (iface_x == Face::XMIN) or (iface_x == Face::XMAX) ) and + ( (iface_y == Face::YMIN) or (iface_y == Face::YMAX) ) and + ( (iface_z == Face::ZMIN) or (iface_z == Face::ZMAX) ) ) && "Wrong corner ids"); + // clang-format on + + return orchard_key_t<3>::get_corner_neighbor_key(key, iface_x, iface_y, iface_z, brick_sizes); + +} // compute_corner_neighbor_key + +// ============================================================== +// ============================================================== +/** + * Compute neighbor orchard key of neighbor octant across a given corner, at finer level - 2d. + * + * Implementation note: key_neigh_same_level is the orchard key of the father octant of the octant + * we are looking for, we just need to identify the child id. + * + * \param[in] key_neigh_same_level is the orchard key of the neighbor octant at same level + * \param[in] iface_x is a face id + * \param[in] iface_y is a face id + * + * Remember that 2 orthogonal faces are needed to specify a corner. + */ +KOKKOS_INLINE_FUNCTION +key_t +compute_corner_neighbor_finer_key(key_t key_neigh_same_level, + Face::face_t iface_x, + Face::face_t iface_y) +{ + + // clang-format off + KOKKOS_ASSERT( ( ( (iface_x == Face::XMIN) or (iface_x == Face::XMAX) ) and + ( (iface_y == Face::YMIN) or (iface_y == Face::YMAX) ) ) && "Wrong corner ids"); + // clang-format on + + const auto corner = compute_corner_id(iface_x, iface_y); + + // if (corner == 0) + // return orchard_key_t::child(key_neigh_same_level, 3); + // if (corner == 1) + // return orchard_key_t::child(key_neigh_same_level, 2); + // if (corner == 2) + // return orchard_key_t::child(key_neigh_same_level, 1); + // if (corner == 3) + // return orchard_key_t::child(key_neigh_same_level, 0); + + return orchard_key_t<2>::child(key_neigh_same_level, 3 - corner); + +} // compute_corner_neighbor_finer_key + +// ============================================================== +// ============================================================== +/** + * Compute neighbor orchard key of neighbor octant across a given corner, at finer level - 2d. + */ +KOKKOS_INLINE_FUNCTION +key_t +compute_corner_neighbor_finer_key(key_t key_neigh_same_level, + Face::face_t iface_x, + Face::face_t iface_y, + Face::face_t iface_z) +{ + + // clang-format off + KOKKOS_ASSERT( ( ( (iface_x == Face::XMIN) or (iface_x == Face::XMAX) ) and + ( (iface_y == Face::YMIN) or (iface_y == Face::YMAX) ) and + ( (iface_z == Face::ZMIN) or (iface_z == Face::ZMAX) ) ) && "Wrong corner ids"); + // clang-format on + + const auto corner = compute_corner_id(iface_x, iface_y, iface_z); + + return orchard_key_t<3>::child(key_neigh_same_level, 7 - corner); + +} // compute_corner_neighbor_finer_key + +// ============================================================== +// ============================================================== +/** + * Compute neighbor orchard key of neighbor octant across a given corner, at finer level - 2d. + */ +template +KOKKOS_INLINE_FUNCTION key_t +compute_corner_neighbor_finer_key(key_t key_neigh_same_level, + Kokkos::Array const & faces) +{ + + // clang-format off + if constexpr (dim == 2) + { + KOKKOS_ASSERT((((faces[IX] == Face::XMIN) or (faces[IX] == Face::XMAX)) and + ((faces[IY] == Face::YMIN) or (faces[IY] == Face::YMAX))) && + "Wrong corner ids"); + } + if constexpr (dim == 3) + { + KOKKOS_ASSERT((((faces[IX] == Face::XMIN) or (faces[IX] == Face::XMAX)) and + ((faces[IY] == Face::YMIN) or (faces[IY] == Face::YMAX)) and + ((faces[IZ] == Face::ZMIN) or (faces[IZ] == Face::ZMAX))) && + "Wrong corner ids"); + } + // clang-format on + + const auto corner = compute_corner_id(faces); + + return orchard_key_t::child(key_neigh_same_level, (1 << dim) - 1 - corner); + +} // compute_corner_neighbor_finer_key + +// ============================================================== +// ============================================================== +/** + * Compute neighbor orchard key of neighbor octant across a given edge, at same level + */ +KOKKOS_INLINE_FUNCTION key_t +compute_edge_neighbor_key(key_t key, Edge::edge_t edge, brick_size_t<3> const & brick_sizes) +{ + Face::face_t face0, face1; + edge_to_faces(edge, face0, face1); + + // edge along Z + if (edge < 4) + { + // face0 is X + // face1 is Y + return orchard_key_t<3>::get_corner_neighbor_key(key, face0, face1, face0, brick_sizes); + } + // edge along X + else if (edge >= 4 and edge < 8) + { + // face0 is Y + // face1 is Z + return orchard_key_t<3>::get_corner_neighbor_key(key, face0, face0, face1, brick_sizes); + } + // edge along Y + else if (edge >= 8) + { + // face0 is X + // face1 is Z + return orchard_key_t<3>::get_corner_neighbor_key(key, face0, face0, face1, brick_sizes); + } + + // return self as invalid value + return key; + +} // compute_edge_neighbor_key + +// ============================================================== +// ============================================================== +/** + * Compute neighbor orchard key of neighbor octant across a given edge, at same level. + * + * We will use the neighbor at same level, and look for a child id. + */ +KOKKOS_INLINE_FUNCTION Kokkos::Array::NB_EDGE_NEIGHBORS_FINE> +compute_edge_neighbor_finer_key(key_t key_neigh_same_level, Edge::edge_t edge) +{ + Face::face_t face0, face1; + edge_to_faces(edge, face0, face1); + + // edge along Z + if (edge < 4) + { + // face0 is X + // face1 is Y + Edge::edge_t edge2 = 3 - edge; + return { orchard_key_t<3>::child(key_neigh_same_level, edge2), + orchard_key_t<3>::child(key_neigh_same_level, edge2 + 4) }; + } + // edge along X + else if (edge < 8) + { + // face0 is Y + // face1 is Z + Edge::edge_t edge2 = static_cast((3 - (edge - 4)) << 1); + return { orchard_key_t<3>::child(key_neigh_same_level, edge2), + orchard_key_t<3>::child(key_neigh_same_level, edge2 + 1) }; + } + // edge along Y + else + { + // face0 is Z + // face1 is X + Edge::edge_t tmp = (3 - (edge - 8)); + Edge::edge_t edge2 = static_cast(((tmp >> 1) << 2) + (tmp & 0x1)); + return { orchard_key_t<3>::child(key_neigh_same_level, edge2), + orchard_key_t<3>::child(key_neigh_same_level, edge2 + 2) }; + } + +} // compute_edge_neighbor_finer_key + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_FILLBLOCKGHOSTS_COMMON_H_ diff --git a/src/kalypsso/core/FillOutside_utils.h b/src/kalypsso/core/FillOutside_utils.h new file mode 100644 index 0000000..f69509c --- /dev/null +++ b/src/kalypsso/core/FillOutside_utils.h @@ -0,0 +1,142 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file FillOutside_utils.h + * + * Helper routines for filling blocks outside of physical domain (border conditions) + * that can be re-used in different applications + */ +#ifndef KALYPSSO_CORE_FILLOUTSIDE_UTILS_H_ +#define KALYPSSO_CORE_FILLOUTSIDE_UTILS_H_ + +#include +#include +#include // for struct Face +#include + +#include +#include +#include + +#include + +namespace kalypsso +{ + +//! return the total number of border regions +template +constexpr auto +NUM_BORDERS() +{ + return Face::num_faces(); // + Edge::num_edges() + Corner::num_corners(); +} + +// ============================================================================= +// ============================================================================= +// pointwise init functor +struct AnalyticalZeroBC +{ + KOKKOS_FUNCTION auto + operator()([[maybe_unused]] real_t x, [[maybe_unused]] real_t y, [[maybe_unused]] int var) const + { + return ZERO_F; + } + + KOKKOS_FUNCTION auto + operator()([[maybe_unused]] real_t x, + [[maybe_unused]] real_t y, + [[maybe_unused]] real_t z, + [[maybe_unused]] int var) const + { + return ZERO_F; + } +}; + +// =========================================================================== +// =========================================================================== +//! +//! This struct does nothing except defining an enum of all possible border conditions +//! +//! \tparam BC must be a (better enum) +//! +template +struct BorderConditionsConfig +{ + + //! type alias to specify border conditions (other than periodic) + template + using bc_array_t = Kokkos::Array()>; + + // ============================================================================== + // ============================================================================== + template + static bc_array_t + default_init() + { + if constexpr (dim == 2) + { + return { BC::PERIODIC, BC::PERIODIC, BC::PERIODIC, BC::PERIODIC }; + } + else if constexpr (dim == 3) + { + return { BC::PERIODIC, BC::PERIODIC, BC::PERIODIC, BC::PERIODIC, BC::PERIODIC, BC::PERIODIC }; + } + } // default_init + + // ============================================================================== + // ============================================================================== + template + static bc_array_t + read_border_condition(ConfigMap const & config_map, ParallelEnv const & par_env) + { + // default value is periodic + bc_array_t bc_types = default_init(); + + // boundary_location_str can be "boundary_type_xmin", "boundary_type_xmax", ... + auto read_bc = [&](std::string const & boundary_location_str) -> BC { + auto bc_type_str = config_map.getString("mesh", boundary_location_str, ""); + + // check if bc_type_str is a valid value + auto maybe_bc = BC::_from_string_nothrow(bc_type_str.c_str()); + if (!maybe_bc) + { + // the string is not recognized or invalid + if (par_env.rank() == 0) + { + std::cout << "[BorderConditionsConfig::read_border_condition] " << "\"" << bc_type_str + << "\"" << " is invalid (Using BC::NONE instead).\n" + << "Check your input parameter file.\n" + << "Valid values are:\n"; + for (const char * name : BC::_names()) + std::cout << name << " "; + std::cout << "\n"; + } + return BC::NONE; + } + else + { + return *maybe_bc; + } + }; + + bc_types[XMIN] = read_bc("boundary_type_xmin"); + bc_types[XMAX] = read_bc("boundary_type_xmax"); + bc_types[YMIN] = read_bc("boundary_type_ymin"); + bc_types[YMAX] = read_bc("boundary_type_ymax"); + if constexpr (dim == 3) + { + bc_types[ZMIN] = read_bc("boundary_type_zmin"); + bc_types[ZMAX] = read_bc("boundary_type_zmax"); + } + + return bc_types; + + } // read_border_condition + +}; // struct BorderConditionsConfig + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_FILLOUTSIDE_UTILS_H_ diff --git a/src/kalypsso/core/FiniteDifferenceData.h b/src/kalypsso/core/FiniteDifferenceData.h new file mode 100644 index 0000000..0e09799 --- /dev/null +++ b/src/kalypsso/core/FiniteDifferenceData.h @@ -0,0 +1,160 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file FiniteDifferenceData.h + */ +#ifndef KALYPSSO_CORE_FINITEDIFFERENCEDATA_H_ +#define KALYPSSO_CORE_FINITEDIFFERENCEDATA_H_ + +#include +#include +#include // for math functions (max, min, ...) + +#include +#include <../../better-enums/enum.h> + +namespace kalypsso +{ + +namespace core +{ + +// clang-format off +/** + * Enumerate types of stencil used for finite difference estimation first derivative. + */ +BETTER_ENUM(FIRST_DERIVATIVE_STENCIL, int, + THREE_POINTS, + FIVE_POINTS, + SEVEN_POINTS) +// clang-format on + +/** + * A companion data structure for every class that need to do finite difference computation. + */ +struct FiniteDifferenceData +{ + + // clang-format off + //! first order derivative coefs using three point stencil + //! ./stencil_coefs_helper.py -o 1 -p 1.0 2.0 + //! ./stencil_coefs_helper.py -o 1 -p -1.0 1.0 # this the second order central difference scheme + //! ./stencil_coefs_helper.py -o 1 -p -2.0 -1.0 +#ifdef KALYPSSO_CORE_USE_DOUBLE + const Kokkos::Array FIRST_ORDER_3{ + 0.0, 2.0, -1.0 / 2, + -1.0 / 2, 0.0, 1.0 / 2, + 1.0 / 2, -2.0, 0.0 }; +#else + const Kokkos::Array FIRST_ORDER_3{ + 0.0f, 2.0f, -1.0f / 2, + -1.0f / 2, 0.0f, 1.0f / 2, + 1.0f / 2, -2.0f, 0.0f }; +#endif + + //! first order derivative coefs using five point stencil + //! coefficient are obtained by: + //! ./stencil_coefs_helper.py -o 1 -p 1.0 2.0 3.0 4.0 + //! ./stencil_coefs_helper.py -o 1 -p -1.0 1.0 2.0 3.0 + //! ./stencil_coefs_helper.py -o 1 -p -2.0 -1.0 1.0 2.0 # this is the famous fourth order central difference scheme + //! ./stencil_coefs_helper.py -o 1 -p -3.0 -2.0 -1.0 1.0 + //! ./stencil_coefs_helper.py -o 1 -p -4.0 -3.0 -2.0 -1.0 +#ifdef KALYPSSO_CORE_USE_DOUBLE + const Kokkos::Array FIRST_ORDER_5{ + 0.0 , 4.0, -3.0 , 4.0 / 3, -1.0 / 4 , + -1.0 / 4 , 0.0, 3.0 / 2, -1.0 / 2, 1.0 / 12, + 1.0 / 12, -2.0 / 3, 0.0 , 2.0 / 3, -1.0 / 12, + -1.0 / 12, 1.0 / 2, -3.0 / 2, 0.0 , 1.0 / 4 , + 1.0 / 4 , -4.0 / 3, 3.0 , -4.0 , 0.0 }; +#else + const Kokkos::Array FIRST_ORDER_5{ + 0.0f , 4.0f, -3.0f , 4.0f / 3, -1.0f / 4 , + -1.0f / 4 , 0.0f, 3.0f / 2, -1.0f / 2, 1.0f / 12, + 1.0f / 12, -2.0f / 3, 0.0f , 2.0f / 3, -1.0f / 12, + -1.0f / 12, 1.0f / 2, -3.0f / 2, 0.0f , 1.0f / 4 , + 1.0f / 4 , -4.0f / 3, 3.0f , -4.0f , 0.0f }; +#endif + + //! first order derivative coefs using seven point stencil + //! coefficient are obtained by: + //! ./stencil_coefs_helper.py -o 1 -p 1.0 2.0 3.0 4.0 5.0 6.0 + //! ./stencil_coefs_helper.py -o 1 -p -1.0 1.0 2.0 3.0 4.0 5.0 + //! ./stencil_coefs_helper.py -o 1 -p -2.0 -1.0 1.0 2.0 3.0 4.0 + //! ./stencil_coefs_helper.py -o 1 -p -3.0 -2.0 -1.0 1.0 2.0 3.0 + //! ./stencil_coefs_helper.py -o 1 -p -4.0 -3.0 -2.0 -1.0 1.0 2.0 + //! ./stencil_coefs_helper.py -o 1 -p -5.0 -4.0 -3.0 -2.0 -1.0 1.0 + //! ./stencil_coefs_helper.py -o 1 -p -6.0 -5.0 -4.0 -3.0 -2.0 -1.0 +#ifdef KALYPSSO_CORE_USE_DOUBLE + const Kokkos::Array FIRST_ORDER_7{ + 0.0 , 6.0 , -15.0 / 2 , 20.0 / 3, -15.0 / 4, 6.0 / 5, -1.0 / 6, + -1.0 / 6, 0.0 , 5.0 / 2, -5.0 / 3, 5.0 / 6, -1.0 / 4, 1.0 / 30, + 1.0 / 30, -2.0 / 5 , 0.0 , 4.0 / 3, -1.0 / 2, 2.0 / 15, -1.0 / 60, + -1.0 / 60, 3.0 / 20, -3.0 / 4, 0.0 , 3.0 / 4, -3.0 / 20, 1.0 / 60, + 1.0 / 60, -2.0 / 15, 1.0 / 2, -4.0 / 3, 0.0 , 2.0 / 5, -1.0 / 30, + -1.0 / 30, 1.0 / 4 , -5.0 / 6, 5.0 / 3, -5.0 / 2, 0.0 , 1.0 / 6, + 1.0 / 6, -6.0 / 5 , 15.0 / 4, -20.0 / 3, 15.0 / 2, -6.0, 0.0 + }; +#else + const Kokkos::Array FIRST_ORDER_7{ + 0.0f , 6.0f , -15.0f / 2 , 20.0f / 3, -15.0f / 4, 6.0f / 5, -1.0f / 6, + -1.0f / 6, 0.0f , 5.0f / 2, -5.0f / 3, 5.0f / 6, -1.0f / 4, 1.0f / 30, + 1.0f / 30, -2.0f / 5 , 0.0f , 4.0f / 3, -1.0f / 2, 2.0f / 15, -1.0f / 60, + -1.0f / 60, 3.0f / 20, -3.0f / 4, 0.0f , 3.0f / 4, -3.0f / 20, 1.0f / 60, + 1.0f / 60, -2.0f / 15, 1.0f / 2, -4.0f / 3, 0.0f , 2.0f / 5, -1.0f / 30, + -1.0f / 30, 1.0f / 4 , -5.0f / 6, 5.0f / 3, -5.0f / 2, 0.0f , 1.0f / 6, + 1.0f / 6, -6.0f / 5 , 15.0f / 4, -20.0f / 3, 15.0f / 2, -6.0f, 0.0f + }; +#endif + // clang-format on + + // ============================================================== + // ============================================================== + /** + * Get first order finite direction coefficient (3 points). + * + * \param[in] pos current cell position in the stencil. + * \param[in] shift + */ + KOKKOS_INLINE_FUNCTION real_t const & + coef3(int32_t pos, int32_t shift) const + { + return FIRST_ORDER_3[pos * 3 + shift]; + } + + // ============================================================== + // ============================================================== + /** + * Get first order finite direction coefficient (5 points). + * + * \param[in] pos current cell position in the stencil. + * \param[in] shift + */ + KOKKOS_INLINE_FUNCTION real_t const & + coef5(int32_t pos, int32_t shift) const + { + return FIRST_ORDER_5[pos * 5 + shift]; + } + + // ============================================================== + // ============================================================== + /** + * Get first order finite direction coefficient (7 points). + * + * \param[in] pos current cell position in the stencil. + * \param[in] shift + */ + KOKKOS_INLINE_FUNCTION real_t const & + coef7(int32_t pos, int32_t shift) const + { + return FIRST_ORDER_7[pos * 7 + shift]; + } + +}; // struct FiniteDifferenceData + +} // namespace core + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_FINITEDIFFERENCEDATA_H_ diff --git a/src/kalypsso/core/FirstOrderDerivativeFiniteDifference.cpp b/src/kalypsso/core/FirstOrderDerivativeFiniteDifference.cpp new file mode 100644 index 0000000..3f7a5b4 --- /dev/null +++ b/src/kalypsso/core/FirstOrderDerivativeFiniteDifference.cpp @@ -0,0 +1,334 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file FirstOrderDerivativeFiniteDifference.cpp + */ + +#include + +namespace kalypsso +{ + +namespace core +{ + +// ================================================================================================ +// ================================================================================================ +template +FirstOrderDerivativeFiniteDifference::FirstOrderDerivativeFiniteDifference( + ConfigMap const & config_map, + orchard_key_view_t orchard_keys, + int32_t iOct_begin, + int32_t num_octants, + DataArrayGhostedBlock_t userdata_in, + int32_t ivar_in, + DataArrayGhostedBlock_t userdata_out, + int32_t ivar_out, + int32_t derivative_dir, + FIRST_DERIVATIVE_STENCIL stencil_length, + real_t scalar_factor) + : m_keys(orchard_keys) + , m_iOct_begin(iOct_begin) + , m_num_octants(num_octants) + , m_userdata_in(userdata_in) + , m_ivar_in(ivar_in) + , m_userdata_out(userdata_out) + , m_ivar_out(ivar_out) + , m_derivative_dir(derivative_dir) + , m_stencil_length(stencil_length) + , m_scaling_factor(get_scaling_factor(config_map)) + , m_xyz_min(get_xyz_min(config_map)) + , m_fd() + , m_scalar_factor(scalar_factor) +{} + +// ================================================================================================ +// ================================================================================================ +template +void +FirstOrderDerivativeFiniteDifference::check_args_validity( + DataArrayGhostedBlock_t userdata_in, + DataArrayGhostedBlock_t userdata_out, + FIRST_DERIVATIVE_STENCIL stencil_length) +{ + if (userdata_in.block_size() != userdata_out.block_size()) + { + Kokkos::abort("userdata in/out must have same sizes"); + } + + if (stencil_length == +FIRST_DERIVATIVE_STENCIL::FIVE_POINTS) + { + { + const auto & b = userdata_in.block_size(); + bool invalid_size = b[IX] < 5 or b[IY] < 5; + if constexpr (dim == 3) + { + invalid_size = invalid_size or b[IZ] < 5; + } + if (invalid_size) + Kokkos::abort("Userdata block size is must have at least 5 cells in direction to apply the " + "5 points stencil. "); + } + } + if (stencil_length == +FIRST_DERIVATIVE_STENCIL::SEVEN_POINTS) + { + { + const auto & b = userdata_in.block_size(); + bool invalid_size = b[IX] < 7 or b[IY] < 7; + if constexpr (dim == 3) + { + invalid_size = invalid_size or b[IZ] < 7; + } + if (invalid_size) + Kokkos::abort("Userdata block size is must have at least 7 cells in direction to apply the " + "7 points stencil. "); + } + } +} // FirstOrderDerivativeFiniteDifference::check_args_validity + +// ================================================================================================ +// ================================================================================================ +template +void +FirstOrderDerivativeFiniteDifference::first_derivative( + ConfigMap const & config_map, + orchard_key_view_t orchard_keys, + int32_t iOct_begin, + int32_t num_octants, + DataArrayGhostedBlock_t userdata_in, + int32_t ivar_in, + DataArrayGhostedBlock_t userdata_out, + int32_t ivar_out, + int32_t derivative_dir, + FIRST_DERIVATIVE_STENCIL stencil_length) +{ + check_args_validity(userdata_in, userdata_out, stencil_length); + + FirstOrderDerivativeFiniteDifference functor(config_map, + orchard_keys, + iOct_begin, + num_octants, + userdata_in, + ivar_in, + userdata_out, + ivar_out, + derivative_dir, + stencil_length, + 1.0 // not used + ); + + const auto nbCellsTotal = num_octants * userdata_in.num_cells_inner(); + + Kokkos::parallel_for("FirstOrderDerivativeFiniteDifference - first derivative", + Kokkos::RangePolicy(0, nbCellsTotal), + functor); + +}; // FirstOrderDerivativeFiniteDifference::first_derivative + +// ================================================================================================ +// ================================================================================================ +template +void +FirstOrderDerivativeFiniteDifference::normalized_gradient( + ConfigMap const & config_map, + orchard_key_view_t orchard_keys, + int32_t iOct_begin, + int32_t num_octants, + DataArrayGhostedBlock_t userdata_in, + int32_t ivar_in, + DataArrayGhostedBlock_t userdata_out, + FIRST_DERIVATIVE_STENCIL stencil_length) +{ + check_args_validity(userdata_in, userdata_out, stencil_length); + + if (static_cast(userdata_out.num_vars()) < dim) + { + Kokkos::abort( + "Error: userdata_out should be allocated to hold at dim components of a gradient field."); + } + + FirstOrderDerivativeFiniteDifference functor(config_map, + orchard_keys, + iOct_begin, + num_octants, + userdata_in, + ivar_in, + userdata_out, + -1, // not used + -1, // not used + stencil_length, + 1.0 // not used + ); + + const auto nbCellsTotal = num_octants * userdata_in.num_cells_inner(); + + Kokkos::parallel_for("FirstOrderDerivativeFiniteDifference - normalized gradient", + Kokkos::RangePolicy(0, nbCellsTotal), + functor); + +}; // FirstOrderDerivativeFiniteDifference::normalized_gradient + +// ================================================================================================ +// ================================================================================================ +template +void +FirstOrderDerivativeFiniteDifference::divergence( + ConfigMap const & config_map, + orchard_key_view_t orchard_keys, + int32_t iOct_begin, + int32_t num_octants, + DataArrayGhostedBlock_t userdata_in, + DataArrayGhostedBlock_t userdata_out, + FIRST_DERIVATIVE_STENCIL stencil_length, + real_t scalar_factor) +{ + check_args_validity(userdata_in, userdata_out, stencil_length); + + if (static_cast(userdata_in.num_vars()) < dim) + { + Kokkos::abort("Error: userdata_in should have at least dim components so that we can take the " + "divergence of it."); + } + + FirstOrderDerivativeFiniteDifference functor(config_map, + orchard_keys, + iOct_begin, + num_octants, + userdata_in, + -1, // not used + userdata_out, + -1, // not used + -1, // not used + stencil_length, + scalar_factor); + + const auto nbCellsTotal = num_octants * userdata_in.num_cells_inner(); + + Kokkos::parallel_for("FirstOrderDerivativeFiniteDifference - divergence", + Kokkos::RangePolicy(0, nbCellsTotal), + functor); + +}; // FirstOrderDerivativeFiniteDifference::divergence + +// ================================================================================================ +// ================================================================================================ +template +KOKKOS_INLINE_FUNCTION void +FirstOrderDerivativeFiniteDifference::operator()(TagFirstDerivative const &, + const index_t & global_index) const +{ + const auto iOct_local = global_index / m_userdata_in.num_cells_inner(); + const auto cell_index = global_index - iOct_local * m_userdata_in.num_cells_inner(); + + const auto iOct_global = m_iOct_begin + iOct_local; + + const auto ijk = cellindex_to_coord(cell_index, m_userdata_in.block_size()); + + real_t value = 0.0; + + if (m_derivative_dir == IX) + { + value = compute_first_derivative(ijk, m_ivar_in, iOct_global); + } + else if (m_derivative_dir == IY) + { + value = compute_first_derivative(ijk, m_ivar_in, iOct_global); + } + if constexpr (dim == 3) + { + if (m_derivative_dir == IZ) + { + value = compute_first_derivative(ijk, m_ivar_in, iOct_global); + } + } + + m_userdata_out(ijk, m_ivar_out, iOct_global) = value; + +} // FirstOrderDerivativeFiniteDifference::operator() - TagFirstDerivative + +// ================================================================================================ +// ================================================================================================ +template +KOKKOS_INLINE_FUNCTION void +FirstOrderDerivativeFiniteDifference::operator()(TagNormalizedGradient const &, + const index_t & global_index) const +{ + const auto iOct_local = global_index / m_userdata_in.num_cells_inner(); + const auto cell_index = global_index - iOct_local * m_userdata_in.num_cells_inner(); + + const auto iOct_global = m_iOct_begin + iOct_local; + + const auto ijk = cellindex_to_coord(cell_index, m_userdata_in.block_size()); + + const auto df_dx = compute_first_derivative(ijk, 0, iOct_global); + const auto df_dy = compute_first_derivative(ijk, 0, iOct_global); + + real_t norm = ZERO_F; + + if constexpr (dim == 2) + { + norm = sqrt(df_dx * df_dx + df_dy * df_dy); + + // if norm is very small, it means the sif is almost uniform + // avoid division by zero + if (norm < SMALL_NORM_F) + norm = ONE_F; + + m_userdata_out(ijk, IX, iOct_global) = df_dx / norm; + m_userdata_out(ijk, IY, iOct_global) = df_dy / norm; + } + else if constexpr (dim == 3) + { + const auto df_dz = compute_first_derivative(ijk, 0, iOct_global); + norm = sqrt(df_dx * df_dx + df_dy * df_dy + df_dz * df_dz); + + // if norm is very small, it means the sif is almost uniform + // avoid division by zero + if (norm < SMALL_NORM_F) + norm = ONE_F; + + m_userdata_out(ijk, IX, iOct_global) = df_dx / norm; + m_userdata_out(ijk, IY, iOct_global) = df_dy / norm; + m_userdata_out(ijk, IZ, iOct_global) = df_dz / norm; + } +} // FirstOrderDerivativeFiniteDifference::operator() - TagNormalizedGradient + +// ================================================================================================ +// ================================================================================================ +template +KOKKOS_INLINE_FUNCTION void +FirstOrderDerivativeFiniteDifference::operator()(TagDivergence const &, + const index_t & global_index) const +{ + const auto iOct_local = global_index / m_userdata_in.num_cells_inner(); + const auto cell_index = global_index - iOct_local * m_userdata_in.num_cells_inner(); + + const auto iOct_global = m_iOct_begin + iOct_local; + + const auto ijk = cellindex_to_coord(cell_index, m_userdata_in.block_size()); + + const auto dfx_dx = compute_first_derivative(ijk, IX, iOct_global); + const auto dfy_dy = compute_first_derivative(ijk, IY, iOct_global); + + auto div = dfx_dx + dfy_dy; + + if constexpr (dim == 3) + { + const auto dfz_dz = compute_first_derivative(ijk, IZ, iOct_global); + div += dfz_dz; + } + + m_userdata_out(ijk, 0, iOct_global) = m_scalar_factor * div; + +} // FirstOrderDerivativeFiniteDifference::operator() - TagDivergence + +// ================================================================================================ +// ================================================================================================ +template class FirstOrderDerivativeFiniteDifference<2, kalypsso::DefaultDevice>; +template class FirstOrderDerivativeFiniteDifference<3, kalypsso::DefaultDevice>; + +} // namespace core + +} // namespace kalypsso diff --git a/src/kalypsso/core/FirstOrderDerivativeFiniteDifference.h b/src/kalypsso/core/FirstOrderDerivativeFiniteDifference.h new file mode 100644 index 0000000..4880094 --- /dev/null +++ b/src/kalypsso/core/FirstOrderDerivativeFiniteDifference.h @@ -0,0 +1,473 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file FirstOrderDerivativeFiniteDifference.h + * + * First order derivative implementation. + */ +#ifndef KALYPSSO_CORE_FIRSTORDERDERIVATIVEFINITEDIFFERENCE_H_ +#define KALYPSSO_CORE_FIRSTORDERDERIVATIVEFINITEDIFFERENCE_H_ + +#include +#include +#include // for DataArray, DataArrayHost, DataArrayGhostedBlock +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace kalypsso +{ + +namespace core +{ + +// ============================================================================= +// ============================================================================= +/** + * \class FirstOrderDerivativeFiniteDifference + * + * Implement first order derivative using a N points finite difference approximation using only + * points inside the same AMR block of cells. + * We support either N=2 or N=4 points. + * + * When N=2 points, the canonical stencil is to use two points equispaced from the central points + * interpreted as cell-centered values (see drawing below): + * + * Central stencil + * ___________________________________ + * | | | | + * | | | | + * | x[0] | x_c | x[1] | + * | | | | + * |__________|___________|___________| + * + * When left and right neighbor are at the same AMR level), the usual second order approximation + * reads : \f$ \frac{df}{dx} = \frac{f(x+h) - f(x-h)}{2h} + \mathcal{O}(h^2)\f$, where + * \f$ h \f$ is the central cell size. + * + * This formula can be recast into \f$ \frac{df}{dx} = \sum_{i=0}^{i=1} c_i * (f(x_i)-f(x_c))/h + * where \f$ x_c\f$ is the central point, \f$ c_0=-1/2\f$, \f$ c_1=1/2\f$ are the finite + * difference coefficients and \f$ x_0 = x_c-h \f$ and \f$ x_1=x_c+h \f$. + * + * Forward stencil + * ___________________________________ + * | | | | + * | | | | + * | x_c | x[0] | x[1] | + * | | | | + * |__________|___________|___________| + * + * When the point where we want to compute derivative is touching the block border, one switch to a + * forward finite difference formula. + * The new coefficients can be computed using the utility file stencil_ceofs_helper.py + * + * Using length in units of h (current cell size), and taking origin at x_c, then x[0]=1.0 + * and x[1]=2.0, the finite difference coefficients can be obtained using our + * stencil_coefs_helper.py + * + * ./stencil_coefs_helper.py -o 1 -p 1.0 2.0 + * + * from which we obtain: coeff[0]=2.0 and coeff[1]=-1/2 + * + * Backward stencil + * ___________________________________ + * | | | | + * | | | | + * | x[0] | x[1] | x_c | + * | | | | + * |__________|___________|___________| + * + * ./stencil_coefs_helper.py -o 1 -p -2.0 -1.0 + * + * from which we obtain: coeff[0]=1/2 and coeff[1]=2 + * + * \tparam dim is dimension (2 or 3) + * \tparam device_t is a kokkos device + * + * \note input and output data are DataArrayGhostedBlock eventhough it would only require + * DataArrayBlock since we only compute the inner part of the ghosted block. Most downstream user + * code of this class will actually use ghosted array. + */ +template +class FirstOrderDerivativeFiniteDifference +{ + +public: + using exec_space = typename device_t::execution_space; + using index_t = int32_t; + using orchard_key_view_t = typename orchard_key_base_t::view_t; + + // using DataArrayBlock_t = DataArrayBlock; + using DataArrayGhostedBlock_t = DataArrayGhostedBlock; + + //! Compute first derivative + struct TagFirstDerivative + {}; + + //! Compute (normalized) gradient + struct TagNormalizedGradient + {}; + + //! Compute divergence + struct TagDivergence + {}; + + // ==================================================================== + // ==================================================================== + //! constructor. + FirstOrderDerivativeFiniteDifference(ConfigMap const & config_map, + orchard_key_view_t orchard_keys, + int32_t iOct_begin, + int32_t num_octants, + DataArrayGhostedBlock_t userdata_in, + int32_t ivar_in, + DataArrayGhostedBlock_t userdata_out, + int32_t ivar_out, + int32_t derivative_dir, + FIRST_DERIVATIVE_STENCIL stencil_length, + real_t scalar_factor); + + // ==================================================================== + // ==================================================================== + //! destructor. + ~FirstOrderDerivativeFiniteDifference() = default; + + // ==================================================================== + // ==================================================================== + static void + check_args_validity(DataArrayGhostedBlock_t userdata_in, + DataArrayGhostedBlock_t userdata_out, + FIRST_DERIVATIVE_STENCIL stencil_length); + + // ==================================================================== + // ==================================================================== + static void + first_derivative(ConfigMap const & config_map, + orchard_key_view_t orchard_keys, + int32_t iOct_begin, + int32_t num_octants, + DataArrayGhostedBlock_t userdata_in, + int32_t ivar_in, + DataArrayGhostedBlock_t userdata_out, + int32_t ivar_out, + int32_t derivative_dir, + FIRST_DERIVATIVE_STENCIL stencil_length); + + // ==================================================================== + // ==================================================================== + static void + normalized_gradient(ConfigMap const & config_map, + orchard_key_view_t orchard_keys, + int32_t iOct_begin, + int32_t num_octants, + DataArrayGhostedBlock_t userdata_in, + int32_t ivar_in, + DataArrayGhostedBlock_t userdata_out, + FIRST_DERIVATIVE_STENCIL stencil_length); + + // ==================================================================== + // ==================================================================== + static void + divergence(ConfigMap const & config_map, + orchard_key_view_t orchard_keys, + int32_t iOct_begin, + int32_t num_octants, + DataArrayGhostedBlock_t userdata_in, + DataArrayGhostedBlock_t userdata_out, + FIRST_DERIVATIVE_STENCIL stencil_length, + real_t scalar_factor); + + // ============================================================== + // ============================================================== + /** + * Compute first derivative along direction dir using a 3 point stencil. + * + * Derivative is estimated with big O(h^2) approximation. + */ + template + KOKKOS_INLINE_FUNCTION real_t + compute_first_derivative_3_points(coord_t const & ijk, + int ivar, + int32_t const & iOct_local) const + { + + KOKKOS_ASSERT(dir < dim && "wrong direction"); + + const auto & b = m_userdata_in.block_size(); + + const auto iOct_global = m_iOct_begin + iOct_local; + + const auto key = m_keys(iOct_global); + const auto dx = compute_cell_length(key, b[dir]) * m_scaling_factor; + + real_t derivative = 0.0; + + // current cell value + const auto data_C = m_userdata_in(ijk, ivar, iOct_global); + + // current position inside stencil + int32_t pos = 1; + + if (ijk[dir] == 0) + { + pos = 0; + } + else if (ijk[dir] == b[dir] - 1) + { + pos = 2; + } + + // s is a stencil position + for (int s = 0; s < 3; ++s) + { + auto ijk_s = ijk; + ijk_s[dir] += (s - pos); + + derivative += m_fd.coef3(pos, s) * (m_userdata_in(ijk_s, ivar, iOct_global) - data_C) / dx; + } + + return derivative; + + } // compute_first_derivative_3_points + + // ============================================================== + // ============================================================== + /** + * Compute first derivative along direction dir using a 5 point stencil. + * + * Derivative is estimated with big O(h^4) approximation. + */ + template + KOKKOS_INLINE_FUNCTION real_t + compute_first_derivative_5_points(coord_t const & ijk, + int ivar, + int32_t const & iOct_local) const + { + + KOKKOS_ASSERT(dir < dim && "wrong direction"); + + const auto & b = m_userdata_in.block_size(); + + const auto iOct_global = m_iOct_begin + iOct_local; + + const auto key = m_keys(iOct_global); + const auto dx = compute_cell_length(key, b[dir]) * m_scaling_factor; + + real_t derivative = 0.0; + + // current cell value + const auto data_C = m_userdata_in(ijk, ivar, iOct_global); + + // current position inside stencil + int32_t pos = 2; + + if (ijk[dir] == 0) + { + pos = 0; + } + else if (ijk[dir] == 1) + { + pos = 1; + } + else if (ijk[dir] == b[dir] - 2) + { + pos = 3; + } + else if (ijk[dir] == b[dir] - 1) + { + pos = 4; + } + + // s is a stencil position + for (int s = 0; s < 5; ++s) + { + auto ijk_s = ijk; + ijk_s[dir] += (s - pos); + + derivative += m_fd.coef5(pos, s) * (m_userdata_in(ijk_s, ivar, iOct_global) - data_C) / dx; + } + + return derivative; + + } // compute_first_derivative_5_points + + // ============================================================== + // ============================================================== + /** + * Compute first derivative along direction dir using a 7 point stencil. + * + * Derivative is estimated with big O(h^6) approximation. + */ + template + KOKKOS_INLINE_FUNCTION real_t + compute_first_derivative_7_points(coord_t const & ijk, + int ivar, + int32_t const & iOct_local) const + { + + KOKKOS_ASSERT(dir < dim && "wrong direction"); + + const auto & b = m_userdata_in.block_size(); + + const auto iOct_global = m_iOct_begin + iOct_local; + + const auto key = m_keys(iOct_global); + const auto dx = compute_cell_length(key, b[dir]) * m_scaling_factor; + + real_t derivative = 0.0; + + // current cell value + const auto data_C = m_userdata_in(ijk, ivar, iOct_global); + + // current position inside stencil + int32_t pos = 3; + + if (ijk[dir] == 0) + { + pos = 0; + } + else if (ijk[dir] == 1) + { + pos = 1; + } + else if (ijk[dir] == 2) + { + pos = 2; + } + else if (ijk[dir] == b[dir] - 3) + { + pos = 4; + } + else if (ijk[dir] == b[dir] - 2) + { + pos = 5; + } + else if (ijk[dir] == b[dir] - 1) + { + pos = 6; + } + + // s is a stencil position + for (int s = 0; s < 7; ++s) + { + auto ijk_s = ijk; + ijk_s[dir] += (s - pos); + + derivative += m_fd.coef7(pos, s) * (m_userdata_in(ijk_s, ivar, iOct_global) - data_C) / dx; + } + + return derivative; + + } // compute_first_derivative_7_points + + // ============================================================== + // ============================================================== + template + KOKKOS_INLINE_FUNCTION real_t + compute_first_derivative(coord_t const & ijk, int ivar, int32_t const & iOct_local) const + { + + if (m_stencil_length == +FIRST_DERIVATIVE_STENCIL::THREE_POINTS) + { + return compute_first_derivative_3_points(ijk, ivar, iOct_local); + } + else if (m_stencil_length == +FIRST_DERIVATIVE_STENCIL::FIVE_POINTS) + { + return compute_first_derivative_5_points(ijk, ivar, iOct_local); + } + else if (m_stencil_length == +FIRST_DERIVATIVE_STENCIL::SEVEN_POINTS) + { + return compute_first_derivative_7_points(ijk, ivar, iOct_local); + } + + // we shouldn't be here + return 0.0; + } + + // ============================================================== + // ============================================================== + /** + * Range policy functor for computing just the first derivative of a scalar field along a given + * direction. + */ + KOKKOS_INLINE_FUNCTION void + operator()(TagFirstDerivative const &, const index_t & global_index) const; + + // ============================================================== + // ============================================================== + /** + * Range policy functor for computing normalized gradient. + */ + KOKKOS_INLINE_FUNCTION void + operator()(TagNormalizedGradient const &, const index_t & global_index) const; + + // ============================================================== + // ============================================================== + /** + * Range policy functor for computing divergence of a vector field. + */ + KOKKOS_INLINE_FUNCTION void + operator()(TagDivergence const &, const index_t & global_index) const; + +private: + //! Array of orchard keys. + orchard_key_view_t m_keys; + + //! Starting octant id + const int32_t m_iOct_begin; + + //! Number of octant to process, starting at m_iOct_begin. + const int32_t m_num_octants; + + //! Input block data array + DataArrayGhostedBlock_t m_userdata_in; + + //! index to variable in input array used to compute derivative + int32_t m_ivar_in; + + //! a block data array (no ghosts) + DataArrayGhostedBlock_t m_userdata_out; + + //! index to variable in output array where to write the result (derivative) + //! only when tag is TagFirstDerivative + int32_t m_ivar_out; + + //! Direction along which derivative is computed + int32_t m_derivative_dir; + + //! stencil length + const FIRST_DERIVATIVE_STENCIL m_stencil_length; + + //! get geometrical scaling factor + const real_t m_scaling_factor; + + //! get domain lower left corner + const Kokkos::Array m_xyz_min; + + //! Finite difference coefficients + const FiniteDifferenceData m_fd; + + //! Scalar factor used in computing divergence + const real_t m_scalar_factor; + + //! small norm + KALYPSSO_STATIC_MATH_CONSTANT(SMALL_NORM, 1e-13); + +}; // class FirstOrderDerivativeFiniteDifference + +extern template class FirstOrderDerivativeFiniteDifference<2, kalypsso::DefaultDevice>; +extern template class FirstOrderDerivativeFiniteDifference<3, kalypsso::DefaultDevice>; + +} // namespace core + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_FIRSTORDERDERIVATIVEFINITEDIFFERENCE_H_ diff --git a/src/kalypsso/core/GravityField.cpp b/src/kalypsso/core/GravityField.cpp new file mode 100644 index 0000000..6e45b93 --- /dev/null +++ b/src/kalypsso/core/GravityField.cpp @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +namespace kalypsso +{ + +// ================================================================================ +// ================================================================================ +GravityType +get_gravity_type(ConfigMap const & config_map) +{ + auto gravityTypeStr = config_map.getString("gravity", "gravityType", "undefined"); + if (gravityTypeStr == "uniform") + return GravityType::UNIFORM; + else + return GravityType::UNDEFINED; +} + +} // namespace kalypsso diff --git a/src/kalypsso/core/GravityField.h b/src/kalypsso/core/GravityField.h new file mode 100644 index 0000000..afef152 --- /dev/null +++ b/src/kalypsso/core/GravityField.h @@ -0,0 +1,124 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * GravityField.h + * + * Implementation notes: + * - currently only uniform gravity is implemented, but other type of analytical or numerical + * gravity field can be implemented by adding items in GravityType enum and providing implementation + * in class GravityField + * + * - we don't use class inheritance and virtual member to implement different types of gravity + * because virtual members are not supported in device code. + * + * A gravity field must provide member gx, gy, and gz to access grivity field component at location + * (x,y,z). + * + */ +#ifndef KALYPSSO_CORE_GRAVITYFIELD_H_ +#define KALYPSSO_CORE_GRAVITYFIELD_H_ + +#include +#include +#include // for math functions (max, min, ...) +#include +#include + +#include <../better-enums/enum.h> + +namespace kalypsso +{ + +// clang-format off +BETTER_ENUM(GravityType, int, UNDEFINED, UNIFORM) +// clang-format on + +GravityType +get_gravity_type(ConfigMap const & config_map); + +// ======================================================================= +template +Kokkos::Array +get_uniform_gravity_vector(ConfigMap const & config_map) +{ + Kokkos::Array res; + res[IX] = config_map.getReal("gravity", "uniform_gx", ZERO_F); + res[IY] = config_map.getReal("gravity", "uniform_gy", ZERO_F); + if constexpr (dim == 3) + { + res[IZ] = config_map.getReal("gravity", "uniform_gz", ZERO_F); + } + return res; +} + +/** + * TODO: remove this type alias when several types of gravity fields will be implemented. + */ +template +using UniformGravityField = Kokkos::Array; + +// =============================================================================================== +// =============================================================================================== +// =============================================================================================== +/** + * Uniform gravity field. + */ +// template +// class UniformGravityField +// { +// public: +// // ============================================================= +// // ============================================================= +// UniformGravityField(ConfigMap const & config_map) +// : m_g(get_uniform_gravity_vector(config_map)) +// {} + +// ~UniformGravityField() = default; + +// /** +// * uniform gravity implementation +// */ +// KOKKOS_INLINE_FUNCTION +// real_t +// gx(Kokkos::Array const & xyz) const +// { +// return m_g[IX]; +// } + +// KOKKOS_INLINE_FUNCTION +// real_t +// gy(Kokkos::Array const & xyz) const +// { +// return m_g[IY]; +// } + +// KOKKOS_INLINE_FUNCTION +// real_t +// gz(Kokkos::Array const & xyz) const +// { +// return [&]() { +// if constexpr (dim == 2) +// return 0; +// else if constexpr (dim == 3) +// return m_g[IZ]; +// }(); +// } + +// KOKKOS_INLINE_FUNCTION +// Kokkos::Array +// g(Kokkos::Array const & xyz) const +// { +// return m_g; +// } + +// private: +// //! uniform gravity vector +// Kokkos::Array m_g; + +// }; // class UniformGravityField + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_GRAVITYFIELD_H_ diff --git a/src/kalypsso/core/HDF5_IO_common.cpp b/src/kalypsso/core/HDF5_IO_common.cpp new file mode 100644 index 0000000..74d89da --- /dev/null +++ b/src/kalypsso/core/HDF5_IO_common.cpp @@ -0,0 +1,283 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file HDF5_IO_common.cpp + * + */ +#include + +#include + +namespace kalypsso +{ + +#ifdef KALYPSSO_CORE_USE_MPI +// ======================================================= +// ======================================================= +void +check_collective_io(const HighFive::DataTransferProps & xfer_props) +{ + auto mnccp = HighFive::MpioNoCollectiveCause(xfer_props); + if (mnccp.getLocalCause() || mnccp.getGlobalCause()) + { + std::cout << "The operation was successful, but couldn't use collective MPI-IO. local cause: " + << mnccp.getLocalCause() << " global cause:" << mnccp.getGlobalCause() << std::endl; + } +} +#endif // KALYPSSO_CORE_USE_MPI + +// ======================================================= +// ======================================================= +template <> +void +io_fill_coordinates<2>(forest_t<2> * forest, + geometry_t<2> * geom, + p4est::topidx_t which_tree, + quadrant_t<2> * q, + float * data, + std::array bSize) +{ + + auto ROOT_LEN = p4est::Wrapper<2>::ROOT_LEN; + auto QUADRANT_LEN = p4est::Wrapper<2>::QUADRANT_LEN; + [[maybe_unused]] auto NB_CHILDREN = p4est::Wrapper<2>::NB_CHILDREN; + using qcoord_t = p4est::qcoord_t; + + double xyz[3] = { 0, 0, 0 }; + double XYZ[3] = { 0, 0, 0 }; + + const double hd = (static_cast(QUADRANT_LEN(q->level))) / ROOT_LEN; + const double h2 = 0.5 * QUADRANT_LEN(q->level) / ROOT_LEN; + const qcoord_t h = static_cast(QUADRANT_LEN(q->level)); + const double intsize = 1.0 / ROOT_LEN; + const double scale = 1; + + int & bx = bSize[0]; + int & by = bSize[1]; + int & bz = bSize[2]; + + bool use_block_amr = (bx < 0 or by < 0 or bz < 0) ? false : true; + + if (use_block_amr) + { + + double hx = hd / bx; + double hy = hd / by; + // double hz = hd/bz; + + double eta_z = 0; + + int inode = 0; + for (int32_t jy = 0; jy < by + 1; ++jy) + { + double eta_y = intsize * q->y + hy * jy; + for (int32_t jx = 0; jx < bx + 1; ++jx) + { + double eta_x = intsize * q->x + hx * jx; + + if (geom != nullptr) + { + + xyz[0] = eta_x; + xyz[1] = eta_y; + xyz[2] = eta_z; + // from logical coordinates to physical cartesian coordinates + geom->X(geom, which_tree, xyz, XYZ); + data[3 * inode + 0] = static_cast(XYZ[0]); + data[3 * inode + 1] = static_cast(XYZ[1]); + data[3 * inode + 2] = static_cast(XYZ[2]); + } + else + { + std::array eta{ eta_x, eta_y, 0 }; + + auto vcoord = logical2vertex<2>(forest, which_tree, eta); + + data[3 * inode + 0] = static_cast(vcoord[0]); + data[3 * inode + 1] = static_cast(vcoord[1]); + data[3 * inode + 2] = static_cast(vcoord[2]); + } + + ++inode; + + } // end for jx + } // end for jy + } + else // cell-based AMR + { + int k = 0; // vertex index + double eta_z = 0; + + for (int yi = 0; yi < 2; ++yi) + { + double eta_y = intsize * q->y + h2 * (1.0 + (yi * 2 - 1) * scale); + + for (int xi = 0; xi < 2; ++xi) + { + KALYPSSO_ASSERT(0 <= k and k < NB_CHILDREN); + double eta_x = intsize * q->x + h2 * (1.0 + (xi * 2 - 1) * scale); + + if (geom != nullptr) + { + + xyz[0] = eta_x; + xyz[1] = eta_y; + xyz[2] = eta_z; + // from logical coordinates to physical cartesian coordinates + geom->X(geom, which_tree, xyz, XYZ); + data[3 * k + 0] = static_cast(XYZ[0]); + data[3 * k + 1] = static_cast(XYZ[1]); + data[3 * k + 2] = static_cast(XYZ[2]); + } + else + { + + qcoord_t qxyz[3] = { q->x + h * xi, q->y + h * yi, 0 }; + + // cartesian geometry, use the regular qcoord_to_vertex function + // to retrieve physical coordinates + p4est::Wrapper<2>::qcoord_to_vertex(forest->connectivity, which_tree, qxyz, XYZ); + + data[3 * k + 0] = static_cast(XYZ[0]); + data[3 * k + 1] = static_cast(XYZ[1]); + data[3 * k + 2] = static_cast(XYZ[2]); + } + ++k; + } // for xi + } // for yi + } + +} // io_fill_coordinates - 2D + +// ======================================================= +// ======================================================= +template <> +void +io_fill_coordinates<3>(forest_t<3> * forest, + geometry_t<3> * geom, + p4est::topidx_t which_tree, + quadrant_t<3> * q, + float * data, + std::array bSize) +{ + + auto ROOT_LEN = p4est::Wrapper<3>::ROOT_LEN; + auto QUADRANT_LEN = p4est::Wrapper<3>::QUADRANT_LEN; + [[maybe_unused]] auto NB_CHILDREN = p4est::Wrapper<3>::NB_CHILDREN; + using qcoord_t = p4est::qcoord_t; + + double xyz[3] = { 0, 0, 0 }; + double XYZ[3] = { 0, 0, 0 }; + + const double hd = (static_cast(QUADRANT_LEN(q->level))) / ROOT_LEN; + const double h2 = 0.5 * QUADRANT_LEN(q->level) / ROOT_LEN; + const qcoord_t h = static_cast(QUADRANT_LEN(q->level)); + const double intsize = 1.0 / ROOT_LEN; + const double scale = 1; + + int & bx = bSize[0]; + int & by = bSize[1]; + int & bz = bSize[2]; + + bool use_block_amr = (bx < 0 or by < 0 or bz < 0) ? false : true; + + if (use_block_amr) + { + + double hx = hd / bx; + double hy = hd / by; + double hz = hd / bz; + + int inode = 0; + for (int32_t jz = 0; jz < bz + 1; ++jz) + { + double eta_z = intsize * q->z + hz * jz; + for (int32_t jy = 0; jy < by + 1; ++jy) + { + double eta_y = intsize * q->y + hy * jy; + for (int32_t jx = 0; jx < bx + 1; ++jx) + { + double eta_x = intsize * q->x + hx * jx; + + if (geom != nullptr) + { + + xyz[0] = eta_x; + xyz[1] = eta_y; + xyz[2] = eta_z; + // from logical coordinates to physical cartesian coordinates + geom->X(geom, which_tree, xyz, XYZ); + data[3 * inode + 0] = static_cast(XYZ[0]); + data[3 * inode + 1] = static_cast(XYZ[1]); + data[3 * inode + 2] = static_cast(XYZ[2]); + } + else + { + std::array eta{ eta_x, eta_y, eta_z }; + + auto vcoord = logical2vertex<3>(forest, which_tree, eta); + + data[3 * inode + 0] = static_cast(vcoord[0]); + data[3 * inode + 1] = static_cast(vcoord[1]); + data[3 * inode + 2] = static_cast(vcoord[2]); + } + + ++inode; + + } // end for jx + } // end for jy + } // end for jz + } + else // cell-based AMR + { + int k = 0; // vertex index + + for (int zi = 0; zi < 2; ++zi) + { + double eta_z = intsize * q->z + h2 * (1.0 + (zi * 2 - 1) * scale); + + for (int yi = 0; yi < 2; ++yi) + { + double eta_y = intsize * q->y + h2 * (1.0 + (yi * 2 - 1) * scale); + + for (int xi = 0; xi < 2; ++xi) + { + KALYPSSO_ASSERT(0 <= k and k < NB_CHILDREN); + double eta_x = intsize * q->x + h2 * (1.0 + (xi * 2 - 1) * scale); + + if (geom != nullptr) + { + + xyz[0] = eta_x; + xyz[1] = eta_y; + xyz[2] = eta_z; + // from logical coordinates to physical cartesian coordinates + geom->X(geom, which_tree, xyz, XYZ); + data[3 * k + 0] = static_cast(XYZ[0]); + data[3 * k + 1] = static_cast(XYZ[1]); + data[3 * k + 2] = static_cast(XYZ[2]); + } + else + { + + qcoord_t qxyz[3] = { q->x + h * xi, q->y + h * yi, q->z + h * zi }; + + // cartesian geometry, use the regular qcoord_to_vertex function + // to retrieve physical coordinates + p4est::Wrapper<3>::qcoord_to_vertex(forest->connectivity, which_tree, qxyz, XYZ); + + data[3 * k + 0] = static_cast(XYZ[0]); + data[3 * k + 1] = static_cast(XYZ[1]); + data[3 * k + 2] = static_cast(XYZ[2]); + } + ++k; + } // for xi + } // for yi + } // for zi + } // end cell-based amr + +} // io_fill_coordinates - 3d + +} // namespace kalypsso diff --git a/src/kalypsso/core/HDF5_IO_common.h b/src/kalypsso/core/HDF5_IO_common.h new file mode 100644 index 0000000..74b1d3d --- /dev/null +++ b/src/kalypsso/core/HDF5_IO_common.h @@ -0,0 +1,167 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file HDF5_IO_common.h + * + * Some utilities to save AMR data using hdf5+xdmf file format + */ +#ifndef KALYPSSO_CORE_HDF5_IO_COMMON_H_ +#define KALYPSSO_CORE_HDF5_IO_COMMON_H_ + +#include // for KALYPSSO_CORE_USE_DOUBLE, KALYPSSO_CORE_USE_MPI, ... +#include + +#include + +#define IO_NODES_PER_CELL_2D 4 +#define IO_TOPOLOGY_TYPE_2D "Quadrilateral" +#define IO_NODES_PER_CELL_3D 8 +#define IO_TOPOLOGY_TYPE_3D "Hexahedron" + +#define IO_XDMF_NUMBER_TYPE "NumberType=\"Float\" Precision=\"4\"" + +#ifndef IO_HDF5_COMPRESSION +# define IO_HDF5_COMPRESSION 3 +#endif + +namespace kalypsso +{ + +#ifdef KALYPSSO_CORE_USE_MPI +// Currently, HighFive doesn't wrap retrieving information from property lists. +// Therefore, one needs to use HDF5 directly. For example, to see if collective +// MPI-IO operations were used, one may. Conveniently, this also provides identifiers +// of the cause for not using collective MPI calls. +void +check_collective_io(const HighFive::DataTransferProps & xfer_props); +#endif // KALYPSSO_CORE_USE_MPI + +/** + * \brief All the supported attribute types. + * + * \todo should probably be refactored or removed + */ +enum io_attribute_type_t +{ + IO_CELL_SCALAR, //!< A cell-centered scalar. + IO_CELL_VECTOR, //!< A cell-centered vector. + IO_NODE_SCALAR, //!< A node-centered scalar. + IO_NODE_VECTOR, //!< A node-centered vector. + IO_FACE_DATA, //!< face-centered data (only for checkpointing FaceDataArrayBlock). +}; + +/** + * \brief Compute the coordinates of the 4 (or 8) nodes of a quadrant. + * + * Usually, data will be a huge array containing all the local nodes of a + * process. To write the nodes for a specific quadrant, we call this function + * with (data + offset) where the offset is calculated as: + * offset = 3 * P4EST_CHILDREN * current_quadrant_id + * + * This routine is only used when a p4est_node_t is not available, i.e. the scale + * parameter is smaller than 1.0. + * + * + * If block size parameter (bSize) contains non-negative values, then write + * all coordinates of all nodes of a sub cartesian grid of size (m_bx,m_by,m_bz). + */ +template +void +io_fill_coordinates(forest_t * forest, + geometry_t * geom, + p4est::topidx_t which_tree, + quadrant_t * q, + float * data, + std::array bSize); + +// 2D specialization +template <> +void +io_fill_coordinates<2>(forest_t<2> * forest, + geometry_t<2> * geom, + p4est::topidx_t which_tree, + quadrant_t<2> * q, + float * data, + std::array bSize); + +// 3D specialization +template <> +void +io_fill_coordinates<3>(forest_t<3> * forest, + geometry_t<3> * geom, + p4est::topidx_t which_tree, + quadrant_t<3> * q, + float * data, + std::array bSize); + +// ======================================================= +// ======================================================= +/** + * \brief Convert a H5T_NATIVE_* type to an XDMF NumberType + */ +template +static inline const char * +hdf5_native_type_to_string() +{ + + if constexpr (std::is_same::value) + { + return "NumberType=\"Int\""; + } + + else if constexpr (std::is_same::value) + { + return "NumberType=\"UInt\""; + } + + else if constexpr (std::is_same::value) + { + return "NumberType=\"Char\""; + } + + else if constexpr (std::is_same::value) + { + return "NumberType=\"UChar\""; + } + + else if constexpr (std::is_same::value or std::is_same::value) + { + return "NumberType=\"Short\""; + } + + else if constexpr (std::is_same::value or + std::is_same::value) + { + return "NumberType=\"UShort\""; + } + + else if constexpr (std::is_same::value) + { + return "NumberType=\"UInt\" Precision=\"4\""; + } + + else if constexpr (std::is_same::value) + { + return "NumberType=\"UInt\" Precision=\"8\""; + } + + else if constexpr (std::is_same::value) + { + return "NumberType=\"Float\" Precision=\"4\""; + } + + else if constexpr (std::is_same::value) + { + return "NumberType=\"Float\" Precision=\"8\""; + } + + // KALYPSSO_GLOBAL_INFOF ("Unsupported number type %ld.\n", (long int)type); + return IO_XDMF_NUMBER_TYPE; + +} // hdf5_native_type_to_string + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_HDF5_IO_COMMON_H_ diff --git a/src/kalypsso/core/HDF5_Xdmf_Reader.h b/src/kalypsso/core/HDF5_Xdmf_Reader.h new file mode 100644 index 0000000..f5fa954 --- /dev/null +++ b/src/kalypsso/core/HDF5_Xdmf_Reader.h @@ -0,0 +1,754 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file HDF5_Xdmf_Reader.h + * \brief same functionality as HDF5_IO but only relying on orchard key hashmap, no p4est here. + * + * The main reason for redesigning io is to be ease implementing a class that could dump regular + * quadrants (inside domain), as well as outside quadrants. + */ +#ifndef KALYPSSO_CORE_HDF5_XDMF_READER_H_ +#define KALYPSSO_CORE_HDF5_XDMF_READER_H_ + +#include +#include + +#include +#include +#include // for DataArrayLeaf, DataArrayLeafHost + +#include +#include +#include + +#include // includes kalypsso_core_config.h +#include +#include +#include +#include +#include +#include +#include +#include // for compute_level_histogram +#include +#include + +#include + +#include + +#include +#include // for std::reduce +#include // for std::multiplies + +namespace kalypsso +{ + +// ================================================================================================ +// ================================================================================================ +/** + * \brief HDF5 reader for both quadrant inside / outside domain. + * + * Optionally a Xdmf file is written so that hdf5 can be opened directly in paraview using VTU data + * format (VTK Unstructured Mesh) + * + * \tparam dim = 2 or 3 + */ +template +class HDF5_Xdmf_Reader +{ + +public: + // using DataArrayLeafHost_t = DataArrayLeafHost; + using DataArrayBlockLegacyHost_t = DataArrayBlockLegacyHost; + using DataArrayBlockHost_t = DataArrayBlock; + using DataArrayBlockMultiVarHost_t = DataArrayBlockMultiVar; + using MaterialPresenceHost_t = MaterialPresenceView; + using FaceDataArrayBlock_t = FaceDataArrayBlock; + + using p4est_t = typename p4est::Wrapper; + + using MeshMap_t = MeshMap; + + using orchard_key_view_t = typename orchard_key_base_t::view_t; + using orchard_key_view_host_t = typename orchard_key_base_t::view_host_t; + + /** + * \brief Constructor. + * + * The reader is has no opened files by default except a main xmf file + * that contains a collection of all the files that are to be written + * next as a `Temporal` Collection. + * + * \param[in] par_env is the parallel environment (to access MPI_Comm) + * \param[in] config_map to access parameters settings + * \param[in] mesh_map to get access to orchard keys (as kokkos view) and amr_mesh_info + * + */ + HDF5_Xdmf_Reader(ParallelEnv const & par_env, + ConfigMap const & config_map, + std::shared_ptr mesh_map); + + //! destructor + virtual ~HDF5_Xdmf_Reader(); + + /** + * To be called as often as the mesh changes (refine/coarsen/balance/repartition). + * + * Recompute local (current MPI proc) and global (all MPI proc) number + * of quadrants (forest leaves) and mesh block nodes. + */ + void + update_mesh_info(); + + /** + * \brief Open the HDF5 file for reading. + */ + void + open(std::string basename, std::string outDir); + + /** + * \brief Open the HDF5 file for reading. + */ + void + open(std::string full_path); + + /** + * \brief Close the HDF5 file. + */ + void + close(); + + /** + * Read scalar attribute. + * + * \param[in] group_name hdf5 group name + * \param[in] var_name scalar variable name + * \param[out] data variable value + */ + template + uint64_t + read_scalar_attribute(const std::string & group_name, + const std::string & var_name, + data_t & data); + + + /** + * \brief Read cell-centered attribute. + * + * \param [in] name The name of the attribute. + * \param [in] data The data to be read. + * \param [in] dimData In the case of a vector, this is the dimension of each + * element in the vector field. + * \param [in] ftype The type of the attribute. See supported types in + * the io_attribute_type_t enum. + * + * \return number of bytes written + */ + template + uint64_t + read_cc_attribute(const std::string & name, + data_t * data, + size_t dimData, + io_attribute_type_t ftype); + + /** + * \brief Read face-centered data. + * + * Be aware that face data can't be added to xmdf and read directly in Paraview. + * Currently this is only useful for checkpoint/restart a FaceDataArrayBlock. + * At some point, we will see if we could update our paraview plugin to read face data, convert + * them into cell-centered data on the fly. + * + * \param [in] name The name of the attribute. + * \param [out] data Pointer where data will be copied. + * \param [in] dimData In the case of a vector, this is the dimension of each + * element in the vector field. + * \param [in] number of face elements per octants (see + * FaceDataArrayBlock::num_elements_per_octant). + * + * \return number of bytes read + */ + template + uint64_t + read_fc_attribute(const std::string & name, + data_t * data, + size_t dimData, + size_t num_elements_per_oct); + + /** + * \brief Read a cell-centered scalar attributes. + * + * \param [out] datah a host Kokkos View with the user data (block data) + * \param [in] varIdx scalar index identify scalar field to read + * \param [in] varName variable name + * \param [in] iOct_begin index to first octant to read + * \param [in] nbOcts number of octants to read + */ + uint64_t + read_quadrant_attribute(DataArrayBlockHost_t datah, + int32_t varIdx, + const std::string varName, + int32_t iOct_begin, + int32_t nbOcts); + + /** + * \brief Read face-centered data. + * + * \param [out] face_data a device view of some face data. + * \param [in] varName variable name + */ + uint64_t + read_quadrant_attribute(FaceDataArrayBlock_t face_data, const std::string varName); + + /** + * \brief Read all cell-centered scalar attributes. + * + * \param [out] datah a host multivar array + * \param [in] matph a host material presence object + * \param [in] num_mats the number of materials + * \param [in] num_vars_per_mat the number of variables per material + * \param [in] mat_varIdx scalar index identify scalar field to read related to materials + * \param [in] varName variable name (prefix) + */ + // uint64_t + // read_quadrant_multi_mat_attribute(DataArrayBlockMultiVarHost_t datah, + // MaterialPresenceHost_t matph, + // int32_t num_mats, + // int32_t num_vars_per_mat, + // int32_t mat_varIdx, + // std::string varName, + // int32_t iOct_begin, + // int32_t nbOcts); + + ParallelEnv const & m_par_env; + ConfigMap const & m_config_map; + + std::shared_ptr m_mesh_map; + + int m_bx; //!< block size along x + int m_by; //!< block size along y + int m_bz; //!< block size along z + + uint32_t m_nbCellsPerLeaf; //!< only useful for block amr + + HighFive::File * m_hdf5_file; //!< HDF5 file descriptor + FILE * m_xdmf_file; //!< XDMF file descriptor + FILE * m_main_xdmf_file; //!< main XDMF file descriptor + + // store information about nodes for writing node data + + uint64_t m_global_num_quads; //!< global accumulated (all MPI proc) number of quads/octs + uint32_t m_local_num_quads; //!< local (current MPI proc) number of quads/octs + uint64_t m_global_first_quad; //!< global index of first quadrant in current MPI proc + + bool m_use_outside_quads = false; + + void + use_outside_quads(bool value); + +private: + /* + * HDF5 utilities. + */ + + /** + * \brief Read a given dataset from the HDF5 file. + * + * \param [in] fd An open file descriptor to a HDF5 file. + * \param [in] dataset_name The name of the dataset we are writing. + * \param [out] data_ptr memory pointer to data read from file. + * \param [in] dims The global dimensions of the dataset. + * \param [in] count The local dimensions of the dataset. + * \param [in] offset The offset of the local data with respect to the global + * positioning. + * + * \return total number of bytes written. + * + * \sa H5TPublic.h + */ + template + uint64_t + io_hdf5_readv(HighFive::File & hdf5_file, + std::string dataset_prefix, + const std::string & dataset_name, + data_t * data_ptr, + std::vector const & count, + std::vector const & offset); + +}; // class HDF5_Xdmf_Reader + +// ============================================================================= +// ============================================================================= +// class HDF5_Xdmf_Reader definition +// ============================================================================= +// ============================================================================= + +// ======================================================= +// ======================================================= +template +HDF5_Xdmf_Reader::HDF5_Xdmf_Reader(ParallelEnv const & par_env, + ConfigMap const & config_map, + std::shared_ptr mesh_map) + : m_par_env(par_env) + , m_config_map(config_map) + , m_mesh_map(mesh_map) + , m_bx(config_map.getInteger("amr", "bx", 1)) + , m_by(config_map.getInteger("amr", "by", 1)) + , m_bz(config_map.getInteger("amr", "bz", 1)) + , m_nbCellsPerLeaf(dim == 2 ? static_cast(m_bx * m_by) + : static_cast(m_bx * m_by * m_bz)) + , m_hdf5_file() + , m_global_num_quads(0) + , m_local_num_quads(0) + , m_global_first_quad(0) +{ + + // TODO + update_mesh_info(); + +} // HDF5_Xdmf_Reader::HDF5_Xdmf_Reader + +// ======================================================= +// ======================================================= +template +HDF5_Xdmf_Reader::~HDF5_Xdmf_Reader() +{ + + // close hdf5 file + close(); + +} // HDF5_Xdmf_Reader::~HDF5_Xdmf_Reader + +// ======================================================= +// ======================================================= +template +void +HDF5_Xdmf_Reader::update_mesh_info() +{ + + m_local_num_quads = static_cast(m_mesh_map->get_amr_mesh_info().local_num_quadrants()); + m_global_num_quads = + static_cast(m_mesh_map->get_amr_mesh_info().global_num_quadrants()); + m_global_first_quad = + static_cast(m_mesh_map->get_amr_mesh_info().global_first_quadrant()); + +} // HDF5_Xdmf_Reader::update_mesh_info + +// ======================================================= +// ======================================================= +template +void +HDF5_Xdmf_Reader::open(std::string basename, std::string outDir) +{ + // build filename + std::string filename = basename + ".h5"; + std::string full_path = outDir + "/" + filename; + + this->open(full_path); + +} // HDF5_Xdmf_Reader::open + +// ======================================================= +// ======================================================= +template +void +HDF5_Xdmf_Reader::open(std::string full_path) +{ + /* + * create file access property list + */ + HighFive::FileAccessProps fapl; +#ifdef KALYPSSO_CORE_USE_MPI + fapl.add(HighFive::MPIOFileAccess{ m_par_env.mpi_comm(), MPI_INFO_NULL }); + // all metadata are written using collective IO + fapl.add(HighFive::MPIOCollectiveMetadata{}); +#endif // KALYPSSO_CORE_USE_MPI + + /* + * Open parallel HDF5 resources. + */ + m_hdf5_file = new HighFive::File(full_path, HighFive::File::ReadOnly, fapl); + +} // HDF5_Xdmf_Reader::open + +// ======================================================= +// ======================================================= +template +void +HDF5_Xdmf_Reader::close() +{ + // flush HDF5 + if (m_hdf5_file != nullptr) + { + m_hdf5_file->flush(); + delete m_hdf5_file; + m_hdf5_file = nullptr; + } + +} // HDF5_Xdmf_Reader::close + +// ======================================================= +// ======================================================= +template +template +uint64_t +HDF5_Xdmf_Reader::read_scalar_attribute(const std::string & group_name, + const std::string & var_name, + data_t & data) +{ + if (m_hdf5_file->exist(group_name)) + { + // const auto group = m_hdf5_file->getGroup(group_name); + // const auto attribute = group.getAttribute(var_name); + const auto attribute = m_hdf5_file->getGroup(group_name).getAttribute(var_name); + data = attribute.read(); + } + + return sizeof(data_t); + +} // HDF5_Xdmf_Reader::read_scalar_attribute + +// ======================================================= +// ======================================================= +template +template +uint64_t +HDF5_Xdmf_Reader::read_cc_attribute(const std::string & data_name, + data_t * data, + size_t dimData, + io_attribute_type_t ftype) +{ + uint64_t num_bytes = 0; + + std::vector dims{ 0 }; + std::vector count{ 0 }; + std::vector start{ 0 }; + + if (dimData > 0) + { + dims.resize(2); + count.resize(2); + start.resize(2); + } + + if (ftype == IO_CELL_SCALAR) + { + + dims[0] = static_cast(m_mesh_map->get_amr_mesh_info().global_num_quadrants()) * + m_nbCellsPerLeaf; + if (dimData > 0) + dims[1] = dimData; + + count[0] = + static_cast(m_mesh_map->get_amr_mesh_info().local_num_quadrants()) * m_nbCellsPerLeaf; + if (dimData > 0) + count[1] = dims[1]; + + // get global index of the first octant of current mpi processor + start[0] = static_cast(m_mesh_map->get_amr_mesh_info().global_first_quadrant()) * + m_nbCellsPerLeaf; + if (dimData > 0) + start[1] = 0; + } + + num_bytes += io_hdf5_readv(*m_hdf5_file, "/celldata/", data_name, data, count, start); + + return num_bytes; + +} // HDF5_Xdmf_Reader::read_cc_attribute + +// ======================================================= +// ======================================================= +template +template +uint64_t +HDF5_Xdmf_Reader::read_fc_attribute(const std::string & data_name, + data_t * data, + size_t dimData, + size_t num_elements_per_oct) +{ + uint64_t num_bytes = 0; + + std::vector dims{ 0 }; + std::vector count{ 0 }; + std::vector start{ 0 }; + + if (dimData > 0) + { + dims.resize(2); + count.resize(2); + start.resize(2); + } + + dims[0] = static_cast(m_mesh_map->get_amr_mesh_info().global_num_quadrants()) * + num_elements_per_oct; + + count[0] = static_cast(m_mesh_map->get_amr_mesh_info().local_num_quadrants()) * + num_elements_per_oct; + + // get global index of the first octant of current mpi processor + start[0] = static_cast(m_mesh_map->get_amr_mesh_info().global_first_quadrant()) * + num_elements_per_oct; + + num_bytes += io_hdf5_readv(*m_hdf5_file, "/facedata/", data_name, data, count, start); + + return num_bytes; + +} // HDF5_Xdmf_Reader::read_fc_attribute + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Reader::read_quadrant_attribute(DataArrayBlockHost_t datah, + int32_t varIdx, + const std::string varName, + int32_t iOct_begin, + int32_t nbOcts) +{ + uint64_t num_bytes = 0; + const int32_t nbCellsPerOct = dim == 2 ? m_bx * m_by : m_bx * m_by * m_bz; + + assertm(datah.num_cells() == nbCellsPerOct, + "HDF5_Xdmf_Reader::read_quadrant_attribute wrong data size"); + + // we need to gather data corresponding to a given scalar variable + using DataArrayScalar = Kokkos::View; + + // remember that + // - data.extent(0) is the number of cells per octant + // - data.extent(1) is the number of scalar fields + // - data.extent(2) is the total number of oct in current MPI process + DataArrayScalar dataVar = + DataArrayScalar("scalar_array_for_hdf5_io", static_cast(nbCellsPerOct * nbOcts)); + + // actual data writing + num_bytes += read_cc_attribute(varName, dataVar.data(), 0, IO_CELL_SCALAR); + + // copy dataVar into datah + Kokkos::parallel_for( + "HDF5_Xdmf_Reader::read_quadrant_attribute", + Kokkos::RangePolicy(iOct_begin, iOct_begin + nbOcts), + KOKKOS_LAMBDA(int32_t iOct) { + for (int32_t iCell = 0; iCell < nbCellsPerOct; ++iCell) + datah(iCell, varIdx, iOct) = dataVar(iCell + nbCellsPerOct * (iOct - iOct_begin)); + }); + + // make sure all kokkos kernels are done before actual writing + Kokkos::fence(); + + return num_bytes; + +} // HDF5_Xdmf_Reader::read_quadrant_attribute + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Reader::read_quadrant_attribute(FaceDataArrayBlock_t face_data, + const std::string varName) +{ + + uint64_t num_bytes = 0; + size_t num_elements_per_octant = static_cast(face_data.num_elements_per_octant()); + + auto face_data_storage = face_data.logical_view(); + auto face_data_storage_host = Kokkos::create_mirror_view(Kokkos::HostSpace{}, face_data_storage); + + // actual data writing + num_bytes += + read_fc_attribute(varName, face_data_storage_host.data(), 0, num_elements_per_octant); + + Kokkos::deep_copy(face_data_storage, face_data_storage_host); + + return num_bytes; + +} // HDF5_Xdmf_Reader::read_quadrant_attribute - FaceDataArrayBlock + +// ======================================================= +// ======================================================= +// template +// uint64_t +// HDF5_Xdmf_Reader::read_quadrant_multi_mat_attribute( +// DataArrayBlockMultiVarHost_t datah, +// MaterialPresenceHost_t matph, +// int32_t num_mats, +// int32_t num_vars_per_mat, +// int32_t mat_varIdx, +// std::string varName, +// int32_t iOct_begin, +// int32_t nbOcts) +// { +// varName = "_" + varName; + +// uint64_t num_bytes = 0; +// int32_t nbCellsPerOct = dim == 2 ? m_bx * m_by : m_bx * m_by * m_bz; + +// int32_t size_x = m_bx + 2 * m_gx; +// int32_t size_y = m_by + 2 * m_gy; +// [[maybe_unused]] int32_t size_z = m_bz + 2 * m_gz; + +// // we don't want to capture this in kokkos lambda's +// auto bx = m_bx; +// auto by = m_by; +// [[maybe_unused]] auto bz = m_bz; + +// auto gx = m_gx; +// auto gy = m_gy; +// [[maybe_unused]] auto gz = m_gz; + +// if (m_extract_block_ghost) +// { +// [[maybe_unused]] int32_t nbCellsPerOct_ghosted = +// dim == 2 ? size_x * size_y : size_x * size_y * size_z; + +// assertm(datah.num_cells() == nbCellsPerOct_ghosted, +// "HDF5_Xdmf_Reader::read_quadrant_multi_mat_attribute wrong data size"); +// } +// else +// { +// assertm(datah.num_cells() == nbCellsPerOct, +// "HDF5_Xdmf_Reader::read_quadrant_multi_mat_attribute wrong data size"); +// } + +// // we need to gather data corresponding to a given scalar variable +// using DataArrayScalar = Kokkos::View; + +// // array used for each material +// DataArrayScalar dataVar("scalar_array_for_hdf5_io", static_cast(nbCellsPerOct * +// nbOcts)); + +// for (int32_t imat = 0; imat < num_mats; imat++) +// { +// if (m_extract_block_ghost == false) +// { +// Kokkos::parallel_for( +// "HDF5_Xdmf_Reader::read_quadrant_multi_mat_attribute", +// Kokkos::RangePolicy(iOct_begin, iOct_begin + nbOcts), +// KOKKOS_LAMBDA(int32_t iOct) { +// if (matph.get(iOct, imat)) +// { +// const auto mat_id = matph.material_index(iOct, imat); +// const auto var_id = mat_id * num_vars_per_mat + mat_varIdx; +// for (int32_t iCell = 0; iCell < nbCellsPerOct; ++iCell) +// dataVar(iCell + nbCellsPerOct * (iOct - iOct_begin)) = datah(iCell, var_id, iOct); +// } +// else // We set the value to nan to indicates that the value "does not exist" +// for (int32_t iCell = 0; iCell < nbCellsPerOct; ++iCell) +// dataVar(iCell + nbCellsPerOct * (iOct - iOct_begin)) = nan(""); +// }); +// } +// else +// { +// Kokkos::parallel_for( +// "HDF5_Xdmf_Reader::read_quadrant_multi_mat_attribute", +// Kokkos::RangePolicy(iOct_begin, iOct_begin + nbOcts), +// KOKKOS_LAMBDA(int32_t iOct) { + +// // the only reason of the following dummy code to be here, is that cuda nvcc compile +// doesn't +// // support capturing variables inside the inner lambda inside a constexpr if +// // TODO: remove ASAP nvcc is fixed +// #ifdef __NVCC__ +// [[maybe_unused]] int dummy = 0; +// if (gx == 0 or gy == 0 or gz == 0 or bx == 0 or by == 0 or bz == 0 or size_x == 0 or +// size_y == 0 or size_z == 0 or datah.num_cells() == 0 or dataVar.extent(0) == 0 or +// mat_varIdx == -1 or iOct_begin == 0) +// dummy++; +// #endif + +// if (matph.get(iOct, imat)) +// { +// const auto mat_id = matph.material_index(iOct, imat); +// const auto var_id = mat_id * num_vars_per_mat + mat_varIdx; + +// for (int32_t iCell = 0; iCell < nbCellsPerOct; ++iCell) +// { +// if constexpr (dim == 2) +// { +// // iCell = ix + iy*m_bx +// const auto iy = iCell / bx; +// const auto ix = iCell - iy * bx; + +// const auto iCell2 = (ix + gx) + size_x * (iy + gy); +// dataVar(iCell + nbCellsPerOct * (iOct - iOct_begin)) = datah(iCell2, var_id, +// iOct); +// } +// else if constexpr (dim == 3) +// { +// // iCell = ix + iy*m_bx + iz*m_bx*m_by +// const auto iz = iCell / bx / by; +// const auto tmp = iCell - iz * bx * by; +// const auto iy = tmp / bx; +// const auto ix = tmp - iy * bx; + +// auto iCell2 = (ix + gx) + size_x * (iy + gy) + size_x * size_y * (iz + gz); +// dataVar(iCell + nbCellsPerOct * (iOct - iOct_begin)) = datah(iCell2, var_id, +// iOct); +// } +// } +// } +// else // We set the value to nan to indicates that the value "does not exist" +// for (int32_t iCell = 0; iCell < nbCellsPerOct; ++iCell) +// dataVar(iCell + nbCellsPerOct * (iOct - iOct_begin)) = nan(""); +// }); +// } + +// // make sure all kokkos kernels are done before actual writing +// Kokkos::fence(); + +// // actual data writing +// num_bytes += read_cc_attribute(std::to_string(imat) + varName, dataVar.data(), 0, +// IO_CELL_SCALAR); +// } + +// return num_bytes; + +// } // HDF5_Xdmf_Reader::read_quadrant_multi_mat_attribute + +// ======================================================= +// ======================================================= +template +template +uint64_t +HDF5_Xdmf_Reader::io_hdf5_readv(HighFive::File & hdf5_file, + std::string dataset_prefix, + const std::string & dataset_name, + data_t * data_ptr, + std::vector const & count, + std::vector const & offset) +{ + if (hdf5_file.exist(dataset_prefix + dataset_name)) + { + + // create dataset + HighFive::DataSet dataset = hdf5_file.getDataSet(dataset_prefix + dataset_name); + + auto xfer_props = HighFive::DataTransferProps{}; +#ifdef KALYPSSO_CORE_USE_MPI + xfer_props.add(HighFive::UseCollectiveIO{}); +#endif // KALYPSSO_CORE_USE_MPI + + dataset.select(offset, count).read_raw(data_ptr, xfer_props); +#ifdef KALYPSSO_CORE_USE_MPI + check_collective_io(xfer_props); +#endif // KALYPSSO_CORE_USE_MPI + + // return the total number of bytes written in local MPI process + return sizeof(data_t) * std::reduce(count.begin(), count.end(), 1u, std::multiplies<>()); + } + else + { + const std::string err_msg = + "DataSet " + dataset_prefix + dataset_name + " doesn't exist; you can't perform a restart"; + Kokkos::abort(err_msg.c_str()); + return 0; + } +} // HDF5_Xdmf_Reader::io_hdf5_readv + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_HDF5_XDMF_READER_H_ diff --git a/src/kalypsso/core/HDF5_Xdmf_Writer.h b/src/kalypsso/core/HDF5_Xdmf_Writer.h new file mode 100644 index 0000000..bd573ff --- /dev/null +++ b/src/kalypsso/core/HDF5_Xdmf_Writer.h @@ -0,0 +1,2605 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file HDF5_Xdmf_Writer.h + * \brief same functionality as HDF5_IO but only relying on orchard key hashmap, no p4est here. + * + * The main reason for redesigning io is to be ease implementing a class that could dump regular + * quadrants (inside domain), as well as outside quadrants. + */ +#ifndef KALYPSSO_CORE_HDF5_XDMF_WRITER_H_ +#define KALYPSSO_CORE_HDF5_XDMF_WRITER_H_ + +#include +#include + +#include +#include +#include // for DataArrayLeaf, DataArrayLeafHost + +#include +#include +#include + +#include // includes kalypsso_core_config.h +#include +#include +#include +#include +#include +#include +#include +#include // for compute_level_histogram +#include +#include + +#include + +#include + +#include +#include // for std::reduce +#include // for std::multiplies + +namespace kalypsso +{ + +// ================================================================================================ +// ================================================================================================ +/** + * \brief HDF5 writer for both quadrant inside / outside domain. + * + * Optionally a Xdmf file is written so that hdf5 can be opened directly in paraview using VTU data + * format (VTK Unstructured Mesh) + * + * + * \tparam dim = 2 or 3 + */ +template +class HDF5_Xdmf_Writer +{ + +public: + using DataArrayLeafHost_t = DataArrayLeafHost; + using DataArrayBlockLegacyHost_t = DataArrayBlockLegacyHost; + using DataArrayBlockHost_t = DataArrayBlock; + using DataArrayBlockMultiVarHost_t = DataArrayBlockMultiVar; + using MaterialPresenceHost_t = MaterialPresenceView; + using FaceDataArrayBlock_t = FaceDataArrayBlock; + + using p4est_t = typename p4est::Wrapper; + + using MeshMap_t = MeshMap; + + enum orchard_key_dump_type_t : uint32_t + { + REDUCED_LOCAL = 0, + REDUCED_GLOBAL = 1, + FULL = 2 + }; + + enum writing_mode_t : uint32_t + { + BLOCK_MODE = 0, + LEAF_MODE = 1 + }; + + using orchard_key_view_t = typename orchard_key_base_t::view_t; + using orchard_key_view_host_t = typename orchard_key_base_t::view_host_t; + + /** + * \brief Constructor. + * + * The writer is has no opened files by default except a main xmf file + * that contains a collection of all the files that are to be written + * next as a `Temporal` Collection. + * + * \param[in] config_map to access parameters settings + * \param[in] par_env is the parallel environment (to access MPI_Comm) + * \param[in] keys is a Kokkos::View of orchard keys + * \param[in] amr_mesh_info access number of owned, MPI ghosts and outside quadrants + * \param[in] xdmf_main_suffix is a string used to create filename for main xdmf file + * + */ + HDF5_Xdmf_Writer(ParallelEnv const & par_env, + ConfigMap const & config_map, + std::shared_ptr mesh_map, + std::string xdmf_main_suffix = "_main"); + + //! destructor + virtual ~HDF5_Xdmf_Writer(); + + /** + * To be called as often as the mesh changes (refine/coarsen/balance/repartition). + * + * Recompute local (current MPI proc) and global (all MPI proc) number + * of quadrants (forest leaves) and mesh block nodes. + */ + void + update_mesh_info(); + + /** + * \brief Open the HDF5 and XMF files for writing. + * + * Also includes this file inside the main xmf file, if needed. + */ + void + open(std::string basename, std::string outDir); + + /** + * \brief Close the HDF5 and XMF files. + * + * Also closes the main xmf file, if it was opened. + */ + void + close(); + + //! write amr metadata (orchard key array, ...) + uint64_t + write_amr_metadata(orchard_key_view_host_t keys); + + /** + * \brief Write the header for the XMF and HDF5 files. + * + * The header includes the node information, connectivity information and + * the treeid, level or mpi rank for each quadrant, if required. + * + * In the case of the XMF file, this will defined the topology and geometry + * of the mesh and point to the relevant fields in the HDF5 file. + * + * \return number of bytes written + */ + uint64_t + write_header(double time); + + /** + * \brief Write the XMF footer. + * + * Closes all the XML tags. + */ + int + write_footer(); + + /** + * Write scalar attribute. + * + * \param[in] group_name hdf5 group name + * \param[in] var_name scalar variable name + * \param[in] data variable value + */ + template + uint64_t + write_scalar_attribute(const std::string & group_name, const std::string & var_name, data_t data); + + /** + * \brief Write cell-centered data. + * + * \param [in] name The name of the attribute. + * \param [in] data The data to be written. + * \param [in] dimData In the case of a vector, this is the dimension of each + * element in the vector field. + * \param [in] ftype The type of the attribute. See supported types in + * the io_attribute_type_t enum. + * + * \return number of bytes written + */ + template + uint64_t + write_cc_attribute(const std::string & name, + data_t * data, + size_t dimData, + io_attribute_type_t ftype); + + /** + * \brief Write face-centered data. + * + * Be aware that face data can't be added to xmdf and read directly in Paraview. + * Currently this is only useful for checkpoint/restart a FaceDataArrayBlock. + * At some point, we will see if we could update our paraview plugin to read face data, convert + * them into cell-centered data on the fly. + * + * \param [in] name The name of the attribute. + * \param [in] data The data to be written. + * \param [in] dimData In the case of a vector, this is the dimension of each + * element in the vector field. + * \param [in] number of face elements per octants (see + * FaceDataArrayBlock::num_elements_per_octant). + * + * \return number of bytes written + */ + template + uint64_t + write_fc_attribute(const std::string & name, + data_t * data, + size_t dimData, + size_t num_elements_per_oct); + + /** + * \brief Write a cell-centered scalar dataset. + * + * \param [in] datah a host Kokkos View with the user data + * \param [in] varIdx scalar index identify scalar field to write + * \param [in] varName variable name + * + * \return number of bytes written + */ + uint64_t + write_quadrant_attribute(DataArrayLeafHost_t datah, int32_t varIdx, const std::string varName); + + /** + * \brief Write a cell-centered scalar attributes when block amr is enabled. + * + * \param [in] datah a host Kokkos View with the user data (block data) + * \param [in] varIdx scalar index identify scalar field to write + * \param [in] varName variable name + * \param [in] iOct_begin index to first octant to write + * \param [in] nbOcts number of octants to write + */ + uint64_t + write_quadrant_attribute(DataArrayBlockHost_t datah, + int32_t varIdx, + const std::string varName, + int32_t iOct_begin, + int64_t nbOcts); + + /** + * \brief Write face-centered attributes when block amr is enabled. + * + * \param [in] face_data a device view of some face data. + * \param [in] varName variable name + */ + uint64_t + write_quadrant_attribute(FaceDataArrayBlock_t face_data, const std::string varName); + + /** + * \brief Write all cell-centered scalar attributes when block amr is enabled. + * + * \param [in] datah a host multivar array + * \param [in] matph a host material presence object + * \param [in] num_mats the number of materials + * \param [in] num_vars_per_mat the number of variables per material + * \param [in] mat_varIdx scalar index identify scalar field to write related to materials + * \param [in] varName variable name (prefix) + */ + uint64_t + write_quadrant_multi_mat_attribute(DataArrayBlockMultiVarHost_t datah, + MaterialPresenceHost_t matph, + int32_t num_mats, + int32_t num_vars_per_mat, + int32_t mat_varIdx, + std::string varName, + int32_t iOct_begin, + int32_t nbOcts); + + /** + * write a scalar array (one scale per quadrant/octant). + */ + template + uint64_t + write_quadrant_leaf_scalar(host_view_t data_h, const std::string dataName); + + + ParallelEnv const & m_par_env; + ConfigMap const & m_config_map; + + std::shared_ptr m_mesh_map; + std::string m_basename; //!< the base name of the two files + writing_mode_t m_writing_mode; //!< writing mode (leaf or block) + + bool m_write_xdmf; //!< write xdmf + bool m_write_mesh_info; //!< write mesh info (oct level, mpi proc, ...) + bool m_write_tree; //!< default write_mesh_info (false) + bool m_write_level; //!< default write_mesh_info (false) + bool m_write_rank; //!< default write_mesh_info (false) + bool m_write_reduced_orchard_key_global; //!< default write_mesh_info (false) + bool m_write_reduced_orchard_key_local; //!< default write_mesh_info (false) + bool m_write_full_orchard_key; //!< default write_mesh_info (false) + bool m_write_at_domain_border; //!< default write_mesh_info (false) + bool m_write_at_tree_border; //!< default write_mesh_info (false) + bool m_write_is_outside; //!< default write_mesh_info (false) + + bool m_write_block_data; //!< if true, expect a DataArrayBlock object + + int m_bx; //!< block size along x + int m_by; //!< block size along y + int m_bz; //!< block size along z + + bool m_extract_block_ghost; //!< remove block ghost before writing data + int m_gx; //!< ghost width along x + int m_gy; //!< ghost width along y + int m_gz; //!< ghost width along z + + uint8_t m_nbNodesPerCell; //!< 4 (2d) or 8 (3d) + uint32_t m_nbCellsPerLeaf; //!< only useful for block amr + + HighFive::File * m_hdf5_file; //!< HDF5 file descriptor + FILE * m_xdmf_file; //!< XDMF file descriptor + FILE * m_main_xdmf_file; //!< main XDMF file descriptor + + bool m_write_iOct; //!< default false + + // store information about nodes for writing node data + + uint64_t m_global_num_nodes; //!< global accumulated (all MPI proc) number of nodes + uint32_t m_local_num_nodes; //!< local (current MPI proc) number of nodes + uint64_t m_global_num_quads; //!< global accumulated (all MPI proc) number of quads/octs + uint32_t m_local_num_quads; //!< local (current MPI proc) number of quads/octs + uint64_t m_global_first_quad; //!< global index of first quadrant in current MPI proc + + bool m_use_outside_quads = false; + + void + use_outside_quads(bool value); + +private: + /* + * XMDF utilities. + */ + + /** + * \brief Write the header of the main XMF file. + */ + void + io_xdmf_write_main_header(); + + /** + * \brief Write the XMF header information: topology and geometry. + */ + void + io_xdmf_write_header(double time); + + /** + * \brief Write the include for the current file. + */ + void + io_xdmf_write_main_include(const std::string & name); + + /** + * \brief Write information about an attribute. + * + * \param [in] fd file descriptor for xmf file + * \param [in] basename The basename. + * \param [in] name The name of the attribute. + * \param [in] type The type. + * \param [in] dims The dimensions of the attribute. If it is a scalar, dims[1] + * will be ignored. + */ + void + io_xdmf_write_attribute(const std::string & name, + const std::string & number_type, + io_attribute_type_t type, + std::vector const & dims); + + /** + * \brief Close the remaining tags for the main file. + */ + void + io_xdmf_write_main_footer(); + + /** + * \brief Close all remaining tags. + * + * \param[in] fd xmff file descriptor + */ + void + io_xdmf_write_footer(); + + /* + * HDF5 utilities. + */ + /** + * \brief Write a given dataset into the HDF5 file. + * + * \param [in] fd An open file descriptor to a HDF5 file. + * \param [in] dataset_name The name of the dataset we are writing. + * \param [in] data_ptr pointer to data to write. + * \param [in] dims The global dimensions of the dataset. + * \param [in] count The local dimensions of the dataset. + * \param [in] offset The offset of the local data with respect to the global + * positioning. + * + * \return total number of bytes written. + * + * \sa H5TPublic.h + */ + template + uint64_t + io_hdf5_writev(HighFive::File & hdf5_file, + const std::string dataset_prefix, + const std::string & dataset_name, + data_t * data_ptr, + std::vector const & dims, + std::vector const & count, + std::vector const & offset); + + /** + * \brief Compute and write the coordinates of all the mesh nodes. + * + * Mesh coordinates is only necessary if one wants to load data into paraview as a VTU + * (VTK Unstructured mesh) + * + * \return number of bytes written + */ + uint64_t + io_hdf5_write_coordinates(); + + /** + * \brief Compute and write the connectivity information for each quadrant. + * \return number of bytes written + */ + uint64_t + io_hdf5_write_connectivity(); + + /** + * \brief Compute and write the tree id for each quadrant. + * \return number of bytes written + */ + uint64_t + io_hdf5_write_tree(); + + /** + * \brief Compute and write the level for each quadrant. + * \return number of bytes written + */ + uint64_t + io_hdf5_write_level(); + + /** + * \brief Write local quadrant id (local to current MPI process). + * Purely for debug purpose. + * \return number of bytes written + */ + uint64_t + io_hdf5_write_iOct(); + + /** + * \brief Compute and write the MPI rank for each quadrant. + * \return number of bytes written + */ + uint64_t + io_hdf5_write_rank(); + + /** + * \brief Compute and write the reduced orchard key for each quadrant. + * + * reduced orchard key is obtain by considering only the octant part (discard level and tree), + * and then right shift the bits by level_max (to keep only most significant bits) + * + * + * \param[in] global if global is true, compute reduced key with tree, else without tree + * \return number of bytes written + */ + uint64_t + io_hdf5_write_reduced_orchard_key(orchard_key_dump_type_t type); + + /** + * \brief Compute and write for each quadrant a boolean value indicating if quadrant touches + * external border. + * \return number of bytes written + */ + uint64_t + io_hdf5_write_at_domain_border(); + + /** + * \brief Compute and write for each quadrant a boolean value indicating if quadrant touches + * tree border. + * \return number of bytes written + */ + uint64_t + io_hdf5_write_at_tree_border(); + + /** + * \brief Write for each quadrant a integer value indicating if quadrant is outside domain. + * \return number of bytes written + */ + uint64_t + io_hdf5_write_is_outside(); + + //! get cell linear index from (jx,jy,jz) + int32_t + subCellIndex(int jx, int jy, int jz); + + //! get node linear index from (jx,jy,jz) + int32_t + subNodeIndex(int jx, int jy, int jz); + +public: + //! switch to block mode for writing DataArrayBlock. + //! block sizes are set from config_map + void + set_block_mode(); + + void + set_block_mode(block_size_t block_size, block_size_t ghost_width); + + //! switch to leaf mode for writing DataArrayLeaf. + //! one cell per (octree) leaf. + void + set_leaf_mode(); + + //! Configure if we want to write mesh info. + //! + //! \param[in] value, if true we write all mesh info if not specified in input parameter + //! \note if write_mesh_info is set in input parameter it will be used in constructor + //! outside of constructor this method can be used to change behavior of writer. + void + set_write_mesh_info(bool value) + { + m_write_mesh_info = value; + m_write_tree = m_config_map.getBool("output", "write_mesh_tree", m_write_mesh_info); + m_write_level = m_config_map.getBool("output", "write_mesh_level", m_write_mesh_info); + m_write_rank = m_config_map.getBool("output", "write_mesh_rank", m_write_mesh_info); + m_write_reduced_orchard_key_global = + m_config_map.getBool("output", "write_mesh_reduced_orchard_key_global", m_write_mesh_info); + m_write_reduced_orchard_key_local = + m_config_map.getBool("output", "write_mesh_reduced_orchard_key_local", m_write_mesh_info); + m_write_full_orchard_key = + m_config_map.getBool("output", "write_mesh_full_orchard_key", m_write_mesh_info); + m_write_at_domain_border = + m_config_map.getBool("output", "write_mesh_at_domain_border", m_write_mesh_info); + m_write_at_tree_border = + m_config_map.getBool("output", "write_mesh_at_tree_border", m_write_mesh_info); + m_write_is_outside = m_config_map.getBool("output", "write_mesh_is_outside", m_write_mesh_info); + } // set_write_mesh_info + +}; // class HDF5_Xdmf_Writer + +// ============================================================================= +// ============================================================================= +// class HDF5_Xdmf_Writer definition +// ============================================================================= +// ============================================================================= + +// ======================================================= +// ======================================================= +template +HDF5_Xdmf_Writer::HDF5_Xdmf_Writer(ParallelEnv const & par_env, + ConfigMap const & config_map, + std::shared_ptr mesh_map, + std::string xdmf_main_suffix) + : m_par_env(par_env) + , m_config_map(config_map) + , m_mesh_map(mesh_map) + , m_basename("") + , m_writing_mode(BLOCK_MODE) + , m_write_xdmf(config_map.getBool("output", "write_xdmf", true)) + , m_write_mesh_info(config_map.getBool("output", "write_mesh_info", false)) + , m_write_tree(config_map.getBool("output", "write_mesh_tree", m_write_mesh_info)) + , m_write_level(config_map.getBool("output", "write_mesh_level", m_write_mesh_info)) + , m_write_rank(config_map.getBool("output", "write_mesh_rank", m_write_mesh_info)) + , m_write_reduced_orchard_key_global( + config_map.getBool("output", "write_mesh_reduced_orchard_key_global", m_write_mesh_info)) + , m_write_reduced_orchard_key_local( + config_map.getBool("output", "write_mesh_reduced_orchard_key_local", m_write_mesh_info)) + , m_write_full_orchard_key( + config_map.getBool("output", "write_mesh_full_orchard_key", m_write_mesh_info)) + , m_write_at_domain_border( + config_map.getBool("output", "write_mesh_at_domain_border", m_write_mesh_info)) + , m_write_at_tree_border( + config_map.getBool("output", "write_mesh_at_tree_border", m_write_mesh_info)) + , m_write_is_outside(config_map.getBool("output", "write_mesh_is_outside", m_write_mesh_info)) + , m_write_block_data(true) + , m_bx(1) + , m_by(1) + , m_bz(1) + , m_extract_block_ghost(false) + , m_gx(0) + , m_gy(0) + , m_gz(0) + , m_nbNodesPerCell(dim == TWO_D ? IO_NODES_PER_CELL_2D : IO_NODES_PER_CELL_3D) + , m_nbCellsPerLeaf(dim == 2 ? static_cast(m_bx * m_by) + : static_cast(m_bx * m_by * m_bz)) + , m_hdf5_file() + , m_xdmf_file(NULL) + , m_main_xdmf_file(NULL) + , m_write_iOct(config_map.getBool("output", "write_iOct", false)) + , m_global_num_nodes(0) + , m_local_num_nodes(0) + , m_global_num_quads(0) + , m_local_num_quads(0) + , m_global_first_quad(0) + , m_use_outside_quads(false) +{ + // only meaningful when one wants to write block data (i.e. cell-wise) + // if true, write data cell-wise (bx*by*bz data per leaf quadrant) + // if false, write data quadrant-wise (i.e. only one data per quadrant) + if (m_writing_mode == BLOCK_MODE) + set_block_mode(); + else + set_leaf_mode(); + + update_mesh_info(); + + // is actually hdf5 enabled ? + const bool hdf5_enabled = m_config_map.getBool("output", "hdf5_enabled", false); + + // when nOutput==0, it means no output at all + const bool nOutput = config_map.getInteger("run", "noutput", 100); + + if (m_par_env.rank() == 0 and hdf5_enabled and nOutput != 0 and m_write_xdmf) + { + + std::string outputPrefix = m_config_map.getString("output", "outputPrefix", "output"); + std::string outputDir = m_config_map.getString("output", "outputDir", "output"); + + std::string filename; + filename = outputDir + "/" + outputPrefix + xdmf_main_suffix + ".xmf"; + + // KALYPSSO_GLOBAL_INFOF("Writing main XMDF file \"%s\".\n", filename.c_str()); + m_main_xdmf_file = fopen(filename.c_str(), "w"); + io_xdmf_write_main_header(); + } + else + { + m_main_xdmf_file = nullptr; + } + +} // HDF5_Xdmf_Writer::HDF5_Xdmf_Writer + +// ======================================================= +// ======================================================= +template +HDF5_Xdmf_Writer::~HDF5_Xdmf_Writer() +{ + + /* + * Only rank 0 needs to close the main xdmf file. + */ + if (m_par_env.rank() == 0 and m_write_xdmf and m_main_xdmf_file != nullptr) + { + io_xdmf_write_main_footer(); + + fflush(m_main_xdmf_file); + fclose(m_main_xdmf_file); + m_main_xdmf_file = nullptr; + } + + // close the other files (hdf5 + xdmf) + close(); + +} // HDF5_Xdmf_Writer::~HDF5_Xdmf_Writer + +// ======================================================= +// ======================================================= +template +void +HDF5_Xdmf_Writer::update_mesh_info() +{ + + m_local_num_quads = static_cast(m_mesh_map->get_amr_mesh_info().local_num_quadrants()); + m_global_num_quads = + static_cast(m_mesh_map->get_amr_mesh_info().global_num_quadrants()); + m_global_first_quad = + static_cast(m_mesh_map->get_amr_mesh_info().global_first_quadrant()); + + m_local_num_nodes = m_nbNodesPerCell * m_local_num_quads; + m_global_num_nodes = m_nbNodesPerCell * m_global_num_quads; + +} // HDF5_Xdmf_Writer::update_mesh_info + +// ======================================================= +// ======================================================= +template +void +HDF5_Xdmf_Writer::use_outside_quads(bool value) +{ + m_use_outside_quads = value; + + if (value) + { + // use outside quads + m_local_num_quads = + static_cast(m_mesh_map->get_amr_mesh_info().local_num_quadrants_outside()); + m_global_num_quads = + static_cast(m_mesh_map->get_amr_mesh_info().global_num_quadrants_outside()); + m_global_first_quad = + static_cast(m_mesh_map->get_amr_mesh_info().global_first_quadrant_outside()); + + m_local_num_nodes = m_nbNodesPerCell * m_local_num_quads; + m_global_num_nodes = m_nbNodesPerCell * m_global_num_quads; + } + else + { + // use inside quads + m_local_num_quads = + static_cast(m_mesh_map->get_amr_mesh_info().local_num_quadrants()); + m_global_num_quads = + static_cast(m_mesh_map->get_amr_mesh_info().global_num_quadrants()); + m_global_first_quad = + static_cast(m_mesh_map->get_amr_mesh_info().global_first_quadrant()); + + m_local_num_nodes = m_nbNodesPerCell * m_local_num_quads; + m_global_num_nodes = m_nbNodesPerCell * m_global_num_quads; + } + +} // HDF5_Xdmf_Writer::use_outside_quads + +// ======================================================= +// ======================================================= +template +void +HDF5_Xdmf_Writer::open(std::string basename, std::string outDir) +{ + // build filename + std::string filename = basename + ".h5"; + std::string full_path = outDir + "/" + filename; + m_basename = basename; + + /* + * create file access property list + */ + HighFive::FileAccessProps fapl; +#ifdef KALYPSSO_CORE_USE_MPI + fapl.add(HighFive::MPIOFileAccess{ m_par_env.mpi_comm(), MPI_INFO_NULL }); + // all metadata are written using collective IO + fapl.add(HighFive::MPIOCollectiveMetadata{}); +#endif // KALYPSSO_CORE_USE_MPI + + /* + * Open parallel HDF5 resources. + */ + m_hdf5_file = new HighFive::File(full_path, HighFive::File::Truncate, fapl); + + // add creation date (collectively), creation is initialized in root MPI process (rank 0) + { + auto date = get_current_date(); + +#ifdef KALYPSSO_CORE_USE_MPI + auto date_size = date.size(); + m_par_env.comm().MPI_Bcast(&date_size, 1, 0); + if (m_par_env.rank() != 0) + date.resize(date_size); + + m_par_env.comm().MPI_Bcast(const_cast(date.data()), static_cast(date_size), 0); +#endif // KALYPSSO_CORE_USE_MPI + + m_hdf5_file->createAttribute("creation_date", date); + } + + // add a group "amr" for storing amr related metadata + // everything that is needed to reconstruct geometrical mesh information + { + auto group = m_hdf5_file->createGroup("amr"); + group.createAttribute("dim", dim); + + std::array block_sizes; + block_sizes[IX] = m_bx; + block_sizes[IY] = m_by; + if constexpr (dim == 3) + block_sizes[IZ] = m_bz; + group.createAttribute("block_sizes", block_sizes); + + const auto brick_sizes = + to_std_array::value_type, dim>(get_brick_sizes(m_config_map)); + group.createAttribute("brick_sizes", brick_sizes); + + const auto xyz_min = to_std_array(get_xyz_min(m_config_map)); + group.createAttribute("xyz_min", xyz_min); + + const auto scaling_factor = get_scaling_factor(m_config_map); + group.createAttribute("scaling_factor", scaling_factor); + + group.createAttribute("level_min", m_config_map.getInteger("amr", "level_min", 0)); + group.createAttribute("level_max", m_config_map.getInteger("amr", "level_max", 0)); + + // write level histogram + { + const auto level_min = m_config_map.getInteger("amr", "level_min", 0); + const auto level_max = m_config_map.getInteger("amr", "level_max", 0); + + auto monitor = AMRMeshMonitoring(m_config_map); + + // all MPI procesus computes its local histogram + const auto level_histo = monitor.compute_level_histogram(m_par_env, *m_mesh_map); + const auto level_histo_host = + Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, level_histo); + + // convert histogram into a std::vector for ease using createAttribute right after + std::size_t nb_levels = static_cast(level_max - level_min + 1); + std::vector level_histo_vector(nb_levels); + for (size_t l = 0; l < nb_levels; ++l) + { + level_histo_vector[l] = level_histo_host[l]; + } + + group.createAttribute("level_histogram", level_histo_vector); + } + } + + // open xdmf files (one for each hdf5, a main xdmf file) + if (m_par_env.rank() == 0 and m_write_xdmf) + { + + filename = basename + ".xmf"; + full_path = outDir + "/" + filename; + m_xdmf_file = fopen(full_path.c_str(), "w"); + + if (m_main_xdmf_file) + { + io_xdmf_write_main_include(filename); + } + } + +} // HDF5_Xdmf_Writer::open + +// ======================================================= +// ======================================================= +template +void +HDF5_Xdmf_Writer::close() +{ + // flush HDF5 + if (m_hdf5_file != nullptr) + { + m_hdf5_file->flush(); + delete m_hdf5_file; + m_hdf5_file = nullptr; + } + + // close XDMF file descriptor + if (m_write_xdmf and m_xdmf_file) + { + fflush(m_xdmf_file); + fclose(m_xdmf_file); + m_xdmf_file = nullptr; + } + +} // HDF5_Xdmf_Writer::close + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer::write_amr_metadata(orchard_key_view_host_t keys) +{ + std::vector dims{ static_cast( + m_mesh_map->get_amr_mesh_info().global_num_quadrants()) }; + std::vector count{ static_cast( + m_mesh_map->get_amr_mesh_info().local_num_quadrants()) }; + std::vector start{ static_cast( + m_mesh_map->get_amr_mesh_info().global_first_quadrant()) }; + + using data_t = typename orchard_key_view_host_t::value_type; + + // write amr keys + { + // create dataset + HighFive::DataSet dataset = + m_hdf5_file->createDataSet("/amr/keys", HighFive::DataSpace(dims)); + + auto xfer_props = HighFive::DataTransferProps{}; +#ifdef KALYPSSO_CORE_USE_MPI + xfer_props.add(HighFive::UseCollectiveIO{}); +#endif // KALYPSSO_CORE_USE_MPI + + dataset.select(start, count).write_raw(keys.data(), xfer_props); +#ifdef KALYPSSO_CORE_USE_MPI + check_collective_io(xfer_props); +#endif // KALYPSSO_CORE_USE_MPI + } + + // write level index + { + const auto level_min = static_cast(m_config_map.getInteger("amr", "level_min", 4)); + const auto level_max = static_cast(m_config_map.getInteger("amr", "level_max", 4)); + auto level_indexes = compute_index_by_level( + keys, level_min, level_max, m_mesh_map->get_amr_mesh_info().local_num_quadrants(), m_par_env); + + // create dataset + HighFive::DataSet dataset = + m_hdf5_file->createDataSet("/amr/level_indexes", HighFive::DataSpace(dims)); + + auto xfer_props = HighFive::DataTransferProps{}; +#ifdef KALYPSSO_CORE_USE_MPI + xfer_props.add(HighFive::UseCollectiveIO{}); +#endif // KALYPSSO_CORE_USE_MPI + + dataset.select(start, count).write_raw(level_indexes.data(), xfer_props); +#ifdef KALYPSSO_CORE_USE_MPI + check_collective_io(xfer_props); +#endif // KALYPSSO_CORE_USE_MPI + } + + // Let's ensure that everything has been written do disk. + m_hdf5_file->flush(); + + // return the total number of bytes written in local MPI process + return sizeof(data_t) * count[0]; + +} // HDF5_Xdmf_Writer::write_amr_metadata + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer::write_header(double time) +{ + + // write the xmdf file first + if (m_par_env.rank() == 0 and m_write_xdmf) + { + io_xdmf_write_header(time); + } + + uint64_t num_bytes = 0; + + // and write stuff into the hdf5 file + + // when write_xdmf is false, no xdmf file is produce, no coordinates/connectivity written in hdf5 + // file + if (m_write_xdmf) + { + num_bytes += io_hdf5_write_coordinates(); + num_bytes += io_hdf5_write_connectivity(); + } + num_bytes += io_hdf5_write_level(); + num_bytes += io_hdf5_write_tree(); + num_bytes += io_hdf5_write_rank(); + num_bytes += io_hdf5_write_iOct(); + if (m_write_reduced_orchard_key_global) + num_bytes += io_hdf5_write_reduced_orchard_key(REDUCED_GLOBAL); + if (m_write_reduced_orchard_key_local) + num_bytes += io_hdf5_write_reduced_orchard_key(REDUCED_LOCAL); + if (m_write_full_orchard_key) + num_bytes += io_hdf5_write_reduced_orchard_key(FULL); + num_bytes += io_hdf5_write_at_domain_border(); + num_bytes += io_hdf5_write_at_tree_border(); + num_bytes += io_hdf5_write_is_outside(); + + return num_bytes; + +} // HDF5_Xdmf_Writer::write_header + +// ======================================================= +// ======================================================= +template +int +HDF5_Xdmf_Writer::write_footer() +{ + if (m_par_env.rank() == 0 and m_write_xdmf) + { + io_xdmf_write_footer(); + } + + return 0; + +} // HDF5_Xdmf_Writer::write_footer + +// ======================================================= +// ======================================================= +template +template +uint64_t +HDF5_Xdmf_Writer::write_scalar_attribute(const std::string & group_name, + const std::string & var_name, + data_t data) +{ + auto group = m_hdf5_file->exist(group_name) ? m_hdf5_file->getGroup(group_name) + : m_hdf5_file->createGroup(group_name); + + group.createAttribute(var_name, data); + + return sizeof(data_t); +} // HDF5_Xdmf_Writer::write_scalar_attribute + +// ======================================================= +// ======================================================= +template +template +uint64_t +HDF5_Xdmf_Writer::write_cc_attribute(const std::string & name, + data_t * data, + size_t dimData, + io_attribute_type_t ftype) +{ + uint64_t num_bytes = 0; + + std::vector dims{ 0 }; + std::vector count{ 0 }; + std::vector start{ 0 }; + + if (dimData > 0) + { + dims.resize(2); + count.resize(2); + start.resize(2); + } + + if (ftype == IO_CELL_SCALAR) + { + + if (m_use_outside_quads) + { + dims[0] = + static_cast(m_mesh_map->get_amr_mesh_info().global_num_quadrants_outside()) * + m_nbCellsPerLeaf; + if (dimData > 0) + dims[1] = dimData; + + count[0] = + static_cast(m_mesh_map->get_amr_mesh_info().local_num_quadrants_outside()) * + m_nbCellsPerLeaf; + if (dimData > 0) + count[1] = dims[1]; + + // get global index of the first octant of current mpi processor + start[0] = + static_cast(m_mesh_map->get_amr_mesh_info().global_first_quadrant_outside()) * + m_nbCellsPerLeaf; + if (dimData > 0) + start[1] = 0; + } + else + { + dims[0] = static_cast(m_mesh_map->get_amr_mesh_info().global_num_quadrants()) * + m_nbCellsPerLeaf; + if (dimData > 0) + dims[1] = dimData; + + count[0] = static_cast(m_mesh_map->get_amr_mesh_info().local_num_quadrants()) * + m_nbCellsPerLeaf; + if (dimData > 0) + count[1] = dims[1]; + + // get global index of the first octant of current mpi processor + start[0] = static_cast(m_mesh_map->get_amr_mesh_info().global_first_quadrant()) * + m_nbCellsPerLeaf; + if (dimData > 0) + start[1] = 0; + } + } + + if (m_par_env.rank() == 0 and m_write_xdmf) + { + const char * dtype_str = hdf5_native_type_to_string(); + io_xdmf_write_attribute(name, dtype_str, ftype, dims); + } + + num_bytes += io_hdf5_writev(*m_hdf5_file, "/celldata/", name, data, dims, count, start); + + return num_bytes; + +} // HDF5_Xdmf_Writer::write_cc_attribute + +// ======================================================= +// ======================================================= +template +template +uint64_t +HDF5_Xdmf_Writer::write_fc_attribute(const std::string & name, + data_t * data, + size_t dimData, + size_t num_elements_per_oct) +{ + uint64_t num_bytes = 0; + + std::vector dims{ 0 }; + std::vector count{ 0 }; + std::vector start{ 0 }; + + if (dimData > 0) + { + dims.resize(2); + count.resize(2); + start.resize(2); + } + + dims[0] = static_cast(m_mesh_map->get_amr_mesh_info().global_num_quadrants()) * + num_elements_per_oct; + + count[0] = static_cast(m_mesh_map->get_amr_mesh_info().local_num_quadrants()) * + num_elements_per_oct; + + // get global index of the first octant of current mpi processor + start[0] = static_cast(m_mesh_map->get_amr_mesh_info().global_first_quadrant()) * + num_elements_per_oct; + + num_bytes += io_hdf5_writev(*m_hdf5_file, "/facedata/", name, data, dims, count, start); + + return num_bytes; + +} // HDF5_Xdmf_Writer::write_fc_attribute + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer::write_quadrant_attribute(DataArrayLeafHost_t datah, + int32_t varIdx, + const std::string varName) +{ + + assertm(m_write_block_data == false, "[HDF5_Xdmf_Writer] m_write_block_data must be false here."); + + uint64_t num_bytes = 0; + + // if DataArray has a left layout, we only need to define + // a slice to actual scalar data + // if DataArrayLeafHost_t has right layout, we need to actually extract + // the slide so that it is memory contiguous + if (std::is_same::value) + { + + uint32_t begin = m_use_outside_quads ? m_mesh_map->get_amr_mesh_info().local_num_quadrants() + + m_mesh_map->get_amr_mesh_info().local_num_ghosts() + : 0; + uint32_t nbOcts = m_use_outside_quads + ? m_mesh_map->get_amr_mesh_info().local_num_quadrants_outside() + : m_mesh_map->get_amr_mesh_info().local_num_quadrants(); + + auto dataVar = Kokkos::subview(datah, std::make_pair(begin, begin + nbOcts), varIdx); + + // actual data writing + num_bytes += write_cc_attribute(varName, dataVar.data(), 0, IO_CELL_SCALAR); + } + else + { + using DataArrayScalar = Kokkos::View; + + int32_t begin = m_use_outside_quads ? m_mesh_map->get_amr_mesh_info().local_num_quadrants() + + m_mesh_map->get_amr_mesh_info().local_num_ghosts() + : 0; + int32_t nbOcts = m_use_outside_quads + ? m_mesh_map->get_amr_mesh_info().local_num_quadrants_outside() + : m_mesh_map->get_amr_mesh_info().local_num_quadrants(); + + DataArrayScalar dataVar("scalar_array_for_hdf5_io", static_cast(nbOcts)); + + Kokkos::parallel_for( + "HDF5_Xdmf_Writer::write_quadrant_attribute", + Kokkos::RangePolicy(begin, begin + nbOcts), + KOKKOS_LAMBDA(int32_t iOct) { dataVar(iOct - begin) = datah(iOct, varIdx); }); + + // make sure all kokkos kernels are done before actual writing + Kokkos::fence(); + + // actual data writing + num_bytes += write_cc_attribute(varName, dataVar.data(), 0, IO_CELL_SCALAR); + } + + return num_bytes; + +} // HDF5_Xdmf_Writer::write_quadrant_attribute + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer::write_quadrant_attribute(DataArrayBlockHost_t datah, + int32_t varIdx, + const std::string varName, + int32_t iOct_begin, + int64_t nbOcts) +{ + uint64_t num_bytes = 0; + int32_t nbCellsPerOct = dim == 2 ? m_bx * m_by : m_bx * m_by * m_bz; + + int32_t size_x = m_bx + 2 * m_gx; + int32_t size_y = m_by + 2 * m_gy; + [[maybe_unused]] int32_t size_z = m_bz + 2 * m_gz; + + // we don't want to capture this in kokkos lambda's + auto bx = m_bx; + auto by = m_by; + [[maybe_unused]] auto bz = m_bz; + + auto gx = m_gx; + auto gy = m_gy; + [[maybe_unused]] auto gz = m_gz; + + if (m_extract_block_ghost) + { + [[maybe_unused]] int32_t nbCellsPerOct_ghosted = + dim == 2 ? size_x * size_y : size_x * size_y * size_z; + + assertm(datah.num_cells() == nbCellsPerOct_ghosted, + "HDF5_Xdmf_Writer::write_quadrant_attribute wrong data size"); + } + else + { + assertm(datah.num_cells() == nbCellsPerOct, + "HDF5_Xdmf_Writer::write_quadrant_attribute wrong data size"); + } + + // we need to gather data corresponding to a given scalar variable + using DataArrayScalar = Kokkos::View; + + // remember that + // - data.extent(0) is the number of cells per octant + // - data.extent(1) is the number of scalar fields + // - data.extent(2) is the total number of oct in current MPI process + DataArrayScalar dataVar = + DataArrayScalar("scalar_array_for_hdf5_io", static_cast(nbCellsPerOct * nbOcts)); + + if (m_extract_block_ghost == false) + { + Kokkos::parallel_for( + "HDF5_Xdmf_Writer::write_quadrant_attribute", + Kokkos::RangePolicy(iOct_begin, iOct_begin + nbOcts), + KOKKOS_LAMBDA(int32_t iOct) { + for (int32_t iCell = 0; iCell < nbCellsPerOct; ++iCell) + dataVar(iCell + nbCellsPerOct * (iOct - iOct_begin)) = datah(iCell, varIdx, iOct); + }); + } + else + { + Kokkos::parallel_for( + "HDF5_Xdmf_Writer::write_quadrant_attribute", + Kokkos::RangePolicy(iOct_begin, iOct_begin + nbOcts), + KOKKOS_LAMBDA(int32_t iOct) { + + // the only reason of the following dummy code to be here, is that cuda nvcc compile doesn't + // support capturing variables inside the inner lambda inside a constexpr if + // TODO: remove ASAP nvcc is fixed +#ifdef __NVCC__ + [[maybe_unused]] int dummy = 0; + if (gx == 0 or gy == 0 or gz == 0 or bx == 0 or by == 0 or bz == 0 or size_x == 0 or + size_y == 0 or size_z == 0 or datah.num_cells() == 0 or dataVar.extent(0) == 0 or + varIdx == -1 or iOct_begin == 0) + dummy++; +#endif + + for (int32_t iCell = 0; iCell < nbCellsPerOct; ++iCell) + { + if constexpr (dim == 2) + { + // iCell = ix + iy*m_bx + const auto iy = iCell / bx; + const auto ix = iCell - iy * bx; + + const auto iCell2 = (ix + gx) + size_x * (iy + gy); + dataVar(iCell + nbCellsPerOct * (iOct - iOct_begin)) = datah(iCell2, varIdx, iOct); + } + else if constexpr (dim == 3) + { + // iCell = ix + iy*m_bx + iz*m_bx*m_by + const auto iz = iCell / bx / by; + const auto tmp = iCell - iz * bx * by; + const auto iy = tmp / bx; + const auto ix = tmp - iy * bx; + + auto iCell2 = (ix + gx) + size_x * (iy + gy) + size_x * size_y * (iz + gz); + dataVar(iCell + nbCellsPerOct * (iOct - iOct_begin)) = datah(iCell2, varIdx, iOct); + } + } + }); + } + + // make sure all kokkos kernels are done before actual writing + Kokkos::fence(); + + // actual data writing + num_bytes += write_cc_attribute(varName, dataVar.data(), 0, IO_CELL_SCALAR); + + return num_bytes; + +} // HDF5_Xdmf_Writer::write_quadrant_attribute + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer::write_quadrant_attribute(FaceDataArrayBlock_t face_data, + const std::string varName) +{ + uint64_t num_bytes = 0; + size_t num_elements_per_octant = static_cast(face_data.num_elements_per_octant()); + + auto face_data_storage = face_data.logical_view(); + auto face_data_storage_host = + Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, face_data_storage); + + // make sure all kokkos copy are done before actual writing + Kokkos::fence(); + + // actual data writing + num_bytes += + write_fc_attribute(varName, face_data_storage_host.data(), 0, num_elements_per_octant); + + return num_bytes; + +} // HDF5_Xdmf_Writer::write_quadrant_attribute - FaceDataArrayBlock_t + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer::write_quadrant_multi_mat_attribute( + DataArrayBlockMultiVarHost_t datah, + MaterialPresenceHost_t matph, + int32_t num_mats, + int32_t num_vars_per_mat, + int32_t mat_varIdx, + std::string varName, + int32_t iOct_begin, + int32_t nbOcts) +{ + varName = "_" + varName; + + uint64_t num_bytes = 0; + int32_t nbCellsPerOct = dim == 2 ? m_bx * m_by : m_bx * m_by * m_bz; + + int32_t size_x = m_bx + 2 * m_gx; + int32_t size_y = m_by + 2 * m_gy; + [[maybe_unused]] int32_t size_z = m_bz + 2 * m_gz; + + // we don't want to capture this in kokkos lambda's + auto bx = m_bx; + auto by = m_by; + [[maybe_unused]] auto bz = m_bz; + + auto gx = m_gx; + auto gy = m_gy; + [[maybe_unused]] auto gz = m_gz; + + if (m_extract_block_ghost) + { + [[maybe_unused]] int32_t nbCellsPerOct_ghosted = + dim == 2 ? size_x * size_y : size_x * size_y * size_z; + + assertm(datah.num_cells() == nbCellsPerOct_ghosted, + "HDF5_Xdmf_Writer::write_quadrant_multi_mat_attribute wrong data size"); + } + else + { + assertm(datah.num_cells() == nbCellsPerOct, + "HDF5_Xdmf_Writer::write_quadrant_multi_mat_attribute wrong data size"); + } + + // we need to gather data corresponding to a given scalar variable + using DataArrayScalar = Kokkos::View; + + // array used for each material + DataArrayScalar dataVar("scalar_array_for_hdf5_io", static_cast(nbCellsPerOct * nbOcts)); + + for (int32_t imat = 0; imat < num_mats; imat++) + { + if (m_extract_block_ghost == false) + { + Kokkos::parallel_for( + "HDF5_Xdmf_Writer::write_quadrant_multi_mat_attribute", + Kokkos::RangePolicy(iOct_begin, iOct_begin + nbOcts), + KOKKOS_LAMBDA(int32_t iOct) { + if (matph.get(iOct, imat)) + { + const auto mat_id = matph.material_index(iOct, imat); + const auto var_id = mat_id * num_vars_per_mat + mat_varIdx; + for (int32_t iCell = 0; iCell < nbCellsPerOct; ++iCell) + dataVar(iCell + nbCellsPerOct * (iOct - iOct_begin)) = datah(iCell, var_id, iOct); + } + else // We set the value to nan to indicates that the value "does not exist" + for (int32_t iCell = 0; iCell < nbCellsPerOct; ++iCell) + dataVar(iCell + nbCellsPerOct * (iOct - iOct_begin)) = nan(""); + }); + } + else + { + Kokkos::parallel_for( + "HDF5_Xdmf_Writer::write_quadrant_multi_mat_attribute", + Kokkos::RangePolicy(iOct_begin, iOct_begin + nbOcts), + KOKKOS_LAMBDA(int32_t iOct) { + + // the only reason of the following dummy code to be here, is that cuda nvcc compile doesn't + // support capturing variables inside the inner lambda inside a constexpr if + // TODO: remove ASAP nvcc is fixed +#ifdef __NVCC__ + [[maybe_unused]] int dummy = 0; + if (gx == 0 or gy == 0 or gz == 0 or bx == 0 or by == 0 or bz == 0 or size_x == 0 or + size_y == 0 or size_z == 0 or datah.num_cells() == 0 or dataVar.extent(0) == 0 or + mat_varIdx == -1 or iOct_begin == 0) + dummy++; +#endif + + if (matph.get(iOct, imat)) + { + const auto mat_id = matph.material_index(iOct, imat); + const auto var_id = mat_id * num_vars_per_mat + mat_varIdx; + + for (int32_t iCell = 0; iCell < nbCellsPerOct; ++iCell) + { + if constexpr (dim == 2) + { + // iCell = ix + iy*m_bx + const auto iy = iCell / bx; + const auto ix = iCell - iy * bx; + + const auto iCell2 = (ix + gx) + size_x * (iy + gy); + dataVar(iCell + nbCellsPerOct * (iOct - iOct_begin)) = datah(iCell2, var_id, iOct); + } + else if constexpr (dim == 3) + { + // iCell = ix + iy*m_bx + iz*m_bx*m_by + const auto iz = iCell / bx / by; + const auto tmp = iCell - iz * bx * by; + const auto iy = tmp / bx; + const auto ix = tmp - iy * bx; + + auto iCell2 = (ix + gx) + size_x * (iy + gy) + size_x * size_y * (iz + gz); + dataVar(iCell + nbCellsPerOct * (iOct - iOct_begin)) = datah(iCell2, var_id, iOct); + } + } + } + else // We set the value to nan to indicates that the value "does not exist" + for (int32_t iCell = 0; iCell < nbCellsPerOct; ++iCell) + dataVar(iCell + nbCellsPerOct * (iOct - iOct_begin)) = nan(""); + }); + } + + // make sure all kokkos kernels are done before actual writing + Kokkos::fence(); + + // actual data writing + num_bytes += + write_cc_attribute(std::to_string(imat) + varName, dataVar.data(), 0, IO_CELL_SCALAR); + } + + return num_bytes; +} + +// ======================================================= +// ======================================================= +template +template +uint64_t +HDF5_Xdmf_Writer::write_quadrant_leaf_scalar(host_view_t data_h, + const std::string dataName) +{ + static_assert(Kokkos::is_view::value); + static_assert(host_view_t::rank == 1); + static_assert(KokkosExt::is_accessible_from_host::value); + + assertm(m_writing_mode == LEAF_MODE, + "[HDF5_Xdmf_Writer::write_quadrant_leaf_scalar] writing mode must be LEAF_MODE here."); + + [[maybe_unused]] const auto nbOcts = m_mesh_map->get_amr_mesh_info().local_num_quadrants(); + + assertm(data_h.extent(0) >= static_cast(nbOcts), + "[HDF5_Xdmf_Writer::write_quadrant_leaf_scalar] input data is too short."); + + return write_cc_attribute(dataName, data_h.data(), 0, IO_CELL_SCALAR); + +} // HDF5_Xdmf_Writer::write_quadrant_leaf_scalar + +// ======================================================= +// ======================================================= +template +template +uint64_t +HDF5_Xdmf_Writer::io_hdf5_writev(HighFive::File & hdf5_file, + std::string dataset_prefix, + const std::string & dataset_name, + data_t * data_ptr, + std::vector const & dims, + std::vector const & count, + std::vector const & offset) +{ + // create dataset + HighFive::DataSet dataset = + hdf5_file.createDataSet(dataset_prefix + dataset_name, HighFive::DataSpace(dims)); + + auto xfer_props = HighFive::DataTransferProps{}; +#ifdef KALYPSSO_CORE_USE_MPI + xfer_props.add(HighFive::UseCollectiveIO{}); +#endif // KALYPSSO_CORE_USE_MPI + + dataset.select(offset, count).write_raw(data_ptr, xfer_props); +#ifdef KALYPSSO_CORE_USE_MPI + check_collective_io(xfer_props); +#endif // KALYPSSO_CORE_USE_MPI + + // Let's ensure that everything has been written do disk. + hdf5_file.flush(); + + // return the total number of bytes written in local MPI process + return sizeof(data_t) * std::reduce(count.begin(), count.end(), 1u, std::multiplies<>()); + +} // HDF5_Xdmf_Writer::io_hdf5_writev + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer::io_hdf5_write_coordinates() +{ + + // get up to date orchard keys + const auto orchard_keys = m_mesh_map->orchard_keys_host(); + + const auto brick_sizes = get_brick_sizes(m_config_map); + + // get periodicity property from config map + const auto is_brick_periodic = get_brick_periodicity(m_config_map); + + // get geometrical scaling factor + const auto scaling_factor = get_scaling_factor(m_config_map); + + // get domain lower left corner + const auto xyz_min = get_xyz_min(m_config_map); + + // do we have at least one non-periodic border ? + bool has_non_periodic_border = false; + for (size_t idim = 0; idim < dim; ++idim) + { + if (!is_brick_periodic[idim]) + has_non_periodic_border = true; + } + + uint64_t num_bytes = 0; + + if (m_write_block_data) + { + uint32_t nbNodesPerLeaf = dim == TWO_D + ? static_cast((m_bx + 1) * (m_by + 1)) + : static_cast((m_bx + 1) * (m_by + 1) * (m_bz + 1)); + uint64_t totalNumOfCoords = + 3 * m_local_num_quads * static_cast((m_bx + 1) * (m_by + 1) * (m_bz + 1)); + + std::vector data(totalNumOfCoords); + + /* + * construct the list of node coordinates (using either inside or outside quadrants) + */ + + uint32_t inode = 0; + for (int32_t iOct = 0; iOct < static_cast(m_local_num_quads); ++iOct) + { + auto iOct2 = m_use_outside_quads + ? iOct + m_mesh_map->get_amr_mesh_info().local_num_quadrants() + + m_mesh_map->get_amr_mesh_info().local_num_ghosts() + : iOct; + + const auto key = static_cast(orchard_keys(iOct2)); + + // compute cell length in vertex space + const auto dx_cell = compute_cell_length(key, m_bx); + + // get lower left corner coordinates (in p4est connectivity coordinates units) + auto lower_left_coord = orchard_key_to_vertex_coord(key, false); + + // if using outside quads, we need to shift coordinates, to actually get outside coordinates + // TODO: refactor code below, it is too tightly linked to + // MeshMap::compute_orchard_keys_view_host + if (m_use_outside_quads and has_non_periodic_border) + { + lower_left_coord = outside_key_to_vertex_coord(key, false, brick_sizes); + } // end if m_use_outside_quads + + // cell corner coordinates (in p4est connectivity coordinates units) + Kokkos::Array vertex_coords; + + if constexpr (dim == 2) + { + for (int32_t iy = 0; iy < m_by + 1; ++iy) + { + for (int32_t ix = 0; ix < m_bx + 1; ++ix) + { + vertex_coords[IX] = lower_left_coord[IX] + ix * dx_cell; + vertex_coords[IY] = lower_left_coord[IY] + iy * dx_cell; + + auto real_coords = + vertex_coord_to_real_space<2>(vertex_coords, scaling_factor, xyz_min); + + data[3 * inode + 0] = static_cast(real_coords[IX]); + data[3 * inode + 1] = static_cast(real_coords[IY]); + data[3 * inode + 2] = static_cast(0.0); + inode++; + } + } + } + else if constexpr (dim == 3) + { + for (int32_t iz = 0; iz < m_bz + 1; ++iz) + { + for (int32_t iy = 0; iy < m_by + 1; ++iy) + { + for (int32_t ix = 0; ix < m_bx + 1; ++ix) + { + vertex_coords[IX] = lower_left_coord[IX] + ix * dx_cell; + vertex_coords[IY] = lower_left_coord[IY] + iy * dx_cell; + vertex_coords[IZ] = lower_left_coord[IZ] + iz * dx_cell; + + auto real_coords = + vertex_coord_to_real_space<3>(vertex_coords, scaling_factor, xyz_min); + + data[3 * inode + 0] = static_cast(real_coords[IX]); + data[3 * inode + 1] = static_cast(real_coords[IY]); + data[3 * inode + 2] = static_cast(real_coords[IZ]); + inode++; + } + } + } + } + } // end for iOct + + // get prepared for writing hdf5 file + + std::vector dims{ 0, 0 }; + std::vector count{ 0, 0 }; + std::vector start{ 0, 0 }; + + // get the dimensions and offset of the node coordinates array + dims[0] = m_global_num_quads * nbNodesPerLeaf; + dims[1] = 3; + + count[0] = m_local_num_quads * nbNodesPerLeaf; + count[1] = 3; + + // get global index of the first octant of current mpi processor + start[0] = m_global_first_quad * nbNodesPerLeaf; + start[1] = 0; + + // write the node coordinates + num_bytes += io_hdf5_writev( + *m_hdf5_file, "/unstructured_mesh/", "coordinates", data.data(), dims, count, start); + } + else // cell-based AMR, one cell per quadrant + { + + // array with all local nodes coordinates + std::vector data(3 * m_local_num_nodes); + + /* + * construct the list of node coordinates + */ + + uint32_t inode = 0; + for (int32_t iOct = 0; iOct < static_cast(m_local_num_quads); ++iOct) + { + + auto iOct2 = m_use_outside_quads + ? iOct + m_mesh_map->get_amr_mesh_info().local_num_quadrants() + + m_mesh_map->get_amr_mesh_info().local_num_ghosts() + : iOct; + + const auto key = static_cast(orchard_keys(iOct2)); + + // compute cell length in vertex space + const auto dx_cell = compute_cell_length(key, 1); + + // get lower left corner coordinates + auto lower_left_coord = orchard_key_to_vertex_coord(key, false); + + // if using outside quads, we need to shift coordinates, to actually get outside coordinates + // TODO: refactor code below, it is too tightly linked to + // MeshMap::compute_orchard_keys_view_host + if (m_use_outside_quads and has_non_periodic_border) + { + lower_left_coord = outside_key_to_vertex_coord(key, false, brick_sizes); + } // end if m_use_outside_quads + + // cell corner coordinates (in p4est connectivity coordinates units) + Kokkos::Array vertex_coords; + + if constexpr (dim == 2) + { + for (uint8_t iy = 0; iy < 2; ++iy) + { + for (uint8_t ix = 0; ix < 2; ++ix) + { + vertex_coords[IX] = lower_left_coord[IX] + ix * dx_cell; + vertex_coords[IY] = lower_left_coord[IY] + iy * dx_cell; + + auto real_coords = + vertex_coord_to_real_space<2>(vertex_coords, scaling_factor, xyz_min); + + data[3 * inode + 0] = static_cast(real_coords[IX]); + data[3 * inode + 1] = static_cast(real_coords[IY]); + data[3 * inode + 2] = static_cast(0.0); + inode++; + } + } + } + else if constexpr (dim == 3) + { + for (uint8_t iz = 0; iz < 2; ++iz) + { + for (uint8_t iy = 0; iy < 2; ++iy) + { + for (uint8_t ix = 0; ix < 2; ++ix) + { + vertex_coords[IX] = lower_left_coord[IX] + ix * dx_cell; + vertex_coords[IY] = lower_left_coord[IY] + iy * dx_cell; + vertex_coords[IZ] = lower_left_coord[IZ] + iz * dx_cell; + + auto real_coords = + vertex_coord_to_real_space<3>(vertex_coords, scaling_factor, xyz_min); + + data[3 * inode + 0] = static_cast(real_coords[IX]); + data[3 * inode + 1] = static_cast(real_coords[IY]); + data[3 * inode + 2] = static_cast(real_coords[IZ]); + inode++; + } + } + } + } + } // end for iOct + + // get prepared for writing hdf5 file + + std::vector dims{ 0, 0 }; + std::vector count{ 0, 0 }; + std::vector start{ 0, 0 }; + + // get the dimensions and offset of the node coordinates array + dims[0] = m_global_num_nodes; + dims[1] = 3; + + count[0] = m_local_num_nodes; + count[1] = 3; + + // get global index of the first octant of current mpi processor + start[0] = m_nbNodesPerCell * m_global_first_quad; + start[1] = 0; + + // write the node coordinates + num_bytes += io_hdf5_writev( + *m_hdf5_file, "/unstructured_mesh/", "coordinates", data.data(), dims, count, start); + + } // end one cell per quadrant + + return num_bytes; + +} // HDF5_Xdmf_Writer::io_hdf5_write_coordinates + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer::io_hdf5_write_connectivity() +{ + + uint64_t num_bytes = 0; + + if (m_write_block_data) + { + + int nbNodesPerLeaf = + dim == TWO_D ? (m_bx + 1) * (m_by + 1) : (m_bx + 1) * (m_by + 1) * (m_bz + 1); + + // TODO: clarification: should/can data type used here be int64_t ? + // - from xdmf specification it is not clear + // - paraview seems ok to read int64_t + std::vector data(m_local_num_quads * m_nbCellsPerLeaf * m_nbNodesPerCell); + + // first node of current MPI process + int64_t globalNodeOffset = nbNodesPerLeaf * static_cast(m_global_first_quad); + + const auto nbConnectivityPerLeaf = m_nbCellsPerLeaf * m_nbNodesPerCell; + + // get connectivity data + for (size_t iLeaf = 0; iLeaf < m_local_num_quads; ++iLeaf) + { + int64_t localNodeOffset = nbNodesPerLeaf * static_cast(iLeaf); + + // sweep subcells + int nz = dim == 2 ? 1 : m_bz; + for (int jz = 0; jz < nz; ++jz) + { + for (int jy = 0; jy < m_by; ++jy) + { + for (int jx = 0; jx < m_bx; ++jx) + { + + int64_t nodeOffset = globalNodeOffset + localNodeOffset; + + size_t idx = nbConnectivityPerLeaf * iLeaf + + static_cast(subCellIndex(jx, jy, jz)) * m_nbNodesPerCell; + + data[idx + 0] = nodeOffset + subNodeIndex(jx, jy, jz); + data[idx + 1] = nodeOffset + subNodeIndex(jx + 1, jy, jz); + data[idx + 2] = nodeOffset + subNodeIndex(jx + 1, jy + 1, jz); + data[idx + 3] = nodeOffset + subNodeIndex(jx, jy + 1, jz); + + if (dim == THREE_D) + { + data[idx + 4] = nodeOffset + subNodeIndex(jx, jy, jz + 1); + data[idx + 5] = nodeOffset + subNodeIndex(jx + 1, jy, jz + 1); + data[idx + 6] = nodeOffset + subNodeIndex(jx + 1, jy + 1, jz + 1); + data[idx + 7] = nodeOffset + subNodeIndex(jx, jy + 1, jz + 1); + } + + } // end for jx + } // end for jy + } // end for jz + + } // end for iLeaf + + // now write connectivity with hdf5 + + std::vector dims{ 0, 0 }; + std::vector count{ 0, 0 }; + std::vector start{ 0, 0 }; + + // get the dimensions and offsets for each connectivity array + dims[0] = m_global_num_quads * m_nbCellsPerLeaf; + dims[1] = m_nbNodesPerCell; + + count[0] = m_local_num_quads * m_nbCellsPerLeaf; + count[1] = m_nbNodesPerCell; + + start[0] = m_global_first_quad * m_nbCellsPerLeaf; + start[1] = 0; + + // write cell-to-cell connectivity + num_bytes += io_hdf5_writev( + *m_hdf5_file, "/unstructured_mesh/", "connectivity", data.data(), dims, count, start); + } + else + { // regular AMR mesh, i.e. one cell per quad/oct + + uint32_t node[8] = { 0, 1, 3, 2, 0, 0, 0, 0 }; + + if (dim == 3) + { + node[4] = 4; + node[5] = 5; + node[6] = 7; + node[7] = 6; + } + + std::vector data(m_local_num_quads * m_nbNodesPerCell); + + uint32_t in = 0; + + // get connectivity data + for (uint32_t i = 0; i < m_local_num_quads; ++i) + { + + for (uint32_t j = 0; j < m_nbNodesPerCell; ++j) + { + size_t idx = m_nbNodesPerCell * i + j; + + data[idx] = static_cast(m_nbNodesPerCell * m_global_first_quad + in + node[j]); + } // end for j + + in += m_nbNodesPerCell; + + } // end for i + + // now write connectivity with hdf5 + + std::vector dims{ 0, 0 }; + std::vector count{ 0, 0 }; + std::vector start{ 0, 0 }; + + // get the dimensions and offsets for each connectivity array + dims[0] = m_global_num_quads; + dims[1] = m_nbNodesPerCell; + + count[0] = m_local_num_quads; + count[1] = m_nbNodesPerCell; + + start[0] = m_global_first_quad; + start[1] = 0; + + // write cell-to-cell connectivity + num_bytes += io_hdf5_writev( + *m_hdf5_file, "/unstructured_mesh/", "connectivity", data.data(), dims, count, start); + } + + return num_bytes; + +} // HDF5_Xdmf_Writer::io_hdf5_write_connectivity + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer::io_hdf5_write_level() +{ + + if (!this->m_write_level) + { + return 0; + } + + const auto orchard_keys = m_mesh_map->orchard_keys_host(); + + const uint32_t nbData = m_local_num_quads * m_nbCellsPerLeaf; + + std::vector data(nbData); + + size_t i = 0; + for (int32_t iOct = 0; iOct < static_cast(m_local_num_quads); ++iOct) + { + auto iOct2 = m_use_outside_quads + ? iOct + m_mesh_map->get_amr_mesh_info().local_num_quadrants() + + m_mesh_map->get_amr_mesh_info().local_num_ghosts() + : iOct; + + auto level = orchard_key_t::level(orchard_keys(iOct2)); + + for (uint32_t j = 0; j < m_nbCellsPerLeaf; ++j) + { + data[i] = level; + ++i; + } + } // end for iOct + + return write_cc_attribute("level", data.data(), 0, IO_CELL_SCALAR); + +} // HDF5_Xdmf_Writer::io_hdf5_write_level + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer::io_hdf5_write_tree() +{ + + if (!this->m_write_tree) + { + return 0; + } + + const auto orchard_keys = m_mesh_map->orchard_keys_host(); + + const auto brick_sizes = get_brick_sizes(m_config_map); + + // tree linear index to xyz converter + BrickConnectivityData convert(brick_sizes); + + uint32_t nbData = m_local_num_quads * m_nbCellsPerLeaf; + + std::vector data(nbData); + + size_t i = 0; + + auto get_treeid = [&](uint64_t key) { + auto tree_coords = orchard_key_t::get_tree_coords(key); + + if constexpr (dim == 2) + return convert.treeId(tree_coords[0], tree_coords[1]); + else if constexpr (dim == 3) + return convert.treeId(tree_coords[0], tree_coords[1], tree_coords[2]); + }; + + + for (int32_t iOct = 0; iOct < static_cast(m_local_num_quads); ++iOct) + { + + const auto iOct2 = m_use_outside_quads + ? iOct + m_mesh_map->get_amr_mesh_info().local_num_quadrants() + + m_mesh_map->get_amr_mesh_info().local_num_ghosts() + : iOct; + + const auto treeid = get_treeid(orchard_keys(iOct2)); + + for (uint32_t j = 0; j < m_nbCellsPerLeaf; ++j) + { + data[i] = treeid; + ++i; + } + } // end for iOct + + return write_cc_attribute("treeid", data.data(), 0, IO_CELL_SCALAR); + +} // HDF5_Xdmf_Writer::io_hdf5_write_tree + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer::io_hdf5_write_iOct() +{ + + if (!this->m_write_iOct) + { + return 0; + } + + uint32_t nbData = m_local_num_quads * m_nbCellsPerLeaf; + + std::vector data(nbData); + + // gather level for each local quadrant + size_t i = 0; + for (int32_t iOct = 0; iOct < static_cast(m_local_num_quads); ++iOct) + { + + auto iOct2 = m_use_outside_quads + ? iOct + m_mesh_map->get_amr_mesh_info().local_num_quadrants() + + m_mesh_map->get_amr_mesh_info().local_num_ghosts() + : iOct; + + for (uint32_t j = 0; j < m_nbCellsPerLeaf; ++j) + { + data[i] = static_cast(iOct2); + ++i; + } + } + + return write_cc_attribute("iOct", data.data(), 0, IO_CELL_SCALAR); + +} // HDF5_Xdmf_Writer::io_hdf5_write_iOct + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer::io_hdf5_write_rank() +{ + + if (!this->m_write_rank) + { + return 0; + } + + uint32_t nbData = m_local_num_quads * m_nbCellsPerLeaf; + + std::vector data(nbData); + + // gather rank for each local quadrant + for (size_t i = 0; i < nbData; ++i) + { + data[i] = m_par_env.rank(); + } + + return write_cc_attribute("rank", data.data(), 0, IO_CELL_SCALAR); + +} // HDF5_Xdmf_Writer::io_hdf5_write_rank + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer::io_hdf5_write_reduced_orchard_key(orchard_key_dump_type_t type) +{ + const auto orchard_keys = m_mesh_map->orchard_keys_host(); + + const uint32_t nbData = m_local_num_quads * m_nbCellsPerLeaf; + + std::vector data(nbData); + + // check if connectivity is either "unit" or "brick" + auto conn_name = m_config_map.getString("amr", "connectivity", "invalid_connectivity"); + + if (conn_name == "brick") + { + + const auto brick_sizes = get_brick_sizes(m_config_map); + + // tree linear index to xyz converter + BrickConnectivityData convert(brick_sizes); + + auto max_level = m_config_map.getInteger("amr", "level_max", 0); + + size_t icell = 0; + for (int32_t iOct = 0; iOct < static_cast(m_local_num_quads); ++iOct) + { + auto iOct2 = m_use_outside_quads + ? iOct + m_mesh_map->get_amr_mesh_info().local_num_quadrants() + + m_mesh_map->get_amr_mesh_info().local_num_ghosts() + : iOct; + + const auto key = orchard_keys(iOct2); + + if (type == FULL) + { + for (size_t i = 0; i < m_nbCellsPerLeaf; ++i) + { + data[icell] = key; + icell++; + } + } + else + { + // reduced key + auto octCoords = orchard_key_t::get_octant_coords(key); + auto treeCoords = orchard_key_t::get_tree_coords(key); + + // shift bit to reduced to the effective number of levels allowed in input parameters + octCoords[IX] >>= (orchard_key_t::MAX_LEVEL - max_level); + octCoords[IY] >>= (orchard_key_t::MAX_LEVEL - max_level); + if constexpr (dim == 3) + octCoords[IZ] >>= (orchard_key_t::MAX_LEVEL - max_level); + + // re-encode key using only tree and octant information + // set level to zero (reduced key, no level involved) + uint64_t orchard_key = orchard_key_t::encode_orchard(treeCoords, octCoords, 0); + uint64_t morton_oct = orchard_key_t::morton_octant(orchard_key); + uint64_t morton_key = orchard_key_t::morton_tree(orchard_key); + uint64_t reduced_key = + type == REDUCED_GLOBAL + ? morton_oct + (morton_key << (dim * static_cast(max_level))) + : morton_oct; + + for (size_t i = 0; i < m_nbCellsPerLeaf; ++i) + { + data[icell] = reduced_key; // morton_oct; // orchard_key; + icell++; + } + } + + } // end for iOct + } + else + { + // to be clarified, we could defined an orchard key, but the meaning is unclear + for (uint32_t i = 0; i < nbData; ++i) + { + data[i] = 0; + } + } + + std::string attribute_str = type == FULL ? "full_orchard_key" + : type == REDUCED_GLOBAL ? "reduced_orchard_key_global" + : "reduced_orchard_key_local"; + + return write_cc_attribute(attribute_str, data.data(), 0, IO_CELL_SCALAR); + + +} // HDF5_Xdmf_Writer::io_hdf5_write_reduced_orchard_key + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer::io_hdf5_write_at_domain_border() +{ + + if (!this->m_write_at_domain_border) + { + return 0; + } + + const auto orchard_keys = m_mesh_map->orchard_keys_host(); + + const uint32_t nbData = m_local_num_quads * m_nbCellsPerLeaf; + + std::vector data(nbData); + size_t icell = 0; + + // check if connectivity is either "unit" or "brick" + auto conn_name = m_config_map.getString("amr", "connectivity", "invalid_connectivity"); + + if (conn_name == "brick") + { + // gather orchard key for each local quadrant / cell + + // brick sizes + const auto brick_sizes = get_brick_sizes(m_config_map); + + // tree linear index to xyz converter + BrickConnectivityData convert(brick_sizes); + + for (int32_t iOct = 0; iOct < static_cast(m_local_num_quads); ++iOct) + { + auto iOct2 = m_use_outside_quads + ? iOct + m_mesh_map->get_amr_mesh_info().local_num_quadrants() + + m_mesh_map->get_amr_mesh_info().local_num_ghosts() + : iOct; + + auto orchard_key = orchard_keys(iOct2); + + for (size_t i = 0; i < m_nbCellsPerLeaf; ++i) + { + auto atDomainBorder = static_cast( + orchard_key_t::is_at_any_domain_border(orchard_key, brick_sizes)); + data[icell] = atDomainBorder; + icell++; + } + } // end for iOct + } + else + { + // not defined, so use "zero" as default value + // writing this attribute should be disabled in config_map + for (size_t i = 0; i < nbData; ++i) + { + data[i] = 0; + } + } + + return write_cc_attribute("at_domain_border", data.data(), 0, IO_CELL_SCALAR); + +} // HDF5_Xdmf_Writer::io_hdf5_write_at_domain_border + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer::io_hdf5_write_at_tree_border() +{ + + if (!this->m_write_at_tree_border) + { + return 0; + } + + const auto orchard_keys = m_mesh_map->orchard_keys_host(); + + const uint32_t nbData = m_local_num_quads * m_nbCellsPerLeaf; + + std::vector data(nbData); + size_t icell = 0; + + // check if connectivity is either "unit" or "brick" + auto conn_name = m_config_map.getString("amr", "connectivity", "invalid_connectivity"); + + if (conn_name == "brick") + { + // gather orchard for each local quadrant / cell + + // brick sizes + const auto brick_sizes = get_brick_sizes(m_config_map); + + // tree linear index to xyz converter + BrickConnectivityData convert(brick_sizes); + + // loop over all octants + for (int32_t iOct = 0; iOct < static_cast(m_local_num_quads); ++iOct) + { + auto iOct2 = m_use_outside_quads + ? iOct + m_mesh_map->get_amr_mesh_info().local_num_quadrants() + + m_mesh_map->get_amr_mesh_info().local_num_ghosts() + : iOct; + + auto orchard_key = orchard_keys(iOct2); + + auto at_any_tree_border = + static_cast(orchard_key_t::is_at_any_tree_border(orchard_key)); + + auto at_any_tree_corner = + static_cast(orchard_key_t::is_at_any_tree_corner(orchard_key)); + + uint32_t at_any_tree_edge = 0; + if constexpr (dim == 3) + at_any_tree_edge = + static_cast(orchard_key_t::is_at_any_tree_edge(orchard_key)); + + for (size_t i = 0; i < m_nbCellsPerLeaf; ++i) + { + data[icell] = at_any_tree_border + at_any_tree_corner + at_any_tree_edge; + icell++; + } + + } // end for iOct + } + else + { + // not defined, so use "zero" as default value + // writing this attribute should be disabled in config_map + for (uint32_t i = 0; i < nbData; ++i) + { + data[i] = 0; + } + } + + return write_cc_attribute("at_tree_border", data.data(), 0, IO_CELL_SCALAR); + +} // HDF5_Xdmf_Writer::io_hdf5_write_at_tree_border + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer::io_hdf5_write_is_outside() +{ + + if (!this->m_write_is_outside) + { + return 0; + } + + const auto orchard_keys = m_mesh_map->orchard_keys_host(); + + const uint32_t nbData = m_local_num_quads * m_nbCellsPerLeaf; + + std::vector data(nbData); + size_t icell = 0; + + // check if connectivity is either "unit" or "brick" + auto conn_name = m_config_map.getString("amr", "connectivity", "invalid_connectivity"); + + if (conn_name == "brick") + { + // gather orchard for each local quadrant / cell + + // brick sizes + const auto brick_sizes = get_brick_sizes(m_config_map); + + // tree linear index to xyz converter + BrickConnectivityData convert(brick_sizes); + + // loop over all octants + for (int32_t iOct = 0; iOct < static_cast(m_local_num_quads); ++iOct) + { + auto iOct2 = m_use_outside_quads + ? iOct + m_mesh_map->get_amr_mesh_info().local_num_quadrants() + + m_mesh_map->get_amr_mesh_info().local_num_ghosts() + : iOct; + + auto orchard_key = orchard_keys(iOct2); + + for (size_t i = 0; i < m_nbCellsPerLeaf; ++i) + { + data[icell] = static_cast(orchard_key_t::is_outside(orchard_key)); + icell++; + } + + } // end for iOct + } + else + { + // not defined, so use "zero" as default value + // writing this attribute should be disabled in config_map + for (size_t i = 0; i < nbData; ++i) + { + data[i] = 0; + } + } + + return write_cc_attribute("is_outside", data.data(), 0, IO_CELL_SCALAR); + +} // HDF5_Xdmf_Writer::io_hdf5_write_is_outside + +// ======================================================= +// ======================================================= +// Private members +// ======================================================= +// ======================================================= + +// ======================================================= +// ======================================================= +template +void +HDF5_Xdmf_Writer::io_xdmf_write_main_header() +{ + + FILE * fd = m_main_xdmf_file; + + fprintf(fd, "\n"); + fprintf(fd, "\n"); + fprintf(fd, + "\n"); + fprintf(fd, " \n"); + fprintf(fd, + " \n"); + +} // HDF5_Xdmf_Writer::io_xdmf_write_main_header + +// ======================================================= +// ======================================================= +template +void +HDF5_Xdmf_Writer::io_xdmf_write_header(double time) +{ + + FILE * fd = this->m_xdmf_file; + size_t global_num_cells = this->m_global_num_quads; + size_t global_num_nodes = global_num_cells * m_nbNodesPerCell; + + if (m_write_block_data) + { + + global_num_cells *= m_nbCellsPerLeaf; + + int nbNodesPerLeaf = + dim == TWO_D ? (m_bx + 1) * (m_by + 1) : (m_bx + 1) * (m_by + 1) * (m_bz + 1); + + global_num_nodes = this->m_global_num_quads * static_cast(nbNodesPerLeaf); + } + + const std::string IO_TOPOLOGY_TYPE = dim == TWO_D ? IO_TOPOLOGY_TYPE_2D : IO_TOPOLOGY_TYPE_3D; + + fprintf(fd, "\n"); + fprintf(fd, "\n"); + fprintf(fd, "\n"); + fprintf(fd, " \n"); + fprintf(fd, " \n", this->m_basename.c_str()); + fprintf(fd, " \n"); + fprintf(fd, " \n"); + fprintf(fd, "\n"); + +} // HDF5_Xdmf_Writer::io_xdmf_write_main_footer + +// ======================================================= +// ======================================================= +template +void +HDF5_Xdmf_Writer::io_xdmf_write_footer() +{ + + FILE * fd = this->m_xdmf_file; + + fprintf(fd, " \n"); + fprintf(fd, " \n"); + fprintf(fd, "\n"); + +} // HDF5_Xdmf_Writer::io_xdmf_write_footer + +// ======================================================= +// ======================================================= +template +int32_t +HDF5_Xdmf_Writer::subCellIndex(int jx, int jy, int jz) +{ + + return jx + m_bx * (jy + m_by * jz); + +} // HDF5_Xdmf_Writer::subCellIndex + +// ======================================================= +// ======================================================= +template +int32_t +HDF5_Xdmf_Writer::subNodeIndex(int jx, int jy, int jz) +{ + + return jx + (m_bx + 1) * (jy + (m_by + 1) * jz); + +} // HDF5_Xdmf_Writer::subNodeIndex + +// ======================================================= +// ======================================================= +template +void +HDF5_Xdmf_Writer::set_block_mode() +{ + + m_writing_mode = BLOCK_MODE; + m_bx = m_config_map.getInteger("amr", "bx", 1); + m_by = m_config_map.getInteger("amr", "by", 1); + m_bz = dim == 2 ? 1 : m_config_map.getInteger("amr", "bz", 1); + + m_write_block_data = true; + m_extract_block_ghost = false; + m_nbCellsPerLeaf = + dim == 2 ? static_cast(m_bx * m_by) : static_cast(m_bx * m_by * m_bz); + +} // HDF5_Xdmf_Writer::set_block_mode() + +// ======================================================= +// ======================================================= +template +void +HDF5_Xdmf_Writer::set_block_mode(block_size_t block_size, + block_size_t ghost_width) +{ + + m_writing_mode = BLOCK_MODE; + m_bx = block_size[0]; + m_by = block_size[1]; + m_bz = dim == 3 ? block_size[2] : 1; + + m_write_block_data = true; + m_extract_block_ghost = true; + m_nbCellsPerLeaf = + dim == 2 ? static_cast(m_bx * m_by) : static_cast(m_bx * m_by * m_bz); + + m_gx = ghost_width[0]; + m_gy = ghost_width[1]; + m_gz = dim == 3 ? ghost_width[2] : 0; + +} // HDF5_Xdmf_Writer::set_block_mode() + +// ======================================================= +// ======================================================= +template +void +HDF5_Xdmf_Writer::set_leaf_mode() +{ + + m_writing_mode = LEAF_MODE; + m_bx = 1; + m_by = 1; + m_bz = 1; + m_write_block_data = false; + m_nbCellsPerLeaf = + dim == 2 ? static_cast(m_bx * m_by) : static_cast(m_bx * m_by * m_bz); + +} // HDF5_Xdmf_Writer::set_block_mode() + +// ===================================================================== +// ===================================================================== + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_HDF5_XDMF_WRITER_H_ diff --git a/src/kalypsso/core/HDF5_Xdmf_Writer_legacy.h b/src/kalypsso/core/HDF5_Xdmf_Writer_legacy.h new file mode 100644 index 0000000..bf4d9cc --- /dev/null +++ b/src/kalypsso/core/HDF5_Xdmf_Writer_legacy.h @@ -0,0 +1,1919 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file HDF5_Xdmf_Writer_legacy.h + */ +#ifndef KALYPSSO_CORE_HDF5_XDMF_WRITER_LEGACY_H_ +#define KALYPSSO_CORE_HDF5_XDMF_WRITER_LEGACY_H_ + +#include +#include + +#include +#include +#include // for DataArray, DataArrayHost + +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#include // for std::cerr + +namespace kalypsso +{ + +// ===================================================================== +// ===================================================================== +/** + * \brief HDF5 XDMF writer (legacy version). + * + * prefer using HDF5_Xdmf_Writer which is additionally able to write outside cell data. + * + * \tparam dim = 2 or 3 + */ +template +class HDF5_Xdmf_Writer_legacy +{ + +public: + using DataArrayLeafHost_t = DataArrayLeafHost; + using DataArrayBlockHost_t = DataArrayBlock; + + using p4est_t = typename p4est::Wrapper; + + enum orchard_key_dump_type_t : uint32_t + { + REDUCED_LOCAL = 0, + REDUCED_GLOBAL = 1, + FULL = 2 + }; + + /** + * \brief Constructor. + * + * The writer is has no opened files by default except a main xmf file + * that contains a collection of all the files that are to be written + * next as a `Temporal` Collection. + * + * \param [in] forest p4est main object + * \param [in] geometry p4est geometry (can be nullptr) + * \param [in] ConfigMap + * \param [in] write_xdmf_main is a boolean flag to enable/disable write the main xdmf file + * + */ + HDF5_Xdmf_Writer_legacy(forest_t * forest, + geometry_t * geom, + const ConfigMap & config_map, + block_size_t block_size, + coord_t start_index, + std::string xdmf_main = "_main"); + + //! destructor + virtual ~HDF5_Xdmf_Writer_legacy(); + + /** + * To be called as often as the mesh changes (refine/coarsen/balance/repartition). + * + * Recompute local (current MPI proc) and global (all MPI proc) number + * of quadrants (forest leaves) and mesh block nodes. + */ + void + update_mesh_info(); + + /** + * \brief Open the HDF5 and XMF files for writing. + * + * Also includes this file inside the main xmf file, if needed. + */ + void + open(std::string basename, std::string outDir); + + /** + * \brief Close the HDF5 and XMF files. + * + * Also closes the main xmf file, if it was opened. + */ + void + close(); + + /** + * \brief Write the header for the XMF and HDF5 files. + * + * The header includes the node information, connectivity information and + * the treeid, level or mpi rank for each quadrant, if required. + * + * In the case of the XMF file, this will defined the topology and geometry + * of the mesh and point to the relevant fields in the HDF5 file. + */ + uint64_t + write_header(double time); + + /** + * \brief Write the XMF footer. + * + * Closes all the XML tags. + */ + int + write_footer(); + + + /** + * \brief Write a node-centered or cell-centered attribute. + * + * \param [in] name The name of the attribute. + * \param [in] data The data to be written. + * \param [in] dimData In the case of a vector, this is the dimension of each + * element in the vector field. + * \param [in] ftype The type of the attribute. See supported types in + * the io_attribute_type_t enum. + * \param [in] dtype The type of the data we are writing. This is given as a + * native HDF5 type. See the types defined in the H5Tpublic.h header. + * \param [in] wtype The type of the data written to the file. The + * conversion between the data type and the written data is handled + * by HDF5. + */ + template + uint64_t + write_attribute(const std::string & name, + data_t * data, + size_t dimData, + io_attribute_type_t ftype); + + /** + * \brief Write all cell-centered scalar attributes. + * + * \param [in] datah a host Kokkos View with the user data + * \param [in] varIdx scalar index identify scalar field to write + * \param [in] varName variable name + */ + uint64_t + write_quadrant_attribute(DataArrayLeafHost_t datah, int32_t varIdx, const std::string varName); + + /** + * \brief Write all cell-centered scalar attributes when block amr is enabled. + * + * \param [in] datah a host Kokkos View with the user data (block data) + * \param [in] varIdx scalar index identify scalar field to write + * \param [in] varName variable name + */ + uint64_t + write_quadrant_attribute(DataArrayBlockHost_t datah, int32_t varIdx, const std::string varName); + + forest_t * m_forest; //!< p4est main object + geometry_t * m_geom; //!< p4est geometry + std::string m_basename; //!< the base name of the two files + + const ConfigMap & m_config_map; + + bool m_write_mesh_info; //!< write mesh info (oct level, mpi proc, ...) + bool m_write_tree; //!< default write_mesh_info (false) + bool m_write_level; //!< default write_mesh_info (false) + bool m_write_rank; //!< default write_mesh_info (false) + bool m_write_reduced_orchard_key_global; //!< default write_mesh_info (false) + bool m_write_reduced_orchard_key_local; //!< default write_mesh_info (false) + bool m_write_full_orchard_key; //!< default write_mesh_info (false) + bool m_write_at_domain_border; //!< default write_mesh_info (false) + bool m_write_at_tree_border; //!< default write_mesh_info (false) + + bool m_write_block_data; //!< if true, expect a DataArrayBlock object + + //! sizes of the block of cells per octant + block_size_t m_block_size; + + //! start index (used when user wants to extract a block from the original data block of cells) + coord_t m_istart; + + uint8_t m_nbNodesPerCell; //!< 4 (2d) or 8 (3d) + uint32_t m_nbCellsPerLeaf; //!< only useful for block amr + + HighFive::File * m_hdf5_file; //!< HDF5 file descriptor + FILE * m_xdmf_file; //!< XDMF file descriptor + FILE * m_main_xdmf_file; //!< main XDMF file descriptor + + bool m_write_iOct; //!< default false + + int m_mpiRank; //!< mpi rank (as stored in p4est) + + // store information about nodes for writing node data + + uint64_t m_global_num_nodes; //!< global accumulated (all MPI proc) number of nodes + uint32_t m_local_num_nodes; //!< local (current MPI proc) number of nodes + uint64_t m_global_num_quads; //!< global accumulated (all MPI proc) number of quads/octs + uint32_t m_local_num_quads; //!< local (current MPI proc) number of quads/octs + +private: + /* + * XMDF utilities. + */ + + /** + * \brief Write the header of the main XMF file. + */ + void + io_xdmf_write_main_header(); + + /** + * \brief Write the XMF header information: topology and geometry. + */ + void + io_xdmf_write_header(double time); + + /** + * \brief Write the include for the current file. + */ + void + io_xdmf_write_main_include(const std::string & name); + + /** + * \brief Write information about an attribute. + * + * \param [in] fd file descriptor for xmf file + * \param [in] basename The basename. + * \param [in] name The name of the attribute. + * \param [in] type The type. + * \param [in] dims The dimensions of the attribute. If it is a scalar, dims[1] + * will be ignored. + */ + void + io_xdmf_write_attribute(const std::string & name, + const std::string & number_type, + io_attribute_type_t type, + std::vector const & dims); + + /** + * \brief Close the remaining tags for the main file. + */ + void + io_xdmf_write_main_footer(); + + /** + * \brief Close all remaining tags. + * + * \param[in] fd xmff file descriptor + */ + void + io_xdmf_write_footer(); + + /* + * HDF5 utilities. + */ + /** + * \brief Write a given dataset into the HDF5 file. + * + * \param [in] fd An open file descriptor to a HDF5 file. + * \param [in] name The name of the dataset we are writing. + * \param [in] data The data to write. + * \param [in] dtype_id The native HDF5 type of the given data. + * \param [in] wtype_id The native HDF5 type of the written data. + * \param [in] rank The rank of the dataset. 1 if it is a vector, 2 for a matrix. + * \param [in] dims The global dimensions of the dataset. + * \param [in] count The local dimensions of the dataset. + * \param [in] start The offset of the local data with respect to the global + * positioning. + * + * \return total number of bytes written. + * + * \sa H5TPublic.h + */ + template + uint64_t + io_hdf5_writev(HighFive::File & hdf5_file, + const std::string & dataset_name, + data_t * data_ptr, + std::vector const & dims, + std::vector const & count, + std::vector const & offset); + + /** + * \brief Compute and write the coordinates of all the mesh nodes. + * + * Mesh coordinates is only necessary if one wants to load data into paraview as a VTU + * (VTK Unstructured mesh) + * + * \return number of bytes written + */ + uint64_t + io_hdf5_write_coordinates(); + + /** + * \brief Compute and write the connectivity information for each quadrant. + * \return number of bytes written + */ + uint64_t + io_hdf5_write_connectivity(); + + /** + * \brief Compute and write the tree id for each quadrant. + * \return number of bytes written + */ + uint64_t + io_hdf5_write_tree(); + + /** + * \brief Compute and write the level for each quadrant. + * \return number of bytes written + */ + uint64_t + io_hdf5_write_level(); + + /** + * \brief Write local quadrant id (local to current MPI process). + * Purely for debug purpose. + * \return number of bytes written + */ + uint64_t + io_hdf5_write_iOct(); + + /** + * \brief Compute and write the MPI rank for each quadrant. + * \return number of bytes written + */ + uint64_t + io_hdf5_write_rank(); + + /** + * \brief Compute and write the reduced orchard key for each quadrant. + * + * reduced orchard key is obtain by considering only the octant part (discard level and tree), + * and then right shift the bits by level_max (to keep only most significant bits) + * + * + * \param[in] global if global is true, compute reduced key with tree, else without tree + * \return number of bytes written + */ + uint64_t + io_hdf5_write_reduced_orchard_key(orchard_key_dump_type_t type); + + /** + * \brief Compute and write for each quadrant a boolean value indicating if quadrant touches + * external border. + * \return number of bytes written + */ + uint64_t + io_hdf5_write_at_domain_border(); + + /** + * \brief Compute and write for each quadrant a boolean value indicating if quadrant touches + * tree border. + * \return number of bytes written + */ + uint64_t + io_hdf5_write_at_tree_border(); + + //! get cell linear index from (jx,jy,jz) + int32_t + subCellIndex(int jx, int jy, int jz); + + //! get node linear index from (jx,jy,jz) + int32_t + subNodeIndex(int jx, int jy, int jz); + +public: + //! change block size for writing DataArrayBlock. + void + set_block_mode(block_size_t block_size, coord_t start_index); + + //! switch to leaf mode for writing DataArrayLeaf. + //! one cell per (octree) leaf. + void + set_leaf_mode(); + + //! Configure if we want to write mesh info. + //! + //! \param[in] value, if true we write all mesh info if not specified in input parameter + //! \note if write_mesh_info is set in input parameter it will be used in constructor + //! outside of constructor this method can be used to change behavior of writer. + void + set_write_mesh_info(bool value) + { + m_write_mesh_info = value; + m_write_tree = m_config_map.getBool("output", "write_mesh_tree", m_write_mesh_info); + m_write_level = m_config_map.getBool("output", "write_mesh_level", m_write_mesh_info); + m_write_rank = m_config_map.getBool("output", "write_mesh_rank", m_write_mesh_info); + m_write_reduced_orchard_key_global = + m_config_map.getBool("output", "write_mesh_reduced_orchard_key_global", m_write_mesh_info); + m_write_reduced_orchard_key_local = + m_config_map.getBool("output", "write_mesh_reduced_orchard_key_local", m_write_mesh_info); + m_write_full_orchard_key = + m_config_map.getBool("output", "write_mesh_full_orchard_key", m_write_mesh_info); + m_write_at_domain_border = + m_config_map.getBool("output", "write_mesh_at_domain_border", m_write_mesh_info); + m_write_at_tree_border = + m_config_map.getBool("output", "write_mesh_at_tree_border", m_write_mesh_info); + } // set_write_mesh_info + +}; // class HDF5_Xdmf_Writer_legacy + +// ============================================================================= +// ============================================================================= +// class HDF5_Xdmf_Writer_legacy definition +// ============================================================================= +// ============================================================================= + +// ======================================================= +// ======================================================= +template +HDF5_Xdmf_Writer_legacy::HDF5_Xdmf_Writer_legacy(forest_t * forest, + geometry_t * geom, + const ConfigMap & config_map, + block_size_t block_size, + coord_t start_index, + std::string xdmf_main_suffix) + : m_forest(forest) + , m_geom(geom) + , m_basename("") + , m_config_map(config_map) + , m_write_mesh_info(false) + , m_write_tree(false) + , m_write_level(false) + , m_write_rank(false) + , m_write_reduced_orchard_key_global(false) + , m_write_reduced_orchard_key_local(false) + , m_write_full_orchard_key(false) + , m_write_at_domain_border(false) + , m_write_at_tree_border(false) + , m_write_block_data(Kokkos::dim_prod(block_size) > 1) + , m_block_size(block_size) + , m_istart(start_index) + , m_nbNodesPerCell(dim == TWO_D ? IO_NODES_PER_CELL_2D : IO_NODES_PER_CELL_3D) + , m_nbCellsPerLeaf(static_cast(Kokkos::dim_prod(block_size))) +{ + + // set write_mesh_info from config, but it can be changed afterwards + set_write_mesh_info(m_config_map.getBool("output", "write_mesh_info", false)); + + m_write_iOct = m_config_map.getBool("output", "write_iOct", false); + + update_mesh_info(); + + m_hdf5_file = 0; + m_xdmf_file = nullptr; + + m_mpiRank = m_forest->mpirank; + + // is actually hdf5 enabled ? + bool hdf5_enabled = m_config_map.getBool("output", "hdf5_enabled", false); + + if (m_mpiRank == 0 and hdf5_enabled) + { + + std::string outputPrefix = m_config_map.getString("output", "outputPrefix", "output"); + std::string outputDir = m_config_map.getString("output", "outputDir", "output"); + + std::string filename; + filename = outputDir + "/" + outputPrefix + xdmf_main_suffix + ".xmf"; + + // KALYPSSO_GLOBAL_INFOF("Writing main XMDF file \"%s\".\n", filename.c_str()); + m_main_xdmf_file = fopen(filename.c_str(), "w"); + io_xdmf_write_main_header(); + } + else + { + m_main_xdmf_file = nullptr; + } + +} // HDF5_Xdmf_Writer_legacy::HDF5_Xdmf_Writer_legacy + +// ======================================================= +// ======================================================= +template +HDF5_Xdmf_Writer_legacy::~HDF5_Xdmf_Writer_legacy() +{ + + /* + * Only rank 0 needs to close the main xdmf file. + */ + if (m_mpiRank == 0 and m_main_xdmf_file != nullptr) + { + io_xdmf_write_main_footer(); + + fflush(m_main_xdmf_file); + fclose(m_main_xdmf_file); + m_main_xdmf_file = nullptr; + } + + // close other file + close(); + +} // HDF5_Xdmf_Writer_legacy::~HDF5_Xdmf_Writer_legacy + +// ======================================================= +// ======================================================= +template +void +HDF5_Xdmf_Writer_legacy::update_mesh_info() +{ + + m_local_num_quads = static_cast(m_forest->local_num_quadrants); + m_global_num_quads = static_cast(m_forest->global_num_quadrants); + + m_local_num_nodes = m_nbNodesPerCell * m_local_num_quads; + m_global_num_nodes = m_nbNodesPerCell * m_global_num_quads; + +} // HDF5_Xdmf_Writer_legacy::update_mesh_info + +// ======================================================= +// ======================================================= +template +void +HDF5_Xdmf_Writer_legacy::open(std::string basename, std::string outDir) +{ + // build filename + std::string filename = basename + ".h5"; + std::string full_path = outDir + "/" + filename; + m_basename = basename; + + /* + * create file access property list + */ + HighFive::FileAccessProps fapl; +#ifdef KALYPSSO_CORE_USE_MPI + fapl.add(HighFive::MPIOFileAccess{ m_forest->mpicomm, MPI_INFO_NULL }); + // all metadata are written using collective IO + fapl.add(HighFive::MPIOCollectiveMetadata{}); +#endif // KALYPSSO_CORE_USE_MPI + + /* + * Open parallel HDF5 resources. + */ + m_hdf5_file = new HighFive::File(full_path, HighFive::File::Truncate, fapl); + + // open xdmf files (one for each hdf5, a main xdmf file) + if (m_mpiRank == 0) + { + + filename = basename + ".xmf"; + full_path = outDir + "/" + filename; + m_xdmf_file = fopen(full_path.c_str(), "w"); + + if (m_main_xdmf_file) + { + io_xdmf_write_main_include(filename); + } + } + +} // HDF5_Xdmf_Writer_legacy::open + +// ======================================================= +// ======================================================= +template +void +HDF5_Xdmf_Writer_legacy::close() +{ + // close HDF5 + if (m_hdf5_file != nullptr) + { + m_hdf5_file->flush(); + delete m_hdf5_file; + m_hdf5_file = nullptr; + } + + // close XDMF file descriptor + if (m_xdmf_file) + { + fflush(m_xdmf_file); + fclose(m_xdmf_file); + m_xdmf_file = nullptr; + } + +} // HDF5_Xdmf_Writer_legacy::close + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer_legacy::write_header(double time) +{ + + // write the xmdf file first + if (m_mpiRank == 0) + { + io_xdmf_write_header(time); + } + + uint64_t num_bytes = 0; + + // and write stuff into the hdf file + num_bytes += io_hdf5_write_coordinates(); + num_bytes += io_hdf5_write_connectivity(); + num_bytes += io_hdf5_write_level(); + num_bytes += io_hdf5_write_tree(); + num_bytes += io_hdf5_write_rank(); + num_bytes += io_hdf5_write_iOct(); + if (m_write_reduced_orchard_key_global) + num_bytes += io_hdf5_write_reduced_orchard_key(REDUCED_GLOBAL); + if (m_write_reduced_orchard_key_local) + num_bytes += io_hdf5_write_reduced_orchard_key(REDUCED_LOCAL); + if (m_write_full_orchard_key) + num_bytes += io_hdf5_write_reduced_orchard_key(FULL); + num_bytes += io_hdf5_write_at_domain_border(); + num_bytes += io_hdf5_write_at_tree_border(); + + return num_bytes; + +} // HDF5_Xdmf_Writer_legacy::write_header + +// ======================================================= +// ======================================================= +template +int +HDF5_Xdmf_Writer_legacy::write_footer() +{ + if (m_mpiRank == 0) + { + io_xdmf_write_footer(); + } + + return 0; + +} // HDF5_Xdmf_Writer_legacy::write_footer + +// ======================================================= +// ======================================================= +template +template +uint64_t +HDF5_Xdmf_Writer_legacy::write_attribute(const std::string & name, + data_t * data, + size_t dimData, + io_attribute_type_t ftype) +{ + uint64_t num_bytes = 0; + + std::vector dims{ 0 }; + std::vector count{ 0 }; + std::vector start{ 0 }; + + if (dimData > 0) + { + dims.resize(2); + count.resize(2); + start.resize(2); + } + + if (ftype == IO_CELL_SCALAR || ftype == IO_CELL_VECTOR) + { + + dims[0] = static_cast(m_forest->global_num_quadrants) * m_nbCellsPerLeaf; + if (dimData > 0) + dims[1] = dimData; + + count[0] = static_cast(m_forest->local_num_quadrants) * m_nbCellsPerLeaf; + if (dimData > 0) + count[1] = dims[1]; + + // get global index of the first octant of current mpi processor + start[0] = static_cast(m_forest->global_first_quadrant[m_mpiRank]) * m_nbCellsPerLeaf; + if (dimData > 0) + start[1] = 0; + } + else + { + + // is this relevant ? + + // dims[0] = m_global_num_nodes; + // if (dimData > 0) + // dims[1] = dim; + + // count[0] = m_local_num_nodes; + // if (dimData > 0) + // count[1] = dims[1]; + + // start[0] = m_start_nodes; + // if (dimData > 0) + // start[1] = 0; + } + + if (m_mpiRank == 0) + { + const char * dtype_str = hdf5_native_type_to_string(); + io_xdmf_write_attribute(name, dtype_str, ftype, dims); + } + + num_bytes += io_hdf5_writev(*m_hdf5_file, name, data, dims, count, start); + + return num_bytes; + +} // HDF5_Xdmf_Writer_legacy::write_attribute + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer_legacy::write_quadrant_attribute(DataArrayLeafHost_t datah, + int32_t varIdx, + const std::string varName) +{ + + + uint64_t num_bytes = 0; + + // if DataArray has a left layout, we only need to define + // a slice to actual scalar data + // if DataArrayLeafHost_t has right layout, we need to actually extract + // the slide so that it is memory contiguous + if (std::is_same::value) + { + + auto dataVar = Kokkos::subview(datah, Kokkos::ALL(), varIdx); + + // actual data writing + num_bytes += write_attribute(varName, dataVar.data(), 0, IO_CELL_SCALAR); + } + else + { + + using DataArrayScalar = Kokkos::View; + + DataArrayScalar dataVar = DataArrayScalar("scalar_array_for_hdf5_io", datah.extent(0)); + + size_t nbOcts = datah.extent(0); + + Kokkos::parallel_for( + "HDF5_Xdmf_Writer_legacy::write_quadrant_attribute", + Kokkos::RangePolicy(0, nbOcts), + KOKKOS_LAMBDA(uint32_t iOct) { dataVar(iOct) = datah(iOct, varIdx); }); + + // actual data writing + num_bytes += write_attribute(varName, dataVar.data(), 0, IO_CELL_SCALAR); + } + + return num_bytes; + +} // HDF5_Xdmf_Writer_legacy::write_quadrant_attribute + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer_legacy::write_quadrant_attribute(DataArrayBlockHost_t datah, + int32_t varIdx, + const std::string varName) +{ + + uint64_t num_bytes = 0; + int32_t nbCellsPerOct = Kokkos::dim_prod(m_block_size); + + int32_t nbOcts = datah.num_quadrants(); + + // we need to gather data corresponding to a given scalar variable + using DataArrayScalar = Kokkos::View; + + // remember that + // - data.extent(0) is the number of cells per octant + // - data.extent(1) is the number of scalar fields + // - data.extent(2) is the total number of oct in current MPI process + DataArrayScalar dataVar = + DataArrayScalar("scalar_array_for_hdf5_io", static_cast(nbCellsPerOct * nbOcts)); + + auto block_size = m_block_size; + auto istart = m_istart; + + Kokkos::parallel_for( + "HDF5_Xdmf_Writer_legacy::write_quadrant_attribute", + Kokkos::RangePolicy(0, nbOcts * nbCellsPerOct), + KOKKOS_LAMBDA(int32_t global_index) { + const auto iOct_local = global_index / nbCellsPerOct; + const auto cell_index = global_index - iOct_local * nbCellsPerOct; + const auto coords = cellindex_to_coord(cell_index, block_size); + + // the only reason of the following dummy code to be here, is that cuda nvcc compile doesn't + // support capturing variables inside the inner lambda inside a constexpr if + // + // strangely, nvc++ is ok and don't need that + // TODO: remove theses lines when nvcc will have support for this. +#ifdef __NVCC__ + [[maybe_unused]] int dummy = 0; + if (dataVar.extent(0) == 0 or datah.num_cells() == 0 or varIdx == 0 or istart[IX] == 0) + dummy++; +#endif + if constexpr (dim == 2) + { + dataVar(cell_index + nbCellsPerOct * iOct_local) = + datah(coords[IX] + istart[IX], coords[IY] + istart[IY], varIdx, iOct_local); + } + else if constexpr (dim == 3) + { + dataVar(cell_index + nbCellsPerOct * iOct_local) = datah(coords[IX] + istart[IX], + coords[IY] + istart[IY], + coords[IZ] + istart[IZ], + varIdx, + iOct_local); + } + }); + + // actual data writing + num_bytes += write_attribute(varName, dataVar.data(), 0, IO_CELL_SCALAR); + + return num_bytes; + +} // HDF5_Xdmf_Writer_legacy::write_quadrant_attribute + +// ======================================================= +// ======================================================= +template +template +uint64_t +HDF5_Xdmf_Writer_legacy::io_hdf5_writev(HighFive::File & hdf5_file, + const std::string & dataset_name, + data_t * data_ptr, + std::vector const & dims, + std::vector const & count, + std::vector const & offset) +{ + // create dataset + HighFive::DataSet dataset = + hdf5_file.createDataSet(dataset_name, HighFive::DataSpace(dims)); + + auto xfer_props = HighFive::DataTransferProps{}; +#ifdef KALYPSSO_CORE_USE_MPI + xfer_props.add(HighFive::UseCollectiveIO{}); +#endif // KALYPSSO_CORE_USE_MPI + + dataset.select(offset, count).write_raw(data_ptr, xfer_props); +#ifdef KALYPSSO_CORE_USE_MPI + check_collective_io(xfer_props); +#endif // KALYPSSO_CORE_USE_MPI + + // Let's ensure that everything has been written do disk. + hdf5_file.flush(); + + // return the total number of bytes written in local MPI process + return sizeof(data_t) * std::reduce(count.begin(), count.end(), 1u, std::multiplies<>()); + +} // HDF5_Xdmf_Writer_legacy::io_hdf5_writev + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer_legacy::io_hdf5_write_coordinates() +{ + + const auto NB_CHILDREN = p4est_t::NB_CHILDREN; + const auto quadrant_array_index = &p4est_t::quadrant_array_index; + const auto tree_array_index = &p4est_t::tree_array_index; + + uint64_t num_bytes = 0; + + if (m_write_block_data) + { + uint32_t nbNodesPerLeaf = static_cast(Kokkos::dim_prod(m_block_size + 1)); + + uint64_t totalNumOfCoords = 3 * m_local_num_quads * nbNodesPerLeaf; + + std::vector data(totalNumOfCoords); + + /* + * construct the list of node coordinates + */ + + // bool use_block_amr = true; + std::array bSize{ m_block_size[IX], m_block_size[IY], 1 }; + if constexpr (dim == 3) + { + bSize[IZ] = m_block_size[IZ]; + } + + // array of local trees + sc_array_t * trees = m_forest->trees; + + uint32_t iOct = 0; + + // loop over all local tree + for (auto jt = m_forest->first_local_tree; jt <= m_forest->last_local_tree; ++jt) + { + // get current tree + tree_t * tree = tree_array_index(trees, jt); + + // get quadrant array of current tree + sc_array_t * quadrants = &(tree->quadrants); + + // loop over all local quadrant + for (size_t jq = 0; jq < quadrants->elem_count; ++jq) + { + quadrant_t * q = quadrant_array_index(quadrants, jq); + + // the offset of the first node of the current quadrant + size_t offset = 3 * nbNodesPerLeaf * iOct; + + // compute the coordinates of the NB_CHILDREN nodes and add them to + // the data array + + io_fill_coordinates(m_forest, m_geom, jt, q, &(data[offset]), bSize); + + // next quad/octant + ++iOct; + + } // end for quadrant/octant in current tree + + } // end for tree + + // get prepared for hdf5 writing + + std::vector dims{ 0, 0 }; + std::vector count{ 0, 0 }; + std::vector start{ 0, 0 }; + + // get the dimensions and offset of the node coordinates array + dims[0] = m_global_num_quads * nbNodesPerLeaf; + dims[1] = 3; + + count[0] = m_local_num_quads * nbNodesPerLeaf; + count[1] = 3; + + // get global index of the first octant of current mpi processor + start[0] = static_cast(m_forest->global_first_quadrant[m_mpiRank]) * nbNodesPerLeaf; + start[1] = 0; + + // write the node coordinates + num_bytes += io_hdf5_writev(*m_hdf5_file, "coordinates", data.data(), dims, count, start); + } + else // cell-based AMR, one cell per quadrant + { + + // not using block AMR + std::array bSize{ -1, -1, -1 }; + + // array with all local nodes coordinates + std::vector data(3 * m_local_num_nodes); + + /* + * construct the list of node coordinates + */ + + // array of local trees + sc_array_t * trees = m_forest->trees; + + uint32_t iOct = 0; + + // loop over all local tree + for (auto jt = m_forest->first_local_tree; jt <= m_forest->last_local_tree; ++jt) + { + // get current tree + tree_t * tree = tree_array_index(trees, jt); + + // get quadrant array of current tree + sc_array_t * quadrants = &(tree->quadrants); + + // loop over all local quadrant + for (size_t jq = 0; jq < quadrants->elem_count; ++jq) + { + quadrant_t * q = quadrant_array_index(quadrants, jq); + + // the offset of the first node of the current quadrant + size_t offset = 3 * NB_CHILDREN * iOct; + + // compute the coordinates of the NB_CHILDREN nodes and add them to + // the data array + io_fill_coordinates(m_forest, m_geom, jt, q, &(data[offset]), bSize); + + // next quad/octant + ++iOct; + + } // end for quadrant/octant in current tree + + } // end for tree + + // get prepared for hdf5 writing + + std::vector dims{ 0, 0 }; + std::vector count{ 0, 0 }; + std::vector start{ 0, 0 }; + + // get the dimensions and offset of the node coordinates array + dims[0] = m_global_num_nodes; + dims[1] = 3; + + count[0] = m_local_num_nodes; + count[1] = 3; + + // get global index of the first octant of current mpi processor + start[0] = m_nbNodesPerCell * static_cast(m_forest->global_first_quadrant[m_mpiRank]); + start[1] = 0; + + // write the node coordinates + num_bytes += io_hdf5_writev(*m_hdf5_file, "coordinates", data.data(), dims, count, start); + } + + return num_bytes; + +} // HDF5_Xdmf_Writer_legacy::io_hdf5_write_coordinates + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer_legacy::io_hdf5_write_connectivity() +{ + + uint64_t num_bytes = 0; + + if (m_write_block_data) + { + + uint32_t nbNodesPerLeaf = static_cast(Kokkos::dim_prod(m_block_size + 1)); + + std::vector data(m_local_num_quads * m_nbCellsPerLeaf * m_nbNodesPerCell); + + // first node of current MPI process + uint64_t globalNodeOffset = + nbNodesPerLeaf * static_cast(m_forest->global_first_quadrant[m_mpiRank]); + + uint64_t nbConnectivityPerLeaf = m_nbCellsPerLeaf * m_nbNodesPerCell; + + // get connectivity data + for (uint32_t iLeaf = 0; iLeaf < m_local_num_quads; ++iLeaf) + { + uint64_t localNodeOffset = nbNodesPerLeaf * iLeaf; + + // sweep subcells + int nz = 1; + if constexpr (dim == 3) + { + nz = m_block_size[IZ]; + } + + for (int jz = 0; jz < nz; ++jz) + { + for (int jy = 0; jy < m_block_size[IY]; ++jy) + { + for (int jx = 0; jx < m_block_size[IX]; ++jx) + { + + int64_t nodeOffset = static_cast(globalNodeOffset + localNodeOffset); + + uint64_t idx = nbConnectivityPerLeaf * iLeaf + + static_cast(subCellIndex(jx, jy, jz)) * m_nbNodesPerCell; + + data[idx + 0] = nodeOffset + subNodeIndex(jx, jy, jz); + data[idx + 1] = nodeOffset + subNodeIndex(jx + 1, jy, jz); + data[idx + 2] = nodeOffset + subNodeIndex(jx + 1, jy + 1, jz); + data[idx + 3] = nodeOffset + subNodeIndex(jx, jy + 1, jz); + + if (dim == THREE_D) + { + data[idx + 4] = nodeOffset + subNodeIndex(jx, jy, jz + 1); + data[idx + 5] = nodeOffset + subNodeIndex(jx + 1, jy, jz + 1); + data[idx + 6] = nodeOffset + subNodeIndex(jx + 1, jy + 1, jz + 1); + data[idx + 7] = nodeOffset + subNodeIndex(jx, jy + 1, jz + 1); + } + + } // end for jx + } // end for jy + } // end for jz + + } // end for iLeaf + + // now write connectivity with hdf5 + + std::vector dims{ 0, 0 }; + std::vector count{ 0, 0 }; + std::vector start{ 0, 0 }; + + // get the dimensions and offsets for each connectivity array + dims[0] = m_global_num_quads * m_nbCellsPerLeaf; + dims[1] = m_nbNodesPerCell; + + count[0] = m_local_num_quads * m_nbCellsPerLeaf; + count[1] = m_nbNodesPerCell; + + start[0] = static_cast(m_forest->global_first_quadrant[m_mpiRank]) * m_nbCellsPerLeaf; + start[1] = 0; + + // write the node coordinates + num_bytes += io_hdf5_writev(*m_hdf5_file, "connectivity", data.data(), dims, count, start); + } + else + { // regular AMR mesh, i.e. one cell per quad/oct + + uint32_t node[8] = { 0, 1, 3, 2, 0, 0, 0, 0 }; + + if (dim == 3) + { + node[4] = 4; + node[5] = 5; + node[6] = 7; + node[7] = 6; + } + + std::vector data(m_local_num_quads * m_nbNodesPerCell); + + int64_t in = 0; + + // get connectivity data + for (uint32_t i = 0; i < m_local_num_quads; ++i) + { + + for (uint32_t j = 0; j < m_nbNodesPerCell; ++j) + { + uint64_t idx = m_nbNodesPerCell * i + j; + + data[idx] = + static_cast(m_nbNodesPerCell) * m_forest->global_first_quadrant[m_mpiRank] + in + + static_cast(node[j]); + } // end for j + + in += m_nbNodesPerCell; + + } // end for i + + // now write connectivity with hdf5 + + std::vector dims{ 0, 0 }; + std::vector count{ 0, 0 }; + std::vector start{ 0, 0 }; + + // get the dimensions and offsets for each connectivity array + dims[0] = m_global_num_quads; + dims[1] = m_nbNodesPerCell; + + count[0] = m_local_num_quads; + count[1] = m_nbNodesPerCell; + + start[0] = static_cast(m_forest->global_first_quadrant[m_mpiRank]); + start[1] = 0; + + // write the node coordinates + num_bytes += io_hdf5_writev(*m_hdf5_file, "connectivity", data.data(), dims, count, start); + } + + return num_bytes; + +} // HDF5_Xdmf_Writer_legacy::io_hdf5_write_connectivity + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer_legacy::io_hdf5_write_level() +{ + + // const auto NB_CHILDREN = p4est_t::NB_CHILDREN; + const auto quadrant_array_index = &p4est_t::quadrant_array_index; + const auto tree_array_index = &p4est_t::tree_array_index; + + if (!this->m_write_level) + { + return 0; + } + + uint32_t nbData = m_local_num_quads * m_nbCellsPerLeaf; + + std::vector data(nbData); + + // gather level for each local quadrant + // p4est::locidx_t iOct = 0; + sc_array_t * trees = m_forest->trees; + + uint32_t i = 0; + + // loop over all local tree + for (auto jt = m_forest->first_local_tree; jt <= m_forest->last_local_tree; ++jt) + { + // get current tree + tree_t * tree = tree_array_index(trees, jt); + + // get quadrant array of current tree + sc_array_t * quadrants = &(tree->quadrants); + + // loop over all local quadrant + for (size_t jq = 0; jq < quadrants->elem_count; ++jq) + { + quadrant_t * q = quadrant_array_index(quadrants, jq); + + for (uint32_t j = 0; j < m_nbCellsPerLeaf; ++j) + { + data[i] = q->level; + ++i; + } + + // data[iOct] = q->level; + + // next quad/octant + //++iOct; + + } // end for quadrant/octant in current tree + + } // end for tree + + return write_attribute("level", data.data(), 0, IO_CELL_SCALAR); + +} // HDF5_Xdmf_Writer_legacy::io_hdf5_write_level + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer_legacy::io_hdf5_write_tree() +{ + + // const auto NB_CHILDREN = p4est_t::NB_CHILDREN; + // const auto quadrant_array_index = &p4est_t::quadrant_array_index; + const auto tree_array_index = &p4est_t::tree_array_index; + + if (!this->m_write_tree) + { + return 0; + } + + uint32_t nbData = m_local_num_quads * m_nbCellsPerLeaf; + + std::vector data(nbData); + + // gather level for each local quadrant + // p4est::locidx_t iOct = 0; + sc_array_t * trees = m_forest->trees; + + uint32_t i = 0; + + // loop over all local tree + for (auto jt = m_forest->first_local_tree; jt <= m_forest->last_local_tree; ++jt) + { + // get current tree + tree_t * tree = tree_array_index(trees, jt); + + // get quadrant array of current tree + sc_array_t * quadrants = &(tree->quadrants); + + // loop over all local quadrant + for (size_t jq = 0; jq < quadrants->elem_count; ++jq) + { + // quadrant_t * q = quadrant_array_index(quadrants, jq); + + for (uint32_t j = 0; j < m_nbCellsPerLeaf; ++j) + { + data[i] = jt; + ++i; + } + + // next quad/octant + //++iOct; + + } // end for quadrant/octant in current tree + + } // end for tree + + return write_attribute("treeid", data.data(), 0, IO_CELL_SCALAR); + +} // HDF5_Xdmf_Writer_legacy::io_hdf5_write_tree + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer_legacy::io_hdf5_write_iOct() +{ + + if (!this->m_write_iOct) + { + return 0; + } + + uint32_t nbData = m_local_num_quads * m_nbCellsPerLeaf; + + std::vector data(nbData); + + // gather level for each local quadrant + uint32_t i = 0; + for (uint32_t iLeaf = 0; iLeaf < m_local_num_quads; ++iLeaf) + { + for (uint32_t j = 0; j < m_nbCellsPerLeaf; ++j) + { + data[i] = iLeaf; + ++i; + } + } + + return write_attribute("iOct", data.data(), 0, IO_CELL_SCALAR); + +} // HDF5_Xdmf_Writer_legacy::io_hdf5_write_iOct + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer_legacy::io_hdf5_write_rank() +{ + + if (!this->m_write_rank) + { + return 0; + } + + uint32_t nbData = m_local_num_quads * m_nbCellsPerLeaf; + + std::vector data(nbData); + + // gather rank for each local quadrant + for (uint32_t i = 0; i < nbData; ++i) + { + data[i] = m_mpiRank; + } + + return write_attribute("rank", data.data(), 0, IO_CELL_SCALAR); + +} // HDF5_Xdmf_Writer_legacy::io_hdf5_write_rank + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer_legacy::io_hdf5_write_reduced_orchard_key( + orchard_key_dump_type_t type) +{ + + // const auto NB_CHILDREN = p4est_t::NB_CHILDREN; + const auto quadrant_array_index = &p4est_t::quadrant_array_index; + const auto tree_array_index = &p4est_t::tree_array_index; + + uint32_t nbData = m_local_num_quads * m_nbCellsPerLeaf; + + std::vector data(nbData); + size_t icell = 0; + + // check if connectivity is either "unit" or "brick" + auto conn_name = m_config_map.getString("amr", "connectivity", "invalid_connectivity"); + + if (conn_name == "unit" or conn_name == "brick") + { + // gather orchard for each local quadrant / cell + + // brick sizes + brick_size_t brick_sizes; + brick_sizes[0] = 1; + brick_sizes[1] = 1; + if constexpr (dim == 3) + brick_sizes[2] = 1; + + if (conn_name == "brick") + { + brick_sizes = get_brick_sizes(m_config_map); + } + + // tree linear index to xyz converter + BrickConnectivityData convert(brick_sizes); + + // array of local trees + sc_array_t * trees = m_forest->trees; + + auto max_level = m_config_map.getInteger("amr", "level_max", 0); + + // loop over all local tree + for (auto jt = m_forest->first_local_tree; jt <= m_forest->last_local_tree; ++jt) + { + // get current tree + tree_t * tree = tree_array_index(trees, jt); + + // get tree coordinate + auto tree_xyz = convert.toXYZ(jt); + + // get quadrant array of current tree + sc_array_t * quadrants = &(tree->quadrants); + + // loop over all local quadrant + for (size_t jq = 0; jq < quadrants->elem_count; ++jq) + { + quadrant_t * q = quadrant_array_index(quadrants, jq); + + Kokkos::Array octCoord; + octCoord[0] = + static_cast(q->x >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + octCoord[1] = + static_cast(q->y >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + if constexpr (dim == 3) + { + octCoord[2] = static_cast( + q->z >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + } + + if (type == FULL) + { + uint64_t orchard_key = + orchard_key_t::encode_orchard(tree_xyz, octCoord, static_cast(q->level)); + for (size_t i = 0; i < m_nbCellsPerLeaf; ++i) + { + data[icell] = orchard_key; + icell++; + } + } + else + { + // reduced key + octCoord[0] >>= (orchard_key_t::NUM_LEVELS - max_level); + octCoord[1] >>= (orchard_key_t::NUM_LEVELS - max_level); + if constexpr (dim == 3) + octCoord[2] >>= (orchard_key_t::NUM_LEVELS - max_level); + + uint64_t orchard_key = orchard_key_t::encode_orchard(tree_xyz, octCoord, 0); + uint64_t morton_oct = orchard_key_t::morton_octant(orchard_key); + uint64_t morton_key = orchard_key_t::morton_tree(orchard_key); + uint64_t reduced_key = + type == REDUCED_GLOBAL + ? morton_oct + (morton_key << (dim * static_cast(max_level))) + : morton_oct; + + for (size_t i = 0; i < m_nbCellsPerLeaf; ++i) + { + data[icell] = reduced_key; // morton_oct; // orchard_key; + icell++; + } + } + + } // end for quadrant/octant in current tree + + } // end for tree + } + else + { + // to be clarified, we could defined an orchard key, but the meaning is unclear + for (uint32_t i = 0; i < nbData; ++i) + { + data[i] = 0; + } + } + + std::string attribute_str = type == FULL ? "full_orchard_key" + : type == REDUCED_GLOBAL ? "reduced_orchard_key_global" + : "reduced_orchard_key_local"; + + return write_attribute(attribute_str, data.data(), 0, IO_CELL_SCALAR); + + +} // HDF5_Xdmf_Writer_legacy::io_hdf5_write_reduced_orchard_key + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer_legacy::io_hdf5_write_at_domain_border() +{ + + if (!this->m_write_at_domain_border) + { + return 0; + } + + // const auto NB_CHILDREN = p4est_t::NB_CHILDREN; + const auto quadrant_array_index = &p4est_t::quadrant_array_index; + const auto tree_array_index = &p4est_t::tree_array_index; + + uint32_t nbData = m_local_num_quads * m_nbCellsPerLeaf; + + std::vector data(nbData); + size_t icell = 0; + + // check if connectivity is either "unit" or "brick" + auto conn_name = m_config_map.getString("amr", "connectivity", "invalid_connectivity"); + + if (conn_name == "unit" or conn_name == "brick") + { + // gather orchard for each local quadrant / cell + + // brick sizes + brick_size_t brick_sizes; + brick_sizes[0] = 1; + brick_sizes[1] = 1; + if constexpr (dim == 3) + brick_sizes[2] = 1; + + if (conn_name == "brick") + { + brick_sizes = get_brick_sizes(m_config_map); + } + + // tree linear index to xyz converter + BrickConnectivityData convert(brick_sizes); + + // array of local trees + sc_array_t * trees = m_forest->trees; + + // loop over all local tree + for (auto jt = m_forest->first_local_tree; jt <= m_forest->last_local_tree; ++jt) + { + // get current tree + tree_t * tree = tree_array_index(trees, jt); + + // get tree coordinate + auto tree_xyz = convert.toXYZ(jt); + + // get quadrant array of current tree + sc_array_t * quadrants = &(tree->quadrants); + + // loop over all local quadrant + for (size_t jq = 0; jq < quadrants->elem_count; ++jq) + { + quadrant_t * q = quadrant_array_index(quadrants, jq); + + Kokkos::Array octCoord; + octCoord[0] = + static_cast(q->x >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + octCoord[1] = + static_cast(q->y >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + if constexpr (dim == 3) + { + octCoord[2] = static_cast( + q->z >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + } + + uint64_t orchard_key = + orchard_key_t::encode_orchard(tree_xyz, octCoord, static_cast(q->level)); + + for (size_t i = 0; i < m_nbCellsPerLeaf; ++i) + { + uint32_t atDomainBorder = static_cast( + orchard_key_t::is_at_any_domain_border(orchard_key, brick_sizes)); + data[icell] = atDomainBorder; + icell++; + } + + } // end for quadrant/octant in current tree + + } // end for tree + } + else + { + // not defined, so use "zero" as default value + // writing this attribute should be disabled in config_map + for (uint32_t i = 0; i < nbData; ++i) + { + data[i] = 0; + } + } + + return write_attribute("at_domain_border", data.data(), 0, IO_CELL_SCALAR); + +} // HDF5_Xdmf_Writer_legacy::io_hdf5_write_at_domain_border + +// ======================================================= +// ======================================================= +template +uint64_t +HDF5_Xdmf_Writer_legacy::io_hdf5_write_at_tree_border() +{ + + if (!this->m_write_at_tree_border) + { + return 0; + } + + // const auto NB_CHILDREN = p4est_t::NB_CHILDREN; + const auto quadrant_array_index = &p4est_t::quadrant_array_index; + const auto tree_array_index = &p4est_t::tree_array_index; + + uint32_t nbData = m_local_num_quads * m_nbCellsPerLeaf; + + std::vector data(nbData); + size_t icell = 0; + + // check if connectivity is either "unit" or "brick" + auto conn_name = m_config_map.getString("amr", "connectivity", "invalid_connectivity"); + + if (conn_name == "unit" or conn_name == "brick") + { + // gather orchard for each local quadrant / cell + + // brick sizes + brick_size_t brick_sizes; + brick_sizes[0] = 1; + brick_sizes[1] = 1; + if constexpr (dim == 3) + brick_sizes[2] = 1; + + if (conn_name == "brick") + { + brick_sizes = get_brick_sizes(m_config_map); + } + + // tree linear index to xyz converter + BrickConnectivityData convert(brick_sizes); + + // array of local trees + sc_array_t * trees = m_forest->trees; + + // loop over all local tree + for (auto jt = m_forest->first_local_tree; jt <= m_forest->last_local_tree; ++jt) + { + // get current tree + tree_t * tree = tree_array_index(trees, jt); + + // get tree coordinate + auto tree_xyz = convert.toXYZ(jt); + + // get quadrant array of current tree + sc_array_t * quadrants = &(tree->quadrants); + + // loop over all local quadrant + for (size_t jq = 0; jq < quadrants->elem_count; ++jq) + { + quadrant_t * q = quadrant_array_index(quadrants, jq); + + Kokkos::Array octCoord; + octCoord[0] = + static_cast(q->x >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + octCoord[1] = + static_cast(q->y >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + if constexpr (dim == 3) + { + octCoord[2] = static_cast( + q->z >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + } + + uint64_t orchard_key = + orchard_key_t::encode_orchard(tree_xyz, octCoord, static_cast(q->level)); + + uint32_t at_any_tree_border = + static_cast(orchard_key_t::is_at_any_tree_border(orchard_key)); + + uint32_t at_any_tree_corner = + static_cast(orchard_key_t::is_at_any_tree_corner(orchard_key)); + + uint32_t at_any_tree_edge = 0; + if constexpr (dim == 3) + at_any_tree_edge = + static_cast(orchard_key_t::is_at_any_tree_edge(orchard_key)); + + for (size_t i = 0; i < m_nbCellsPerLeaf; ++i) + { + data[icell] = at_any_tree_border + at_any_tree_corner + at_any_tree_edge; + icell++; + } + + } // end for quadrant/octant in current tree + + } // end for tree + } + else + { + // not defined, so use "zero" as default value + // writing this attribute should be disabled in config_map + for (uint32_t i = 0; i < nbData; ++i) + { + data[i] = 0; + } + } + + return write_attribute("at_tree_border", data.data(), 0, IO_CELL_SCALAR); + +} // HDF5_Xdmf_Writer_legacy::io_hdf5_write_at_tree_border + +// ======================================================= +// ======================================================= +// Private members +// ======================================================= +// ======================================================= + +// ======================================================= +// ======================================================= +template +void +HDF5_Xdmf_Writer_legacy::io_xdmf_write_main_header() +{ + + FILE * fd = m_main_xdmf_file; + + fprintf(fd, "\n"); + fprintf(fd, "\n"); + fprintf(fd, + "\n"); + fprintf(fd, " \n"); + fprintf(fd, + " \n"); + +} // HDF5_Xdmf_Writer_legacy::io_xdmf_write_main_header + +// ======================================================= +// ======================================================= +template +void +HDF5_Xdmf_Writer_legacy::io_xdmf_write_header(double time) +{ + + FILE * fd = this->m_xdmf_file; + size_t global_num_cells = this->m_global_num_quads; + size_t global_num_nodes = global_num_cells * m_nbNodesPerCell; + + if (m_write_block_data) + { + + global_num_cells *= m_nbCellsPerLeaf; + + uint32_t nbNodesPerLeaf = static_cast(Kokkos::dim_prod(m_block_size + 1)); + + global_num_nodes = this->m_global_num_quads * nbNodesPerLeaf; + } + + const std::string IO_TOPOLOGY_TYPE = dim == TWO_D ? IO_TOPOLOGY_TYPE_2D : IO_TOPOLOGY_TYPE_3D; + + fprintf(fd, "\n"); + fprintf(fd, "\n"); + fprintf(fd, "\n"); + fprintf(fd, " \n"); + fprintf(fd, " \n", this->m_basename.c_str()); + fprintf(fd, " \n"); + fprintf(fd, " \n"); + fprintf(fd, "\n"); + +} // HDF5_Xdmf_Writer_legacy::io_xdmf_write_main_footer + +// ======================================================= +// ======================================================= +template +void +HDF5_Xdmf_Writer_legacy::io_xdmf_write_footer() +{ + + FILE * fd = this->m_xdmf_file; + + fprintf(fd, " \n"); + fprintf(fd, " \n"); + fprintf(fd, "\n"); + +} // HDF5_Xdmf_Writer_legacy::io_xdmf_write_footer + +// ======================================================= +// ======================================================= +template +int32_t +HDF5_Xdmf_Writer_legacy::subCellIndex(int jx, int jy, int jz) +{ + + return jx + m_block_size[IX] * (jy + m_block_size[IY] * jz); + +} // HDF5_Xdmf_Writer_legacy::subCellIndex + +// ======================================================= +// ======================================================= +template +int32_t +HDF5_Xdmf_Writer_legacy::subNodeIndex(int jx, int jy, int jz) +{ + + return jx + (m_block_size[IX] + 1) * (jy + (m_block_size[IY] + 1) * jz); + +} // HDF5_Xdmf_Writer_legacy::subNodeIndex + +// ======================================================= +// ======================================================= +template +void +HDF5_Xdmf_Writer_legacy::set_block_mode(block_size_t block_size, + coord_t start_index) +{ + + m_block_size = block_size; + m_nbCellsPerLeaf = static_cast(Kokkos::dim_prod(block_size)); + m_istart = start_index; + + m_write_block_data = (Kokkos::dim_prod(block_size) > 1); + +} // HDF5_Xdmf_Writer_legacy::set_block_mode + +// ======================================================= +// ======================================================= +template +void +HDF5_Xdmf_Writer_legacy::set_leaf_mode() +{ + + m_block_size[IX] = 1; + m_block_size[IY] = 1; + if constexpr (dim == 3) + { + m_block_size[IZ] = 1; + } + m_nbCellsPerLeaf = static_cast(Kokkos::dim_prod(m_block_size)); + + m_istart[IX] = 0; + m_istart[IY] = 0; + if constexpr (dim == 3) + { + m_istart[IZ] = 0; + } + + m_write_block_data = false; + +} // HDF5_Xdmf_Writer_legacy::set_leaf_mode + +// ===================================================================== +// ===================================================================== + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_HDF5_XDMF_WRITER_LEGACY_H_ diff --git a/src/kalypsso/core/HydroParams.cpp b/src/kalypsso/core/HydroParams.cpp new file mode 100644 index 0000000..5cd9936 --- /dev/null +++ b/src/kalypsso/core/HydroParams.cpp @@ -0,0 +1,127 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file HydroParams.cpp + */ +#include "HydroParams.h" + +#include // for exit +#include // for fprintf +#include // for strcmp +#include + +#include // our INI file reader +#include +#include + +namespace kalypsso +{ + +// ======================================================= +// ======================================================= +size_t +get_dim(ConfigMap const & config_map) +{ + // retrieve solver name from settings + const auto dimension = static_cast(config_map.getInteger("run", "dimension", 0)); + + if (dimension != 2 and dimension != 3) + { + Kokkos::abort("dimension must be 2 or 3 (and nothing else) !"); + } + + return dimension; +} // get_dim + +// ======================================================= +// ======================================================= +HydroParams::HydroParams(ConfigMap const & config_map) + : nStepmax(config_map.getInteger("run", "nstepmax", 1000)) + , tEnd(config_map.getReal("run", "tend", KALYPSSO_NUM(0.0))) + , nOutput(config_map.getInteger("run", "noutput", 100)) + , enableOutput(nOutput == 0 ? false : true) + , nlog(config_map.getInteger("run", "nlog", 10)) + , dimType(static_cast(config_map.getInteger("run", "dimension", 0))) + , xmin(config_map.getReal("mesh", "xmin", KALYPSSO_NUM(0.0))) + , xmax(1.0) + , ymin(config_map.getReal("mesh", "ymin", KALYPSSO_NUM(0.0))) + , ymax(1.0) + , zmin(config_map.getReal("mesh", "zmin", KALYPSSO_NUM(0.0))) + , zmax(1.0) + , level_min(config_map.getInteger("amr", "level_min", 5)) + , level_max(config_map.getInteger("amr", "level_max", 10)) + , amr_cycle_enabled(false) + , output_hdf5_enabled(config_map.getBool("output", "hdf5_enabled", false)) + , output_exabrick_enabled(config_map.getBool("output", "exabrick_enabled", false)) + , debug_output(config_map.getBool("output", "debug", false)) + , updateType(UPDATE_CONSERVATIVE_SUM) + , replicated_init_cond(config_map.getBool("hydro", "replicated_init_cond", false)) +{ + if (dimType != 2 and dimType != 3) + { + // we should probably abort + std::cerr << "dimension is not valid (can only be 2 or 3); given value is " << dimType << "\n"; + Kokkos::abort("Invalid dimension"); + } + + setup(config_map); + +} // HydroParams::HydroParams + +// ======================================================= +// ======================================================= +void +HydroParams::setup(ConfigMap const & config_map) +{ + + /* initialize MESH parameters */ + + const auto nbrick_x = config_map.getInteger("p4est_connectivity", "nbrick_x", 1); + const auto nbrick_y = config_map.getInteger("p4est_connectivity", "nbrick_y", 1); + const auto nbrick_z = config_map.getInteger("p4est_connectivity", "nbrick_z", 1); + + const auto scaling_factor = config_map.getReal("mesh", "scaling_factor", KALYPSSO_NUM(1.0)); + + xmax = xmin + static_cast(nbrick_x) * scaling_factor; + ymax = ymin + static_cast(nbrick_y) * scaling_factor; + zmax = zmin + static_cast(nbrick_z) * scaling_factor; + + // default value for amr_cycle_enabled + bool amr_cycle_enabled_default = (level_min != level_max); + + // we can overwrite amr_cycle_enabled; e.g. + // we can chose level_min != level_max for initial condition + // but then switch-off amr cycle. + amr_cycle_enabled = config_map.getBool("amr", "amr_cycle_enabled", amr_cycle_enabled_default); + + std::string utype = config_map.getString("hydro", "updateType", "conservative_sum"); + if (utype == "conservative_sum") + updateType = UPDATE_CONSERVATIVE_SUM; + else + updateType = UPDATE_NON_CONSERVATIVE; + +} // HydroParams::setup + +// ======================================================= +// ======================================================= +void +HydroParams::print() const +{ + + KALYPSSO_INFO("##########################"); + KALYPSSO_INFO("Simulation run parameters:"); + KALYPSSO_INFO("##########################"); + KALYPSSO_INFO("nStepmax : {}", nStepmax); + KALYPSSO_INFO("tEnd : {}", tEnd); + KALYPSSO_INFO("nOutput : {}", nOutput); + KALYPSSO_INFO("update type: {}", updateType); + KALYPSSO_INFO("level_min : {}", level_min); + KALYPSSO_INFO("level_max : {}", level_max); + KALYPSSO_INFO("amr cycle enabled : {}", amr_cycle_enabled); + KALYPSSO_INFO("##########################"); + +} // HydroParams::print + +} // namespace kalypsso diff --git a/src/kalypsso/core/HydroParams.h b/src/kalypsso/core/HydroParams.h new file mode 100644 index 0000000..426bdd2 --- /dev/null +++ b/src/kalypsso/core/HydroParams.h @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file HydroParams.h + * \brief Hydrodynamics solver parameters. + */ +#ifndef KALYPSSO_CORE_HYDRO_PARAMS_H_ +#define KALYPSSO_CORE_HYDRO_PARAMS_H_ + +#include +#include +#include +#include + + +#include + +namespace kalypsso +{ + +/** + * Read dimension from parameter file. + */ +size_t +get_dim(ConfigMap const & config_map); + +// =========================================================================== +// =========================================================================== +/** + * Hydro Parameters (declaration). + */ +struct HydroParams +{ + // run parameters + + //! maximum number of time steps. + int nStepmax; + + //! final simulation time. + real_t tEnd; + + //! number of outputs. + //! - nOutput<0 : output at every time step iteration + //! - nOutput=0 : no output at all + //! - nOutput>0 : one output every Delta_t = (t_end - t_beg)/nOutput + int nOutput; + + //! enable output file write. + bool enableOutput; + + int nlog; /*!< number of time step iterations between 2 consecutive logs. */ + + // int nbvar; /*!< number of conservative variables. */ + + size_t dimType; //!< 2D or 3D. + + real_t xmin; /*!< domain bound */ + real_t xmax; /*!< domain bound */ + real_t ymin; /*!< domain bound */ + real_t ymax; /*!< domain bound */ + real_t zmin; /*!< domain bound */ + real_t zmax; /*!< domain bound */ + + // AMR related parameter + int level_min; + int level_max; + + // switch on/off AMR cycle + bool amr_cycle_enabled; + + // IO parameters + bool output_hdf5_enabled; /*!< enable HDF5 output file format.*/ + bool output_exabrick_enabled; /*!< enable exabrick output file format.*/ + bool debug_output; /*!< more verbose output */ + + // Update type for the hydro solver (conservative / non-conservative) + int updateType; + + bool replicated_init_cond; /*!< if true, init cond is replicated identically in all trees (useful + for weak scaling studies) */ + + HydroParams(ConfigMap const & config_map); + + ~HydroParams() = default; + + //! This is the genuine initialization / setup (fed by parameter file) + void + setup(ConfigMap const & config_map); + + void + print() const; + +}; // struct HydroParams + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_HYDRO_PARAMS_H_ diff --git a/src/kalypsso/core/InterfaceNormalVectorAlgorithmParams.h b/src/kalypsso/core/InterfaceNormalVectorAlgorithmParams.h new file mode 100644 index 0000000..ba4078c --- /dev/null +++ b/src/kalypsso/core/InterfaceNormalVectorAlgorithmParams.h @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ComputeInterfaceNormal.h + * + * Compute schlieren of a scalar field. + * + */ +#ifndef KALYPSSO_CORE_INTERFACENORMALVECTORALGORITHMPARAMS_H_ +#define KALYPSSO_CORE_INTERFACENORMALVECTORALGORITHMPARAMS_H_ + +#include +#include +#include // for math functions (max, min, ...) +#include +#include +#include <../better-enums/enum.h> + +namespace kalypsso +{ + +// clang-format off +/** + * An enum type to represent all possible algorithm variant for computing interface normal vector. + */ +BETTER_ENUM(InterfaceNormalVectorAlgorithmType, uint8_t, + INVALID = 0, + PARKER_AND_YOUNGS = 1, + ELVIRA = 2, + SMOOTH_INTERFACE_FUNCTION = 3) +// clang-format on + +//! Get interface normal vector computation algorithm type for input parameter file. +inline InterfaceNormalVectorAlgorithmType +get_interface_normal_vector_algorithm_type(ConfigMap const & config_map) +{ + const auto algo_name = + config_map.getString("material", "interface_normal_vector_algorithm", "INVALID"); + auto maybe_value = InterfaceNormalVectorAlgorithmType::_from_string_nothrow(algo_name.c_str()); + if (maybe_value) + return *maybe_value; + return InterfaceNormalVectorAlgorithmType::INVALID; +} + +/** + * Parker and Youngs algorithm to compute interface normal. + * + * references: + * - An interface tracking method for a 3D Eulerian hydrodynamics code, D. Youngs (1987); + * https://www.researchgate.net/publication/245345562_An_interface_tracking_method_for_a_3D_Eulerian_hydrodynamics_code + * - Volume of fluid interface reconstruction methods for multi-material problems, D.J. Benson, + * Appl. Mech. Rev. Mar 2002, 55(2): 151-165 (2002). https://doi.org/10.1115/1.1448524 + * + * Interface normal is computed by \f$ n = \frac{\nabla f}{|\nabla f|} where f is the volume + * fraction of one material + */ +struct ParkerAndYoungsParams +{ + real_t alpha; + real_t beta; + real_t gamma; + + ParkerAndYoungsParams(ConfigMap const & config_map) + { + alpha = config_map.getFloat("parker_and_youngs", "alpha", 2.0); + beta = config_map.getFloat("parker_and_youngs", "beta", 2.0); + gamma = config_map.getFloat("parker_and_youngs", "gamma", 4.0); + } + +}; // struct ParkerAndYoungsParams + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_INTERFACENORMALVECTORALGORITHMPARAMS_H_ diff --git a/src/kalypsso/core/Kokkos_Array_extensions.h b/src/kalypsso/core/Kokkos_Array_extensions.h new file mode 100644 index 0000000..0604ad6 --- /dev/null +++ b/src/kalypsso/core/Kokkos_Array_extensions.h @@ -0,0 +1,319 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file Kokkos_Array_extensions.h + * Define some extensions to class Kokkos::Array. + */ +#ifndef KALYPSSO_CORE_KOKKOSARRAYEXTENSIONS_H_ +#define KALYPSSO_CORE_KOKKOSARRAYEXTENSIONS_H_ + +#include +#include // for KOKKOS_ENABLE_XXX + +#include // for std::is_integral +#include + +namespace Kokkos +{ + +// ================================================================ +// ================================================================ +/** + * element-wise addition operator for Kokkos::array + */ +template +KOKKOS_FUNCTION constexpr Array +operator+(const Array & a1, const Array & a2) noexcept +{ + Array a3; + for (size_t i = 0; i < N; ++i) + a3[i] = a1[i] + a2[i]; + return a3; +} + +// ================================================================ +// ================================================================ +/** + * element-wise subtraction operator for Kokkos::array + */ +template +KOKKOS_FUNCTION constexpr Array +operator-(const Array & a1, const Array & a2) noexcept +{ + Array a3; + for (size_t i = 0; i < N; ++i) + a3[i] = a1[i] - a2[i]; + return a3; +} + +// ================================================================ +// ================================================================ +/** + * element-wise multiplication operator for Kokkos::array + */ +template +KOKKOS_INLINE_FUNCTION constexpr Array +operator*(const Array & a1, const Array & a2) +{ + Kokkos::Array a3; + for (size_t i = 0; i < N; ++i) + a3[i] = a1[i] * a2[i]; + return a3; +} + +// ================================================================ +// ================================================================ +/** + * element-wise division operator for Kokkos::array + */ +template +KOKKOS_INLINE_FUNCTION constexpr Array +operator/(const Array & a1, const Array & a2) +{ + Kokkos::Array a3; + for (size_t i = 0; i < N; ++i) + a3[i] = a1[i] / a2[i]; + return a3; +} + +// ================================================================ +// ================================================================ +/** + * element-wise addition by a scalar on the left for Kokkos::array + */ +template +KOKKOS_INLINE_FUNCTION constexpr Array +operator+(const T2 & scalar, const Array & a) +{ + Kokkos::Array a2; + for (size_t i = 0; i < N; ++i) + a2[i] = a[i] + static_cast(scalar); + return a2; +} + +// ================================================================ +// ================================================================ +/** + * element-wise addition by a scalar on the right for Kokkos::array + */ +template +KOKKOS_INLINE_FUNCTION constexpr Array +operator+(const Array & a, const T2 & scalar) +{ + Kokkos::Array a2; + for (size_t i = 0; i < N; ++i) + a2[i] = a[i] + static_cast(scalar); + return a2; +} + +// ================================================================= +// ================================================================= +template +KOKKOS_INLINE_FUNCTION constexpr Array & +operator+=(Array & lhs, const Array & rhs) +{ + + for (size_t i = 0; i < N; ++i) + lhs[i] += rhs[i]; + + return lhs; + +} // operator+= + +// ================================================================= +// ================================================================= +template +KOKKOS_INLINE_FUNCTION Array & + operator-=(Array & lhs, const Array & rhs) +{ + + for (size_t i = 0; i < N; ++i) + lhs[i] -= rhs[i]; + + return lhs; + +} // operator-= + +// ================================================================ +// ================================================================ +/** + * element-wise subtraction by a scalar on the left for Kokkos::array + */ +template +KOKKOS_INLINE_FUNCTION constexpr Array +operator-(const T2 & scalar, const Array & a) +{ + Kokkos::Array a2; + for (size_t i = 0; i < N; ++i) + a2[i] = static_cast(scalar) - a[i]; + return a2; +} + +// ================================================================ +// ================================================================ +/** + * element-wise subtraction by a scalar on the right for Kokkos::array + */ +template +KOKKOS_INLINE_FUNCTION constexpr Array +operator-(const Array & a, const T2 & scalar) +{ + Kokkos::Array a2; + for (size_t i = 0; i < N; ++i) + a2[i] = a[i] - static_cast(scalar); + return a2; +} + +// ================================================================ +// ================================================================ +/** + * element-wise multiplication by a scalar on the left for Kokkos::array + */ +// template +// KOKKOS_INLINE_FUNCTION constexpr Array +// operator*(const T2 & scalar, const Array & a) +// { +// Kokkos::Array a2; +// for (size_t i = 0; i < N; ++i) +// a2[i] = a[i] * static_cast(scalar); +// return a2; +// } + +// ================================================================ +// ================================================================ +/** + * element-wise multiplication by a scalar on the left for Kokkos::array + */ +template +KOKKOS_INLINE_FUNCTION constexpr Array +operator*(const T2 scalar, const Array & a) +{ + Kokkos::Array a2; + for (size_t i = 0; i < N; ++i) + a2[i] = a[i] * static_cast(scalar); + return a2; +} + +// ================================================================ +// ================================================================ +/** + * element-wise multiplication by a scalar on the right for Kokkos::array + */ +template +KOKKOS_INLINE_FUNCTION constexpr Array +operator*(const Array & a, const T2 & scalar) +{ + Kokkos::Array a2; + for (size_t i = 0; i < N; ++i) + a2[i] = a[i] * static_cast(scalar); + return a2; +} + +// ================================================================= +// ================================================================= +template +KOKKOS_INLINE_FUNCTION constexpr Array & +operator*=(Array & lhs, const T2 & scalar) +{ + + for (size_t i = 0; i < N; ++i) + lhs[i] *= static_cast(scalar); + + return lhs; + +} // operator*= + +// ================================================================ +// ================================================================ +/** + * element-wise division by a scalar on the right for Kokkos::array + */ +template +KOKKOS_INLINE_FUNCTION constexpr Array +operator/(const Array & a, const T2 & scalar) +{ + Kokkos::Array a2; + for (size_t i = 0; i < N; ++i) + a2[i] = a[i] / static_cast(scalar); + return a2; +} + +// ================================================================ +// ================================================================ +/** + * Compute the product of all items in Kokkos::Array + */ +template +KOKKOS_INLINE_FUNCTION T +dim_prod(const Array & a) +{ + T res = a[0]; + for (size_t i = 1; i < N; ++i) + res *= a[i]; + return res; +} + +// ================================================================ +// ================================================================ +/** + * Check two Kokkos::Array are equal (integral type only). + * + * \return true if two input array are equal element-wise + */ +template +KOKKOS_INLINE_FUNCTION std::enable_if_t, bool> + operator==(const Array & a1, const Array & a2) +{ + if constexpr (N == 0) + return true; + else + { + bool res = a1[0] == a2[0]; + for (size_t i = 1; i < N; ++i) + { + res = res and (a1[i] == a2[i]); + } + return res; + } +} + +} // namespace Kokkos + +namespace kalypsso +{ +// =========================================================== +// =========================================================== +//! Convert a Kokkos::Array into a std::array +//! +//! \note this could be refactored using std::to_array, but currently on available in c++20 +//! \todo refactor when kalypsso will use c++20 +template +std::array +to_std_array(Kokkos::Array array) +{ + std::array res; + res[0] = array[0]; + res[1] = array[1]; + if constexpr (dim == 3) + res[2] = array[2]; + + return res; +} + +template +KOKKOS_INLINE_FUNCTION constexpr Kokkos::Array +init_kokkos_array(const T scalar) +{ + Kokkos::Array res; + for (size_t i = 0; i < N; ++i) + { + res[i] = scalar; + } + return res; +} + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_KOKKOSARRAYEXTENSIONS_H_ diff --git a/src/kalypsso/core/Kokkos_extensions.h b/src/kalypsso/core/Kokkos_extensions.h new file mode 100644 index 0000000..eb66ce0 --- /dev/null +++ b/src/kalypsso/core/Kokkos_extensions.h @@ -0,0 +1,163 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file Kokkos_extensions.h + * Define some extensions to kokkos. + * + * Adapted from Arborx (BSD-3-Clause). + * https://github.com/arborx/ArborX + */ +#ifndef KALYPSSO_CORE_KOKKOSEXTENSIONS_H_ +#define KALYPSSO_CORE_KOKKOSEXTENSIONS_H_ + +#include // for KOKKOS_ENABLE_XXX + +#include +#include + +namespace KokkosExt +{ + +// !Compute the maximum of two values. +template +KOKKOS_INLINE_FUNCTION constexpr T const & +max(T const & a, T const & b) +{ + return (a > b) ? a : b; +} + +//! Compute the minimum of two values. +template +KOKKOS_INLINE_FUNCTION constexpr T const & +min(T const & a, T const & b) +{ + return (a < b) ? a : b; +} + +template +KOKKOS_INLINE_FUNCTION constexpr T +max(std::initializer_list ilist) +{ + auto const * first = ilist.begin(); + auto const * const last = ilist.end(); + auto result = *first; + if (first == last) + { + return result; + } + while (++first != last) + { + if (result < *first) + { + result = *first; + } + } + return result; +} + +template +KOKKOS_INLINE_FUNCTION constexpr T +min(std::initializer_list ilist) +{ + auto const * first = ilist.begin(); + auto const * const last = ilist.end(); + auto result = *first; + if (first == last) + { + return result; + } + while (++first != last) + { + if (*first < result) + { + result = *first; + } + } + return result; +} + +//! Compute the maximum of two values (passed by value). +template +KOKKOS_INLINE_FUNCTION constexpr T const +max_val(T const a, T const b) +{ + return (a > b) ? a : b; +} + +//! Compute the minimum of two values (passed by value). +template +KOKKOS_INLINE_FUNCTION constexpr T const +min_val(T const a, T const b) +{ + return (a < b) ? a : b; +} + +template +typename View::non_const_type +clone(ExecutionSpace const & space, View const & v, std::string const & label) +{ + static_assert(Kokkos::is_execution_space::value); + static_assert(Kokkos::is_view::value); + typename View::non_const_type w(Kokkos::view_alloc(space, Kokkos::WithoutInitializing, label), + v.layout()); + Kokkos::deep_copy(space, w, v); + return w; +} + +template +typename View::non_const_type +clone(ExecutionSpace const & space, View const & v) +{ + return clone(space, v, v.label()); +} + +template +typename View::non_const_type +cloneWithoutInitializingNorCopying(ExecutionSpace const & space, View const & v) +{ + static_assert(Kokkos::is_execution_space::value); + static_assert(Kokkos::is_view::value); + return Kokkos::create_mirror( + Kokkos::view_alloc(typename View::memory_space{}, space, Kokkos::WithoutInitializing), v); +} + +//! clone a Kokkos::UnorderedMap +//! \todo not compiling, analyze why +template +Map +clone_unordered_map(Map const & map) +{ + Map map2; + // note: deep_copy for unordered map is doing memory allocation + Kokkos::deep_copy(map2, map); + return map2; +} + +template +struct is_accessible_from : std::false_type +{ + static_assert(Kokkos::is_memory_space::value); + static_assert(Kokkos::is_execution_space::value); +}; + +template +struct is_accessible_from< + MemorySpace, + ExecutionSpace, + std::enable_if_t::accessible>> + : std::true_type +{}; + +template +struct is_accessible_from_host + : public is_accessible_from +{ + static_assert(Kokkos::is_view::value); +}; + + +} // namespace KokkosExt + +#endif // KALYPSSO_CORE_KOKKOSEXTENSIONS_H_ diff --git a/src/kalypsso/core/LinearCombination.h b/src/kalypsso/core/LinearCombination.h new file mode 100644 index 0000000..25523b0 --- /dev/null +++ b/src/kalypsso/core/LinearCombination.h @@ -0,0 +1,112 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file LinearCombination.h + */ +#ifndef KALYPSSO_CORE_LINEARCOMBINATION_H_ +#define KALYPSSO_CORE_LINEARCOMBINATION_H_ + +#include +#include +#include // for math functions (max, min, ...) +#include // for DataArray, DataArrayHost, DataArrayGhostedBlock +#include + +namespace kalypsso +{ + +// ==================================================================================== +// ==================================================================================== +// ==================================================================================== +/** + * \class LinearCombination + * + * \tparam dim dimension + * \tparam device_t Kokkos device + * + * This a helper class for computing linear combination of two DataArrayBlock's of same sizes; the + * result is put in the second array. + * + */ +template +class LinearCombination +{ + +public: + using exec_space = typename device_t::execution_space; + using index_t = int32_t; + + //! type alias for a data array at block level (see kalypsso_data_container.h) + using DataArrayBlock_t = DataArrayBlock; + using FaceDataArrayBlock_t = FaceDataArrayBlock; + + /** + * Computes linear combination: data1 = coefs[0]*data0 + coefs[1]*data1 + */ + static void + apply(DataArrayBlock_t data0, + DataArrayBlock_t data1, + Kokkos::Array coefs, + int32_t num_octants) + { + KOKKOS_ASSERT((data0.num_cells() == data1.num_cells()) && + "data0 and data1 must have same number of cells per quadrant"); + KOKKOS_ASSERT((data0.num_vars() == data1.num_vars()) && + "data0 and data1 must have same number of variables"); + KOKKOS_ASSERT((data0.num_quadrants() == data1.num_quadrants()) && + "data0 and data1 must have same number of quadrants"); + KOKKOS_ASSERT((num_octants <= data0.num_quadrants()) && "Wrong number of octants"); + + const auto num_cells = data0.num_cells(); + const auto total_num_cells = num_cells * num_octants; + const auto num_vars = data0.num_vars(); + + Kokkos::parallel_for( + "LinearCombination", + Kokkos::RangePolicy(0, total_num_cells), + KOKKOS_LAMBDA(const int32_t & global_index) { + const auto iOct = global_index / num_cells; + const auto cell_index = global_index - iOct * num_cells; + + for (int32_t ivar = 0; ivar < num_vars; ++ivar) + data1(cell_index, ivar, iOct) = + coefs[0] * data0(cell_index, ivar, iOct) + coefs[1] * data1(cell_index, ivar, iOct); + }); + + } // apply + + static void + apply(FaceDataArrayBlock_t data0, + FaceDataArrayBlock_t data1, + Kokkos::Array coefs, + int32_t num_octants) + { + + KOKKOS_ASSERT((data0.num_elements_per_octant() == data1.num_elements_per_octant()) && + "data0 and data1 must have same number of elements per octant"); + + const auto nbFacePerLeaf = data0.num_elements_per_octant(); + const auto total_num_faces = nbFacePerLeaf * num_octants; + + Kokkos::parallel_for( + "LinearCombination", + Kokkos::RangePolicy(0, total_num_faces), + KOKKOS_LAMBDA(const int32_t & global_index) { + const auto iOct = global_index / nbFacePerLeaf; + const int32_t face_flat_index = static_cast(global_index - iOct * nbFacePerLeaf); + const auto & block_sizes = data0.cell_block_size(); + const auto face_indexes = face_flat_index_unravel( + face_flat_index, block_sizes, data0.offsets(), data0.shift()); + + data1(face_indexes, iOct) = + coefs[0] * data0(face_indexes, iOct) + coefs[1] * data1(face_indexes, iOct); + }); + + } // apply +}; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_LINEARCOMBINATION_H_ diff --git a/src/kalypsso/core/Locations.h b/src/kalypsso/core/Locations.h new file mode 100644 index 0000000..5b13309 --- /dev/null +++ b/src/kalypsso/core/Locations.h @@ -0,0 +1,245 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file Locations.h + * + * Define some base structures used in StencilHelper. + */ +#ifndef KALYPSSO_CORE_LOCATIONS_H_ +#define KALYPSSO_CORE_LOCATIONS_H_ + +#include +#include + +#include +#include +#include // for definition of iOct_t +#include // for definition of function cellindex_to_coord and coord_to_cellindex +#include + +namespace kalypsso +{ + +// ==================================================================================== +// ==================================================================================== +// ==================================================================================== +/** + * \class CellLocation + * + * \tparam dim dimension + * + * This class is designed to hold all geometrical information about a cell inside a block (aka + * octant). + */ +template +struct CellLocation +{ + //! cell coord inside block + coord_t ijk; + + //! orchard key of block + uint64_t key; + + //! octant id (so that we avoid an extra hashmap lookup when calling StencilHelper::getNeighLoc + //! in case the neighbor is in the same octant as current cell). + iOct_t iOct; + + //! when computing location of a cell outside domain, this is true. + bool is_outside_domain; + + KOKKOS_INLINE_FUNCTION + decltype(auto) + level() const + { + return orchard_key_t::level(key); + } // level + + KOKKOS_INLINE_FUNCTION auto + cellindex(block_size_t const & bSize) const + { + KOKKOS_ASSERT((ijk[IX] < bSize[IX]) && + "StencilHelper: coordinate and block size are incompatible"); + KOKKOS_ASSERT((ijk[IY] < bSize[IY]) && + "StencilHelper: coordinate and block size are incompatible"); + + if constexpr (dim == 3) + { + KOKKOS_ASSERT((ijk[IZ] < bSize[IZ]) && + "StencilHelper: coordinate and block size are incompatible"); + } + + + if constexpr (dim == 2) + { + return ijk[IX] + bSize[IX] * ijk[IY]; + } + else if constexpr (dim == 3) + { + return ijk[IX] + bSize[IX] * (ijk[IY] + bSize[IY] * ijk[IZ]); + } + } // cellindex + + /** + * return true when current location correspond to the lower left corner of a group of siblings. + * + * This is true if all coordinates are even. + */ + KOKKOS_INLINE_FUNCTION bool + is_eldest_sibling() const + { + bool res = true; + for (uint8_t dir = 0; dir < dim; ++dir) + { + res = res and (ijk[dir] & 0x1) == 0; + } + return res; + } // is_eldest_sibling + +}; // struct CellLocation + +// ==================================================================================== +// ==================================================================================== +// ==================================================================================== +/** + * \class FaceLocation + * + * \tparam dim dimension + * + * This class is designed to hold all geometrical information about a face inside a block (aka + * octant). + */ +template +struct FaceLocation +{ + //! face coordinate inside block + //! the last index (i.e. ijk[dim]) is face normal direction + face_multiindex_t ijk; + + //! orchard key of block + uint64_t key; + + //! octant id (so that we avoid an extra hashmap lookup when calling StencilHelper::getNeighLoc + //! in case the neighbor face is in the same octant as current face). + iOct_t iOct; + + //! when computing location of a cell outside domain, this is true. + bool is_outside_domain; + + KOKKOS_DEFAULTED_FUNCTION + FaceLocation() = default; + + KOKKOS_DEFAULTED_FUNCTION + FaceLocation(FaceLocation const & other) = default; + + KOKKOS_DEFAULTED_FUNCTION + FaceLocation & + operator=(FaceLocation const & other) = default; + + KOKKOS_INLINE_FUNCTION + decltype(auto) + level() const + { + return orchard_key_t::level(key); + } // level + + KOKKOS_INLINE_FUNCTION + bool + is_valid(block_size_t const & bSize) const + { + auto const & face_dir = ijk[dim]; + + if constexpr (dim == 2) + { + auto const & i = ijk[IX]; + auto const & j = ijk[IY]; + + if (face_dir == IX) + { + return (i >= 0 and i <= bSize[IX]) and (j >= 0 and j < bSize[IY]); + } + else if (face_dir == IY) + { + return (j >= 0 and j <= bSize[IY]) and (i >= 0 and i < bSize[IX]); + } + } + else if constexpr (dim == 3) + { + auto const & i = ijk[IX]; + auto const & j = ijk[IY]; + auto const & k = ijk[IZ]; + + // clang-format off + if (face_dir == IX) + { + return (i >= 0 and i <= bSize[IX]) and (j >= 0 and j < bSize[IY]) and (k >= 0 and k < bSize[IZ]); + } + else if (face_dir == IY) + { + return (j >= 0 and j <= bSize[IY]) and (k >= 0 and k < bSize[IZ]) and (i >= 0 and i < bSize[IX]); + } + else if (face_dir == IZ) + { + return (k >= 0 and k <= bSize[IZ]) and (i >= 0 and i < bSize[IX]) and (j >= 0 and j < bSize[IY]); + } + // clang-format on + } + } // is_valid + +}; // struct FaceLocation + +// ==================================================================================== +// ==================================================================================== +// ==================================================================================== +/** + * \class EdgeLocation + * + * \tparam dim dimension + * + * This class is designed to hold all geometrical information about an edge inside a block (aka + * octant). + */ +template +struct EdgeLocation +{ + //! edge coordinate inside block + //! the last index (i.e. ijk[dim]) is edge direction + edge_multiindex_t ijk; + + //! orchard key of block + uint64_t key; + + //! octant id (so that we avoid an extra hashmap lookup when calling StencilHelper::getNeighLoc + //! in case the neighbor face is in the same octant as current face). + iOct_t iOct; + + //! when computing location of a cell outside domain, this is true. + bool is_outside_domain; + + //! it is always true, except when the edge location is obtained from getEdgeSibling at a hanging + //! edge (touching a coarser neighbor through the middle of a face) + bool is_valid = true; + + KOKKOS_DEFAULTED_FUNCTION + EdgeLocation() = default; + + KOKKOS_DEFAULTED_FUNCTION + EdgeLocation(EdgeLocation const & other) = default; + + KOKKOS_DEFAULTED_FUNCTION + EdgeLocation & + operator=(EdgeLocation const & other) = default; + + KOKKOS_INLINE_FUNCTION + decltype(auto) + level() const + { + return orchard_key_t::level(key); + } // level + +}; // struct EdgeLocation + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_LOCATIONS_H_ diff --git a/src/kalypsso/core/MaterialPresence.h b/src/kalypsso/core/MaterialPresence.h new file mode 100644 index 0000000..5e5ecd3 --- /dev/null +++ b/src/kalypsso/core/MaterialPresence.h @@ -0,0 +1,516 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file MaterialPresence.h + */ + +#ifndef KALYPSSO_CORE_MATERIAL_PRESENCE_H_ +#define KALYPSSO_CORE_MATERIAL_PRESENCE_H_ + +#include +#include +#include +#include + +namespace kalypsso +{ + +/** + * \class MaterialPresenceView + * + * \brief Indicates the presence of a material inside an octant + * + * A MaterialPresenceView is essentially a wrapper around a view of bitsets with extra metadata. + * Each bit in the bitsets correspond to a material and is set to 0 if it is not present and 1 if it + * is. + * + * For example, if we have the following setup: + * + * | + * Oct 2 | Oct 3 + * Mat 1 | Mat 0 + * | + * ----------+---------- + * | + * Oct 0 | Oct 1 + * Mat 1, 2 | Mat 0, 2 + * | + * + * Then the object will look like this (if the problem contains 3 materials total) + * + * Oct 0 | Oct 1 | Oct 2 | Oct 3 + * -------+-------+-------+------- + * 1 1 0 | 1 0 1 | 0 1 0 | 0 0 1 + * + * Names: + * - Material Number (or `mat_num`) is the absolute index of the material in the system. It's the + * numbers used just above. + * - Material Index (or `mat_index`) is the local index of the material inside an octant. For + * example, the Material Index of material 2 in octant 0 or 1 is 2 and is undefined in octant 2 + * or 3. + * + * \tparam device_t Where the inner array is located + */ +template +class MaterialPresenceView +{ +public: + using BitBlock_t = uint64_t; + using View_t = Kokkos::View; + using MaterialPresenceView_t = MaterialPresenceView; + + /** + * \brief Creates a new MaterialPresenceView + */ + MaterialPresenceView(const std::string & name, const uint32_t max_mat, const uint32_t num_octs) + : m_max_mat(max_mat) + , m_len_per_oct((max_mat / NUM_BITS_PER_BLOCK) + (max_mat % NUM_BITS_PER_BLOCK != 0)) + , m_num_octants(num_octs) + , m_mat_pres(name, + DataArrayUtils::allocated_capacity(static_cast(m_len_per_oct * num_octs))) + {} + + /** + * \brief Default constructor + */ + MaterialPresenceView() = default; + + /** + * \brief Move constructor + */ + MaterialPresenceView(const uint32_t max_mat, + const uint32_t len_per_oct, + const uint32_t num_octs, + View_t && mat_pres) + : m_max_mat(max_mat) + , m_len_per_oct(len_per_oct) + , m_num_octants(num_octs) + , m_mat_pres(mat_pres) + {} + + /** + * \brief Creates host mirror + */ + static auto + create_host_mirror_view(MaterialPresenceView_t src) + { + return MaterialPresenceView(src.m_max_mat, + src.m_len_per_oct, + src.m_num_octants, + Kokkos::create_mirror_view(src.m_mat_pres)); + } + + /** + * \brief Creates host mirror and copy + */ + static auto + create_host_mirror_view_and_copy(MaterialPresenceView_t src) + { + return MaterialPresenceView( + src.m_max_mat, + src.m_len_per_oct, + src.m_num_octants, + Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, src.m_mat_pres)); + } + + // ================================================================================== + /** + * \brief Access to raw pointer of internal data + */ + KOKKOS_FORCEINLINE_FUNCTION + auto + data() const + { + return m_mat_pres.data(); + } + + /** + * \brief Gets the internal view (raw) + */ + auto + physical_view() + { + return m_mat_pres; + } + + /** + * \brief Gets the internal view (raw) + */ + KOKKOS_INLINE_FUNCTION auto const + physical_view() const + { + return m_mat_pres; + } + + // ================================================================================== + //! return logical size (total number of elements). Not to be confused with capacity (physical + //! size) + auto + logical_size_in_elements() const + { + return static_cast(m_len_per_oct * m_num_octants); + } + + // ================================================================================== + //! return internal storage as a Kokkos::view sized upon the logical number of octant + auto + logical_view() const + { + const auto logical_range = + std::pair(0, this->logical_size_in_elements()); + return Kokkos::subview(m_mat_pres, logical_range); + } + + /** + * \brief Zeroes out the material presence + */ + void + resets() const + { + Kokkos::deep_copy(m_mat_pres, 0); + } + + /** + * \brief Return inner array capacity. + */ + KOKKOS_INLINE_FUNCTION auto + capacity() const + { + return m_mat_pres.size(); + } + + /** + * \brief Resizes the inner array. + * + * \note Memory will be re-allocated only the new requested size is larger than capacity, + * otherwise we just update the (logical) number of octants. + */ + void + resize(const int32_t num_octs) + { + m_num_octants = static_cast(num_octs); + + if (logical_size_in_elements() > capacity()) + { + size_t new_storage_capacity = + DataArrayUtils::allocated_capacity(static_cast(m_len_per_oct * m_num_octants)); + + Kokkos::resize(m_mat_pres, new_storage_capacity); + } + } + + /** + * \brief Gets the inner size in bytes + */ + auto + allocated_size_in_bytes() const + { + return capacity() * sizeof(typename View_t::value_type); + } + + /** + * \brief Gets the number of octants + */ + KOKKOS_INLINE_FUNCTION + auto + size() const + { + return m_num_octants; + } + + /** + * \brief Gets the maximum number of materials + */ + KOKKOS_INLINE_FUNCTION + auto + max_mat() const + { + return m_max_mat; + } + + /** + * \brief Gets the label + */ + auto + label() const + { + return m_mat_pres.label(); + } + + /** + * \brief Gets the number of bit blocks per octant + */ + auto + block_length_per_octant() const + { + return static_cast(m_len_per_oct); + } + + /** + * \brief Returns true if the material number is considered present + */ + KOKKOS_INLINE_FUNCTION bool + get(int32_t i_oct, int32_t mat_num) const + { +#ifdef KALYPSSO_CORE_DEBUG_BOUNDS_CHECK + KOKKOS_ASSERT((static_cast(mat_num) < m_max_mat) && "Wrong value for mat_num"); + KOKKOS_ASSERT((static_cast(i_oct) < m_num_octants) && "Wrong value for i_oct"); +#endif + + return (m_mat_pres(var(i_oct, mat_num)) & bit(mat_num)) != 0; + } + + /** + * \brief Sets the material presence + */ + KOKKOS_INLINE_FUNCTION void + set(int32_t i_oct, int32_t mat_num) const + { +#ifdef KALYPSSO_CORE_DEBUG_BOUNDS_CHECK + KOKKOS_ASSERT((static_cast(mat_num) < m_max_mat) && "Wrong value for mat_num"); + KOKKOS_ASSERT((static_cast(i_oct) < m_num_octants) && "Wrong value for i_oct"); +#endif + + m_mat_pres(var(i_oct, mat_num)) |= bit(mat_num); + } + + /** + * \brief Unsets the material presence + */ + KOKKOS_INLINE_FUNCTION void + unset(int32_t i_oct, int32_t mat_num) const + { +#ifdef KALYPSSO_CORE_DEBUG_BOUNDS_CHECK + KOKKOS_ASSERT((static_cast(mat_num) < m_max_mat) && "Wrong value for mat_num"); + KOKKOS_ASSERT((static_cast(i_oct) < m_num_octants) && "Wrong value for i_oct"); +#endif + + m_mat_pres(var(i_oct, mat_num)) &= ~bit(mat_num); + } + + /** + * \brief Returns the index of the material number. It is supposed that get(i_oct, mat_num) == + * true. + * + * It essentially counts the number of bits set before (and excluding) the material number's bit. + */ + KOKKOS_INLINE_FUNCTION int32_t + material_index(int32_t i_oct, int32_t mat_num) const + { +#ifdef KALYPSSO_CORE_DEBUG_BOUNDS_CHECK + KOKKOS_ASSERT((static_cast(mat_num) < m_max_mat) && "Wrong value for mat_num"); + KOKKOS_ASSERT((static_cast(i_oct) < m_num_octants) && "Wrong value for i_oct"); + KOKKOS_ASSERT((get(i_oct, mat_num)) && "Wrong value for mat_num"); +#endif + + return material_index_unchecked(i_oct, mat_num); + } + + /** + * \brief Returns the material number at the material index for said octant. It is guaranteed that + * get(i_oct, material_num(n)) == true. Is the inverse of 'material_index' + * + * It essentially counts the number of bits set and the index until we reach 'mat_index' + */ + KOKKOS_INLINE_FUNCTION int32_t + material_num(int32_t i_oct, int32_t mat_index) const + { +#ifdef KALYPSSO_CORE_DEBUG_BOUNDS_CHECK + KOKKOS_ASSERT((static_cast(i_oct) < m_num_octants) && "Wrong value for i_oct"); + KOKKOS_ASSERT((mat_index < num_materials(i_oct)) && "Wrong value for mat_index"); +#endif + + int32_t total = 0; + const uint32_t start_var = var(i_oct, 0); + uint32_t nvar = start_var; + + // We count the number of '1' until we get over the material index + for (; total <= mat_index && nvar < start_var + m_len_per_oct; nvar++) + total += bit_block_popcount(m_mat_pres(nvar)); + + auto bit_block = m_mat_pres(nvar - 1); + total -= bit_block_popcount(bit_block); + + // Then we count until we have our number + for (uint8_t i = 0; i < NUM_BITS_PER_BLOCK; i++) + if (total + bit_block_popcount(bit_block, i + 1) > mat_index) + return static_cast(((nvar - 1 - start_var) * NUM_BITS_PER_BLOCK) + i); + + // Failsafe + return static_cast(m_max_mat); + } + + /** + * \brief Returns the total number of materials + * + * Simply counts the total number of set bits + */ + KOKKOS_INLINE_FUNCTION auto + num_materials(const int32_t i_oct) const + { + // Index of last material + 1 + return material_index_unchecked(i_oct, static_cast(m_max_mat)); + } + + /** + * \brief Updates the left set at octant with the other one + */ + KOKKOS_INLINE_FUNCTION static void + update(const MaterialPresenceView_t & lhs, + const int32_t l_oct, + const MaterialPresenceView_t & rhs, + const int32_t r_oct) + { +#ifdef KALYPSSO_CORE_DEBUG_BOUNDS_CHECK + KOKKOS_ASSERT((lhs.m_len_per_oct && rhs.m_len_per_oct) && "Wrong value for lhs and rhs"); + KOKKOS_ASSERT((static_cast(l_oct) * lhs.m_len_per_oct < lhs.m_mat_pres.size()) && + "Wrong value for l_oct"); + KOKKOS_ASSERT((static_cast(r_oct) * rhs.m_len_per_oct < rhs.m_mat_pres.size()) && + "Wrong value for r_oct"); +#endif + + for (size_t i = 0; i < lhs.m_len_per_oct; i++) + lhs.m_mat_pres(lhs.var(l_oct, 0) + i) |= rhs.m_mat_pres(rhs.var(r_oct, 0) + i); + } + + /** + * \brief Restricts the left set at octant with the other one + */ + KOKKOS_INLINE_FUNCTION static void + do_restriction(const MaterialPresenceView_t & lhs, + const int32_t l_oct, + const MaterialPresenceView_t & rhs, + const int32_t r_oct) + { +#ifdef KALYPSSO_CORE_DEBUG_BOUNDS_CHECK + KOKKOS_ASSERT((lhs.m_len_per_oct && rhs.m_len_per_oct) && "Wrong value for lhs and rhs"); + KOKKOS_ASSERT((static_cast(l_oct) * lhs.m_len_per_oct < lhs.m_mat_pres.size()) && + "Wrong value for l_oct"); + KOKKOS_ASSERT((static_cast(r_oct) * rhs.m_len_per_oct < rhs.m_mat_pres.size()) && + "Wrong value for r_oct"); +#endif + + for (size_t i = 0; i < lhs.m_len_per_oct; i++) + lhs.m_mat_pres(lhs.var(l_oct, 0) + i) &= rhs.m_mat_pres(rhs.var(r_oct, 0) + i); + } + + /** + * \brief Copies the right set at octant on the other one + */ + KOKKOS_INLINE_FUNCTION static void + copy(const MaterialPresenceView_t & lhs, + const int32_t l_oct, + const MaterialPresenceView_t & rhs, + const int32_t r_oct) + { +#ifdef KALYPSSO_CORE_DEBUG_BOUNDS_CHECK + KOKKOS_ASSERT((lhs.m_len_per_oct && rhs.m_len_per_oct) && "Wrong value for lhs and rhs"); + KOKKOS_ASSERT((static_cast(l_oct) * lhs.m_len_per_oct < lhs.m_mat_pres.size()) && + "Wrong value for l_oct"); + KOKKOS_ASSERT((static_cast(r_oct) * rhs.m_len_per_oct < rhs.m_mat_pres.size()) && + "Wrong value for r_oct"); +#endif + + for (size_t i = 0; i < lhs.m_len_per_oct; i++) + lhs.m_mat_pres(lhs.var(l_oct, 0) + i) = rhs.m_mat_pres(rhs.var(r_oct, 0) + i); + } + + + /** + * \brief Helper that computes the num_var array (then given to DataArrayBlockMultiVar) + */ + void + compute_num_vars(const uint32_t num_vars_per_mat, + Kokkos::View num_vars) const + { + Kokkos::RangePolicy policy(0, num_vars.size()); + Kokkos::parallel_for( + "kalypsso::MaterialPresence::compute_num_vars", + policy, + KOKKOS_CLASS_LAMBDA(const uint32_t i_oct) { + num_vars(i_oct) = num_materials(i_oct) * num_vars_per_mat; + }); + } + + +private: + static constexpr auto NUM_BITS_PER_BLOCK = static_cast(sizeof(BitBlock_t) * 8); + + /** + * \brief Returns the index of the material number. + * + * It essentially counts the number of bits set before (and excluding) the material number's bit. + */ + KOKKOS_INLINE_FUNCTION int32_t + material_index_unchecked(int32_t i_oct, int32_t mat_num) const + { + int32_t total = 0; + const auto start_var = var(i_oct, 0); + const auto end_var = var(i_oct, mat_num); + + // Counts the '1' in bit blocks before the nth bit + for (uint32_t i = start_var; i < end_var; i++) + total += bit_block_popcount(m_mat_pres(i)); + + // Counts the remaining '1' + total += + bit_block_popcount(m_mat_pres(end_var), static_cast(mat_num) % NUM_BITS_PER_BLOCK); + return total; + } + + /** + * \brief Given a material number, returns a mask of said bit in a bit block + */ + KOKKOS_INLINE_FUNCTION BitBlock_t + bit(int32_t mat_num) const + { + return BitBlock_t(1) << (static_cast(mat_num) % NUM_BITS_PER_BLOCK); + } + + /** + * \brief Given a material number and an octant, returns the block index of where it is located + */ + KOKKOS_INLINE_FUNCTION uint32_t + var(int32_t i_oct, int32_t mat_num) const + { + return static_cast(i_oct) * m_len_per_oct + + static_cast(mat_num) / NUM_BITS_PER_BLOCK; + } + + /** + * \brief Helper for counting the number of bits inside a block or a block's prefix. + * + * Is essentially a wrapper around a popcount operation. It uses Kokkos' experimental popcount + * because it calls compiler's specific code to execute popc. The ternary operator is used + * because if offset == NUM_BITS_PER_BLOCK (eg the bit width of MAX_VAR), then the shift is + * ignored. + */ + static KOKKOS_INLINE_FUNCTION uint8_t + bit_block_popcount(BitBlock_t v, uint8_t max_bit_mask = NUM_BITS_PER_BLOCK) + { + static constexpr BitBlock_t MAX_VAR = Kokkos::Experimental::finite_max_v; + const uint8_t offset = NUM_BITS_PER_BLOCK - max_bit_mask; + return (max_bit_mask == 0) ? 0 + : static_cast( + Kokkos::Experimental::popcount_builtin(v & (MAX_VAR >> offset))); + } + + //! Indicates the maximum number of materials + uint32_t m_max_mat; + + //! Indicates the length of the bitset + uint32_t m_len_per_oct; + + //! number of octants (must be smaller of equal to m_mat_pres actual size) + uint32_t m_num_octants; + + //! Actual inner view + View_t m_mat_pres; + +}; // class MaterialPresenceView + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_MATERIAL_PRESENCE_H_ diff --git a/src/kalypsso/core/MaterialPresenceExchanger.cpp b/src/kalypsso/core/MaterialPresenceExchanger.cpp new file mode 100644 index 0000000..44b0fbc --- /dev/null +++ b/src/kalypsso/core/MaterialPresenceExchanger.cpp @@ -0,0 +1,243 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file MaterialPresenceExchanger.cpp + */ + +#include + +namespace kalypsso +{ + +// =========================================================================== +// =========================================================================== +template +void +MaterialPresenceExchanger::exchange( + MaterialPresenceView_t userdata_material_presence) +{ + + // check if (re-)allocation needed + if (static_cast(m_amr_mesh.local_num_mirrors()) != m_send.size() or + static_cast(m_amr_mesh.local_num_ghosts()) != m_recv.size()) + allocate_send_recv_buffers(); + + pack_mirror_data(userdata_material_presence, m_mesh_map.mirror_orchard_keys()); + + do_mpi_send_recv(); + + unpack_ghost_data(userdata_material_presence); + +} // exchange + +// =========================================================================== +// =========================================================================== +template +auto +MaterialPresenceExchanger::allocated_size_in_bytes() const -> size_t +{ + return m_send.allocated_size_in_bytes() + m_recv.allocated_size_in_bytes(); +} + +// =========================================================================== +// =========================================================================== +template +void +MaterialPresenceExchanger::allocate_send_recv_buffers() +{ + // number of mirror quadrants to send in current MPI process + // IMPORTANT: not to be confused with p4est ghost->mirrors.elem_count + // which contains all the mirror quadrants without redundancy + // several mirror quadrants are sent to multiple MPI neighbor process + // auto num_mirror_quad = m_amr_mesh.ghost()->mirrors.elem_count; + auto num_mirror_octs = m_amr_mesh.local_num_mirrors(); + m_send.resize(num_mirror_octs); + + // number of ghost quadrants in current MPI process + auto num_ghost_octs = m_amr_mesh.local_num_ghosts(); + m_recv.resize(num_ghost_octs); + +} // allocate_send_recv_buffers + +// =========================================================================== +// =========================================================================== +template +void +MaterialPresenceExchanger::pack_mirror_data( + MaterialPresenceView_t userdata_material_presence, + orchard_key_view_t mirror_keys_device) +{ + auto amr_hashmap = m_mesh_map.hashmap(); + auto num_mirror_quad = m_amr_mesh.local_num_mirrors(); + + [[maybe_unused]] auto local_num_quadrants = m_amr_mesh.local_num_quadrants(); + + // make sure m_send buffer was allocated with the right size + assertm(static_cast(num_mirror_quad) == m_send.size(), + "[MaterialPresenceExchanger::pack_mirror_data] send buffer has wrong size"); + + { + // traverse the list of mirror keys, and for each key try to find it in the old hashmap + Kokkos::parallel_for( + "pack_mirror_data", + Kokkos::RangePolicy(0, num_mirror_quad), + KOKKOS_CLASS_LAMBDA(const int32_t & imirror) { + auto key = mirror_keys_device(imirror); + + // find key index in map + auto key_index_map = amr_hashmap.find(key); + + // first check if key exists in the hashmap, if it exists, it means we found a matching + // owned mirror quadrant + if (amr_hashmap.valid_at(key_index_map)) + { + auto userdata_index = static_cast(amr_hashmap.value_at(key_index_map)); + KOKKOS_ASSERT( + userdata_index < local_num_quadrants && + "[MaterialPresenceExchanger::pack_mirror_data] index must correspond to a locally " + "owned quadrant."); + MaterialPresenceView_t::copy(m_send, imirror, userdata_material_presence, userdata_index); + } + }); + } + + Kokkos::fence(); +} // pack_mirror_data + +// =========================================================================== +// =========================================================================== +template +void +MaterialPresenceExchanger::do_mpi_send_recv() +{ + // total number of MPI procs + auto num_procs = m_par_env.size(); + + // vector of MPI request for asynchronous send and receives + std::vector requests; + + uint32_t num_requests_recv = 0; + uint32_t num_requests_send = 0; + + // the following could be factorized in AMRmesh class (where the ghost object is stored) + // + // Compute the total number of MPI request (send and receive) + // + for (auto iproc = 0; iproc < num_procs; ++iproc) + { + auto nghosts = m_amr_mesh.local_num_ghosts(iproc); + KALYPSSO_ASSERT(nghosts >= 0); + if (nghosts > 0) + { + num_requests_recv++; + } + + auto nmirrors = m_amr_mesh.local_num_mirrors(iproc); + KALYPSSO_ASSERT(nmirrors >= 0); + if (nmirrors > 0) + { + num_requests_send++; + } + } + requests.reserve(num_requests_recv + num_requests_send); + + // + // Start to receive ghost data. num_recv is the total number of message to receive. + // + uint32_t num_recv = 0; + for (auto iproc = 0; iproc < num_procs; ++iproc) + { + // number of ghosts quadrants owned by remote MPI process iproc + auto nghosts = m_amr_mesh.local_num_ghosts(iproc); + KALYPSSO_ASSERT(nghosts >= 0); + + // if nghosts = 0, it means MPI process don't actually own ghosts, we won't receive any data + // from MPI process iproc + + // remote MPI process iproc actually owns interesting data, we want to receive those data + if (nghosts > 0) + { + auto recv_range = std::pair( + m_amr_mesh.ghost()->proc_offsets[iproc] * m_recv.block_length_per_octant(), + (m_amr_mesh.ghost()->proc_offsets[iproc] + nghosts) * m_recv.block_length_per_octant()); + auto recv_buff = Kokkos::subview(m_recv.logical_view(), recv_range); + + requests[num_recv] = + m_par_env.comm().MPI_Irecv(recv_buff, iproc, KALYPSSO_COMM_GHOST_EXCHANGE_TAG); + + num_recv++; + } + } // end for iproc + + // + // Start to send mirror data. num_send is the total number of message to send. + // + uint32_t num_send = 0; + for (auto iproc = 0; iproc < num_procs; ++iproc) + { + // number of mirror quadrants owned by current MPI process to be filled + auto nmirrors = m_amr_mesh.local_num_mirrors(iproc); + KALYPSSO_ASSERT(nmirrors >= 0); + + // if nmirrors = 0, it means current MPI process don't need to send data to MPI process + // iproc + + if (nmirrors > 0) + { + auto send_range = std::pair( + m_amr_mesh.ghost()->mirror_proc_offsets[iproc] * m_send.block_length_per_octant(), + (m_amr_mesh.ghost()->mirror_proc_offsets[iproc] + nmirrors) * + m_send.block_length_per_octant()); + auto send_buff = Kokkos::subview(m_send.logical_view(), send_range); + + requests[num_requests_recv + num_send] = + m_par_env.comm().MPI_Isend(send_buff, iproc, KALYPSSO_COMM_GHOST_EXCHANGE_TAG); + + num_send++; + } + } // end for iproc + + // + // let's wait for all send/recv comm to finish + // + m_par_env.comm().MPI_Waitall(num_requests_recv + num_requests_send, requests.data()); + +} // do_mpi_send_recv + +// =========================================================================== +// =========================================================================== +template +void +MaterialPresenceExchanger::unpack_ghost_data( + MaterialPresenceView_t userdata_material_presence) +{ + // get the number of owned quadrants in local MPI process + const auto num_owned_quad = m_amr_mesh.local_num_quadrants(); + + // get the number of ghost quadrants in local MPI process + const auto num_ghost_quad = m_amr_mesh.local_num_ghosts(); + + // number of cells per block, and scalar values per cell + const auto num_elts_per_quad = userdata_material_presence.block_length_per_octant(); + + // make sure m_recv buffer was allocated with the right size + assertm(static_cast(num_ghost_quad) == m_recv.size(), + "[MaterialPresenceExchanger::unpack_ghost_data] receive buffer has wrong size"); + + auto recv_range = std::pair( + num_elts_per_quad * num_owned_quad, num_elts_per_quad * (num_owned_quad + num_ghost_quad)); + auto userdata_mp_ghost = Kokkos::subview(userdata_material_presence.logical_view(), recv_range); + + Kokkos::deep_copy(userdata_mp_ghost, m_recv.logical_view()); + + Kokkos::fence(); + +} // unpack_ghost_data + +// explicit template instantiation +template class MaterialPresenceExchanger<2, DefaultDevice>; +template class MaterialPresenceExchanger<3, DefaultDevice>; + +} // namespace kalypsso diff --git a/src/kalypsso/core/MaterialPresenceExchanger.h b/src/kalypsso/core/MaterialPresenceExchanger.h new file mode 100644 index 0000000..4da8516 --- /dev/null +++ b/src/kalypsso/core/MaterialPresenceExchanger.h @@ -0,0 +1,162 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file MaterialPresenceExchanger.h + */ + +#ifndef KALYPSSO_CORE_MATERIALPRESENCEEXCHANGER_H_ +#define KALYPSSO_CORE_MATERIALPRESENCEEXCHANGER_H_ + +#include // for assertm +#include +#include // for DataArray, DataArrayHost +#include + +#include +#include + +#include +#include // for orchard_key_view_t and amr_map_t +#include + +#include +#include + +namespace kalypsso +{ + +/** + * \class MaterialPresenceExchanger + * + * \brief Does the same thing as MeshGhostsExchanger but specialized for material presence. + */ +template +class MaterialPresenceExchanger +{ +public: + using exec_space = typename device_t::execution_space; + + using MaterialPresenceView_t = MaterialPresenceView; + + using comm_buffer_t = MaterialPresenceView_t; + + using orchard_key_view_t = typename orchard_key_base_t::view_t; + using amr_hashmap_t = typename hashmap_base_t::map_t; + + // ========================================================================= + // ========================================================================= + //! constructor + MaterialPresenceExchanger(const ConfigMap & config_map, + const ParallelEnv & par_env, + AMRmesh & amr_mesh, + MeshMap & mesh_map) + : m_config_map(config_map) + , m_par_env(par_env) + , m_amr_mesh(amr_mesh) + , m_mesh_map(mesh_map) + , m_send("MaterialPresenceExchanger::allocate send", + static_cast(config_map.getInteger("run", "nmat", 1)), + 0) + , m_recv("MaterialPresenceExchanger::allocate recv", + static_cast(config_map.getInteger("run", "nmat", 1)), + 0) + {} + + // ========================================================================= + // ========================================================================= + //! destructor + ~MaterialPresenceExchanger() = default; + + // ========================================================================= + // ========================================================================= + //! do it all, pack, exchange, unpack. + //! + //! \param[in] userdata_block is a userdata block array (i.e. one value per face); must + //! be sized upon the total number of octant in current MPI process (owned + ghost) + void + exchange(MaterialPresenceView_t userdata_material_presence); + + // ========================================================================= + // ========================================================================= + auto + allocated_size_in_bytes() const -> size_t; + + // ========================================================================= + // ========================================================================= + //! allocate device buffer for performing ghost data exchange + //! + void + allocate_send_recv_buffers(); + + // ========================================================================= + // ========================================================================= + //! pack mirror data into send buffer (device function) - block (cell-center) version. + //! + //! implementation use array of orchard key (mirror quads) to address userdata and copy data into + //! send buffer. + //! + //! \param[in] userdata_material_presence is supposed to be of size (forest->local_num_quadrants + + //! ghost->ghosts>elem_count) + //! \param[in] mirror_keys_device orchard keys of mirror quadrant + //! + //! IMPORTANT NOTE: remember that orchard keys in mirror array are sorted first by MPI processor + //! to send to and secondly by Morton order. + //! + //! make sure that MeshMap object is up-to-date before using this; we need the unordered map + //! (orchard keys, index) to be valid, i.e. calling MeshMap::fill_map is necessary after any mesh + //! changes (p4est_balance, p4est_partition) + void + pack_mirror_data(MaterialPresenceView_t userdata_material_presence, + orchard_key_view_t mirror_keys_device); + + // ========================================================================= + // ========================================================================= + //! actually perform MPI comm to send and receive ghost quadrant userdata + void + do_mpi_send_recv(); + + // ========================================================================= + // ========================================================================= + //! unpack received data (recv buffer) into ghost data (cell-center block). + //! + //! we will fill all ghost quadrant data, i.e. from index forest->local_num_quad + //! to index forest->local_num_quadrants + ghost->ghosts>elem_count + //! + //! \param[in] MaterialPresenceView_t userdata_material_presence is supposed to be of size + //! (forest->local_num_quadrants + ghost->ghosts->elem_count) + //! + void + unpack_ghost_data(MaterialPresenceView_t userdata_material_presence); + + // ========================================================================= + // ========================================================================= +private: + //! config map (input parameter) + const ConfigMap & m_config_map; + + //! parallel environment + const ParallelEnv & m_par_env; + + //! AMRmesh reference object, mostly for accessing p4est ghost object + AMRmesh & m_amr_mesh; + + //! a MeshMap object so that we can extract the orchard keys as a kokkos view or unordered map + MeshMap & m_mesh_map; + + //! send buffers + comm_buffer_t m_send; + + //! recv buffers + comm_buffer_t m_recv; + +}; // class MaterialPresenceExchanger + +// explicit template instantiation +extern template class MaterialPresenceExchanger<2, DefaultDevice>; +extern template class MaterialPresenceExchanger<3, DefaultDevice>; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_MATERIALPRESENCEEXCHANGER_H_ diff --git a/src/kalypsso/core/MeshGhostsExchanger.cpp b/src/kalypsso/core/MeshGhostsExchanger.cpp new file mode 100644 index 0000000..38009e8 --- /dev/null +++ b/src/kalypsso/core/MeshGhostsExchanger.cpp @@ -0,0 +1,876 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file MeshGhostsExchanger.cpp + */ + +#include + +#include + +namespace kalypsso +{ +// ========================================================================= +// ========================================================================= +template +void +MeshGhostsExchanger::resize(size_t data_size_per_quad) +{ + const auto current_capacity = m_storage.size(); + const auto num_blocks = static_cast(m_amr_mesh.local_num_mirrors()) + + static_cast(m_amr_mesh.local_num_ghosts()); + const auto requested_capacity = data_size_per_quad * num_blocks; + + if (requested_capacity > current_capacity) + { + // enlarge capacity by factor capacity_growth_rate + size_t new_storage_capacity = + DataArrayUtils::allocated_capacity(static_cast(requested_capacity)); + + Kokkos::resize( + Kokkos::view_alloc(Kokkos::WithoutInitializing), m_storage, new_storage_capacity); + } + + const auto offset = data_size_per_quad * static_cast(m_amr_mesh.local_num_mirrors()); + // clang-format off + m_send = + comm_buffer_unmanaged_t(m_storage.data(), + data_size_per_quad * static_cast(m_amr_mesh.local_num_mirrors())); + m_recv = + comm_buffer_unmanaged_t(m_storage.data() + offset, + data_size_per_quad * static_cast(m_amr_mesh.local_num_ghosts())); + // clang-format on + + // This is essentially what the value is + m_data_size_per_quad = data_size_per_quad; + +} // resize + +// ========================================================================= +// ========================================================================= +template +auto +MeshGhostsExchanger::allocated_size_in_bytes() const -> size_t +{ + size_t size = m_storage.size() * sizeof(T); + return size; +} + +// ========================================================================= +// ========================================================================= +template +void +MeshGhostsExchanger::pack_mirror_data(DataArrayLeaf_t userdata_leaf, + orchard_key_view_t mirror_keys_device) +{ + + auto local_num_mirrors = m_amr_mesh.local_num_mirrors(); + + // make sure m_send buffer was allocated with the right size + assertm(static_cast(local_num_mirrors) * userdata_leaf.extent(1) == m_send.extent(0), + "[MeshGhostsExchanger::pack_mirror_data] send buffer has wrong size"); + + [[maybe_unused]] auto local_num_quadrants = m_amr_mesh.local_num_quadrants(); + + // number of scalar values per quad + auto num_vars = userdata_leaf.extent(1); + + DataArrayLeafUnmanaged_t mir_data = DataArrayLeafUnmanaged_t( + m_send.data(), static_cast(local_num_mirrors), static_cast(num_vars)); + + auto amr_hashmap = m_mesh_map.hashmap(); + + // traverse the list of mirror keys, and for each key try to find it in the old hashmap + Kokkos::parallel_for( + "pack_mirror_data - leaf", + Kokkos::RangePolicy(0, local_num_mirrors), + KOKKOS_LAMBDA(const int imirror) { + auto key = mirror_keys_device(imirror); + + // find key index in map + auto key_index_map = amr_hashmap.find(key); + + // first check if key exists in the hashmap, if it exists, it means we found a matching + // owned mirror quadrant + if (amr_hashmap.valid_at(key_index_map)) + { + auto index = amr_hashmap.value_at(key_index_map); + + KOKKOS_ASSERT(index < local_num_quadrants && + "[MeshGhostsExchanger::pack_mirror_data] index must correspond to a locally " + "owned quadrant."); + + for (uint32_t ivar = 0; ivar < num_vars; ++ivar) + { + mir_data(imirror, ivar) = userdata_leaf(index, ivar); + } + } + else + { + // printf("[MeshGhostsExchanger::pack_mirror_data] Invalid key !\n"); + } + }); + + Kokkos::fence(); + + // now m_send (aka mir_data) is ready to be sent + +} // pack_mirror_data - leaf version + +// ========================================================================= +// ========================================================================= +template +void +MeshGhostsExchanger::pack_mirror_data(DataArrayBlock_t userdata_block, + orchard_key_view_t mirror_keys_device) +{ + + auto amr_hashmap = m_mesh_map.hashmap(); + auto num_mirror_quad = m_amr_mesh.local_num_mirrors(); + + // number of cells per block, and scalar values per cell + const auto num_cells = userdata_block.num_cells(); + const auto bSizes = userdata_block.block_size(); + const auto num_vars = userdata_block.num_vars(); + + [[maybe_unused]] auto local_num_quadrants = m_amr_mesh.local_num_quadrants(); + + // make sure m_send buffer was allocated with the right size + assertm(static_cast(num_cells * num_vars * num_mirror_quad) == m_send.extent(0), + "[MeshGhostsExchanger::pack_mirror_data] send buffer has wrong size"); + + auto mir_data = DataArrayBlockUnmanaged_t(m_send.data(), bSizes, num_vars, num_mirror_quad); + + { + // traverse the list of mirror keys, and for each key try to find it in the old hashmap + Kokkos::parallel_for( + "pack_mirror_data - block - cell center data", + Kokkos::RangePolicy(0, num_mirror_quad * num_cells), + KOKKOS_LAMBDA(const int64_t & global_index) { + const auto imirror = global_index / num_cells; + const auto icell = global_index - imirror * num_cells; + auto key = mirror_keys_device(imirror); + + // find key index in map + auto key_index_map = amr_hashmap.find(key); + + // first check if key exists in the hashmap, if it exists, it means we found a matching + // owned mirror quadrant + if (amr_hashmap.valid_at(key_index_map)) + { + auto userdata_index = amr_hashmap.value_at(key_index_map); + KOKKOS_ASSERT( + userdata_index < local_num_quadrants && + "[MeshGhostsExchanger::pack_mirror_data] index must correspond to a locally " + "owned quadrant."); + for (int32_t ivar = 0; ivar < num_vars; ++ivar) + { + mir_data(icell, ivar, imirror) = userdata_block(icell, ivar, userdata_index); + } + } + }); + } + Kokkos::fence(); + + // now m_send (aka mir_data) is ready to be sent + +} // pack_mirror_data - block (cell-center) version - new + +// ========================================================================= +// ========================================================================= +template +void +MeshGhostsExchanger::pack_mirror_data_multi_var( + DataArrayBlockMultiVar_t userdata_block, + int32_t num_block_mirrors, + orchard_key_view_t mirror_keys_device, + Kokkos::View mirror_offsets) +{ + auto amr_hashmap = m_mesh_map.hashmap(); + auto num_mirror_quad = m_amr_mesh.local_num_mirrors(); + + // number of cells per block, and scalar values per cell + const auto num_cells = userdata_block.num_cells(); + const auto bSizes = userdata_block.block_size(); + + [[maybe_unused]] auto local_num_quadrants = m_amr_mesh.local_num_quadrants(); + + // make sure m_send buffer was allocated with the right size + assertm(static_cast(num_cells * num_block_mirrors) == m_send.extent(0), + "[MeshGhostsExchanger::pack_mirror_data_multi_var] send buffer has wrong size"); + + auto mir_data = + DataArrayBlockUnmanaged_t(m_send.data(), bSizes, 1, static_cast(num_block_mirrors)); + + { + // traverse the list of mirror keys, and for each key try to find it in the old hashmap + Kokkos::parallel_for( + "pack_mirror_data_multi_var - block - cell center data", + Kokkos::RangePolicy(0, num_mirror_quad * num_cells), + KOKKOS_LAMBDA(const int32_t & global_index) { + const auto imirror = global_index / num_cells; + const auto icell = global_index - imirror * num_cells; + auto key = mirror_keys_device(imirror); + + // find key index in map + auto key_index_map = amr_hashmap.find(key); + + // first check if key exists in the hashmap, if it exists, it means we found a matching + // owned mirror quadrant + if (amr_hashmap.valid_at(key_index_map)) + { + auto userdata_index = static_cast(amr_hashmap.value_at(key_index_map)); + KOKKOS_ASSERT(userdata_index < local_num_quadrants && + "[MeshGhostsExchanger::pack_mirror_data_multi_var] index must correspond " + "to a locally owned quadrant."); + const auto nb_var = userdata_block.num_vars(userdata_index); + const auto start_block = static_cast(mirror_offsets(imirror)); + for (int32_t ivar = 0; ivar < nb_var; ++ivar) + mir_data(icell, 0, start_block + ivar) = userdata_block(icell, ivar, userdata_index); + } + }); + } + + Kokkos::fence(); + +} // pack_mirror_data_multi_var - block (cell-center) version - new + +// ========================================================================= +// ========================================================================= +template +void +MeshGhostsExchanger::pack_mirror_data(FaceDataArrayBlock_t face_userdata, + orchard_key_view_t mirror_keys_device) +{ + + auto amr_hashmap = m_mesh_map.hashmap(); + auto num_mirror_quad = m_amr_mesh.local_num_mirrors(); + + // number of face elements per block (total number of faces) + auto num_elts_per_octant = face_userdata.num_elements_per_octant(); + + [[maybe_unused]] auto local_num_quadrants = m_amr_mesh.local_num_quadrants(); + + // make sure m_send buffer was allocated with the right size + assertm(static_cast(num_elts_per_octant * num_mirror_quad) == m_send.extent(0), + "[MeshGhostsExchanger::pack_mirror_data] send buffer has wrong size"); + + // create an unmanaged 1D view of data to send + FaceFlatArrayUnmanaged_t mir_data = FaceFlatArrayUnmanaged_t( + m_send.data(), static_cast(num_elts_per_octant * num_mirror_quad)); + + // get 1D view of face data + auto userdata = face_userdata.logical_view(); + + { + // traverse the list of mirror keys, and for each key try to find it in the old hashmap + Kokkos::parallel_for( + "pack_mirror_data - block - face center data", + Kokkos::RangePolicy(0, num_mirror_quad * num_elts_per_octant), + KOKKOS_LAMBDA(const int64_t & global_index) { + const auto imirror = global_index / num_elts_per_octant; + const auto ielt = global_index - imirror * num_elts_per_octant; + auto key = mirror_keys_device(imirror); + + // find key index in map + auto key_index_map = amr_hashmap.find(key); + + // first check if key exists in the hashmap, if it exists, it means we found a matching + // owned mirror quadrant + if (amr_hashmap.valid_at(key_index_map)) + { + auto userdata_index = amr_hashmap.value_at(key_index_map); + KOKKOS_ASSERT( + userdata_index < local_num_quadrants && + "[MeshGhostsExchanger::pack_mirror_data] index must correspond to a locally " + "owned quadrant."); + { + mir_data(ielt + num_elts_per_octant * imirror) = + userdata(ielt + num_elts_per_octant * userdata_index); + } + } + }); + } + Kokkos::fence(); + + // now m_send (aka mir_data) is ready to be sent + +} // pack_mirror_data - block (face-center) version + +// ========================================================================= +// ========================================================================= +template +void +MeshGhostsExchanger::unpack_ghost_data(DataArrayLeaf_t userdata_leaf) +{ + // get the number of owned quadrants in local MPI process + auto num_owned_quad = m_amr_mesh.local_num_quadrants(); + + // get the number of ghost quadrants in local MPI process + auto num_ghost_quad = m_amr_mesh.local_num_ghosts(); + + // number of scalar values per quad + [[maybe_unused]] auto num_vars = userdata_leaf.extent(1); + + // make sure m_recv buffer was allocated with the right size + assertm(static_cast(num_ghost_quad) * num_vars == m_recv.extent(0), + "[MeshGhostsExchanger::pack_mirror_data] send buffer has wrong size"); + + DataArrayLeafUnmanaged_t recv_data = DataArrayLeafUnmanaged_t( + m_recv.data(), static_cast(num_ghost_quad), static_cast(num_vars)); + + auto recv_range = + std::pair(num_owned_quad, num_owned_quad + num_ghost_quad); + auto userdata_leaf_ghost = Kokkos::subview(userdata_leaf, recv_range, Kokkos::ALL); + + Kokkos::deep_copy(userdata_leaf_ghost, recv_data); + + Kokkos::fence(); + +} // unpack_ghost_data - leaf version + +// ========================================================================= +// ========================================================================= +template +void +MeshGhostsExchanger::unpack_ghost_data(DataArrayBlock_t userdata_block) +{ + // get the number of owned quadrants in local MPI process + const auto num_owned_quad = m_amr_mesh.local_num_quadrants(); + + // get the number of ghost quadrants in local MPI process + const auto num_ghost_quad = m_amr_mesh.local_num_ghosts(); + + // number of cells per block, and scalar values per cell + const auto num_cells = userdata_block.num_cells(); + const auto bSizes = userdata_block.block_size(); + const auto num_vars = userdata_block.num_vars(); + const auto num_elts_per_quad = num_cells * num_vars; + + // make sure m_recv buffer was allocated with the right size + assertm(static_cast(num_ghost_quad * num_vars * num_cells) == m_recv.extent(0), + "[MeshGhostsExchanger::pack_mirror_data] receive buffer has wrong size"); + + auto recv_data = DataArrayBlockUnmanaged_t(m_recv.data(), bSizes, num_vars, num_ghost_quad); + + auto recv_range = std::pair( + num_elts_per_quad * num_owned_quad, num_elts_per_quad * (num_owned_quad + num_ghost_quad)); + auto userdata_block_ghost = Kokkos::subview(userdata_block.logical_view(), recv_range); + + Kokkos::deep_copy(userdata_block_ghost, recv_data.logical_view()); + + Kokkos::fence(); + +} // unpack_ghost_data - cell-center block version + +// ========================================================================= +// ========================================================================= +template +void +MeshGhostsExchanger::unpack_ghost_data_multi_var( + DataArrayBlockMultiVar_t userdata_block, + int32_t num_block_owned, + int32_t num_block_ghosts) +{ + // number of cells per block, and scalar values per cell + const auto num_cells = userdata_block.num_cells(); + const auto bSizes = userdata_block.block_size(); + + // make sure m_recv buffer was allocated with the right size + assertm(static_cast(num_block_ghosts * num_cells) == m_recv.extent(0), + "[MeshGhostsExchanger::unpack_ghost_data_multi_var] receive buffer has wrong size"); + + auto recv_data = + DataArrayBlockUnmanaged_t(m_recv.data(), bSizes, 1, static_cast(num_block_ghosts)); + + auto recv_range = std::pair( + num_cells * num_block_owned, num_cells * (num_block_owned + num_block_ghosts)); + auto userdata_block_ghost = Kokkos::subview(userdata_block.storage().logical_view(), recv_range); + + Kokkos::deep_copy(userdata_block_ghost, recv_data.logical_view()); + + Kokkos::fence(); +} // unpack_ghost_data_multi_var - cell-center block version + +// ========================================================================= +// ========================================================================= +template +void +MeshGhostsExchanger::unpack_ghost_data(FaceDataArrayBlock_t face_userdata) +{ + // get the number of owned quadrants in local MPI process + const auto num_owned_quad = m_amr_mesh.local_num_quadrants(); + + // get the number of ghost quadrants in local MPI process + const auto num_ghost_quad = m_amr_mesh.local_num_ghosts(); + + // number of face elements per block (total number of faces) + const auto num_elts_per_octant = face_userdata.num_elements_per_octant(); + + // make sure m_recv buffer was allocated with the right size + assertm(static_cast(num_elts_per_octant * num_ghost_quad) == m_recv.extent(0), + "[MeshGhostsExchanger::pack_mirror_data] receive buffer has wrong size"); + + FaceFlatArrayUnmanaged_t recv_data = FaceFlatArrayUnmanaged_t( + m_recv.data(), static_cast(num_elts_per_octant * num_ghost_quad)); + + // get 1D view of face data + auto userdata = face_userdata.logical_view(); + + auto recv_range = std::pair( + num_elts_per_octant * num_owned_quad, num_elts_per_octant * (num_owned_quad + num_ghost_quad)); + auto userdata_ghost = Kokkos::subview(userdata, recv_range); + + Kokkos::deep_copy(userdata_ghost, recv_data); + + Kokkos::fence(); + +} // unpack_ghost_data - face-center block version + +// ========================================================================= +// ========================================================================= +template +void +MeshGhostsExchanger::do_mpi_send_recv() +{ + // total number of MPI procs + auto num_procs = m_par_env.size(); + + // vector of MPI request for asynchronous send and receives + std::vector requests; + + uint32_t num_requests_recv = 0; + uint32_t num_requests_send = 0; + + // the following could be factorized in AMRmesh class (where the ghost object is stored) + // + // Compute the total number of MPI request (send and receive) + // + for (auto iproc = 0; iproc < num_procs; ++iproc) + { + auto nghosts = m_amr_mesh.local_num_ghosts(iproc); + KALYPSSO_ASSERT(nghosts >= 0); + if (nghosts > 0) + { + num_requests_recv++; + } + + auto nmirrors = m_amr_mesh.local_num_mirrors(iproc); + KALYPSSO_ASSERT(nmirrors >= 0); + if (nmirrors > 0) + { + num_requests_send++; + } + } + requests.reserve(num_requests_recv + num_requests_send); + + // + // Start to receive ghost data. num_recv is the total number of message to receive. + // + uint32_t num_recv = 0; + for (auto iproc = 0; iproc < num_procs; ++iproc) + { + // number of ghosts quadrants owned by remote MPI process iproc + auto nghosts = m_amr_mesh.local_num_ghosts(iproc); + KALYPSSO_ASSERT(nghosts >= 0); + + // if nghosts = 0, it means MPI process don't actually own ghosts, we won't receive any data + // from MPI process iproc + + // remote MPI process iproc actually owns interesting data, we want to receive those data + if (nghosts > 0) + { + auto recv_range = std::pair( + static_cast(m_amr_mesh.ghost()->proc_offsets[iproc]) * m_data_size_per_quad, + static_cast((m_amr_mesh.ghost()->proc_offsets[iproc] + nghosts)) * + m_data_size_per_quad); + auto recv_buff = Kokkos::subview(m_recv, recv_range); + + requests[num_recv] = + m_par_env.comm().MPI_Irecv(recv_buff, iproc, KALYPSSO_COMM_GHOST_EXCHANGE_TAG); + + num_recv++; + } + } // end for iproc + + // + // Start to send mirror data. num_send is the total number of message to send. + // + uint32_t num_send = 0; + for (auto iproc = 0; iproc < num_procs; ++iproc) + { + // number of mirror quadrants owned by current MPI process to be filled + auto nmirrors = m_amr_mesh.local_num_mirrors(iproc); + KALYPSSO_ASSERT(nmirrors >= 0); + + // if nmirrors = 0, it means current MPI process don't need to send data to MPI process + // iproc + + if (nmirrors > 0) + { + auto send_range = std::pair( + static_cast(m_amr_mesh.ghost()->mirror_proc_offsets[iproc]) * m_data_size_per_quad, + static_cast((m_amr_mesh.ghost()->mirror_proc_offsets[iproc] + nmirrors)) * + m_data_size_per_quad); + auto send_buff = Kokkos::subview(m_send, send_range); + + requests[num_requests_recv + num_send] = + m_par_env.comm().MPI_Isend(send_buff, iproc, KALYPSSO_COMM_GHOST_EXCHANGE_TAG); + + num_send++; + } + } // end for iproc + + // + // let's wait for all send/recv comm to finish + // + m_par_env.comm().MPI_Waitall(num_requests_recv + num_requests_send, requests.data()); + +} // do_mpi_send_recv + +// ========================================================================= +// ========================================================================= +template +void +MeshGhostsExchanger::do_mpi_send_recv_multi_var( + const Kokkos::View & ghost_offsets, + const Kokkos::View & mirror_offsets) +{ + // total number of MPI procs + auto num_procs = m_par_env.size(); + + // vector of MPI request for asynchronous send and receives + std::vector requests; + + uint32_t num_requests_recv = 0; + uint32_t num_requests_send = 0; + + // the following could be factorized in AMRmesh class (where the ghost object is stored) + // + // Compute the total number of MPI request (send and receive) + // + for (auto iproc = 0; iproc < num_procs; ++iproc) + { + auto nghosts = m_amr_mesh.local_num_ghosts(iproc); + KALYPSSO_ASSERT(nghosts >= 0); + if (nghosts > 0) + { + num_requests_recv++; + } + + auto nmirrors = m_amr_mesh.local_num_mirrors(iproc); + KALYPSSO_ASSERT(nmirrors >= 0); + if (nmirrors > 0) + { + num_requests_send++; + } + } + requests.reserve(num_requests_recv + num_requests_send); + + // + // Start to receive ghost data. num_recv is the total number of message to receive. + // + uint32_t num_recv = 0; + for (auto iproc = 0; iproc < num_procs; ++iproc) + { + // number of ghosts quadrants owned by remote MPI process iproc + auto nghosts = m_amr_mesh.local_num_ghosts(iproc); + KALYPSSO_ASSERT(nghosts >= 0); + + // if nghosts = 0, it means MPI process don't actually own ghosts, we won't receive any data + // from MPI process iproc + + // remote MPI process iproc actually owns interesting data, we want to receive those data + if (nghosts > 0) + { + const auto offset = m_amr_mesh.ghost()->proc_offsets[iproc]; + auto recv_range = + std::pair(ghost_offsets(offset) * m_data_size_per_quad, + ghost_offsets(offset + nghosts) * m_data_size_per_quad); + auto recv_buff = Kokkos::subview(m_recv, recv_range); + + requests[num_recv] = + m_par_env.comm().MPI_Irecv(recv_buff, iproc, KALYPSSO_COMM_GHOST_EXCHANGE_TAG); + + num_recv++; + } + } // end for iproc + + // + // Start to send mirror data. num_send is the total number of message to send. + // + uint32_t num_send = 0; + for (auto iproc = 0; iproc < num_procs; ++iproc) + { + // number of mirror quadrants owned by current MPI process to be filled + auto nmirrors = m_amr_mesh.local_num_mirrors(iproc); + KALYPSSO_ASSERT(nmirrors >= 0); + + // if nmirrors = 0, it means current MPI process don't need to send data to MPI process + // iproc + + if (nmirrors > 0) + { + const auto offset = m_amr_mesh.ghost()->mirror_proc_offsets[iproc]; + auto send_range = std::pair( + mirror_offsets(offset) * m_data_size_per_quad, + mirror_offsets(offset + nmirrors) * m_data_size_per_quad); + auto send_buff = Kokkos::subview(m_send, send_range); + + requests[num_requests_recv + num_send] = + m_par_env.comm().MPI_Isend(send_buff, iproc, KALYPSSO_COMM_GHOST_EXCHANGE_TAG); + + num_send++; + } + } // end for iproc + + // + // let's wait for all send/recv comm to finish + // + m_par_env.comm().MPI_Waitall(num_requests_recv + num_requests_send, requests.data()); + +} // do_mpi_send_recv_multi_var + +// ========================================================================= +// ========================================================================= +template +void +MeshGhostsExchanger::do_mpi_send_recv_inplace( + DataArrayGhostedBlock_t userdata_ghosted_block) +{ + const auto data_size_per_quad = + userdata_ghosted_block.num_cells() * userdata_ghosted_block.num_vars(); + + auto userdata_view = userdata_ghosted_block.flat_view(); + + // total number of MPI procs + const auto num_procs = m_par_env.size(); + + // vector of MPI request for asynchronous send and receives + std::vector requests; + + uint32_t num_requests_recv = 0; + uint32_t num_requests_send = 0; + + // the following could be factorized in AMRmesh class (where the ghost object is stored) + // + // Compute the total number of MPI request (send and receive) + // + for (auto iproc = 0; iproc < num_procs; ++iproc) + { + auto nghosts = m_amr_mesh.local_num_ghosts(iproc); + KALYPSSO_ASSERT(nghosts >= 0); + if (nghosts > 0) + { + num_requests_recv++; + } + + auto nmirrors = m_amr_mesh.local_num_mirrors(iproc); + KALYPSSO_ASSERT(nmirrors >= 0); + if (nmirrors > 0) + { + num_requests_send++; + } + } + requests.reserve(num_requests_recv + num_requests_send); + + // + // Start to receive ghost data. num_recv is the total number of message to receive. + // + uint32_t num_recv = 0; + for (auto iproc = 0; iproc < num_procs; ++iproc) + { + // number of ghosts quadrants owned by remote MPI process iproc + auto nghosts = m_amr_mesh.local_num_ghosts(iproc); + KALYPSSO_ASSERT(nghosts >= 0); + + // if nghosts = 0, it means MPI process don't actually own ghosts, we won't receive any data + // from MPI process iproc + + // MPI process iproc actually owns interesting data, we want to receive those data + if (nghosts > 0) + { + auto recv_offset = + (m_amr_mesh.local_num_mirrors() + m_amr_mesh.ghost()->proc_offsets[iproc]) * + data_size_per_quad; + auto recv_size = nghosts * data_size_per_quad; + + // declare recv buffer as unmanaged kokkos view used for inplace transfer + comm_buffer_unmanaged_t recv_buff(userdata_view.data() + recv_offset, + static_cast(recv_size)); + + requests[num_recv] = + m_par_env.comm().MPI_Irecv(recv_buff, iproc, KALYPSSO_COMM_GHOST_EXCHANGE_TAG); + + num_recv++; + } + } // end for iproc + + // + // Start to send mirror data. num_send is the total number of message to send. + // + uint32_t num_send = 0; + for (auto iproc = 0; iproc < num_procs; ++iproc) + { + // number of mirror quadrants owned by current MPI process to be filled + auto nmirrors = m_amr_mesh.local_num_mirrors(iproc); + KALYPSSO_ASSERT(nmirrors >= 0); + + // if nmirrors = 0, it means current MPI process don't need to send data to MPI process + // iproc + + if (nmirrors > 0) + { + auto send_offset = (m_amr_mesh.ghost()->mirror_proc_offsets[iproc]) * data_size_per_quad; + auto send_size = nmirrors * data_size_per_quad; + + // declare send buffer as unmanaged kokkos view used for inplace transfer + comm_buffer_unmanaged_t send_buff(userdata_view.data() + send_offset, + static_cast(send_size)); + + requests[num_requests_recv + num_send] = + m_par_env.comm().MPI_Isend(send_buff, iproc, KALYPSSO_COMM_GHOST_EXCHANGE_TAG); + + num_send++; + } + } // end for iproc + + // + // let's wait for all send/recv comm to finish + // + m_par_env.comm().MPI_Waitall(num_requests_recv + num_requests_send, requests.data()); + +} // do_mpi_send_recv_inplace + +// ========================================================================= +// ========================================================================= +template +void +MeshGhostsExchanger::exchange(DataArrayLeaf_t userdata_leaf) +{ + + resize(userdata_leaf.extent(1)); + + pack_mirror_data(userdata_leaf, m_mesh_map.mirror_orchard_keys()); + + do_mpi_send_recv(); + + unpack_ghost_data(userdata_leaf); + +} // exchange + +// ========================================================================= +// ========================================================================= +template +void +MeshGhostsExchanger::exchange(DataArrayBlock_t userdata_block) +{ + + // check if (re-)allocation needed + const auto data_size_per_quad = + static_cast(userdata_block.num_cells() * userdata_block.num_vars()); + + resize(data_size_per_quad); + + pack_mirror_data(userdata_block, m_mesh_map.mirror_orchard_keys()); + + do_mpi_send_recv(); + + unpack_ghost_data(userdata_block); + +} // exchange + +// ========================================================================= +// ========================================================================= +template +void +MeshGhostsExchanger::exchange(FaceDataArrayBlock_t userdata_block_face) +{ + + // check if (re-)allocation needed + const auto data_size_per_quad = userdata_block_face.num_elements_per_octant(); + + resize(static_cast(data_size_per_quad)); + + pack_mirror_data(userdata_block_face, m_mesh_map.mirror_orchard_keys()); + + do_mpi_send_recv(); + + unpack_ghost_data(userdata_block_face); + +} // exchange + +// ========================================================================= +// ========================================================================= +template +void +MeshGhostsExchanger::exchange_multi_var(DataArrayBlockMultiVar_t userdata_block) +{ + //! TODO: Would it be better to not copy the offsets to the host? + using HostSpace = typename HostDevice::execution_space; + + auto mirror_keys = m_mesh_map.mirror_orchard_keys(); + auto hashmap = m_mesh_map.hashmap(); + + Kokkos::View ghost_offsets( + "ghost offsets", static_cast(m_amr_mesh.local_num_ghosts()) + 1); + + Kokkos::View mirror_offsets( + "mirror offsets", static_cast(m_amr_mesh.local_num_mirrors()) + 1); + + const auto start_ghost = m_amr_mesh.local_num_quadrants(); + Kokkos::parallel_for( + "init ghost offsets", + Kokkos::RangePolicy(0, m_amr_mesh.local_num_ghosts() + 1), + KOKKOS_LAMBDA(const int32_t i_oct) { + auto start_offset = userdata_block.offsets()(start_ghost); + ghost_offsets(i_oct) = userdata_block.offsets()(start_ghost + i_oct) - start_offset; + }); + + const auto num_mirrors = m_amr_mesh.local_num_mirrors(); + Kokkos::parallel_scan( + "init mirror offsets", + Kokkos::RangePolicy(0, num_mirrors + 1), + KOKKOS_LAMBDA(const int32_t i_oct, uint32_t & offset, bool is_final) { + if (is_final) + mirror_offsets(i_oct) = offset; + if (i_oct != num_mirrors) + { + const auto key = mirror_keys(i_oct); + const auto orig_oct = hashmap.value_at(hashmap.find(key)); + offset += static_cast(userdata_block.num_vars(orig_oct)); + } + }); + + auto ghost_offsets_host = Kokkos::create_mirror_view_and_copy(HostSpace{}, ghost_offsets); + auto mirror_offsets_host = Kokkos::create_mirror_view_and_copy(HostSpace{}, mirror_offsets); + + const int32_t num_block_ghosts = + static_cast(ghost_offsets_host(ghost_offsets.size() - 1)); + const int32_t num_block_mirrors = + static_cast(mirror_offsets_host(mirror_offsets.size() - 1)); + uint32_t num_block_owned = 0; + Kokkos::deep_copy(num_block_owned, + Kokkos::subview(userdata_block.offsets().logical_view(), start_ghost)); + + // check if (re-)allocation needed + const auto data_size_per_block = static_cast(userdata_block.num_cells()); + + resize(data_size_per_block); + + pack_mirror_data_multi_var(userdata_block, num_block_mirrors, mirror_keys, mirror_offsets); + + do_mpi_send_recv_multi_var(ghost_offsets_host, mirror_offsets_host); + + unpack_ghost_data_multi_var( + userdata_block, static_cast(num_block_owned), num_block_ghosts); + +} // exchange_multi_var + +// explicit template instantiation +template class MeshGhostsExchanger<2, real_t, DefaultDevice>; +template class MeshGhostsExchanger<3, real_t, DefaultDevice>; + +} // namespace kalypsso diff --git a/src/kalypsso/core/MeshGhostsExchanger.h b/src/kalypsso/core/MeshGhostsExchanger.h new file mode 100644 index 0000000..d10b913 --- /dev/null +++ b/src/kalypsso/core/MeshGhostsExchanger.h @@ -0,0 +1,405 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file MeshGhostsExchanger.h + */ +#ifndef KALYPSSO_CORE_MESHGHOSTSEXCHANGER_H_ +#define KALYPSSO_CORE_MESHGHOSTSEXCHANGER_H_ + +#include // for assertm +#include +#include // for DataArray, DataArrayHost + +#include +#include + +#include +#include // for orchard_key_view_t and amr_map_t +#include + +#include +#include + +namespace kalypsso +{ + +/** + * \class MeshGhostsExchanger + * + * p4est vocabulary: + * - a mirror quadrant is a locally owned quadrant that is a ghost for another quadrant in another + * MPI process. In other words, a mirror quadrant "touches" a MPI border between 2 MPI processes. + * - a ghost quadrant is remote quadrant which data are locally copied into current MPI process + * + * Main functions: + * + * - make sure p4est_ghost is up to date + * - make the ghost array is properly allocated/sized + * - pack data from mirror quadrant into send buffer + * - prepare receive buffer sized upon the number of ghost quadrant + * - perform all MPI comm (either a collective version using MPI_Alltoallv or a + * point-to-version) + * - unpack the receive buffer into ghost data array + * + * Implementation note: + * we some follow original `p4est_ghost_exchange_custom_begin` but packing / unpacking is done on + * device, and we require our MPI implementation to be device-aware (i.e. cuda-aware or hip-aware + * when using GPU's). + * + * \tparam dim is dimension (integer: 2 or 3) + * \tparam T is the type of each element of the userdata exchanged (most of the time real_t), used + * to allocate send/recv buffers + * \tparam device_t is a kokkos device class (e.g. Kokkos::CudaSpace::device_type) + * + */ +template +class MeshGhostsExchanger +{ + +public: + using exec_space = typename device_t::execution_space; + + //! type alias to access p4est C API (2D or 3D) + using p4est_t = typename p4est::Wrapper; + + using forest_t = typename p4est_t::forest_t; + using ghost_t = typename p4est_t::ghost_t; + + using DataArrayLeafAoS_t = DataArrayLeafAoS; + + using DataArrayLeaf_t = DataArrayLeaf; + + using DataArrayLeafUnmanaged_t = DataArrayLeafUnmanaged; + + using DataArrayBlock_t = DataArrayBlock; + using DataArrayBlockUnmanaged_t = DataArrayBlock; + + using DataArrayGhostedBlock_t = DataArrayGhostedBlock; + + using FaceDataArrayBlock_t = FaceDataArrayBlock; + using FaceFlatArray_t = typename FaceDataArrayBlock_t::FaceFlatArray_t; + using FaceFlatArrayUnmanaged_t = typename FaceDataArrayBlock_t::FaceFlatArrayUnmanaged_t; + + using MaterialPresenceView_t = MaterialPresenceView; + using DataArrayBlockMultiVar_t = DataArrayBlockMultiVar; + + using comm_buffer_t = Kokkos::View; + using comm_buffer_unmanaged_t = + Kokkos::View>; + + using orchard_key_view_t = typename orchard_key_base_t::view_t; + using amr_hashmap_t = typename hashmap_base_t::map_t; + + // ========================================================================= + // ========================================================================= + //! constructor + MeshGhostsExchanger(const ConfigMap & config_map, + const ParallelEnv & par_env, + AMRmesh & amr_mesh, + MeshMap & mesh_map) + : m_config_map(config_map) + , m_par_env(par_env) + , m_amr_mesh(amr_mesh) + , m_mesh_map(mesh_map) + , m_send(nullptr, 0) + , m_recv(nullptr, 0) + , m_storage("MeshGhostsExchanger::storage", 0) + , m_data_size_per_quad(0) + {} + + // ========================================================================= + // ========================================================================= + //! destructor + virtual ~MeshGhostsExchanger() = default; + + // ========================================================================= + // ========================================================================= + //! resize device buffer for performing ghost data exchange. + //! + //! \param[in] data_size_per_quad is the number of element per block / leaf (i.e. number of cells + //! times number of variables) to transfer + virtual void + resize(size_t data_size_per_quad); + + // ========================================================================= + // ========================================================================= + /** + * return amount of allocated memory in bytes + */ + virtual auto + allocated_size_in_bytes() const -> size_t; + + // ========================================================================= + // ========================================================================= + //! pack mirror data into send buffer (device function) - leaf version. + //! + //! implementation use array of orchard key (mirror quads) to address userdata and copy data into + //! send buffer. + //! + //! \param[in] userdata is supposed to be of size (forest->local_num_quadrants + + //! ghost->ghosts>elem_count) + //! \param[in] array or orchard keys of mirror quadrant + //! + //! IMPORTANT NOTE: remember that orchard keys in mirror array are sorted first by MPI processor + //! to send to and secondly by Morton order. + //! + //! make sure that MeshMap object is up-to-date before using this; we need the unordered map + //! (orchard keys, index) to be valid, i.e. calling MeshMap::fill_map is necessary after any mesh + //! changes (p4est_balance, p4est_partition) + void + pack_mirror_data(DataArrayLeaf_t userdata_leaf, orchard_key_view_t mirror_keys_device); + + // ========================================================================= + // ========================================================================= + //! pack mirror data into send buffer (device function) - block (cell-center) version. + //! + //! implementation use array of orchard key (mirror quads) to address userdata and copy data into + //! send buffer. + //! + //! \param[in] userdata is supposed to be of size (forest->local_num_quadrants + + //! ghost->ghosts>elem_count) + //! \param[in] array or orchard keys of mirror quadrant + //! \param[in] amr_hashmap is an unordered map (key=orchard_key, value=index) of all local + //! quadrants (owned+ghosts), and please remember that mirror quadrants are locally owned + //! quadrants) + //! + //! IMPORTANT NOTE: remember that orchard keys in mirror array are sorted first by MPI processor + //! to send to and secondly by Morton order. + //! + //! make sure that MeshMap object is up-to-date before using this; we need the unordered map + //! (orchard keys, index) to be valid, i.e. calling MeshMap::fill_map is necessary after any mesh + //! changes (p4est_balance, p4est_partition) + void + pack_mirror_data(DataArrayBlock_t userdata_block, orchard_key_view_t mirror_keys_device); + + // ========================================================================= + // ========================================================================= + //! Does the same thing as pack_mirror_data but for multi variable data array blocks + //! + //! \param[in] userdata is supposed to be of size (forest->local_num_quadrants + + //! ghost->ghosts>elem_count) + //! \param[in] num_block_mirrors number of mirror blocks + //! \param[in] array or orchard keys of mirror quadrant + //! \param[in] mirror_offsets mirror offset array on device + void + pack_mirror_data_multi_var(DataArrayBlockMultiVar_t userdata_block, + int32_t num_block_mirrors, + orchard_key_view_t mirror_keys_device, + Kokkos::View mirror_offsets); + + // ========================================================================= + // ========================================================================= + //! pack mirror data into send buffer (device function) - block (face-center) version. + //! + //! implementation use array of orchard key (mirror quads) to address userdata and copy data into + //! send buffer. + //! + //! \param[in] userdata is supposed to be of size (forest->local_num_quadrants + + //! ghost->ghosts>elem_count) + //! \param[in] array or orchard keys of mirror quadrant + //! \param[in] amr_hashmap is an unordered map (key=orchard_key, value=index) of all local + //! quadrants (owned+ghosts), and please remember that mirror quadrants are locally owned + //! quadrants) + //! + //! IMPORTANT NOTE: remember that orchard keys in mirror array are sorted first by MPI processor + //! to send to and secondly by Morton order. + //! + //! make sure that MeshMap object is up-to-date before using this; we need the unordered map + //! (orchard keys, index) to be valid, i.e. calling MeshMap::fill_map is necessary after any mesh + //! changes (p4est_balance, p4est_partition) + void + pack_mirror_data(FaceDataArrayBlock_t face_userdata, orchard_key_view_t mirror_keys_device); + + // ========================================================================= + // ========================================================================= + //! unpack received data (recv buffer) into ghost data. + //! + //! we will fill all ghost quadrant data, i.e. from index forest->local_num_quad + //! to index forest->local_num_quadrants + ghost->ghosts>elem_count + //! + //! \param[in] userdata is supposed to be of size (forest->local_num_quadrants + + //! ghost->ghosts->elem_count) + //! + void + unpack_ghost_data(DataArrayLeaf_t userdata_leaf); + + // ========================================================================= + // ========================================================================= + //! unpack received data (recv buffer) into ghost data (cell-center block). + //! + //! we will fill all ghost quadrant data, i.e. from index forest->local_num_quad + //! to index forest->local_num_quadrants + ghost->ghosts>elem_count + //! + //! \param[in] userdata is supposed to be of size (forest->local_num_quadrants + + //! ghost->ghosts->elem_count) + //! + void + unpack_ghost_data(DataArrayBlock_t userdata_block); + + // ========================================================================= + // ========================================================================= + //! Does the same thing as unpack_mirror_data but for multi variable data array blocks + //! + //! \param[in] num_block_ghosts number of owned blocks + //! \param[in] num_block_ghosts number of ghosts blocks + //! \param[in] userdata is supposed to be of size (forest->local_num_quadrants + + //! ghost->ghosts->elem_count) + //! + void + unpack_ghost_data_multi_var(DataArrayBlockMultiVar_t userdata_block, + int32_t num_block_owned, + int32_t num_block_ghosts); + + // ========================================================================= + // ========================================================================= + //! unpack received data (recv buffer) into ghost data (face-center block). + //! + //! we will fill all ghost quadrant data, i.e. from index forest->local_num_quad + //! to index forest->local_num_quadrants + ghost->ghosts>elem_count + //! + //! \param[in] userdata is supposed to be of size (forest->local_num_quadrants + + //! ghost->ghosts->elem_count) + //! + void + unpack_ghost_data(FaceDataArrayBlock_t face_userdata); + + // ========================================================================= + // ========================================================================= + //! actually perform MPI comm to send and receive ghost quadrant userdata + void + do_mpi_send_recv(); + + // ========================================================================= + // ========================================================================= + //! actually perform MPI comm to send and receive ghost quadrant userdata with multi var + //! + //! \param[in] ghost_offsets Host view with the block offsets in m_recv + //! \param[in] mirror_offsets Host view with the block offsets in m_send + void + do_mpi_send_recv_multi_var(const Kokkos::View & ghost_offsets, + const Kokkos::View & mirror_offsets); + + // ========================================================================= + // ========================================================================= + //! Perform MPI comm to send mirror data and receive in ghost quadrant userdata + //! in place in input buffer + //! + //! Exact same communication pattern as in do_mpi_send_recv, but doing send/recv inplace. + //! + //! \param[in,out] userdata_ghosted_block is a userdata ghosted block array (i.e. one value per + //! cell); must be sized upon the sum of number of mirror quads and number of + //! ghost quads. + void + do_mpi_send_recv_inplace(DataArrayGhostedBlock_t userdata_ghosted_block); + + // ========================================================================= + // ========================================================================= + //! do it all, pack, exchange, unpack. + //! + //! \param[in] userdata_leaf is a uaerdata array (at leaf, i.e. one value per leaf octant); must + //! be sized upon the total number of octant in current MPI process (owned + ghost) + //! \param[in] amr_hashmap_device is a device Kokkos::UnorderedMap of keys,value where value is + //! the memory index to find userdata corresponding to a given key + //! \param[in] do_allocate is a boolean to tell if we want to allocate internal communication + //! buffers (can be set to false, if you know that re-allocation is not needed) + void + exchange(DataArrayLeaf_t userdata_leaf); + + // ========================================================================= + // ========================================================================= + //! do it all, pack, exchange, unpack. + //! + //! \param[in] userdata_block is a userdata block array (i.e. one value per cell); must + //! be sized upon the total number of octant in current MPI process (owned + ghost) + //! \param[in] amr_hashmap_device is a device Kokkos::UnorderedMap of keys,value where value is + //! the memory index to find userdata corresponding to a given key + //! \param[in] do_allocate is a boolean to tell if we want to allocate internal communication + //! buffers (can be set to false, if you know that re-allocation is not needed) + void + exchange(DataArrayBlock_t userdata_block); + + // ========================================================================= + // ========================================================================= + //! do it all, pack, exchange, unpack. + //! + //! \param[in] userdata_block is a userdata block array (i.e. one value per face); must + //! be sized upon the total number of octant in current MPI process (owned + ghost) + //! \param[in] amr_hashmap_device is a device Kokkos::UnorderedMap of keys,value where value is + //! the memory index to find userdata corresponding to a given key + //! \param[in] do_allocate is a boolean to tell if we want to allocate internal communication + //! buffers (can be set to false, if you know that re-allocation is not needed) + void + exchange(FaceDataArrayBlock_t userdata_block_face); + + // ========================================================================= + // ========================================================================= + //! do it all, pack, exchange, unpack. + //! + //! \param[in] userdata_block is a userdata block array (i.e. one value per cell); must + //! be sized upon the total number of octant in current MPI process (owned + ghost) + void + exchange_multi_var(DataArrayBlockMultiVar_t userdata_block); + + // ========================================================================= + // ========================================================================= + //! Exchange mirror/ghost without packing/unpacking, inplace. + //! + //! In this function we don't use send/recv buffer; the input userdata is directly used to + //! send/recv data inplace. So to be clear, in this function the userdata in assumed to be sized + //! upon the number of mirror quads + number of ghost quad. The first part of the array (mirror + //! quad userdata) will be sent directly, and the second part of the array (ghosts quadrant) will + //! be used to receive data in place. + //! + //! \param[in,out] userdata_ghosted_block is a userdata ghosted block array (i.e. one value per + //! cell); must be sized upon the sum of number of mirror quads and number of + //! ghost quads. + void + exchange_inplace(DataArrayGhostedBlock_t userdata_ghosted_block) + { + + // make sure m_send buffer was allocated with the right size + assertm(static_cast(m_amr_mesh.local_num_mirrors() + m_amr_mesh.local_num_ghosts()) == + static_cast(userdata_ghosted_block.num_quadrants()), + "[MeshGhostsExchanger::exchange_inplace] input data has wrong size"); + + do_mpi_send_recv_inplace(userdata_ghosted_block); + + } // exchange_inplace + +protected: + //! config map (input parameter) + const ConfigMap & m_config_map; + + //! parallel environment + const ParallelEnv & m_par_env; + + //! AMRmesh reference object, mostly for accessing p4est ghost object + AMRmesh & m_amr_mesh; + + //! a MeshMap object so that we can extract the orchard keys as a kokkos view or unordered map + MeshMap & m_mesh_map; + + //! send buffers (subview of m_storage) + comm_buffer_unmanaged_t m_send; + + //! recv buffers (subview of m_storage) + comm_buffer_unmanaged_t m_recv; + + //! storage buffer where send/recv message will be stored + comm_buffer_t m_storage; + + //! data size per quad (i.e. number of cells per leaf times number of variables) + //! this value is set each time we call allocate_send_recv_buffers + size_t m_data_size_per_quad; + +}; // class MeshGhostsExchanger + +// explicit template instantiation +extern template class MeshGhostsExchanger<2, real_t, DefaultDevice>; +extern template class MeshGhostsExchanger<3, real_t, DefaultDevice>; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_MESHGHOSTSEXCHANGER_H_ diff --git a/src/kalypsso/core/MeshGhostsExchangerMultiVar.cpp b/src/kalypsso/core/MeshGhostsExchangerMultiVar.cpp new file mode 100644 index 0000000..137ed79 --- /dev/null +++ b/src/kalypsso/core/MeshGhostsExchangerMultiVar.cpp @@ -0,0 +1,368 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file MeshGhostsExchangerMultiVar.cpp + */ + +#include + +#include + +namespace kalypsso +{ +// ========================================================================= +// ========================================================================= +template +void +MeshGhostsExchangerMultiVar::resize_mv(int32_t num_block_mirrors, + int32_t num_block_ghosts, + size_t data_size_per_block) +{ + const auto current_capacity = this->m_storage.size(); + const auto num_blocks = + static_cast(num_block_mirrors) + static_cast(num_block_ghosts); + const auto requested_capacity = data_size_per_block * num_blocks; + + if (requested_capacity > current_capacity) + { + // enlarge capacity by factor capacity_growth_rate + size_t new_storage_capacity = + DataArrayUtils::allocated_capacity(static_cast(requested_capacity)); + + Kokkos::resize( + Kokkos::view_alloc(Kokkos::WithoutInitializing), this->m_storage, new_storage_capacity); + } + + const auto offset = data_size_per_block * static_cast(num_block_mirrors); + // clang-format off + this->m_send = + comm_buffer_unmanaged_t(this->m_storage.data(), + data_size_per_block * static_cast(num_block_mirrors)); + this->m_recv = + comm_buffer_unmanaged_t(this->m_storage.data() + offset, + data_size_per_block * static_cast(num_block_ghosts)); + // clang-format on + + // This is essentially what the value is + m_data_size_per_block = data_size_per_block; + +} // resize_mv + +// ========================================================================= +// ========================================================================= +template +void +MeshGhostsExchangerMultiVar::resize_offsets() +{ + const auto offsets_capacity_current = m_offsets_storage.size(); + + const auto offsets_capacity_required = + (static_cast(this->m_amr_mesh.local_num_ghosts()) + 1) + + (static_cast(this->m_amr_mesh.local_num_mirrors()) + 1); + + if (offsets_capacity_required > offsets_capacity_current) + { + auto offsets_capacity_new = + DataArrayUtils::allocated_capacity(static_cast(offsets_capacity_required)); + + Kokkos::resize( + Kokkos::view_alloc(Kokkos::WithoutInitializing), m_offsets_storage, offsets_capacity_new); + Kokkos::resize(Kokkos::view_alloc(Kokkos::WithoutInitializing), + m_offsets_storage_host, + offsets_capacity_new); + } + + m_ghost_offsets = offset_dev_unmanaged_t( + m_offsets_storage.data(), static_cast(this->m_amr_mesh.local_num_ghosts()) + 1); + + m_mirror_offsets = offset_dev_unmanaged_t( + m_offsets_storage.data() + static_cast(this->m_amr_mesh.local_num_ghosts()) + 1, + static_cast(this->m_amr_mesh.local_num_mirrors()) + 1); + + m_ghost_offsets_host = offset_host_unmanaged_t( + m_offsets_storage_host.data(), static_cast(this->m_amr_mesh.local_num_ghosts()) + 1); + + m_mirror_offsets_host = offset_host_unmanaged_t( + m_offsets_storage_host.data() + static_cast(this->m_amr_mesh.local_num_ghosts()) + 1, + static_cast(this->m_amr_mesh.local_num_mirrors()) + 1); + +} // resize_offsets + +// ========================================================================= +// ========================================================================= +template +auto +MeshGhostsExchangerMultiVar::allocated_size_in_bytes() const -> size_t +{ + auto size = MeshGhostsExchanger::allocated_size_in_bytes(); + + size += m_offsets_storage.size() * sizeof(typename offset_dev_t::non_const_value_type); + + return size; +} + +// ========================================================================= +// ========================================================================= +template +void +MeshGhostsExchangerMultiVar::pack_mirror_data_multi_var( + DataArrayBlockMultiVar_t userdata_block, + int32_t num_block_mirrors, + orchard_key_view_t mirror_keys_device, + Kokkos::View mirror_offsets) +{ + auto amr_hashmap = this->m_mesh_map.hashmap(); + auto num_mirror_quad = this->m_amr_mesh.local_num_mirrors(); + + // number of cells per block, and scalar values per cell + const auto num_cells = userdata_block.num_cells(); + const auto bSizes = userdata_block.block_size(); + + [[maybe_unused]] auto local_num_quadrants = this->m_amr_mesh.local_num_quadrants(); + + // make sure m_send buffer was allocated with the right size + assertm(static_cast(num_cells * num_block_mirrors) == this->m_send.extent(0), + "[MeshGhostsExchangerMultiVar::pack_mirror_data_multi_var] send buffer has wrong size"); + + auto mir_data = DataArrayBlockUnmanaged_t( + this->m_send.data(), bSizes, 1, static_cast(num_block_mirrors)); + + { + // traverse the list of mirror keys, and for each key try to find it in the old hashmap + Kokkos::parallel_for( + "pack_mirror_data_multi_var - block - cell center data", + Kokkos::RangePolicy(0, num_mirror_quad * num_cells), + KOKKOS_LAMBDA(const int32_t & global_index) { + const auto imirror = global_index / num_cells; + const auto icell = global_index - imirror * num_cells; + auto key = mirror_keys_device(imirror); + + // find key index in map + auto key_index_map = amr_hashmap.find(key); + + // first check if key exists in the hashmap, if it exists, it means we found a matching + // owned mirror quadrant + if (amr_hashmap.valid_at(key_index_map)) + { + auto userdata_index = static_cast(amr_hashmap.value_at(key_index_map)); + KOKKOS_ASSERT( + userdata_index < local_num_quadrants && + "[MeshGhostsExchangerMultiVar::pack_mirror_data_multi_var] index must correspond " + "to a locally owned quadrant."); + const auto nb_var = userdata_block.num_vars(userdata_index); + const auto start_block = static_cast(mirror_offsets(imirror)); + for (int32_t ivar = 0; ivar < nb_var; ++ivar) + mir_data(icell, 0, start_block + ivar) = userdata_block(icell, ivar, userdata_index); + } + }); + } + + Kokkos::fence(); + +} // pack_mirror_data_multi_var - block (cell-center) version - new + +// ========================================================================= +// ========================================================================= +template +void +MeshGhostsExchangerMultiVar::unpack_ghost_data_multi_var( + DataArrayBlockMultiVar_t userdata_block, + int32_t num_block_owned, + int32_t num_block_ghosts) +{ + // number of cells per block, and scalar values per cell + const auto num_cells = userdata_block.num_cells(); + const auto bSizes = userdata_block.block_size(); + + // make sure m_recv buffer was allocated with the right size + assertm( + static_cast(num_block_ghosts * num_cells) == this->m_recv.extent(0), + "[MeshGhostsExchangerMultiVar::unpack_ghost_data_multi_var] receive buffer has wrong size"); + + auto recv_data = DataArrayBlockUnmanaged_t( + this->m_recv.data(), bSizes, 1, static_cast(num_block_ghosts)); + + auto recv_range = std::pair( + num_cells * num_block_owned, num_cells * (num_block_owned + num_block_ghosts)); + auto userdata_block_ghost = Kokkos::subview(userdata_block.storage().logical_view(), recv_range); + + Kokkos::deep_copy(userdata_block_ghost, recv_data.logical_view()); + + Kokkos::fence(); +} // unpack_ghost_data_multi_var - cell-center block version + +// ========================================================================= +// ========================================================================= +template +void +MeshGhostsExchangerMultiVar::do_mpi_send_recv_multi_var( + const Kokkos::View & ghost_offsets, + const Kokkos::View & mirror_offsets) +{ + // total number of MPI procs + auto num_procs = this->m_par_env.size(); + + // vector of MPI request for asynchronous send and receives + std::vector requests; + + uint32_t num_requests_recv = 0; + uint32_t num_requests_send = 0; + + // the following could be factorized in AMRmesh class (where the ghost object is stored) + // + // Compute the total number of MPI request (send and receive) + // + for (auto iproc = 0; iproc < num_procs; ++iproc) + { + auto nghosts = this->m_amr_mesh.local_num_ghosts(iproc); + KALYPSSO_ASSERT(nghosts >= 0); + if (nghosts > 0) + { + num_requests_recv++; + } + + auto nmirrors = this->m_amr_mesh.local_num_mirrors(iproc); + KALYPSSO_ASSERT(nmirrors >= 0); + if (nmirrors > 0) + { + num_requests_send++; + } + } + requests.reserve(num_requests_recv + num_requests_send); + + // + // Start to receive ghost data. num_recv is the total number of message to receive. + // + uint32_t num_recv = 0; + for (auto iproc = 0; iproc < num_procs; ++iproc) + { + // number of ghosts quadrants owned by remote MPI process iproc + auto nghosts = this->m_amr_mesh.local_num_ghosts(iproc); + KALYPSSO_ASSERT(nghosts >= 0); + + // if nghosts = 0, it means MPI process don't actually own ghosts, we won't receive any data + // from MPI process iproc + + // remote MPI process iproc actually owns interesting data, we want to receive those data + if (nghosts > 0) + { + const auto offset = this->m_amr_mesh.ghost()->proc_offsets[iproc]; + auto recv_range = std::pair( + ghost_offsets(offset) * m_data_size_per_block, + ghost_offsets(offset + nghosts) * m_data_size_per_block); + auto recv_buff = Kokkos::subview(this->m_recv, recv_range); + + requests[num_recv] = + this->m_par_env.comm().MPI_Irecv(recv_buff, iproc, KALYPSSO_COMM_GHOST_EXCHANGE_TAG); + + num_recv++; + } + } // end for iproc + + // + // Start to send mirror data. num_send is the total number of message to send. + // + uint32_t num_send = 0; + for (auto iproc = 0; iproc < num_procs; ++iproc) + { + // number of mirror quadrants owned by current MPI process to be filled + auto nmirrors = this->m_amr_mesh.local_num_mirrors(iproc); + KALYPSSO_ASSERT(nmirrors >= 0); + + // if nmirrors = 0, it means current MPI process don't need to send data to MPI process + // iproc + + if (nmirrors > 0) + { + const auto offset = this->m_amr_mesh.ghost()->mirror_proc_offsets[iproc]; + auto send_range = std::pair( + mirror_offsets(offset) * m_data_size_per_block, + mirror_offsets(offset + nmirrors) * m_data_size_per_block); + auto send_buff = Kokkos::subview(this->m_send, send_range); + + requests[num_requests_recv + num_send] = + this->m_par_env.comm().MPI_Isend(send_buff, iproc, KALYPSSO_COMM_GHOST_EXCHANGE_TAG); + + num_send++; + } + } // end for iproc + + // + // let's wait for all send/recv comm to finish + // + this->m_par_env.comm().MPI_Waitall(num_requests_recv + num_requests_send, requests.data()); + +} // do_mpi_send_recv_multi_var + +// ========================================================================= +// ========================================================================= +template +void +MeshGhostsExchangerMultiVar::exchange_multi_var( + DataArrayBlockMultiVar_t userdata_block) +{ + //! TODO: Would it be better to not copy the offsets to the host? + + auto mirror_keys = this->m_mesh_map.mirror_orchard_keys(); + auto hashmap = this->m_mesh_map.hashmap(); + + resize_offsets(); + + const auto start_ghost = this->m_amr_mesh.local_num_quadrants(); + Kokkos::parallel_for( + "init ghost offsets", + Kokkos::RangePolicy(0, this->m_amr_mesh.local_num_ghosts() + 1), + KOKKOS_LAMBDA(const int32_t i_oct) { + auto start_offset = userdata_block.offsets()(start_ghost); + m_ghost_offsets(i_oct) = userdata_block.offsets()(start_ghost + i_oct) - start_offset; + }); + + const auto num_mirrors = this->m_amr_mesh.local_num_mirrors(); + Kokkos::parallel_scan( + "init mirror offsets", + Kokkos::RangePolicy(0, num_mirrors + 1), + KOKKOS_LAMBDA(const int32_t i_oct, uint32_t & offset, bool is_final) { + if (is_final) + m_mirror_offsets(i_oct) = offset; + if (i_oct != num_mirrors) + { + const auto key = mirror_keys(i_oct); + const auto orig_oct = hashmap.value_at(hashmap.find(key)); + offset += static_cast(userdata_block.num_vars(orig_oct)); + } + }); + + Kokkos::deep_copy(m_ghost_offsets_host, m_ghost_offsets); + Kokkos::deep_copy(m_mirror_offsets_host, m_mirror_offsets); + + const int32_t num_block_ghosts = + static_cast(m_ghost_offsets_host(m_ghost_offsets_host.size() - 1)); + + const int32_t num_block_mirrors = + static_cast(m_mirror_offsets_host(m_mirror_offsets_host.size() - 1)); + + uint32_t num_block_owned = 0; + Kokkos::deep_copy(num_block_owned, + Kokkos::subview(userdata_block.offsets().logical_view(), start_ghost)); + + // check if (re-)allocation needed + const auto data_size_per_block = static_cast(userdata_block.num_cells()); + + resize_mv(num_block_mirrors, num_block_ghosts, data_size_per_block); + + pack_mirror_data_multi_var(userdata_block, num_block_mirrors, mirror_keys, m_mirror_offsets); + + do_mpi_send_recv_multi_var(m_ghost_offsets_host, m_mirror_offsets_host); + + unpack_ghost_data_multi_var( + userdata_block, static_cast(num_block_owned), num_block_ghosts); + +} // exchange_multi_var + +// explicit template instantiation +template class MeshGhostsExchangerMultiVar<2, real_t, DefaultDevice>; +template class MeshGhostsExchangerMultiVar<3, real_t, DefaultDevice>; + +} // namespace kalypsso diff --git a/src/kalypsso/core/MeshGhostsExchangerMultiVar.h b/src/kalypsso/core/MeshGhostsExchangerMultiVar.h new file mode 100644 index 0000000..5d365e8 --- /dev/null +++ b/src/kalypsso/core/MeshGhostsExchangerMultiVar.h @@ -0,0 +1,190 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file MeshGhostsExchangerMultiVar.h + */ +#ifndef KALYPSSO_CORE_MESHGHOSTSEXCHANGERMULTIVAR_H_ +#define KALYPSSO_CORE_MESHGHOSTSEXCHANGERMULTIVAR_H_ + +#include + +namespace kalypsso +{ + +/** + * \class MeshGhostsExchangerMultiVar + * + * \copydoc MeshGhostsExchanger + * + * Main differences with MeshGhostExchanger are + * - using "data-per-block" instead of "data-per-octant". + * - additional array for storing offsets. + * + */ +template +class MeshGhostsExchangerMultiVar : public MeshGhostsExchanger +{ + +public: + using exec_space = typename device_t::execution_space; + + //! type alias to access p4est C API (2D or 3D) + using p4est_t = typename p4est::Wrapper; + + using forest_t = typename p4est_t::forest_t; + using ghost_t = typename p4est_t::ghost_t; + + using DataArrayBlock_t = DataArrayBlock; + using DataArrayBlockUnmanaged_t = DataArrayBlock; + using DataArrayGhostedBlock_t = DataArrayGhostedBlock; + + using MaterialPresenceView_t = MaterialPresenceView; + using DataArrayBlockMultiVar_t = DataArrayBlockMultiVar; + + using comm_buffer_t = Kokkos::View; + using comm_buffer_unmanaged_t = + Kokkos::View>; + + using orchard_key_view_t = typename orchard_key_base_t::view_t; + using amr_hashmap_t = typename hashmap_base_t::map_t; + + using offset_dev_t = Kokkos::View; + using offset_dev_unmanaged_t = + Kokkos::View>; + + using offset_host_t = Kokkos::View; + using offset_host_unmanaged_t = + Kokkos::View>; + + + // ========================================================================= + // ========================================================================= + //! constructor + MeshGhostsExchangerMultiVar(const ConfigMap & config_map, + const ParallelEnv & par_env, + AMRmesh & amr_mesh, + MeshMap & mesh_map) + : MeshGhostsExchanger(config_map, par_env, amr_mesh, mesh_map) + , m_offsets_storage("offsets (device storage)", 0) + , m_offsets_storage_host("offsets (host storage)", 0) + , m_ghost_offsets(nullptr, 0) + , m_ghost_offsets_host(nullptr, 0) + , m_mirror_offsets(nullptr, 0) + , m_mirror_offsets_host(nullptr, 0) + , m_data_size_per_block(0) + + {} + + // ========================================================================= + // ========================================================================= + //! destructor + ~MeshGhostsExchangerMultiVar() = default; + + // ========================================================================= + // ========================================================================= + //! resize all buffer for performing ghost data exchange in the multivar case. + //! + //! \param[in] num_block_mirrors number of mirror blocks + //! \param[in] num_block_ghosts number of ghosts blocks + //! \param[in] data_size_per_block is the number of element per block (i.e. number of cells in an + //! octant) to transfer + void + resize_mv(int32_t num_block_mirrors, int32_t num_block_ghosts, size_t data_size_per_block); + + // ========================================================================= + // ========================================================================= + //! resize offset buffers. + void + resize_offsets(); + + // ========================================================================= + // ========================================================================= + /** + * return amount of allocated memory in bytes + */ + auto + allocated_size_in_bytes() const -> size_t override; + + // ========================================================================= + // ========================================================================= + //! Does the same thing as pack_mirror_data but for multi variable data array blocks + //! + //! \param[in] userdata is supposed to be of size (forest->local_num_quadrants + + //! ghost->ghosts>elem_count) + //! \param[in] num_block_mirrors number of mirror blocks + //! \param[in] array or orchard keys of mirror quadrant + //! \param[in] mirror_offsets mirror offset array on device + void + pack_mirror_data_multi_var(DataArrayBlockMultiVar_t userdata_block, + int32_t num_block_mirrors, + orchard_key_view_t mirror_keys_device, + Kokkos::View mirror_offsets); + + // ========================================================================= + // ========================================================================= + //! Does the same thing as unpack_mirror_data but for multi variable data array blocks + //! + //! \param[in] num_block_ghosts number of owned blocks + //! \param[in] num_block_ghosts number of ghosts blocks + //! \param[in] userdata is supposed to be of size (forest->local_num_quadrants + + //! ghost->ghosts->elem_count) + //! + void + unpack_ghost_data_multi_var(DataArrayBlockMultiVar_t userdata_block, + int32_t num_block_owned, + int32_t num_block_ghosts); + + // ========================================================================= + // ========================================================================= + //! actually perform MPI comm to send and receive ghost quadrant userdata with multi var + //! + //! \param[in] ghost_offsets Host view with the block offsets in m_recv + //! \param[in] mirror_offsets Host view with the block offsets in m_send + void + do_mpi_send_recv_multi_var(const Kokkos::View & ghost_offsets, + const Kokkos::View & mirror_offsets); + + // ========================================================================= + // ========================================================================= + //! do it all, pack, exchange, unpack. + //! + //! \param[in] userdata_block is a userdata block array (i.e. one value per cell); must + //! be sized upon the total number of octant in current MPI process (owned + ghost) + void + exchange_multi_var(DataArrayBlockMultiVar_t userdata_block); + +private: + //! device storage for offsets used to locate the begin of each block data in ghost and mirror + //! octants + offset_dev_t m_offsets_storage; + + //! host storage for offsets used to locate the begin of each block data in ghost and mirror + //! octants + offset_host_t m_offsets_storage_host; + + //! device offsets used to locate the begin of each block data in ghost octants + offset_dev_unmanaged_t m_ghost_offsets; + + //! host offsets used to locate the begin of each block data in ghost octants + offset_host_unmanaged_t m_ghost_offsets_host; + + //! device offsets used to locate the begin of each block data in mirror octants + offset_dev_unmanaged_t m_mirror_offsets; + + //! host offsets used to locate the begin of each block data in mirror octants + offset_host_unmanaged_t m_mirror_offsets_host; + + //! data size per block (i.e. number of cells per block of cells) + size_t m_data_size_per_block; + +}; // class MeshGhostsExchangerMultiVar + +// explicit template instantiation +extern template class MeshGhostsExchangerMultiVar<2, real_t, DefaultDevice>; +extern template class MeshGhostsExchangerMultiVar<3, real_t, DefaultDevice>; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_MESHGHOSTSEXCHANGERMULTIVAR_H_ diff --git a/src/kalypsso/core/MeshMap.cpp b/src/kalypsso/core/MeshMap.cpp new file mode 100644 index 0000000..dc38a73 --- /dev/null +++ b/src/kalypsso/core/MeshMap.cpp @@ -0,0 +1,728 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file MeshMap.cpp + * \brief Container for mesh data adapted from Kokkos::UnorderedMap. + * + */ + +#include + +namespace kalypsso +{ + +// ========================================================================= +// ========================================================================= +template +MeshMap::MeshMap(ConfigMap const & config_map, ParallelEnv const & par_env) + : m_config_map(config_map) + , m_par_env(par_env) + , m_outside_quads_info() + , m_amr_mesh_info() +{ + + m_brick_sizes = []() { + if constexpr (dim == 2) + return brick_size_t<2>{ 1, 1 }; + else if constexpr (dim == 3) + return brick_size_t<3>{ 1, 1, 1 }; + }(); + m_is_brick_periodic = []() { + if constexpr (dim == 2) + return Kokkos::Array{ false, false }; + else if constexpr (dim == 3) + return Kokkos::Array{ false, false, false }; + }(); + + // check if connectivity is either "unit" or "brick" + const auto conn_name = m_config_map.getString("amr", "connectivity", "invalid_connectivity"); + + if (conn_name == "brick") + { + m_brick_sizes = get_brick_sizes(config_map); + + m_is_brick_periodic[IX] = static_cast( + m_config_map.getInteger("p4est_connectivity", "periodic_x", CONNECTIVITY_PERIODIC_FALSE)); + m_is_brick_periodic[IY] = static_cast( + m_config_map.getInteger("p4est_connectivity", "periodic_y", CONNECTIVITY_PERIODIC_FALSE)); + if constexpr (dim == 3) + m_is_brick_periodic[IZ] = static_cast( + m_config_map.getInteger("p4est_connectivity", "periodic_z", CONNECTIVITY_PERIODIC_FALSE)); + } +} // MeshMap::MeshMap - constructor + +// ========================================================================= +// ========================================================================= +template +void +MeshMap::compute_orchard_keys_view_host(/*const*/ forest_t * forest, + /*const*/ ghost_t * ghost) +{ + + // type alias to access p4est C API (2D or 3D) + using p4est_t = typename p4est::Wrapper; + + // perform update mesh info (only if required) + update_amr_mesh_info(forest, ghost); + + [[maybe_unused]] const auto total_num_quads = m_amr_mesh_info.local_num_quadrants_total(); + + assertm(m_orchard_keys_host.extent(0) == static_cast(total_num_quads), + "m_orchard_keys_host has wrong size !"); + + // tree linear index to xyz converter + const BrickConnectivityData convert(m_brick_sizes); + + size_t quadIndex = 0; + + // + // fill array of orchard keys using MPI-owned quadrants + // + for (auto treeid = forest->first_local_tree; treeid <= forest->last_local_tree; ++treeid) + { + // get current tree + auto tree = p4est_t::tree_array_index(forest->trees, treeid); + + // get tree coordinate + auto tree_xyz = convert.toXYZ(treeid); + + for (size_t qId = 0; qId < tree->quadrants.elem_count; ++qId) + { + auto q = p4est_t::quadrant_array_index(&(tree->quadrants), qId); + + Kokkos::Array octCoord; + octCoord[0] = + static_cast(q->x >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + octCoord[1] = + static_cast(q->y >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + if constexpr (dim == 3) + { + octCoord[2] = + static_cast(q->z >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + } + + auto orchard_key = + orchard_key_t::encode_orchard(tree_xyz, octCoord, static_cast(q->level)); + + m_orchard_keys_host(quadIndex) = orchard_key; + + quadIndex++; + } + } // end filling with owned quadrants + + // const auto num_owned_quads = quadIndex; + + // + // complete array of orchard keys using (MPI) ghost quadrants + // + for (size_t qId = 0; qId < ghost->ghosts.elem_count; ++qId) + { + auto q = p4est_t::quadrant_array_index(&(ghost->ghosts), qId); + + // get current ghost's tree id + auto treeid = q->p.which_tree; + + // get tree coordinate + auto tree_xyz = convert.toXYZ(treeid); + + Kokkos::Array octCoord; + octCoord[0] = + static_cast(q->x >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + octCoord[1] = + static_cast(q->y >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + if constexpr (dim == 3) + { + octCoord[2] = + static_cast(q->z >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + } + + auto orchard_key = + orchard_key_t::encode_orchard(tree_xyz, octCoord, static_cast(q->level)); + + m_orchard_keys_host(quadIndex) = orchard_key; + + quadIndex++; + + } // end filling with ghost quadrants + + const auto num_owned_and_ghost_quads = quadIndex; + + // + // complete array of orchard keys using quadrants outside of domain (external border) + // - if brick connectivity is periodic, these quadrants are already counted as (MPI) ghost + // quadrants (see above) + // - if brick connectivity is not periodic, we chose to add a layer of one quadrant all + // around the domain (i.e. outside domain) + // + // idea of implementation: just loop over all owned orchard keys, and check if corresponding + // quadrant touches outside domain by a face, edge or corner, and if true, then add the mirror + // key to the list of key + + // do we have at least one non-periodic border ? + bool has_non_periodic_border = false; + for (size_t idim = 0; idim < dim; ++idim) + { + if (!m_is_brick_periodic[idim]) + has_non_periodic_border = true; + } + + if (has_non_periodic_border) + { + // when computing outside quadrant key, we always use a "virtual" key computed as the periodic + // image of the outside quadrant; so here we need is_periodic to be array of "true" + // constexpr auto is_periodic = get_bool_array(true); + + // get list of faces + const auto faces = Face::get_all_faces(); + + // finally loop over quadrant at border (from the inside) + for (size_t qId = 0; qId < num_owned_and_ghost_quads; ++qId) + { + auto key = m_orchard_keys_host(qId); + + // + // insert face-outside neighbors + // + for (const auto face : faces) + { + if (orchard_key_t::is_at_domain_border(key, face, m_brick_sizes)) + { + if (!m_is_brick_periodic[face / 2]) + { + const auto displacement = orchard_key_t::face_to_displacement(face); + auto mirror_key = orchard_key_t::get_neighbor_key_same_level( + key, displacement, m_brick_sizes, m_is_brick_periodic); + + // et voila ! + m_orchard_keys_host(quadIndex) = mirror_key; + + quadIndex++; + } + } + } // end for face + + // insert edge-outside neighbors (3D only) + if constexpr (dim == 3) + { + Face::face_t face0, face1; + for (uint8_t iEdge = 0; iEdge < Edge::num_edges(); ++iEdge) + { + edge_to_faces(iEdge, face0, face1); + if (orchard_key_t::is_at_domain_border(key, face0, m_brick_sizes) and + orchard_key_t::is_at_domain_border(key, face1, m_brick_sizes)) + { + if (!m_is_brick_periodic[face0 / 2] or !m_is_brick_periodic[face1 / 2]) + { + + const auto displacement = orchard_key_t::face_to_displacement(face0, face1); + auto mirror_key = orchard_key_t::get_neighbor_key_same_level( + key, displacement, m_brick_sizes, m_is_brick_periodic); + + // et voila ! + m_orchard_keys_host(quadIndex) = mirror_key; + + quadIndex++; + } + } + } + } + + // + // insert corner-outside neighbors + // + for (uint8_t iCorner = 0; iCorner < Corner::num_corners(); ++iCorner) + { + if constexpr (dim == 2) + { + Face::face_t face0, face1; + corner_to_faces(iCorner, face0, face1); + if (orchard_key_t::is_at_domain_border(key, face0, m_brick_sizes) and + orchard_key_t::is_at_domain_border(key, face1, m_brick_sizes)) + { + if (!m_is_brick_periodic[face0 / 2] or !m_is_brick_periodic[face1 / 2]) + { + const auto displacement = orchard_key_t::face_to_displacement(face0, face1); + auto mirror_key = orchard_key_t::get_neighbor_key_same_level( + key, displacement, m_brick_sizes, m_is_brick_periodic); + + // et voila ! + m_orchard_keys_host(quadIndex) = mirror_key; + + quadIndex++; + } + } + } + else if constexpr (dim == 3) + { + Face::face_t face0, face1, face2; + corner_to_faces(iCorner, face0, face1, face2); + if (orchard_key_t::is_at_domain_border(key, face0, m_brick_sizes) and + orchard_key_t::is_at_domain_border(key, face1, m_brick_sizes) and + orchard_key_t::is_at_domain_border(key, face2, m_brick_sizes)) + { + // clang-format off + if (!m_is_brick_periodic[face0 / 2] or + !m_is_brick_periodic[face1 / 2] or + !m_is_brick_periodic[face2 / 2]) + { + const auto displacement = + orchard_key_t::face_to_displacement(face0, face1, face2); + auto mirror_key = orchard_key_t::get_neighbor_key_same_level( + key, displacement, m_brick_sizes, m_is_brick_periodic); + + // et voila ! + m_orchard_keys_host(quadIndex) = mirror_key; + + quadIndex++; + } + // clang-format on + } + } + } // end for iCorner + + } // end filling external quadrant + + } // end has_non_periodic_border == true + +} // MeshMap::compute_orchard_keys_view_host + +// ========================================================================= +// ========================================================================= +template +void +MeshMap::update_orchard_keys(/*const*/ forest_t * forest, + /*const*/ ghost_t * ghost) +{ + + assertm(ghost != nullptr, "ghost is not valid / allocated"); + + // perform update mesh info (only if required) + update_amr_mesh_info(forest, ghost); + + [[maybe_unused]] const auto total_num_quads = m_amr_mesh_info.local_num_quadrants_total(); + + // memory allocation host view + Kokkos::resize(Kokkos::view_alloc(Kokkos::WithoutInitializing), + m_orchard_keys_host, + static_cast(total_num_quads)); + + compute_orchard_keys_view_host(forest, ghost); + + // memory allocation device view + Kokkos::resize(Kokkos::view_alloc(exec_space(), Kokkos::WithoutInitializing), + m_orchard_keys, + static_cast(total_num_quads)); + + // upload view to device + Kokkos::deep_copy(m_orchard_keys, m_orchard_keys_host); + +} // MeshMap::update_orchard_keys + +// ========================================================================= +// ========================================================================= +template +void +MeshMap::update_conformal_status() +{ + + Kokkos::resize(Kokkos::view_alloc(exec_space(), Kokkos::WithoutInitializing), + m_conformal_status_view, + static_cast(m_amr_mesh_info.local_num_quadrants() + + m_amr_mesh_info.local_num_ghosts())); + + ComputeConformalStatusFunctor::apply(m_amr_hashmap, + m_orchard_keys, + m_amr_mesh_info, + m_brick_sizes, + m_is_brick_periodic, + m_conformal_status_view); + +} // MeshMap::update_conformal_status + +// ========================================================================= +// ========================================================================= +template +auto +MeshMap::update_conformal_full_status() + -> conformal_full_status_view_t +{ + auto conformal_full_status_view = conformal_full_status_view_t( + "conformal full status view", + static_cast(m_amr_mesh_info.local_num_quadrants() + + m_amr_mesh_info.local_num_ghosts())); + + ComputeConformalFullStatusFunctor::apply(m_amr_hashmap, + m_orchard_keys, + m_amr_mesh_info, + m_brick_sizes, + m_is_brick_periodic, + conformal_full_status_view); + + return conformal_full_status_view; + +} // MeshMap::update_conformal_full_status + +// ========================================================================= +// ========================================================================= +template +void +MeshMap::compute_mirror_orchard_keys_view_host(/*const*/ ghost_t * ghost) +{ + + // type alias to access p4est C API (2D or 3D) + using p4est_t = typename p4est::Wrapper; + + assertm(static_cast(m_mirror_orchard_keys_host.extent(0)) == + (ghost->mirror_proc_offsets[m_par_env.size()]), + "m_mirror_orchard_keys_host has wrong size, should be " + "ghost->mirror_proc_offsets[par_env.size()] !"); + + // tree linear index to xyz converter + const BrickConnectivityData convert(m_brick_sizes); + + // + // very important : + // - mirrors in p4est ghost->mirrors array are sorted only by Morton key + // - mirrors data used in MPI ghost exchange routine (e.g. p4est_ghost_exchange_custom_begin) + // must be sorted first by MPI process, then by Morton key; this sorting is done thanks to + // ghost->mirror_proc_mirrors (that we actually use below) + // + uint32_t index = 0; + for (int iproc = 0; iproc < m_par_env.size(); ++iproc) + { + // retrieve num of mirror quadrants involved in sending data to process iproc + auto num_mirrors = ghost->mirror_proc_offsets[iproc + 1] - ghost->mirror_proc_offsets[iproc]; + + auto index_offset = ghost->mirror_proc_offsets[iproc]; + + for (int mirrorId = 0; mirrorId < num_mirrors; ++mirrorId) + { + // get index (ordered by Morton order) for addressing ghost->mirrors + auto mir_index = ghost->mirror_proc_mirrors[index_offset + mirrorId]; + + // get the mirror quadrant itself + auto q = p4est_t::quadrant_array_index(&(ghost->mirrors), static_cast(mir_index)); + auto which_tree = q->p.which_tree; + + // get tree cartesian coordinate (with respect to brick connectivity) + auto tree_xyz = convert.toXYZ(which_tree); + + Kokkos::Array octCoord; + octCoord[0] = + static_cast(q->x >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + octCoord[1] = + static_cast(q->y >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + if constexpr (dim == 3) + { + octCoord[2] = + static_cast(q->z >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + } + + auto orchard_key = + orchard_key_t::encode_orchard(tree_xyz, octCoord, static_cast(q->level)); + + m_mirror_orchard_keys_host(index) = orchard_key; + ++index; + } + + } // end for iproc + +} // MeshMap::compute_mirror_orchard_keys_view_host + +// ========================================================================= +// ========================================================================= +template +void +MeshMap::update_mirror_orchard_keys(/*const*/ ghost_t * ghost) +{ + + assertm(ghost != nullptr, "ghost is not valid / allocated"); + + // memory allocation for host view + Kokkos::resize(Kokkos::view_alloc(Kokkos::WithoutInitializing), + m_mirror_orchard_keys_host, + static_cast(ghost->mirror_proc_offsets[m_par_env.size()])); + + compute_mirror_orchard_keys_view_host(ghost); + + // memory allocation for device view + Kokkos::resize(Kokkos::view_alloc(Kokkos::WithoutInitializing), + m_mirror_orchard_keys, + static_cast(ghost->mirror_proc_offsets[m_par_env.size()])); + + // upload view to device + Kokkos::deep_copy(m_mirror_orchard_keys, m_mirror_orchard_keys_host); + +} // MeshMap::update_mirror_orchard_keys + +// ========================================================================= +// ========================================================================= +template +void +MeshMap::update_hashmap(bool on_device) +{ + const auto total_num_quads = m_amr_mesh_info.local_num_quadrants_total(); + + if (on_device) + { + // avoid nvcc's warning about lambda capturing this implicitly + // TODO see if we can avoid this (eventhough the following are shadow copies) + auto amr_hashmap_device = m_amr_hashmap; + auto orchard_keys_device = m_orchard_keys; + + // 2. fill the hash map on device (using our device_t) + parallel_for( + "MeshMap::fill_map", + Kokkos::RangePolicy(0, total_num_quads), + KOKKOS_LAMBDA(const int & index) { + amr_hashmap_device.insert(orchard_keys_device(index), index); + }); + + //! 3. download hash map to host + m_amr_hashmap_host.rehash(static_cast(total_num_quads)); + Kokkos::deep_copy(m_amr_hashmap_host, m_amr_hashmap); + } + else + { + // avoid nvcc's warning about lambda capturing this implicitly + // TODO see if we can avoid this (eventhough the following are shadow copies) + auto amr_hashmap_host = m_amr_hashmap_host; + auto orchard_keys_host = m_orchard_keys_host; + + //! 2. fill the hash map on host (using Kokkos::OpenMP) + parallel_for( + "MeshMap::fill_map", + Kokkos::RangePolicy(0, total_num_quads), + KOKKOS_LAMBDA(const int & index) { + amr_hashmap_host.insert(orchard_keys_host(index), index); + }); + + //! 3. upload hash map on device + m_amr_hashmap.rehash(static_cast(total_num_quads)); + Kokkos::deep_copy(m_amr_hashmap, m_amr_hashmap_host); + } +} // MeshMap::update_hashmap + +// ========================================================================= +// ========================================================================= +template +void +MeshMap::update_hashmap(/* const */ forest_t * forest, + /* const */ ghost_t * ghost, + bool on_device) +{ + + // perform update mesh info (only if required) + update_amr_mesh_info(forest, ghost); + + [[maybe_unused]] const auto total_num_quads = m_amr_mesh_info.local_num_quadrants_total(); + + // update orchard keys arrays (host and device) + update_orchard_keys(forest, ghost); + + // clear (device) hashmap + m_amr_hashmap.clear(); + m_amr_hashmap_host.clear(); + + // resize (device) hashmap to hold all quad (owned + ghosts + outside) of current MPI task + m_amr_hashmap.rehash(static_cast(total_num_quads)); + m_amr_hashmap_host.rehash(static_cast(total_num_quads)); + + update_hashmap(on_device); + +} // MeshMap::update_hashmap + +// ========================================================================= +// ========================================================================= +template +void +MeshMap::update_hashmap_serial(/*const*/ forest_t * forest, + /*const*/ ghost_t * ghost) +{ + // type alias to access p4est C API (2D or 3D) + using p4est_t = typename p4est::Wrapper; + + // tree linear index to xyz converter + const BrickConnectivityData convert(m_brick_sizes); + + // perform update mesh info (only if required) + update_amr_mesh_info(forest, ghost); + + [[maybe_unused]] const auto total_num_quads = m_amr_mesh_info.local_num_quadrants_total(); + + // clear hashmap + m_amr_hashmap.clear(); + + // resize hashmap to hold all quad (owned + ghosts + external) of current MPI task + m_amr_hashmap.rehash(static_cast(total_num_quads)); + + amr_hashmap_host_t amr_hashmap_host; + amr_hashmap_host.rehash(static_cast(total_num_quads)); + + iOct_t quadIndex = 0; + + for (auto treeid = forest->first_local_tree; treeid <= forest->last_local_tree; ++treeid) + { + // get current tree + auto tree = p4est_t::tree_array_index(forest->trees, treeid); + + // get tree coordinate + auto tree_xyz = convert.toXYZ(treeid); + + for (size_t qId = 0; qId < tree->quadrants.elem_count; ++qId) + { + auto q = p4est_t::quadrant_array_index(&(tree->quadrants), qId); + + Kokkos::Array octCoord; + octCoord[0] = + static_cast(q->x >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + octCoord[1] = + static_cast(q->y >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + if constexpr (dim == 3) + { + octCoord[2] = + static_cast(q->z >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + } + + auto orchard_key = + orchard_key_t::encode_orchard(tree_xyz, octCoord, static_cast(q->level)); + + // auto insert_result = + amr_hashmap_host.insert(orchard_key, quadIndex); + + quadIndex++; + } + } // end filling hashmap with owned quadrants + + for (size_t qId = 0; qId < ghost->ghosts.elem_count; ++qId) + { + auto q = p4est_t::quadrant_array_index(&(ghost->ghosts), qId); + + // get current ghost's tree id + auto treeid = q->p.which_tree; + + // get tree coordinate + auto tree_xyz = convert.toXYZ(treeid); + + Kokkos::Array octCoord; + octCoord[0] = + static_cast(q->x >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + octCoord[1] = + static_cast(q->y >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + if constexpr (dim == 3) + { + octCoord[2] = + static_cast(q->z >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + } + + auto orchard_key = + orchard_key_t::encode_orchard(tree_xyz, octCoord, static_cast(q->level)); + + // auto insert_result = + amr_hashmap_host.insert(orchard_key, quadIndex); + + quadIndex++; + + } // end filling hashmap with ghost quadrants + + if (amr_hashmap_host.failed_insert() and m_par_env.rank() == 0) + printf("[MeshMap] Something went wrong in fill the hash table)\n"); + + m_amr_hashmap.rehash(static_cast(total_num_quads)); + Kokkos::deep_copy(m_amr_hashmap, amr_hashmap_host); + +} // MeshMap::update_hashmap_serial + +// ========================================================================= +// ========================================================================= +template +bool +MeshMap::is_amr_mesh_info_uptodate(/*const*/ forest_t * forest, + /*const*/ ghost_t * ghost) +{ + // clang-format off + return + (m_amr_mesh_info.local_num_quadrants() == forest->local_num_quadrants) && + (m_amr_mesh_info.global_num_quadrants() == forest->global_num_quadrants) && + (m_amr_mesh_info.local_num_ghosts() == static_cast(ghost->ghosts.elem_count)) && + (m_amr_mesh_info.local_num_mirrors() == ghost->mirror_proc_offsets[m_par_env.size()]); + // clang-format on +} // MeshMap::is_amr_mesh_info_uptodate + +// ========================================================================= +// ========================================================================= +template +void +MeshMap::compute_outside_quad_info(/*const*/ forest_t * forest, + /*const*/ ghost_t * ghost) +{ + // do we have at least one non-periodic border ? + bool has_non_periodic_border = false; + for (size_t idim = 0; idim < dim; ++idim) + { + if (!m_is_brick_periodic[idim]) + has_non_periodic_border = true; + } + + // if all border are periodic, ok no quad outside + if (!has_non_periodic_border) + { + m_outside_quads_info.reset(); + } + else + { + auto num_outside_quads = + compute_number_outside_quads(forest, m_par_env, m_is_brick_periodic, m_brick_sizes); + auto num_outside_ghosts = + compute_number_outside_ghosts(ghost, m_par_env, m_is_brick_periodic, m_brick_sizes); + + m_outside_quads_info.update(ghost, m_par_env, num_outside_quads, num_outside_ghosts); + } +} // MeshMap::compute_outside_quad_info + +// ========================================================================= +// ========================================================================= +template +void +MeshMap::update_amr_mesh_info(/*const*/ forest_t * forest, + /*const*/ ghost_t * ghost) +{ + + { + // make sure the number of outside quads is up to date + compute_outside_quad_info(forest, ghost); + + m_amr_mesh_info.local_num_quadrants() = forest->local_num_quadrants; + m_amr_mesh_info.global_num_quadrants() = forest->global_num_quadrants; + m_amr_mesh_info.global_first_quadrant() = forest->global_first_quadrant[m_par_env.rank()]; + + m_amr_mesh_info.local_num_ghosts() = static_cast(ghost->ghosts.elem_count); + m_amr_mesh_info.local_num_mirrors() = ghost->mirror_proc_offsets[forest->mpisize]; + + // clang-format off + m_amr_mesh_info.local_num_quadrants_outside() = m_outside_quads_info.local_num_outside_quads; + m_amr_mesh_info.global_num_quadrants_outside() = m_outside_quads_info.global_num_outside_quads; + m_amr_mesh_info.global_first_quadrant_outside() = m_outside_quads_info.first_outside_quad_global_id; + m_amr_mesh_info.local_num_quadrants_outside_ghost() = m_outside_quads_info.local_num_outside_ghosts; + m_amr_mesh_info.mpi_rank() = m_par_env.rank(); + // clang-format on + } + +} // MeshMap::update_amr_mesh_info + +// explicit template instantiation +template class MeshMap<2, kalypsso::DefaultDevice>; +template class MeshMap<3, kalypsso::DefaultDevice>; + +// only instantiate those class when the default device is not on host +#if defined(KOKKOS_ENABLE_DEFAULT_DEVICE_TYPE_OPENMP) || \ + defined(KOKKOS_ENABLE_DEFAULT_DEVICE_TYPE_THREADS) || \ + defined(KOKKOS_ENABLE_DEFAULT_DEVICE_TYPE_SERIAL) +#else +template class MeshMap<2, kalypsso::HostDevice>; +template class MeshMap<3, kalypsso::HostDevice>; +#endif // KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE + +} // namespace kalypsso diff --git a/src/kalypsso/core/MeshMap.h b/src/kalypsso/core/MeshMap.h new file mode 100644 index 0000000..2e3a253 --- /dev/null +++ b/src/kalypsso/core/MeshMap.h @@ -0,0 +1,538 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file MeshMap.h + * \brief Container for mesh data adapted from Kokkos::UnorderedMap. + * + */ +#ifndef KALYPSSO_CORE_MESHMAP_H_ +#define KALYPSSO_CORE_MESHMAP_H_ + +#include + +#include +#include + +#include +#include // for CONNECTIVITY_PERIODIC_FALSE +// #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // for std::pair + +namespace kalypsso +{ + +template +KOKKOS_INLINE_FUNCTION auto +key_to_value(uint64_t const & key, + amr_map_t const & amr_map) -> Kokkos::pair +{ + using value_t = typename amr_map_t::value_type; + + // find key index in map + auto key_index_in_map = amr_map.find(key); + + auto is_valid = amr_map.valid_at(key_index_in_map); + + value_t value = is_valid ? amr_map.value_at(key_index_in_map) : value_t{}; + return Kokkos::pair(is_valid, value); +} + +// ======================================================================================== +// ======================================================================================== +// ======================================================================================== +/** + * This class is a kokkos-based class implementing a bidirectional dictionary to map + * a key (orchard key identifying a unique quadrant/octant of a p4est mesh), to a memory index + * where other application-specific data can be found for this quadrant/octant. + * + * This class holds 2 data structures: + * - the hashmap (using Kokkos::UnorderedMap): maps orchard keys to memory index (Morton order) + * - an array of orchard keys (stored in the Morton order): maps index to key + * + * These two data structure together constitute the birectionnal dictionary. + */ +template +class MeshMap +{ +public: + using value_t = iOct_t; + using exec_space = typename device_t::execution_space; + + using orchard_key_view_t = typename orchard_key_base_t::view_t; + using orchard_key_view_host_t = typename orchard_key_base_t::view_host_t; + + //! type alias for a device hashmap with key=orchard_key and value=memory index + using amr_hashmap_t = typename hashmap_base_t::map_t; + + //! type alias for a host mirror of amr_hashmap_t + using amr_hashmap_host_t = typename amr_hashmap_t::HostMirror; + +private: + //! config map (input parameter) + const ConfigMap & m_config_map; + + //! parallel environment + const ParallelEnv & m_par_env; + + //! unordered map from orchard key to memory index + //! + //! Until a better idea pops out, we store the hash map here + //! - amr_hashmap is used in UserDataRemapper but not owned / created there + //! - amr_hashmap is used in MeshGhostsExchanger but not owned / created there + amr_hashmap_t m_amr_hashmap; + + //! host mirror of m_amr_hashmap + amr_hashmap_host_t m_amr_hashmap_host; + + //! orchard keys (locally owned + MPI ghosts + outside) on device + orchard_key_view_t m_orchard_keys; + + //! orchard keys (locally owned + MPI ghosts + outside) on host + orchard_key_view_host_t m_orchard_keys_host; + + //! orchard keys (mirror keys) on device. + //! just to remember mirror quadrants are quadrants that are used to pack data for MPI + //! communication send buffer and we will sent to fill MPI ghosts quadrant in other MPI processes. + orchard_key_view_t m_mirror_orchard_keys; + + //! same as m_mirror_orchard_keys but on host + orchard_key_view_host_t m_mirror_orchard_keys_host; + + //! for each local tree (i.e. a tree inside current MPI task), store the memory index + //! of the first quadrant of these trees + //! \TODO: clarify if this is really needed + // Kokkos::View m_local_first_quad_id_per_tree; + + //! p4est connectivity sizes. + //! brick sizes are used to transform logical coordinates into vertex space (real space) + brick_size_t m_brick_sizes; + + //! array of bool to tell if mesh is periodic or not (one value per direction). + Kokkos::Array m_is_brick_periodic; + + //! outside quadrants information + OutsideQuadsInfo m_outside_quads_info; + + //! structure storing current state of AMR mesh (number of owned, ghost and outside quadrants). + AMRMeshInfo m_amr_mesh_info; + + //! conformal face status array on device (size : locally owned quadrants + MPI quadrants) + conformal_status_view_t m_conformal_status_view; + +public: + // ========================================================================= + // ========================================================================= + //! constructor + MeshMap(ConfigMap const & config_map, ParallelEnv const & par_env); + + // ========================================================================= + // ========================================================================= + //! destructor + ~MeshMap() = default; + + // ========================================================================= + // ========================================================================= + auto + orchard_keys() + { + return m_orchard_keys; + } + + // ========================================================================= + // ========================================================================= + auto + orchard_keys() const + { + return m_orchard_keys; + } + + // ========================================================================= + // ========================================================================= + auto + orchard_keys_clone() + { + using ExecutionSpace = typename orchard_key_view_t::execution_space; + return KokkosExt::clone(ExecutionSpace{}, m_orchard_keys); + } + + // ========================================================================= + // ========================================================================= + auto + orchard_keys_host() + { + return m_orchard_keys_host; + } + + // ========================================================================= + // ========================================================================= + auto + orchard_keys_host_clone() + { + using ExecutionSpace = typename orchard_key_view_host_t::execution_space; + return KokkosExt::clone(ExecutionSpace{}, m_orchard_keys_host); + } + + // ========================================================================= + // ========================================================================= + auto + mirror_orchard_keys() + { + return m_mirror_orchard_keys; + } + + // ========================================================================= + // ========================================================================= + auto + mirror_orchard_keys_host() + { + return m_mirror_orchard_keys_host; + } + + // ========================================================================= + // ========================================================================= + auto + hashmap() + { + return m_amr_hashmap; + } + + // ========================================================================= + // ========================================================================= + auto + hashmap_clone() + { + amr_hashmap_t clone; + clone.rehash(m_amr_hashmap.size()); + Kokkos::deep_copy(clone, m_amr_hashmap); + return clone; + // return KokkosExt::clone_unordered_map(m_amr_hashmap); + } + + // ========================================================================= + // ========================================================================= + auto + hashmap_host() + { + return m_amr_hashmap_host; + } + + // ========================================================================= + // ========================================================================= + auto + hashmap_host_clone() + { + amr_hashmap_host_t clone; + clone.rehash(m_amr_hashmap_host.size()); + Kokkos::deep_copy(clone, m_amr_hashmap_host); + return clone; + // return KokkosExt::clone_unordered_map(m_amr_hashmap_host); + } + + // ========================================================================= + // ========================================================================= + auto + conformal_status() + { + return m_conformal_status_view; + } + + // ========================================================================= + // ========================================================================= + auto + conformal_status_host() + { + return Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, m_conformal_status_view); + } + + // ========================================================================= + // ========================================================================= + auto + is_brick_periodic() const + { + return m_is_brick_periodic; + } + +private: + // ========================================================================= + // ========================================================================= + //! create a host view of all orchard keys (owned + ghost) using current state of p4est. + //! + //! for each quadrant in p4est mesh (locally owned by current MPI process, as well as mpi ghost + //! quadrants), we compute orchard key and put it in a Kokkos::View. The first local_num_quadrants + //! items are the owned quadrant of current MPI process, then the additional + //! ghost->ghosts.elem_count items are the orchard keys of the ghost quadrants. + //! Important note: + //! - if p4est brick connectivity is periodic, external border is treated as any MPI border, + //! nothing special to do + //! - if p4est brick connectivity is not periodic, we chose to include a layer of ghost block + //! outside the domain + //! + //! \param[in] forest is the forest data mesh + //! \param[in] ghost is the p4est data structure containing array of ghost quadrant + //! + //! \note m_orchard_keys_host a host view of all compute orchards keys (locally owned + MPI + //! ghost); it must have been allocated in the calling function + //! + //! Be careful forest and ghost are C structs (no constness inside C API) + //! const qualifier are commented here, just to inform the developper/user that this function + //! is not intended to modify the mesh nor the ghost quadrants. + //! + //! This function assumes (and asserts) orchard_keys_host has the expect size; so memory + //! allocation must be done in the calling function. + //! + //! TODO: this function should probably be made private, only update_orchard_keys should be + //! used publicly + void + compute_orchard_keys_view_host(/*const*/ forest_t * forest, + /*const*/ ghost_t * ghost); + +public: + // ========================================================================= + // ========================================================================= + //! Update host and device views of ALL orchard keys (locally owned + MPI ghost + outside) using + //! current state of p4est. + //! + //! Two steps: + //! 1. update host view (\see compute_orchard_keys_view_host) using p4est API + //! 2. upload on device + //! + //! \param[in] forest is the forest data mesh + //! \param[in] ghost is the p4est data structure containing array of ghost quadrant + //! + void + update_orchard_keys(/*const*/ forest_t * forest, + /*const*/ ghost_t * ghost); + + // ========================================================================= + // ========================================================================= + //! Update device views of ALL conformal status. + //! + //! Take as input orchard_keys view and compute conformal status (for each owned + MPI quadrants). + //! More precisely, ComputeConformalStatusFunctor fills kokkos view m_conformal_status_view, which + //! a 1D array of integers (8bit in 2d and 16bit in 3D), one integer per quadrant, encoding each + //! face conformal status. + //! Conformal status is defined in ConformalFaceStatus.h, see struct conformal_face_status_t. + //! + void + update_conformal_status(); + + //! compute conformal full status + auto + update_conformal_full_status() -> conformal_full_status_view_t; + +private: + // ========================================================================= + // ========================================================================= + //! create a host view of all mirror quadrant's orchard keys using current state of p4est. + //! + //! for each mirror quadrant in p4est mesh, + //! compute orchard key and put it in a Kokkos::View. + //! + //! this routine is mostly useful for performing mesh ghost exchange (MPI). See class + //! MeshGhostsExchanger. + //! + //! \param[in] ghost is the p4est data structure containing array of ghost quadrant + //! + //! be careful forest and ghost are C structs (no constness inside C API) + //! const qualifier are commented here, just to inform the developper/user that this function + //! is not intended to modify the mesh nor the ghost quadrants. + //! + //! IMPORTANT NOTE: orchard key are sorted by MPI processor to send to and then by Morton order + //! + //! TODO: this function should probably be made private, only update_orchard_keys should be + //! used publicly + void + compute_mirror_orchard_keys_view_host(/*const*/ ghost_t * ghost); + +public: + // ========================================================================= + // ========================================================================= + //! Update host and device view of (ghost) mirror quadrant's orchard keys using current state of + //! p4est. + //! + //! Two steps: + //! 1. create a host view using p4est API + //! 2. upload on device + //! + //! \param[in] ghost is the p4est data structure containing array of ghost quadrant + //! + //! + void + update_mirror_orchard_keys(/*const*/ ghost_t * ghost); + + // ========================================================================= + // ========================================================================= + //! create both host and device views of mirror quadrant's orchard keys using current state of + //! p4est. + //! + //! Two steps: + //! 1. create a host view (\see create_orchard_keys_view_serial) using p4est API + //! 2. upload on device + //! + //! \param[in] forest is the forest data mesh + //! \param[in] ghost is the p4est data structure containing array of ghost quadrant + //! + //! \return a pair of Kokkos::View (host and device) of orchards keys (mirror quadrants only) + // std::pair + // create_mirror_orchard_keys_views( + // /*const*/ forest_t * forest, + // /*const*/ ghost_t * ghost, + // std::string view_name) + // { + + // assertm(ghost != nullptr, "ghost is not valid / allocated"); + + // // memory allocation for device view + // orchard_key_view_t keys_view_device(Kokkos::view_alloc(Kokkos::WithoutInitializing, + // view_name), + // ghost->mirror_proc_offsets[m_par_env.size()]); + + // // create view on host + // auto keys_view_host = Kokkos::create_mirror_view(keys_view_device); + + // compute_mirror_orchard_keys_view_host(forest, ghost); + + // // upload view to device + // Kokkos::deep_copy(keys_view_device, keys_view_host); + + // return { keys_view_host, keys_view_device }; + + // } // create_mirror_orchard_keys_views + + // ========================================================================= + // ========================================================================= + //! update hashmap (key = orchard key, value = memory index). + //! + //! this routine assume the orchard keys arrays are up to date. + //! + void + update_hashmap(bool on_device = false); + + // ========================================================================= + // ========================================================================= + //! update orchard keys array and then update hashmap (key = orchard key, value = memory index) + //! + //! There are 2 slightly different implementations (TODO: benchmark which can be fastest when + //! device is GPU). + //! + //! In any case, we start by updating the orchard keys arrays (host and device). + //! + //! if on_device is true, fill the hash map on device (using our device_t) + //! if on_device is false, fill the hash map on host (using Kokkos::OpenMP) + //! + //! \param[in] forest + //! \param[in] ghost + //! \param[in] on_device : if true, filling hash_map is update on device and copied on host + //! else updated on host first then copied on device + //! + void + update_hashmap(/* const */ forest_t * forest, + /* const */ ghost_t * ghost, + bool on_device = false); + + // ========================================================================= + // ========================================================================= + //! clear and fill hash table using current state of p4est. + //! for each quadrant in p4est mesh + mpi ghost quadrants, + //! compute orchard key and insert tuple (orchard_key, octant index) + //! into the hash table + //! + //! \param[in] forest is the forest data mesh + //! \param[in] ghost is the p4est data structure containing array of ghost quadrant + //! + //! be careful there are C structs (no constness inside C API) + //! const qualifier are commented here, just to inform the developper/user that this function + //! is not intended to modify the mesh nor the ghost quadrants. + //! + //! \note beware this function only consider owned and MPI ghost quadrants. Outside quadrants + //! are not used here. Please use fill_map instead. fill_map internally first updates the + //! orchard keys array by considering all keys (owned, ghost and outside), and then fill the map + //! with them. + void + update_hashmap_serial(/*const*/ forest_t * forest, /*const*/ ghost_t * ghost); + + // ========================================================================= + // ========================================================================= + //! Pseudo check to test if current amr_mesh_info is up to date. + bool + is_amr_mesh_info_uptodate(/*const*/ forest_t * forest, /*const*/ ghost_t * ghost); + + // ========================================================================= + // ========================================================================= + //! compute outside quad information + //! + //! This routine assumes m_num_outside_quads and m_num_outside_ghosts are up to date. + //! This is ok to call it from inside update_amr_mesh_info. + void + compute_outside_quad_info(/*const*/ forest_t * forest, /*const*/ ghost_t * ghost); + + // ========================================================================= + // ========================================================================= + //! provide outside quadrants information. + OutsideQuadsInfo + get_outside_quads_info() const + { + return m_outside_quads_info; + } + + // ========================================================================= + // ========================================================================= + //! provide a valid instance of AMRMeshInfo from m_outside_quads_info. + //! + //! computes the number of quadrants per type (owned, ghost, outside and outside_ghost) + void + update_amr_mesh_info(/*const*/ forest_t * forest, /*const*/ ghost_t * ghost); + + // ========================================================================= + // ========================================================================= + //! provide outside quadrants information. + AMRMeshInfo const & + get_amr_mesh_info() const + { + return m_amr_mesh_info; + } + + // ========================================================================= + // ========================================================================= + //! provide outside quadrants information. + OutsideQuadsInfo & + get_outside_quads_info() + { + return m_outside_quads_info; + } + +}; // class MeshMap + +// explicit template instantiation +extern template class MeshMap<2, kalypsso::DefaultDevice>; +extern template class MeshMap<3, kalypsso::DefaultDevice>; + +// only instantiate those class when the default device is not on host +#if defined(KOKKOS_ENABLE_DEFAULT_DEVICE_TYPE_OPENMP) || \ + defined(KOKKOS_ENABLE_DEFAULT_DEVICE_TYPE_THREADS) || \ + defined(KOKKOS_ENABLE_DEFAULT_DEVICE_TYPE_SERIAL) +#else +extern template class MeshMap<2, kalypsso::HostDevice>; +extern template class MeshMap<3, kalypsso::HostDevice>; +#endif // KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE + + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_MESHMAP_H_ diff --git a/src/kalypsso/core/MeshPartitioner.h b/src/kalypsso/core/MeshPartitioner.h new file mode 100644 index 0000000..2698169 --- /dev/null +++ b/src/kalypsso/core/MeshPartitioner.h @@ -0,0 +1,655 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file MeshPartitioner.h + */ +#ifndef KALYPSSO_CORE_MESHPARTITIONER_H_ +#define KALYPSSO_CORE_MESHPARTITIONER_H_ + +#include +#include +#include +#include // for DataArrayBlock, FaceDataArrayBlock, ... +#include + +#include +#include + +#include + +#include + +#include + +#include // for std::vector + +#include "MeshPartitioner_helper.h" + +namespace kalypsso +{ + +/** + * \class MeshPartitioner + * + * Two main functions: + * + * - wrapper around p4est API p4est_partition for re-partitioning mesh among MPI processes + * - propagate new partition to user data + */ +template +class MeshPartitioner +{ + +private: +public: + using exec_space = typename device_t::execution_space; + + //! type alias to access p4est C API (2D or 3D) + using p4est_t = typename p4est::Wrapper; + using forest_t = typename p4est_t::forest_t; + using weight_cb_t = typename p4est_t::weight_cb_t; + + template + using DataArrayLeafSoA_t = DataArrayLeafSoA; + + template + using DataArrayLeafAoS_t = DataArrayLeafAoS; + + template + using DataArrayBlock_t = DataArrayBlock; + + template + using DataArrayGhostedBlock_t = DataArrayGhostedBlock; + + template + using DataArrayBlockMultiVar_t = DataArrayBlockMultiVar; + + template + using FaceDataArrayBlock_t = FaceDataArrayBlock; + + using MaterialPresenceView_t = MaterialPresenceView; + + using Weights_t = Kokkos::View; + using Hashmap_t = typename hashmap_base_t::map_t::HostMirror; + + // ========================================================================= + // ========================================================================= + //! constructor + MeshPartitioner(const ConfigMap & config_map, const ParallelEnv & par_env) + : m_config_map(config_map) + , m_par_env(par_env) + , m_local_num_quadrants_new(0) + { + m_lb_gfq_old.resize(static_cast(m_par_env.nRanks() + 1)); + m_lb_gfq_new.resize(static_cast(m_par_env.nRanks() + 1)); + } + + // ========================================================================= + // ========================================================================= + //! destructor + ~MeshPartitioner() = default; + + // ========================================================================= + // ========================================================================= + //! Repartition a p4est mesh (ONLY meta data, NO user data). + //! + //! We also use alternatively the words \"load balancing\" for designating the mesh partitioning + //! operation. + //! + //! \param[in] forest, the p4est main object + //! + //! After this call, p4est object will change, e.g. : + //! - local number of quadrant will change; this is the main goal of p4est_partition to have a new + //! mesh for which the local number of quadrants is (almost) the same on all MPI processes + //! - ghost won't be valid anymore + void + partition_mesh(forest_t * forest) + { + Kokkos::Profiling::ScopedRegion myprof("MeshPartitioner::partition_mesh"); + + // if there is only one MPI rank, nothing to do + if (m_par_env.nRanks() < 2) + return; + + // remainder: MPI partition is encoded into the gfq array which is an array containing the + // global index of the first quadrant of each MPI process) + + // 1. record the current (soon to be old) partition gfq array + { + const size_t gfq_size = m_lb_gfq_old.size(); + for (uint32_t rank = 0; rank < gfq_size; ++rank) + { + m_lb_gfq_old[rank] = forest->global_first_quadrant[rank]; + } + } + + // 2. call p4est_partition + const int allow_for_coarsening = 1; + const weight_cb_t weight_cb = nullptr; + + KALYPSSO_INFO("before p4est_partition"); + + p4est_t::partition(forest, allow_for_coarsening, weight_cb); + + KALYPSSO_INFO("after p4est_partition"); + + // 3. record new gfq (global first quadrant index) + { + const auto gfq_size = m_lb_gfq_new.size(); + for (uint32_t rank = 0; rank < gfq_size; ++rank) + { + m_lb_gfq_new[rank] = forest->global_first_quadrant[rank]; + } + } + + // 4. record the new current number of leaves/quadrants + m_local_num_quadrants_new = forest->local_num_quadrants; + + } // partition_mesh + + // ========================================================================= + // ========================================================================= + //! Inner type for partition_mesh_with_weights + struct WeightsCallback + { + using topidx_t = p4est::topidx_t; + using quadrant_t = typename p4est_t::quadrant_t; + + struct Data + { + Weights_t weights; + BrickConnectivityData convert; + Hashmap_t hashmap; + }; + + static Data * data; + + static int + callback([[maybe_unused]] forest_t * forest, topidx_t tree_id, quadrant_t * quadrant) + { + Kokkos::Array oct_coord; + oct_coord[0] = static_cast( + quadrant->x >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + + oct_coord[1] = static_cast( + quadrant->y >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + + if constexpr (dim == 3) + oct_coord[2] = static_cast( + quadrant->z >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + + const auto orchard_key = orchard_key_t::encode_orchard( + data->convert.toXYZ(tree_id), oct_coord, static_cast(quadrant->level)); + + const auto i_oct_hash = data->hashmap.find(orchard_key); + if (data->hashmap.valid_at(i_oct_hash)) + { + const auto i_oct = data->hashmap.value_at(i_oct_hash); + return static_cast(data->weights(i_oct)); + } + else + { + KOKKOS_ASSERT("Invalid orchard id in partition_mesh_with_weights callback"); + return 0; + } + } + }; + + // ========================================================================= + // ========================================================================= + //! + //! \param[in] forest the p4est main object + //! \param[in] weights a host kokkos view that contains the weight of each octant + //! \param[in] hashmap the orchard key to octant index map + //! + void + partition_mesh_with_weights(forest_t * forest, + Weights_t weights, + brick_size_t brick_sizes, + Hashmap_t hashmap) + { + Kokkos::Profiling::ScopedRegion myprof("MeshPartitioner::partition_mesh_with_weights"); + + // if there is only one MPI rank, nothing to do + if (m_par_env.nRanks() < 2) + return; + + // remainder: MPI partition is encoded into the gfq array which is an array containing the + // global index of the first quadrant of each MPI process) + + // 1. record the current (soon to be old) partition gfq array + { + const size_t gfq_size = m_lb_gfq_old.size(); + for (uint32_t rank = 0; rank < gfq_size; ++rank) + { + m_lb_gfq_old[rank] = forest->global_first_quadrant[rank]; + } + } + + // 2. call p4est_partition + const int allow_for_coarsening = 1; + typename WeightsCallback::Data data{ weights, + BrickConnectivityData(brick_sizes), + hashmap }; + WeightsCallback::data = &data; + const weight_cb_t weight_cb = &WeightsCallback::callback; + + KALYPSSO_INFO("before p4est_partition"); + + p4est_t::partition(forest, allow_for_coarsening, weight_cb); + + KALYPSSO_INFO("after p4est_partition"); + + WeightsCallback::data = nullptr; + + // 3. record new gfq (global first quadrant index) + { + const auto gfq_size = m_lb_gfq_new.size(); + for (uint32_t rank = 0; rank < gfq_size; ++rank) + { + m_lb_gfq_new[rank] = forest->global_first_quadrant[rank]; + } + } + + // 4. record the new current number of leaves/quadrants + m_local_num_quadrants_new = forest->local_num_quadrants; + + } // partition_mesh_with_weights + + // ======================================================= + // ======================================================= + //! cell-center user data load balancing (repartitioning) with MPI data communication. + //! + //! MPI communication pattern is provided by + //! p4est, and used inside API p4est_transfer_fixed + //! \note load balancing is also called re-partitioning + //! + //! \param[in] userdata_old a DataArrayBlock_t (device Kokkos::View) before re-partition + //! \param[in,out] userdata_new a DataArrayBlock_t (device Kokkos::View) after re-partition, + //! userdata_new will be properly resized + //! + //! Implementation note: use old and new gfq, and p4est_transfer_fixed to re-partition user data + template + void + repartition_userdata(DataArrayBlock_t userdata_old, DataArrayBlock_t & userdata_new) + { + Kokkos::Profiling::ScopedRegion myprof( + "MeshPartitioner::repartition_userdata - cell-center block"); + + // if there is only one MPI rank, nothing to do + if (m_par_env.nRanks() < 2) + return; + + // resize userdata_new (actually just reallocate memory without initializing) + // so that it can at least old data for the new partition + // if (userdata_new.extent(2) < static_cast(m_local_num_quadrants_new)) + { + userdata_new.resize(static_cast(m_local_num_quadrants_new)); + } + + // size in bytes of data per octant + const size_t datasize_per_octant_bytes = + static_cast(userdata_old.num_cells() * userdata_old.num_vars()) * sizeof(T); + + // \note userdata_old is a Kokkos::View with Left memory layout, so that all data attached to + // a octant is memory contiguous, so we don't need any auxiliary array, userdata_old can be + // directly used in the p4est_transfer_fixed API \note here we strongly assume the MPI + // implementation to be CUDA aware (i.e. when device_t is Kokkos::CUDA); this is a requirement + // tested by top-level cmake + // TODO: if the MPI implementation is not cuda-aware (we need to first transfer data on host, + // before calling p4est_transfer_fixed); this maybe useful for debug on a platform where + // cuda-aware MPI is not available (even though, it is always possible to recompile locally + // OpenMPI from source with cuda-awareness) + // p4est_t::transfer_fixed(m_lb_gfq_new.data(), + // m_lb_gfq_old.data(), + // m_par_env.mpi_comm(), + // KALYPSSO_COMM_MESH_PARTITIONER_TAG, + // userdata_new.data(), // after re-partition + // userdata_old.data(), // before re-partition + // datasize_per_octant_bytes); + MeshPartitioner_helper::transfer_fixed( + m_lb_gfq_new.data(), + m_lb_gfq_old.data(), + m_par_env.mpi_comm(), + KALYPSSO_COMM_MESH_PARTITIONER_TAG, + userdata_new.data(), // after re-partition + userdata_old.data(), // before re-partition + datasize_per_octant_bytes); + + } // repartition_userdata - cell-center block data + + // ======================================================= + // ======================================================= + //! face-center user data load balancing (repartitioning) with MPI data communication. + //! + //! MPI communication pattern is provided by + //! p4est, and used inside API p4est_transfer_fixed + //! \note load balancing is also called re-partitioning + //! + //! \param[in] userdata_old a FaceDataArrayBlock_t (device Kokkos::View) before re-partition + //! \param[in,out] userdata_new a FaceDataArrayBlock_t (device Kokkos::View) after re-partition, + //! userdata_new will be properly resized + //! + //! Implementation note: use old and new gfq, and p4est_transfer_fixed to re-partition user data + template + void + repartition_userdata(FaceDataArrayBlock_t userdata_old, FaceDataArrayBlock_t & userdata_new) + { + Kokkos::Profiling::ScopedRegion myprof( + "MeshPartitioner::repartition_userdata - face-center block"); + + // if there is only one MPI rank, nothing to do + if (m_par_env.nRanks() < 2) + return; + + // resize userdata_new (actually just reallocate memory without initializing) + // so that it can at least old data for the new partition + userdata_new.resize(static_cast(m_local_num_quadrants_new)); + + // size in bytes of data per octant + const size_t datasize_per_octant_bytes = + static_cast(userdata_old.num_elements_per_octant()) * sizeof(T); + + // \note userdata_old is a FaceDataArrayBlock which uses sort of left memory layout, so that all + // data attached to a octant is memory contiguous, so we don't need any auxiliary array, + // userdata_old can be directly used in the p4est_transfer_fixed API. + // + // \note here we STRONGLY assume the MPI implementation to be CUDA aware + // (i.e. when device_t is Kokkos::CUDA); this is a requirement tested by top-level cmake + // TODO: if the MPI implementation is not cuda-aware (we need to first transfer data on host, + // before calling p4est_transfer_fixed); this maybe useful for debug on a platform where + // cuda-aware MPI is not available (even though, it is always possible to recompile locally + // OpenMPI from source with cuda-awareness) + MeshPartitioner_helper::transfer_fixed( + m_lb_gfq_new.data(), + m_lb_gfq_old.data(), + m_par_env.mpi_comm(), + KALYPSSO_COMM_MESH_PARTITIONER_TAG, + userdata_new.data(), // after re-partition + userdata_old.data(), // before re-partition + datasize_per_octant_bytes); + + } // repartition_userdata - face-center block data + + // ======================================================= + // ======================================================= + //! user data load balancing (repartitioning) with MPI data communication. + //! + //! MPI communication pattern is provided by + //! p4est, and used inside API p4est_transfer_fixed + //! \note load balancing is also called re-partitioning + //! + //! \param[in] userdata_old a DataArrayGhostedBlock_t (device Kokkos::View) before re-partition + //! \param[in,out] userdata_new a DataArrayGhostedBlock_t (device Kokkos::View) after + //! re-partition, userdata_new will be properly resized + //! + //! Implementation note: use old and new gfq, and p4est_transfer_fixed to re-partition user data + //! + //! Important note: we actually transfer the entire block (ghost included) + //! if this is a performance problem, we will refactor and transfer only the inner part (by + //! removing the ghost cells.) + template + void + repartition_userdata(DataArrayGhostedBlock_t userdata_old, + DataArrayGhostedBlock_t & userdata_new) + { + + repartition_userdata(userdata_old.view(), userdata_new.view_ref()); + + } // repartition_userdata - ghosted block + + // ======================================================= + // ======================================================= + //! user data load balancing (repartitioning) with MPI data communication. + //! + //! MPI communication pattern is provided by + //! p4est, and used inside API p4est_transfer_fixed + //! \note load balancing is also called re-partitioning + //! + //! \param[in] userdata_old a DataArrayBlock_t (device Kokkos::View) before re-partition + //! \param[in,out] userdata_new a DataArrayBlock_t (device Kokkos::View) after re-partition, + //! userdata_new will be properly resized + //! + //! Implementation note: use old and new gfq, and p4est_transfer_fixed to re-partition user data + template + void + repartition_userdata(DataArrayLeafAoS_t userdata_old, DataArrayLeafAoS_t & userdata_new) + { + Kokkos::Profiling::ScopedRegion myprof("MeshPartitioner::repartition_userdata - leaf"); + + // if there is only one MPI rank, nothing to do + if (m_par_env.nRanks() < 2) + return; + + // resize userdata_new (actually just reallocate memory without initializing) + // so that it can at least old data for the new partition + if (userdata_new.extent(0) < static_cast(m_local_num_quadrants_new)) + { + Kokkos::realloc(Kokkos::view_alloc(Kokkos::WithoutInitializing), + userdata_new, + static_cast(m_local_num_quadrants_new), + userdata_old.extent(1)); + } + + // size in bytes of data per octant + const auto datasize_per_octant_bytes = userdata_old.extent(1) * sizeof(T); + + // \note userdata_old is a Kokkos::View with Left memory layout, so that all data attached to + // a octant is memory contiguous, so we don't need any auxiliary array, userdata_old can be + // directly used in the p4est_transfer_fixed API \note here we strongly assume the MPI + // implementation to be CUDA aware (i.e. when device_t is Kokkos::CUDA); this is a requirement + // tested by top-level cmake + // TODO: if the MPI implementation is not cuda-aware (we need to first transfer data on host, + // before calling p4est_transfer_fixed); this maybe useful for debug on a platform where + // cuda-aware MPI is not available (even though, it is always possible to recompile locally + // OpenMPI from source with cuda-awareness) + p4est_t::transfer_fixed(m_lb_gfq_new.data(), + m_lb_gfq_old.data(), + m_par_env.mpi_comm(), + KALYPSSO_COMM_MESH_PARTITIONER_TAG, + userdata_new.data(), // after re-partition + userdata_old.data(), // before re-partition + datasize_per_octant_bytes); + + } // repartition_userdata + + // ======================================================= + // ======================================================= + //! user data load balancing (repartitioning) with MPI data communication. + //! + //! MPI communication pattern is provided by + //! p4est, and used inside API p4est_transfer_fixed + //! \note load balancing is also called re-partitioning + //! + //! \param[in] userdata_old a MaterialPresenceView_t before re-partition + //! \param[in,out] userdata_new a MaterialPresenceView_t after re-partition, userdata_new will be + //! properly resized + //! + //! Implementation note: use old and new gfq, and p4est_transfer_fixed to re-partition user data + //! Because a MaterialPresenceView_t is simply a collection of integers for each octant, we can + //! easily reuse the already created transfer functions. + void + repartition_userdata(MaterialPresenceView_t userdata_old, MaterialPresenceView_t & userdata_new) + { + Kokkos::Profiling::ScopedRegion myprof( + "MeshPartitioner::repartition_userdata - material presence"); + + // if there is only one MPI rank, nothing to do + if (m_par_env.nRanks() < 2) + return; + + // resize userdata_new so that it can at least old data for the new partition + userdata_new.resize(m_local_num_quadrants_new); + + // size in bytes of data per octant + const size_t datasize_per_octant_bytes = + static_cast(userdata_old.block_length_per_octant()) * + sizeof(typename MaterialPresenceView_t::BitBlock_t); + + // \note userdata_old is a Kokkos::View with Left memory layout, so that all data attached to + // a octant is memory contiguous, so we don't need any auxiliary array, userdata_old can be + // directly used in the p4est_transfer_fixed API \note here we strongly assume the MPI + // implementation to be CUDA aware (i.e. when device_t is Kokkos::CUDA); this is a requirement + // tested by top-level cmake + // TODO: if the MPI implementation is not cuda-aware (we need to first transfer data on host, + // before calling p4est_transfer_fixed); this maybe useful for debug on a platform where + // cuda-aware MPI is not available (even though, it is always possible to recompile locally + // OpenMPI from source with cuda-awareness) + MeshPartitioner_helper::transfer_fixed( + m_lb_gfq_new.data(), + m_lb_gfq_old.data(), + m_par_env.mpi_comm(), + KALYPSSO_COMM_MESH_PARTITIONER_TAG, + userdata_new.data(), // after re-partition + userdata_old.data(), // before re-partition + datasize_per_octant_bytes); + } // repartition_userdata + + // ======================================================= + // ======================================================= + //! cell-center user data load balancing (repartitioning) with MPI data communication. + //! + //! MPI communication pattern is provided by + //! p4est, and used inside API p4est_transfer_fixed + //! \note load balancing is also called re-partitioning + //! + //! \param[in] userdata_old a DataArrayBlockMultiVar_t (device Kokkos::View) before re-partition + //! \param[in,out] userdata_new a DataArrayBlockMultiVar_t (device Kokkos::View) after + //! re-partition, is is supposed to be properly sized + //! + //! Implementation note: use old and new gfq, and p4est_transfer_fixed to re-partition user data + template + void + repartition_userdata(DataArrayBlockMultiVar_t userdata_old, + DataArrayBlockMultiVar_t & userdata_new) + { + Kokkos::Profiling::ScopedRegion myprof( + "MeshPartitioner::repartition_userdata - cell-center block"); + + // if there is only one MPI rank, nothing to do + if (m_par_env.nRanks() < 2) + return; + + // size in bytes of data per block + const size_t datasize_per_block_bytes = + static_cast(userdata_old.num_cells()) * sizeof(T); + + // using HostSpace = typename HostDevice::execution_space; + const auto old_offsets = + DataArrayBlockMultiVar_t::Offsets_t::create_host_mirror_view_and_copy( + userdata_old.offsets()); + const auto new_offsets = + DataArrayBlockMultiVar_t::Offsets_t::create_host_mirror_view_and_copy( + userdata_new.offsets()); + + MeshPartitioner_helper::transfer_fixed_multi_var( + m_lb_gfq_new.data(), + m_lb_gfq_old.data(), + m_par_env.mpi_comm(), + KALYPSSO_COMM_MESH_PARTITIONER_TAG, + userdata_new.storage().data(), // after re-partition + userdata_old.storage().data(), // before re-partition + datasize_per_block_bytes, + old_offsets.data(), + new_offsets.data()); + + } // repartition_userdata - cell-center block data + + // ======================================================= + // ======================================================= + //! user data load balancing (repartitioning) with MPI data communication. + //! + //! MPI communication pattern is provided by + //! p4est, and used inside API p4est_transfer_fixed + //! \note load balancing is also called re-partitioning + //! + //! \param[in] userdata_old a DataArrayBlock_t (device Kokkos::View) before re-partition + //! \param[in,out] userdata_new a DataArrayBlock_t (device Kokkos::View) after re-partition, + //! userdata_new will be properly resized + //! + //! Implementation note: use old and new gfq, and p4est_transfer_fixed to re-partition user data + template + void + repartition_userdata(DataArrayLeafSoA_t userdata_old, DataArrayLeafSoA_t & userdata_new) + { + Kokkos::Profiling::ScopedRegion myprof("MeshPartitioner::repartition_userdata - leaf"); + + // if there is only one MPI rank, nothing to do + if (m_par_env.nRanks() < 2) + return; + + if constexpr (0 == 1) + { + // currently DataArrayLeaf_t use's a layout where the octant id is the first index (SoA). + // to be able to transfer data: + // - we either need to transpose data (this is a question of AoS versus SoA) + // - or perform transfer variable per variable + // TODO + + // resize userdata_new (actually just reallocate memory without initializing) + // so that it can at least old data for the new partition + if (userdata_new.extent(0) < static_cast(m_local_num_quadrants_new)) + { + Kokkos::realloc(Kokkos::view_alloc(Kokkos::WithoutInitializing), + userdata_new, + static_cast(m_local_num_quadrants_new), + userdata_old.extent(1)); + } + + // size in bytes of data per octant + const auto datasize_per_octant_bytes = userdata_old.extent(1) * sizeof(T); + + // \note userdata_old is a Kokkos::View with Left memory layout, so that all data attached to + // a octant is memory contiguous, so we don't need any auxiliary array, userdata_old can be + // directly used in the p4est_transfer_fixed API \note here we strongly assume the MPI + // implementation to be CUDA aware (i.e. when device_t is Kokkos::CUDA); this is a requirement + // tested by top-level cmake + // TODO: if the MPI implementation is not cuda-aware (we need to first transfer data on host, + // before calling p4est_transfer_fixed); this maybe useful for debug on a platform where + // cuda-aware MPI is not available (even though, it is always possible to recompile locally + // OpenMPI from source with cuda-awareness) + p4est_t::transfer_fixed(m_lb_gfq_new.data(), + m_lb_gfq_old.data(), + m_par_env.mpi_comm(), + KALYPSSO_COMM_MESH_PARTITIONER_TAG, + userdata_new.data(), // after re-partition + userdata_old.data(), // before re-partition + datasize_per_octant_bytes); + } + else + { + KALYPSSO_ERROR("Please implement me"); + } + } // repartition_userdata + +private: + //! config map (input parameter) + const ConfigMap & m_config_map; + + //! parallel environment + const ParallelEnv & m_par_env; + + /* + * Data array used for MPI load balancing / mesh partitioning + */ + + //! array of global index to the first quadrant of each MPI process, before re-partitioning + std::vector m_lb_gfq_old; + + //! array of global index to the first quadrant of each MPI process, after re-partitioning + std::vector m_lb_gfq_new; + + //! local (current MPI process) number of quadrants + int32_t m_local_num_quadrants_new; + +}; // class MeshPartitioner + +template +typename MeshPartitioner::WeightsCallback::Data * + MeshPartitioner::WeightsCallback::data = nullptr; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_MESHPARTITIONER_H_ diff --git a/src/kalypsso/core/MeshPartitioner_helper.h b/src/kalypsso/core/MeshPartitioner_helper.h new file mode 100644 index 0000000..1cc558a --- /dev/null +++ b/src/kalypsso/core/MeshPartitioner_helper.h @@ -0,0 +1,519 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file MeshPartitioner_helper.h + */ + +#include +#include +#include // for DataArray, DataArrayHost + +#include +#include + +#include + +#include + +#include + +#include // for std::vector +#include +#include // for std::is_same_v + +namespace kalypsso +{ + +/** + * Re-implement p4est_transfer_fixed for bringing device support (CUDA, HIP, ...) + */ +template +struct MeshPartitioner_helper +{ + using p4est_t = typename p4est::Wrapper; + + using ExecSpace = typename device_t::execution_space; + using MemorySpace = typename ExecSpace::memory_space; + + // ======================================================================================== + // ======================================================================================== + static void + transfer_fixed(const p4est::gloidx_t * dest_gfq, + const p4est::gloidx_t * src_gfq, + MPI_Comm mpicomm, + int tag, + void * dest_data, + const void * src_data, + size_t data_size_in_bytes) + { + typename p4est_t::transfer_context_t * tc; + + tc = transfer_fixed_begin( + dest_gfq, src_gfq, mpicomm, tag, dest_data, src_data, data_size_in_bytes); + p4est_t::transfer_fixed_end(tc); + } // kalypsso_transfer_fixed + + // ======================================================================================== + // ======================================================================================== + static void + transfer_fixed_multi_var(const p4est::gloidx_t * dest_gfq, + const p4est::gloidx_t * src_gfq, + MPI_Comm mpicomm, + int tag, + void * dest_data, + const void * src_data, + size_t data_size_in_bytes, + const uint32_t * src_offsets, + const uint32_t * dst_offsets) + { + typename p4est_t::transfer_context_t * tc; + + tc = transfer_fixed_multi_var_begin(dest_gfq, + src_gfq, + mpicomm, + tag, + dest_data, + src_data, + data_size_in_bytes, + src_offsets, + dst_offsets); + p4est_t::transfer_fixed_end(tc); + } // transfer_fixed_multi_var + + // ======================================================================================== + // ======================================================================================== + static void + transfer_assign_comm([[maybe_unused]] const p4est::gloidx_t * dest_gfq, + [[maybe_unused]] const p4est::gloidx_t * src_gfq, + sc_MPI_Comm mpicomm, + int * mpisize, + int * mpirank) + { + int mpiret; + + P4EST_ASSERT(dest_gfq != NULL && src_gfq != NULL); + P4EST_ASSERT(dest_gfq[0] == 0 && src_gfq[0] == 0); + P4EST_ASSERT(mpicomm != sc_MPI_COMM_NULL); + P4EST_ASSERT(mpisize != NULL && mpirank != NULL); + + mpiret = sc_MPI_Comm_size(mpicomm, mpisize); + SC_CHECK_MPI(mpiret); + mpiret = sc_MPI_Comm_rank(mpicomm, mpirank); + SC_CHECK_MPI(mpiret); + + P4EST_ASSERT(dest_gfq[*mpisize] == src_gfq[*mpisize]); + P4EST_ASSERT(0 <= dest_gfq[*mpirank] && dest_gfq[*mpirank] <= dest_gfq[*mpirank + 1] && + dest_gfq[*mpirank + 1] <= dest_gfq[*mpisize]); + P4EST_ASSERT(0 <= src_gfq[*mpirank] && src_gfq[*mpirank] <= src_gfq[*mpirank + 1] && + src_gfq[*mpirank + 1] <= src_gfq[*mpisize]); + } + + // ======================================================================================== + // ======================================================================================== + static typename p4est_t::transfer_context_t * + transfer_fixed_begin(const p4est::gloidx_t * dest_gfq, + const p4est::gloidx_t * src_gfq, + sc_MPI_Comm mpicomm, + int tag, + void * dest_data, + const void * src_data, + size_t data_size) + { + typename p4est_t::transfer_context_t * tc; + int mpiret; + int mpisize, mpirank; + int q; + int first_sender, last_sender; + int first_receiver, last_receiver; + char * rb; + char * dest_cp, *src_cp; + size_t byte_len, cp_len; + p4est::gloidx_t dest_begin, dest_end; + p4est::gloidx_t src_begin, src_end; + p4est::gloidx_t gbegin, gend; + sc_MPI_Request * rq; + + /* setup context structure */ + tc = P4EST_ALLOC_ZERO(typename p4est_t::transfer_context_t, 1); + tc->variable = 0; + + /* there is nothing to do when there is no data */ + if (data_size == 0) + { + return tc; + } + + /* grab local partition information */ + transfer_assign_comm(dest_gfq, src_gfq, mpicomm, &mpisize, &mpirank); + dest_begin = dest_gfq[mpirank]; + dest_end = dest_gfq[mpirank + 1]; + src_begin = src_gfq[mpirank]; + src_end = src_gfq[mpirank + 1]; + + /* prepare data copy for local overlap */ + dest_cp = src_cp = NULL; + cp_len = 0; + + /* figure out subset of processes to receive from */ + if (dest_begin < dest_end) + { + P4EST_ASSERT(dest_data != NULL); + + /* our process as the receiver is not empty */ + first_sender = p4est_bsearch_partition(dest_begin, src_gfq, mpisize); + P4EST_ASSERT(0 <= first_sender && first_sender < mpisize); + last_sender = + p4est_t::bsearch_partition(dest_end - 1, &src_gfq[first_sender], mpisize - first_sender) + + first_sender; + P4EST_ASSERT(first_sender <= last_sender && last_sender < mpisize); + tc->num_senders = last_sender - first_sender + 1; + P4EST_ASSERT(tc->num_senders > 0); + + /* go through sender processes and post receive calls */ + gend = dest_begin; + rq = tc->recv_req = P4EST_ALLOC(sc_MPI_Request, static_cast(tc->num_senders)); + rb = reinterpret_cast(dest_data); + for (q = first_sender; q <= last_sender; ++q) + { + /* prepare positions for the sender process q */ + gbegin = gend; + gend = src_gfq[q + 1]; + if (gend > dest_end) + { + P4EST_ASSERT(q == last_sender); + gend = dest_end; + } + P4EST_ASSERT(q == first_sender || q == last_sender ? gbegin < gend : gbegin <= gend); + + /* choose how to treat the sender process */ + if (gbegin == gend) + { + /* the sender process is empty; we need no message */ + P4EST_ASSERT(first_sender < q && q < last_sender); + *rq++ = sc_MPI_REQUEST_NULL; + } + else + { + /* nonzero message from this sender */ + byte_len = static_cast(gend - gbegin) * data_size; + if (q == mpirank) + { + /* on the same rank we remember pointers for memcpy */ + cp_len = byte_len; + dest_cp = rb; + *rq++ = sc_MPI_REQUEST_NULL; + } + else + { + /* we receive a proper message */ + mpiret = + sc_MPI_Irecv(rb, static_cast(byte_len), sc_MPI_BYTE, q, tag, mpicomm, rq++); + SC_CHECK_MPI(mpiret); + } + rb += byte_len; + } + } + P4EST_ASSERT(rb - (char *)dest_data == (ptrdiff_t)((dest_end - dest_begin) * data_size)); + } + + /* figure out subset of processes to send to */ + if (src_begin < src_end) + { + P4EST_ASSERT(src_data != NULL); + + /* our process as the sender is not empty */ + first_receiver = p4est_t::bsearch_partition(src_begin, dest_gfq, mpisize); + P4EST_ASSERT(0 <= first_receiver && first_receiver < mpisize); + last_receiver = p4est_t::bsearch_partition( + src_end - 1, &dest_gfq[first_receiver], mpisize - first_receiver) + + first_receiver; + P4EST_ASSERT(first_receiver <= last_receiver && last_receiver < mpisize); + tc->num_receivers = last_receiver - first_receiver + 1; + P4EST_ASSERT(tc->num_receivers > 0); + + /* go through receiver processes and post send calls */ + gend = src_begin; + rq = tc->send_req = P4EST_ALLOC(sc_MPI_Request, static_cast(tc->num_receivers)); + rb = const_cast(reinterpret_cast(src_data)); + for (q = first_receiver; q <= last_receiver; ++q) + { + /* prepare positions for the receiver process q */ + gbegin = gend; + gend = dest_gfq[q + 1]; + if (gend > src_end) + { + P4EST_ASSERT(q == last_receiver); + gend = src_end; + } + P4EST_ASSERT(q == first_receiver || q == last_receiver ? gbegin < gend : gbegin <= gend); + + /* choose how to treat the receiver process */ + if (gbegin == gend) + { + /* the receiver process is empty; we need no message */ + P4EST_ASSERT(first_receiver < q && q < last_receiver); + *rq++ = sc_MPI_REQUEST_NULL; + } + else + { + /* nonzero message for this receiver */ + byte_len = static_cast(gend - gbegin) * data_size; + if (q == mpirank) + { + /* on the same rank we remember pointers for memcpy */ + P4EST_ASSERT(cp_len == byte_len); + src_cp = rb; + *rq++ = sc_MPI_REQUEST_NULL; + } + else + { + /* we send a proper message */ + mpiret = + sc_MPI_Isend(rb, static_cast(byte_len), sc_MPI_BYTE, q, tag, mpicomm, rq++); + SC_CHECK_MPI(mpiret); + } + rb += byte_len; + } + } + P4EST_ASSERT(rb - (char *)src_data == (ptrdiff_t)((src_end - src_begin) * data_size)); + } + + /* copy the data that remains local */ + P4EST_ASSERT((dest_cp == NULL) == (src_cp == NULL)); + if (cp_len > 0) + { + P4EST_ASSERT(dest_cp != NULL && src_cp != NULL); + if constexpr (std::is_same::value) + { + memcpy(dest_cp, src_cp, cp_len); + } +#if defined(KOKKOS_ENABLE_CUDA) + else if constexpr (std::is_same_v) + { + cudaMemcpy(dest_cp, src_cp, cp_len, cudaMemcpyDeviceToDevice); + } +#endif +#if defined(KOKKOS_ENABLE_HIP) + else if constexpr (std::is_same_v) + { + hipMemcpy(dest_cp, src_cp, cp_len, hipMemcpyDeviceToDevice); + } +#endif + } + + /* the rest goes into the p4est_transfer_fixed_end function */ + return tc; + + } // kalypsso_transfer_fixed_begin + + // ======================================================================================== + // ======================================================================================== + static typename p4est_t::transfer_context_t * + transfer_fixed_multi_var_begin(const p4est::gloidx_t * dest_gfq, + const p4est::gloidx_t * src_gfq, + sc_MPI_Comm mpicomm, + int tag, + void * dest_data, + const void * src_data, + size_t data_size, + const uint32_t * src_offsets, + const uint32_t * dst_offsets) + { + typename p4est_t::transfer_context_t * tc; + int mpiret; + int mpisize, mpirank; + int q; + int first_sender, last_sender; + int first_receiver, last_receiver; + char * rb; + char * dest_cp, *src_cp; + size_t byte_len, cp_len; + p4est::gloidx_t dest_begin, dest_end; + p4est::gloidx_t src_begin, src_end; + p4est::gloidx_t gbegin, gend; + sc_MPI_Request * rq; + + /* setup context structure */ + tc = P4EST_ALLOC_ZERO(typename p4est_t::transfer_context_t, 1); + tc->variable = 0; + + /* there is nothing to do when there is no data */ + if (data_size == 0) + { + return tc; + } + + /* grab local partition information */ + transfer_assign_comm(dest_gfq, src_gfq, mpicomm, &mpisize, &mpirank); + dest_begin = dest_gfq[mpirank]; /* this essentially correspond to the 0 'i_oct' of dst */ + dest_end = dest_gfq[mpirank + 1]; + src_begin = src_gfq[mpirank]; /* this essentially correspond to the 0 'i_oct' of src */ + src_end = src_gfq[mpirank + 1]; + + /* prepare data copy for local overlap */ + dest_cp = src_cp = NULL; + cp_len = 0; + + /* figure out subset of processes to receive from */ + if (dest_begin < dest_end) + { + P4EST_ASSERT(dest_data != NULL); + + /* our process as the receiver is not empty */ + first_sender = p4est_bsearch_partition(dest_begin, src_gfq, mpisize); + P4EST_ASSERT(0 <= first_sender && first_sender < mpisize); + last_sender = + p4est_t::bsearch_partition(dest_end - 1, &src_gfq[first_sender], mpisize - first_sender) + + first_sender; + P4EST_ASSERT(first_sender <= last_sender && last_sender < mpisize); + tc->num_senders = last_sender - first_sender + 1; + P4EST_ASSERT(tc->num_senders > 0); + + /* go through sender processes and post receive calls */ + gend = dest_begin; + rq = tc->recv_req = P4EST_ALLOC(sc_MPI_Request, static_cast(tc->num_senders)); + rb = reinterpret_cast(dest_data); + for (q = first_sender; q <= last_sender; ++q) + { + /* prepare positions for the sender process q */ + gbegin = gend; + gend = src_gfq[q + 1]; + if (gend > dest_end) + { + P4EST_ASSERT(q == last_sender); + gend = dest_end; + } + P4EST_ASSERT(q == first_sender || q == last_sender ? gbegin < gend : gbegin <= gend); + + /* choose how to treat the sender process */ + if (gbegin == gend) + { + /* the sender process is empty; we need no message */ + P4EST_ASSERT(first_sender < q && q < last_sender); + *rq++ = sc_MPI_REQUEST_NULL; + } + else + { + /* nonzero message from this sender */ + byte_len = + static_cast(dst_offsets[gend - dest_begin] - dst_offsets[gbegin - dest_begin]) * + data_size; + if (q == mpirank) + { + /* on the same rank we remember pointers for memcpy */ + cp_len = byte_len; + dest_cp = rb; + *rq++ = sc_MPI_REQUEST_NULL; + } + else + { + /* we receive a proper message */ + mpiret = + sc_MPI_Irecv(rb, static_cast(byte_len), sc_MPI_BYTE, q, tag, mpicomm, rq++); + SC_CHECK_MPI(mpiret); + } + rb += byte_len; + } + } + P4EST_ASSERT(rb - (char *)dest_data == + (ptrdiff_t)(dst_offsets[dest_end - dest_begin] * data_size)); + } + + /* figure out subset of processes to send to */ + if (src_begin < src_end) + { + P4EST_ASSERT(src_data != NULL); + + /* our process as the sender is not empty */ + first_receiver = p4est_t::bsearch_partition(src_begin, dest_gfq, mpisize); + P4EST_ASSERT(0 <= first_receiver && first_receiver < mpisize); + last_receiver = p4est_t::bsearch_partition( + src_end - 1, &dest_gfq[first_receiver], mpisize - first_receiver) + + first_receiver; + P4EST_ASSERT(first_receiver <= last_receiver && last_receiver < mpisize); + tc->num_receivers = last_receiver - first_receiver + 1; + P4EST_ASSERT(tc->num_receivers > 0); + + /* go through receiver processes and post send calls */ + gend = src_begin; + rq = tc->send_req = P4EST_ALLOC(sc_MPI_Request, static_cast(tc->num_receivers)); + rb = const_cast(reinterpret_cast(src_data)); + for (q = first_receiver; q <= last_receiver; ++q) + { + /* prepare positions for the receiver process q */ + gbegin = gend; + gend = dest_gfq[q + 1]; + if (gend > src_end) + { + P4EST_ASSERT(q == last_receiver); + gend = src_end; + } + P4EST_ASSERT(q == first_receiver || q == last_receiver ? gbegin < gend : gbegin <= gend); + + /* choose how to treat the receiver process */ + if (gbegin == gend) + { + /* the receiver process is empty; we need no message */ + P4EST_ASSERT(first_receiver < q && q < last_receiver); + *rq++ = sc_MPI_REQUEST_NULL; + } + else + { + /* nonzero message for this receiver */ + byte_len = + static_cast(src_offsets[gend - src_begin] - src_offsets[gbegin - src_begin]) * + data_size; + if (q == mpirank) + { + /* on the same rank we remember pointers for memcpy */ + P4EST_ASSERT(cp_len == byte_len); + src_cp = rb; + *rq++ = sc_MPI_REQUEST_NULL; + } + else + { + /* we send a proper message */ + mpiret = + sc_MPI_Isend(rb, static_cast(byte_len), sc_MPI_BYTE, q, tag, mpicomm, rq++); + SC_CHECK_MPI(mpiret); + } + rb += byte_len; + } + } + P4EST_ASSERT(rb - (char *)src_data == + (ptrdiff_t)(src_offsets[src_end - src_begin] * data_size)); + } + + /* copy the data that remains local */ + P4EST_ASSERT((dest_cp == NULL) == (src_cp == NULL)); + if (cp_len > 0) + { + P4EST_ASSERT(dest_cp != NULL && src_cp != NULL); + if constexpr (std::is_same::value) + { + memcpy(dest_cp, src_cp, cp_len); + } +#if defined(KOKKOS_ENABLE_CUDA) + else if constexpr (std::is_same_v) + { + cudaMemcpy(dest_cp, src_cp, cp_len, cudaMemcpyDeviceToDevice); + } +#endif +#if defined(KOKKOS_ENABLE_HIP) + else if constexpr (std::is_same_v) + { + hipMemcpy(dest_cp, src_cp, cp_len, hipMemcpyDeviceToDevice); + } +#endif + } + + /* the rest goes into the p4est_transfer_fixed_end function */ + return tc; + + } // transfer_fixed_multi_var_begin + +}; // struct MeshPartitioner_helper + +} // namespace kalypsso diff --git a/src/kalypsso/core/MultiMatFillBlockGhostCells.h b/src/kalypsso/core/MultiMatFillBlockGhostCells.h new file mode 100644 index 0000000..1ce6e3a --- /dev/null +++ b/src/kalypsso/core/MultiMatFillBlockGhostCells.h @@ -0,0 +1,810 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file MultiMatFillBlockGhostCells.h + */ +#ifndef KALYPSSO_CORE_MULTI_MAT_FILL_BLOCK_GHOST_CELLS_H_ +#define KALYPSSO_CORE_MULTI_MAT_FILL_BLOCK_GHOST_CELLS_H_ + +#include +#include +#include +#include +#include +#include + +namespace kalypsso +{ + +// ================================================================================================ +// ================================================================================================ + +/** + * \class MultiMatFillBlockGhostCellsFunctor + * + * Does the same thing as FillBlockGhostCellsFunctor using a DataArrayBlockMultiVar and material + * presence array. + * + * TODO: Would it be interesting to make a common class with FillBlockGhostCellsFunctor? + * TODO: Precompute material number for destination in advance? + */ +template +class MultiMatFillBlockGhostCellsFunctor +{ +public: + using AmrHashmap_t = typename hashmap_base_t::map_t; + using OrchardKeys_t = typename orchard_key_base_t::view_t; + using MaterialPresenceView_t = MaterialPresenceView; + using DataArrayBlockMultiVar_t = DataArrayBlockMultiVar; + using DataArrayGhostedBlockMultiVar_t = DataArrayGhostedBlockMultiVar; + using StencilHelper_t = StencilHelper; + using CellLocation_t = CellLocation; + + static void + apply(ConfigMap const & config_map, + AmrHashmap_t amr_hashmap, + OrchardKeys_t orchard_keys, + int32_t start_octant, + int32_t end_octant, + DataArrayBlockMultiVar_t userdata_in, + MaterialPresenceView_t in_mat, + DataArrayGhostedBlockMultiVar_t userdata_out, + MaterialPresenceView_t out_mat, + int32_t num_vars_per_mat, + Kokkos::Array is_brick_periodic) + { + auto stencil_helper = StencilHelper_t(amr_hashmap, + orchard_keys, + userdata_in.block_size(), + get_brick_sizes(config_map), + is_brick_periodic); + + MultiMatFillBlockGhostCellsFunctor functor( + stencil_helper, + userdata_in, + in_mat, + userdata_out, + out_mat, + get_cell_prolongation_type(config_map), + num_vars_per_mat); + + const auto num_cells = Kokkos::dim_prod(userdata_out.shape()); + Kokkos::RangePolicy policy(start_octant * num_cells, + end_octant * num_cells); + + // for AMR tree leaf, explore the neighbor block + Kokkos::parallel_for("kalypsso::MultiMatFillBlockGhostCellsFunctor", policy, functor); + } + + KOKKOS_INLINE_FUNCTION void + operator()(const int32_t global_index) const + { + const auto nbCellsPerGhostedLeaf = m_userdata_out.num_cells(); + + const auto iOct_global = global_index / nbCellsPerGhostedLeaf; + const auto cell_index = global_index - iOct_global * nbCellsPerGhostedLeaf; + + const auto coord_out = + cellindex_to_coord(cell_index, m_userdata_out.shape(), m_userdata_out.shift()); + + fill_ghosts(cell_index, coord_out, iOct_global); + } + +private: + // ============================================================================================== + // ============================================================================================== + // INNER FUNCTIONS ADAPTED FROM FillBlockGhostCellsFunctor + // ============================================================================================== + // ============================================================================================== + template = true> + KOKKOS_INLINE_FUNCTION void + linear_extrapolate_using_limited_slopes(CellLocation<2> const & cell_loc_neigh, + coord_t<2> const & coord_in, + int32_t const & cellindex_out, + int32_t const & iOct_global) const + { + const real_t slope_type = 1; // TODO : investigate if a better value should be searched for + + const auto cell_loc_left_x = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(-XDIR)); + const auto cell_loc_right_x = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(+XDIR)); + + const auto cell_loc_left_y = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(-YDIR)); + const auto cell_loc_right_y = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(+YDIR)); + + // determine local position of current cell inside virtual parent cell using integer coordinates + // in -1, +1 + const int ix = 2 * (coord_in[IX] - 2 * (coord_in[IX] / 2)) - 1; + const int iy = 2 * (coord_in[IY] - 2 * (coord_in[IY] / 2)) - 1; + + const auto nbmat = m_out_mat.num_materials(iOct_global); + + for (int32_t imat = 0; imat < nbmat; ++imat) + { + // Extrapolation needs variable at each face so we check if it is present across each face. + const auto mat_num = m_out_mat.material_num(iOct_global, imat); + const bool has_mat = m_in_mat.get(static_cast(cell_loc_left_x.iOct), mat_num) && + m_in_mat.get(static_cast(cell_loc_right_x.iOct), mat_num) && + m_in_mat.get(static_cast(cell_loc_left_y.iOct), mat_num) && + m_in_mat.get(static_cast(cell_loc_right_y.iOct), mat_num) && + m_in_mat.get(static_cast(cell_loc_neigh.iOct), mat_num); + + if (has_mat) // Is the material present in the source octant? + for (int32_t ivar = 0; ivar < m_num_vars_per_mat; ++ivar) + { + const auto in_ivar_neigh = + ivar + m_num_vars_per_mat * + m_in_mat.material_index(static_cast(cell_loc_neigh.iOct), mat_num); + + const auto in_ivar_left_x = + ivar + m_num_vars_per_mat * + m_in_mat.material_index(static_cast(cell_loc_left_x.iOct), mat_num); + + const auto in_ivar_right_x = + ivar + m_num_vars_per_mat * + m_in_mat.material_index(static_cast(cell_loc_right_x.iOct), mat_num); + + const auto in_ivar_left_y = + ivar + m_num_vars_per_mat * + m_in_mat.material_index(static_cast(cell_loc_left_y.iOct), mat_num); + + const auto in_ivar_right_y = + ivar + m_num_vars_per_mat * + m_in_mat.material_index(static_cast(cell_loc_right_y.iOct), mat_num); + + const auto out_ivar = ivar + m_num_vars_per_mat * imat; + + // compute limited slopes + auto const dudx = m_stencil_helper.compute_minmod_slopes(cell_loc_neigh, + in_ivar_neigh, + cell_loc_right_x, + in_ivar_right_x, + cell_loc_left_x, + in_ivar_left_x, + m_userdata_in, + slope_type); + + auto const dudy = m_stencil_helper.compute_minmod_slopes(cell_loc_neigh, + in_ivar_neigh, + cell_loc_right_y, + in_ivar_right_y, + cell_loc_left_y, + in_ivar_left_y, + m_userdata_in, + slope_type); + + // extrapolate + m_userdata_out(cellindex_out, out_ivar, iOct_global) = + m_userdata_in( + cell_loc_neigh.cellindex(m_block_sizes), in_ivar_neigh, cell_loc_neigh.iOct) + + KALYPSSO_NUM(0.25) * static_cast(ix) * dudx + + KALYPSSO_NUM(0.25) * static_cast(iy) * dudy; + } + else // If not, set the value to 0 (maybe best to switch extrapolation method?) + for (int32_t ivar = 0; ivar < m_num_vars_per_mat; ++ivar) + { + const auto out_ivar = ivar + m_num_vars_per_mat * imat; + m_userdata_out(cellindex_out, out_ivar, iOct_global) = 0; + } + } + } + + template = true> + KOKKOS_INLINE_FUNCTION void + linear_extrapolate_using_limited_slopes(CellLocation<3> const & cell_loc_neigh, + coord_t<3> const & coord_in, + int32_t const & cellindex_out, + int32_t const & iOct_global) const + { + const real_t slope_type = 1; // TODO : investigate if a better value should be searched for + + const auto cell_loc_left_x = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(-XDIR)); + const auto cell_loc_right_x = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(+XDIR)); + + const auto cell_loc_left_y = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(-YDIR)); + const auto cell_loc_right_y = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(+YDIR)); + + const auto cell_loc_left_z = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(-ZDIR)); + const auto cell_loc_right_z = + m_stencil_helper.getNeighLoc(cell_loc_neigh, m_stencil_helper.unit_shift(+ZDIR)); + + // determine local position of current cell inside virtual parent cell using integer coordinates + // in -1, +1 + const int ix = 2 * (coord_in[IX] - 2 * (coord_in[IX] / 2)) - 1; + const int iy = 2 * (coord_in[IY] - 2 * (coord_in[IY] / 2)) - 1; + const int iz = 2 * (coord_in[IZ] - 2 * (coord_in[IZ] / 2)) - 1; + + const auto nbmat = m_out_mat.num_materials(iOct_global); + + for (int32_t imat = 0; imat < nbmat; ++imat) + { + // Extrapolation needs variable at each face so we check if it is present across each face. + const auto mat_num = m_out_mat.material_num(iOct_global, imat); + const bool has_mat = m_in_mat.get(static_cast(cell_loc_left_x.iOct), mat_num) && + m_in_mat.get(static_cast(cell_loc_right_x.iOct), mat_num) && + m_in_mat.get(static_cast(cell_loc_left_y.iOct), mat_num) && + m_in_mat.get(static_cast(cell_loc_right_y.iOct), mat_num) && + m_in_mat.get(static_cast(cell_loc_left_z.iOct), mat_num) && + m_in_mat.get(static_cast(cell_loc_right_z.iOct), mat_num) && + m_in_mat.get(static_cast(cell_loc_neigh.iOct), mat_num); + + if (has_mat) // Is the material present in the source octant? + for (int32_t ivar = 0; ivar < m_num_vars_per_mat; ++ivar) + { + const auto in_ivar_neigh = + ivar + m_num_vars_per_mat * + m_in_mat.material_index(static_cast(cell_loc_neigh.iOct), mat_num); + + const auto in_ivar_left_x = + ivar + m_num_vars_per_mat * + m_in_mat.material_index(static_cast(cell_loc_left_x.iOct), mat_num); + + const auto in_ivar_right_x = + ivar + m_num_vars_per_mat * + m_in_mat.material_index(static_cast(cell_loc_right_x.iOct), mat_num); + + const auto in_ivar_left_y = + ivar + m_num_vars_per_mat * + m_in_mat.material_index(static_cast(cell_loc_left_y.iOct), mat_num); + + const auto in_ivar_right_y = + ivar + m_num_vars_per_mat * + m_in_mat.material_index(static_cast(cell_loc_right_y.iOct), mat_num); + + const auto in_ivar_left_z = + ivar + m_num_vars_per_mat * + m_in_mat.material_index(static_cast(cell_loc_left_z.iOct), mat_num); + + const auto in_ivar_right_z = + ivar + m_num_vars_per_mat * + m_in_mat.material_index(static_cast(cell_loc_right_z.iOct), mat_num); + + const auto out_ivar = ivar + m_num_vars_per_mat * imat; + + // compute limited slopes + auto const dudx = m_stencil_helper.compute_minmod_slopes(cell_loc_neigh, + in_ivar_neigh, + cell_loc_right_x, + in_ivar_right_x, + cell_loc_left_x, + in_ivar_left_x, + m_userdata_in, + slope_type); + + auto const dudy = m_stencil_helper.compute_minmod_slopes(cell_loc_neigh, + in_ivar_neigh, + cell_loc_right_y, + in_ivar_right_y, + cell_loc_left_y, + in_ivar_left_y, + m_userdata_in, + slope_type); + + auto const dudz = m_stencil_helper.compute_minmod_slopes(cell_loc_neigh, + in_ivar_neigh, + cell_loc_right_z, + in_ivar_right_z, + cell_loc_left_z, + in_ivar_left_z, + m_userdata_in, + slope_type); + + // extrapolate + m_userdata_out(cellindex_out, out_ivar, iOct_global) = + m_userdata_in( + cell_loc_neigh.cellindex(m_block_sizes), in_ivar_neigh, cell_loc_neigh.iOct) + + KALYPSSO_NUM(0.25) * static_cast(ix) * dudx + + KALYPSSO_NUM(0.25) * static_cast(iy) * dudy + + KALYPSSO_NUM(0.25) * static_cast(iz) * dudz; + } + else // If not, set the value to 0 (maybe best to switch extrapolation method?) + for (int32_t ivar = 0; ivar < m_num_vars_per_mat; ++ivar) + { + const auto out_ivar = ivar + m_num_vars_per_mat * imat; + m_userdata_out(cellindex_out, out_ivar, iOct_global) = 0; + } + } + } + + KOKKOS_INLINE_FUNCTION + void + fill_inner(int32_t cellindex_in, int32_t cellindex_out, int32_t iOct_global) const + { + const auto nbmat = m_out_mat.num_materials(iOct_global); + + for (int32_t imat = 0; imat < nbmat; ++imat) + { + const auto mat_num = m_out_mat.material_num(iOct_global, imat); + + if (m_in_mat.get(iOct_global, mat_num)) // Is the material present in the source octant? + for (int32_t ivar = 0; ivar < m_num_vars_per_mat; ++ivar) + { + const auto in_ivar = + ivar + m_num_vars_per_mat * m_in_mat.material_index(iOct_global, mat_num); + const auto out_ivar = ivar + m_num_vars_per_mat * imat; + + m_userdata_out(cellindex_out, out_ivar, iOct_global) = + m_userdata_in(cellindex_in, in_ivar, iOct_global); + } + else + for (int32_t ivar = 0; ivar < m_num_vars_per_mat; ++ivar) + { + const auto out_ivar = ivar + m_num_vars_per_mat * imat; + m_userdata_out(cellindex_out, out_ivar, iOct_global) = 0; + } + } + } + + KOKKOS_INLINE_FUNCTION void + fill_ghosts(int32_t const & cellindex_out, + coord_t const & coord_out, + int32_t const & iOct_global) const + { + + const auto & b = m_block_sizes; + + coord_t coord_in; + const auto dir = ghosted_coords_to_inner_coords(coord_in, coord_out, b); + + int32_t cellindex_in = coord_to_cellindex(coord_in, m_block_sizes); + + auto dir_norm = dir[IX] * dir[IX] + dir[IY] * dir[IY]; + if constexpr (dim == 3) + dir_norm += dir[IZ] * dir[IZ]; + + if (dir_norm == 0) + { + // current cell is inside current block + fill_inner(cellindex_in, cellindex_out, iOct_global); + } + else + { + // current cell is a ghost cell (thus belonging to a neighbor block) + + /* + * fill ghosts all around + */ + + // get orchard key of current octant + auto key_cur = m_stencil_helper.key(iOct_global); + + shift_t shift; + shift[IX] = b[IX] * dir[IX]; + shift[IY] = b[IY] * dir[IY]; + if constexpr (dim == 3) + { + shift[IZ] = b[IZ] * dir[IZ]; + } + + const CellLocation_t cell_loc_cur{ coord_in, key_cur, iOct_global, false }; + const auto cell_loc_neigh = m_stencil_helper.getNeighLoc(cell_loc_cur, shift); + + /* + * Dealing with the 3 possibilities: + * - neighbor octant is at same AMR level : doing a simple copy + * - neighbor octant is at finer AMR level : doing a restriction (average values) + * - neighbor octant is at coarser AMR level : doing a prolongation + */ + const auto iOct_in = static_cast(cell_loc_neigh.iOct); + const auto nbmat = m_out_mat.num_materials(iOct_global); + + if (cell_loc_neigh.level() == cell_loc_cur.level()) + { + // doing a simple copy + cellindex_in = cell_loc_neigh.cellindex(m_block_sizes); + + for (int32_t imat = 0; imat < nbmat; ++imat) + { + const auto mat_num = m_out_mat.material_num(iOct_global, imat); + + if (m_in_mat.get(iOct_in, mat_num)) // Is the material present in the source octant? + for (int32_t ivar = 0; ivar < m_num_vars_per_mat; ++ivar) + { + const auto in_ivar = + ivar + m_num_vars_per_mat * m_in_mat.material_index(iOct_in, mat_num); + const auto out_ivar = ivar + m_num_vars_per_mat * imat; + + m_userdata_out(cellindex_out, out_ivar, iOct_global) = + m_userdata_in(cellindex_in, in_ivar, iOct_in); + } + else + for (int32_t ivar = 0; ivar < m_num_vars_per_mat; ++ivar) + { + const auto out_ivar = ivar + m_num_vars_per_mat * imat; + m_userdata_out(cellindex_out, out_ivar, iOct_global) = 0; + } + } + } + else if (cell_loc_neigh.level() + 1 == cell_loc_cur.level()) + { + // doing a prolongation because neighbor is coarser + + if (m_prolongation == +CellCenteredProlongationType::SIMPLE_COPY) + { + // simple copy of the coarse value + + cellindex_in = cell_loc_neigh.cellindex(m_block_sizes); + + for (int32_t imat = 0; imat < nbmat; ++imat) + { + const auto mat_num = m_out_mat.material_num(iOct_global, imat); + + if (m_in_mat.get(iOct_in, mat_num)) // Is the material present in the source octant? + for (int32_t ivar = 0; ivar < m_num_vars_per_mat; ++ivar) + { + const auto in_ivar = + ivar + m_num_vars_per_mat * m_in_mat.material_index(iOct_in, mat_num); + const auto out_ivar = ivar + m_num_vars_per_mat * imat; + + m_userdata_out(cellindex_out, out_ivar, iOct_global) = + m_userdata_in(cellindex_in, in_ivar, iOct_in); + } + else + for (int32_t ivar = 0; ivar < m_num_vars_per_mat; ++ivar) + { + const auto out_ivar = ivar + m_num_vars_per_mat * imat; + m_userdata_out(cellindex_out, out_ivar, iOct_global) = 0; + } + } + } + else if (m_prolongation == +CellCenteredProlongationType::EXTRAPOLATE_LINEAR_MINMOD) + { + linear_extrapolate_using_limited_slopes( + cell_loc_neigh, coord_in, cellindex_out, iOct_global); + } + } + else if (cell_loc_neigh.level() == cell_loc_cur.level() + 1) + { + for (int32_t imat = 0; imat < nbmat; ++imat) + { + const auto mat_num = m_out_mat.material_num(iOct_global, imat); + + if (m_in_mat.get(iOct_in, mat_num)) // Is the material present in the source octant? + for (int32_t ivar = 0; ivar < m_num_vars_per_mat; ++ivar) + { + const auto out_ivar = ivar + m_num_vars_per_mat * imat; + const auto in_ivar = + ivar + m_num_vars_per_mat * m_in_mat.material_index(iOct_in, mat_num); + + m_userdata_out(cellindex_out, out_ivar, iOct_global) = + m_stencil_helper.compute_siblings_average( + cell_loc_neigh, m_block_sizes, in_ivar, m_userdata_in); + } + else + for (int32_t ivar = 0; ivar < m_num_vars_per_mat; ++ivar) + { + const auto out_ivar = ivar + m_num_vars_per_mat * imat; + m_userdata_out(cellindex_out, out_ivar, iOct_global) = 0; + } + } + } + else + { + KOKKOS_ASSERT(false && "Logic error: neighbor octant not found (Kernel Panic !)"); + } + + } // end if (dir_norm == 0) + } + + // ============================================================================================== + // ============================================================================================== + // ============================================================================================== + // ============================================================================================== + // ============================================================================================== + + MultiMatFillBlockGhostCellsFunctor(StencilHelper_t stencil_helper, + DataArrayBlockMultiVar_t userdata_in, + MaterialPresenceView_t in_mat, + DataArrayGhostedBlockMultiVar_t userdata_out, + MaterialPresenceView_t out_mat, + CellCenteredProlongationType prolongation, + int32_t num_vars_per_mat) + : m_stencil_helper(stencil_helper) + , m_in_mat(in_mat) + , m_out_mat(out_mat) + , m_userdata_in(userdata_in) + , m_userdata_out(userdata_out) + , m_block_sizes(userdata_in.shape()) + , m_num_vars_per_mat(num_vars_per_mat) + , m_prolongation(prolongation) + {} + + //! helper to compute neighbor cell location + StencilHelper_t m_stencil_helper; + + //! Source material presence + MaterialPresenceView_t m_in_mat; + + //! Destination material presence + MaterialPresenceView_t m_out_mat; + + //! a block data array (no ghosts) + DataArrayBlockMultiVar_t m_userdata_in; + + //! a block data array (ghosts) + DataArrayGhostedBlockMultiVar_t m_userdata_out; + + //! block sizes + const block_size_t m_block_sizes; + + //! num vars per mat + const int32_t m_num_vars_per_mat; + + //! prolongation type + const CellCenteredProlongationType m_prolongation; +}; + +// ============================================================================================== +// ============================================================================================== +// ============================================================================================== +// ============================================================================================== +// ============================================================================================== + +// ============================================================================================== +// ============================================================================================== +// ============================================================================================== +// ============================================================================================== +// ============================================================================================== + +/** + * \class MultiMatFillBlockGhostCellsMatPresence + * + * Generates the material presence array of the destination. Used to reorganize the destination + * matrix + */ +template +class MultiMatFillBlockGhostCellsMatPresence +{ +public: + using AmrHashmap_t = typename hashmap_base_t::map_t; + using OrchardKeys_t = typename orchard_key_base_t::view_t; + using MaterialPresenceView_t = MaterialPresenceView; + + static void + apply(const MaterialPresenceView_t src_mat, + const AmrHashmap_t hashmap, + const OrchardKeys_t keys, + const brick_size_t brick_size, + const Kokkos::Array brick_periodicity, + const int32_t start_octant, + const int32_t end_octant, + MaterialPresenceView_t dst_mat) + { + MultiMatFillBlockGhostCellsMatPresence functor( + src_mat, hashmap, keys, brick_size, brick_periodicity, dst_mat); + Kokkos::RangePolicy policy(start_octant, end_octant); + Kokkos::parallel_for("kalypsso::MultiMatFillBlockGhostCellsMatPresence", policy, functor); + } + + KOKKOS_FUNCTION void + operator()(const int32_t i_oct) const + { + const auto key = m_keys(i_oct); + MaterialPresenceView_t::copy(m_dst_mat, i_oct, m_src_mat, i_oct); + + // Loop over faces + for (uint8_t face = 0; face < Face::num_faces(); face++) + get_mat_over_face(key, face, i_oct); + + // Loop over edges (if and only if dim == 3) + if constexpr (dim == 3) + for (uint8_t edge = 0; edge < Edge::num_edges(); edge++) + get_mat_over_edge(key, edge, i_oct); + + // Loop over corners + for (uint8_t corner = 0; corner < Corner::num_corners(); corner++) + get_mat_over_corner(key, corner, i_oct); + } + +private: + MultiMatFillBlockGhostCellsMatPresence(const MaterialPresenceView_t src_mat, + const AmrHashmap_t hashmap, + const OrchardKeys_t keys, + const brick_size_t brick_size, + const Kokkos::Array brick_periodicity, + const MaterialPresenceView_t dst_mat) + : m_src_mat(src_mat) + , m_dst_mat(dst_mat) + , m_hashmap(hashmap) + , m_keys(keys) + , m_brick_size(brick_size) + , m_brick_periodicity(brick_periodicity) + {} + + //! Updates the material presence with the ones over the faces + KOKKOS_FUNCTION void + get_mat_over_face(const key_t key, const uint8_t face, const int32_t i_dst_oct) const + { + int32_t i_src_oct; + + coord_t dir{}; + dir[face >> 1] = (face & 1) ? 1 : -1; + + const auto neighbor_key = + orchard_key_t::get_neighbor_key_same_level(key, dir, m_brick_size, m_brick_periodicity); + auto neighbor_key_hash = m_hashmap.find(neighbor_key); + auto is_key_valid = m_hashmap.valid_at(neighbor_key_hash); + + // Neighbor is at same level + if (is_key_valid) + { + i_src_oct = static_cast(m_hashmap.value_at(neighbor_key_hash)); + return MaterialPresenceView_t::update(m_dst_mat, i_dst_oct, m_src_mat, i_src_oct); + } + + const auto neighbor_key_coarser = orchard_key_t::father(neighbor_key); + neighbor_key_hash = m_hashmap.find(neighbor_key_coarser); + is_key_valid = m_hashmap.valid_at(neighbor_key_hash); + + // Neighbor is at coarser level + if (is_key_valid) + { + i_src_oct = static_cast(m_hashmap.value_at(neighbor_key_hash)); + return MaterialPresenceView_t::update(m_dst_mat, i_dst_oct, m_src_mat, i_src_oct); + } + + const auto neighbor_keys_finer = compute_face_neighbor_key_finer(neighbor_key, face); + neighbor_key_hash = m_hashmap.find(neighbor_keys_finer[0]); + is_key_valid = m_hashmap.valid_at(neighbor_key_hash); + + // Neighbor is at finer level + if (is_key_valid) + { + i_src_oct = static_cast(m_hashmap.value_at(neighbor_key_hash)); + MaterialPresenceView_t::update(m_dst_mat, i_dst_oct, m_src_mat, i_src_oct); + for (uint8_t i = 1; i < neighbor_keys_finer.size(); i++) + { + i_src_oct = + static_cast(m_hashmap.value_at(m_hashmap.find(neighbor_keys_finer[i]))); + MaterialPresenceView_t::update(m_dst_mat, i_dst_oct, m_src_mat, i_src_oct); + } + } + } + + //! Updates the material presence with the ones over the edges (dim 3 only) + template = true> + KOKKOS_FUNCTION void + get_mat_over_edge(const key_t key, const uint8_t edge, const int32_t i_dst_oct) const + { + coord_t<3, int8_t> dir{}; + + // Taken from 'edge_to_faces' + if (edge < 4) // edge along Z + { + dir[IX] = edge & 0x1 ? 1 : -1; + dir[IY] = edge & 0x2 ? 1 : -1; + } + else if (edge < 8) // edge along X + { + dir[IY] = edge & 0x1 ? 1 : -1; + dir[IZ] = edge & 0x2 ? 1 : -1; + } + else // edge along Y (not a circular permutation - respect Morton order) + { + dir[IX] = edge & 0x1 ? 1 : -1; + dir[IZ] = edge & 0x2 ? 1 : -1; + } + + const auto neighbor_key = + orchard_key_t<3>::get_neighbor_key_same_level(key, dir, m_brick_size, m_brick_periodicity); + auto neighbor_key_hash = m_hashmap.find(neighbor_key); + auto is_key_valid = m_hashmap.valid_at(neighbor_key_hash); + + int32_t i_src_oct; + + // Neighbor is at same level + if (is_key_valid) + { + i_src_oct = static_cast(m_hashmap.value_at(neighbor_key_hash)); + return MaterialPresenceView_t::update(m_dst_mat, i_dst_oct, m_src_mat, i_src_oct); + } + + const auto neighbor_key_coarser = orchard_key_t<3>::father(neighbor_key); + neighbor_key_hash = m_hashmap.find(neighbor_key_coarser); + is_key_valid = m_hashmap.valid_at(neighbor_key_hash); + + // Neighbor is at coarser level + if (is_key_valid) + { + i_src_oct = static_cast(m_hashmap.value_at(neighbor_key_hash)); + return MaterialPresenceView_t::update(m_dst_mat, i_dst_oct, m_src_mat, i_src_oct); + } + + const auto neighbor_keys_finer = compute_edge_neighbor_finer_key(neighbor_key, edge); + neighbor_key_hash = m_hashmap.find(neighbor_keys_finer[0]); + is_key_valid = m_hashmap.valid_at(neighbor_key_hash); + + // Neighbor is at finer level + if (is_key_valid) + { + i_src_oct = static_cast(m_hashmap.value_at(neighbor_key_hash)); + MaterialPresenceView_t::update(m_dst_mat, i_dst_oct, m_src_mat, i_src_oct); + for (uint8_t i = 1; i < neighbor_keys_finer.size(); i++) + { + i_src_oct = + static_cast(m_hashmap.value_at(m_hashmap.find(neighbor_keys_finer[i]))); + MaterialPresenceView_t::update(m_dst_mat, i_dst_oct, m_src_mat, i_src_oct); + } + } + } + + //! Updates the material presence with the ones over the corners + KOKKOS_FUNCTION void + get_mat_over_corner(const key_t key, const uint8_t corner, const int32_t i_dst_oct) const + { + coord_t dir{}; + dir[IX] = (corner >> 0 & 1) ? 1 : -1; + dir[IY] = (corner >> 1 & 1) ? 1 : -1; + if constexpr (dim == 3) + dir[IZ] = (corner >> 2 & 1) ? 1 : -1; + + const auto neighbor_key = + orchard_key_t::get_neighbor_key_same_level(key, dir, m_brick_size, m_brick_periodicity); + auto neighbor_key_hash = m_hashmap.find(neighbor_key); + auto is_key_valid = m_hashmap.valid_at(neighbor_key_hash); + + int32_t i_src_oct; + + // Neighbor is at same level + if (is_key_valid) + { + i_src_oct = static_cast(m_hashmap.value_at(neighbor_key_hash)); + return MaterialPresenceView_t::update(m_dst_mat, i_dst_oct, m_src_mat, i_src_oct); + } + + const auto neighbor_key_coarser = orchard_key_t::father(neighbor_key); + neighbor_key_hash = m_hashmap.find(neighbor_key_coarser); + is_key_valid = m_hashmap.valid_at(neighbor_key_hash); + + // Neighbor is at coarser level + if (is_key_valid) + { + i_src_oct = static_cast(m_hashmap.value_at(neighbor_key_hash)); + return MaterialPresenceView_t::update(m_dst_mat, i_dst_oct, m_src_mat, i_src_oct); + } + + const auto neighbor_key_finer = + compute_corner_neighbor_finer_key(neighbor_key, corner_to_faces(corner)); + neighbor_key_hash = m_hashmap.find(neighbor_key_finer); + is_key_valid = m_hashmap.valid_at(neighbor_key_hash); + + // Neighbor is at finer level + if (is_key_valid) + { + i_src_oct = static_cast(m_hashmap.value_at(neighbor_key_hash)); + return MaterialPresenceView_t::update(m_dst_mat, i_dst_oct, m_src_mat, i_src_oct); + } + } + + //! Source material presence + MaterialPresenceView_t m_src_mat; + + //! Destination material presence + MaterialPresenceView_t m_dst_mat; + + //! keys to index hashmap + AmrHashmap_t m_hashmap; + + //! index to keys array + OrchardKeys_t m_keys; + + //! Brick size + brick_size_t m_brick_size; + + //! Brick periodicity + Kokkos::Array m_brick_periodicity; +}; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_MULTI_MAT_FILL_BLOCK_GHOST_CELLS_H_ diff --git a/src/kalypsso/core/OutsideQuadsInfo.h b/src/kalypsso/core/OutsideQuadsInfo.h new file mode 100644 index 0000000..1b984e4 --- /dev/null +++ b/src/kalypsso/core/OutsideQuadsInfo.h @@ -0,0 +1,461 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file OutsideQuadsInfo.h + * \brief A small utility class holding geometric information about quadrants that are outside + * domain. + * + * Just remember that if p4est brick connectivity is not periodic, we provide quads that are outside + * domain to ease border condition implementation. We should also be able to dump these specific + * quadrants in the hdf5 output files. Here we pre-compute MPI decomposition of these outside + * quadrants. + */ +#ifndef KALYPSSO_CORE_OUTSIDEQUADSINFO_H_ +#define KALYPSSO_CORE_OUTSIDEQUADSINFO_H_ + +#include // which include kalypsso_core_config.h + +#include +#include +#include // for CONNECTIVITY_PERIODIC_FALSE +#include +#include +#include +#include +#include +#include + +#include + +namespace kalypsso +{ + +// ========================================================================================== +// ========================================================================================== +//! \brief recompute the local number of outside quads. +//! +//! Let us remind ourselves that out side quads are additional quadrants outside physical +//! domain used to implement border condition other than periodic (which is taken into account +//! natively by p4est). +//! We need to add an outside quadrant for all border than is not periodic. +//! +//! +//! \note this free standing function is needed here, as well as in MeshMap. +template +int32_t +compute_number_outside_quads(/*const*/ forest_t * forest, + [[maybe_unused]] const ParallelEnv & par_env, + Kokkos::Array is_brick_periodic, + brick_size_t brick_sizes) +{ + + // type alias to access p4est C API (2D or 3D) + using p4est_t = typename p4est::Wrapper; + + // do we have at least one non-periodic border ? + bool has_non_periodic_border = false; + for (size_t idim = 0; idim < dim; ++idim) + { + if (!is_brick_periodic[idim]) + has_non_periodic_border = true; + } + + // if all border are periodic, ok no outside quads + if (!has_non_periodic_border) + return 0; + + // tree linear index to xyz converter + const BrickConnectivityData convert(brick_sizes); + + // init returned value + int32_t number_outside_quads = 0; + + // get list of faces + const auto faces = Face::get_all_faces(); + + // loop over all owned (inside) quadrants, and compute how many are touching external border + // through a non-periodic border + for (auto treeid = forest->first_local_tree; treeid <= forest->last_local_tree; ++treeid) + { + // get current tree + auto tree = p4est_t::tree_array_index(forest->trees, treeid); + + // get tree coordinate + auto tree_xyz = convert.toXYZ(treeid); + + for (size_t qId = 0; qId < tree->quadrants.elem_count; ++qId) + { + auto q = p4est_t::quadrant_array_index(&(tree->quadrants), qId); + + Kokkos::Array octCoord; + octCoord[0] = + static_cast(q->x >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + octCoord[1] = + static_cast(q->y >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + if constexpr (dim == 3) + { + octCoord[2] = + static_cast(q->z >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + } + + auto key = + orchard_key_t::encode_orchard(tree_xyz, octCoord, static_cast(q->level)); + + // look for outside quad across a face + for (const auto face : faces) + { + if (orchard_key_t::is_at_domain_border(key, face, brick_sizes)) + { + if (!is_brick_periodic[face / 2]) + number_outside_quads++; + } + } // end for faces + + // look for outside quad across an edge + if constexpr (dim == 3) + { + Face::face_t face0, face1; + for (uint8_t iEdge = 0; iEdge < Edge::num_edges(); ++iEdge) + { + edge_to_faces(iEdge, face0, face1); + if (orchard_key_t::is_at_domain_border(key, face0, brick_sizes) and + orchard_key_t::is_at_domain_border(key, face1, brick_sizes)) + { + if (!is_brick_periodic[face0 / 2] or !is_brick_periodic[face1 / 2]) + number_outside_quads++; + } + } + } // end edge - 3d + + // look for outside quad across a corner + for (uint8_t iCorner = 0; iCorner < Corner::num_corners(); ++iCorner) + { + if constexpr (dim == 2) + { + Face::face_t face0, face1; + corner_to_faces(iCorner, face0, face1); + if (orchard_key_t::is_at_domain_border(key, face0, brick_sizes) and + orchard_key_t::is_at_domain_border(key, face1, brick_sizes)) + { + if (!is_brick_periodic[face0 / 2] or !is_brick_periodic[face1 / 2]) + number_outside_quads++; + } + } + else if constexpr (dim == 3) + { + Face::face_t face0, face1, face2; + corner_to_faces(iCorner, face0, face1, face2); + if (orchard_key_t::is_at_domain_border(key, face0, brick_sizes) and + orchard_key_t::is_at_domain_border(key, face1, brick_sizes) and + orchard_key_t::is_at_domain_border(key, face2, brick_sizes)) + { + // clang-format off + if (!is_brick_periodic[face0 / 2] or + !is_brick_periodic[face1 / 2] or + !is_brick_periodic[face2 / 2]) + number_outside_quads++; + // clang-format on + } + } + } // end for iCorner + + } // end for qId + + } // end for treeid + + return number_outside_quads; + +} // compute_number_outside_quads + +// ========================================================================================== +// ========================================================================================== +//! recompute the local number of outside quads of ghosts. +//! this free standing function is needed here, as well as in MeshMap. +template +int32_t +compute_number_outside_ghosts(/*const*/ ghost_t * ghost, + [[maybe_unused]] const ParallelEnv & par_env, + Kokkos::Array is_brick_periodic, + brick_size_t brick_sizes) +{ + // type alias to access p4est C API (2D or 3D) + using p4est_t = typename p4est::Wrapper; + + if (ghost == nullptr) + return 0; + + // do we have at least one non-periodic border ? + bool has_non_periodic_border = false; + for (size_t idim = 0; idim < dim; ++idim) + { + if (!is_brick_periodic[idim]) + has_non_periodic_border = true; + } + + // if all border are periodic, ok no quad outside (owned or ghost anyway) + if (!has_non_periodic_border) + return 0; + + // tree linear index to xyz converter + const BrickConnectivityData convert(brick_sizes); + + // init returned value + int32_t number_outside_ghosts = 0; + + // get list of faces + const auto faces = Face::get_all_faces(); + + // + // complete array of orchard keys using (MPI) ghost quadrants + // + for (size_t qId = 0; qId < ghost->ghosts.elem_count; ++qId) + { + auto q = p4est_t::quadrant_array_index(&(ghost->ghosts), qId); + + // get current ghost's tree id + auto treeid = q->p.which_tree; + + // get tree coordinate + auto tree_xyz = convert.toXYZ(treeid); + + Kokkos::Array octCoord; + octCoord[0] = + static_cast(q->x >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + octCoord[1] = + static_cast(q->y >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + if constexpr (dim == 3) + { + octCoord[2] = + static_cast(q->z >> (p4est_t::QMAXLEVEL - orchard_key_t::NUM_LEVELS + 2)); + } + + auto key = + orchard_key_t::encode_orchard(tree_xyz, octCoord, static_cast(q->level)); + + for (const auto face : faces) + { + if (orchard_key_t::is_at_domain_border(key, face, brick_sizes)) + { + if (!is_brick_periodic[face / 2]) + { + number_outside_ghosts++; + // if (par_env.rank() == 1) + // { + // const auto xyz = orchard_key_to_vertex_coord(key, false); + + // printf("[rank %d] KK %d %lu | %f %f\n", + // par_env.rank(), + // number_outside_ghosts, + // key, + // xyz[0], + // xyz[1]); + // } + } + } + } // end for faces + + // look for outside quad across an edge + if constexpr (dim == 3) + { + Face::face_t face0, face1; + for (uint8_t iEdge = 0; iEdge < Edge::num_edges(); ++iEdge) + { + edge_to_faces(iEdge, face0, face1); + if (orchard_key_t::is_at_domain_border(key, face0, brick_sizes) and + orchard_key_t::is_at_domain_border(key, face1, brick_sizes)) + { + if (!is_brick_periodic[face0 / 2] or !is_brick_periodic[face1 / 2]) + { + number_outside_ghosts++; + } + } + } + } // end edge - 3d + + // look for outside quad across a corner + for (uint8_t iCorner = 0; iCorner < Corner::num_corners(); ++iCorner) + { + if constexpr (dim == 2) + { + Face::face_t face0, face1; + corner_to_faces(iCorner, face0, face1); + if (orchard_key_t::is_at_domain_border(key, face0, brick_sizes) and + orchard_key_t::is_at_domain_border(key, face1, brick_sizes)) + { + if (!is_brick_periodic[face0 / 2] or !is_brick_periodic[face1 / 2]) + { + number_outside_ghosts++; + } + } + } + else if constexpr (dim == 3) + { + Face::face_t face0, face1, face2; + corner_to_faces(iCorner, face0, face1, face2); + if (orchard_key_t::is_at_domain_border(key, face0, brick_sizes) and + orchard_key_t::is_at_domain_border(key, face1, brick_sizes) and + orchard_key_t::is_at_domain_border(key, face2, brick_sizes)) + { + if (!is_brick_periodic[face0 / 2] or !is_brick_periodic[face1 / 2] or + !is_brick_periodic[face2 / 2]) + { + number_outside_ghosts++; + } + } + } + } // end for iCorner + + } // end for qId + + return number_outside_ghosts; + +} // compute_number_outside_ghosts + +// ========================================================================================== +// ========================================================================================== +// ========================================================================================== +/** + * Simple class to pass the number of outside quads to HDF5_Writer. + */ +struct OutsideQuadsInfo +{ + + //! number of outside quadrants for current MPI process + int32_t local_num_outside_quads; + + //! total number of outside quadrants for all MPI processes + int32_t global_num_outside_quads; + + //! global id of the first outside quadrant of current process. + //! this is only useful when dumping outside quadrants in a file (parallel HDF5) + int32_t first_outside_quad_global_id; + + //! local num of MPI ghost quadrants. + //! this is useful because outside quadrants comes after MPI ghosts quadrant, + //! so we need this number to compute the right offset to access data in UserData arrays. + int32_t local_num_ghost_quads; + + //! local number of outside quadrants that are associated to a ghost quadrants + int32_t local_num_outside_ghosts; + + // ========================================================================= + // ========================================================================= + //! constructor + OutsideQuadsInfo() + : local_num_outside_quads(0) + , global_num_outside_quads(0) + , first_outside_quad_global_id(0) + , local_num_ghost_quads(0) + , local_num_outside_ghosts(0) + {} + + + // ========================================================================= + // ========================================================================= + void + print() + { + std::cout << "local_num_outside_quads: " << local_num_outside_quads << "\n"; + std::cout << "global_num_outside_quads: " << global_num_outside_quads << "\n"; + std::cout << "first_outside_quad_global_id: " << first_outside_quad_global_id << "\n"; + std::cout << "local_num_ghost_quads: " << local_num_ghost_quads << "\n"; + } // print + + // ========================================================================= + // ========================================================================= + void + reset() + { + local_num_outside_quads = 0; + global_num_outside_quads = 0; + first_outside_quad_global_id = 0; + local_num_ghost_quads = 0; + local_num_outside_ghosts = 0; + } + + // ========================================================================= + // ========================================================================= + //! update outside quadrants information using p4est forest + template + void + update(/*const*/ ghost_t * ghost, + [[maybe_unused]] const ParallelEnv & par_env, + int32_t num_outside_quads, + int32_t num_outside_ghosts) + { + // from here we assume m_num_outside_quads and num_outside_ghosts have already been computed + local_num_outside_quads = num_outside_quads; + local_num_outside_ghosts = num_outside_ghosts; + + // perform a MPI reduce to compute total number of outside quads +#ifdef KALYPSSO_CORE_USE_MPI + par_env.comm().MPI_Allreduce( + &local_num_outside_quads, &global_num_outside_quads, 1); +#else + global_num_outside_quads = local_num_outside_quads; +#endif // KALYPSSO_CORE_USE_MPI + + // perform a MPI scan to compute global index of the first outside quads in current MPI proc +#ifdef KALYPSSO_CORE_USE_MPI + par_env.comm().MPI_Exscan( + &local_num_outside_quads, &first_outside_quad_global_id, 1); +#else + // it is an exclusive scan, no MPI + first_outside_quad_global_id = 0; +#endif // KALYPSSO_CORE_USE_MPI + + local_num_ghost_quads = static_cast(ghost->ghosts.elem_count); + + } // update + + // ========================================================================= + // ========================================================================= + //! update outside quadrants information using p4est forest (no ghost, only for test/debug) + template + void + update(/*const*/ forest_t * forest, + /*const*/ ghost_t * ghost, + [[maybe_unused]] const ParallelEnv & par_env, + Kokkos::Array is_brick_periodic, + brick_size_t brick_sizes) + { + // from here we assume m_num_outside_quads has already been computed + local_num_outside_quads = + compute_number_outside_quads(forest, par_env, is_brick_periodic, brick_sizes); + + // perform a MPI reduce to compute total number of outside quads +#ifdef KALYPSSO_CORE_USE_MPI + par_env.comm().MPI_Allreduce( + &local_num_outside_quads, &global_num_outside_quads, 1); +#else + // it is an exclusive scan, no MPI + global_num_outside_quads = local_num_outside_quads; +#endif // KALYPSSO_CORE_USE_MPI + + // perform a MPI scan to compute global index of the first outside quads in current MPI proc +#ifdef KALYPSSO_CORE_USE_MPI + par_env.comm().MPI_Exscan( + &local_num_outside_quads, &first_outside_quad_global_id, 1); +#else + first_outside_quad_global_id = 0; +#endif // KALYPSSO_CORE_USE_MPI + + + if (ghost != nullptr) + local_num_ghost_quads = ghost->ghosts.elem_count; + else + local_num_ghost_quads = 0; + + // compute local number of outside quadrants associated to an inside ghost + local_num_outside_ghosts = + compute_number_outside_ghosts(ghost, par_env, is_brick_periodic, brick_sizes); + + } // update + +}; // struct OutsideQuadsInfo + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_OUTSIDEQUADSINFO_H_ diff --git a/src/kalypsso/core/Point.h b/src/kalypsso/core/Point.h new file mode 100644 index 0000000..e918a69 --- /dev/null +++ b/src/kalypsso/core/Point.h @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef KALYPSSO_CORE_POINT_H +#define KALYPSSO_CORE_POINT_H + +#include +#include + +namespace kalypsso +{ + +/** typedef Point holding coordinates of a point. */ +template +using Point = std::array; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_POINT_H diff --git a/src/kalypsso/core/SimpleVTKIO.h b/src/kalypsso/core/SimpleVTKIO.h new file mode 100644 index 0000000..619a093 --- /dev/null +++ b/src/kalypsso/core/SimpleVTKIO.h @@ -0,0 +1,232 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file SimpleVTKIO.h + */ +#ifndef KALYPSSO_CORE_SIMPLE_VTK_IO_H_ +#define KALYPSSO_CORE_SIMPLE_VTK_IO_H_ + +#include +#include +#include +#include // for DataArray, DataArrayHost +#include + +// #include "kalypsso/core/bitpit_common.h" +#include +#include + +#include +#include +#include +#include +#include + +namespace kalypsso +{ + +/** + * Simple VTK IO routine (simple means Partitioned VTU, using ASCII). + * + * Here we assume DataArray size is the same as the number of AMR mesh octants. + * + * \param[in] forest a forest_t const reference + * \param[in] filename a string specify the output filename suffix (e.g. nb of iter) + * \param[in] data a Kokkos::View to the data to save + * \param[in] fm a field map to access data + * \param[in] names2index a map of names (of scalar field to save) to id (to fm) + * \param[in] config_map a ConfigMap object to access input parameter file data (ini file format) + */ +template +void +writeVTK(typename p4est::Wrapper::forest_t * forest, + typename p4est::Wrapper::geometry_t * geom, + DataArrayLeaf data, + const model_t & model, + std::string filename) +{ + + using p4est_t = typename p4est::Wrapper; + + using locidx_t = p4est::locidx_t; + + // copy data from device to host + auto datah = Kokkos::create_mirror(data); + + // copy device data to host + Kokkos::deep_copy(datah, data); + + // dimension : 2 or 3 ? + // constexpr uint8_t dim = dim_; + + const auto & id2names = model.get_id2names_map(); + const auto & fm = model.get_fieldmap(); + + // number of scalar fields + int numScalarFields = id2names.size(); + + // create p4est vtk context + typename p4est_t::vtk_context_t * context; + context = p4est_t::vtk_context_new(forest, filename.c_str()); + + if (geom != nullptr) + p4est_t::vtk_context_set_geom(context, geom); + + // we do not write point data (only cell data), so it is safe to set + // continuous to true. + // this will not save any space though since the default scale is < 1. + p4est_t::vtk_context_set_continuous(context, 1); + + // write vtk header (vertex positions and quadrant-to-vertex mapping) + context = p4est_t::vtk_write_header(context); + + // write meta data (tree, level, MPI rank) + int write_tree = 1; + int write_level = 1; + int write_rank = 1; + int wrap_rank = 0; + + // local number of quadrants/octants + auto numOcts = forest->local_num_quadrants; + + sc_array_t ** vtkdata = (sc_array_t **)malloc(sizeof(sc_array_t *) * 5); + std::vector varNames; + + // prepare sc_array_t with scalar field from our Kokkos::View + int id = 0; + for (auto iter : id2names) + { + + // get variables string name + const auto varName = iter.second; + + // get variable id + auto iVar = static_cast(iter.first); + + // allocate an sc_array_t + vtkdata[id] = sc_array_new_size(sizeof(double), numOcts); + + // register scalar field name + varNames.push_back(varName); + + // copy data to vtkdata + for (locidx_t iOct = 0; iOct < numOcts; iOct++) + { + + double * ptr = (double *)sc_array_index(vtkdata[id], iOct); + ptr[0] = datah(iOct, fm[iVar]); + } + + ++id; + + } // end for iter + + // call p4est vtk API to write all scalar fields at once + switch (numScalarFields) + { + case 1: + context = p4est_t::vtk_write_cell_dataf(context, + write_tree, + write_level, + write_rank, + wrap_rank, + numScalarFields, /* nb scalars */ + 0, /* nb vector fields */ + varNames[0].c_str(), + vtkdata[0], + context); + break; + case 2: + context = p4est_t::vtk_write_cell_dataf(context, + write_tree, + write_level, + write_rank, + wrap_rank, + numScalarFields, /* nb scalars */ + 0, /* nb vector fields */ + varNames[0].c_str(), + vtkdata[0], + varNames[1].c_str(), + vtkdata[1], + context); + break; + case 3: + context = p4est_t::vtk_write_cell_dataf(context, + write_tree, + write_level, + write_rank, + wrap_rank, + numScalarFields, /* nb scalars */ + 0, /* nb vector fields */ + varNames[0].c_str(), + vtkdata[0], + varNames[1].c_str(), + vtkdata[1], + varNames[2].c_str(), + vtkdata[2], + context); + break; + case 4: + context = p4est_t::vtk_write_cell_dataf(context, + write_tree, + write_level, + write_rank, + wrap_rank, + numScalarFields, /* nb scalars */ + 0, /* nb vector fields */ + varNames[0].c_str(), + vtkdata[0], + varNames[1].c_str(), + vtkdata[1], + varNames[2].c_str(), + vtkdata[2], + varNames[3].c_str(), + vtkdata[3], + context); + break; + case 5: + context = p4est_t::vtk_write_cell_dataf(context, + write_tree, + write_level, + write_rank, + wrap_rank, + numScalarFields, /* nb scalars */ + 0, /* nb vector fields */ + varNames[0].c_str(), + vtkdata[0], + varNames[1].c_str(), + vtkdata[1], + varNames[2].c_str(), + vtkdata[2], + varNames[3].c_str(), + vtkdata[3], + varNames[4].c_str(), + vtkdata[4], + context); + break; + default: + fprintf(stderr, + "Wrong number of scalar fields. You should consider refactoring writeVTK routine.\n"); + break; + } + + // write vtk file footer + p4est_t::vtk_write_footer(context); + + // destroy intermediate sc_array_t's + id = 0; + for (auto iter : id2names) + { + sc_array_destroy(vtkdata[id]); + ++id; + } + + free(vtkdata); + +} // writeVTK + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_SIMPLE_VTK_IO_H_ diff --git a/src/kalypsso/core/SmoothInterfaceFunctionData.cpp b/src/kalypsso/core/SmoothInterfaceFunctionData.cpp new file mode 100644 index 0000000..152fd34 --- /dev/null +++ b/src/kalypsso/core/SmoothInterfaceFunctionData.cpp @@ -0,0 +1,219 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file SmoothInterfaceFunctionData.cpp + * \brief Definition of a container for data needed in smooth interface function + * related algorithms. + * + * Algorithms include: + * + * - THINC: Tangent Hyperbolic INterface Capturing + * - surface tension source term as implemented in Garrick et al (2017) + * + * References: + * + * - A finite-volume HLLC-based scheme for compressible interfacial flows with surface tension, + * Garrick Owkes and Regele, Journal of Computational Physics Volume 339, 15 June 2017, Pages 46-67. + * https://doi.org/10.1016/j.jcp.2017.03.007 + * - An interface capturing scheme for modeling atomization in compressible flows, Garrick, Hagen + * and Regele, Journal of Computational Physics, Volume 344, 1 September 2017, Pages 260-280. + * https://doi.org/10.1016/j.jcp.2017.04.079 + * + */ + +#include + +#include +#include +#include +#include + +namespace kalypsso +{ + +// ================================================================================================ +// ================================================================================================ +template +void +SmoothInterfaceFunctionData::resize() +{ + + const auto num_quadrants = m_mesh_map.get_amr_mesh_info().local_num_quadrants_total(); + + if (m_enabled) + { + m_sif.resize(num_quadrants); + m_normal_vector.resize(num_quadrants); + + if (m_surface_tension_enabled) + { + m_curvature_weights.resize(num_quadrants); + m_unfiltered_curvature.resize(num_quadrants); + m_curvature.resize(num_quadrants); + } + } + +} // SmoothInterfaceFunctionData::resize + +// ================================================================================================ +// ================================================================================================ +template +uint64_t +SmoothInterfaceFunctionData::total_mem_size_in_bytes() const +{ + uint64_t total = 0; + + if (m_enabled) + { + // the following array are needed for THINC computation + total += m_sif.allocated_size_in_bytes(); + total += m_normal_vector.allocated_size_in_bytes(); + + // the following arrays are only need for surface tension computation + if (m_surface_tension_enabled) + { + total += m_curvature_weights.allocated_size_in_bytes(); + total += m_unfiltered_curvature.allocated_size_in_bytes(); + total += m_curvature.allocated_size_in_bytes(); + } + } + +#ifdef KALYPSSO_CORE_USE_MPI + total += m_mesh_ghosts_exchanger.allocated_size_in_bytes(); +#endif + + return total; + +} // SmoothInterfaceFunctionData::total_mem_size_in_bytes + +// ================================================================================================ +// ================================================================================================ +template +void +SmoothInterfaceFunctionData::compute_smooth_interface_function( + DataArrayBlock_t const & userdata_in, + int32_t ivar) +{ + KOKKOS_ASSERT(userdata_in.num_quadrants() == m_sif.num_quadrants() && "Wrong sizes"); + + // fill m_sif + ComputeSmoothInterfaceFunction::apply( + m_config_map, userdata_in, m_sif, ivar, 0, m_sif.num_quadrants()); + +} // SmoothInterfaceFunctionData::compute_smooth_interface_function + +// ========================================================================= +// ========================================================================= +template +void +SmoothInterfaceFunctionData::compute_interface_normal_vector( + const OrchardKeys & keys) +{ + const auto stencil_length = core::FIRST_DERIVATIVE_STENCIL::SEVEN_POINTS; + + // compute normal vector + core::FirstOrderDerivativeFiniteDifference::normalized_gradient( + m_config_map, keys, 0, m_sif.num_quadrants(), m_sif, 0, m_normal_vector, stencil_length); + + // fill ghost cells around block + FillBlockGhostCellsInPlaceFunctor::apply(m_config_map, + m_mesh_map.hashmap(), + keys, + m_normal_vector, + get_brick_sizes(m_config_map), + get_brick_periodicity(m_config_map)); + +#ifdef KALYPSSO_CORE_USE_MPI + m_mesh_ghosts_exchanger.exchange(m_normal_vector.data()); +#endif + +} // SmoothInterfaceFunctionData::compute_interface_normal_vector + +// ========================================================================= +// ========================================================================= +template +void +SmoothInterfaceFunctionData::compute_curvature(const OrchardKeys & keys, + DataArrayGhostedBlock_t const & qdata, + int32_t iphi) +{ + + if (m_surface_tension_enabled) + { + const auto stencil_length = core::FIRST_DERIVATIVE_STENCIL::SEVEN_POINTS; + + core::FirstOrderDerivativeFiniteDifference::divergence( + m_config_map, + keys, + 0, + m_normal_vector.num_quadrants(), + m_normal_vector, + m_unfiltered_curvature, + stencil_length, + -1.0); + + // fill ghost cells around block + FillBlockGhostCellsInPlaceFunctor::apply( + m_config_map, + m_mesh_map.hashmap(), + keys, + m_unfiltered_curvature, + get_brick_sizes(m_config_map), + get_brick_periodicity(m_config_map)); + +#ifdef KALYPSSO_CORE_USE_MPI + m_mesh_ghosts_exchanger.exchange(m_unfiltered_curvature.data()); +#endif + + Kokkos::deep_copy(m_curvature.data().logical_view(), + m_unfiltered_curvature.data().logical_view()); + + const auto num_filt_iter = + m_config_map.getInteger("smooth_interface_function", "curvature_filter_iterations", 3); + + if (num_filt_iter > 0) + { + // apply filter + ComputeFilteredCurvature::apply(qdata, + iphi, + m_unfiltered_curvature, + m_curvature_weights, + m_curvature, + 0, + m_curvature.num_quadrants(), + m_config_map); + + + // if the number of filtering steps is even, we need to swap array so stat the final filtered + // curvature is contained in m_curvature + if (num_filt_iter % 2 == 0) + { + my_swap(m_curvature, m_unfiltered_curvature); + } + +#ifdef KALYPSSO_CORE_USE_MPI + m_mesh_ghosts_exchanger.exchange(m_curvature.data()); +#endif + } + else + { + Kokkos::deep_copy(m_curvature.data().logical_view(), + m_unfiltered_curvature.data().logical_view()); + } + } + else + { + KALYPSSO_ERROR( + "Cannot compute curvature; please set \"smooth_interface_function/surface_tension_enabled\" ", + "to true in the input parameter file."); + } + +} // SmoothInterfaceFunctionData::compute_curvature + +// explicit template instantiation +template class SmoothInterfaceFunctionData<2, kalypsso::DefaultDevice>; +template class SmoothInterfaceFunctionData<3, kalypsso::DefaultDevice>; + +} // namespace kalypsso diff --git a/src/kalypsso/core/SmoothInterfaceFunctionData.h b/src/kalypsso/core/SmoothInterfaceFunctionData.h new file mode 100644 index 0000000..3f932e8 --- /dev/null +++ b/src/kalypsso/core/SmoothInterfaceFunctionData.h @@ -0,0 +1,225 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file SmoothInterfaceFunctionData.h + * \brief Container for data needed in smooth interface function related algorithms. + */ +#ifndef KALYPSSO_CORE_SMOOTH_INTERFACE_FUNCTION_DATA_H_ +#define KALYPSSO_CORE_SMOOTH_INTERFACE_FUNCTION_DATA_H_ + +#include +#include +#include +#include // for DataArrayBlock, FaceDataArrayBlock, ... +#include +#include +#include + +#include // for get_block_sizes +#include + +#include +#include + +// AMR services +#ifdef KALYPSSO_CORE_USE_MPI +# include +#endif // KALYPSSO_CORE_USE_MPI + +namespace kalypsso +{ + +// ======================================================================================== +// ======================================================================================== +// ======================================================================================== +/** + * \class SmoothInterfaceFunctionData + * + * \brief A container class for all data used in smooth interface function related algorithms. + * + * Algorithms are: + * - compute the smooth interface function psi + * - compute interface normal vector (gradient of psi) using a fourth order central difference + * scheme + * - compute surface tension + * + * Note that interface normal vector and curvature are stored in a DataArrayGhostedBlock but only + * the inner part is computed here. We chose to use DataArrayGhostedBlock instead of DataArrayBlock + * to ease the use of these data in downstream code (e.g. multifluid Riemann solver). + * We use a ghostwidth of 1 all around (TODO: see if ghostwidth needs to be a configuration + * variable). + * + * \tparam dim is dimension (integer: 2 or 3) + * \tparam device_t is a kokkos device class (e.g. Kokkos::CudaSpace::device_type) + */ +template +class SmoothInterfaceFunctionData +{ +public: + using exec_space = typename device_t::execution_space; + using DataArrayBlock_t = DataArrayBlock; + using DataArrayGhostedBlock_t = DataArrayGhostedBlock; + using OrchardKeys = typename orchard_key_base_t::view_t; + + static constexpr int NUM_VALUES = 1; + + // ========================================================================= + // ========================================================================= + //! constructor + SmoothInterfaceFunctionData([[maybe_unused]] ParallelEnv const & par_env, + ConfigMap const & config_map, + [[maybe_unused]] AMRmesh & amr_mesh, + [[maybe_unused]] MeshMap & mesh_map) + : m_config_map(config_map) + , m_enabled(config_map.getBool("smooth_interface_function", "enabled", false)) + , m_surface_tension_enabled( + config_map.getBool("smooth_interface_function", "surface_tension_enabled", false)) + , m_block_sizes(get_block_sizes(config_map)) + , m_sif() + , m_normal_vector() + , m_curvature_weights() + , m_unfiltered_curvature() + , m_curvature() + , m_mesh_map(mesh_map) +#ifdef KALYPSSO_CORE_USE_MPI + , m_mesh_ghosts_exchanger(config_map, par_env, amr_mesh, mesh_map) +#endif // KALYPSSO_CORE_USE_MPI + { + + if (m_enabled) + { + m_sif = DataArrayGhostedBlock_t(m_block_sizes, + m_block_sizes + 2 * 0, + get_shift(0), + "smooth_interface_function", + NUM_VALUES, + 0); + m_normal_vector = DataArrayGhostedBlock_t( + m_block_sizes, m_block_sizes + 2 * 1, get_shift(-1), "normal_vector", dim, 0); + + if (m_surface_tension_enabled) + { + m_curvature_weights = DataArrayGhostedBlock_t(m_block_sizes, + m_block_sizes + 2 * 1, + get_shift(-1), + "curvature_weights", + NUM_VALUES, + 0); + + m_unfiltered_curvature = DataArrayGhostedBlock_t(m_block_sizes, + m_block_sizes + 2 * 1, + get_shift(-1), + "unfiltered curvature", + NUM_VALUES, + 0); + + m_curvature = DataArrayGhostedBlock_t( + m_block_sizes, m_block_sizes + 2 * 1, get_shift(-1), "curvature", NUM_VALUES, 0); + } + } + + } // SmoothInterfaceFunctionData::SmoothInterfaceFunctionData + + // ========================================================================= + // ========================================================================= + //! constructor + ~SmoothInterfaceFunctionData() = default; + + // ========================================================================= + // ========================================================================= + auto + enabled() const + { + return m_enabled; + } + + // ========================================================================= + // ========================================================================= + auto + surface_tension_enabled() const + { + return m_surface_tension_enabled; + } + + // ========================================================================= + // ========================================================================= + //! Resize our workspace data. + //! + //! \param[in] num_octants new number of octants + void + resize(); + + // ========================================================================= + // ========================================================================= + //! memory footprint monitoring + uint64_t + total_mem_size_in_bytes() const; + + // ========================================================================= + // ========================================================================= + //! compute smooth interface function (SIF) + void + compute_smooth_interface_function(DataArrayBlock_t const & userdata_in, int32_t ivar); + + // ========================================================================= + // ========================================================================= + //! compute interface normal vector + void + compute_interface_normal_vector(const OrchardKeys & keys); + + // ========================================================================= + // ========================================================================= + //! compute curvature + //! + //! \param[in] keys Orchard keys. + //! \param[in] qdata Array of primitive variables + //! \param[in] iphi index to volume fraction + void + compute_curvature(const OrchardKeys & keys, DataArrayGhostedBlock_t const & qdata, int32_t iphi); + + // + // Data members + // + + //! config map + const ConfigMap & m_config_map; + + //! smooth interface function enabled ? + bool m_enabled; + + //! surface tension enabled ? + bool m_surface_tension_enabled; + + //! block sizes + const block_size_t m_block_sizes; + + //! smooth interface function + DataArrayGhostedBlock_t m_sif; + + //! interface normal vector (required for doing THINC reconstruction) + DataArrayGhostedBlock_t m_normal_vector; + + //! curvature weights (optional) used in filtering step + DataArrayGhostedBlock_t m_curvature_weights; + + //! unfiltered curvature (optional) + DataArrayGhostedBlock_t m_unfiltered_curvature; + + //! curvature (optional) + DataArrayGhostedBlock_t m_curvature; + + //! mesh map is a helper class for accessing orchard keys + MeshMap & m_mesh_map; + +#ifdef KALYPSSO_CORE_USE_MPI + //! MPI communications to exchange ghost block userdata + MeshGhostsExchanger m_mesh_ghosts_exchanger; +#endif // KALYPSSO_CORE_USE_MPI + +}; // class SmoothInterfaceFunctionData + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_SMOOTH_INTERFACE_FUNCTION_DATA_H_ diff --git a/src/kalypsso/core/SolverBase.cpp b/src/kalypsso/core/SolverBase.cpp new file mode 100644 index 0000000..9e00d21 --- /dev/null +++ b/src/kalypsso/core/SolverBase.cpp @@ -0,0 +1,441 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file SolverBase.cpp + */ +#include // for KALYPSSO_CORE_USE_MPI +#include +#include + +#include +#include + +#ifdef KALYPSSO_CORE_USE_MPI +// #include "kalypsso/core/mpiBorderUtils.h" +#endif // KALYPSSO_CORE_USE_MPI + +namespace kalypsso +{ + +// ======================================================= +// ==== CLASS SolverBase IMPL ============================ +// ======================================================= + +// ======================================================= +// ======================================================= +SolverBase::SolverBase(ParallelEnv const & par_env, + HydroParams const & params, + ConfigMap const & config_map) + : m_par_env(par_env) + , m_params(params) + , m_config_map(config_map) + , m_profiling_mgr(par_env) + , m_restart_run_enabled(false) + , m_restart_run_filename("") + , m_amr_cycle_counter(0) + +{ + + /* + * init some variables by reading the ini parameter file. + */ + read_config(); + + /* + * other variables initialization. + */ + m_times_saved = 0; + m_times_saved_restart = 0; + + m_nCells = -1; + m_nDofsPerCell = -1; + + // statistics + m_total_num_cell_updates = 0; + +#ifdef KALYPSSO_CORE_USE_MPI + // const int nbvar = m_params.nbvar; + + // TODO + +#endif // KALYPSSO_CORE_USE_MPI + + // initialize memory allocation growth rate + DataArrayUtils::set_growth_rate(config_map); + +} // SolverBase::SolverBase + +// ======================================================= +// ======================================================= +SolverBase::~SolverBase() {} // SolverBase::~SolverBase + +// ======================================================= +// ======================================================= +void +SolverBase::do_amr_cycle() +{ + + // Example of what must be implemented in derived class + + // 1. User data comm to update ghost cell values + + // 2. mark cell for refinement / coarsening + + // 3. adapt mesh + + // 4. user data remapping + + // increase number of AMR cycle + m_amr_cycle_counter++; + +} // SolverBase::do_amr_cycle + +// ======================================================= +// ======================================================= +void +SolverBase::do_load_balancing() +{ + + // perform MPI load balancing (mesh + user data) + +} // SolverBase::do_load_balancing + +// ======================================================= +// ======================================================= +void +SolverBase::read_config() +{ + + // restart run : default is no + m_restart_run_enabled = m_config_map.getInteger("run", "restart_enabled", 0); + m_restart_run_filename = m_config_map.getString("run", "restart_filename", ""); + + m_tBegin = m_config_map.getReal("run", "tBegin", KALYPSSO_NUM(0.0)); + m_tEnd = m_config_map.getReal("run", "tEnd", KALYPSSO_NUM(0.0)); + + // note: when doing a restart run, m_t / m_tBegin will be read from the restart data file + m_t = m_tBegin; + + m_max_iterations = m_params.nStepmax; + + // maximum number of output written + m_max_save_count = m_params.nOutput; + + // save initial condition ? + m_save_initial_condition = m_config_map.getBool("run", "save_initial_condition", true); + + // maximum number of checkpoint output written + m_max_checkpoint_count = m_config_map.getInteger("checkpoint", "count", 1); + + m_amr_cycle_frequency = m_config_map.getInteger("amr", "cycle_frequency", 10); + + m_amr_load_balancing_frequency = m_config_map.getInteger("amr", "load_balancing_frequency", 20); + + m_dt = m_tEnd; + m_cfl = m_config_map.getReal("hydro", "cfl", KALYPSSO_NUM(1.0)); + m_nlog = m_config_map.getInteger("run", "nlog", 10); + m_iteration = 0; + + m_problem_name = m_config_map.getString("hydro", "problem", "unknown"); + + m_solver_name = m_config_map.getString("run", "solver_name", "unknown"); + +} // SolverBase::read_config + +// ======================================================= +// ======================================================= +void +SolverBase::compute_dt() +{ + +#ifdef KALYPSSO_CORE_USE_MPI + + // get local time step + real_t dt_local = compute_dt_local(); + + // perform MPI_Allreduce to get global time step + real_t dt_global; + m_par_env.comm().MPI_Allreduce(&dt_local, &dt_global, 1); + + m_dt = dt_global; + +#else + + m_dt = compute_dt_local(); + +#endif // KALYPSSO_CORE_USE_MPI + + // correct m_dt if necessary + if (m_t + m_dt > m_tEnd) + { + m_dt = m_tEnd - m_t; + } + +} // SolverBase::compute_dt + +// ======================================================= +// ======================================================= +real_t +SolverBase::compute_dt_local() +{ + + // the actual numerical scheme must provide it a genuine implementation + + return m_tEnd; + +} // SolverBase::compute_dt_local + +// ======================================================= +// ======================================================= +int +SolverBase::finished() +{ + + return m_t >= (m_tEnd - KALYPSSO_NUM(1e-14)) || m_iteration >= m_max_iterations; + +} // SolverBase::finished + +// ======================================================= +// ======================================================= +// TODO: better strategy to decide when to adapt ? +bool +SolverBase::should_do_amr_cycle() +{ + + // default behavior : once every amr_cycle_frequency time steps + const bool do_amr_cycle = m_params.amr_cycle_enabled and m_amr_cycle_frequency > 0 and + m_iteration > 0 and ((m_iteration % m_amr_cycle_frequency) == 0); + + const auto disable_first_amr_cycle = m_config_map.getBool("amr", "disable_first_amr_cycle", true); + + return do_amr_cycle or (m_iteration == 0 and disable_first_amr_cycle == false); + +} // SolverBase::should_do_amr_cycle + +// ======================================================= +// ======================================================= +// TODO: design a better strategy when print AMR info during a simulation run +bool +SolverBase::should_print_monitoring_info() +{ + const auto enable_amr_cycle_monitoring = + m_config_map.getBool("amr", "amy_cycle_monitoring_enable", false); + + // strategy once every X amr cycle + constexpr int default_value = 10; + + auto num_amr_cycles_monitoring = + m_config_map.getInteger("amr", "num_amr_cycle_between_monitoring_print", default_value); + + // check for invalid values + if (num_amr_cycles_monitoring < 1) + num_amr_cycles_monitoring = default_value; + + return enable_amr_cycle_monitoring and (m_amr_cycle_counter % num_amr_cycles_monitoring == 0); + +} // SolverBase::should_print_monitoring_info + +// ======================================================= +// ======================================================= +bool +SolverBase::should_do_load_balancing() +{ + + // default behavior : true once every amr_load_balancing_frequency + return m_iteration != 0 and (m_iteration % m_amr_load_balancing_frequency) == 0; + +} // SolverBase::should_do_load_balancing + +// ======================================================= +// ======================================================= +void +SolverBase::next_iteration() +{ + + // setup a timer here (?) + + // genuine implementation called here + next_iteration_impl(); + + // increment time + ++m_iteration; + m_t += m_dt; + +} // SolverBase::next_iteration + +// ======================================================= +// ======================================================= +void +SolverBase::next_iteration_impl() +{ + + // This is application dependent + +} // SolverBase::next_iteration_impl + +// ======================================================= +// ======================================================= +void +SolverBase::run() +{ + + /* + * Default implementation for the time loop + */ + while (!finished()) + { + next_iteration(); + } // end time loop + +} // SolverBase::run + +// ======================================================= +// ======================================================= +void +SolverBase::save_solution(bool pure_checkpoint) +{ + + // save solution to output file + save_solution_impl(pure_checkpoint); + + // increment output file number + // do not increment when dumping initial condition + if (m_iteration > 0) + ++m_times_saved; + +} // SolverBase::save_solution + +// ======================================================= +// ======================================================= +void +SolverBase::save_solution_impl([[maybe_unused]] bool pure_checkpoint) +{} // SolverBase::save_solution_impl + +// ======================================================= +// ======================================================= +void +SolverBase::print_monitoring_info() +{} // SolverBase::print_monitoring_info + +// ======================================================= +// ======================================================= +void +SolverBase::print_monitoring_info_final() +{} // SolverBase::print_monitoring_info_final + +// ======================================================= +// ======================================================= +void +SolverBase::register_volume_integrals([[maybe_unused]] bool is_reference) +{} // SolverBase::register_volume_integrals + +// ======================================================= +// ======================================================= +void +SolverBase::print_conservativity_check_report() const +{} // SolverBase::print_conservativity_check_report + +// ======================================================= +// ======================================================= +bool +SolverBase::should_save_solution() +{ + + real_t interval = (m_tEnd - m_tBegin) / static_cast(m_params.nOutput); + + // m_params.nOutput == 0 means no output at all + if (m_max_save_count == 0) + { + return false; + } + + // m_params.nOutput < 0 means always output + if (m_max_save_count < 0) + { + return true; + } + + if (m_iteration == 0 and m_save_initial_condition) + { + return true; + } + else if ((m_t - m_tBegin) > (static_cast(m_times_saved + 1) * interval)) + { + return true; + } + + // always write the last time step + if (ISFUZZYNULL(m_t - m_tEnd)) + { + return true; + } + + return false; + +} // SolverBase::should_save_solution + +// ======================================================= +// ======================================================= +bool +SolverBase::should_do_checkpoint() +{ + + // all outputs are turned into a checkpoint file + if (m_config_map.getBool("checkpoint", "all_outputs_are_checkpoint", false)) + { + return should_save_solution(); + } + + // doing checkpoint at regular intervals + if (m_max_checkpoint_count > 0) + { + + real_t interval = (m_tEnd - m_tBegin) / static_cast(m_max_checkpoint_count); + + // never write restart file at t = m_tBegin + if (((m_t - m_tBegin) - static_cast(m_times_saved_restart) * interval) > interval) + { + return true; + } + + // always write the restart at the last time step + if (ISFUZZYNULL(m_t - m_tEnd)) + { + return true; + } + } + + return false; + +} // SolverBase::should_do_checkpoint + +// ======================================================= +// ======================================================= +std::string +SolverBase::output_time_suffix() +{ + // prepare suffix string + std::ostringstream strsuffix; + strsuffix << "iter"; + strsuffix.width(7); + strsuffix.fill('0'); + strsuffix << m_iteration; + + return strsuffix.str(); + +} // SolverBase::output_time_suffix + +// ======================================================= +// ======================================================= +std::string +SolverBase::output_basename() +{ + std::string outputPrefix = m_config_map.getString("output", "outputPrefix", "output"); + + return outputPrefix + "_" + output_time_suffix(); + +} // SolverBase::output_basename + +} // namespace kalypsso diff --git a/src/kalypsso/core/SolverBase.h b/src/kalypsso/core/SolverBase.h new file mode 100644 index 0000000..035f773 --- /dev/null +++ b/src/kalypsso/core/SolverBase.h @@ -0,0 +1,327 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file SolverBase.h + */ +#ifndef KALYPSSO_CORE_SOLVER_BASE_H_ +#define KALYPSSO_CORE_SOLVER_BASE_H_ + +#include +#include // for std::unique_ptr / std::shared_ptr + +#include // for KALYPSSO_CORE_USE_HDF5, ... +#include +#include +#include +#include + +#include +#include +#include + +#ifdef KALYPSSO_CORE_USE_HDF5 +# include +#endif // KALYPSSO_CORE_USE_HDF5 + +namespace kalypsso +{ + +// ========================================================================== +// ========================================================================== +// ========================================================================== +/** + * Abstract base class for all our actual solvers. + */ +class SolverBase +{ +public: + SolverBase(ParallelEnv const & par_env, HydroParams const & params, ConfigMap const & config_map); + virtual ~SolverBase(); + +protected: + //! parallel environment (MPI, Kokkos, p4est) + ParallelEnv const & m_par_env; + + //! hydrodynamics parameters settings + HydroParams const & m_params; + + //! unordered map of parameters read from input ini file + ConfigMap const & m_config_map; + + //! Profiling manager + ProfilingManager m_profiling_mgr; + + //! is this a restart run ? + int m_restart_run_enabled; + + //! filename containing data from a previous run. + std::string m_restart_run_filename; + + // iteration info + real_t m_t; //!< the time at the current iteration + real_t m_dt; //!< the time step at the current iteration + int m_iteration; //!< the current iteration (integer) + int m_max_iterations; //!< user defined maximum iteration count + real_t m_tBegin; //!< begin time (might be non-zero upon restart) + real_t m_tEnd; //!< maximum time + real_t m_cfl; //!< Courant number + int m_nlog; //!< number of steps between two monitoring print on screen + + int m_max_save_count; //!< max number of output written + int m_max_checkpoint_count; //!< max number of checkpoint file written + bool m_save_initial_condition; //!< true if you want to save initial condition + + //! control how often AMR cycle is done (how many time steps between two AMR cycles) + int m_amr_cycle_frequency; + + //! number of AMR cycle done + int m_amr_cycle_counter; + + /** + * specify how often load balancing must be done. + * Load balancing is performed once every amr_load_balancing_frequency + * This value must be strictly larger than 0. + */ + int m_amr_load_balancing_frequency; + + long long int m_nCells; //!< number of cells + long long int m_nDofsPerCell; //!< number of degrees of freedom per cell + + // statistics + //! total number of quadrant update + uint64_t m_total_num_cell_updates; + + //! init condition name (or problem) + std::string m_problem_name; + + //! solver name (use in output file). + std::string m_solver_name; + + //! dimension (2 or 3) + int m_dim; + +public: + ConfigMap const & + config_map() const + { + return m_config_map; + } + + ParallelEnv const & + par_env() const + { + return m_par_env; + } + + HydroParams const & + hydro_params() const + { + return m_params; + } + + auto + begin_time() const + { + return m_tBegin; + } + + auto & + begin_time() + { + return m_tBegin; + } + + auto + current_time() const + { + return m_t; + } + + auto & + current_time() + { + return m_t; + } + + auto + iteration() const + { + return m_iteration; + } + + auto & + iteration() + { + return m_iteration; + } + + auto + problem_name() const + { + return m_problem_name; + } + + auto & + profiling_mgr() + { + return m_profiling_mgr; + } + + /* + * + * Computation interface that may be overridden in a derived + * concrete implementation. + * + */ + //! solver name + virtual std::string + solver_name() const + { + return "unknown_solver"; + }; + + //! perform AMR cycle (mark cells, adapt = refine/coarsen, remap user data) + virtual void + do_amr_cycle(); + + //! do MPI load balancing + virtual void + do_load_balancing(); + + //! Read and parse the configuration file (ini format). + virtual void + read_config(); + + //! Compute CFL condition (allowed time step), over all MPI process. + virtual void + compute_dt(); + + //! Compute CFL condition local to current MPI process + virtual real_t + compute_dt_local(); + + //! Check if current time is larger than end time. + virtual int + finished(); + + //! Check if AMR cycle is required + virtual bool + should_do_amr_cycle(); + + //! Check if printing monitoring info is required + virtual bool + should_print_monitoring_info(); + + //! Check if Load Balancing is required + virtual bool + should_do_load_balancing(); + + //! This is where action takes place. Wrapper around next_iteration_impl. + virtual void + next_iteration(); + + //! This is the next iteration computation (application specific). + virtual void + next_iteration_impl(); + + //! This is were the time loop is done + virtual void + run(); + + //! Decides if the current time step is eligible for saving data to file + virtual bool + should_save_solution(); + + //! Decides if the current time step is eligible for checkpoint, ie. + //! - saving p4est mesh state to file, + //! - saving all required variables (e.g. conservative variables). + //! + //! This can be evaluated at any time step. + //! + //! If a regular output is also requested, i.e. should_save_solution() is true, then we make sure + //! that all required variable will be save + //! If a regular outpur is not requested, we are doing a "pure" checkpoint. + //! + //! The default behavior is to return false. The user has to explicitly request a checkpoint. + // + //! Note: By setting parameter checkpoint/all_outputs_are_checkpoint to true in the ini parameter + //! file every output will be turned to a checkpoint file. default behavior is that only the last + //! output is always a checkpoint. + virtual bool + should_do_checkpoint(); + + //! return suffix for output files name containing time iteration format + virtual std::string + output_time_suffix(); + + //! return base name for output files + virtual std::string + output_basename(); + + //! main routine to dump solution (and additional derived quantities) to file. + //! + //! param[in] pure_checkpoint boolean indicating if the output should be considerate as a pure + //! checkpoint file + //! + //! When doing a pure checkpoint, only the required field are dump to file + virtual void + save_solution(bool pure_checkpoint); + + //! main routine to dump solution to file. + //! \sa save_solution + virtual void + save_solution_impl(bool pure_checkpoint); + + //! main routine to dump p4est mesh to file (for checkpoint / restart) + //! + //! \param[in] filename name of the p4est mesh file + //! \param[in] forest p4est main data structure + template + void + save_p4est_mesh(std::string filename, typename kalypsso::p4est::Wrapper::forest_t * forest) + { + static const int save_data = 0; + static const int save_partition = 0; + + if constexpr (dim_ == 2) + { + p4est::Wrapper<2>::save_ext(filename.c_str(), forest, save_data, save_partition); + } + else if constexpr (dim_ == 3) + { + p4est::Wrapper<3>::save_ext(filename.c_str(), forest, save_data, save_partition); + } + } + + //! print monitoring information + virtual void + print_monitoring_info(); + + //! print monitoring information after final timep step + virtual void + print_monitoring_info_final(); + + //! helper to register volume integrals + virtual void + register_volume_integrals(bool is_reference); + + //! print conservativity check report + virtual void + print_conservativity_check_report() const; + + + /* IO related */ + + //! counter incremented each time an output is written + int m_times_saved; + + //! counter incremented each time a restart file is written + int m_times_saved_restart; + +}; // class SolverBase + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_SOLVER_BASE_H_ diff --git a/src/kalypsso/core/StencilHelper.cpp b/src/kalypsso/core/StencilHelper.cpp new file mode 100644 index 0000000..ba50186 --- /dev/null +++ b/src/kalypsso/core/StencilHelper.cpp @@ -0,0 +1,2244 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file StencilHelper.cpp + */ +#include +#include +#include + +namespace kalypsso +{ + +// ==================================================================== +// ==================================================================== +template +KOKKOS_FUNCTION CellEdgeLocation +get_CellEdgeLocation(EdgeLocation const & edge_loc, block_size_t const & block_size) +{ + + if constexpr (dim == 2) + { + KOKKOS_ASSERT((edge_loc.ijk[dim] == IZ) && "Wrong edge direction (must be along Z in 2d)"); + + int res = 0; + + if (edge_loc.ijk[IX] == block_size[IX]) + res += 1; + if (edge_loc.ijk[IY] == block_size[IY]) + res += 2; + return static_cast(res); + } + else if constexpr (dim == 3) + { + if (edge_loc.ijk[dim] == IZ) + { + int res = 0; + + if (edge_loc.ijk[IX] == block_size[IX]) + res += 1; + if (edge_loc.ijk[IY] == block_size[IY]) + res += 2; + return static_cast(res); + } + else if (edge_loc.ijk[dim] == IX) + { + int res = 0; + + if (edge_loc.ijk[IY] == block_size[IY]) + res += 1; + if (edge_loc.ijk[IZ] == block_size[IZ]) + res += 2; + return static_cast(res); + } + else if (edge_loc.ijk[dim] == IY) + { + int res = 0; + + if (edge_loc.ijk[IX] == block_size[IX]) + res += 1; + if (edge_loc.ijk[IZ] == block_size[IZ]) + res += 2; + return static_cast(res); + } + return EDGE_INVALID; + } // dim == 3 + +} // get_CellEdgeLocation + +template KOKKOS_FUNCTION CellEdgeLocation +get_CellEdgeLocation(EdgeLocation<2> const & edge_loc, block_size_t<2> const & block_size); +template KOKKOS_FUNCTION CellEdgeLocation +get_CellEdgeLocation(EdgeLocation<3> const & edge_loc, block_size_t<3> const & block_size); + +// ==================================================================== +// ==================================================================== +template +StencilHelper::StencilHelper(amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + block_size_t block_sizes, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic) + : m_amr_hashmap_device(amr_hashmap) + , m_orchard_keys_device(orchard_keys) + , m_block_sizes(block_sizes) + , m_brick_sizes(brick_sizes) + , m_is_brick_periodic(is_brick_periodic) +{} // StencilHelper::StencilHelper + +// ========================================================================================= +// ========================================================================================= +template +KOKKOS_FUNCTION bool +StencilHelper::is_cell_location_at_domain_border( + CellLocation_t const & cell_loc) const +{ + const auto & cell_block_sizes = m_block_sizes; + + if (orchard_key_t::is_at_domain_border(cell_loc.key, Face::XMIN, m_brick_sizes) and + (cell_loc.ijk[IX] == 0)) + return true; + + if (orchard_key_t::is_at_domain_border(cell_loc.key, Face::XMAX, m_brick_sizes) and + cell_loc.ijk[IX] == (cell_block_sizes[IX] - 1)) + return true; + + if (orchard_key_t::is_at_domain_border(cell_loc.key, Face::YMIN, m_brick_sizes) and + (cell_loc.ijk[IY] == 0)) + return true; + + if (orchard_key_t::is_at_domain_border(cell_loc.key, Face::YMAX, m_brick_sizes) and + cell_loc.ijk[IY] == (cell_block_sizes[IY] - 1)) + return true; + + if constexpr (dim == 3) + { + if (orchard_key_t::is_at_domain_border(cell_loc.key, Face::ZMIN, m_brick_sizes) and + (cell_loc.ijk[IZ] == 0)) + return true; + + if (orchard_key_t::is_at_domain_border(cell_loc.key, Face::ZMAX, m_brick_sizes) and + cell_loc.ijk[IZ] == (cell_block_sizes[IZ] - 1)) + return true; + } + + return false; + +} // is_cell_location_at_domain_border + +// ========================================================================================= +// ========================================================================================= +template +KOKKOS_FUNCTION bool +StencilHelper::is_face_location_at_block_border( + FaceLocation_t const & face_loc) const +{ + const auto & cell_block_sizes = m_block_sizes; + auto const & ivar = face_loc.ijk[dim]; + + if (ivar == IX and (face_loc.ijk[IX] == 0 or face_loc.ijk[IX] == cell_block_sizes[IX])) + return true; + + if (ivar == IY and (face_loc.ijk[IY] == 0 or face_loc.ijk[IY] == cell_block_sizes[IY])) + return true; + + if constexpr (dim == 3) + { + if (ivar == IZ and (face_loc.ijk[IZ] == 0 or face_loc.ijk[IZ] == cell_block_sizes[IZ])) + return true; + } + + return false; + +} // is_face_location_at_block_border + +// ========================================================================================= +// ========================================================================================= +template +KOKKOS_FUNCTION bool +StencilHelper::is_edge_location_at_block_border( + EdgeLocation_t const & edge_loc) const +{ + const auto & cell_block_sizes = m_block_sizes; + auto const & edge_dir = edge_loc.ijk[dim]; + + if constexpr (dim == 2) + { + KOKKOS_ASSERT((edge_dir == IZ) && "Wrong edge direction"); + } + + // get transverse directions + const auto dir1 = (edge_dir + 1) % 3; + const auto dir2 = (edge_dir + 2) % 3; + + if (edge_loc.ijk[dir1] == 0 or edge_loc.ijk[dir1] == cell_block_sizes[dir1] or + edge_loc.ijk[dir2] == 0 or edge_loc.ijk[dir2] == cell_block_sizes[dir2]) + return true; + + return false; + +} // is_edge_location_at_block_border + +// ========================================================================================= +// ========================================================================================= +template +KOKKOS_FUNCTION bool +StencilHelper::is_edge_location_at_domain_border( + EdgeLocation_t const & edge_loc) const +{ + const auto & cell_block_sizes = m_block_sizes; + + if (orchard_key_t::is_at_domain_border(edge_loc.key, Face::XMIN, m_brick_sizes) and + (edge_loc.ijk[dim] != IX) and (edge_loc.ijk[IX] == 0)) + return true; + + if (orchard_key_t::is_at_domain_border(edge_loc.key, Face::XMAX, m_brick_sizes) and + (edge_loc.ijk[dim] != IX) and (edge_loc.ijk[IX] == (cell_block_sizes[IX]))) + return true; + + if (orchard_key_t::is_at_domain_border(edge_loc.key, Face::YMIN, m_brick_sizes) and + (edge_loc.ijk[dim] != IY) and (edge_loc.ijk[IY] == 0)) + return true; + + if (orchard_key_t::is_at_domain_border(edge_loc.key, Face::YMAX, m_brick_sizes) and + (edge_loc.ijk[dim] != IY) and (edge_loc.ijk[IY] == (cell_block_sizes[IY]))) + return true; + + if constexpr (dim == 3) + { + if (orchard_key_t::is_at_domain_border(edge_loc.key, Face::ZMIN, m_brick_sizes) and + (edge_loc.ijk[dim] != IZ) and (edge_loc.ijk[IZ] == 0)) + return true; + + if (orchard_key_t::is_at_domain_border(edge_loc.key, Face::ZMAX, m_brick_sizes) and + (edge_loc.ijk[dim] != IZ) and (edge_loc.ijk[IZ] == (cell_block_sizes[IZ]))) + return true; + } + + return false; + +} // is_edge_location_at_domain_border + +// ========================================================================================= +// ========================================================================================= +template +KOKKOS_FUNCTION auto +StencilHelper::getNeighLoc(CellLocation const & cell_loc, + shift_t shift) const -> CellLocation_t +{ + + const auto & b = m_block_sizes; + + // beware signed values + coord_t coord_neigh; + coord_neigh[IX] = static_cast(cell_loc.ijk[IX]) + shift[IX]; + coord_neigh[IY] = static_cast(cell_loc.ijk[IY]) + shift[IY]; + if constexpr (dim == 3) + coord_neigh[IZ] = static_cast(cell_loc.ijk[IZ]) + shift[IZ]; + + // compute direction to neighbor octant + auto dir = []() { + if constexpr (dim == 2) + return Kokkos::Array{ 0, 0 }; + else if constexpr (dim == 3) + return Kokkos::Array{ 0, 0, 0 }; + }(); + + if (coord_neigh[IX] < 0) + dir[IX] = -1; + else if (coord_neigh[IX] >= static_cast(b[IX])) + dir[IX] = 1; + + if (coord_neigh[IY] < 0) + dir[IY] = -1; + else if (coord_neigh[IY] >= static_cast(b[IY])) + dir[IY] = 1; + + if constexpr (dim == 3) + { + if (coord_neigh[IZ] < 0) + dir[IZ] = -1; + else if (coord_neigh[IZ] >= static_cast(b[IZ])) + dir[IZ] = 1; + } + + auto dir_norm = dir[IX] * dir[IX] + dir[IY] * dir[IY]; + if constexpr (dim == 3) + dir_norm += dir[IZ] * dir[IZ]; + + // neighbor cell is inside current block (search is over) + if (dir_norm == 0) + { + // unsigned neighbor coordinate + coord_t coord_neigh_u; + coord_neigh_u[IX] = static_cast::value_type>(coord_neigh[IX]); + coord_neigh_u[IY] = static_cast::value_type>(coord_neigh[IY]); + if constexpr (dim == 3) + coord_neigh_u[IZ] = static_cast::value_type>(coord_neigh[IZ]); + + CellLocation_t cell_loc_neigh{ + coord_neigh_u, cell_loc.key, cell_loc.iOct, cell_loc.is_outside_domain + }; + + return cell_loc_neigh; + } + + // neighbor cell is outside current block + + // compute neighbor cell coord in the neighbor block + coord_t coord_neigh_u; + coord_neigh_u[IX] = static_cast::value_type>( + coord_neigh[IX] - b[IX] * (dir[IX] == 1) + b[IX] * (dir[IX] == -1)); + coord_neigh_u[IY] = static_cast::value_type>( + coord_neigh[IY] - b[IY] * (dir[IY] == 1) + b[IY] * (dir[IY] == -1)); + if constexpr (dim == 3) + { + coord_neigh_u[IZ] = static_cast::value_type>( + coord_neigh[IZ] - b[IZ] * (dir[IZ] == 1) + b[IZ] * (dir[IZ] == -1)); + } + + // + // check if neighbor cell belongs to a block at same level + // + + // get neighbor key at same level + auto key_neigh_same_level = orchard_key_t::get_neighbor_key_same_level( + cell_loc.key, dir, m_brick_sizes, m_is_brick_periodic); + + // check if neighbor key exists in hash map + auto key_neigh_hashindex = m_amr_hashmap_device.find(key_neigh_same_level); + auto is_at_same_level = m_amr_hashmap_device.valid_at(key_neigh_hashindex); + if (is_at_same_level) + { + auto iOct_neigh = m_amr_hashmap_device.value_at(key_neigh_hashindex); + + CellLocation_t cell_loc_neigh{ coord_neigh_u, key_neigh_same_level, iOct_neigh, false }; + + // check if neighbor is outside global domain + if (orchard_key_t::is_at_any_domain_border(cell_loc.key, m_brick_sizes)) + { + const auto normal = + orchard_key_t::get_outside_normal(cell_loc.key, m_brick_sizes, m_is_brick_periodic); + + auto prod_scal = normal[IX] * shift[IX] + normal[IY] * shift[IY]; + if constexpr (dim == 3) + prod_scal += normal[IZ] * shift[IZ]; + + // if this scalar prod is greater than zero it means we are looking for a neighbor that is + // really outside domain + // this scalar prod will be zero if we are at a periodic border, and then the neighbor will be + // found in the hashmap + if (prod_scal > 0) + { + cell_loc_neigh.is_outside_domain = true; + } + } + + return cell_loc_neigh; + } + + // + // check if neighbor cell belongs to a block at coarser level + // + auto key_neigh_coarser = orchard_key_t::father(key_neigh_same_level); + + key_neigh_hashindex = m_amr_hashmap_device.find(key_neigh_coarser); + + auto is_at_coarser_level = m_amr_hashmap_device.valid_at(key_neigh_hashindex); + + if (is_at_coarser_level) + { + auto iOct_neigh = m_amr_hashmap_device.value_at(key_neigh_hashindex); + + // get child id of current block (needed to compute neighbor cell coordinates) + const auto child_id = orchard_key_t::child_id(key_neigh_same_level); + + coord_neigh_u[IX] = (coord_neigh_u[IX] + ((child_id & 0x1) >> 0) * b[IX]) / 2; + coord_neigh_u[IY] = (coord_neigh_u[IY] + ((child_id & 0x2) >> 1) * b[IY]) / 2; + if constexpr (dim == 3) + { + coord_neigh_u[IZ] = (coord_neigh_u[IZ] + ((child_id & 0x4) >> 2) * b[IZ]) / 2; + } + + CellLocation_t cell_loc_neigh{ coord_neigh_u, key_neigh_coarser, iOct_neigh, false }; + return cell_loc_neigh; + } + + // + // check if neighbor cell belongs to a block at finer level + // + { + coord_neigh_u = coord_neigh_u * 2; + + // determine in which finer octant we need to use (i.e. determine child id) + uint8_t child_id = 0; + if (coord_neigh_u[IX] >= b[IX]) + { + coord_neigh_u[IX] -= b[IX]; + child_id += 1; + } + if (coord_neigh_u[IY] >= b[IY]) + { + coord_neigh_u[IY] -= b[IY]; + child_id += 2; + } + if constexpr (dim == 3) + if (coord_neigh_u[IZ] >= b[IZ]) + { + coord_neigh_u[IZ] -= b[IZ]; + child_id += 4; + } + + // lookup for this child in the hashmap + const auto key_neigh_fine = orchard_key_t::child(key_neigh_same_level, child_id); + + const auto key_neigh_fine_hashindex = m_amr_hashmap_device.find(key_neigh_fine); + const auto valid = m_amr_hashmap_device.valid_at(key_neigh_fine_hashindex); + + if (valid) + { + auto iOct_neigh = m_amr_hashmap_device.value_at(key_neigh_fine_hashindex); + + CellLocation_t cell_loc_neigh{ coord_neigh_u, key_neigh_fine, iOct_neigh, false }; + return cell_loc_neigh; + } + } // end search for neighbor at finer level + + // if we are here, we have a genuine problem/bug !!! + + // default : return "self" as invalid value here + return cell_loc; + +} // StencilHelper::getNeighLoc - cell + +// ========================================================================================= +// ========================================================================================= +template +KOKKOS_FUNCTION auto +StencilHelper::getNeighLoc(FaceLocation const & face_loc, + shift_t shift) const -> FaceLocation_t +{ + + const auto & cell_block_sizes = m_block_sizes; + auto face_block_sizes = cell_block_sizes; + auto const & ivar = face_loc.ijk[dim]; + if constexpr (dim == 2) + { + if (ivar < 2) + face_block_sizes[ivar] += 1; + } + else if constexpr (dim == 3) + { + face_block_sizes[ivar] += 1; + } + + // beware signed values + face_multiindex_t coord_neigh; + coord_neigh[IX] = static_cast(face_loc.ijk[IX]) + shift[IX]; + coord_neigh[IY] = static_cast(face_loc.ijk[IY]) + shift[IY]; + if constexpr (dim == 3) + { + coord_neigh[IZ] = static_cast(face_loc.ijk[IZ]) + shift[IZ]; + } + coord_neigh[dim] = face_loc.ijk[dim]; + + // compute direction to neighbor octant + auto dir = []() { + if constexpr (dim == 2) + return Kokkos::Array{ 0, 0 }; + else if constexpr (dim == 3) + return Kokkos::Array{ 0, 0, 0 }; + }(); + + if (coord_neigh[IX] < 0) + dir[IX] = -1; + else if (coord_neigh[IX] >= static_cast(face_block_sizes[IX])) + dir[IX] = 1; + + if (coord_neigh[IY] < 0) + dir[IY] = -1; + else if (coord_neigh[IY] >= static_cast(face_block_sizes[IY])) + dir[IY] = 1; + + if constexpr (dim == 3) + { + if (coord_neigh[IZ] < 0) + dir[IZ] = -1; + else if (coord_neigh[IZ] >= static_cast(face_block_sizes[IZ])) + dir[IZ] = 1; + } + + auto dir_norm = dir[IX] * dir[IX] + dir[IY] * dir[IY]; + if constexpr (dim == 3) + dir_norm += dir[IZ] * dir[IZ]; + + // neighbor cell is inside current block (search is over) + if (dir_norm == 0) + { + // unsigned neighbor coordinate + face_multiindex_t coord_neigh_u; + coord_neigh_u[IX] = static_cast::value_type>(coord_neigh[IX]); + coord_neigh_u[IY] = static_cast::value_type>(coord_neigh[IY]); + if constexpr (dim == 3) + { + coord_neigh_u[IZ] = static_cast::value_type>(coord_neigh[IZ]); + } + coord_neigh_u[dim] = coord_neigh[dim]; + + FaceLocation_t face_loc_neigh{ + coord_neigh_u, face_loc.key, face_loc.iOct, face_loc.is_outside_domain + }; + + return face_loc_neigh; + } + + // neighbor cell is outside current block + + // compute neighbor cell coord in the neighbor block + face_multiindex_t coord_neigh_u; + coord_neigh_u[IX] = static_cast::value_type>( + coord_neigh[IX] - cell_block_sizes[IX] * (dir[IX] == 1) + + cell_block_sizes[IX] * (dir[IX] == -1)); + coord_neigh_u[IY] = static_cast::value_type>( + coord_neigh[IY] - cell_block_sizes[IY] * (dir[IY] == 1) + + cell_block_sizes[IY] * (dir[IY] == -1)); + if constexpr (dim == 3) + { + coord_neigh_u[IZ] = static_cast::value_type>( + coord_neigh[IZ] - cell_block_sizes[IZ] * (dir[IZ] == 1) + + cell_block_sizes[IZ] * (dir[IZ] == -1)); + } + coord_neigh_u[dim] = coord_neigh[dim]; + + // + // check if neighbor cell belongs to a block at same level + // + + // get neighbor key at same level + auto key_neigh_same_level = orchard_key_t::get_neighbor_key_same_level( + face_loc.key, dir, m_brick_sizes, m_is_brick_periodic); + + // check if neighbor key exists in hash map + auto key_neigh_hashindex = m_amr_hashmap_device.find(key_neigh_same_level); + auto is_at_same_level = m_amr_hashmap_device.valid_at(key_neigh_hashindex); + if (is_at_same_level) + { + auto iOct_neigh = m_amr_hashmap_device.value_at(key_neigh_hashindex); + + FaceLocation_t face_loc_neigh{ coord_neigh_u, key_neigh_same_level, iOct_neigh, false }; + + // check if neighbor is outside global domain + if (orchard_key_t::is_at_any_domain_border(face_loc.key, m_brick_sizes)) + { + const auto normal = + orchard_key_t::get_outside_normal(face_loc.key, m_brick_sizes, m_is_brick_periodic); + + auto prod_scal = normal[IX] * shift[IX] + normal[IY] * shift[IY]; + if constexpr (dim == 3) + prod_scal += normal[IZ] * shift[IZ]; + + // if this scalar prod is greater than zero it means we are looking for a neighbor that is + // really outside domain + // this scalar prod will be zero if we are at a periodic border, and then the neighbor will be + // found in the hashmap + if (prod_scal > 0) + { + face_loc_neigh.is_outside_domain = true; + } + } + + return face_loc_neigh; + } + + // + // check if neighbor cell belongs to a block at coarser level + // + auto key_neigh_coarser = orchard_key_t::father(key_neigh_same_level); + + key_neigh_hashindex = m_amr_hashmap_device.find(key_neigh_coarser); + + auto is_at_coarser_level = m_amr_hashmap_device.valid_at(key_neigh_hashindex); + + if (is_at_coarser_level) + { + auto iOct_neigh = m_amr_hashmap_device.value_at(key_neigh_hashindex); + + // get child id of current block (needed to compute neighbor face coordinates) + const auto child_id = orchard_key_t::child_id(key_neigh_same_level); + + coord_neigh_u[IX] = (coord_neigh_u[IX] + ((child_id & 0x1) >> 0) * face_block_sizes[IX]) / 2; + coord_neigh_u[IY] = (coord_neigh_u[IY] + ((child_id & 0x2) >> 1) * face_block_sizes[IY]) / 2; + if constexpr (dim == 3) + { + coord_neigh_u[IZ] = (coord_neigh_u[IZ] + ((child_id & 0x4) >> 2) * face_block_sizes[IZ]) / 2; + } + + FaceLocation_t face_loc_neigh{ coord_neigh_u, key_neigh_coarser, iOct_neigh, false }; + return face_loc_neigh; + } + + // + // check if neighbor cell belongs to a block at finer level + // + { + coord_neigh_u[IX] = coord_neigh_u[IX] * 2; + coord_neigh_u[IY] = coord_neigh_u[IY] * 2; + if constexpr (dim == 3) + { + coord_neigh_u[IZ] = coord_neigh_u[IZ] * 2; + } + + // determine in which finer octant we need to use (i.e. determine child id) + uint8_t child_id = 0; + if (coord_neigh_u[IX] >= cell_block_sizes[IX]) + { + coord_neigh_u[IX] -= cell_block_sizes[IX]; + child_id += 1; + } + if (coord_neigh_u[IY] >= cell_block_sizes[IY]) + { + coord_neigh_u[IY] -= cell_block_sizes[IY]; + child_id += 2; + } + if constexpr (dim == 3) + { + if (coord_neigh_u[IZ] >= cell_block_sizes[IZ]) + { + coord_neigh_u[IZ] -= cell_block_sizes[IZ]; + child_id += 4; + } + } + + // lookup for this child in the hashmap + const auto key_neigh_fine = orchard_key_t::child(key_neigh_same_level, child_id); + + const auto key_neigh_fine_hashindex = m_amr_hashmap_device.find(key_neigh_fine); + const auto valid = m_amr_hashmap_device.valid_at(key_neigh_fine_hashindex); + + if (valid) + { + auto iOct_neigh = m_amr_hashmap_device.value_at(key_neigh_fine_hashindex); + FaceLocation_t face_loc_neigh{ coord_neigh_u, key_neigh_fine, iOct_neigh, false }; + return face_loc_neigh; + } + } // end search for neighbor at finer level + + // if we are here, we have a genuine problem/bug !!! + + // default : return "self" as invalid value here + return face_loc; + +} // StencilHelper::getNeighLoc - face + +// ========================================================================================= +// ========================================================================================= +template +KOKKOS_FUNCTION auto +StencilHelper::getNeighLocCoarser(CellLocation const & cell_loc, + shift_t shift) const -> CellLocation_t +{ + + const auto & b = m_block_sizes; + + // beware signed values + coord_t coord_neigh; + coord_neigh[IX] = static_cast(cell_loc.ijk[IX]) + shift[IX]; + coord_neigh[IY] = static_cast(cell_loc.ijk[IY]) + shift[IY]; + if constexpr (dim == 3) + coord_neigh[IZ] = static_cast(cell_loc.ijk[IZ]) + shift[IZ]; + + // compute direction to neighbor octant + auto dir = []() { + if constexpr (dim == 2) + return Kokkos::Array{ 0, 0 }; + else if constexpr (dim == 3) + return Kokkos::Array{ 0, 0, 0 }; + }(); + + if (coord_neigh[IX] < 0) + dir[IX] = -1; + else if (coord_neigh[IX] >= static_cast(b[IX])) + dir[IX] = 1; + + if (coord_neigh[IY] < 0) + dir[IY] = -1; + else if (coord_neigh[IY] >= static_cast(b[IY])) + dir[IY] = 1; + + if constexpr (dim == 3) + { + if (coord_neigh[IZ] < 0) + dir[IZ] = -1; + else if (coord_neigh[IZ] >= static_cast(b[IZ])) + dir[IZ] = 1; + } + + [[maybe_unused]] const auto dir_norm = [&dir]() { + if constexpr (dim == 2) + return dir[IX] * dir[IX] + dir[IY] * dir[IY]; + else if constexpr (dim == 3) + return dir[IX] * dir[IX] + dir[IY] * dir[IY] + dir[IZ] * dir[IZ]; + }(); + + // dir_norm can't be 0, neighbor cell is necessarily outside current block + KOKKOS_ASSERT(dir_norm > 0); + + // compute neighbor cell coord in the neighbor block + coord_t coord_neigh_u; + coord_neigh_u[IX] = static_cast::value_type>( + coord_neigh[IX] - b[IX] * (dir[IX] == 1) + b[IX] * (dir[IX] == -1)); + coord_neigh_u[IY] = static_cast::value_type>( + coord_neigh[IY] - b[IY] * (dir[IY] == 1) + b[IY] * (dir[IY] == -1)); + if constexpr (dim == 3) + { + coord_neigh_u[IZ] = static_cast::value_type>( + coord_neigh[IZ] - b[IZ] * (dir[IZ] == 1) + b[IZ] * (dir[IZ] == -1)); + } + + // note that neighbor cell can't be in an outside block (because face neighbor that are outside + // are necessarily conform by design, so they can't be at coarser level) + + // get neighbor key at same level + const auto key_neigh_same_level = orchard_key_t::get_neighbor_key_same_level( + cell_loc.key, dir, m_brick_sizes, m_is_brick_periodic); + + const auto key_neigh_coarser = orchard_key_t::father(key_neigh_same_level); + + const auto key_neigh_hashindex = m_amr_hashmap_device.find(key_neigh_coarser); + + // neighbor MUST be at coarser level, if not it means + // - either current function is called without checking neighbor is coarser + // - either we have a genuine bug + [[maybe_unused]] const auto is_at_coarser_level = + m_amr_hashmap_device.valid_at(key_neigh_hashindex); + KOKKOS_ASSERT(is_at_coarser_level); + + const auto iOct_neigh = m_amr_hashmap_device.value_at(key_neigh_hashindex); + + // get child id of current block (needed to compute neighbor cell coordinates) + const auto child_id = orchard_key_t::child_id(key_neigh_same_level); + + coord_neigh_u[IX] = (coord_neigh_u[IX] + ((child_id & 0x1) >> 0) * b[IX]) / 2; + coord_neigh_u[IY] = (coord_neigh_u[IY] + ((child_id & 0x2) >> 1) * b[IY]) / 2; + if constexpr (dim == 3) + { + coord_neigh_u[IZ] = (coord_neigh_u[IZ] + ((child_id & 0x4) >> 2) * b[IZ]) / 2; + } + + CellLocation_t cell_loc_neigh{ coord_neigh_u, key_neigh_coarser, iOct_neigh, false }; + + return cell_loc_neigh; + +} // StencilHelper::getNeighLocCoarser + +// ========================================================================================= +// ========================================================================================= +template +KOKKOS_FUNCTION auto +StencilHelper::getNeighLocFiner(CellLocation const & cell_loc, + shift_t shift) const -> CellLocation_t +{ + + const auto & b = m_block_sizes; + + // beware signed values + coord_t coord_neigh; + coord_neigh[IX] = static_cast(cell_loc.ijk[IX]) + shift[IX]; + coord_neigh[IY] = static_cast(cell_loc.ijk[IY]) + shift[IY]; + if constexpr (dim == 3) + coord_neigh[IZ] = static_cast(cell_loc.ijk[IZ]) + shift[IZ]; + + // compute direction to neighbor octant + auto dir = []() { + if constexpr (dim == 2) + return Kokkos::Array{ 0, 0 }; + else if constexpr (dim == 3) + return Kokkos::Array{ 0, 0, 0 }; + }(); + + if (coord_neigh[IX] < 0) + dir[IX] = -1; + else if (coord_neigh[IX] >= static_cast(b[IX])) + dir[IX] = 1; + + if (coord_neigh[IY] < 0) + dir[IY] = -1; + else if (coord_neigh[IY] >= static_cast(b[IY])) + dir[IY] = 1; + + if constexpr (dim == 3) + { + if (coord_neigh[IZ] < 0) + dir[IZ] = -1; + else if (coord_neigh[IZ] >= static_cast(b[IZ])) + dir[IZ] = 1; + } + + [[maybe_unused]] const auto dir_norm = [&dir]() { + if constexpr (dim == 2) + return dir[IX] * dir[IX] + dir[IY] * dir[IY]; + else if constexpr (dim == 3) + return dir[IX] * dir[IX] + dir[IY] * dir[IY] + dir[IZ] * dir[IZ]; + }(); + + // dir_norm can't be 0, neighbor cell is necessarily outside current block + KOKKOS_ASSERT(dir_norm > 0); + + // compute neighbor cell coord in the neighbor block + coord_t coord_neigh_u; + coord_neigh_u[IX] = static_cast::value_type>( + coord_neigh[IX] - b[IX] * (dir[IX] == 1) + b[IX] * (dir[IX] == -1)); + coord_neigh_u[IY] = static_cast::value_type>( + coord_neigh[IY] - b[IY] * (dir[IY] == 1) + b[IY] * (dir[IY] == -1)); + if constexpr (dim == 3) + { + coord_neigh_u[IZ] = static_cast::value_type>( + coord_neigh[IZ] - b[IZ] * (dir[IZ] == 1) + b[IZ] * (dir[IZ] == -1)); + } + + // note that neighbor cell can't be in an outside block (because face neighbor that are outside + // are necessarily conform by design, so they can't be at finer level) + + coord_neigh_u = coord_neigh_u * 2; + + // determine in which finer octant we need to use (i.e. determine child id) + uint8_t child_id = 0; + if (coord_neigh_u[IX] >= b[IX]) + { + coord_neigh_u[IX] -= b[IX]; + child_id += 1; + } + if (coord_neigh_u[IY] >= b[IY]) + { + coord_neigh_u[IY] -= b[IY]; + child_id += 2; + } + if constexpr (dim == 3) + { + if (coord_neigh_u[IZ] >= b[IZ]) + { + coord_neigh_u[IZ] -= b[IZ]; + child_id += 4; + } + } + + // get neighbor key at same level + const auto key_neigh_same_level = orchard_key_t::get_neighbor_key_same_level( + cell_loc.key, dir, m_brick_sizes, m_is_brick_periodic); + + // lookup for this child in the hashmap + const auto key_neigh_fine = orchard_key_t::child(key_neigh_same_level, child_id); + + const auto key_neigh_hashindex = m_amr_hashmap_device.find(key_neigh_fine); + + // neighbor MUST be at coarser level, if not it means + // - either current function is called without checking neighbor is finer + // - either we have a genuine bug + [[maybe_unused]] const auto is_at_finer_level = + m_amr_hashmap_device.valid_at(key_neigh_hashindex); + KOKKOS_ASSERT(is_at_finer_level); + + const auto iOct_neigh = m_amr_hashmap_device.value_at(key_neigh_hashindex); + + CellLocation_t cell_loc_neigh{ coord_neigh_u, key_neigh_fine, iOct_neigh, false }; + + return cell_loc_neigh; + +} // StencilHelper::getNeighLocFiner + +// ========================================================================================= +// ========================================================================================= +template +KOKKOS_FUNCTION auto +StencilHelper::getNeighLocFinerNearer(CellLocation const & cell_loc, + shift_t shift) const -> CellLocation_t +{ + auto cell_loc_neigh = getNeighLocFiner(cell_loc, shift); + + if (shift[IX] < 0) + { + cell_loc_neigh.ijk[IX] += 1; + }; + + if (shift[IY] < 0) + { + cell_loc_neigh.ijk[IY] += 1; + }; + + if constexpr (dim == 3) + { + if (shift[IZ] < 0) + { + cell_loc_neigh.ijk[IZ] += 1; + }; + } + + return cell_loc_neigh; + +} // StencilHelper::getNeighLocFinerNearer + +// ========================================================================================= +// ========================================================================================= +template +KOKKOS_FUNCTION auto +StencilHelper::getBorderFaceLocSymmetric(FaceLocation const & face_loc) const + -> FaceLocation_t +{ + + const auto & cell_block_sizes = m_block_sizes; + auto const & ivar = face_loc.ijk[dim]; + + const auto cell_coord = face_to_cell_coords(face_loc.ijk, cell_block_sizes); + const CellLocation_t cell_loc_cur{ + cell_coord, face_loc.key, face_loc.iOct, face_loc.is_outside_domain + }; + + // check if face is at border + if (ivar == IX and face_loc.ijk[IX] == 0) + { + const auto cell_loc_neigh = getNeighLoc(cell_loc_cur, unit_shift(-XDIR)); + auto face_index_neigh = to_face_multiindex(cell_loc_neigh.ijk, ivar); + face_index_neigh[IX] = cell_block_sizes[IX]; + const FaceLocation_t sym_face_loc{ + face_index_neigh, cell_loc_neigh.key, cell_loc_neigh.iOct, cell_loc_neigh.is_outside_domain + }; + return sym_face_loc; + } + else if (ivar == IX and face_loc.ijk[IX] == cell_block_sizes[IX]) + { + const auto cell_loc_neigh = getNeighLoc(cell_loc_cur, unit_shift(+XDIR)); + auto face_index_neigh = to_face_multiindex(cell_loc_neigh.ijk, ivar); + face_index_neigh[IX] = 0; + const FaceLocation_t sym_face_loc{ + face_index_neigh, cell_loc_neigh.key, cell_loc_neigh.iOct, cell_loc_neigh.is_outside_domain + }; + return sym_face_loc; + } + else if (ivar == IY and face_loc.ijk[IY] == 0) + { + const auto cell_loc_neigh = getNeighLoc(cell_loc_cur, unit_shift(-YDIR)); + auto face_index_neigh = to_face_multiindex(cell_loc_neigh.ijk, ivar); + face_index_neigh[IY] = cell_block_sizes[IY]; + const FaceLocation_t sym_face_loc{ + face_index_neigh, cell_loc_neigh.key, cell_loc_neigh.iOct, cell_loc_neigh.is_outside_domain + }; + return sym_face_loc; + } + else if (ivar == IY and face_loc.ijk[IY] == cell_block_sizes[IY]) + { + const auto cell_loc_neigh = getNeighLoc(cell_loc_cur, unit_shift(+YDIR)); + auto face_index_neigh = to_face_multiindex(cell_loc_neigh.ijk, ivar); + face_index_neigh[IY] = 0; + const FaceLocation_t sym_face_loc{ + face_index_neigh, cell_loc_neigh.key, cell_loc_neigh.iOct, cell_loc_neigh.is_outside_domain + }; + return sym_face_loc; + } + + if constexpr (dim == 3) + { + if (ivar == IZ and face_loc.ijk[IZ] == 0) + { + const auto cell_loc_neigh = getNeighLoc(cell_loc_cur, unit_shift(-ZDIR)); + auto face_index_neigh = to_face_multiindex(cell_loc_neigh.ijk, ivar); + face_index_neigh[IZ] = cell_block_sizes[IZ]; + const FaceLocation_t sym_face_loc{ + face_index_neigh, cell_loc_neigh.key, cell_loc_neigh.iOct, cell_loc_neigh.is_outside_domain + }; + return sym_face_loc; + } + else if (ivar == IZ and face_loc.ijk[IZ] == cell_block_sizes[IZ]) + { + const auto cell_loc_neigh = getNeighLoc(cell_loc_cur, unit_shift(+ZDIR)); + auto face_index_neigh = to_face_multiindex(cell_loc_neigh.ijk, ivar); + face_index_neigh[IZ] = 0; + const FaceLocation_t sym_face_loc{ + face_index_neigh, cell_loc_neigh.key, cell_loc_neigh.iOct, cell_loc_neigh.is_outside_domain + }; + return sym_face_loc; + } + } + + FaceLocation_t default_face_loc = face_loc; + + return default_face_loc; + +} // getBorderFaceLocSymmetric + +// ========================================================================================= +// ========================================================================================= +template +KOKKOS_FUNCTION auto +StencilHelper::edge_to_cell_location(EdgeLocation const & edge_loc) const + -> CellLocation_t +{ + auto const & cell_block_sizes = m_block_sizes; + + const auto cell_coord = edge_to_cell_coords(edge_loc.ijk, cell_block_sizes); + + const CellLocation_t res{ cell_coord, edge_loc.key, edge_loc.iOct, edge_loc.is_outside_domain }; + + return res; +} + +// ========================================================================================= +// ========================================================================================= +template +KOKKOS_FUNCTION auto +StencilHelper::getBorderEdgeLocSymmetric(EdgeLocation const & edge_loc, + EdgeNormalType edge_normal_type) const + -> EdgeLocation_t +{ + + const auto & cell_block_sizes = m_block_sizes; + auto const & edge_dir = edge_loc.ijk[dim]; + + const auto cell_coord = edge_to_cell_coords(edge_loc.ijk, cell_block_sizes); + const CellLocation_t cell_loc_cur{ + cell_coord, edge_loc.key, edge_loc.iOct, edge_loc.is_outside_domain + }; + + // shift vector to neighbor block where symmetric edge is located + const auto out_shift = + get_edge_outside_unit_vector(edge_loc.ijk, cell_block_sizes, edge_normal_type); + + const auto cell_loc_neigh = getNeighLoc(cell_loc_cur, out_shift); + + // if neighbor is coarser, just return edge_loc itself + // + // currently, the main use of this routine is when neighbor it at same or finer level + // when neighbor is coarser, there may be no-colocated corresponding edge on the other side. + // + // \todo evaluate if something more useful could be returned here, if really needed + if (cell_loc_neigh.level() < cell_loc_cur.level()) + { + EdgeLocation_t sym_edge_loc = edge_loc; + return sym_edge_loc; + } + + auto edge_index_neigh = to_edge_multiindex(cell_loc_neigh.ijk, edge_dir); + + for (size_t idim = 0; idim < dim; ++idim) + { + if (out_shift[idim] < 0) + edge_index_neigh[idim] = cell_block_sizes[idim]; + else if (out_shift[idim] > 0) + edge_index_neigh[idim] = 0; + } + + // watch out inversion dir1 <==> dir2 + if (edge_normal_type == +EdgeNormalType::DIR1) + { + const auto dir2 = (edge_dir + 2) % 3; + if (edge_loc.ijk[dir2] == cell_block_sizes[dir2]) + edge_index_neigh[dir2] = cell_block_sizes[dir2]; + } + else if (edge_normal_type == +EdgeNormalType::DIR2) + { + const auto dir1 = (edge_dir + 1) % 3; + if (edge_loc.ijk[dir1] == cell_block_sizes[dir1]) + edge_index_neigh[dir1] = cell_block_sizes[dir1]; + } + + const EdgeLocation_t sym_edge_loc{ edge_index_neigh, + cell_loc_neigh.key, + cell_loc_neigh.iOct, + cell_loc_neigh.is_outside_domain, + true }; + + return sym_edge_loc; + +} // getBorderEdgeLocSymmetric + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION auto +StencilHelper::getEdgeSiblingLoc(EdgeLocation_t const & edge_loc_in, + int nth_shift) const -> EdgeLocation_t +{ + const auto cell_edge_location = get_CellEdgeLocation(edge_loc_in, m_block_sizes); + KOKKOS_ASSERT((cell_edge_location >= 0 and cell_edge_location < 4) && + "Wrong value for a CellEdgeLocation : must be integer in range [0,3]"); + + auto const & edge_dir = edge_loc_in.ijk[dim]; + const auto cell_shift = EdgeUtils::edge_neighbor_shift( + cell_edge_location, nth_shift, static_cast(edge_dir)); + + // get current cell location + const auto cell_loc = edge_to_cell_location(edge_loc_in); + + // get shifted cell location + const auto cell_loc2 = getNeighLoc(cell_loc, cell_shift); + + // if shifted cell is inside current octant, just return edge_loc_in + if (cell_loc2.iOct == edge_loc_in.iOct) + return edge_loc_in; + + // shifted cell is outside current octant, we need to compute the edge coordinates as seen from + // the shifted cell + + // get edge transverse directions for an edge along IZ + int dir1 = IX; + int dir2 = IY; + if (edge_dir == IX) + { + dir1 = IY; + dir2 = IZ; + } + else if (edge_dir == IY) + { + dir1 = IX; + dir2 = IZ; + } + + auto ijk_sibling = cell_loc2.ijk; + + // shift back to have "correct" edge index + if (cell_loc2.level() <= cell_loc.level()) + { + // neighbor cell is at same or coarser AMR level => shift by 1 + if (cell_shift[dir1] < 0 or (cell_shift[dir1] == 0 and + (cell_edge_location == EDGE_01 or cell_edge_location == EDGE_11))) + ijk_sibling[dir1]++; + if (cell_shift[dir2] < 0 or (cell_shift[dir2] == 0 and + (cell_edge_location == EDGE_10 or cell_edge_location == EDGE_11))) + ijk_sibling[dir2]++; + } + else + { + // neighbor cell is at finer AMR level => shift by 2 + if (cell_shift[dir1] < 0 or (cell_shift[dir1] == 0 and + (cell_edge_location == EDGE_01 or cell_edge_location == EDGE_11))) + ijk_sibling[dir1] += 2; + if (cell_shift[dir2] < 0 or (cell_shift[dir2] == 0 and + (cell_edge_location == EDGE_10 or cell_edge_location == EDGE_11))) + ijk_sibling[dir2] += 2; + } + + // check edge location validity: + // is edge touching a coarser neighbor through the middle of a face ? in that case edge location + // is invalid + auto is_edge_valid = true; + if (cell_loc2.level() < cell_loc.level()) + { + if ((edge_loc_in.ijk[dir1] == 0 or edge_loc_in.ijk[dir1] == m_block_sizes[dir1]) and + ((edge_loc_in.ijk[dir2] & 0x1) == 1)) + is_edge_valid = false; + + if ((edge_loc_in.ijk[dir2] == 0 or edge_loc_in.ijk[dir2] == m_block_sizes[dir2]) and + ((edge_loc_in.ijk[dir1] & 0x1) == 1)) + is_edge_valid = false; + } + + const auto edge_index_out = to_edge_multiindex(ijk_sibling, edge_dir); + + EdgeLocation_t edge_loc_out{ + edge_index_out, cell_loc2.key, cell_loc2.iOct, cell_loc2.is_outside_domain, is_edge_valid + }; + + return edge_loc_out; + +} // StencilHelper::getEdgeSiblingLoc + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION void +StencilHelper::getAllEdgeSiblingLoc(EdgeLocation_t const & edge_loc, + EdgeLocation_t & edge_loc0, + EdgeLocation_t & edge_loc1, + EdgeLocation_t & edge_loc2) const +{ + edge_loc0 = getEdgeSiblingLoc(edge_loc, 0); + edge_loc1 = getEdgeSiblingLoc(edge_loc, 1); + edge_loc2 = getEdgeSiblingLoc(edge_loc, 2); +} + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION real_t +StencilHelper::compute_siblings_average( + CellLocation const & cell_loc, + block_size_t const & block_sizes, + int ivar, + DataArrayBlock const & userdata) const +{ + auto const & b = block_sizes; + auto const & coord = cell_loc.ijk; + + KOKKOS_ASSERT(cell_loc.cellindex(userdata.block_size()) < userdata.num_cells() && + "compute_siblings_average: wrong cell_loc's cellindex"); + KOKKOS_ASSERT(cell_loc.iOct < userdata.num_quadrants() && + "compute_siblings_average: wrong cell_loc's octant id"); + KOKKOS_ASSERT(ivar < userdata.num_vars() && "compute_siblings_average: wrong var id"); + + + // get eldest siblings coordinates (all even coordinates) + coord_t coord0; + coord0[IX] = (coord[IX] / 2) * 2; + coord0[IY] = (coord[IY] / 2) * 2; + if constexpr (dim == 3) + coord0[IZ] = (coord[IZ] / 2) * 2; + + auto cell_index0 = coord_to_cellindex(coord0, b); + + real_t data = 0; + uint16_t nbCells = 1 << dim; + for (uint16_t i = 0; i < nbCells; ++i) + { + if constexpr (dim == 2) + { + // clang-format off + data += userdata(cell_index0 + + ((i & 0x1) >> 0) + + ((i & 0x2) >> 1) * b[IX], + ivar, cell_loc.iOct); + // clang-format on + } + else if constexpr (dim == 3) + { + // clang-format off + data += userdata(cell_index0 + + ((i & 0x1) >> 0) + + ((i & 0x2) >> 1) * b[IX] + + ((i & 0x4) >> 2) * b[IX] * b[IY], + ivar, + cell_loc.iOct); + // clang-format on + } + } + + return data / nbCells; + +} // StencilHelper::compute_siblings_average + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION real_t +StencilHelper::compute_siblings_average( + CellLocation const & cell_loc, + int ivar, + DataArrayGhostedBlock const & userdata) const +{ + // auto const & b = block_sizes; + auto const & coord = cell_loc.ijk; + + KOKKOS_ASSERT(cell_loc.cellindex(userdata.block_size()) < userdata.num_cells_inner() && + "compute_siblings_average: wrong cell_loc's cellindex"); + KOKKOS_ASSERT(cell_loc.iOct < userdata.num_quadrants() && + "compute_siblings_average: wrong cell_loc's octant id"); + KOKKOS_ASSERT(ivar < userdata.num_vars() && "compute_siblings_average: wrong var id"); + + + // get eldest siblings coordinates (all even coordinates) + coord_t coord0; + coord0[IX] = (coord[IX] / 2) * 2; + coord0[IY] = (coord[IY] / 2) * 2; + if constexpr (dim == 3) + coord0[IZ] = (coord[IZ] / 2) * 2; + + // auto cell_index0 = coord_to_cellindex(coord0, b); + auto const & i0 = coord0[IX]; + auto const & j0 = coord0[IY]; + + real_t data = 0; + uint16_t nbCells = 1 << dim; + + if constexpr (dim == 2) + { + + for (int jj = 0; jj < 2; ++jj) + { + for (int ii = 0; ii < 2; ++ii) + { + data += userdata(i0 + ii, j0 + jj, ivar, cell_loc.iOct); + } + } + } // dim == 2 + else if constexpr (dim == 3) + { + auto const & k0 = coord0[IZ]; + + for (int kk = 0; kk < 2; ++kk) + { + for (int jj = 0; jj < 2; ++jj) + { + for (int ii = 0; ii < 2; ++ii) + { + data += userdata(i0 + ii, j0 + jj, k0 + kk, ivar, cell_loc.iOct); + } + } + } + } // dim==3 + + return data / nbCells; + +} // StencilHelper::compute_siblings_average + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION real_t +StencilHelper::compute_siblings_average( + CellLocation const & cell_loc, + block_size_t const & block_sizes, + int ivar, + DataArrayBlockMultiVar_t const & userdata) const +{ + auto const & b = block_sizes; + auto const & coord = cell_loc.ijk; + + KOKKOS_ASSERT(cell_loc.cellindex(userdata.block_size()) < userdata.num_cells() && + "compute_siblings_average: wrong cell_loc's cellindex"); + KOKKOS_ASSERT(cell_loc.iOct < userdata.num_quadrants() && + "compute_siblings_average: wrong cell_loc's octant id"); + KOKKOS_ASSERT(ivar < userdata.num_vars(cell_loc.iOct) && + "compute_siblings_average: wrong var id"); + + + // get eldest siblings coordinates (all even coordinates) + coord_t coord0; + coord0[IX] = (coord[IX] / 2) * 2; + coord0[IY] = (coord[IY] / 2) * 2; + if constexpr (dim == 3) + coord0[IZ] = (coord[IZ] / 2) * 2; + + auto cell_index0 = coord_to_cellindex(coord0, b); + + real_t data = 0; + uint16_t nbCells = 1 << dim; + for (uint16_t i = 0; i < nbCells; ++i) + { + if constexpr (dim == 2) + { + // clang-format off + data += userdata(cell_index0 + + ((i & 0x1) >> 0) + + ((i & 0x2) >> 1) * b[IX], + ivar, cell_loc.iOct); + // clang-format on + } + else if constexpr (dim == 3) + { + // clang-format off + data += userdata(cell_index0 + + ((i & 0x1) >> 0) + + ((i & 0x2) >> 1) * b[IX] + + ((i & 0x4) >> 2) * b[IX] * b[IY], + ivar, + cell_loc.iOct); + // clang-format on + } + } + + return data / nbCells; + +} // StencilHelper::compute_siblings_average + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION real_t +StencilHelper::compute_face_siblings_average( + CellLocation const & cell_loc, + int ivar, + face_type_t ivar_face, + FaceDataArrayBlock_t const & Bface) const +{ + // only used in debug mode + [[maybe_unused]] const auto & b = m_block_sizes; + + auto const & coord = cell_loc.ijk; + + // get eldest siblings coordinates (all even coordinates) + const auto coord0 = [&coord]() { + if constexpr (dim == 2) + { + coord_t<2> res{ (coord[IX] / 2) * 2, (coord[IY] / 2) * 2 }; + return res; + } + else if constexpr (dim == 3) + { + coord_t<3> res{ (coord[IX] / 2) * 2, (coord[IY] / 2) * 2, (coord[IZ] / 2) * 2 }; + return res; + } + }(); + + const auto offset_x = (ivar == IX and ivar_face == face_type_t::RIGHT) ? 1 : 0; + const auto offset_y = (ivar == IY and ivar_face == face_type_t::RIGHT) ? 1 : 0; + const auto offset_z = (ivar == IZ and ivar_face == face_type_t::RIGHT) ? 1 : 0; + + real_t value = 0; + + // number of face siblings : + // - 2 in 2d + // - 4 in 3d + // that is 2^(dim-1) + // except along IZ direction, it is always 4 (2d and 3d) !! + const int num_face_siblings = (ivar == IZ) ? 4 : 1 << (dim - 1); + + if constexpr (dim == 2) + { + for (auto jj = 0; jj < 2; ++jj) + { + for (auto ii = 0; ii < 2; ++ii) + { + + KOKKOS_ASSERT((coord0[IX] + ii < b[IX]) && "Wrong values for coord0[IX]"); + KOKKOS_ASSERT((coord0[IY] + jj < b[IY]) && "Wrong values for coord0[IY]"); + + // compute corresponding cell index in current block of cells + const auto coord_sibling = coord_t<2>{ coord0[IX] + ii, coord0[IY] + jj }; + + // average over face siblings + // if ivar is IZ, we take them all + if ((ivar == IX and coord_sibling[IX] == coord[IX]) or + (ivar == IY and coord_sibling[IY] == coord[IY]) or (ivar == IZ)) + { + // clang-format off + value += + Bface(coord_sibling[IX] + offset_x, + coord_sibling[IY] + offset_y, + ivar, + cell_loc.iOct); + // clang-format on + } + } // for ii + } // for jj + } + else if constexpr (dim == 3) + { + for (auto kk = 0; kk < 2; ++kk) + { + for (auto jj = 0; jj < 2; ++jj) + { + for (auto ii = 0; ii < 2; ++ii) + { + + KOKKOS_ASSERT((coord0[IX] + ii < b[IX]) && "Wrong values for coord0[IX]"); + KOKKOS_ASSERT((coord0[IY] + jj < b[IY]) && "Wrong values for coord0[IY]"); + KOKKOS_ASSERT((coord0[IZ] + kk < b[IZ]) && "Wrong values for coord0[IZ]"); + + // compute corresponding cell index in current block of cells + const auto coord_sibling = + coord_t<3>{ coord0[IX] + ii, coord0[IY] + jj, coord0[IZ] + kk }; + + if ((ivar == IX and coord_sibling[IX] == coord[IX]) or + (ivar == IY and coord_sibling[IY] == coord[IY]) or + (ivar == IZ and coord_sibling[IZ] == coord[IZ])) + { + // clang-format off + value += Bface(coord_sibling[IX] + offset_x, + coord_sibling[IY] + offset_y, + coord_sibling[IZ] + offset_z, + ivar, + cell_loc.iOct); + // clang-format on + } + } // for ii + } // for jj + } // for kk + } + + return value / static_cast(num_face_siblings); + +} // StencilHelper::compute_face_siblings_average + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION real_t +StencilHelper::compute_face_siblings_average( + FaceLocation const & face_loc, + [[maybe_unused]] block_size_t const & cell_block_size, + int32_t ivar, + DataArrayBlock_t const & fluxes) const +{ + // check that fluxes is a flux array along the given face location (normal) + { + [[maybe_unused]] const auto dir = face_loc.ijk[dim]; + KOKKOS_ASSERT((fluxes.shape()[dir] == cell_block_size[dir] + 1) && "fluxes has wrong size"); + KOKKOS_ASSERT((fluxes.shape()[static_cast(dir + 1) % dim] == + cell_block_size[static_cast(dir + 1) % dim]) && + "fluxes has wrong size"); + if constexpr (dim == 3) + { + KOKKOS_ASSERT((fluxes.shape()[static_cast(dir + 2) % dim] == + cell_block_size[static_cast(dir + 2) % dim]) && + "fluxes has wrong size"); + } + } + + auto const & ijk = face_loc.ijk; + + auto const & dir = face_loc.ijk[dim]; + + real_t value = 0; + + // number of face siblings : + // - 2 in 2d + // - 4 in 3d + // that is 2^(dim-1) + const int num_face_siblings = 1 << (dim - 1); + + if constexpr (dim == 2) + { + // clang-format off + if (dir == IX) + { + const auto i0 = ijk[IX]; + const auto j0 = (ijk[IY] / 2) * 2; + + value += fluxes(i0, j0 , ivar, face_loc.iOct); + value += fluxes(i0, j0 + 1, ivar, face_loc.iOct); + } + else if (dir == IY) + { + const auto i0 = (ijk[IX] / 2) * 2; + const auto j0 = ijk[IY]; + + value += fluxes(i0 , j0, ivar, face_loc.iOct); + value += fluxes(i0 + 1, j0, ivar, face_loc.iOct); + } + // clang-format on + } + else if constexpr (dim == 3) + { + // clang-format off + if (dir == IX) + { + const auto i0 = ijk[IX]; + const auto j0 = (ijk[IY] / 2) * 2; + const auto k0 = (ijk[IZ] / 2) * 2; + value += fluxes(i0 , j0 , k0 , ivar, face_loc.iOct); + value += fluxes(i0 , j0 + 1, k0 , ivar, face_loc.iOct); + value += fluxes(i0 , j0 , k0 + 1, ivar, face_loc.iOct); + value += fluxes(i0 , j0 + 1, k0 + 1, ivar, face_loc.iOct); + } + else if (dir == IY) + { + const auto i0 = (ijk[IX] / 2) * 2; + const auto j0 = ijk[IY]; + const auto k0 = (ijk[IZ] / 2) * 2; + value += fluxes(i0 , j0 , k0 , ivar, face_loc.iOct); + value += fluxes(i0 + 1, j0 , k0 , ivar, face_loc.iOct); + value += fluxes(i0 , j0 , k0 + 1, ivar, face_loc.iOct); + value += fluxes(i0 + 1, j0 , k0 + 1, ivar, face_loc.iOct); + } + else if (dir == IZ) + { + const auto i0 = (ijk[IX] / 2) * 2; + const auto j0 = (ijk[IY] / 2) * 2; + const auto k0 = ijk[IZ]; + value += fluxes(i0 , j0 , k0 , ivar, face_loc.iOct); + value += fluxes(i0 + 1, j0 , k0 , ivar, face_loc.iOct); + value += fluxes(i0 , j0 + 1, k0 , ivar, face_loc.iOct); + value += fluxes(i0 + 1, j0 + 1, k0 , ivar, face_loc.iOct); + } + // clang-format on + } + + return value / static_cast(num_face_siblings); + +} // StencilHelper::compute_face_siblings_average + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION real_t +StencilHelper::compute_external_face_siblings_average( + CellLocation const & cell_loc, + int ivar, + face_type_t ivar_face, + FaceDataArrayBlock_t const & Bface) const +{ + // only used in debug mode + [[maybe_unused]] const auto & b = m_block_sizes; + + auto const & coord = cell_loc.ijk; + + KOKKOS_ASSERT(cell_loc.is_eldest_sibling() && "Wrong cell location; must be a eldest sibling."); + + const auto offset_x = (ivar == IX and ivar_face == face_type_t::RIGHT) ? 2 : 0; + const auto offset_y = (ivar == IY and ivar_face == face_type_t::RIGHT) ? 2 : 0; + const auto offset_z = (ivar == IZ and ivar_face == face_type_t::RIGHT) ? 2 : 0; + + real_t value = ZERO_F; + + // number of face siblings : + // - 2 in 2d + // - 4 in 3d + // that is 2^(dim-1) + // except along IZ direction, it is always 4 (2d and 3d) !! + const int num_face_siblings = (ivar == IZ) ? 4 : 1 << (dim - 1); + + if constexpr (dim == 2) + { + for (auto jj = 0; jj < 2; ++jj) + { + for (auto ii = 0; ii < 2; ++ii) + { + + KOKKOS_ASSERT((coord[IX] + ii < b[IX]) && "Wrong values for coord[IX]"); + KOKKOS_ASSERT((coord[IY] + jj < b[IY]) && "Wrong values for coord[IY]"); + + // compute corresponding cell index in current block of cells + const auto coord_sibling = coord_t<2>{ coord[IX] + ii, coord[IY] + jj }; + + // average over face siblings + // if ivar is IZ, we take them all + if ((ivar == IX and ii == 0) or (ivar == IY and jj == 0) or (ivar == IZ)) + { + // clang-format off + value += + Bface(coord_sibling[IX] + offset_x, + coord_sibling[IY] + offset_y, + ivar, + cell_loc.iOct); + // clang-format on + } + } // for ii + } // for jj + } + else if constexpr (dim == 3) + { + for (auto kk = 0; kk < 2; ++kk) + { + for (auto jj = 0; jj < 2; ++jj) + { + for (auto ii = 0; ii < 2; ++ii) + { + + KOKKOS_ASSERT((coord[IX] + ii < b[IX]) && "Wrong values for coord[IX]"); + KOKKOS_ASSERT((coord[IY] + jj < b[IY]) && "Wrong values for coord[IY]"); + KOKKOS_ASSERT((coord[IZ] + kk < b[IZ]) && "Wrong values for coord[IZ]"); + + // compute corresponding cell index in current block of cells + const auto coord_sibling = coord_t<3>{ coord[IX] + ii, coord[IY] + jj, coord[IZ] + kk }; + + if ((ivar == IX and ii == 0) or (ivar == IY and jj == 0) or (ivar == IZ and kk == 0)) + { + // clang-format off + value += Bface(coord_sibling[IX] + offset_x, + coord_sibling[IY] + offset_y, + coord_sibling[IZ] + offset_z, + ivar, + cell_loc.iOct); + // clang-format on + } + } // for ii + } // for jj + } // for kk + } + + return value / static_cast(num_face_siblings); + +} // StencilHelper::compute_external_face_siblings_average + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION real_t +StencilHelper::compute_face_siblings_average( + FaceLocation const & face_loc, + FaceDataArrayBlock_t const & Bface) const +{ + // only used in debug mode + [[maybe_unused]] const auto & b = m_block_sizes; + + auto const & ivar = face_loc.ijk[dim]; + + auto const & ijk = face_loc.ijk; + + real_t value = 0; + + // number of face siblings : + // - 2 in 2d + // - 4 in 3d + // that is 2^(dim-1) + // except along IZ direction, it is always 4 (2d and 3d) !! + const int num_face_siblings = (ivar == IZ) ? 4 : 1 << (dim - 1); + + if constexpr (dim == 2) + { + // clang-format off + if (ivar == IX) + { + const auto i0 = ijk[IX]; + const auto j0 = (ijk[IY] / 2) * 2; + + value += Bface(i0, j0 , ivar, face_loc.iOct); + value += Bface(i0, j0 + 1, ivar, face_loc.iOct); + } + else if (ivar == IY) + { + const auto i0 = (ijk[IX] / 2) * 2; + const auto j0 = ijk[IY]; + + value += Bface(i0 , j0, ivar, face_loc.iOct); + value += Bface(i0 + 1, j0, ivar, face_loc.iOct); + } + else if (ivar == IZ) + { + const auto i0 = (ijk[IX] / 2) * 2; + const auto j0 = (ijk[IY] / 2) * 2; + + value += Bface(i0 , j0 , ivar, face_loc.iOct); + value += Bface(i0 + 1, j0 , ivar, face_loc.iOct); + value += Bface(i0 , j0 + 1, ivar, face_loc.iOct); + value += Bface(i0 + 1, j0 + 1, ivar, face_loc.iOct); + } + // clang-format on + } + else if constexpr (dim == 3) + { + // clang-format off + if (ivar == IX) + { + const auto i0 = ijk[IX]; + const auto j0 = (ijk[IY] / 2) * 2; + const auto k0 = (ijk[IZ] / 2) * 2; + value += Bface(i0 , j0 , k0 , ivar, face_loc.iOct); + value += Bface(i0 , j0 + 1, k0 , ivar, face_loc.iOct); + value += Bface(i0 , j0 , k0 + 1, ivar, face_loc.iOct); + value += Bface(i0 , j0 + 1, k0 + 1, ivar, face_loc.iOct); + } + else if (ivar == IY) + { + const auto i0 = (ijk[IX] / 2) * 2; + const auto j0 = ijk[IY]; + const auto k0 = (ijk[IZ] / 2) * 2; + value += Bface(i0 , j0 , k0 , ivar, face_loc.iOct); + value += Bface(i0 + 1, j0 , k0 , ivar, face_loc.iOct); + value += Bface(i0 , j0 , k0 + 1, ivar, face_loc.iOct); + value += Bface(i0 + 1, j0 , k0 + 1, ivar, face_loc.iOct); + } + else if (ivar == IZ) + { + const auto i0 = (ijk[IX] / 2) * 2; + const auto j0 = (ijk[IY] / 2) * 2; + const auto k0 = ijk[IZ]; + value += Bface(i0 , j0 , k0 , ivar, face_loc.iOct); + value += Bface(i0 + 1, j0 , k0 , ivar, face_loc.iOct); + value += Bface(i0 , j0 + 1, k0 , ivar, face_loc.iOct); + value += Bface(i0 + 1, j0 + 1, k0 , ivar, face_loc.iOct); + } + // clang-format on + } + + return value / static_cast(num_face_siblings); + +} // StencilHelper::compute_face_siblings_average + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION real_t +StencilHelper::compute_face_siblings_sum(CellLocation_t const & cell_loc, + int ivar, + DataArrayBlock_t const & userdata, + int dir) const +{ + // this version is only for regular DataArrayBlock (not flux) + + if constexpr (dim == 2) + { + KOKKOS_ASSERT((dir == IX or dir == IY) && "Wrong direction"); + } + else if constexpr (dim == 3) + { + KOKKOS_ASSERT((dir == IX or dir == IY or dir == IZ) && "Wrong direction"); + } + + auto const & coord = cell_loc.ijk; + + // round down coordinates to a multiple of two (to get eldest sibling) + // in principle, this is useless, if cell_loc was obtained from a coarse quadrant, then + // shifted + // coord are a eldest sibling (even coordinates) + coord_t coord0; + coord0[IX] = (coord[IX] / 2) * 2; + coord0[IY] = (coord[IY] / 2) * 2; + if constexpr (dim == 3) + coord0[IZ] = (coord[IZ] / 2) * 2; + + // auto cell_index0 = coord_to_cellindex(coord0, b); + + real_t data = 0; + + auto flux_offset = init_kokkos_array(0); + // if (is_flux) + // { + // flux_offset[dir] = 1; + // } + + const uint16_t nbCells = (1 << (dim - 1)); + for (uint16_t i = 0; i < nbCells; ++i) + { + // index ii will span all siblings index matching a face orthogonal to direction "dir" + // in other words, in the following we swap bit "dir" the last dimension (IY in 2D, IZ in 3D) + uint16_t ii = swapBits(i, dim - 1, dir); + + // if coord[dir] is odd we are in a right face + // if coord[dir] is even we are in a left face + if (coord[dir] % 2 == 1) + ii += static_cast(1 << dir); + + if constexpr (dim == 2) + { + // data += userdata(cell_index0 + (ii & 0x1) + ((ii & 0x2) >> 1) * b[IX], ivar, + // cell_loc.iOct); + data += userdata(coord0[IX] + ((ii & 0x1) << 0) + flux_offset[IX], + coord0[IY] + ((ii & 0x2) >> 1) + flux_offset[IY], + ivar, + cell_loc.iOct); + } + else if constexpr (dim == 3) + { + // data += userdata(cell_index0 + (ii & 0x1) + ((ii & 0x2) >> 1) * b[IX] + + // ((ii & 0x4) >> 2) * b[IX] * b[IY], + // ivar, + // cell_loc.iOct); + data += userdata(coord0[IX] + ((ii & 0x1) >> 0) + flux_offset[IX], + coord0[IY] + ((ii & 0x2) >> 1) + flux_offset[IY], + coord0[IZ] + ((ii & 0x4) >> 2) + flux_offset[IZ], + ivar, + cell_loc.iOct); + } + } + + return data; + +} // StencilHelper::compute_face_siblings_sum + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION real_t +StencilHelper::compute_face_siblings_sum(CellLocation_t const & cell_loc, + int ivar, + DataArrayBlock_t const & userdata, + bool use_right_flux) const +{ + // determine flux direction + int dir = -1; + if (userdata.is_flux_array(m_block_sizes, IX)) + { + dir = IX; + } + if (userdata.is_flux_array(m_block_sizes, IY)) + { + dir = IY; + } + if constexpr (dim == 3) + { + if (userdata.is_flux_array(m_block_sizes, IZ)) + { + dir = IZ; + } + } + + // this version is only DataArrayBlock that are also flux array in direction dir + KOKKOS_ASSERT(dir >= 0 && "userdata is not a flux array"); + if (dir < 0) + { + // we could also abort; we should not be here + return 0; + } + + auto const & coord = cell_loc.ijk; + + // round down coordinates to a multiple of two (to get eldest sibling) + // in principle, this is useless, if cell_loc was obtained from a coarse quadrant, then + // shifted + // coord are a eldest sibling (even coordinates) + coord_t coord0; + coord0[IX] = (coord[IX] / 2) * 2; + coord0[IY] = (coord[IY] / 2) * 2; + if constexpr (dim == 3) + coord0[IZ] = (coord[IZ] / 2) * 2; + + // when using right flux (we must add an extra offset to shift index on the right) + auto flux_offset = init_kokkos_array(0); + if (use_right_flux) + { + flux_offset[dir] = 1; + } + + // return value + real_t data = 0; + + // perform sum + const uint16_t nbCells = (1 << (dim - 1)); + for (uint16_t i = 0; i < nbCells; ++i) + { + // index ii will span all siblings index matching a face orthogonal to direction "dir" + // in other words, in the following we swap bit "dir" the last dimension (IY in 2D, IZ in 3D) + uint16_t ii = swapBits(i, dim - 1, dir); + + // if coord[dir] is odd we are in a right face + // if coord[dir] is even we are in a left face + if (coord[dir] % 2 == 1) + ii += static_cast(1 << dir); + + if constexpr (dim == 2) + { + data += userdata(coord0[IX] + ((ii & 0x1) << 0) + flux_offset[IX], + coord0[IY] + ((ii & 0x2) >> 1) + flux_offset[IY], + ivar, + cell_loc.iOct); + } + else if constexpr (dim == 3) + { + data += userdata(coord0[IX] + ((ii & 0x1) >> 0) + flux_offset[IX], + coord0[IY] + ((ii & 0x2) >> 1) + flux_offset[IY], + coord0[IZ] + ((ii & 0x4) >> 2) + flux_offset[IZ], + ivar, + cell_loc.iOct); + } + } + + return data; + +} // StencilHelper::compute_face_siblings_sum + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION real_t +StencilHelper::compute_minmod_slopes(CellLocation_t const & cell_loc_cur, + CellLocation_t const & cell_loc_right, + CellLocation_t const & cell_loc_left, + int ivar, + DataArrayBlock_t const & userdata, + real_t slope_type) const +{ + KOKKOS_ASSERT((cell_loc_right.level() == cell_loc_cur.level() or + cell_loc_right.level() == cell_loc_cur.level() + 1) && + "neighbor must be finer or at same level"); + + KOKKOS_ASSERT((cell_loc_left.level() == cell_loc_cur.level() or + cell_loc_left.level() == cell_loc_cur.level() + 1) && + "neighbor must be finer or at same level"); + + auto const & bSize = userdata.block_size(); + + const real_t data_cur = userdata(cell_loc_cur.cellindex(bSize), ivar, cell_loc_cur.iOct); + + const real_t data_right = cell_loc_right.level() == cell_loc_cur.level() + ? userdata(cell_loc_right.cellindex(bSize), ivar, cell_loc_right.iOct) + : compute_siblings_average(cell_loc_right, bSize, ivar, userdata); + + const real_t data_left = cell_loc_left.level() == cell_loc_cur.level() + ? userdata(cell_loc_left.cellindex(bSize), ivar, cell_loc_left.iOct) + : compute_siblings_average(cell_loc_left, bSize, ivar, userdata); + + return minmod_scalar(data_cur, data_right, data_left, slope_type); + +} // StencilHelper::compute_minmod_slopes + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION real_t +StencilHelper::compute_minmod_slopes(CellLocation_t const & cell_loc_cur, + CellLocation_t const & cell_loc_right, + CellLocation_t const & cell_loc_left, + int ivar, + DataArrayGhostedBlock_t const & userdata, + real_t slope_type) const +{ + KOKKOS_ASSERT((cell_loc_right.level() == cell_loc_cur.level() or + cell_loc_right.level() == cell_loc_cur.level() + 1) && + "neighbor must be finer or at same level"); + + KOKKOS_ASSERT((cell_loc_left.level() == cell_loc_cur.level() or + cell_loc_left.level() == cell_loc_cur.level() + 1) && + "neighbor must be finer or at same level"); + + const real_t data_cur = userdata(cell_loc_cur.ijk, ivar, cell_loc_cur.iOct); + + const real_t data_right = cell_loc_right.level() == cell_loc_cur.level() + ? userdata(cell_loc_right.ijk, ivar, cell_loc_right.iOct) + : compute_siblings_average(cell_loc_right, ivar, userdata); + + const real_t data_left = cell_loc_left.level() == cell_loc_cur.level() + ? userdata(cell_loc_left.ijk, ivar, cell_loc_left.iOct) + : compute_siblings_average(cell_loc_left, ivar, userdata); + + return minmod_scalar(data_cur, data_right, data_left, slope_type); + +} // StencilHelper::compute_minmod_slopes + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION real_t +StencilHelper::compute_minmod_slopes(CellLocation_t const & cell_loc_cur, + int ivar_cur, + CellLocation_t const & cell_loc_right, + int ivar_right, + CellLocation_t const & cell_loc_left, + int ivar_left, + DataArrayBlockMultiVar_t const & userdata, + real_t slope_type) const +{ + KOKKOS_ASSERT((cell_loc_right.level() == cell_loc_cur.level() or + cell_loc_right.level() == cell_loc_cur.level() + 1) && + "neighbor must be finer or at same level"); + + KOKKOS_ASSERT((cell_loc_left.level() == cell_loc_cur.level() or + cell_loc_left.level() == cell_loc_cur.level() + 1) && + "neighbor must be finer or at same level"); + + auto const & bSize = userdata.block_size(); + + const real_t data_cur = userdata(cell_loc_cur.cellindex(bSize), ivar_cur, cell_loc_cur.iOct); + + const real_t data_right = + cell_loc_right.level() == cell_loc_cur.level() + ? userdata(cell_loc_right.cellindex(bSize), ivar_right, cell_loc_right.iOct) + : compute_siblings_average(cell_loc_right, bSize, ivar_right, userdata); + + const real_t data_left = + cell_loc_left.level() == cell_loc_cur.level() + ? userdata(cell_loc_left.cellindex(bSize), ivar_left, cell_loc_left.iOct) + : compute_siblings_average(cell_loc_left, bSize, ivar_left, userdata); + + return minmod_scalar(data_cur, data_right, data_left, slope_type); +} + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION real_t +StencilHelper::compute_minmod_slopes_prolongation( + CellLocation_t const & cell_loc_cur, + CellLocation_t const & cell_loc_right, + CellLocation_t const & cell_loc_left, + int ivar, + DataArrayBlock_t const & userdata, + real_t slope_type) const +{ + + auto const & bSize = userdata.block_size(); + + const real_t data_cur = userdata(cell_loc_cur.cellindex(bSize), ivar, cell_loc_cur.iOct); + + const real_t data_right = cell_loc_right.level() <= cell_loc_cur.level() + ? userdata(cell_loc_right.cellindex(bSize), ivar, cell_loc_right.iOct) + : compute_siblings_average(cell_loc_right, bSize, ivar, userdata); + + const real_t data_left = cell_loc_left.level() <= cell_loc_cur.level() + ? userdata(cell_loc_left.cellindex(bSize), ivar, cell_loc_left.iOct) + : compute_siblings_average(cell_loc_left, bSize, ivar, userdata); + + return minmod_scalar(data_cur, data_right, data_left, slope_type); +} + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION real_t +StencilHelper::compute_minmod_slopes_prolongation( + CellLocation_t const & cell_loc_cur, + int ivar_cur, + CellLocation_t const & cell_loc_right, + int ivar_right, + CellLocation_t const & cell_loc_left, + int ivar_left, + DataArrayBlockMultiVar_t const & userdata, + real_t slope_type) const +{ + + auto const & bSize = userdata.block_size(); + + const real_t data_cur = userdata(cell_loc_cur.cellindex(bSize), ivar_cur, cell_loc_cur.iOct); + + const real_t data_right = + cell_loc_right.level() <= cell_loc_cur.level() + ? userdata(cell_loc_right.cellindex(bSize), ivar_right, cell_loc_right.iOct) + : compute_siblings_average(cell_loc_right, bSize, ivar_right, userdata); + + const real_t data_left = + cell_loc_left.level() <= cell_loc_cur.level() + ? userdata(cell_loc_left.cellindex(bSize), ivar_left, cell_loc_left.iOct) + : compute_siblings_average(cell_loc_left, bSize, ivar_left, userdata); + + return minmod_scalar(data_cur, data_right, data_left, slope_type); +} + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION real_t +StencilHelper::compute_minmod_slopes_prolongation( + FaceLocation_t const & face_loc_cur, + FaceLocation_t const & face_loc_right, + FaceLocation_t const & face_loc_left, + FaceDataArrayBlock_t const & facedata) const +{ + auto const & ivar = face_loc_cur.ijk[dim]; + + if constexpr (dim == 2) + { + const real_t data_cur = + facedata(face_loc_cur.ijk[IX], face_loc_cur.ijk[IY], ivar, face_loc_cur.iOct); + + const real_t data_right = + face_loc_right.level() <= face_loc_cur.level() + ? facedata(face_loc_right.ijk[IX], face_loc_right.ijk[IY], ivar, face_loc_right.iOct) + : compute_face_siblings_average(face_loc_right, facedata); + + const real_t data_left = + face_loc_left.level() <= face_loc_cur.level() + ? facedata(face_loc_left.ijk[IX], face_loc_left.ijk[IY], ivar, face_loc_left.iOct) + : compute_face_siblings_average(face_loc_left, facedata); + + return minmod_scalar(data_cur, data_right, data_left, 1.0); + } + else if constexpr (dim == 3) + { + const real_t data_cur = facedata( + face_loc_cur.ijk[IX], face_loc_cur.ijk[IY], face_loc_cur.ijk[IZ], ivar, face_loc_cur.iOct); + + const real_t data_right = face_loc_right.level() <= face_loc_cur.level() + ? facedata(face_loc_right.ijk[IX], + face_loc_right.ijk[IY], + face_loc_right.ijk[IZ], + ivar, + face_loc_right.iOct) + : compute_face_siblings_average(face_loc_right, facedata); + + const real_t data_left = face_loc_left.level() <= face_loc_cur.level() + ? facedata(face_loc_left.ijk[IX], + face_loc_left.ijk[IY], + face_loc_left.ijk[IZ], + ivar, + face_loc_left.iOct) + : compute_face_siblings_average(face_loc_left, facedata); + + return minmod_scalar(data_cur, data_right, data_left, 1.0); + } +} // compute_minmod_slopes_prolongation + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION real_t +StencilHelper::compute_linear_combination(CellLocation_t const & cell_loc_left, + CellLocation_t const & cell_loc_cur, + CellLocation_t const & cell_loc_right, + uint8_t target_level, + int const ivar, + DataArrayBlock_t const & userdata, + Kokkos::Array const coef) const +{ + KOKKOS_ASSERT( + (cell_loc_left.level() == target_level or cell_loc_left.level() == target_level + 1) && + "neighbor must be finer or at same level"); + + KOKKOS_ASSERT( + (cell_loc_cur.level() == target_level or cell_loc_cur.level() == target_level + 1) && + "neighbor must be finer or at same level"); + + KOKKOS_ASSERT( + (cell_loc_right.level() == target_level or cell_loc_right.level() == target_level + 1) && + "neighbor must be finer or at same level"); + + + auto const & bSize = userdata.block_size(); + + const real_t data_cur = cell_loc_cur.level() == target_level + ? userdata(cell_loc_cur.cellindex(bSize), ivar, cell_loc_cur.iOct) + : compute_siblings_average(cell_loc_cur, bSize, ivar, userdata); + + const real_t data_right = cell_loc_right.level() == target_level + ? userdata(cell_loc_right.cellindex(bSize), ivar, cell_loc_right.iOct) + : compute_siblings_average(cell_loc_right, bSize, ivar, userdata); + + const real_t data_left = cell_loc_left.level() == target_level + ? userdata(cell_loc_left.cellindex(bSize), ivar, cell_loc_left.iOct) + : compute_siblings_average(cell_loc_left, bSize, ivar, userdata); + + return coef[0] * data_left + coef[1] * data_cur + coef[2] * data_right; + +} // StencilHelper::compute_linear_combination + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION real_t +StencilHelper::compute_linear_combination(CellLocation_t const & cell_loc_left2, + CellLocation_t const & cell_loc_left, + CellLocation_t const & cell_loc_cur, + CellLocation_t const & cell_loc_right, + CellLocation_t const & cell_loc_right2, + uint8_t target_level, + int const ivar, + DataArrayBlock_t const & userdata, + Kokkos::Array const coef) const +{ + KOKKOS_ASSERT( + (cell_loc_left2.level() == target_level or cell_loc_left2.level() == target_level + 1) && + "neighbor must be finer or at same level"); + + KOKKOS_ASSERT( + (cell_loc_left.level() == target_level or cell_loc_left.level() == target_level + 1) && + "neighbor must be finer or at same level"); + + KOKKOS_ASSERT( + (cell_loc_cur.level() == target_level or cell_loc_cur.level() == target_level + 1) && + "neighbor must be finer or at same level"); + + KOKKOS_ASSERT( + (cell_loc_right.level() == target_level or cell_loc_right.level() == target_level + 1) && + "neighbor must be finer or at same level"); + + KOKKOS_ASSERT( + (cell_loc_right2.level() == target_level or cell_loc_right2.level() == target_level + 1) && + "neighbor must be finer or at same level"); + + auto const & bSize = userdata.block_size(); + + const real_t data_left2 = cell_loc_left2.level() == target_level + ? userdata(cell_loc_left2.cellindex(bSize), ivar, cell_loc_left2.iOct) + : compute_siblings_average(cell_loc_left2, bSize, ivar, userdata); + + const real_t data_left = cell_loc_left.level() == target_level + ? userdata(cell_loc_left.cellindex(bSize), ivar, cell_loc_left.iOct) + : compute_siblings_average(cell_loc_left, bSize, ivar, userdata); + + const real_t data_cur = cell_loc_cur.level() == target_level + ? userdata(cell_loc_cur.cellindex(bSize), ivar, cell_loc_cur.iOct) + : compute_siblings_average(cell_loc_cur, bSize, ivar, userdata); + + const real_t data_right = cell_loc_right.level() == target_level + ? userdata(cell_loc_right.cellindex(bSize), ivar, cell_loc_right.iOct) + : compute_siblings_average(cell_loc_right, bSize, ivar, userdata); + + const real_t data_right2 = + cell_loc_right2.level() == target_level + ? userdata(cell_loc_right2.cellindex(bSize), ivar, cell_loc_right2.iOct) + : compute_siblings_average(cell_loc_right2, bSize, ivar, userdata); + + return coef[0] * data_left2 + coef[1] * data_left + coef[2] * data_cur + coef[3] * data_right + + coef[4] * data_right2; + +} // StencilHelper::compute_linear_combination + +// explicit template instantiation +template class StencilHelper<2, kalypsso::DefaultDevice>; +template class StencilHelper<3, kalypsso::DefaultDevice>; + +#ifdef KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE +template class StencilHelper<2, kalypsso::HostDevice>; +template class StencilHelper<3, kalypsso::HostDevice>; +#endif // KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE + +} // namespace kalypsso diff --git a/src/kalypsso/core/StencilHelper.h b/src/kalypsso/core/StencilHelper.h new file mode 100644 index 0000000..00603b5 --- /dev/null +++ b/src/kalypsso/core/StencilHelper.h @@ -0,0 +1,1046 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file StencilHelper.h + */ +#ifndef KALYPSSO_CORE_STENCILHELPER_H_ +#define KALYPSSO_CORE_STENCILHELPER_H_ + +#include +#include + +#include // for DataArray, DataArrayHost, DataArrayGhostedBlock +#include +#include +#include +#include +#include +#include + +#include + +#include // for definition of function cellindex_to_coord and coord_to_cellindex + +#include + +namespace kalypsso +{ + +// ==================================================================================== +// ==================================================================================== +// ==================================================================================== +class EdgeUtils +{ +private: + using edge_shift_t = Kokkos::Array; + + KOKKOS_FUNCTION static int + edge_shift_x(int i) + { + static constexpr edge_shift_t EDGE_SHIFT_X{ -1, 0, 1, -1, 0, 1, -1, 0, 1 }; + return EDGE_SHIFT_X[i]; + } + + KOKKOS_FUNCTION static int + edge_shift_y(int i) + { + const edge_shift_t EDGE_SHIFT_Y{ -1, -1, -1, 0, 0, 0, 1, 1, 1 }; + return EDGE_SHIFT_Y[i]; + } + + KOKKOS_FUNCTION static int + index_to_shift(int i) + { + // clang-format off + const Kokkos::Array INDEX_TO_SHIFT{ 0, 1, 3, /* neighbors of EDGE_00 */ + 1, 2, 5, /* neighbors of EDGE_01 */ + 3, 6, 7, /* neighbors of EDGE_10 */ + 5, 7, 8 }; /* neighbors of EDGE_11 */ + // clang-format on + return INDEX_TO_SHIFT[i]; + } + +public: + // ========================================================================================= + // ========================================================================================= + /** + * For a given edge, return shift to one of the 3 other cells that share this common edge. + * + * In 2d, an edge is always along Z axis (so its also a corner). + * + * An edge is identified by enum CellEdgeLocation. + * + * In the drawing below, we represent the example situation where edge is EDGE_00 (i.e. lower left + * corner), then the three shifts vector are + * (-1, -1), (0,-1) and (-1,0) given in that order so that the cells pointed to are in Morton + * order. + * ______ + * | | + * | | + * (-1,0) --- X_____| + * / | + * / | + * / | + * (-1,-1) (0,-1) + * + * \param[in] cell_edge_location + * \param[in] nth_shift integer indicating which shift user wants (must be O, 1, or 2; there are + * only possibilities) + * + */ + template + KOKKOS_FUNCTION static shift_t + edge_neighbor_shift(CellEdgeLocation cell_edge_location, int nth_shift, ComponentIndex3D edge_dir) + { + + if constexpr (dim == 2) + { + KOKKOS_ASSERT((nth_shift >= 0 and nth_shift <= 2) && + "nth_shift wrong value must be 0, 1 or 2."); + + const auto index = index_to_shift(3 * cell_edge_location + nth_shift); + + return shift_t<2>{ edge_shift_x(index), edge_shift_y(index) }; + } + else if constexpr (dim == 3) + { + KOKKOS_ASSERT((nth_shift >= 0 and nth_shift <= 2) && + "nth_shift wrong value must be 0, 1 or 2."); + + const auto index = index_to_shift(3 * cell_edge_location + nth_shift); + + if (edge_dir == IZ) + { + return shift_t<3>{ edge_shift_x(index), edge_shift_y(index), 0 }; + } + else if (edge_dir == IX) + { + return shift_t<3>{ 0, edge_shift_x(index), edge_shift_y(index) }; + } + else if (edge_dir == IY) + { + return shift_t<3>{ edge_shift_x(index), 0, edge_shift_y(index) }; + } + + return shift_t<3>{ 0, 0, 0 }; + } + + } // edge_neighbor_shift + +}; // class EdgeUtils + +// ========================================================================================= +// ========================================================================================= +/** + * For a given edge (in 3d) along edge_dir, return shift to one of the 3 other cells that share + * this common edge. + */ +template +KOKKOS_FUNCTION CellEdgeLocation +get_CellEdgeLocation(EdgeLocation const & edge_loc, block_size_t const & block_size); + +// ==================================================================================== +// ==================================================================================== +// ==================================================================================== +/** + * \class StencilHelper + * + * \tparam dim dimension + * \tparam device_t Kokkos device + * + * This a helper class for doing stencil operation without using block ghost, i.e. we provide direct + * access to neighbor values. + * + * We also provide default behavior for accessing cells outside domain. + */ +template +class StencilHelper +{ + +public: + using exec_space = typename device_t::execution_space; + using index_t = int32_t; + + using amr_hashmap_t = typename hashmap_base_t::map_t; + using orchard_key_view_t = typename orchard_key_base_t::view_t; + + using CellLocation_t = CellLocation; + using FaceLocation_t = FaceLocation; + using EdgeLocation_t = EdgeLocation; + + using DataArrayBlock_t = DataArrayBlock; + using DataArrayGhostedBlock_t = DataArrayGhostedBlock; + using FaceDataArrayBlock_t = FaceDataArrayBlock; + using DataArrayBlockMultiVar_t = DataArrayBlockMultiVar; + + // ==================================================================== + // ==================================================================== + //! constructor. + StencilHelper(amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + block_size_t block_sizes, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic); + + virtual ~StencilHelper() = default; + + KOKKOS_INLINE_FUNCTION + auto + key(iOct_t iOct) const + { + return m_orchard_keys_device(iOct); + } + + // ========================================================================================= + // ========================================================================================= + /** + * Return unit shift. + * + * -XDIR, +XDIR, -YDIR, +YDIR, -ZDIR, +ZDIR + */ + KOKKOS_INLINE_FUNCTION + auto + unit_shift(int dir) const + { + KOKKOS_ASSERT((dir >= -static_cast(dim) and dir <= static_cast(dim) and dir != 0) && + "WRONG value"); + + shift_t shift; + + for (int i = 1; i <= static_cast(dim); ++i) + { + if (dir == i) + shift[i - 1] = 1; + else if (dir == -i) + shift[i - 1] = -1; + else + shift[i - 1] = 0; + } + return shift; + } + + // ========================================================================================= + // ========================================================================================= + KOKKOS_FUNCTION + bool + is_cell_location_at_domain_border(CellLocation_t const & cell_loc) const; + + // ========================================================================================= + // ========================================================================================= + KOKKOS_FUNCTION + bool + is_face_location_at_block_border(FaceLocation_t const & face_loc) const; + + // ========================================================================================= + // ========================================================================================= + KOKKOS_FUNCTION bool + is_edge_location_at_block_border(EdgeLocation_t const & edge_loc) const; + + // ========================================================================================= + // ========================================================================================= + KOKKOS_FUNCTION bool + is_edge_location_at_domain_border(EdgeLocation_t const & edge_loc) const; + + // ========================================================================================= + // ========================================================================================= + KOKKOS_INLINE_FUNCTION + bool + is_brick_periodic(int dir) const + { + KOKKOS_ASSERT((dir >= 0 and dir < static_cast(dim)) && + "[StencilHelper::is_brick_periodic] wrong value for dir"); + return m_is_brick_periodic[dir]; + } + + // ========================================================================================= + // ========================================================================================= + /** + * Compute cell location of the neighbor of a given cell (from a given block) + * + * \param[in] cell_loc current cell location + * \param[in] shift is array of index shift in unit of current block cells (signed values) + * + * Note: + * - if shift points to a cell outside current block, we need to explore mesh to find neighbor + * octant first, then cell indexes inside the neighbor block. + * -- if neighbor octant is at same level, no problem, only one key and one cell to return + * -- if neighbor octant is coarser, we return the corresponding coarse cell coord + * -- if neighbor octant is finer, we return the cell coord of the first child (also called + * eldest) + * - if shift points to a cell outside domain (and p4est brick connectivity is non-periodic) then + * return the periodic cell location inside domain and set is_outside_domain to true + */ + KOKKOS_FUNCTION + auto + getNeighLoc(CellLocation_t const & cell_loc, shift_t shift) const -> CellLocation_t; + + // ========================================================================================= + // ========================================================================================= + /** + * Compute face location of the neighbor of a given face (from a given block) + * + * \param[in] face_loc current face location + * \param[in] shift is array of index shift in unit of current block cells (signed values) + * + * Note: + * - if shift points to a face outside current block, we need to explore mesh to find neighbor + * octant first, then face indexes inside the neighbor block. + * -- if neighbor octant is at same level, no problem, only one key and one face to return + * -- if neighbor octant is coarser, we return the corresponding coarse face coord + * -- if neighbor octant is finer, we return the face coord of the first child (also called + * eldest) + * - if shift points to a face outside domain (and p4est brick connectivity is non-periodic) then + * return the periodic face location inside domain and set is_outside_domain to true + */ + KOKKOS_FUNCTION + auto + getNeighLoc(FaceLocation_t const & face_loc, shift_t shift) const -> FaceLocation_t; + + // ========================================================================================= + // ========================================================================================= + /** + * Same as getNeighLoc but here the caller is sure that neighbor is coarser. + * + * Besides, this function is intended to be called from block border cells with a shift vector + * that leads to a coarser block. The caller of this function must have checked before hand that + * the neighbor is actually coarser (this information is available by looking at conformal status + * array, which itself is a member of MeshMap). + * + * Below is a 4x4 block of cells where outer border cells are flagged with a "x". + * + * ____________ + * |x |x |x |x | + * |__|__|__|__| + * |x | | |x | + * |__|__|__|__| + * |x | | |x | + * |__|__|__|__| + * |x |x |x | x| + * |__|__|__|__| + * + * + * \param[in] cell_loc current cell location + * \param[in] shift is array of index shift in unit of current block cells (signed values) + * + */ + KOKKOS_FUNCTION + auto + getNeighLocCoarser(CellLocation_t const & cell_loc, shift_t shift) const -> CellLocation_t; + + // ========================================================================================= + // ========================================================================================= + /** + * Same as getNeighLoc but here the caller is sure that neighbor is finer. + * + * Besides, this function is intended to be called from block border cells with a shift vector + * that leads to a finer block. The caller of this function must have checked before hand that + * the neighbor is actually finer (this information is available by looking at conformal status + * array, which itself is a member of MeshMap). + * + * \param[in] cell_loc current cell location + * \param[in] shift is array of index shift in unit of current block cells (signed values) + * + * \return a cell location to the eldest siblings in the correspond finer octant (in a local group + * a 2^dim cells). + */ + KOKKOS_FUNCTION + auto + getNeighLocFiner(CellLocation_t const & cell_loc, shift_t shift) const -> CellLocation_t; + + // ========================================================================================= + // ========================================================================================= + /** + * Same as getNeighLocFiner but we modify the returned cell loc to be nearer the original + * location. + * + * Given the location returned by getNeighLocFiner, we shift back the location in the opposite + * shift direction by one cell. + * + * \param[in] cell_loc current cell location + * \param[in] shift is array of index shift in unit of current block cells (signed values) + * + */ + KOKKOS_FUNCTION + auto + getNeighLocFinerNearer(CellLocation_t const & cell_loc, + shift_t shift) const -> CellLocation_t; + + // ========================================================================================= + // ========================================================================================= + /** + * Given a face location (corresponding to a block border), compute symmetric face location as + * seen from the other side. + * + * Some example situation using 4x4 block of cells. + * + * Example 1: + * - face location on the left block is is the input face location + * - face location on the right block is is the output face location + * + * ________ ________ + * |_|_|_|_| |_|_|_|_| + * |_|_|_|_| in out |_|_|_|_| + * |_|_|_|_| <--- ---> |_|_|_|_| + * |_|_|_|_| |_|_|_|_| + * + * \note if input face location is strictly inside block (not at block border), we return itself + * + * \param[in] face_loc current face location + * \return symmetric face location + */ + KOKKOS_FUNCTION + auto + getBorderFaceLocSymmetric(FaceLocation_t const & face_loc) const -> FaceLocation_t; + + // ========================================================================================= + // ========================================================================================= + /** + */ + KOKKOS_FUNCTION + auto + edge_to_cell_location(EdgeLocation_t const & edge_loc) const -> CellLocation_t; + + // ========================================================================================= + // ========================================================================================= + /** + * Given a edge location (at block border), compute symmetric edge location as + * seen from the other side assuming the other is either at same or finer AMR level; in all other + * cases, we return the input edge location as default invalid value. + * + * An example situation using 4x4 block of cells. + * Remember that in 2d, only edges along Z have a meaning, i.e. edge are cell corner in 2d. + * + * Example 1 in 2d: + * - edge location "x" on the left block is is the input face location + * - edge location on the right block is is the output face location + * + * + * Input block ________ + * ________________ |_|_|_|_| + * | | | | | |_|_|_|_| + * |___|___|___|___| |_|_|_|_| + * | | | | | #_|_|_|_| + * |___|___|___|___# _______ + * | | | | | |_|_|_|_| + * |___|___|___|___x => x_|_|_|_| + * | | | | | |_|_|_|_| + * |___|___|___|___o |_|_|_|_| + * + * - if input edge is also a block edge, e.g. at corner labeled with "o", there are 3 possible + * answers corresponding to the 3 other blocks touching "o". To choose which one is desired, we + * use enum EdgeNormalType. When there is only one possibility, this parameter is unused. + * - if input edge is exactly in the middle of a non-conformal face (e.g. node labeled with "#"), + * there are actually 2 possibles symmetric corresponding nodes: the upper-left corner of bottom + * right block, or the lower-left corner of upper block (also labeled with "#"), identified as the + * block with the highest octant id (greater in the Morton order sense). + * + * \param[in] edge_loc current edge location + * \param[in] edge_normal_type + * \return symmetric edge location + */ + KOKKOS_FUNCTION + auto + getBorderEdgeLocSymmetric(EdgeLocation_t const & edge_loc, + EdgeNormalType edge_normal_type = EdgeNormalType::DIAGONAL) const + -> EdgeLocation_t; + + // ========================================================================================= + // ========================================================================================= + /** + * Given an input edge location return one of the sibling edge location (sharing the same edge). + * + * In 2D, an edge is always shared by at most four siblings edge locations (see drawing below). + * If input edge location is at AMR level l, then the sibling edge locations (noted x0, x1 and x2) + * either live at level l, l+1 or l,l-1, but it can not be a mix between l-1, l and l+1 (because + * of 2:1 balance constraint). As we are looking for one of the 3 sibling edge locations, we must + * provide in input index 0, 1 or 2 (which enumerate the sibling in Morton order, see function + * EdgeUtils::edge_neighbor_shift). + * + * When an edge is a on a T (hanging face), with no corresponding siblings on the other side, + * there are only 2 siblings. In that case we return an edge location for which field is_valid is + * false. + * + * In 3D, there is a special case, if one of the neighbor cell is at finer AMR level (i.e. l+1), + * there are actually two possible edge locations that can be returned, here we always return + * the edge location corresponding to the lowest Morton index. + * + * Idea of the algorithm applied here: + * + * - step 1. convert the input edge location in to a pair (cell_location, cell_edge_location) + * note that: for all edge inside a block, we consider the edge belongs to the cell on its + * right, except the last edge on the block which belongs to the cell on its left (N cells / N+1 + * edges) + * - step 2. apply the cell shift + * - step 3. apply the edge mirroring and return the new edge location + * + * ______________________ + * | | | + * | | | + * | | | + * | | | + * | x | x2 | + * |__________|__________| + * | x0 | x1 | | + * | | | | + * | |_____|____| + * | | | | + * | | | | + * |__________|_____|____| + * + * \param[in] edge_loc_in input edge location (labeled "x") + * \param[in] nth_shift integer (0, 1 or 2) which identifies one of the 3 neighbor edge siblings + * location. + * + */ + KOKKOS_FUNCTION + auto + getEdgeSiblingLoc(EdgeLocation_t const & edge_loc_in, int nth_shift) const -> EdgeLocation_t; + + // ========================================================================================= + // ========================================================================================= + /** + * Given an edge location, this edge is shared by 4 cells, return the 3 other edge locations + * corresponding to the same edge but seen from the neighbor cells. + * + * Example situation: let's consider an edge shared by four cells, suppose in input, edge location + * labeled by "x" is known, and we want in output the 3 edge locations labeled "x0", "x1" and + * "x2", that determine in which octant they live, at which AMR level, ... the full geometrical + * information. + * + * ______________________ + * | | | + * | | | + * | | | + * | | | + * | x | x2 | + * |__________|__________| + * | x0 | x1 | | + * | | | | + * | |_____|____| + * | | | | + * | | | | + * |__________|_____|____| + * + * \note if the four cells sharing the input edge are in the same octant, obviously all edge + * locations will be identical. This routine is only really useful when at one of the edge + * location belong to different octant. + * + * \param[in] edge_loc current edge location + * \param[out] edge_loc0 current edge location + * \param[out] edge_loc1 current edge location + * \param[out] edge_loc2 current edge location + */ + KOKKOS_FUNCTION + void + getAllEdgeSiblingLoc(EdgeLocation_t const & edge_loc, + EdgeLocation_t & edge_loc0, + EdgeLocation_t & edge_loc1, + EdgeLocation_t & edge_loc2) const; + + // ============================================================== + // ============================================================== + //! compute average of all sibling cells given a cell location. + //! + //! the cell location must have a xyz field containing even integers; this is actually the case + //! when the cell location is returned by getNeighLoc for a neighbor at finer level. + //! + //! For example, given a 8x8 block grid, and given cell marked with "o", all the siblings are + //! marked with "x". The lower left sibling is called the eldest sibling. + //! _______________ + //! |_|_|_|_|_|_|_|_| + //! |_|_|_|_|_|_|_|_| + //! |_|_|_|_|_|_|_|_| + //! |_|_|_|_|_|_|_|_| + //! |_|_|x|o|_|_|_|_| + //! |_|_|x|x|_|_|_|_| + //! |_|_|_|_|_|_|_|_| + //! |_|_|_|_|_|_|_|_| + //! + //! + //! \param[in] cell_loc is current cell + //! \param[in] bloc_ksizes + //! \param[in] ivar identify which variable we want to average + //! \param[in] userdata + //! + //! \return average value + KOKKOS_FUNCTION real_t + compute_siblings_average(CellLocation_t const & cell_loc, + block_size_t const & block_sizes, + int ivar, + DataArrayBlock_t const & userdata) const; + + // ============================================================== + // ============================================================== + //! This does the same thing as 'compute_siblings_average' when input is a + //! 'DataArrayGhostedBlock'. + //! + //! \param[in] cell_loc is current cell + //! \param[in] bloc_ksizes + //! \param[in] ivar identify which variable we want to average + //! \param[in] userdata + //! + //! \return average value + KOKKOS_FUNCTION real_t + compute_siblings_average(CellLocation_t const & cell_loc, + int ivar, + DataArrayGhostedBlock_t const & userdata) const; + + // ============================================================== + // ============================================================== + //! This does the same thing as 'compute_siblings_average' with a 'DataArrayBlockMultiVar' + //! instead. + //! + //! \param[in] cell_loc is current cell + //! \param[in] bloc_ksizes + //! \param[in] ivar identify which variable we want to average + //! \param[in] userdata + //! + //! \return average value + KOKKOS_FUNCTION real_t + compute_siblings_average(CellLocation_t const & cell_loc, + block_size_t const & block_sizes, + int ivar, + DataArrayBlockMultiVar_t const & userdata) const; + + // ============================================================== + // ============================================================== + //! + //! compute face siblings average in a group of cell-sibling (same parent). + //! + //! In other words, given the coordinate of a given cell, let's consider its 2^dim neighborhood + //! (associated to virtual parent coarse cell). We perform the average of a given face field of + //! over all face siblings of this cell. + //! ____________ + //! | | | + //! | o | | + //! |_____|_____| + //! | | | + //! | x o | x | + //! |_____|_____| + //! + //! \note: + //! - the lower left cell marked with "x" and "o" is the eldest cell of all siblings in a block of + //! 2^dim cells. + //! - the two cells marked with "x" are face-siblings for face = YMIN + //! - the two cells marked with "o" are face-siblings for face = XMIN + //! + //! There are 2*dim distinct faces, hence 2*dim averages possible : XMIN/XMAX/YMIN/YMAX/ZMIN/ZMAX + //! + //! The implementation is cell-wise oriented because it is easier the reason about a block 2^dim + //! cells, rather than faces. + //! + //! \note We can only average X-component over X-siblings, Y-component over Y-siblings, ... + //! + //! Just to be clear, when, e.g. + //! - ivar=IX and ivar_face=LEFT, we are averaging the left x-face of the 2 cells marked with "o" + //! - ivar=IX and ivar_face=RIGHT, we are averaging the right x-face of the 2 cells marked with + //! "o" (i.e. the internal vertical faces of the 2^dim block of cells). + //! - ivar=IY and ivar_face=LEFT, we are averaging By over the left face of the 2 cells marked + //! with "x" + //! + //! \param[in] cell_loc contains the CELL location of one sibling. + //! \param[in] ivar variable id : IX, IY or IZ (even in 2d; magnetic field can have a Z component + //! in 2d) + //! \param[in] ivar_face : LEFT or RIGHT + //! \param[in] Bface a FaceDataArrayBlock_t container (e.g. magnetic field) + //! + //! \return average value + //! + KOKKOS_FUNCTION real_t + compute_face_siblings_average(CellLocation_t const & cell_loc, + int ivar, + face_type_t ivar_face, + FaceDataArrayBlock_t const & Bface) const; + + // ============================================================== + // ============================================================== + //! + //! Compute average of face variables on the left side (same parent). + //! + //! In other words, given the coordinate of a given cell, let's consider its 2^dim neighborhood + //! (associated to virtual parent coarse cell). We perform the average of a given face field of + //! over all face siblings of this cell. + //! ____________ + //! | | | + //! o | | + //! |_____|_____| + //! | | | + //! o | | + //! |__x__|__x__| + //! + //! + //! \param[in] face_loc contains the FACE location of the eldest sibling. + //! \param[in] cell_block_size array of number of cells along each direction. + //! \param[in] ivar is the variable identifying what should be averaged. + //! \param[in] data is a data array (assume to be a flux array). + //! + //! \note Remember that a flux array is an array which block sizes are one unit larger in one + //! direction that the cell block size. + //! As an example, consider a cell block of size (bx,by,bz). A flux array along Y direction must + //! be of size (bx,by+1,bz). + KOKKOS_FUNCTION real_t + compute_face_siblings_average(FaceLocation_t const & face_loc, + block_size_t const & cell_block_size, + int32_t ivar, + DataArrayBlock_t const & data) const; + + // ============================================================== + // ============================================================== + //! + //! compute face siblings average in a group of cell-sibling (same parent coarse cell). + //! + //! Here, the input cell location MUST be the eldest child (as given by the output of getNeighLoc + //! in case neighbor is finer); in other words the cell coordinates must be all even integers. + //! We perform the average of a given face field + //! all face siblings. + //! + //! ^ ^ + //! ___|_______|____ + //! | | | + //! | | | + //! |-> | |-> horizontal arrow : Bx + //! | | | vertical arrow : By + //! |_______|_______| + //! | | | + //! | | | + //! |-> E | |-> + //! | ^ | ^ | + //! |___|___|___|___| + //! + //! \note: + //! - the lower left cell marked with "E" is the eldest child of all siblings. + //! - we draw the face-centered arrow for the external faces + //! + //! There are 2*dim distinct faces, hence 2*dim averages possible : XMIN/XMAX/YMIN/YMAX/ZMIN/ZMAX + //! + //! \note We can only average X-component over X-siblings, Y-component over Y-siblings, ... + //! + //! + //! \param[in] cell_loc contains the CELL location of the eldest sibling (must be all even + //! integers). + //! \param[in] ivar variable id : IX, IY or IZ (even in 2d; magnetic field can have a Z component + //! in 2d) + //! \param[in] ivar_face : LEFT or RIGHT external face + //! \param[in] Bface a FaceDataArrayBlock_t container (e.g. magnetic field) + //! + //! \return average value + //! + KOKKOS_FUNCTION real_t + compute_external_face_siblings_average(CellLocation_t const & cell_loc, + int ivar, + face_type_t ivar_face, + FaceDataArrayBlock_t const & Bface) const; + + // ============================================================== + // ============================================================== + KOKKOS_FUNCTION real_t + compute_face_siblings_average(FaceLocation_t const & cell_loc, + FaceDataArrayBlock_t const & Bface) const; + + + // ============================================================== + // ============================================================== + //! compute sum over face neighbor values in a 2^dim cell mesh. + //! + //! Example situation: + //! - consider 4x4 block of cells, + //! - consider a cell located at i=3,j=2 (labeled with symbol "c"), + //! + //! One wants to sum data in "c" with its 2^(dim-1) face siblings; it 2d it amounts to suming "c" + //! with either data from "c1" on the left or from "c2" above. + //! + //! This function becomes interesting when "c" is a cell that touches the outer block limit, and + //! if the neighbor block is at a different AMR level (e.g. coarser level) + //! + //! ___________________________________________________________________________ ... + //! | | | | | | | + //! | | | | c2 | | | + //! | | | | | | | + //! |________|________|________|________| n | | + //! | | | | | | | + //! | | | c1 | c | | | + //! | | | | | | | + //! |________|________|________|________|_________________|_________________|___ ... + //! | | | | | | | + //! | | | | | | | + //! | | | | | | | + //! |________|________|________|________| | | + //! | | | | | | | + //! | | | | | | | + //! | | | | | | | + //! |________|________|________|________|_________________|_________________|____ ... + //! + //! + //! when the block on the right is at coarser level, we may need to update cell "n" by suming + //! fluxes data coming from cell "c" and "c2.". This is useful in a finite volume scheme, one + //! needs to sum all the fluxes at a non-conform interface. + //! + //! Code is written in a generic way to identify face-siblings in a 2^dim neighborhood. + //! + //! There are 2 interfaces to this function: + //! + //! \param[in] cell_loc (can be obtained by calling getNeighLoc, beware in case the output cell is + //! in a finer block, cell_loc will be populated with ijk coordinates of the eldest child of the + //! local 2^dim cell neighborhood). + //! + //! \param[in] ivar identifies which variable we want to average + //! + //! \param[in] userdata a DataArrayBlock (assumes same shapes as block_sizes, except in one + //! direction, where size is one more block_sizes) + //! + //! \param[in] direction is used to identify the face siblings participating to + //! the sum (it should be coherent with the shift used to find the neighbor octant) + //! In other words, direction can be IX, IY or IZ, it identifies a direction orthogonal + //! to the shift. + //! + //! \note important note, cell block sizes are deduced from userdata shape. Don't use this + //! interface when suming flux. + //! + //! \return sum value + KOKKOS_FUNCTION real_t + compute_face_siblings_sum(CellLocation_t const & cell_loc, + int ivar, + DataArrayBlock_t const & userdata, + int dir) const; + + + // ============================================================== + // ============================================================== + //! compute sum over face neighbor values in a DataArrayBlock that is a flux array in some + //! direction. + KOKKOS_FUNCTION real_t + compute_face_siblings_sum(CellLocation_t const & cell_loc, + int ivar, + DataArrayBlock_t const & userdata, + bool use_right_flux) const; + + // ============================================================== + // ============================================================== + //! evaluate minmod slopes with neighbor at finer or same AMR level. + //! + //! \param[in] cell_loc_cur current location + //! \param[in] cell_loc_right right location + //! \param[in] cell_loc_left left location + //! \param[in] ivar variable index + //! \param[in] userdata a DataArrayBlock used for computing slopes + //! \param[in] slope_type + //! + //! important note: + //! let l be the AMR level at current location. + //! We assume left and right neighbors locations are at level l or l+1 but NOT l-1 (with would + //! imply a prolongation). + KOKKOS_FUNCTION real_t + compute_minmod_slopes(CellLocation_t const & cell_loc_cur, + CellLocation_t const & cell_loc_right, + CellLocation_t const & cell_loc_left, + int ivar, + DataArrayBlock_t const & userdata, + real_t slope_type) const; + + // ============================================================== + // ============================================================== + //! evaluate minmod slopes with neighbor at finer or same AMR level. + //! + //! \param[in] cell_loc_cur current location + //! \param[in] cell_loc_right right location + //! \param[in] cell_loc_left left location + //! \param[in] ivar variable index + //! \param[in] userdata a DataArrayBlock used for computing slopes + //! \param[in] slope_type + //! + //! important note: + //! let l be the AMR level at current location. + //! We assume left and right neighbors locations are at level l or l+1 but NOT l-1 (with would + //! imply a prolongation). + KOKKOS_FUNCTION real_t + compute_minmod_slopes(CellLocation_t const & cell_loc_cur, + CellLocation_t const & cell_loc_right, + CellLocation_t const & cell_loc_left, + int ivar, + DataArrayGhostedBlock_t const & userdata, + real_t slope_type) const; + + // ============================================================== + // ============================================================== + //! Equivalent of 'compute_minmod_slopes' for 'DataArrayBlockMultiVar' + //! + //! \param[in] cell_loc_cur current location + //! \param[in] ivar_cur variable index of current + //! \param[in] cell_loc_right right location + //! \param[in] ivar_right variable index of right + //! \param[in] cell_loc_left left location + //! \param[in] ivar_left variable index of left + //! \param[in] userdata a DataArrayBlockMultiVar used for computing slopes + //! \param[in] slope_type + //! + KOKKOS_FUNCTION real_t + compute_minmod_slopes(CellLocation_t const & cell_loc_cur, + int ivar_cur, + CellLocation_t const & cell_loc_right, + int ivar_right, + CellLocation_t const & cell_loc_left, + int ivar_left, + DataArrayBlockMultiVar_t const & userdata, + real_t slope_type) const; + + // ============================================================== + // ============================================================== + //! evaluate minmod slopes without any level constraint. + //! + //! This variant is to be used when doing prolongation from a coarse level to fine level. + //! + //! \param[in] cell_loc_cur current location + //! \param[in] cell_loc_right right location + //! \param[in] cell_loc_left left location + //! \param[in] ivar variable index + //! \param[in] userdata a DataArrayBlock used for computing slopes + //! \param[in] slope_type + //! + //! important note: + //! let l be the AMR level at current location. + //! We assume left and right neighbors locations maybe at any level l-1,l,l+1. When neighbor is at + //! level l-1, it would it in principle require a prolongation, but then one would have a chicken + //! and egg problem. So it that case, we do a simple copy. + //! + KOKKOS_FUNCTION real_t + compute_minmod_slopes_prolongation(CellLocation_t const & cell_loc_cur, + CellLocation_t const & cell_loc_right, + CellLocation_t const & cell_loc_left, + int ivar, + DataArrayBlock_t const & userdata, + real_t slope_type) const; + + // ============================================================== + // ============================================================== + //! Equivalent of 'compute_minmod_slopes_prolongation' for 'DataArrayBlockMultiVar' + //! + //! \param[in] cell_loc_cur current location + //! \param[in] ivar_cur variable index of current + //! \param[in] cell_loc_right right location + //! \param[in] ivar_right variable index of right + //! \param[in] cell_loc_left left location + //! \param[in] ivar_left variable index of left + //! \param[in] userdata a DataArrayBlockMultiVar used for computing slopes + //! \param[in] slope_type + //! + KOKKOS_FUNCTION real_t + compute_minmod_slopes_prolongation(CellLocation_t const & cell_loc_cur, + int ivar_cur, + CellLocation_t const & cell_loc_right, + int ivar_right, + CellLocation_t const & cell_loc_left, + int ivar_left, + DataArrayBlockMultiVar_t const & userdata, + real_t slope_type) const; + + + // ============================================================== + // ============================================================== + //! evaluate minmod slopes with neighbor at finer or same AMR level. + //! + //! \param[in] face_loc_cur current location + //! \param[in] face_loc_right right location + //! \param[in] face_loc_left left location + //! \param[in] userdata a FaceDataArrayBlock used for computing slopes + //! + //! important note: + //! let l be the AMR level at current location. + //! We assume left and right neighbors locations maybe at any level l-1,l,l+1. When neighbor is at + //! level l-1, it would it in principle require a prolongation, but then one would have a chicken + //! and egg problem. So it that case, we do a simple copy. + //! + KOKKOS_FUNCTION real_t + compute_minmod_slopes_prolongation(FaceLocation_t const & face_loc_cur, + FaceLocation_t const & face_loc_right, + FaceLocation_t const & face_loc_left, + FaceDataArrayBlock_t const & facedata) const; + + // ============================================================== + // ============================================================== + //! evaluate linear combination with neighbors at finer or same AMR level. + //! + //! \param[in] cell_loc_left left location + //! \param[in] cell_loc_cur current location + //! \param[in] cell_loc_right right location + //! \param[in] target_level AMR level at which data must be used + //! \param[in] ivar variable index + //! \param[in] userdata a DataArrayBlock + //! \param[in] coef + //! + //! important note: + //! + //! We assume that all cell locations are at level target_level or target_level+1 but NOT + //! target_level-1 (with would imply a prolongation). + KOKKOS_FUNCTION real_t + compute_linear_combination(CellLocation_t const & cell_loc_left, + CellLocation_t const & cell_loc_cur, + CellLocation_t const & cell_loc_right, + uint8_t target_level, + int const ivar, + DataArrayBlock_t const & userdata, + Kokkos::Array const coef) const; + + // ============================================================== + // ============================================================== + //! evaluate linear combination with neighbors at finer or same AMR level. + //! + //! \param[in] cell_loc_left left2 location + //! \param[in] cell_loc_left left location + //! \param[in] cell_loc_cur current location + //! \param[in] cell_loc_right right location + //! \param[in] cell_loc_right right2 location + //! \param[in] target_level AMR level at which data must be used + //! \param[in] ivar variable index + //! \param[in] userdata a DataArrayBlock + //! \param[in] coef + //! + //! important note: + //! + //! We assume that all cell locations are at level target_level or target_level+1 but NOT + //! target_level-1 (with would imply a prolongation). + KOKKOS_FUNCTION real_t + compute_linear_combination(CellLocation_t const & cell_loc_left2, + CellLocation_t const & cell_loc_left, + CellLocation_t const & cell_loc_cur, + CellLocation_t const & cell_loc_right, + CellLocation_t const & cell_loc_right2, + uint8_t target_level, + int const ivar, + DataArrayBlock_t const & userdata, + Kokkos::Array const coef) const; + + +public: + //! AMR unordered map which maps orchard keys to quadrant number for all key in the mesh + //! (owned quadrants and ghost quadrants) + amr_hashmap_t m_amr_hashmap_device; + +private: + //! list of orchard key of the mesh + orchard_key_view_t m_orchard_keys_device; + + //! block sizes + const block_size_t m_block_sizes; + + //! p4est brick connectivity sizes + const brick_size_t m_brick_sizes; + + //! is p4est connectivity periodic ? + const Kokkos::Array m_is_brick_periodic; + +}; // class StencilHelper + +// explicit template instantiation +extern template class StencilHelper<2, kalypsso::DefaultDevice>; +extern template class StencilHelper<3, kalypsso::DefaultDevice>; + +#ifdef KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE +extern template class StencilHelper<2, kalypsso::HostDevice>; +extern template class StencilHelper<3, kalypsso::HostDevice>; +#endif // KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_STENCILHELPER_H diff --git a/src/kalypsso/core/THINCParams.h b/src/kalypsso/core/THINCParams.h new file mode 100644 index 0000000..8cde150 --- /dev/null +++ b/src/kalypsso/core/THINCParams.h @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file THINCParams.h + */ +#ifndef KALYPSSO_CORE_THINC_PARAMS_H_ +#define KALYPSSO_CORE_THINC_PARAMS_H_ + +#include // for assertm +#include +#include + +namespace kalypsso +{ + +/** + * THINC (Tangent Hyperbolic INterface Capturing) parameters. + */ +struct THINCParams +{ + //! turn on/off THINC reconstruction at run run-time + bool enabled; + + //! assumed interface width + real_t beta; + + //! volume fraction threshold controlling when THINC reconstruction is used + real_t epsilon; + + THINCParams(ConfigMap const & config_map) + : enabled(config_map.getBool("THINC", "enabled", false)) + , beta(config_map.getReal("THINC", "beta", KALYPSSO_NUM(1.5))) + , epsilon(config_map.getReal("THINC", "epsilon", KALYPSSO_NUM(1e-3))) + { + if (enabled) + { + assertm(beta >= 0, "[THINCParams] beta must be non-negative."); + assertm(epsilon >= 0, "[THINCParams] epsilon must be non-negative."); + } + } + +}; // struct THINCParams + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_THINC_PARAMS_H_ diff --git a/src/kalypsso/core/TimeIntegratorConfig.h b/src/kalypsso/core/TimeIntegratorConfig.h new file mode 100644 index 0000000..b20580e --- /dev/null +++ b/src/kalypsso/core/TimeIntegratorConfig.h @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file TimeIntegrationConfig.h + * + * Implement classic time integrator algorithm, e.g. : + * + * - Hancock (single state, second order) + * - RK2-SSP (two stages, second order) + * - RK3-SSP (three stages, third order) + * + * Reference on strong stability preserving Runge-Kutta methods: + * + * 1. http://epubs.siam.org/doi/pdf/10.1137/S0036142901389025 + * A NEW CLASS OF OPTIMAL HIGH-ORDER STRONG-STABILITY-PRESERVING + * TIME DISCRETIZATION METHODS + * RAYMOND J. SPITERI AND STEVEN J. RUUTH, + * SIAM J. Numer. Anal, Vol 40, No 2, pp 469-491 + * + * 2. https://link.springer.com/book/10.1007/978-1-4419-6412-0 + * Numerical Methods for Fluid Dynamics With Applications to Geophysics, Dale R. Durran + * textbook, 2010, Springer. See section 2.3.3. + */ +#ifndef KALYPSSO_CORE_TIMEINTEGRATORCONFIG_H_ +#define KALYPSSO_CORE_TIMEINTEGRATORCONFIG_H_ + +#include +#include +#include // for math functions (max, min, ...) + +#include +#include <../../better-enums/enum.h> + +namespace kalypsso +{ +// clang-format off +/** + * An enum type to represent all implemented time integration methods. + */ +BETTER_ENUM(TimeIntegrator, uint8_t, + HANCOCK = 0, + RK2_SSP = 1, + RK3_SSP = 2) +// clang-format on + +/** + * A companion data structure for class ComputeRefineFlags holding parameters used to + * compute refine flags. + */ +struct TimeIntegratorConfig +{ + static TimeIntegrator + get_time_integrator(ConfigMap const & config_map) + { + auto time_integrator_name = config_map.getString("amr", "time_integrator", "HANCOCK"); + auto maybe_value = TimeIntegrator::_from_string_nothrow(time_integrator_name.c_str()); + if (maybe_value) + { + return *maybe_value; + } + else + { + Kokkos::abort("Wrong time integrator parameter."); + } + return TimeIntegrator::HANCOCK; + } + + using RKCoefs = Kokkos::Array; + + static constexpr RKCoefs RK2_SSP_STAGE1_COEFS{ KALYPSSO_NUM(0.0), KALYPSSO_NUM(1.0) }; + static constexpr RKCoefs RK2_SSP_STAGE2_COEFS{ KALYPSSO_NUM(0.5), KALYPSSO_NUM(0.5) }; + + static constexpr RKCoefs RK3_SSP_STAGE1_COEFS{ KALYPSSO_NUM(0.0), KALYPSSO_NUM(1.0) }; + static constexpr RKCoefs RK3_SSP_STAGE2_COEFS{ KALYPSSO_NUM(0.75), KALYPSSO_NUM(0.25) }; + static constexpr RKCoefs RK3_SSP_STAGE3_COEFS{ KALYPSSO_NUM(1.0) / 3, KALYPSSO_NUM(2.0) / 3 }; + +}; // struct TimeIntegratorConfig + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_TIMEINTEGRATORCONFIG_H_ diff --git a/src/kalypsso/core/UserDataRemapper.cpp b/src/kalypsso/core/UserDataRemapper.cpp new file mode 100644 index 0000000..92b5ebd --- /dev/null +++ b/src/kalypsso/core/UserDataRemapper.cpp @@ -0,0 +1,248 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file UserDataRemapper.cpp + * \brief \copybrief UserDataRemapper.h + */ +#include +#include + +#include +#include +#include +#include + +namespace kalypsso +{ + +// =========================================================================== +// =========================================================================== +template +void +UserDataRemapper::remap_leaf_data( + typename UserDataRemapper::ExecutionSpace const & exec_space, + DataArrayLeaf_t userdataLeaf_old, + DataArrayLeaf_t userdataLeaf_new) +{ + // avoid warning "Implicit capture of 'this' in extended lambda expression" + // by the cuda compiler + auto amr_hashmap_device_old = m_amr_hashmap_device_old; + auto orchard_keys_device_new = m_orchard_keys_device_new; + + auto num_vars = userdataLeaf_old.extent(1); + + // traverse the new list of keys, and for each key try to find it in the old hashmap + Kokkos::parallel_for( + "Userdata_remap_after_amr_cycle_hashmap - leaf", + Kokkos::RangePolicy(exec_space, 0, m_local_num_octants_new), + KOKKOS_LAMBDA(const int iOct) { + auto key_new = orchard_keys_device_new(iOct); + + auto key_index = amr_hashmap_device_old.find(key_new); + + // printf("%d %d | %d\n", i, key_new, key_index); + + // first check if key exists in the old hashmap, if it exists, it means we have a + // an octant that didn't change level + if (amr_hashmap_device_old.valid_at(key_index)) + { + auto iOct_old = amr_hashmap_device_old.value_at(key_index); + for (uint32_t ivar = 0; ivar < num_vars; ++ivar) + userdataLeaf_new(iOct, ivar) = userdataLeaf_old(iOct_old, ivar); + } + else + { + // we either have a refinement or coarsening + + // check if the key correspond to a refinement (increase of level) + // to do that we search for father octant's key + auto key_to_test = orchard_key_t::father(key_new); + key_index = amr_hashmap_device_old.find(key_to_test); + + if (amr_hashmap_device_old.valid_at(key_index)) + { + auto iOct_old = amr_hashmap_device_old.value_at(key_index); + // copy the value from the old mesh + for (uint32_t ivar = 0; ivar < num_vars; ++ivar) + userdataLeaf_new(iOct, ivar) = userdataLeaf_old(iOct_old, ivar); + } + else + { + // check if the key correspond to a "coarsening" (decrease of level) + // to do that we just need to search for the eldest child key in the old map, + // and then average all the values from all the children (coarse graining values) + + /* + * In principe, we should check that all the children are available in the map + * but here, we just check for the eldest child key, and assume all the children + * are also in the map (p4est ensure that). + */ + + // auto children_keys = orchard_key_t::all_children(key_new); + // for (int ichild = 0; ichild < 0; ++ichild) + // { + // check that child key exist in old map + // TODO + // } + + constexpr auto NB_CHILDREN = orchard_key_t::NB_CHILDREN; + + auto eldest_child_key = orchard_key_t::eldest_child(key_new); + key_index = amr_hashmap_device_old.find(eldest_child_key); + if (amr_hashmap_device_old.valid_at(key_index)) + { + auto iOct_old = amr_hashmap_device_old.value_at(key_index); + + for (uint32_t ivar = 0; ivar < num_vars; ++ivar) + { + auto coarsen_value = ZERO_F; + // average value since we are coarsening + // WARNING : here we assume all the children are available in the map + // if this is not true, it means the map is corrupted => should abort + // in principle, this is highly not probable because p4est already checked that + // the whole family of octant are present + for (int64_t ichild = 0; ichild < NB_CHILDREN; ++ichild) + { + coarsen_value += userdataLeaf_old(iOct_old + ichild, ivar); + } + userdataLeaf_new(iOct, ivar) = coarsen_value / NB_CHILDREN; + } // end for ivar + } + } // end refine or coarsen + } + }); + +} // UserDataRemapper::remap_leaf_data + +// =========================================================================== +// =========================================================================== +template +void +UserDataRemapper::remap_block_data( + [[maybe_unused]] typename UserDataRemapper::ExecutionSpace const & exec_space, + const DataArrayBlock userdataBlock_old, + DataArrayBlock userdataBlock_new) +{ + + const auto nbOcts = m_local_num_octants_new; + const auto num_cells = userdataBlock_old.num_cells(); + + UserDataRemapperImplBCC functor(userdataBlock_old, + userdataBlock_new, + m_amr_hashmap_device_old, + m_orchard_keys_device_new, + m_orchard_keys_device_old, + m_local_num_octants_new, + m_block_sizes, + m_config_map); + + Kokkos::parallel_for("Userdata_remap_after_amr_cycle_hashmap - cell", + Kokkos::RangePolicy(0, num_cells * nbOcts), + functor); + +} // UserDataRemapper::remap_block_data - DataArrayBlock_t + +// =========================================================================== +// =========================================================================== +template +void +UserDataRemapper::remap_block_data( + [[maybe_unused]] typename UserDataRemapper::ExecutionSpace const & exec_space, + const FaceDataArrayBlock facedata_old, + FaceDataArrayBlock facedata_new) +{ + + const auto num_elements_per_octant = facedata_old.num_elements_per_octant(); + const auto nbOcts = m_local_num_octants_new; + + UserDataRemapperImplBFC functor(facedata_old, + facedata_new, + m_amr_hashmap_device_old, + m_orchard_keys_device_new, + m_orchard_keys_device_old, + m_local_num_octants_new, + m_block_sizes, + m_config_map); + + using TagComputeAllButInternalFaces = + typename UserDataRemapperImplBFC::TagComputeAllButInternalFaces; + + // traverse the new list of keys, and for each key try to find it in the old hashmap + Kokkos::parallel_for( + "Userdata_remap_after_amr_cycle_hashmap - face data - all but internal face prolongation", + Kokkos::RangePolicy( + 0, num_elements_per_octant * nbOcts), + functor); + + using TagComputeInternalFaces = + typename UserDataRemapperImplBFC::TagComputeInternalFaces; + + Kokkos::parallel_for( + "Userdata_remap_after_amr_cycle_hashmap - face data - all but internal face prolongation", + Kokkos::RangePolicy(0, + num_elements_per_octant * nbOcts), + functor); + +} // UserDataRemapper::remap_block_data - FaceDataArrayBlock_t + +// =========================================================================== +// =========================================================================== +template +void +UserDataRemapper::remap_material_presence( + [[maybe_unused]] ExecutionSpace const & exec_space, + const MaterialPresenceView_t mat_pres_old, + MaterialPresenceView_t mat_pres_new) +{ + UserDataRemapperImplMP functor(mat_pres_old, + mat_pres_new, + m_amr_hashmap_device_old, + m_orchard_keys_device_old, + m_orchard_keys_device_new, + m_config_map); + + Kokkos::parallel_for("kalypsso::UserDataRemapperImplMP", + Kokkos::RangePolicy(0, m_local_num_octants_new), + functor); +} + +// =========================================================================== +// =========================================================================== +template +void +UserDataRemapper::remap_material_data( + [[maybe_unused]] ExecutionSpace const & exec_space, + const DataArrayBlockMultiVar_t mat_data_old, + const MaterialPresenceView_t mat_pres_old, + DataArrayBlockMultiVar_t mat_data_new, + const MaterialPresenceView_t mat_pres_new, + const uint32_t num_vars_per_mat) +{ + UserDataRemapperImplMD functor(mat_data_old, + mat_pres_old, + mat_data_new, + mat_pres_new, + m_amr_hashmap_device_old, + m_orchard_keys_device_old, + m_orchard_keys_device_new, + num_vars_per_mat, + m_config_map); + + Kokkos::parallel_for( + "kalypsso::UserDataRemapperImplMD", + Kokkos::RangePolicy(0, mat_data_new.num_cells() * m_local_num_octants_new), + functor); +} + +// explicit template instantiation +template class UserDataRemapper<2, DefaultDevice>; +template class UserDataRemapper<3, DefaultDevice>; + +#ifdef KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE +template class UserDataRemapper<2, HostDevice>; +template class UserDataRemapper<3, HostDevice>; +#endif // KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE + +} // namespace kalypsso diff --git a/src/kalypsso/core/UserDataRemapper.h b/src/kalypsso/core/UserDataRemapper.h new file mode 100644 index 0000000..42eb481 --- /dev/null +++ b/src/kalypsso/core/UserDataRemapper.h @@ -0,0 +1,171 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file UserDataRemapper.h + * \brief Perform user data remapping after mesh change (AMR cycle) + */ +#ifndef KALYPSSO_CORE_USERDATAREMAPPER_H_ +#define KALYPSSO_CORE_USERDATAREMAPPER_H_ + +#include +#include +#include // for DataArray, DataArrayHost + +#include +#include + +#include + +#include + +#include + +// #include +// #include + +namespace kalypsso +{ + +/** + * \class UserDataRemapper + * + * Remap user data from the old mesh to the new mesh (after AMR cycle modifications). + * Currently user data are considered as conservative variables. + * + * There are 3 situations depending on source and destination octant levels: + * - when level(dest) == level(src) : doing a simple copy + * - when level(dest) < level(src) : doing a restriction (i.e. just averaging values from source) + * - when level(dest) > level(src) : doing a prolongation (destination is at a finer AMR level) + * + * Prolongation is configurable, currently we support: + * + * - SIMPLE_COPY : + * values from the source (mother cell) is directly copied into the destination cells (child + * cells) + * - EXTRAPOLATE_LINEAR_MINMOD : + * we first estimate limited slopes at source location using source + * neighbors cells, then do a linear extrapolation from the mother + * cells to the children cells. + * + * This class is able to remap + * - cell-centered data stored in a DataArrayBlock_t + * - face-centered data stored in a FaceDataArrayBlock_t + * + */ +template +class UserDataRemapper +{ +public: + using DataArrayLeaf_t = DataArrayLeaf; + using DataArrayBlock_t = DataArrayBlock; + using FaceDataArrayBlock_t = FaceDataArrayBlock; + using DataArrayBlockMultiVar_t = DataArrayBlockMultiVar; + using MaterialPresenceView_t = MaterialPresenceView; + + using amr_hashmap_t = typename hashmap_base_t::map_t; + using orchard_key_view_t = typename orchard_key_base_t::view_t; + + using ExecutionSpace = typename device_t::execution_space; + + UserDataRemapper() = delete; + UserDataRemapper(amr_hashmap_t amr_hashmap_old, + orchard_key_view_t orchard_keys_new, + orchard_key_view_t orchard_keys_old, + int32_t local_num_octants, + block_size_t bSizes, + ConfigMap const & config_map) + : m_amr_hashmap_device_old(amr_hashmap_old) + , m_orchard_keys_device_new(orchard_keys_new) + , m_orchard_keys_device_old(orchard_keys_old) + , m_local_num_octants_new(local_num_octants) + , m_block_sizes(bSizes) + , m_config_map(config_map) + {} + + // =========================================================================== + // =========================================================================== + //! Apply data remapping (on device) using the hash map approach - leaf data + void + remap_leaf_data(ExecutionSpace const & exec_space, + DataArrayLeaf_t userdataLeaf_old, + DataArrayLeaf_t userdataLeaf_new); + + // =========================================================================== + // =========================================================================== + //! Apply data remapping (on device) using the hash map approach - block data - cell center + //! + //! this only work when we don't use ghost cells at the block level. + void + remap_block_data(ExecutionSpace const & exec_space, + const DataArrayBlock_t userdataBlock_old, + DataArrayBlock_t userdataBlock_new); + + // =========================================================================== + // =========================================================================== + //! Apply data remapping (on device) using the hash map approach - block data - face-center + //! + //! this only work when we don't use ghost cells at the block level. + void + remap_block_data(ExecutionSpace const & exec_space, + const FaceDataArrayBlock_t facedata_old, + FaceDataArrayBlock_t facedata_new); + + // =========================================================================== + // =========================================================================== + //! Apply data remapping (on device) using the hash map approach - material presence + //! + //! Remaps the material presence from the old material presence to the new one + void + remap_material_presence(ExecutionSpace const & exec_space, + const MaterialPresenceView_t mat_pres_old, + MaterialPresenceView_t mat_pres_new); + + // =========================================================================== + // =========================================================================== + //! Apply data remapping (on device) using the hash map approach - material data + //! + //! Remaps the material data from the old material data to the new one + void + remap_material_data(ExecutionSpace const & exec_space, + const DataArrayBlockMultiVar_t mat_data_old, + const MaterialPresenceView_t mat_pres_old, + DataArrayBlockMultiVar_t mat_data_new, + const MaterialPresenceView_t mat_pres_new, + const uint32_t num_vars_per_mat); + +private: + //! AMR unordered map which map orchard keys to octant number for all key in the mesh + //! before AMR cycle modification + amr_hashmap_t m_amr_hashmap_device_old; + + //! list of orchard key of the new mesh (after AMR cycle modification) + orchard_key_view_t m_orchard_keys_device_new; + + //! list of orchard key of the old mesh (before AMR cycle modification) + orchard_key_view_t m_orchard_keys_device_old; + + //! number of octants in the new mesh + int32_t m_local_num_octants_new; + + //! block sizes + block_size_t m_block_sizes; + + //! config map + const ConfigMap & m_config_map; + +}; // class UserDataRemapper + +// explicit template instantiation +extern template class UserDataRemapper<2, DefaultDevice>; +extern template class UserDataRemapper<3, DefaultDevice>; + +#ifdef KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE +extern template class UserDataRemapper<2, HostDevice>; +extern template class UserDataRemapper<3, HostDevice>; +#endif // KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_USERDATAREMAPPER_H_ diff --git a/src/kalypsso/core/UserDataRemapperImplBCC.cpp b/src/kalypsso/core/UserDataRemapperImplBCC.cpp new file mode 100644 index 0000000..3deb446 --- /dev/null +++ b/src/kalypsso/core/UserDataRemapperImplBCC.cpp @@ -0,0 +1,596 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file UserDataRemapperImplBCC.cpp + * \brief \copybrief UserDataRemapperImplBCC.h + */ +#include + +namespace kalypsso +{ +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION void +UserDataRemapperImplBCC::operator()(const index_t & global_index) const +{ + // get (octant id, cellindex) from global_index + // using left indexing, we get + // global_index = cellindex + iOct * num_cells + const auto num_cells = m_userdataBlock_old.num_cells(); + + const auto iOct = global_index / num_cells; + const auto cellindex = global_index - iOct * num_cells; + + const auto num_vars = m_userdataBlock_old.num_vars(); + const auto bSize = m_userdataBlock_old.block_size(); + + // get orchard key of current block/octant + const auto key_new = m_orchard_keys_device_new(iOct); + + // for each key try to find it in the old hashmap + auto key_index = m_amr_hashmap_device_old.find(key_new); + + // first check if key exists in the old hashmap, if it exists, it means we have a + // an octant that didn't change level + if (m_amr_hashmap_device_old.valid_at(key_index)) + { + // auto key_old = amr_hashmap_device_old.key_at(key_index); + auto iOct_old = m_amr_hashmap_device_old.value_at(key_index); + + for (int32_t ivar = 0; ivar < num_vars; ++ivar) + { + // copy from old quadrant to new quadrant + m_userdataBlock_new(cellindex, ivar, iOct) = m_userdataBlock_old(cellindex, ivar, iOct_old); + } + } + else + { + // check if the key correspond to a refinement (increase of level / prolongation) + // to do that we search for father octant's key + auto key_to_test = orchard_key_t::father(key_new); + key_index = m_amr_hashmap_device_old.find(key_to_test); + + if (m_amr_hashmap_device_old.valid_at(key_index)) + { + auto iOct_old = m_amr_hashmap_device_old.value_at(key_index); + + // get family id of the new key, used to determine where exactly in the old block we + // will copy data + auto fid = static_cast(orchard_key_t::family_id(key_new)); + + // transform iCoord (new cell) into iCoord (corresponding coarse cell in old + // quadrant) + auto iCoord = cellindex_to_coord(cellindex, bSize); + + coord_t iCoord_old; + if constexpr (dim == 2) + { + iCoord_old[IX] = iCoord[IX] / 2 + ((fid & 0x1) >> 0) * (bSize[IX] / 2); + iCoord_old[IY] = iCoord[IY] / 2 + ((fid & 0x2) >> 1) * (bSize[IY] / 2); + } + else + { + iCoord_old[IX] = iCoord[IX] / 2 + ((fid & 0x1) >> 0) * (bSize[IX] / 2); + iCoord_old[IY] = iCoord[IY] / 2 + ((fid & 0x2) >> 1) * (bSize[IY] / 2); + iCoord_old[IZ] = iCoord[IZ] / 2 + ((fid & 0x4) >> 2) * (bSize[IZ] / 2); + } + const auto cellindex_old = coord_to_cellindex(iCoord_old, bSize); + + if (m_prolongation == +CellCenteredProlongationType::SIMPLE_COPY) + { + // simple copy of the coarse value from the old mesh + for (int32_t ivar = 0; ivar < num_vars; ++ivar) + { + m_userdataBlock_new(cellindex, ivar, iOct) = + m_userdataBlock_old(cellindex_old, ivar, iOct_old); + } // end for ivar + } + else if (m_prolongation == +CellCenteredProlongationType::EXTRAPOLATE_LINEAR_MINMOD) + { + // current cell location in old mesh + const CellLocation_t cell_loc_old{ iCoord_old, key_to_test, iOct_old, false }; + linear_extrapolate_using_limited_slopes(cell_loc_old, iCoord, cellindex, iOct); + } + else if (m_prolongation == +CellCenteredProlongationType::CONSERVATIVE_INTERPOLATION_ORDER_2) + { + // current cell location in old mesh + const CellLocation_t cell_loc_old{ iCoord_old, key_to_test, iOct_old, false }; + conservative_interpolation_order2(cell_loc_old, iCoord, cellindex, iOct); + } + else if (m_prolongation == +CellCenteredProlongationType::CONSERVATIVE_INTERPOLATION_ORDER_4) + { + // current cell location in old mesh + const CellLocation_t cell_loc_old{ iCoord_old, key_to_test, iOct_old, false }; + conservative_interpolation_order4(cell_loc_old, iCoord, cellindex, iOct); + } + else + { + // Houston we have a problem.... + // we shouldn't be here + KOKKOS_ASSERT(false && "Unsupported prolongation type"); + } + } + else + { + // check if the key correspond to a "coarsening" (decrease of level / restriction) + // to do that we just need to search for the eldest child key in the old map, + // and then average all the values from all the children (coarse graining values) + + /* + * In principe, we should check that all the children are available in the map + * but here, we just check for the eldest child key, and assume all the children + * are also in the map (p4est ensure that anyway). + */ + + constexpr auto NB_CHILDREN = orchard_key_t::NB_CHILDREN; + + auto eldest_child_key = orchard_key_t::eldest_child(key_new); + key_index = m_amr_hashmap_device_old.find(eldest_child_key); + if (m_amr_hashmap_device_old.valid_at(key_index)) + { + auto iOct_eldest = m_amr_hashmap_device_old.value_at(key_index); + + for (int32_t ivar = 0; ivar < num_vars; ++ivar) + { + // transform iCoord (new cell) into iCoord (corresponding coarse cell in old + // quadrant) + auto iCoord = cellindex_to_coord(cellindex, bSize); + + // for a given cell determine the child id where we will need to fetch data in the + // old mesh + uint32_t ichild = 0; + if (iCoord[IX] >= bSize[IX] / 2) + ichild |= 0x1; + if (iCoord[IY] >= bSize[IY] / 2) + ichild |= 0x2; + if constexpr (dim == 3) + { + if (iCoord[IZ] >= bSize[IZ] / 2) + { + ichild |= 0x4; + } + } + + // here we taken into account that a family of octant are stored contiguously in + // memory + auto iOct_old = iOct_eldest + ichild; + + // we need to average value over NB_CHILDREN neighboring cells + real_t coarsen_value = ZERO_F; + if constexpr (dim == 2) + { + for (uint8_t iy = 0; iy < 2; ++iy) + { + auto iiy = 2 * iCoord[IY] + iy >= bSize[IY] ? 2 * iCoord[IY] + iy - bSize[IY] + : 2 * iCoord[IY] + iy; + + for (uint8_t ix = 0; ix < 2; ++ix) + { + auto iix = 2 * iCoord[IX] + ix >= bSize[IX] ? 2 * iCoord[IX] + ix - bSize[IX] + : 2 * iCoord[IX] + ix; + coarsen_value += m_userdataBlock_old(iix, iiy, ivar, iOct_old); + } + } + } + else + { + for (uint8_t iz = 0; iz < 2; ++iz) + { + auto iiz = 2 * iCoord[IZ] + iz >= bSize[IZ] ? 2 * iCoord[IZ] + iz - bSize[IZ] + : 2 * iCoord[IZ] + iz; + for (uint8_t iy = 0; iy < 2; ++iy) + { + auto iiy = 2 * iCoord[IY] + iy >= bSize[IY] ? 2 * iCoord[IY] + iy - bSize[IY] + : 2 * iCoord[IY] + iy; + for (uint8_t ix = 0; ix < 2; ++ix) + { + auto iix = 2 * iCoord[IX] + ix >= bSize[IX] ? 2 * iCoord[IX] + ix - bSize[IX] + : 2 * iCoord[IX] + ix; + coarsen_value += m_userdataBlock_old(iix, iiy, iiz, ivar, iOct_old); + } + } + } + } + + m_userdataBlock_new(cellindex, ivar, iOct) = coarsen_value / NB_CHILDREN; + } + + // average value since we are coarsening + // WARNING : here we assume all the children are available in the map + // if this is not true, it means the map is corrupted => should abort + // in principle, this is highly not probable because p4est already checked that + // the whole family of octant are present + // for (size_t ichild = 0; ichild < NB_CHILDREN; ++ichild) + // { + // value += m_userdataLeaf(index_old + ichild, 0); + // } + } + } + } + +} // operator() + +// ============================================================== +// ============================================================== +/** + * Do linear extrapolation using limited slopes. + */ +template +template > +KOKKOS_INLINE_FUNCTION void +UserDataRemapperImplBCC::linear_extrapolate_using_limited_slopes( + CellLocation<2> const & cell_loc_old, + coord_t<2> const & coord_new, + index_t const & cellindex_new, + int32_t const & iOct_global) const +{ + const real_t slope_type = 1; + const auto nbvar = m_userdataBlock_old.num_vars(); + + // get neighbor location, each of then may be at finer, same or coarser level compared + // cell_loc_old + const auto cell_loc_left_x = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(-XDIR)); + const auto cell_loc_right_x = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(+XDIR)); + + const auto cell_loc_left_y = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(-YDIR)); + const auto cell_loc_right_y = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(+YDIR)); + + // determine local position of current new cell inside old parent cell using integer coordinates + // in -1, +1 + const int ix = 2 * (coord_new[IX] - 2 * (coord_new[IX] / 2)) - 1; + const int iy = 2 * (coord_new[IY] - 2 * (coord_new[IY] / 2)) - 1; + + for (int32_t ivar = 0; ivar < nbvar; ++ivar) + { + // compute limited slopes + auto const dudx = m_stencil_helper.compute_minmod_slopes_prolongation( + cell_loc_old, cell_loc_right_x, cell_loc_left_x, ivar, m_userdataBlock_old, slope_type); + auto const dudy = m_stencil_helper.compute_minmod_slopes_prolongation( + cell_loc_old, cell_loc_right_y, cell_loc_left_y, ivar, m_userdataBlock_old, slope_type); + + // extrapolate + m_userdataBlock_new(cellindex_new, ivar, iOct_global) = + m_userdataBlock_old(cell_loc_old.cellindex(m_block_sizes), ivar, cell_loc_old.iOct) + + ONE_FOURTH_F * static_cast(ix) * dudx + ONE_FOURTH_F * static_cast(iy) * dudy; + } +} // linear_extrapolate_using_limited_slopes - 2d + +// ============================================================== +// ============================================================== +/** + * Do linear extrapolation using limited slopes. + */ +template +template > +KOKKOS_INLINE_FUNCTION void +UserDataRemapperImplBCC::linear_extrapolate_using_limited_slopes( + CellLocation<3> const & cell_loc_old, + coord_t<3> const & coord_new, + index_t const & cellindex_new, + int32_t const & iOct_global) const +{ + const real_t slope_type = 1; + const auto nbvar = m_userdataBlock_old.num_vars(); + + // get neighbor location, each of then may be at finer, same or coarser level compared + // cell_loc_old + const auto cell_loc_left_x = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(-XDIR)); + const auto cell_loc_right_x = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(+XDIR)); + + const auto cell_loc_left_y = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(-YDIR)); + const auto cell_loc_right_y = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(+YDIR)); + + const auto cell_loc_left_z = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(-ZDIR)); + const auto cell_loc_right_z = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(+ZDIR)); + + // determine local position of current new cell inside old parent cell using integer coordinates + // in -1, +1 + const int ix = 2 * (coord_new[IX] - 2 * (coord_new[IX] / 2)) - 1; + const int iy = 2 * (coord_new[IY] - 2 * (coord_new[IY] / 2)) - 1; + const int iz = 2 * (coord_new[IZ] - 2 * (coord_new[IZ] / 2)) - 1; + + for (int32_t ivar = 0; ivar < nbvar; ++ivar) + { + // compute limited slopes + auto const dudx = m_stencil_helper.compute_minmod_slopes_prolongation( + cell_loc_old, cell_loc_right_x, cell_loc_left_x, ivar, m_userdataBlock_old, slope_type); + auto const dudy = m_stencil_helper.compute_minmod_slopes_prolongation( + cell_loc_old, cell_loc_right_y, cell_loc_left_y, ivar, m_userdataBlock_old, slope_type); + auto const dudz = m_stencil_helper.compute_minmod_slopes_prolongation( + cell_loc_old, cell_loc_right_z, cell_loc_left_z, ivar, m_userdataBlock_old, slope_type); + + // extrapolate + m_userdataBlock_new(cellindex_new, ivar, iOct_global) = + m_userdataBlock_old(cell_loc_old.cellindex(m_block_sizes), ivar, cell_loc_old.iOct) + + ONE_FOURTH_F * static_cast(ix) * dudx + + ONE_FOURTH_F * static_cast(iy) * dudy + ONE_FOURTH_F * static_cast(iz) * dudz; + } +} // linear_extrapolate_using_limited_slopes - 3d + +// ============================================================== +// ============================================================== +template +template > +KOKKOS_INLINE_FUNCTION void +UserDataRemapperImplBCC::conservative_interpolation_order2( + CellLocation<2> const & cell_loc_old, + coord_t<2> const & coord_new, + index_t const & cellindex_new, + int32_t const & iOct_global) const +{ + const auto nbvar = m_userdataBlock_old.num_vars(); + + // determine local position of current cell inside virtual parent cell using integer + // coordinates in {0, 1} + // + // -------------------------- + // | | | + // | | | + // | 0,1 | 1,1 | + // | | | + // |___________|____________| + // | | | + // | | | + // | 0,0 | 1,0 | + // | | | + // |___________|____________| + const int ix = (coord_new[IX] - 2 * (coord_new[IX] / 2)); + const int iy = (coord_new[IY] - 2 * (coord_new[IY] / 2)); + + for (int32_t ivar = 0; ivar < nbvar; ++ivar) + { + real_t val[3]; + + for (int j = -1; j < 2; ++j) + { + // interpolate along X + auto cell_loc_x_m1 = m_stencil_helper.getNeighLoc(cell_loc_old, shift_t<2>{ -1, j }); + auto cell_loc_x_0 = m_stencil_helper.getNeighLoc(cell_loc_old, shift_t<2>{ 0, j }); + auto cell_loc_x_p1 = m_stencil_helper.getNeighLoc(cell_loc_old, shift_t<2>{ 1, j }); + + val[j + 1] = m_stencil_helper.compute_linear_combination(cell_loc_x_m1, + cell_loc_x_0, + cell_loc_x_p1, + cell_loc_old.level(), + ivar, + m_userdataBlock_old, + ix == 0 ? m_cons_interpol.COEFS2_L + : m_cons_interpol.COEFS2_R); + } + + m_userdataBlock_new(cellindex_new, ivar, iOct_global) = + m_cons_interpol.order2(val[0], val[1], val[2], iy == 0); + + } // end for ivar + +} // UserDataRemapperImplBCC::conservative_interpolation_order2 - 2d + +// ============================================================== +// ============================================================== +template +template > +KOKKOS_INLINE_FUNCTION void +UserDataRemapperImplBCC::conservative_interpolation_order2( + CellLocation<3> const & cell_loc_old, + coord_t<3> const & coord_new, + index_t const & cellindex_new, + int32_t const & iOct_global) const +{ + const auto nbvar = m_userdataBlock_old.num_vars(); + + // determine local position of current cell inside virtual parent cell using integer coordinates + // in {0, 1} + // -------------------------- + // | | | + // | | | + // | 0,1 | 1,1 | + // | | | + // |___________|____________| + // | | | + // | | | + // | 0,0 | 1,0 | + // | | | + // |___________|____________| + const int ix = (coord_new[IX] - 2 * (coord_new[IX] / 2)); + const int iy = (coord_new[IY] - 2 * (coord_new[IY] / 2)); + const int iz = (coord_new[IZ] - 2 * (coord_new[IZ] / 2)); + + for (int32_t ivar = 0; ivar < nbvar; ++ivar) + { + real_t valx[3][3]; + + for (int k = -1; k < 2; ++k) + { + for (int j = -1; j < 2; ++j) + { + // interpolate along X + auto cell_loc_x_m1 = m_stencil_helper.getNeighLoc(cell_loc_old, shift_t<3>{ -1, j, k }); + auto cell_loc_x_0 = m_stencil_helper.getNeighLoc(cell_loc_old, shift_t<3>{ 0, j, k }); + auto cell_loc_x_p1 = m_stencil_helper.getNeighLoc(cell_loc_old, shift_t<3>{ 1, j, k }); + + valx[k + 1][j + 1] = m_stencil_helper.compute_linear_combination( + cell_loc_x_m1, + cell_loc_x_0, + cell_loc_x_p1, + cell_loc_old.level(), + ivar, + m_userdataBlock_old, + ix == 0 ? m_cons_interpol.COEFS2_L : m_cons_interpol.COEFS2_R); + } + } + real_t valy[3]; + + valy[0] = m_cons_interpol.order2(valx[0][0], valx[0][1], valx[0][2], iy == 0); + valy[1] = m_cons_interpol.order2(valx[1][0], valx[1][1], valx[1][2], iy == 0); + valy[2] = m_cons_interpol.order2(valx[2][0], valx[2][1], valx[2][2], iy == 0); + + m_userdataBlock_new(cellindex_new, ivar, iOct_global) = + m_cons_interpol.order2(valy[0], valy[1], valy[2], iz == 0); + + } // end for ivar + +} // UserDataRemapperImplBCC::conservative_interpolation_order2 - 3d + +// ============================================================== +// ============================================================== +template +template > +KOKKOS_INLINE_FUNCTION void +UserDataRemapperImplBCC::conservative_interpolation_order4( + CellLocation<2> const & cell_loc_neigh, + coord_t<2> const & coord_new, + index_t const & cellindex_new, + int32_t const & iOct_global) const +{ + const auto nbvar = m_userdataBlock_old.num_vars(); + + // determine local position of current cell inside virtual parent cell using integer + // coordinates in {0, 1} + // + // -------------------------- + // | | | + // | | | + // | 0,1 | 1,1 | + // | | | + // |___________|____________| + // | | | + // | | | + // | 0,0 | 1,0 | + // | | | + // |___________|____________| + const int ix = (coord_new[IX] - 2 * (coord_new[IX] / 2)); + const int iy = (coord_new[IY] - 2 * (coord_new[IY] / 2)); + + for (int32_t ivar = 0; ivar < nbvar; ++ivar) + { + real_t val[5]; + + for (int j = -2; j < 3; ++j) + { + // interpolate along X + auto cell_loc_x_m2 = m_stencil_helper.getNeighLoc(cell_loc_neigh, shift_t<2>{ -2, j }); + auto cell_loc_x_m1 = m_stencil_helper.getNeighLoc(cell_loc_neigh, shift_t<2>{ -1, j }); + auto cell_loc_x_0 = m_stencil_helper.getNeighLoc(cell_loc_neigh, shift_t<2>{ 0, j }); + auto cell_loc_x_p1 = m_stencil_helper.getNeighLoc(cell_loc_neigh, shift_t<2>{ 1, j }); + auto cell_loc_x_p2 = m_stencil_helper.getNeighLoc(cell_loc_neigh, shift_t<2>{ 2, j }); + + val[j + 2] = m_stencil_helper.compute_linear_combination(cell_loc_x_m2, + cell_loc_x_m1, + cell_loc_x_0, + cell_loc_x_p1, + cell_loc_x_p2, + cell_loc_neigh.level(), + ivar, + m_userdataBlock_old, + ix == 0 ? m_cons_interpol.COEFS4_L + : m_cons_interpol.COEFS4_R); + } + + m_userdataBlock_new(cellindex_new, ivar, iOct_global) = + m_cons_interpol.order4(val[0], val[1], val[2], val[3], val[4], iy == 0); + + } // end for ivar + +} // UserDataRemapperImplBCC::conservative_interpolation_order4 - 2d + +// ============================================================== +// ============================================================== +template +template > +KOKKOS_INLINE_FUNCTION void +UserDataRemapperImplBCC::conservative_interpolation_order4( + CellLocation<3> const & cell_loc_neigh, + coord_t<3> const & coord_new, + index_t const & cellindex_new, + int32_t const & iOct_global) const +{ + const auto nbvar = m_userdataBlock_old.num_vars(); + + // determine local position of current cell inside virtual parent cell using integer coordinates + // in {0, 1} + // -------------------------- + // | | | + // | | | + // | 0,1 | 1,1 | + // | | | + // |___________|____________| + // | | | + // | | | + // | 0,0 | 1,0 | + // | | | + // |___________|____________| + const int ix = (coord_new[IX] - 2 * (coord_new[IX] / 2)); + const int iy = (coord_new[IY] - 2 * (coord_new[IY] / 2)); + const int iz = (coord_new[IZ] - 2 * (coord_new[IZ] / 2)); + + for (int32_t ivar = 0; ivar < nbvar; ++ivar) + { + real_t valx[5][5]; + + for (int k = -2; k < 3; ++k) + { + for (int j = -2; j < 3; ++j) + { + // interpolate along X + auto cell_loc_x_m2 = m_stencil_helper.getNeighLoc(cell_loc_neigh, shift_t<3>{ -2, j, k }); + auto cell_loc_x_m1 = m_stencil_helper.getNeighLoc(cell_loc_neigh, shift_t<3>{ -1, j, k }); + auto cell_loc_x_0 = m_stencil_helper.getNeighLoc(cell_loc_neigh, shift_t<3>{ 0, j, k }); + auto cell_loc_x_p1 = m_stencil_helper.getNeighLoc(cell_loc_neigh, shift_t<3>{ 1, j, k }); + auto cell_loc_x_p2 = m_stencil_helper.getNeighLoc(cell_loc_neigh, shift_t<3>{ 2, j, k }); + + valx[k + 2][j + 2] = m_stencil_helper.compute_linear_combination( + cell_loc_x_m2, + cell_loc_x_m1, + cell_loc_x_0, + cell_loc_x_p1, + cell_loc_x_p2, + cell_loc_neigh.level(), + ivar, + m_userdataBlock_old, + ix == 0 ? m_cons_interpol.COEFS4_L : m_cons_interpol.COEFS4_R); + } + } + real_t valy[5]; + + valy[0] = + m_cons_interpol.order4(valx[0][0], valx[0][1], valx[0][2], valx[0][3], valx[0][4], iy == 0); + valy[1] = + m_cons_interpol.order4(valx[1][0], valx[1][1], valx[1][2], valx[1][3], valx[1][4], iy == 0); + valy[2] = + m_cons_interpol.order4(valx[2][0], valx[2][1], valx[2][2], valx[2][3], valx[2][4], iy == 0); + valy[3] = + m_cons_interpol.order4(valx[3][0], valx[3][1], valx[3][2], valx[3][3], valx[3][4], iy == 0); + valy[4] = + m_cons_interpol.order4(valx[4][0], valx[4][1], valx[4][2], valx[4][3], valx[4][4], iy == 0); + + m_userdataBlock_new(cellindex_new, ivar, iOct_global) = + m_cons_interpol.order4(valy[0], valy[1], valy[2], valy[3], valy[4], iz == 0); + + } // end for ivar + +} // UserDataRemapperImplBCC::conservative_interpolation_order4 - 3d + + +// explicit template instantiation +template class UserDataRemapperImplBCC<2, kalypsso::DefaultDevice>; +template class UserDataRemapperImplBCC<3, kalypsso::DefaultDevice>; + +#ifdef KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE +template class UserDataRemapperImplBCC<2, HostDevice>; +template class UserDataRemapperImplBCC<3, HostDevice>; +#endif // KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE + +} // namespace kalypsso diff --git a/src/kalypsso/core/UserDataRemapperImplBCC.h b/src/kalypsso/core/UserDataRemapperImplBCC.h new file mode 100644 index 0000000..750e7cb --- /dev/null +++ b/src/kalypsso/core/UserDataRemapperImplBCC.h @@ -0,0 +1,195 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file UserDataRemapperImplBCC.h + * + * \brief Implement actual user data remapping (after mesh change during AMR cycle) for block + * cell-centered (BCC) data. + */ +#ifndef KALYPSSO_CORE_USERDATAREMAPPER_IMPL_BCC_H_ +#define KALYPSSO_CORE_USERDATAREMAPPER_IMPL_BCC_H_ + +#include +#include +#include // for DataArray, DataArrayHost + +#include +#include + +#include +#include +#include +#include // for get_brick_periodicity + +namespace kalypsso +{ +/** + * Implementation details class to be instantiated inside UserDataRemapper. + */ +template +class UserDataRemapperImplBCC +{ +public: + using index_t = int32_t; + + // using DataArrayLeaf_t = DataArrayLeaf; + using DataArrayBlock_t = DataArrayBlock; + using FaceDataArrayBlock_t = FaceDataArrayBlock; + + using amr_hashmap_t = typename hashmap_base_t::map_t; + using orchard_key_view_t = typename orchard_key_base_t::view_t; + + using CellLocation_t = CellLocation; + using StencilHelper_t = StencilHelper; + + using ExecutionSpace = typename device_t::execution_space; + + UserDataRemapperImplBCC() = delete; + UserDataRemapperImplBCC(const DataArrayBlock_t userdataBlock_old, + DataArrayBlock_t userdataBlock_new, + amr_hashmap_t amr_hashmap_old, + orchard_key_view_t orchard_keys_new, + orchard_key_view_t orchard_keys_old, + int32_t local_num_octants, + block_size_t bSizes, + ConfigMap const & config_map) + : m_userdataBlock_old(userdataBlock_old) + , m_userdataBlock_new(userdataBlock_new) + , m_amr_hashmap_device_old(amr_hashmap_old) + , m_orchard_keys_device_new(orchard_keys_new) + , m_local_num_octants_new(local_num_octants) + , m_block_sizes(bSizes) + , m_prolongation(get_cell_prolongation_type(config_map)) + , m_stencil_helper(amr_hashmap_old, + orchard_keys_old, + userdataBlock_old.block_size(), + get_brick_sizes(config_map), + get_brick_periodicity(config_map)) + , m_cons_interpol() + {} + + // ============================================================== + // ============================================================== + /** + * Perform remapping in all cells : + * - level l to level l + * - fine to coarse (aka restriction) + * - coarse to fine (aka prolongation) + */ + KOKKOS_FUNCTION void + operator()(const index_t & global_index) const; + +private: + /** + * Do linear extrapolation using limited slopes. + */ + template = true> + KOKKOS_INLINE_FUNCTION void + linear_extrapolate_using_limited_slopes(CellLocation<2> const & cell_loc_old, + coord_t<2> const & coord_new, + index_t const & cellindex_new, + int32_t const & iOct_global) const; + + /** + * Do linear extrapolation using limited slopes. + */ + template = true> + KOKKOS_INLINE_FUNCTION void + linear_extrapolate_using_limited_slopes(CellLocation<3> const & cell_loc_old, + coord_t<3> const & coord_new, + index_t const & cellindex_new, + int32_t const & iOct_global) const; + + // ============================================================== + // ============================================================== + /** + * Do second order conservative interpolation from coarse to fine cells. + */ + template = true> + KOKKOS_INLINE_FUNCTION void + conservative_interpolation_order2(CellLocation<2> const & cell_loc_old, + coord_t<2> const & coord_new, + index_t const & cellindex_new, + int32_t const & iOct_global) const; + + // ============================================================== + // ============================================================== + /** + * Do second order conservative interpolation from coarse to fine cells. + */ + template = true> + KOKKOS_INLINE_FUNCTION void + conservative_interpolation_order2(CellLocation<3> const & cell_loc_old, + coord_t<3> const & coord_new, + index_t const & cellindex_new, + int32_t const & iOct_global) const; + + // ============================================================== + // ============================================================== + /** + * Do fourth order conservative interpolation from coarse to fine cells. + */ + template = true> + KOKKOS_INLINE_FUNCTION void + conservative_interpolation_order4(CellLocation<2> const & cell_loc_old, + coord_t<2> const & coord_new, + index_t const & cellindex_new, + int32_t const & iOct_global) const; + + // ============================================================== + // ============================================================== + /** + * Do fourth order conservative interpolation from coarse to fine cells. + */ + template = true> + KOKKOS_INLINE_FUNCTION void + conservative_interpolation_order4(CellLocation<3> const & cell_loc_old, + coord_t<3> const & coord_new, + index_t const & cellindex_new, + int32_t const & iOct_global) const; + +private: + //! source data array + const DataArrayBlock_t m_userdataBlock_old; + + //! destination data array (where data will be remapped) + DataArrayBlock_t m_userdataBlock_new; + + //! AMR unordered map which map orchard keys to octant number for all key in the mesh + //! before AMR cycle modification + amr_hashmap_t m_amr_hashmap_device_old; + + //! list of orchard key of the new mesh (after AMR cycle modification) + orchard_key_view_t m_orchard_keys_device_new; + + //! number of octants in the new mesh + int32_t m_local_num_octants_new; + + //! block sizes + block_size_t m_block_sizes; + + //! prolongation type (simple copy or linear extrapolation) + const CellCenteredProlongationType m_prolongation; + + //! helper to compute neighbor cell location + StencilHelper_t m_stencil_helper; + + //! Conservative interpolation data + const ConservativeInterpolation m_cons_interpol; + +}; // class UserDataRemapperImplBCC + +// explicit template instantiation +extern template class UserDataRemapperImplBCC<2, kalypsso::DefaultDevice>; +extern template class UserDataRemapperImplBCC<3, kalypsso::DefaultDevice>; + +#ifdef KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE +extern template class UserDataRemapperImplBCC<2, HostDevice>; +extern template class UserDataRemapperImplBCC<3, HostDevice>; +#endif // KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_USERDATAREMAPPER_IMPL_BCC_H_ diff --git a/src/kalypsso/core/UserDataRemapperImplBFC.cpp b/src/kalypsso/core/UserDataRemapperImplBFC.cpp new file mode 100644 index 0000000..72afc43 --- /dev/null +++ b/src/kalypsso/core/UserDataRemapperImplBFC.cpp @@ -0,0 +1,701 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file UserDataRemapperImplBFC.cpp + * \brief \copybrief UserDataRemapperImplBFC.h + */ +#include +#include + +namespace kalypsso +{ +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION void +UserDataRemapperImplBFC::operator()(TagComputeAllButInternalFaces const &, + const index_t & global_index) const +{ + const iOct_t iOct = global_index / m_facedata_old.num_elements_per_octant(); + const int32_t face_flat_index = + static_cast(global_index - iOct * m_facedata_old.num_elements_per_octant()); + auto const & bx = m_block_sizes[IX]; + + // + // the only reason of the following dummy code to be here, is that cuda nvcc compile doesn't + // support capturing variables inside inside a constexpr if section of a device lambda + // + // strangely, nvc++ is ok and don't need that + // TODO: remove theses lines when nvcc will have support for this. +#ifdef __NVCC__ + [[maybe_unused]] int dummy = 0; + if (m_facedata_new.num_quadrants() == m_facedata_old.num_quadrants() or bx == 0) + dummy++; +#endif + + // compute ix,iy,iz,ivar of local face inside + // block from a face flat-index + const auto face_indexes = face_flat_index_unravel( + face_flat_index, m_block_sizes, m_facedata_old.offsets(), m_facedata_old.shift()); + + const auto & ivar = face_indexes[dim]; + + // get orchard key of current block/octant + const auto key_new = m_orchard_keys_device_new(iOct); + + auto key_index = m_amr_hashmap_device_old.find(key_new); + + // first check if key exists in the old hashmap, if it exists, it means we have a + // an octant that didn't change level + if (m_amr_hashmap_device_old.valid_at(key_index)) + { + // auto key_old = amr_hashmap_device_old.key_at(key_index); + const auto iOct_old = m_amr_hashmap_device_old.value_at(key_index); + + // copy from old quadrant to new quadrant + + if constexpr (dim == 2) + { + auto const & i = face_indexes[IX]; + auto const & j = face_indexes[IY]; + m_facedata_new(i, j, ivar, iOct) = m_facedata_old(i, j, ivar, iOct_old); + } + else if constexpr (dim == 3) + { + auto const & i = face_indexes[IX]; + auto const & j = face_indexes[IY]; + auto const & k = face_indexes[IZ]; + m_facedata_new(i, j, k, ivar, iOct) = m_facedata_old(i, j, k, ivar, iOct_old); + } + } // end if old octant is at same level + else + { + // check if the key correspond to a refinement, aka prolongation (increase of level) + // to do that we search for father octant's key + auto key_old = orchard_key_t::father(key_new); + key_index = m_amr_hashmap_device_old.find(key_old); + + if (m_amr_hashmap_device_old.valid_at(key_index)) + { + const auto iOct_old = m_amr_hashmap_device_old.value_at(key_index); + + // get family id of the new key, used to determine where exactly in the old block we + // will copy data + const auto fid = orchard_key_t::family_id(key_new); + + // prolongation of external face + if (is_external_face(face_indexes)) + { + + if (m_prolongation.m_face_external == +FaceCenteredProlongationExternalType::SIMPLE_COPY) + { + external_faces_prolongation_by_copy(face_indexes, iOct, iOct_old, key_new, key_old, fid); + } + else if (m_prolongation.m_face_external == + +FaceCenteredProlongationExternalType::EXTRAPOLATE_LINEAR_MINMOD) + { + // TODO : prolongation by linear extrapolation + } + } + } // end if old octant is at finer level + else + { + // check if the key correspond to a "coarsening" (decrease of level) + // to do that we just need to search for the eldest child key in the old map, + // and then average all the values from all the children (coarse graining values) + + /* + * In principle, we should check that all the children are available in the map + * but here, we just check for the eldest child key, and assume all the children + * are also in the map (p4est ensure that anyway). + */ + + auto eldest_child_key = orchard_key_t::eldest_child(key_new); + key_index = m_amr_hashmap_device_old.find(eldest_child_key); + if (m_amr_hashmap_device_old.valid_at(key_index)) + { + auto iOct_eldest = m_amr_hashmap_device_old.value_at(key_index); + + // for a given cell determine the child id where we will need to fetch data in the + // old mesh + uint32_t ichild = 0; + if (face_indexes[IX] >= bx / 2) + ichild |= 0x1; + if (face_indexes[IY] >= bx / 2) + ichild |= 0x2; + if constexpr (dim == 3) + { + if (face_indexes[IZ] >= bx / 2) + { + ichild |= 0x4; + } + } + + // here we taken into account that a family of octant are stored contiguously in + // memory + auto iOct_old = iOct_eldest + ichild; + + // we need to average value over face siblings neighboring faces + real_t coarsen_value = ZERO_F; + if constexpr (dim == 2) + { + const auto i_old = + 2 * face_indexes[IX] >= bx ? 2 * face_indexes[IX] - bx : 2 * face_indexes[IX]; + const auto j_old = + 2 * face_indexes[IY] >= bx ? 2 * face_indexes[IY] - bx : 2 * face_indexes[IY]; + + if (ivar == IX) + { + coarsen_value += m_facedata_old(i_old, j_old, ivar, iOct_old); + coarsen_value += m_facedata_old(i_old, j_old + 1, ivar, iOct_old); + coarsen_value /= 2; + } + else if (ivar == IY) + { + coarsen_value += m_facedata_old(i_old, j_old, ivar, iOct_old); + coarsen_value += m_facedata_old(i_old + 1, j_old, ivar, iOct_old); + coarsen_value /= 2; + } + else if (ivar == IZ) + { + // clang-format off + coarsen_value += m_facedata_old(i_old , j_old , ivar, iOct_old); + coarsen_value += m_facedata_old(i_old + 1, j_old , ivar, iOct_old); + coarsen_value += m_facedata_old(i_old , j_old + 1, ivar, iOct_old); + coarsen_value += m_facedata_old(i_old + 1, j_old + 1, ivar, iOct_old); + coarsen_value /= 4; + // clang-format on + } + m_facedata_new(face_indexes[IX], face_indexes[IY], ivar, iOct) = coarsen_value; + } + else if constexpr (dim == 3) + { + const auto i_old = + 2 * face_indexes[IX] >= bx ? 2 * face_indexes[IX] - bx : 2 * face_indexes[IX]; + const auto j_old = + 2 * face_indexes[IY] >= bx ? 2 * face_indexes[IY] - bx : 2 * face_indexes[IY]; + const auto k_old = + 2 * face_indexes[IZ] >= bx ? 2 * face_indexes[IZ] - bx : 2 * face_indexes[IZ]; + + if (ivar == IX) + { + // clang-format off + coarsen_value += m_facedata_old(i_old, j_old , k_old , ivar, iOct_old); + coarsen_value += m_facedata_old(i_old, j_old + 1, k_old , ivar, iOct_old); + coarsen_value += m_facedata_old(i_old, j_old , k_old + 1, ivar, iOct_old); + coarsen_value += m_facedata_old(i_old, j_old + 1, k_old + 1, ivar, iOct_old); + // clang-format on + } + else if (ivar == IY) + { + // clang-format off + coarsen_value += m_facedata_old(i_old , j_old, k_old , ivar, iOct_old); + coarsen_value += m_facedata_old(i_old + 1, j_old, k_old , ivar, iOct_old); + coarsen_value += m_facedata_old(i_old , j_old, k_old + 1, ivar, iOct_old); + coarsen_value += m_facedata_old(i_old + 1, j_old, k_old + 1, ivar, iOct_old); + // clang-format on + } + else if (ivar == IZ) + { + // clang-format off + coarsen_value += m_facedata_old(i_old , j_old , k_old, ivar, iOct_old); + coarsen_value += m_facedata_old(i_old + 1, j_old , k_old, ivar, iOct_old); + coarsen_value += m_facedata_old(i_old , j_old + 1, k_old, ivar, iOct_old); + coarsen_value += m_facedata_old(i_old + 1, j_old + 1, k_old, ivar, iOct_old); + // clang-format on + } + m_facedata_new(face_indexes[IX], face_indexes[IY], face_indexes[IZ], ivar, iOct) = + coarsen_value / 4; + } + } + } // end if new octant is at coarser level + } + +} // operator() - all faces but not internal faces when prolongating + +// ============================================================== +// ============================================================== +template +KOKKOS_FUNCTION void +UserDataRemapperImplBFC::operator()(TagComputeInternalFaces const &, + const index_t & global_index) const +{ + + const iOct_t iOct = global_index / m_facedata_old.num_elements_per_octant(); + const int32_t face_flat_index = + static_cast(global_index - iOct * m_facedata_old.num_elements_per_octant()); + + // + // the only reason of the following dummy code to be here, is that cuda nvcc compile doesn't + // support capturing variables inside inside a constexpr if section of a device lambda + // + // strangely, nvc++ is ok and don't need that + // TODO: remove theses lines when nvcc will have support for this. +#ifdef __NVCC__ + [[maybe_unused]] int dummy = 0; + if (m_facedata_new.num_quadrants() == m_facedata_old.num_quadrants()) + dummy++; +#endif + + // compute ix,iy,iz,ivar of local face inside + // block from a face flat-index + const auto face_indexes = face_flat_index_unravel( + face_flat_index, m_block_sizes, m_facedata_old.offsets(), m_facedata_old.shift()); + + // get orchard key of current block/octant + const auto key_new = m_orchard_keys_device_new(iOct); + + // get orchard key of parent (it must exist when doing prolongation) + const auto key_old = orchard_key_t::father(key_new); + + auto key_old_index = m_amr_hashmap_device_old.find(key_old); + + // key must be valid as we are only interested in prologation (from coarse to fine level) + if (m_amr_hashmap_device_old.valid_at(key_old_index)) + { + if (is_internal_face(face_indexes)) + { + + if (m_prolongation.m_face_internal == +FaceCenteredProlongationInternalType::TOTH_AND_ROE) + { + internal_faces_prolongation_by_toth_and_roe(face_indexes, iOct); + } + + } // end is_internal_face + } // end if old octant is at finer level + +} // operator () - prolongation at internal faces + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +UserDataRemapperImplBFC::external_faces_prolongation_by_copy( + face_multiindex_t const & face_indexes_new, + iOct_t const & iOct_new, + iOct_t const & iOct_old, + uint64_t const & key_new, + uint64_t const & key_old, + uint32_t family_id) const +{ + auto const & bx = m_block_sizes[IX]; + const auto & ivar = face_indexes_new[dim]; + + if constexpr (dim == 2) + { + auto const & i_new = face_indexes_new[IX]; + auto const & j_new = face_indexes_new[IY]; + + const auto i_old = i_new / 2 + static_cast((family_id & 0x1) >> 0) * (bx / 2); + const auto j_old = j_new / 2 + static_cast((family_id & 0x2) >> 1) * (bx / 2); + + // convert face index into cell index, i.e. in range [0, bx-1] + const coord_t ijk_old{ i_old == bx ? i_old - 1 : i_old, j_old == bx ? j_old - 1 : j_old }; + + const CellLocation_t cell_loc_old{ ijk_old, key_old, iOct_old, false }; + + // the following initialization is also valid when ivar = IZ + auto old_value = m_facedata_old(i_old, j_old, ivar, iOct_old); + + // first check if face is at block border of old octant, and if neighbor is already at fine + // in that case we copy data from neighbor + if (ivar == IX and i_old == 0) + { + const auto cell_loc_neigh = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(-XDIR)); + + if (cell_loc_neigh.level() == orchard_key_t::level(key_new)) + { + old_value = m_facedata_old(bx, j_new, ivar, cell_loc_neigh.iOct); + } + } + else if (ivar == IX and i_old == bx) + { + const auto cell_loc_neigh = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(+XDIR)); + + if (cell_loc_neigh.level() == orchard_key_t::level(key_new)) + { + old_value = m_facedata_old(0, j_new, ivar, cell_loc_neigh.iOct); + } + } + else if (ivar == IY and j_old == 0) + { + const auto cell_loc_neigh = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(-YDIR)); + + if (cell_loc_neigh.level() == orchard_key_t::level(key_new)) + { + old_value = m_facedata_old(i_new, bx, ivar, cell_loc_neigh.iOct); + } + } + else if (ivar == IY and j_old == bx) + { + const auto cell_loc_neigh = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(+YDIR)); + + if (cell_loc_neigh.level() == orchard_key_t::level(key_new)) + { + old_value = m_facedata_old(i_new, 0, ivar, cell_loc_neigh.iOct); + } + } + + // copy data from the old mesh (at coarse level) + m_facedata_new(i_new, j_new, ivar, iOct_new) = old_value; + } + else // 3D + { + auto const & i_new = face_indexes_new[IX]; + auto const & j_new = face_indexes_new[IY]; + auto const & k_new = face_indexes_new[IZ]; + + const auto i_old = i_new / 2 + static_cast((family_id & 0x1) >> 0) * (bx / 2); + const auto j_old = j_new / 2 + static_cast((family_id & 0x2) >> 1) * (bx / 2); + const auto k_old = k_new / 2 + static_cast((family_id & 0x4) >> 2) * (bx / 2); + + // convert face index into cell index, i.e. in range [0, bx-1] + const coord_t ijk_old{ i_old == bx ? i_old - 1 : i_old, + j_old == bx ? j_old - 1 : j_old, + k_old == bx ? k_old - 1 : k_old }; + + const CellLocation_t cell_loc_old{ ijk_old, key_old, iOct_old, false }; + + auto old_value = m_facedata_old(i_old, j_old, k_old, ivar, iOct_old); + + // first check if face is at block border of old octant, and if neighbor is already at fine + // in that case we copy data from neighbor + if (ivar == IX and i_old == 0) + { + const auto cell_loc_neigh = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(-XDIR)); + + if (cell_loc_neigh.level() == orchard_key_t::level(key_new)) + { + old_value = m_facedata_old(bx, j_new, k_new, ivar, cell_loc_neigh.iOct); + } + } + else if (ivar == IX and i_old == bx) + { + const auto cell_loc_neigh = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(+XDIR)); + + if (cell_loc_neigh.level() == orchard_key_t::level(key_new)) + { + old_value = m_facedata_old(0, j_new, k_new, ivar, cell_loc_neigh.iOct); + } + } + else if (ivar == IY and j_old == 0) + { + const auto cell_loc_neigh = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(-YDIR)); + + if (cell_loc_neigh.level() == orchard_key_t::level(key_new)) + { + old_value = m_facedata_old(i_new, bx, k_new, ivar, cell_loc_neigh.iOct); + }; + } + else if (ivar == IY and j_old == bx) + { + const auto cell_loc_neigh = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(+YDIR)); + + if (cell_loc_neigh.level() == orchard_key_t::level(key_new)) + { + old_value = m_facedata_old(i_new, 0, k_new, ivar, cell_loc_neigh.iOct); + } + } + else if (ivar == IZ and k_old == 0) + { + const auto cell_loc_neigh = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(-ZDIR)); + + if (cell_loc_neigh.level() == orchard_key_t::level(key_new)) + { + old_value = m_facedata_old(i_new, j_new, bx, ivar, cell_loc_neigh.iOct); + }; + } + else if (ivar == IZ and k_old == bx) + { + const auto cell_loc_neigh = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(+ZDIR)); + + if (cell_loc_neigh.level() == orchard_key_t::level(key_new)) + { + old_value = m_facedata_old(i_new, j_new, 0, ivar, cell_loc_neigh.iOct); + } + } + + // copy data from the old mesh (at coarse level) + m_facedata_new(i_new, j_new, k_new, ivar, iOct_new) = old_value; + + } // end 3D + +} // external_faces_prolongation_by_copy + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +UserDataRemapperImplBFC::internal_faces_prolongation_by_toth_and_roe( + face_multiindex_t const & face_indexes_new, + iOct_t const & iOct_new) const +{ + const auto & ivar = face_indexes_new[dim]; + + if constexpr (dim == 2) + { + auto const & i_new = face_indexes_new[IX]; + auto const & j_new = face_indexes_new[IY]; + + // "eldest" face : all coords even + const auto i_e = (i_new / 2) * 2; + const auto j_e = (j_new / 2) * 2; + + // reminder: we must use m_facedata_new for accessing external faces + + // note : when ivar = IZ (in 2D) there are no "internal" faces + // in other words, ivar = IZ is already taken into account when filling external faces + + real_t Uxx = ZERO_F; + real_t Vyy = ZERO_F; + for (int j = -1; j <= 1; j += 2) + for (int i = -1; i <= 1; i += 2) + { + // clang-format off + Uxx += static_cast(i * j) * m_facedata_new(i_e + (i + 1) / 2, j_e + (j + 1) , IY, iOct_new); + Vyy += static_cast(i * j) * m_facedata_new(i_e + (i + 1) , j_e + (j + 1) / 2, IX, iOct_new); + // clang-format on + } + Uxx /= 4; + Vyy /= 4; + + if (ivar == IX) + { + auto mid_value = HALF_F * (m_facedata_new(i_new - 1, j_new, IX, iOct_new) + + m_facedata_new(i_new + 1, j_new, IX, iOct_new)); + + m_facedata_new(i_new, j_new, ivar, iOct_new) = mid_value + Uxx; + } + else if (ivar == IY) + { + auto mid_value = HALF_F * (m_facedata_new(i_new, j_new - 1, IY, iOct_new) + + m_facedata_new(i_new, j_new + 1, IY, iOct_new)); + + m_facedata_new(i_new, j_new, ivar, iOct_new) = mid_value + Vyy; + } + } + else if constexpr (dim == 3) + { + auto const & i_new = face_indexes_new[IX]; + auto const & j_new = face_indexes_new[IY]; + auto const & k_new = face_indexes_new[IZ]; + + // "eldest" face : all coords even + const auto i_e = (i_new / 2) * 2; + const auto j_e = (j_new / 2) * 2; + const auto k_e = (k_new / 2) * 2; + + // compute local face index among group of 4 co-planar faces + const int ii = 2 * (i_new - i_e) - 1; + const int jj = 2 * (j_new - j_e) - 1; + const int kk = 2 * (k_new - k_e) - 1; + + real_t Uxx = ZERO_F; + real_t Vyy = ZERO_F; + real_t Wzz = ZERO_F; + + real_t Uxyz = ZERO_F; + real_t Vxyz = ZERO_F; + real_t Wxyz = ZERO_F; + + // reminder: we must use m_facedata_new for accessing external faces + + for (int k = -1; k <= 1; k += 2) + for (int j = -1; j <= 1; j += 2) + for (int i = -1; i <= 1; i += 2) + { + // clang-format off + Uxx += + static_cast(i * j) * + m_facedata_new(i_e + (i + 1) / 2, j_e + (j + 1) , k_e + (k + 1) / 2, IY, iOct_new); + Uxx += + static_cast(i * k) * + m_facedata_new(i_e + (i + 1) / 2, j_e + (j + 1) / 2, k_e + (k + 1) , IZ, iOct_new); + + Vyy += + static_cast(j * k) * + m_facedata_new(i_e + (i + 1) / 2, j_e + (j + 1) / 2, k_e + (k + 1) , IZ, iOct_new); + Vyy += + static_cast(j * i) * + m_facedata_new(i_e + (i + 1) , j_e + (j + 1) / 2, k_e + (k + 1) / 2, IX, iOct_new); + + Wzz += + static_cast(k * i) * + m_facedata_new(i_e + (i + 1) , j_e + (j + 1) / 2, k_e + (k + 1) / 2, IX, iOct_new); + Wzz += + static_cast(k * j) * + m_facedata_new(i_e + (i + 1) / 2, j_e + (j + 1) , k_e + (k + 1) / 2, IY, iOct_new); + + Uxyz += + static_cast(i * j * k) * + m_facedata_new(i_e + (i + 1) , j_e + (j + 1) / 2, k_e + (k + 1) / 2, IX, iOct_new); + + Vxyz += + static_cast(i * j * k) * + m_facedata_new(i_e + (i + 1) / 2, j_e + (j + 1) , k_e + (k + 1) / 2, IY, iOct_new); + + Wxyz += + static_cast(i * j * k) * + m_facedata_new(i_e + (i + 1) / 2, j_e + (j + 1) / 2, k_e + (k + 1) , IZ, iOct_new); + // clang-format on + } + Uxx /= 8; + Vyy /= 8; + Wzz /= 8; + Uxyz /= 16; // because Delta x = Delta y = Delta z + Vxyz /= 16; // because Delta x = Delta y = Delta z + Wxyz /= 16; // because Delta x = Delta y = Delta z + + if (ivar == IX) + { + auto mid_value = HALF_F * (m_facedata_new(i_new - 1, j_new, k_new, IX, iOct_new) + + m_facedata_new(i_new + 1, j_new, k_new, IX, iOct_new)); + + m_facedata_new(i_new, j_new, k_new, ivar, iOct_new) = + mid_value + Uxx + static_cast(kk) * Vxyz + static_cast(jj) * Wxyz; + } + if (ivar == IY) + { + auto mid_value = HALF_F * (m_facedata_new(i_new, j_new - 1, k_new, IY, iOct_new) + + m_facedata_new(i_new, j_new + 1, k_new, IY, iOct_new)); + + m_facedata_new(i_new, j_new, k_new, ivar, iOct_new) = + mid_value + Vyy + static_cast(ii) * Wxyz + static_cast(kk) * Uxyz; + } + if (ivar == IZ) + { + auto mid_value = HALF_F * (m_facedata_new(i_new, j_new, k_new - 1, IZ, iOct_new) + + m_facedata_new(i_new, j_new, k_new + 1, IZ, iOct_new)); + + m_facedata_new(i_new, j_new, k_new, ivar, iOct_new) = + mid_value + Wzz + static_cast(jj) * Uxyz + static_cast(ii) * Vxyz; + } + + } // end 3D + +} // internal_faces_prolongation_by_toth_and_roe + +// ============================================================== +// ============================================================== +template +template > +KOKKOS_INLINE_FUNCTION void +UserDataRemapperImplBFC::linear_extrapolate_using_limited_slopes( + CellLocation<2> const & cell_loc_old, + coord_t<2> const & coord_new, + index_t const & cellindex_new, + int32_t const & iOct_global) const +{ + // const real_t slope_type = 1; + // const auto nbvar = m_facedata_old.num_vars(); + + // // get neighbor location, each of then may be at finer, same or coarser level compared + // // cell_loc_old + // const auto cell_loc_left_x = + // m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(-XDIR)); + // const auto cell_loc_right_x = + // m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(+XDIR)); + + // const auto cell_loc_left_y = + // m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(-YDIR)); + // const auto cell_loc_right_y = + // m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(+YDIR)); + + // // determine local position of current new cell inside old parent cell using integer + // coordinates + // // in -1, +1 + // const int ix = 2 * (coord_new[IX] - 2 * (coord_new[IX] / 2)) - 1; + // const int iy = 2 * (coord_new[IY] - 2 * (coord_new[IY] / 2)) - 1; + + // for (int32_t ivar = 0; ivar < nbvar; ++ivar) + // { + // // compute limited slopes + // auto const dudx = m_stencil_helper.compute_minmod_slopes_prolongation( + // cell_loc_old, cell_loc_right_x, cell_loc_left_x, ivar, m_facedata_old, slope_type); + // auto const dudy = m_stencil_helper.compute_minmod_slopes_prolongation( + // cell_loc_old, cell_loc_right_y, cell_loc_left_y, ivar, m_facedata_old, slope_type); + + // // extrapolate + // m_facedata_new(cellindex_new, ivar, iOct_global) = + // m_facedata_old(cell_loc_old.cellindex(m_block_sizes), ivar, cell_loc_old.iOct) + + // ONE_FOURTH_F * ix * dudx + ONE_FOURTH_F * iy * dudy; + // } +} // linear_extrapolate_using_limited_slopes - 2d + +// ============================================================== +// ============================================================== +template +template > +KOKKOS_INLINE_FUNCTION void +UserDataRemapperImplBFC::linear_extrapolate_using_limited_slopes( + CellLocation<3> const & cell_loc_old, + coord_t<3> const & coord_new, + index_t const & cellindex_new, + int32_t const & iOct_global) const +{ + // const real_t slope_type = 1; + // const auto nbvar = m_facedata_old.num_vars(); + + // // get neighbor location, each of then may be at finer, same or coarser level compared + // // cell_loc_old + // const auto cell_loc_left_x = + // m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(-XDIR)); + // const auto cell_loc_right_x = + // m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(+XDIR)); + + // const auto cell_loc_left_y = + // m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(-YDIR)); + // const auto cell_loc_right_y = + // m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(+YDIR)); + + // const auto cell_loc_left_z = + // m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(-ZDIR)); + // const auto cell_loc_right_z = + // m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(+ZDIR)); + + // // determine local position of current new cell inside old parent cell using integer + // coordinates + // // in -1, +1 + // const int ix = 2 * (coord_new[IX] - 2 * (coord_new[IX] / 2)) - 1; + // const int iy = 2 * (coord_new[IY] - 2 * (coord_new[IY] / 2)) - 1; + // const int iz = 2 * (coord_new[IZ] - 2 * (coord_new[IZ] / 2)) - 1; + + // for (int32_t ivar = 0; ivar < nbvar; ++ivar) + // { + // // compute limited slopes + // auto const dudx = m_stencil_helper.compute_minmod_slopes_prolongation( + // cell_loc_old, cell_loc_right_x, cell_loc_left_x, ivar, m_facedata_old, slope_type); + // auto const dudy = m_stencil_helper.compute_minmod_slopes_prolongation( + // cell_loc_old, cell_loc_right_y, cell_loc_left_y, ivar, m_facedata_old, slope_type); + // auto const dudz = m_stencil_helper.compute_minmod_slopes_prolongation( + // cell_loc_old, cell_loc_right_z, cell_loc_left_z, ivar, m_facedata_old, slope_type); + + // // extrapolate + // m_facedata_new(cellindex_new, ivar, iOct_global) = + // m_facedata_old(cell_loc_old.cellindex(m_block_sizes), ivar, cell_loc_old.iOct) + + // ONE_FOURTH_F * ix * dudx + ONE_FOURTH_F * iy * dudy + ONE_FOURTH_F * iz * dudz; + // } +} // linear_extrapolate_using_limited_slopes - 3d + +// explicit template instantiation +template class UserDataRemapperImplBFC<2, kalypsso::DefaultDevice>; +template class UserDataRemapperImplBFC<3, kalypsso::DefaultDevice>; + +#ifdef KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE +template class UserDataRemapperImplBFC<2, HostDevice>; +template class UserDataRemapperImplBFC<3, HostDevice>; +#endif // KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE + +} // namespace kalypsso diff --git a/src/kalypsso/core/UserDataRemapperImplBFC.h b/src/kalypsso/core/UserDataRemapperImplBFC.h new file mode 100644 index 0000000..c760882 --- /dev/null +++ b/src/kalypsso/core/UserDataRemapperImplBFC.h @@ -0,0 +1,192 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file UserDataRemapperImplBFC.h + * + * \brief Implement actual user data remapping (after mesh change during AMR cycle) for block + * face-centered (BFC) data. + */ +#ifndef KALYPSSO_CORE_USERDATAREMAPPER_IMPL_BFC_H_ +#define KALYPSSO_CORE_USERDATAREMAPPER_IMPL_BFC_H_ + +#include +#include +#include // for FaceDataArrayBlock + +#include +#include + +#include +#include +#include +#include // for get_brick_periodicity + +namespace kalypsso +{ +/** + * Implementation details class to be instantiated inside UserDataRemapper. + */ +template +class UserDataRemapperImplBFC +{ +public: + using index_t = int32_t; + + // using DataArrayLeaf_t = DataArrayLeaf; + using DataArrayBlock_t = DataArrayBlock; + using FaceDataArrayBlock_t = FaceDataArrayBlock; + + using amr_hashmap_t = typename hashmap_base_t::map_t; + using orchard_key_view_t = typename orchard_key_base_t::view_t; + + using CellLocation_t = CellLocation; + using StencilHelper_t = StencilHelper; + + using ExecutionSpace = typename device_t::execution_space; + + //! Compute everything Remapping cell at same AMR level, restriction and only partial prolongation + //! (only external faces) + struct TagComputeAllButInternalFaces + {}; + + //! Finalize the remapping by doing prolongation at internal faces + struct TagComputeInternalFaces + {}; + + UserDataRemapperImplBFC() = delete; + UserDataRemapperImplBFC(const FaceDataArrayBlock_t facedata_old, + FaceDataArrayBlock_t facedata_new, + amr_hashmap_t amr_hashmap_old, + orchard_key_view_t orchard_keys_new, + orchard_key_view_t orchard_keys_old, + int32_t local_num_octants, + block_size_t bSizes, + ConfigMap const & config_map) + : m_facedata_old(facedata_old) + , m_facedata_new(facedata_new) + , m_amr_hashmap_device_old(amr_hashmap_old) + , m_orchard_keys_device_new(orchard_keys_new) + , m_local_num_octants_new(local_num_octants) + , m_block_sizes(bSizes) + , m_prolongation(ProlongationParam(config_map)) + , m_stencil_helper(amr_hashmap_old, + orchard_keys_old, + facedata_old.cell_block_size(), + get_brick_sizes(config_map), + get_brick_periodicity(config_map)) + { + if (m_prolongation.m_face_external == + +FaceCenteredProlongationExternalType::EXTRAPOLATE_LINEAR_MINMOD) + { + KALYPSSO_ERROR( + "FaceCenteredProlongationExternalType::EXTRAPOLATE_LINEAR_MINMOD : not yet implemented"); + } + } + + // ============================================================== + // ============================================================== + /** + * Perform remapping: + * - restriction + * - same level + * - prolongation of external faces + */ + KOKKOS_FUNCTION void + operator()(TagComputeAllButInternalFaces const &, const index_t & global_index) const; + + // ============================================================== + // ============================================================== + /** + * Perform remapping: + * - prolongation of internal faces + * + * This is where divergence preserving formulas (e.g. Toth and Roe 2002) are implemented. + */ + KOKKOS_FUNCTION void + operator()(TagComputeInternalFaces const &, const index_t & global_index) const; + +private: + /** + * Do prolongation of external faces by simple copy (injection). + */ + KOKKOS_INLINE_FUNCTION void + external_faces_prolongation_by_copy(face_multiindex_t const & face_indexes_new, + iOct_t const & iOct_new, + iOct_t const & iOct_old, + uint64_t const & key_new, + uint64_t const & key_old, + uint32_t family_id) const; + + /** + * Do prolongation of internal faces using divergence preserving method by Roe and Toth 2002. + * + * reference: + * - "Divergence- and curl-preserving prolongation and restriction formulas.", Toth and Roe, JCP, + * 180, 746-759, 2002. https://doi.org/10.1006/jcph.2002.7120 + */ + KOKKOS_INLINE_FUNCTION void + internal_faces_prolongation_by_toth_and_roe(face_multiindex_t const & face_indexes_new, + iOct_t const & iOct_new) const; + /** + * Do linear extrapolation using limited slopes on external faces. + */ + template = true> + KOKKOS_INLINE_FUNCTION void + linear_extrapolate_using_limited_slopes(CellLocation<2> const & cell_loc_old, + coord_t<2> const & coord_new, + index_t const & cellindex_new, + int32_t const & iOct_global) const; + + /** + * Do linear extrapolation using limited slopes on external faces. + */ + template = true> + KOKKOS_INLINE_FUNCTION void + linear_extrapolate_using_limited_slopes(CellLocation<3> const & cell_loc_old, + coord_t<3> const & coord_new, + index_t const & cellindex_new, + int32_t const & iOct_global) const; + + +private: + //! source data array + const FaceDataArrayBlock_t m_facedata_old; + + //! destination data array (where data will be remapped) + FaceDataArrayBlock_t m_facedata_new; + + //! AMR unordered map which map orchard keys to octant number for all key in the mesh + //! before AMR cycle modification + amr_hashmap_t m_amr_hashmap_device_old; + + //! list of orchard key of the new mesh (after AMR cycle modification) + orchard_key_view_t m_orchard_keys_device_new; + + //! number of octants in the new mesh + int32_t m_local_num_octants_new; + + //! block sizes + block_size_t m_block_sizes; + + //! prolongation parameter + const ProlongationParam m_prolongation; + + //! helper to compute neighbor cell location + StencilHelper_t m_stencil_helper; + +}; // class UserDataRemapperImplBFC + +// explicit template instantiation +extern template class UserDataRemapperImplBFC<2, kalypsso::DefaultDevice>; +extern template class UserDataRemapperImplBFC<3, kalypsso::DefaultDevice>; + +#ifdef KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE +extern template class UserDataRemapperImplBFC<2, HostDevice>; +extern template class UserDataRemapperImplBFC<3, HostDevice>; +#endif // KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_USERDATAREMAPPER_IMPL_BFC_H_ diff --git a/src/kalypsso/core/UserDataRemapperImplMD.cpp b/src/kalypsso/core/UserDataRemapperImplMD.cpp new file mode 100644 index 0000000..9355e23 --- /dev/null +++ b/src/kalypsso/core/UserDataRemapperImplMD.cpp @@ -0,0 +1,442 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file UserDataRemapperImplMP.cpp + */ + +#include +#include // for compute_face_neighbor_key_finer + +namespace kalypsso +{ + +// ================================================================================================ +// ================================================================================================ +template +UserDataRemapperImplMD::UserDataRemapperImplMD(DataArrayBlockMultiVar_t old_data, + MaterialPresenceView_t old_mat, + DataArrayBlockMultiVar_t new_data, + MaterialPresenceView_t new_mat, + AmrHashmap_t amr_hashmap_device_old, + OrchardKeys_t orchard_keys_device_old, + OrchardKeys_t orchard_keys_device_new, + const uint32_t num_vars_per_mat, + const ConfigMap & config_map) + : m_old_data(old_data) + , m_old_mat(old_mat) + , m_new_data(new_data) + , m_new_mat(new_mat) + , m_amr_hashmap_device_old(amr_hashmap_device_old) + , m_orchard_keys_device_old(orchard_keys_device_old) + , m_orchard_keys_device_new(orchard_keys_device_new) + , m_stencil_helper(amr_hashmap_device_old, + orchard_keys_device_old, + old_data.shape(), + get_brick_sizes(config_map), + get_brick_periodicity(config_map)) + , m_prolongation(get_cell_prolongation_type(config_map)) + , m_num_vars_per_mat(static_cast(num_vars_per_mat)) + , m_block_sizes(old_data.shape()) +{} + +// ================================================================================================ +// ================================================================================================ +template +KOKKOS_FUNCTION void +UserDataRemapperImplMD::operator()(const int32_t i_new) const +{ + const auto num_cells = m_new_data.num_cells(); + + const auto i_new_oct = i_new / num_cells; + const auto i_new_cell = i_new - i_new_oct * num_cells; + + const auto nbmat = m_new_mat.num_materials(i_new_oct); + + const auto new_key = m_orchard_keys_device_new(i_new_oct); + const auto new_key_index = m_amr_hashmap_device_old.find(new_key); + + // It hasn't changed + if (m_amr_hashmap_device_old.valid_at(new_key_index)) + { + const auto i_old_oct = static_cast(m_amr_hashmap_device_old.value_at(new_key_index)); + const auto i_old_cell = i_new_cell; + + for (int32_t imat = 0; imat < nbmat; ++imat) + { + const auto mat_num = m_new_mat.material_num(i_new_oct, imat); + + if (m_old_mat.get(i_old_oct, mat_num)) // Is the material present in the source octant? + for (int32_t ivar = 0; ivar < m_num_vars_per_mat; ++ivar) + { + const auto old_ivar = + ivar + m_num_vars_per_mat * m_old_mat.material_index(i_old_oct, mat_num); + const auto new_ivar = ivar + m_num_vars_per_mat * imat; + + m_new_data(i_new_cell, new_ivar, i_new_oct) = m_old_data(i_old_cell, old_ivar, i_old_oct); + } + else + for (int32_t ivar = 0; ivar < m_num_vars_per_mat; ++ivar) + { + const auto new_ivar = ivar + m_num_vars_per_mat * imat; + m_new_data(i_new_cell, new_ivar, i_new_oct) = 0; + } + } + + return; + } + + const auto new_father_key = orchard_key_t::father(new_key); + const auto new_father_key_index = m_amr_hashmap_device_old.find(new_father_key); + + // It was refined + if (m_amr_hashmap_device_old.valid_at(new_father_key_index)) + { + const auto i_old_oct = + static_cast(m_amr_hashmap_device_old.value_at(new_father_key_index)); + const auto child_id = static_cast(orchard_key_t::family_id(new_key)); + + const auto i_new_coord = cellindex_to_coord(i_new_cell, m_block_sizes); + coord_t i_old_coord; + + i_old_coord[IX] = i_new_coord[IX] / 2 + ((child_id & 0b001) >> 0) * (m_block_sizes[IX] / 2); + i_old_coord[IY] = i_new_coord[IY] / 2 + ((child_id & 0b010) >> 1) * (m_block_sizes[IY] / 2); + if constexpr (dim == 3) + i_old_coord[IZ] = i_new_coord[IZ] / 2 + ((child_id & 0b100) >> 2) * (m_block_sizes[IZ] / 2); + + const auto i_old_cell = coord_to_cellindex(i_old_coord, m_block_sizes); + + if (m_prolongation == +CellCenteredProlongationType::SIMPLE_COPY) + for (int32_t imat = 0; imat < nbmat; ++imat) + { + const auto mat_num = m_new_mat.material_num(i_new_oct, imat); + + if (m_old_mat.get(i_old_oct, mat_num)) // Is the material present in the source octant? + for (int32_t ivar = 0; ivar < m_num_vars_per_mat; ++ivar) + { + const auto old_ivar = + ivar + m_num_vars_per_mat * m_old_mat.material_index(i_old_oct, mat_num); + const auto new_ivar = ivar + m_num_vars_per_mat * imat; + + m_new_data(i_new_cell, new_ivar, i_new_oct) = + m_old_data(i_old_cell, old_ivar, i_old_oct); + } + else + for (int32_t ivar = 0; ivar < m_num_vars_per_mat; ++ivar) + { + const auto out_ivar = ivar + m_num_vars_per_mat * imat; + m_new_data(i_new_cell, out_ivar, i_new_oct) = 0; + } + } + else if (m_prolongation == +CellCenteredProlongationType::EXTRAPOLATE_LINEAR_MINMOD) + { + const CellLocation cell_loc{ i_old_coord, new_father_key, i_old_oct, false }; + linear_extrapolate_using_limited_slopes(cell_loc, i_new_coord, i_new_cell, i_new_oct); + } + + return; + } + + // This means it was coarsened so we have to regroup all of the material of its children + const auto new_child_key = orchard_key_t::eldest_child(new_key); + const auto new_child_key_index = m_amr_hashmap_device_old.find(new_child_key); + + // Must be valid + if (m_amr_hashmap_device_old.valid_at(new_child_key_index)) + { + const auto i_eldest_child_oct = + static_cast(m_amr_hashmap_device_old.value_at(new_child_key_index)); + const auto i_new_coord = cellindex_to_coord(i_new_cell, m_block_sizes); + + coord_t i_old_coord = 2 * i_new_coord; + uint8_t i_child = 0; + if (i_new_coord[IX] >= m_block_sizes[IX] / 2) + { + i_old_coord[IX] -= m_block_sizes[IX]; + i_child |= 0x1; + } + if (i_new_coord[IY] >= m_block_sizes[IY] / 2) + { + i_old_coord[IY] -= m_block_sizes[IY]; + i_child |= 0x2; + } + if constexpr (dim == 3) + if (i_new_coord[IZ] >= m_block_sizes[IZ] / 2) + { + i_old_coord[IZ] -= m_block_sizes[IZ]; + i_child |= 0x4; + } + + const auto i_old_oct = i_eldest_child_oct + i_child; + const auto i_old_key = m_orchard_keys_device_old(i_old_oct); + + for (int32_t imat = 0; imat < nbmat; ++imat) + { + const auto mat_num = m_new_mat.material_num(i_new_oct, imat); + + if (m_old_mat.get(i_old_oct, mat_num)) // Is the material present in the source octant? + for (int32_t ivar = 0; ivar < m_num_vars_per_mat; ++ivar) + { + const auto new_ivar = ivar + m_num_vars_per_mat * imat; + const auto old_ivar = + ivar + m_num_vars_per_mat * m_old_mat.material_index(i_old_oct, mat_num); + + const CellLocation cell_loc{ i_old_coord, i_old_key, i_old_oct, false }; + m_new_data(i_new_cell, new_ivar, i_new_oct) = m_stencil_helper.compute_siblings_average( + cell_loc, m_block_sizes, old_ivar, m_old_data); + } + else + for (int32_t ivar = 0; ivar < m_num_vars_per_mat; ++ivar) + { + const auto new_ivar = ivar + m_num_vars_per_mat * imat; + m_new_data(i_new_cell, new_ivar, i_new_oct) = 0; + } + } + + return; + } +} + +// ================================================================================================ +// ================================================================================================ +template class UserDataRemapperImplMD<2, kalypsso::DefaultDevice>; +template class UserDataRemapperImplMD<3, kalypsso::DefaultDevice>; + +#ifdef KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE +template class UserDataRemapperImplMD<2, HostDevice>; +template class UserDataRemapperImplMD<3, HostDevice>; +#endif // KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE + +// ================================================================================================ +// ================================================================================================ +// INNER FUNCTIONS ADAPTED FROM UserDataRemapperImplBCC +// ================================================================================================ +// ================================================================================================ +template +template > +KOKKOS_INLINE_FUNCTION void +UserDataRemapperImplMD::linear_extrapolate_using_limited_slopes( + CellLocation<2> const & cell_loc_old, + coord_t<2> const & coord_new, + int32_t const & i_new_cell, + int32_t const & i_new_oct) const +{ + const real_t slope_type = 1; + + // get neighbor location, each of then may be at finer, same or coarser level compared + // cell_loc_old + const auto cell_loc_left_x = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(-XDIR)); + const auto cell_loc_right_x = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(+XDIR)); + + const auto cell_loc_left_y = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(-YDIR)); + const auto cell_loc_right_y = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(+YDIR)); + + // determine local position of current new cell inside old parent cell using integer + // coordinates in -1, +1 + const int ix = 2 * (coord_new[IX] - 2 * (coord_new[IX] / 2)) - 1; + const int iy = 2 * (coord_new[IY] - 2 * (coord_new[IY] / 2)) - 1; + + const auto nbmat = m_new_mat.num_materials(i_new_oct); + + for (int32_t imat = 0; imat < nbmat; ++imat) + { + // Extrapolation needs variable at each face so we check if it is present across each face. + const auto mat_num = m_new_mat.material_num(i_new_oct, imat); + const bool has_mat = m_old_mat.get(static_cast(cell_loc_left_x.iOct), mat_num) && + m_old_mat.get(static_cast(cell_loc_right_x.iOct), mat_num) && + m_old_mat.get(static_cast(cell_loc_left_y.iOct), mat_num) && + m_old_mat.get(static_cast(cell_loc_right_y.iOct), mat_num) && + m_old_mat.get(static_cast(cell_loc_old.iOct), mat_num); + + if (has_mat) // Is the material present in the source octant? + for (int32_t ivar = 0; ivar < m_num_vars_per_mat; ++ivar) + { + const auto old_ivar = + ivar + m_num_vars_per_mat * + m_old_mat.material_index(static_cast(cell_loc_old.iOct), mat_num); + + const auto old_ivar_left_x = + ivar + m_num_vars_per_mat * + m_old_mat.material_index(static_cast(cell_loc_left_x.iOct), mat_num); + + const auto old_ivar_right_x = + ivar + m_num_vars_per_mat * + m_old_mat.material_index(static_cast(cell_loc_right_x.iOct), mat_num); + + const auto old_ivar_left_y = + ivar + m_num_vars_per_mat * + m_old_mat.material_index(static_cast(cell_loc_left_y.iOct), mat_num); + + const auto old_ivar_right_y = + ivar + m_num_vars_per_mat * + m_old_mat.material_index(static_cast(cell_loc_right_y.iOct), mat_num); + + const auto new_ivar = ivar + m_num_vars_per_mat * imat; + + // compute limited slopes + auto const dudx = m_stencil_helper.compute_minmod_slopes_prolongation(cell_loc_old, + old_ivar, + cell_loc_right_x, + old_ivar_right_x, + cell_loc_left_x, + old_ivar_left_x, + m_old_data, + slope_type); + + auto const dudy = m_stencil_helper.compute_minmod_slopes_prolongation(cell_loc_old, + old_ivar, + cell_loc_right_y, + old_ivar_right_y, + cell_loc_left_y, + old_ivar_left_y, + m_old_data, + slope_type); + + // extrapolate + m_new_data(i_new_cell, new_ivar, i_new_oct) = + m_old_data(cell_loc_old.cellindex(m_block_sizes), old_ivar, cell_loc_old.iOct) + + ONE_FOURTH_F * static_cast(ix) * dudx + + ONE_FOURTH_F * static_cast(iy) * dudy; + } + else // If not, set the value to 0 (maybe best to switch extrapolation method?) + for (int32_t ivar = 0; ivar < m_num_vars_per_mat; ++ivar) + { + const auto new_ivar = ivar + m_num_vars_per_mat * imat; + m_new_data(i_new_cell, new_ivar, i_new_oct) = 0; + } + } +} + +// ================================================================================================ +// ================================================================================================ +template +template > +KOKKOS_INLINE_FUNCTION void +UserDataRemapperImplMD::linear_extrapolate_using_limited_slopes( + CellLocation<3> const & cell_loc_old, + coord_t<3> const & coord_new, + int32_t const & i_new_cell, + int32_t const & i_new_oct) const +{ + const real_t slope_type = 1; + + // get neighbor location, each of then may be at finer, same or coarser level compared + // cell_loc_old + const auto cell_loc_left_x = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(-XDIR)); + const auto cell_loc_right_x = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(+XDIR)); + + const auto cell_loc_left_y = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(-YDIR)); + const auto cell_loc_right_y = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(+YDIR)); + + const auto cell_loc_left_z = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(-ZDIR)); + const auto cell_loc_right_z = + m_stencil_helper.getNeighLoc(cell_loc_old, m_stencil_helper.unit_shift(+ZDIR)); + + // determine local position of current new cell inside old parent cell using integer + // coordinates in -1, +1 + const int ix = 2 * (coord_new[IX] - 2 * (coord_new[IX] / 2)) - 1; + const int iy = 2 * (coord_new[IY] - 2 * (coord_new[IY] / 2)) - 1; + const int iz = 2 * (coord_new[IZ] - 2 * (coord_new[IZ] / 2)) - 1; + + const auto nbmat = m_new_mat.num_materials(i_new_oct); + + for (int32_t imat = 0; imat < nbmat; ++imat) + { + // Extrapolation needs variable at each face so we check if it is present across each face. + const auto mat_num = m_new_mat.material_num(i_new_oct, imat); + const bool has_mat = m_old_mat.get(static_cast(cell_loc_left_x.iOct), mat_num) && + m_old_mat.get(static_cast(cell_loc_right_x.iOct), mat_num) && + m_old_mat.get(static_cast(cell_loc_left_y.iOct), mat_num) && + m_old_mat.get(static_cast(cell_loc_right_y.iOct), mat_num) && + m_old_mat.get(static_cast(cell_loc_left_z.iOct), mat_num) && + m_old_mat.get(static_cast(cell_loc_right_z.iOct), mat_num) && + m_old_mat.get(static_cast(cell_loc_old.iOct), mat_num); + + if (has_mat) // Is the material present in the source octant? + for (int32_t ivar = 0; ivar < m_num_vars_per_mat; ++ivar) + { + const auto old_ivar = + ivar + m_num_vars_per_mat * + m_old_mat.material_index(static_cast(cell_loc_old.iOct), mat_num); + + const auto old_ivar_left_x = + ivar + m_num_vars_per_mat * + m_old_mat.material_index(static_cast(cell_loc_left_x.iOct), mat_num); + + const auto old_ivar_right_x = + ivar + m_num_vars_per_mat * + m_old_mat.material_index(static_cast(cell_loc_right_x.iOct), mat_num); + + const auto old_ivar_left_y = + ivar + m_num_vars_per_mat * + m_old_mat.material_index(static_cast(cell_loc_left_y.iOct), mat_num); + + const auto old_ivar_right_y = + ivar + m_num_vars_per_mat * + m_old_mat.material_index(static_cast(cell_loc_right_y.iOct), mat_num); + + const auto old_ivar_left_z = + ivar + m_num_vars_per_mat * + m_old_mat.material_index(static_cast(cell_loc_left_z.iOct), mat_num); + + const auto old_ivar_right_z = + ivar + m_num_vars_per_mat * + m_old_mat.material_index(static_cast(cell_loc_right_z.iOct), mat_num); + + const auto new_ivar = ivar + m_num_vars_per_mat * imat; + + // compute limited slopes + auto const dudx = m_stencil_helper.compute_minmod_slopes_prolongation(cell_loc_old, + old_ivar, + cell_loc_right_x, + old_ivar_right_x, + cell_loc_left_x, + old_ivar_left_x, + m_old_data, + slope_type); + + auto const dudy = m_stencil_helper.compute_minmod_slopes_prolongation(cell_loc_old, + old_ivar, + cell_loc_right_y, + old_ivar_right_y, + cell_loc_left_y, + old_ivar_left_y, + m_old_data, + slope_type); + + auto const dudz = m_stencil_helper.compute_minmod_slopes_prolongation(cell_loc_old, + old_ivar, + cell_loc_right_z, + old_ivar_right_z, + cell_loc_left_z, + old_ivar_left_z, + m_old_data, + slope_type); + + // extrapolate + m_new_data(i_new_cell, new_ivar, i_new_oct) = + m_old_data(cell_loc_old.cellindex(m_block_sizes), old_ivar, cell_loc_old.iOct) + + ONE_FOURTH_F * static_cast(ix) * dudx + + ONE_FOURTH_F * static_cast(iy) * dudy + + ONE_FOURTH_F * static_cast(iz) * dudz; + } + else // If not, set the value to 0 (maybe best to switch extrapolation method?) + for (int32_t ivar = 0; ivar < m_num_vars_per_mat; ++ivar) + { + const auto new_ivar = ivar + m_num_vars_per_mat * imat; + m_new_data(i_new_cell, new_ivar, i_new_oct) = 0; + } + } +} + +} // namespace kalypsso diff --git a/src/kalypsso/core/UserDataRemapperImplMD.h b/src/kalypsso/core/UserDataRemapperImplMD.h new file mode 100644 index 0000000..592fd33 --- /dev/null +++ b/src/kalypsso/core/UserDataRemapperImplMD.h @@ -0,0 +1,112 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file UserDataRemapperImplMD.h + * + * \brief Implement actual user data remapping (after mesh change during AMR cycle) for material + * data. + */ + +#ifndef KALYPSSO_CORE_USERDATAREMAPPER_IMPL_MD_H_ +#define KALYPSSO_CORE_USERDATAREMAPPER_IMPL_MD_H_ + +#include +#include +#include +#include +#include + +namespace kalypsso +{ + +template +class UserDataRemapperImplMD +{ +public: + using AmrHashmap_t = typename hashmap_base_t::map_t; + using OrchardKeys_t = typename orchard_key_base_t::view_t; + using MaterialPresenceView_t = MaterialPresenceView; + using DataArrayBlockMultiVar_t = DataArrayBlockMultiVar; + using StencilHelper_t = StencilHelper; + + UserDataRemapperImplMD(DataArrayBlockMultiVar_t old_data, + MaterialPresenceView_t old_mat, + DataArrayBlockMultiVar_t new_data, + MaterialPresenceView_t new_mat, + AmrHashmap_t amr_hashmap_device_old, + OrchardKeys_t orchard_keys_device_old, + OrchardKeys_t orchard_keys_device_new, + const uint32_t num_vars_per_mat, + const ConfigMap & config_map); + + KOKKOS_FUNCTION void + operator()(const int32_t i_new) const; + +private: + // ============================================================================================== + // ============================================================================================== + // INNER FUNCTIONS ADAPTED FROM UserDataRemapperImplBCC + // ============================================================================================== + // ============================================================================================== + template = true> + KOKKOS_INLINE_FUNCTION void + linear_extrapolate_using_limited_slopes(CellLocation<2> const & cell_loc_old, + coord_t<2> const & coord_new, + int32_t const & i_new_cell, + int32_t const & i_new_oct) const; + + template = true> + KOKKOS_INLINE_FUNCTION void + linear_extrapolate_using_limited_slopes(CellLocation<3> const & cell_loc_old, + coord_t<3> const & coord_new, + int32_t const & i_new_cell, + int32_t const & i_new_oct) const; + + //! Old data + DataArrayBlockMultiVar_t m_old_data; + + //! Old material presence + MaterialPresenceView_t m_old_mat; + + //! New data + DataArrayBlockMultiVar_t m_new_data; + + //! New material presence + MaterialPresenceView_t m_new_mat; + + //! AMR unordered map which map orchard keys to octant number for all key in the mesh + //! before AMR cycle modification + AmrHashmap_t m_amr_hashmap_device_old; + + //! list of orchard key of the old mesh (before AMR cycle modification) + OrchardKeys_t m_orchard_keys_device_old; + + //! list of orchard key of the new mesh (after AMR cycle modification) + OrchardKeys_t m_orchard_keys_device_new; + + //! Stencil helper + StencilHelper_t m_stencil_helper; + + //! prolongation type (simple copy or linear extrapolation) + CellCenteredProlongationType m_prolongation; + + //! Num vars per material + int32_t m_num_vars_per_mat; + + //! block sizes + block_size_t m_block_sizes; +}; + +extern template class UserDataRemapperImplMD<2, kalypsso::DefaultDevice>; +extern template class UserDataRemapperImplMD<3, kalypsso::DefaultDevice>; + +#ifdef KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE +extern template class UserDataRemapperImplMD<2, HostDevice>; +extern template class UserDataRemapperImplMD<3, HostDevice>; +#endif // KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_USERDATAREMAPPER_IMPL_MD_H_ diff --git a/src/kalypsso/core/UserDataRemapperImplMP.cpp b/src/kalypsso/core/UserDataRemapperImplMP.cpp new file mode 100644 index 0000000..775c608 --- /dev/null +++ b/src/kalypsso/core/UserDataRemapperImplMP.cpp @@ -0,0 +1,152 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file UserDataRemapperImplMP.cpp + */ + +#include +#include // for compute_face_neighbor_key_finer + +namespace kalypsso +{ + +// ================================================================================================ +// ================================================================================================ +template +UserDataRemapperImplMP::UserDataRemapperImplMP(const MaterialPresenceView_t old_mat, + MaterialPresenceView_t new_mat, + AmrHashmap_t amr_hashmap_device_old, + OrchardKeys_t orchard_keys_device_old, + OrchardKeys_t orchard_keys_device_new, + const ConfigMap & config_map) + : m_old_mat(old_mat) + , m_new_mat(new_mat) + , m_amr_hashmap_device_old(amr_hashmap_device_old) + , m_orchard_keys_device_old(orchard_keys_device_old) + , m_orchard_keys_device_new(orchard_keys_device_new) + , m_brick_size(get_brick_sizes(config_map)) + , m_brick_periodicity(get_brick_periodicity(config_map)) + , m_prolongation(get_cell_prolongation_type(config_map)) +{} + +// ================================================================================================ +// ================================================================================================ +template +KOKKOS_FUNCTION void +UserDataRemapperImplMP::operator()(const int32_t i_new_oct) const +{ + const auto new_key = m_orchard_keys_device_new(i_new_oct); + const auto new_key_index = m_amr_hashmap_device_old.find(new_key); + + // It hasn't changed + if (m_amr_hashmap_device_old.valid_at(new_key_index)) + { + const auto i_old_oct = static_cast(m_amr_hashmap_device_old.value_at(new_key_index)); + MaterialPresenceView_t::copy(m_new_mat, i_new_oct, m_old_mat, i_old_oct); + return; + } + + const auto new_father_key = orchard_key_t::father(new_key); + const auto new_father_key_index = m_amr_hashmap_device_old.find(new_father_key); + + // It was refined + if (m_amr_hashmap_device_old.valid_at(new_father_key_index)) + { + const auto i_old_oct = + static_cast(m_amr_hashmap_device_old.value_at(new_father_key_index)); + MaterialPresenceView_t::copy(m_new_mat, i_new_oct, m_old_mat, i_old_oct); + + // If the prolongation is to extrapolate using a linear min-mod, we must create space for the + // new materials on the borders + if (m_prolongation == +CellCenteredProlongationType::EXTRAPOLATE_LINEAR_MINMOD) + for (uint8_t face = 0; face < Face::num_faces(); face++) + get_mat_over_face(new_father_key, face, i_new_oct); + + return; + } + + // This means it was coarsened so we have to regroup all of the material of its children + constexpr auto NB_CHILDREN = orchard_key_t::NB_CHILDREN; + + const auto new_child_key = orchard_key_t::eldest_child(new_key); + const auto new_child_key_index = m_amr_hashmap_device_old.find(new_child_key); + + // Must be valid + if (m_amr_hashmap_device_old.valid_at(new_child_key_index)) + { + const auto i_eldest_child_oct = + static_cast(m_amr_hashmap_device_old.value_at(new_child_key_index)); + for (uint8_t i = 0; i < NB_CHILDREN; i++) + MaterialPresenceView_t::update(m_new_mat, i_new_oct, m_old_mat, i_eldest_child_oct + i); + return; + } +} + +// ================================================================================================ +// ================================================================================================ +template +KOKKOS_FUNCTION void +UserDataRemapperImplMP::get_mat_over_face(const key_t key, + const uint8_t face, + const int32_t i_new_oct) const +{ + coord_t dir{}; + dir[face >> 1] = (face & 1) ? 1 : -1; + + const auto neighbor_key = + orchard_key_t::get_neighbor_key_same_level(key, dir, m_brick_size, m_brick_periodicity); + auto neighbor_key_hash = m_amr_hashmap_device_old.find(neighbor_key); + auto is_key_valid = m_amr_hashmap_device_old.valid_at(neighbor_key_hash); + + // Neighbor is at same level + if (is_key_valid) + { + const auto i_old_oct = + static_cast(m_amr_hashmap_device_old.value_at(neighbor_key_hash)); + return MaterialPresenceView_t::update(m_new_mat, i_new_oct, m_old_mat, i_old_oct); + } + + const auto neighbor_key_coarser = orchard_key_t::father(neighbor_key); + neighbor_key_hash = m_amr_hashmap_device_old.find(neighbor_key_coarser); + is_key_valid = m_amr_hashmap_device_old.valid_at(neighbor_key_hash); + + // Neighbor is at coarser level + if (is_key_valid) + { + const auto i_old_oct = + static_cast(m_amr_hashmap_device_old.value_at(neighbor_key_hash)); + return MaterialPresenceView_t::update(m_new_mat, i_new_oct, m_old_mat, i_old_oct); + } + + const auto neighbor_keys_finer = compute_face_neighbor_key_finer(neighbor_key, face); + neighbor_key_hash = m_amr_hashmap_device_old.find(neighbor_keys_finer[0]); + is_key_valid = m_amr_hashmap_device_old.valid_at(neighbor_key_hash); + + // Neighbor is at finer level + if (is_key_valid) + { + const auto i_old_oct = + static_cast(m_amr_hashmap_device_old.value_at(neighbor_key_hash)); + MaterialPresenceView_t::update(m_new_mat, i_new_oct, m_old_mat, i_old_oct); + for (uint8_t i = 1; i < neighbor_keys_finer.size(); i++) + { + const auto hash = m_amr_hashmap_device_old.find(neighbor_keys_finer[i]); + const auto i_old_oct_bis = static_cast(m_amr_hashmap_device_old.value_at(hash)); + MaterialPresenceView_t::update(m_new_mat, i_new_oct, m_old_mat, i_old_oct_bis); + } + } +} + +// ================================================================================================ +// ================================================================================================ +template class UserDataRemapperImplMP<2, kalypsso::DefaultDevice>; +template class UserDataRemapperImplMP<3, kalypsso::DefaultDevice>; + +#ifdef KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE +template class UserDataRemapperImplMP<2, HostDevice>; +template class UserDataRemapperImplMP<3, HostDevice>; +#endif // KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE + +} // namespace kalypsso diff --git a/src/kalypsso/core/UserDataRemapperImplMP.h b/src/kalypsso/core/UserDataRemapperImplMP.h new file mode 100644 index 0000000..3d3e73c --- /dev/null +++ b/src/kalypsso/core/UserDataRemapperImplMP.h @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file UserDataRemapperImplMP.h + * + * \brief Implement actual user data remapping (after mesh change during AMR cycle) for material + * presence. + */ + +#ifndef KALYPSSO_CORE_USERDATAREMAPPER_IMPL_MP_H_ +#define KALYPSSO_CORE_USERDATAREMAPPER_IMPL_MP_H_ + +#include +#include +#include +#include + +namespace kalypsso +{ + +template +class UserDataRemapperImplMP +{ +public: + using AmrHashmap_t = typename hashmap_base_t::map_t; + using OrchardKeys_t = typename orchard_key_base_t::view_t; + using MaterialPresenceView_t = MaterialPresenceView; + + UserDataRemapperImplMP(const MaterialPresenceView_t old_mat, + MaterialPresenceView_t new_mat, + AmrHashmap_t amr_hashmap_device_old, + OrchardKeys_t orchard_keys_device_old, + OrchardKeys_t orchard_keys_device_new, + const ConfigMap & config_map); + + KOKKOS_FUNCTION void + operator()(const int32_t i_new_oct) const; + +private: + //! Updates the material presence with the ones over the faces + KOKKOS_FUNCTION void + get_mat_over_face(const key_t key, const uint8_t face, const int32_t i_new_oct) const; + + //! Old material presence + MaterialPresenceView_t m_old_mat; + + //! New material presence + MaterialPresenceView_t m_new_mat; + + //! AMR unordered map which map orchard keys to octant number for all key in the mesh + //! before AMR cycle modification + AmrHashmap_t m_amr_hashmap_device_old; + + //! list of orchard key of the old mesh (before AMR cycle modification) + OrchardKeys_t m_orchard_keys_device_old; + + //! list of orchard key of the new mesh (after AMR cycle modification) + OrchardKeys_t m_orchard_keys_device_new; + + //! brick size + brick_size_t m_brick_size; + + //! Brick periodicity + Kokkos::Array m_brick_periodicity; + + //! prolongation type (simple copy or linear extrapolation) + CellCenteredProlongationType m_prolongation; +}; + +extern template class UserDataRemapperImplMP<2, kalypsso::DefaultDevice>; +extern template class UserDataRemapperImplMP<3, kalypsso::DefaultDevice>; + +#ifdef KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE +extern template class UserDataRemapperImplMP<2, HostDevice>; +extern template class UserDataRemapperImplMP<3, HostDevice>; +#endif // KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_USERDATAREMAPPER_IMPL_MP_H_ diff --git a/src/kalypsso/core/ViscosityParams.h b/src/kalypsso/core/ViscosityParams.h new file mode 100644 index 0000000..b72d9df --- /dev/null +++ b/src/kalypsso/core/ViscosityParams.h @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ViscosityParams.h + */ +#ifndef KALYPSSO_CORE_VISCOSITY_PARAMS_H_ +#define KALYPSSO_CORE_VISCOSITY_PARAMS_H_ + +#include // for assertm +#include +#include + +namespace kalypsso +{ + +/** + * Constant uniform viscosity parameters. + */ +struct ViscosityParams +{ + //! turn on/off viscosity at run run-time + bool enabled; + + //! dynamic viscosity + real_t mu; + + //! enable Hancock predictor + bool hancock_predictor_enabled; + + ViscosityParams(ConfigMap const & config_map) + : enabled(config_map.getBool("viscosity", "enabled", false)) + , mu(config_map.getReal("viscosity", "mu", KALYPSSO_NUM(1e-4))) + , hancock_predictor_enabled(config_map.getBool("viscosity", "hancock_predictor_enabled", false)) + { + assertm(mu >= 0, "[ViscosityParams] dynamic viscosity mu must be non-negative."); + } + +}; // struct ViscosityParams + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_VISCOSITY_PARAMS_H_ diff --git a/src/kalypsso/core/amr_hashmap.h b/src/kalypsso/core/amr_hashmap.h new file mode 100644 index 0000000..586e426 --- /dev/null +++ b/src/kalypsso/core/amr_hashmap.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file amr_hashmap.h + */ +#ifndef KALYPSSO_CORE_AMRHASHMAP_H +#define KALYPSSO_CORE_AMRHASHMAP_H + +#include +#include + +#include + +namespace kalypsso +{ + +// ======================================================================================== +// ======================================================================================== +template +struct hashmap_base_t +{ + using value_t = iOct_t; + + //! type alias for a device hashmap with key=orchard_key and value=memory index + using map_t = Kokkos::UnorderedMap; + +}; // struct hashmap_base_t + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_AMRHASHMAP_H diff --git a/src/kalypsso/core/amr_key.h b/src/kalypsso/core/amr_key.h new file mode 100644 index 0000000..b004222 --- /dev/null +++ b/src/kalypsso/core/amr_key.h @@ -0,0 +1,252 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file amr_key.h + * \brief + * Design a "key" type to be used as entry to our adaptive mesh refinement + * hash-table data structure. + * + * Let's try two types of key: + * - morton index + tree + * - morton index + level + tree + * + * The first type should be useful to store all the leaves of tree. + * The second type could be useful to store additionally level ghost cells. + * Indeed this allows to have cells of different sizes covering the same + * location (same x,y,z but different levels). + */ +#ifndef KALYPSSO_CORE_KEY_TYPE_H +#define KALYPSSO_CORE_KEY_TYPE_H + +#include + +namespace kalypsso +{ + +/** + * Cell status. + * + * - "ghost level cells" are inserted in hashmap, to make any regular + * cell have a regular neighborhood; they are inserted by interpolated + * the payload data. + * + * - "ghost external cell" hold the border condition; it inserted in the + * hashmap when visiting a cell that "touches the external border" and that + * hold the very same Morton key, same level, only the payload data is + * different + * + * \note Not used when AMR is done with p4est + */ +enum CellStatus : uint8_t +{ + + CELL_INVALID = 0, + CELL_REGULAR = 1, /*!< a regular cell */ + CELL_GHOST_LEVEL = 2, /*!< a cell inserted in hashmap to make neighbor cell surrounded by cells + that have the same level */ + CELL_GHOST_MPI = 3, /*!< an MPI ghost cell */ + CELL_GHOST_EXTERNAL = 4, /*!< a cell outside the external domain */ + CELL_TO_BE_REMOVED = 5, /*!< this cell should be removed soon */ + CELL_UNINITIALIZED = 6 /*!< this cell should be initialed soon */ + +}; // enum CellStatus + +/** + * \struct amr_key_t + * + * Define the key type as entry to our Kokkos::UnorderedMap data structure + * using morton index + level + tree id. + * + * keys[0] holds the morton index computed inside the tree the cell + * belongs to. + * keys[1] holds additional information: + * - the last 8 bits encode the level (only 5 bits is required, + * since in 2D MAXLEVEL is 29). + * - the next 16 LSB encode the tree id; so only 2^16=65536 trees are + * allowed for now, but it should sufficient for most application + * - the remaining bits are unused + * + * Just for clarification, this key type is not a morton key since here + * cell level is encoded. + * + * \sa amr_key_simple_t + */ +struct amr_key_t +{ + + /** bit mask to extract LEVEL (the last 8 bits) */ + static constexpr uint64_t LEVEL_MASK = 0x00000000000000FF; + + /** bit mask to extract TREEID (16 bits) */ + static constexpr uint64_t TREE_ID_MASK = 0x0000000000FFFF00; + + uint64_t keys[2]; + + KOKKOS_INLINE_FUNCTION + amr_key_t() + : keys{ 0, 0 } + {} + + KOKKOS_INLINE_FUNCTION + amr_key_t(uint64_t key1, uint64_t key2) + : keys{ key1, key2 } + {} + + KOKKOS_INLINE_FUNCTION + uint64_t + operator[](size_t i) const + { + return keys[i]; + } + + KOKKOS_INLINE_FUNCTION + uint64_t & + operator[](size_t i) + { + return keys[i]; + } + + /** extract Morton key */ + KOKKOS_INLINE_FUNCTION + uint64_t + get_morton() + { + return keys[0]; + } // get_morton + + /** extract level */ + KOKKOS_INLINE_FUNCTION + uint8_t + get_level() + { + return (keys[1] & LEVEL_MASK); + } // get_level + + /** extract treeId */ + KOKKOS_INLINE_FUNCTION + uint16_t + get_treeId() + { + return (keys[1] & TREE_ID_MASK) >> 8; + } // get_treeid + + KOKKOS_INLINE_FUNCTION + bool + operator<(const amr_key_t & other) const + { + + return (keys[1] < other.keys[1]) or (keys[1] == other.keys[1] and keys[0] < other.keys[0]); + } + + KOKKOS_INLINE_FUNCTION + bool + operator==(const amr_key_t & other) const + { + + return (keys[1] == other.keys[1]) and (keys[0] == other.keys[0]); + } + +}; // struct amr_key_t + +/** encode level and treeId in an uint64_t integer */ +KOKKOS_INLINE_FUNCTION +static uint64_t +encode_level_tree(int level, int treeId = 0) +{ + + uint64_t res = 0; + + // first 8 bits for level + res = (level & amr_key_t::LEVEL_MASK); + + // then 16 bits for treeId + res = res | ((treeId << 8) & amr_key_t::TREE_ID_MASK); + + return res; + +} // encode_level_tree + + +/** + * \struct morton_key_t + * + * Define the key type as entry to our Kokkos::UnorderedMap data structure + * using morton index + tree id. + * + * keys[0] holds the morton index computed inside the tree the cell + * belongs to. + * keys[1] holds additional information: + * - the last 16 least significant bits encode the tree id; + * so only 2^16=65536 trees are allowed + * for now, but it should sufficient for most application + * - the remaining bits are unused + */ +struct morton_key_t +{ + /** bit mask to extract TREEID (the last 16 bits) */ + static constexpr uint64_t TREE_ID_MASK = 0x000000000000FFFF; + + uint64_t keys[2]; + + KOKKOS_INLINE_FUNCTION + morton_key_t() + : keys{ 0, 0 } + {} + + KOKKOS_INLINE_FUNCTION + morton_key_t(uint64_t key1, uint64_t key2) + : keys{ key1, key2 } + {} + + KOKKOS_INLINE_FUNCTION + uint64_t + operator[](size_t i) const + { + return keys[i]; + } + + KOKKOS_INLINE_FUNCTION + uint64_t & + operator[](size_t i) + { + return keys[i]; + } + + /** extract Morton key */ + KOKKOS_INLINE_FUNCTION + uint64_t + get_morton() + { + return keys[0]; + } // get_morton + + /** extract treeId */ + KOKKOS_INLINE_FUNCTION + uint16_t + get_treeId() + { + return (keys[1] & TREE_ID_MASK); + } // get_treeid + +}; // struct morton_key_t + +/** encode treeId in an uint64_t integer */ +KOKKOS_INLINE_FUNCTION +static uint64_t +encode_tree(int treeId) +{ + + uint64_t res = 0; + + // 16 bits for level + res = (treeId & morton_key_t::TREE_ID_MASK); + + return res; + +} // encode_tree + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_KEY_TYPE_H diff --git a/src/kalypsso/core/brick_base.h b/src/kalypsso/core/brick_base.h new file mode 100644 index 0000000..3fdf8b2 --- /dev/null +++ b/src/kalypsso/core/brick_base.h @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file brick_base.h + */ +#ifndef KALYPSSO_CORE_BRICKBASE_H_ +#define KALYPSSO_CORE_BRICKBASE_H_ + +#include // for assertm +#include + +#include + +namespace kalypsso +{ +//! A type alias to use uniformly in kalypsso for holding brick sizes +template +using brick_size_t = Kokkos::Array; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_BRICKBASE_H_ diff --git a/src/kalypsso/core/brick_connectivity_utils.h b/src/kalypsso/core/brick_connectivity_utils.h new file mode 100644 index 0000000..34892ec --- /dev/null +++ b/src/kalypsso/core/brick_connectivity_utils.h @@ -0,0 +1,358 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file brick_connectivity_utils.h + */ +#ifndef KALYPSSO_CORE_BRICK_CONNECTIVITY_UTILS_H_ +#define KALYPSSO_CORE_BRICK_CONNECTIVITY_UTILS_H_ + +#include +#include +#include // for SC_LOG2_32 + +#include + +namespace kalypsso +{ + +/** + * \class BrickLinearToXYZ + * + * \brief a utility class to convert a tree linear id number (morton tree id, as enumerated by + * p4est) to x,y,z coordinates in the brick tree grid. + * + * + */ +template +struct BrickLinearToXYZ +{ + Kokkos::Array m_brick_sizes; + Kokkos::Array m_rankx; + Kokkos::Array m_logx; + int m_n_iter; + + BrickLinearToXYZ(brick_size_t brick_sizes) + { + m_brick_sizes[0] = brick_sizes[0]; + m_brick_sizes[1] = brick_sizes[1]; + if constexpr (dim == 3) + m_brick_sizes[2] = brick_sizes[2]; + else + m_brick_sizes[2] = 1; + + // compute log2 of each brick sizes, rounded to next integer + // and compute the smallest embedding morton space containing the brick connectivity + // + // initializes m_rankx, and m_logx + m_logx[0] = SC_LOG2_32(brick_sizes[0] - 1) + 1; + m_logx[1] = SC_LOG2_32(brick_sizes[1] - 1) + 1; + m_n_iter = (1 << m_logx[0]) * (1 << m_logx[1]); + if (m_logx[0] <= m_logx[1]) + { + m_rankx[0] = 0; + m_rankx[1] = 1; + } + else + { + m_rankx[0] = 1; + m_rankx[1] = 0; + } + + if constexpr (dim == 3) + { + m_logx[2] = SC_LOG2_32(brick_sizes[2] - 1) + 1; + m_n_iter *= (1 << m_logx[2]); + if (m_logx[2] < m_logx[m_rankx[0]]) + { + m_rankx[2] = m_rankx[1]; + m_rankx[1] = m_rankx[0]; + m_rankx[0] = 2; + } + else if (m_logx[m_rankx[1]] <= m_logx[2]) + { + m_rankx[2] = 2; + } + else + { + m_rankx[2] = m_rankx[1]; + m_rankx[1] = 2; + } + } + } // end BrickLinearToXYZ + + /** + * convert a tree number into its x,y,z coordinates in the brick connectivity. + * + * adapted from the utility function \"brick_linear_to_xyz\" in p4est_connectivity.c + * + * \param[in] treeId is the tree id as used by p4est in the brick connectivity + * + * \return vector of cartesian coordinates of the tree in the brick grid + */ + Kokkos::Array + toXYZ(int treeId) const + { + Kokkos::Array tx; + + int i, j, k; + int lastlog = 0; + + auto sdim = static_cast(dim); + + for (i = 0; i < sdim; i++) + { + tx[i] = 0; + } + + for (i = 0; i < sdim - 1; i++) + { + p4est_topidx_t tempx[3] = { 0, 0, 0 }; + int logi = m_logx[m_rankx[i]] - lastlog; + int idx[3] = { -1, -1, -1 }; + int c = 0; + + for (k = 0; k < sdim - i; k++) + { + int d = m_rankx[i + k]; + + idx[d] = 0; + } + for (k = 0; k < sdim; k++) + { + if (idx[k] == 0) + { + idx[k] = c++; + } + } + + for (j = 0; j < logi; j++) + { + int base = (sdim - i) * j; + int shift = (sdim - i - 1) * j; + + for (k = 0; k < sdim; k++) + { + int id = idx[k]; + + if (id >= 0) + { + tempx[k] |= (treeId & (1 << (base + id))) >> (shift + id); + } + } + } + for (k = 0; k < sdim; k++) + { + tx[k] += static_cast(tempx[k] << lastlog); + } + lastlog += logi; + treeId >>= (sdim - i) * logi; + } + tx[m_rankx[sdim - 1]] += static_cast(treeId << lastlog); + + return tx; + + } // end toXYZ + +}; // BrickLinearToXYZ + +/** + * p4est brick connectivity geometrical data. + * + * + */ +template +struct BrickConnectivityData +{ + BrickConnectivityData(brick_size_t brick_sizes) + : m_brick_sizes(brick_sizes) + , m_brick_coords("brick coords", + dim == 2 + ? static_cast(brick_sizes[0] * brick_sizes[1]) + : static_cast(brick_sizes[0] * brick_sizes[1] * brick_sizes[2])) + , m_treeIds("treeIds", + dim == 2 ? static_cast(brick_sizes[0] * brick_sizes[1]) + : static_cast(brick_sizes[0] * brick_sizes[1] * brick_sizes[2])) + , m_num_trees(dim == 2 ? brick_sizes[0] * brick_sizes[1] + : brick_sizes[0] * brick_sizes[1] * brick_sizes[2]) + + { + init(); + }; + + /** + * Brick connectivity data for device use. + */ + template + struct Dev + { + Dev(brick_size_t<3> _brick_sizes) + : brick_sizes(_brick_sizes) + , brick_coords("brick coords", brick_sizes[0] * brick_sizes[1] * brick_sizes[2]) + , treeIds(Kokkos::view_alloc(Kokkos::WithoutInitializing, "treeIds"), + brick_sizes[0] * brick_sizes[1] * brick_sizes[2]){}; + + brick_size_t<3> brick_sizes; + Kokkos::View brick_coords; + Kokkos::View treeIds; + + KOKKOS_INLINE_FUNCTION + int + get_treeId(int i, int j) + { + return treeIds(i + brick_sizes[0] * j); + } + + KOKKOS_INLINE_FUNCTION + int + get_treeId(int i, int j, int k) + { + return treeIds(i + brick_sizes[0] * j + brick_sizes[0] * brick_sizes[1] * k); + } + }; + + /// brick sizes. + brick_size_t m_brick_sizes; + + /// dynamic array containing xyz coordinates of each tree in the brick connectivity. + /// this is a 2 dimensional array; first dimension span treeId (0 to nbTree-1) and + /// second dimension is the coordinate id (x,y or z). + Kokkos::View m_brick_coords; + + /// contains the tree ids ordered by left layout on the brick connectivity + Kokkos::View m_treeIds; + + /// total number of trees in the brick connectivity + int m_num_trees; + + /// return linear tree id from morton tree id + int + treeId(int i, int j) + { + return m_treeIds(i + m_brick_sizes[0] * j); + } + + /// return linear tree id from morton tree id + int + treeId(int i, int j, int k) + { + return m_treeIds(i + m_brick_sizes[0] * j + m_brick_sizes[0] * m_brick_sizes[1] * k); + } + + /// compute xyz coordinates for each tree. + /// this code is adapted from p4est library, p4est_connectivity_new_brick + /// to make sure we use the same tree numbering + void + init() + { + // tree linear morton index to xyz converter + BrickLinearToXYZ convert(m_brick_sizes); + + // max number of trees in the smallest embedding morton space (for which sizes are powers of 2) + auto n_iter = convert.m_n_iter; + + int treeId = 0; + + for (int i = 0; i < n_iter; ++i) + { + auto tree_xyz = convert.toXYZ(i); + + bool valid_tree = [&] { + if constexpr (dim == 2) + return (tree_xyz[0] < m_brick_sizes[0]) and (tree_xyz[1] < m_brick_sizes[1]); + else + return (tree_xyz[0] < m_brick_sizes[0]) and (tree_xyz[1] < m_brick_sizes[1]) and + (tree_xyz[2] < m_brick_sizes[2]); + }(); + + + // check if current treeId value is inside our brick domain + if (valid_tree) + { + // now fill m_brick_coords + m_brick_coords(treeId, 0) = tree_xyz[0]; + m_brick_coords(treeId, 1) = tree_xyz[1]; + + if constexpr (dim == 3) + { + if (tree_xyz[2] < m_brick_sizes[2]) + m_brick_coords(treeId, 2) = tree_xyz[2]; + else + m_brick_coords(treeId, 2) = 0; + } + + // now fill m_treeIds + if constexpr (dim == 2) + { + auto index = tree_xyz[0] + m_brick_sizes[0] * tree_xyz[1]; + m_treeIds[index] = treeId; + } + if constexpr (dim == 3) + { + auto index = tree_xyz[0] + m_brick_sizes[0] * tree_xyz[1] + + m_brick_sizes[0] * m_brick_sizes[1] * tree_xyz[2]; + m_treeIds[index] = treeId; + } + + // if constexpr (dim == 3) + // printf("KKK %d | %d %d %d\n", + // treeId, + // m_brick_coords(treeId, 0), + // m_brick_coords(treeId, 1), + // m_brick_coords(treeId, 2)); + // else + // printf("KKK %d | %d %d\n", treeId, m_brick_coords(treeId, 0), m_brick_coords(treeId, + // 1)); + + ++treeId; + } + } + + } // init + + /** + * convert a valid tree number into its x,y,z coordinates in the brick connectivity. + */ + Kokkos::Array + toXYZ(int treeId) const + { + Kokkos::Array coords; + + if (treeId < m_num_trees) + { + coords[0] = static_cast(m_brick_coords(treeId, 0)); + coords[1] = static_cast(m_brick_coords(treeId, 1)); + + if constexpr (dim == 3) + coords[2] = static_cast(m_brick_coords(treeId, 2)); + } + else + { + coords[0] = std::numeric_limits::max(); + coords[1] = std::numeric_limits::max(); + if constexpr (dim == 3) + coords[2] = std::numeric_limits::max(); + } + + return coords; + } // toXYZ + + /// export brick connectivity data to be usable on device + template + Dev + to_device() + { + Dev dev_data(m_brick_sizes); + + Kokkos::deep_copy(dev_data.brick_coords, m_brick_coords); + Kokkos::deep_copy(dev_data.treeIds, m_treeIds); + + return dev_data; + } // to_device + +}; // struct BrickConnectivityData + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_BRICK_CONNECTIVITY_UTILS_H_ diff --git a/src/kalypsso/core/brick_utils.h b/src/kalypsso/core/brick_utils.h new file mode 100644 index 0000000..50151f4 --- /dev/null +++ b/src/kalypsso/core/brick_utils.h @@ -0,0 +1,210 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file brick_utils.h + */ +#ifndef KALYPSSO_CORE_BRICKUTILS_H_ +#define KALYPSSO_CORE_BRICKUTILS_H_ + +#include // for assertm +#include +#include +#include +#include +#include // for CONNECTIVITY_PERIODIC_FALSE + +namespace kalypsso +{ + +// =========================================================== +// =========================================================== +//! Just return a dim-dimensional uniform array of boolean. +//! TODO: this function should probably be defined elsewhere. +template +constexpr auto +get_bool_array(bool value) +{ + if constexpr (dim == 2) + { + return Kokkos::Array{ value, value }; + } + if constexpr (dim == 3) + { + return Kokkos::Array{ value, value, value }; + } +} // get_bool_array + +// // =========================================================== +// // =========================================================== +// //! small utility to transform brick_sizes (3d array) into an array of size dim +// template +// auto +// get_brick_sizes(Kokkos::Array brick_sizes) -> Kokkos::Array +// { +// if constexpr (dim == 2) +// { +// return Kokkos::Array{ static_cast(brick_sizes[0]), static_cast(brick_sizes[1]) +// }; +// } +// if constexpr (dim == 3) +// { +// return Kokkos::Array{ static_cast(brick_sizes[0]), +// static_cast(brick_sizes[1]), +// static_cast(brick_sizes[2]) }; +// } +// } // get_brick_sizes + +// =========================================================== +// =========================================================== +//! small utility extract brick_sizes from config map. +template +auto +get_brick_sizes(ConfigMap const & config_map) -> brick_size_t +{ + + // brick sizes + brick_size_t brick_sizes; + + using T = typename brick_size_t::value_type; + + brick_sizes[IX] = static_cast(config_map.getInteger("p4est_connectivity", "nbrick_x", 1)); + brick_sizes[IY] = static_cast(config_map.getInteger("p4est_connectivity", "nbrick_y", 1)); + if constexpr (dim == 3) + brick_sizes[IZ] = static_cast(config_map.getInteger("p4est_connectivity", "nbrick_z", 1)); + + { + // check that nbrick_x, nbrick_y and nbrick_z have values in valid range + assertm(brick_sizes[IX] >= 0 and brick_sizes[IX] <= orchard_key_t::MAX_NB_TREES_PER_DIR, + "nbrick_x has invalid value"); + assertm(brick_sizes[IY] >= 0 and brick_sizes[IY] <= orchard_key_t::MAX_NB_TREES_PER_DIR, + "nbrick_y has invalid value"); + if constexpr (dim == 3) + assertm(brick_sizes[IZ] >= 0 and brick_sizes[IZ] <= orchard_key_t::MAX_NB_TREES_PER_DIR, + "nbrick_z has invalid value"); + } + + return brick_sizes; + +} // get_brick_sizes + +// =========================================================== +// =========================================================== +//! small utility extract brick periodicity from config map. +template +auto +get_brick_periodicity(ConfigMap const & config_map) -> Kokkos::Array + +{ + // brick periodicty + Kokkos::Array res; + + res[IX] = static_cast( + config_map.getInteger("p4est_connectivity", "periodic_x", CONNECTIVITY_PERIODIC_FALSE)); + res[IY] = static_cast( + config_map.getInteger("p4est_connectivity", "periodic_y", CONNECTIVITY_PERIODIC_FALSE)); + + if constexpr (dim == 3) + { + res[IZ] = static_cast( + config_map.getInteger("p4est_connectivity", "periodic_z", CONNECTIVITY_PERIODIC_FALSE)); + } + + return res; + +} // get_brick_periodicity + +// ====================================================== +// ====================================================== +//! return lower left corner coordinates +template +auto +lower_left_corner(ConfigMap const & config_map) +{ + + const real_t xmin = config_map.getReal("mesh", "xmin", ZERO_F); + const real_t ymin = config_map.getReal("mesh", "ymin", ZERO_F); + const real_t zmin = config_map.getReal("mesh", "zmin", ZERO_F); + + if constexpr (dim == 2) + { + Kokkos::Array llc{ xmin, ymin }; + return llc; + } + else if constexpr (dim == 3) + { + Kokkos::Array llc{ xmin, ymin, zmin }; + return llc; + } +} + +// ====================================================== +// ====================================================== +//! same as lower left corner +template +auto +get_xyz_min(ConfigMap const & config_map) +{ + return lower_left_corner(config_map); +} + +// ====================================================== +// ====================================================== +//! return upper right corner coordinates +template +auto +upper_right_corner(ConfigMap const & config_map) +{ + + const auto xmin = config_map.getReal("mesh", "xmin", ZERO_F); + const auto ymin = config_map.getReal("mesh", "ymin", ZERO_F); + const auto zmin = config_map.getReal("mesh", "zmin", ZERO_F); + + const auto nbrick_x = config_map.getInteger("p4est_connectivity", "nbrick_x", 1); + const auto nbrick_y = config_map.getInteger("p4est_connectivity", "nbrick_y", 1); + const auto nbrick_z = config_map.getInteger("p4est_connectivity", "nbrick_z", 1); + + const auto scaling_factor = config_map.getReal("mesh", "scaling_factor", ONE_F); + + const auto xmax = xmin + static_cast(nbrick_x) * scaling_factor; + const auto ymax = ymin + static_cast(nbrick_y) * scaling_factor; + const auto zmax = zmin + static_cast(nbrick_z) * scaling_factor; + + if constexpr (dim == 2) + { + Kokkos::Array urc{ xmax, ymax }; + return urc; + } + else if constexpr (dim == 3) + { + Kokkos::Array urc{ xmax, ymax, zmax }; + return urc; + } +} + +// ====================================================== +// ====================================================== +//! same as upper right corner +template +auto +get_xyz_max(ConfigMap const & config_map) +{ + return upper_right_corner(config_map); +} + +// ====================================================== +// ====================================================== +//! return a Kokkos::Array of scaling factor to apply for convert x,y,z coordinates from p4est +//! brick connectivity space to real space [x_min, x_max] x [y_min, y_max] x [z_min, z_max] +inline auto +get_scaling_factor(ConfigMap const & config_map) +{ + const real_t scaling_factor = config_map.getReal("mesh", "scaling_factor", ONE_F); + + return scaling_factor; +} // get_scaling_factor + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_BRICKUTILS_H_ diff --git a/src/kalypsso/core/cmdline_utils.h b/src/kalypsso/core/cmdline_utils.h new file mode 100644 index 0000000..7a01e58 --- /dev/null +++ b/src/kalypsso/core/cmdline_utils.h @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file cmdline_utils.h + * \brief a simple stes of function to retrieve arguments from command line. + */ +#ifndef KALYPSSO_CORE_CMDLINE_UTILS_HPP +#define KALYPSSO_CORE_CMDLINE_UTILS_HPP + +#include +#include // for std::find +#include // for std::atoi + +namespace kalypsso +{ + +/** + * check if flag was given on command line. + * + * \param[in] begin iterator + * \param[in] end iterator + * \param[in] arg is a string to match + * + * \return boolean indicating if flag was found on command line. + * + */ +bool +cmdline_arg_exists(char ** begin, char ** end, const std::string & arg) +{ + return std::find(begin, end, arg) != end; +} // cmdline_arg_exists + +/** + * Get a string value from command line. + * + * \param[in] begin iterator + * \param[in] end iterator + * \param[in] arg is a string to match + * + * \return string that is the next token after flag. + */ +std::string +cmdline_get_string(char ** begin, char ** end, const std::string & arg) +{ + + char ** itr = std::find(begin, end, arg); + + // check that ++itr exists + if (itr != end && ++itr != end) + { + return std::string(*itr); + } + const std::string empty = ""; + return empty; + +} // cmdline_get_string + +/** + * Get a integer value from command line. + * + * \param[in] begin iterator + * \param[in] end iterator + * \param[in] arg is a string to match + * + * \return integer that is the next token after flag. + */ +int +cmdline_get_integer(char ** begin, char ** end, const std::string & arg) +{ + + char ** itr = std::find(begin, end, arg); + + // check that ++itr exists + if (itr != end && ++itr != end) + { + return std::atoi(*itr); + } + return -1; + +} // cmdline_get_integer + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_CMDLINE_UTILS_HPP diff --git a/src/kalypsso/core/cnpy_io.h b/src/kalypsso/core/cnpy_io.h new file mode 100644 index 0000000..a3638f7 --- /dev/null +++ b/src/kalypsso/core/cnpy_io.h @@ -0,0 +1,109 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file cnpy_io.h + * \brief converting numpy files to/from a Kokkos::View + */ +#ifndef KALYPSSO_CORE_CNPY_IO_H_ +#define KALYPSSO_CORE_CNPY_IO_H_ + +#include +#include + +#include +#include +#include +#include // for std::setfill + +#include + +namespace kalypsso +{ + +// ============================================================================= +// ============================================================================= +/** + * Save a multidimensional Kokkos::View into a file using numpy format. + * + * example of use in python: + * data = np.load('data.npy') + * + * \tparam View is a Kokkos::View class, we check that the view is accessible from host + * + * \param[in] v a View instance containing a data array to be saved + * \param[in] prefix_name is filename prefix + */ +template +void +save_cnpy(View v, std::string prefix_name) +{ + constexpr bool view_accessible_from_host = + Kokkos::SpaceAccessibility::accessible; + + if constexpr (view_accessible_from_host) + { + + // save in numpy format + std::vector shape; + if constexpr (View::rank >= 1) + shape.push_back(v.extent(0)); + if constexpr (View::rank >= 2) + shape.push_back(v.extent(1)); + if constexpr (View::rank >= 3) + shape.push_back(v.extent(2)); + + std::ostringstream ss; + ss << prefix_name << ".npy"; + cnpy::npy_save(ss.str(), v.data(), shape, "w"); + } + +} // save_cnpy + +// ============================================================================= +// ============================================================================= +/** + * Save a multidimensional Kokkos::View into a file using numpy format. + * + * example of use in python: + * data = np.load('data.npy') + * + * \tparam View is a Kokkos::View class, we check that the view is accessible from host + * + * \param[in] v a View instance containing a data array to be saved + * \param[in] prefix_name is filename prefix + * \param[in] par_env is the parallel environment (to access MPI_Comm) + */ +template +void +save_cnpy(View v, std::string prefix_name, const ParallelEnv & par_env) +{ + constexpr bool view_accessible_from_host = + Kokkos::SpaceAccessibility::accessible; + + if constexpr (view_accessible_from_host) + { + + // save in numpy format + std::vector shape; + if constexpr (View::rank >= 1) + shape.push_back(v.extent(0)); + if constexpr (View::rank >= 2) + shape.push_back(v.extent(1)); + if constexpr (View::rank >= 3) + shape.push_back(v.extent(2)); + + std::ostringstream ss; + ss << prefix_name + "_mpi_"; + ss << std::setw(5) << std::setfill('0') << par_env.rank() << ".npy"; + cnpy::npy_save(ss.str(), v.data(), shape, "w"); + } + +} // save_cnpy + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_CNPY_IO_H_ diff --git a/src/kalypsso/core/config_utils.h b/src/kalypsso/core/config_utils.h new file mode 100644 index 0000000..35032c7 --- /dev/null +++ b/src/kalypsso/core/config_utils.h @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * config_utils.h + */ +#ifndef KALYPSSO_CORE_CONFIG_UTILS_H_ +#define KALYPSSO_CORE_CONFIG_UTILS_H_ + +#include // for assertm +#include +#include +#include // for enum connectivity_periodic_t + +namespace kalypsso +{ + +// =========================================================== +// =========================================================== +/** + * read block sizes from config map. + */ +template +auto +get_block_sizes(ConfigMap const & config_map) -> block_size_t +{ + + block_size_t block_sizes; + + block_sizes[IX] = config_map.getInteger("amr", "bx", 0); + block_sizes[IY] = config_map.getInteger("amr", "by", 0); + + if constexpr (dim == 3) + { + block_sizes[IZ] = config_map.getInteger("amr", "bz", 1); + } + + return block_sizes; + +} // get_block_sizes + +// =========================================================== +// =========================================================== +/** + * Increase a block size for holding flux. + */ +template +auto +get_flux_block_sizes(block_size_t block_sizes, int direction = IX) -> block_size_t +{ + + // flux block size + auto flux_block_sizes = block_sizes; + + // direction must be smaller than to be valid + if (static_cast(direction) < dim) + { + flux_block_sizes[direction]++; + } + + return flux_block_sizes; + +} // get_flux_block_sizes + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_CONFIG_UTILS_H_ diff --git a/src/kalypsso/core/cpptrace_utils.cpp b/src/kalypsso/core/cpptrace_utils.cpp new file mode 100644 index 0000000..28e4499 --- /dev/null +++ b/src/kalypsso/core/cpptrace_utils.cpp @@ -0,0 +1,180 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file cpptrace_utils.cpp + */ +#include + +namespace kalypsso +{ + +// ======================================================= +// ======================================================= +/** + * This function is called by the signal handler and is responsible to transfer + * stack frame data to the tracer program for printing. + * + * Communication between current process and tracer process is done via a unix pipe. + */ +void +do_signal_safe_trace(cpptrace::frame_ptr * buffer, std::size_t count) +{ + // Setup pipe and spawn child + pipe_t input_pipe; + [[maybe_unused]] auto status = pipe(input_pipe.data); + const pid_t pid = fork(); + if (pid == -1) + { + const char * fork_failure_message = "[cpptrace] fork() failed\n"; + [[maybe_unused]] auto write_status = + write(STDERR_FILENO, fork_failure_message, strlen(fork_failure_message)); + return; + } + if (pid == 0) + { // child + dup2(input_pipe.io.read_end, STDIN_FILENO); + close(input_pipe.io.read_end); + close(input_pipe.io.write_end); + + // check if env variable CPPTRACE_STACK_PRINTER_EXE is set + if ([[maybe_unused]] const char * env_value = std::getenv("CPPTRACE_STACK_PRINTER_EXE")) + { + execl(env_value, "kalypsso_stack_printer", nullptr); + } + else + { + execl(KALYPSSO_CORE_STACK_PRINTER_FULLPATH, "kalypsso_stack_printer", nullptr); + } + + const char * exec_failure_message = + "[cpptrace] exec(kalypsso_stack_printer) failed: Make sure the \"kalypsso_stack_printer\" " + "executable is in " + "the current working directory and the binary's permissions are correct.\n"; + [[maybe_unused]] auto write_status = + write(STDERR_FILENO, exec_failure_message, strlen(exec_failure_message)); + _exit(1); + } + // Resolve to safe_object_frames and write those to the pipe + for (std::size_t i = 0; i < count; i++) + { + cpptrace::safe_object_frame frame; + cpptrace::get_safe_object_frame(buffer[i], &frame); + [[maybe_unused]] auto write_status = write(input_pipe.io.write_end, &frame, sizeof(frame)); + } + close(input_pipe.io.read_end); + close(input_pipe.io.write_end); + // Wait for child + waitpid(pid, nullptr, 0); +} + +// ======================================================= +// ======================================================= +/** + * The signal handler will do two thing: + * + * - collect the stack trace data in a frame_ptr array named "buffer" + * - pass buffer to do_signal_safe_trace + */ +void +segv_handler([[maybe_unused]] int signo, + [[maybe_unused]] siginfo_t * info, + [[maybe_unused]] void * context) +{ + // Print basic message + const char * message = "SIGSEGV occurred:\n"; + [[maybe_unused]] auto write_status = write(STDERR_FILENO, message, strlen(message)); + // Generate trace + constexpr std::size_t N = 100; + cpptrace::frame_ptr buffer[N]; + std::size_t count = cpptrace::safe_generate_raw_trace(buffer, N); + do_signal_safe_trace(buffer, count); + // Up to you if you want to exit or continue or whatever + _exit(1); +} + +// ======================================================= +// ======================================================= +void +install_segv_handler() +{ + struct sigaction action; + memset(&action, 0, sizeof(struct sigaction)); + + action.sa_flags = 0; + action.sa_sigaction = &kalypsso::segv_handler; + if (sigaction(SIGSEGV, &action, NULL) == -1) + { + perror("sigaction"); + exit(EXIT_FAILURE); + } +} + +// ======================================================= +// ======================================================= +/** + * The signal handler will do two thing: + * + * - collect the stack trace data in a frame_ptr array named "buffer" + * - pass buffer to do_signal_safe_trace + */ +void +abort_handler([[maybe_unused]] int signo, + [[maybe_unused]] siginfo_t * info, + [[maybe_unused]] void * context) +{ + // Print basic message + const char * message = "SIGABRT occurred:\n"; + [[maybe_unused]] auto write_status = write(STDERR_FILENO, message, strlen(message)); + // Generate trace + constexpr std::size_t N = 100; + cpptrace::frame_ptr buffer[N]; + std::size_t count = cpptrace::safe_generate_raw_trace(buffer, N); + do_signal_safe_trace(buffer, count); + // Up to you if you want to exit or continue or whatever + //_exit(1); +} + +// ======================================================= +// ======================================================= +void +install_abort_handler() +{ + struct sigaction action; + memset(&action, 0, sizeof(struct sigaction)); + + action.sa_flags = 0; + action.sa_sigaction = &kalypsso::abort_handler; + if (sigaction(SIGABRT, &action, NULL) == -1) + { + perror("sigaction"); + exit(EXIT_FAILURE); + } +} + +// ======================================================= +// ======================================================= +void +warmup_cpptrace() +{ + cpptrace::frame_ptr buffer[10]; + [[maybe_unused]] std::size_t count = cpptrace::safe_generate_raw_trace(buffer, 10); + cpptrace::safe_object_frame frame; + cpptrace::get_safe_object_frame(buffer[0], &frame); +} + +// ======================================================= +// ======================================================= +void +cpptrace_initialize() +{ + cpptrace::absorb_trace_exceptions(false); + cpptrace::register_terminate_handler(); + warmup_cpptrace(); + + install_segv_handler(); + install_abort_handler(); +} + +} // namespace kalypsso diff --git a/src/kalypsso/core/cpptrace_utils.h b/src/kalypsso/core/cpptrace_utils.h new file mode 100644 index 0000000..8f40544 --- /dev/null +++ b/src/kalypsso/core/cpptrace_utils.h @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file cpptrace_utils.h + * + * Basically define the signal handler needed to print stack trace in case current application +crashes. + * + * The code is adapted from cpptrace sources : + * https://github.com/jeremy-rifkin/cpptrace/blob/main/test/signal_demo.cpp + * provided by cpptrace under MIT license. + * + * See https://github.com/jeremy-rifkin/cpptrace/blob/main/docs/signal-safe-tracing.md +*/ +#ifndef KALYPSSO_CORE_CPPTRACE_UTILS_H_ +#define KALYPSSO_CORE_CPPTRACE_UTILS_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef KALYPSSO_CORE_USE_CPPTRACE +# include +#endif + +namespace kalypsso +{ + +// This is just a utility I like, it makes the pipe API more expressive. +struct pipe_t +{ + struct io_t + { + int read_end; + int write_end; + }; + + union + { + io_t io; + int data[2]; + }; +}; + +// ======================================================= +// ======================================================= +void +do_signal_safe_trace(cpptrace::frame_ptr * buffer, std::size_t count); + +// ======================================================= +// ======================================================= +void +segv_handler(int signo, siginfo_t * info, void * context); + +// ======================================================= +// ======================================================= +void +install_segv_handler(); + +// ======================================================= +// ======================================================= +void +warmup_cpptrace(); + +// ======================================================= +// ======================================================= +void +cpptrace_initialize(); + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_CPPTRACE_UTILS_H_ diff --git a/src/kalypsso/core/enums.h b/src/kalypsso/core/enums.h new file mode 100644 index 0000000..78357b2 --- /dev/null +++ b/src/kalypsso/core/enums.h @@ -0,0 +1,388 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file enums.h + */ +#ifndef KALYPSSO_CORE_ENUMS_H_ +#define KALYPSSO_CORE_ENUMS_H_ + +#include + +namespace kalypsso +{ + +// ======================================================================= +// ======================================================================= +//! dimension of the problem +enum DimensionType +{ + TWO_D = 2, + THREE_D = 3, + DIM2 = 2, + DIM3 = 3 +}; + +//! hydro field indexes +// enum VarIndex { +// ID=0, /*!< ID Density field index */ +// IP=1, /*!< IP Pressure/Energy field index */ +// IE=1, /*!< IE Energy/Pressure field index */ +// IU=2, /*!< X velocity / momentum index */ +// IV=3, /*!< Y velocity / momentum index */ +// IW=4, /*!< Z velocity / momentum index */ +// IA=5, /*!< X magnetic field index */ +// IB=6, /*!< Y magnetic field index */ +// IC=7, /*!< Z magnetic field index */ +// IBX=5, /*!< X magnetic field index */ +// IBY=6, /*!< Y magnetic field index */ +// IBZ=7, /*!< Z magnetic field index */ +// IGX=8, /*!< X gravitational field index */ +// IGY=9, /*!< Y gravitational field index */ +// IGZ=10, /*!< Z gravitational field index */ +// IBFX = 0, +// IBFY = 1, +// IBFZ = 2, +// COMPONENT_SIZE=11 /*!< invalid index, just counting number of fields */ +// }; + +// //! enum class to enumerate field location in a velocity/temperature gradient array in 2d at +// solution points enum class VarIndexGrad2d { +// IGU = 0, /* x-component of velocity gradient */ +// IGV = 1, /* y-component of velocity gradient */ +// IGW = -1, /* UNUSED */ +// IGT = 2 /* temperature gradient component */ +// }; + +// //! enum class to enumerate field location in a velocity/temperature gradient array in 3d at +// solution points enum class VarIndexGrad3d { +// IGU = 0, /* x-component of velocity gradient */ +// IGV = 1, /* y-component of velocity gradient */ +// IGW = 2, /* y-component of velocity gradient */ +// IGT = 3 /* temperature gradient component */ +// }; + +// ======================================================================= +// ======================================================================= +//! enum class to enumerate field location in a velocity / velocity tensor gradient array in 2d at +//! flux points +enum class VarIndexGrad2d +{ + IGU = 0, /* x-component of velocity */ + IGV = 1, /* y-component of velocity */ + IGW = -1, /* UNUSED */ + IGUX = 2, /* partial U over partial x */ + IGUY = 3, /* partial U over partial y */ + IGUZ = -1, /* UNUSED */ + IGVX = 4, /* partial V over partial x */ + IGVY = 5, /* partial V over partial y */ + IGVZ = -1, /* UNUSED */ + IGWX = -1, /* UNUSED */ + IGWY = -1, /* UNUSED */ + IGWZ = -1, /* UNUSED */ + IGT = 6 /* temperature gradient component */ +}; + +// ======================================================================= +// ======================================================================= +//! enum class to enumerate field location in a velocity / velocity tensor gradient array in 3d at +//! flux points +enum class VarIndexGrad3d +{ + IGU = 0, /* x-component of velocity */ + IGV = 1, /* y-component of velocity */ + IGW = 2, /* z-component of velocity */ + IGUX = 3, /* partial U over partial x */ + IGUY = 4, /* partial U over partial y */ + IGUZ = 5, /* partial U over partial z */ + IGVX = 6, /* partial V over partial x */ + IGVY = 7, /* partial V over partial y */ + IGVZ = 8, /* partial V over partial z */ + IGWX = 9, /* partial W over partial x */ + IGWY = 10, /* partial W over partial y */ + IGWZ = 11, /* partial W over partial z */ + IGT = 12 /* temperature gradient component */ +}; + +// ======================================================================= +// ======================================================================= +//! velocity gradient tensor components in 2d +enum class gradientV_IDS_2d +{ + U_X = 0, + U_Y = 1, + U_Z = -1, + + V_X = 2, + V_Y = 3, + V_Z = -1, + + W_X = -1, + W_Y = -1, + W_Z = -1, +}; // enum class gradientV_IDS_2d + +// ======================================================================= +// ======================================================================= +//! velocity gradient tensor components in 3d +enum class gradientV_IDS_3d +{ + U_X = 0, + U_Y = 1, + U_Z = 2, + + V_X = 3, + V_Y = 4, + V_Z = 5, + + W_X = 6, + W_Y = 7, + W_Z = 8 +}; // enum class gradientV_IDS_3d + +// ======================================================================= +// ======================================================================= +struct Dir +{ + using dir_t = uint8_t; + static constexpr dir_t X = 0; + static constexpr dir_t Y = 1; + static constexpr dir_t Z = 2; +}; + +// ======================================================================= +// ======================================================================= +//! type of boundary condition (note that BC_COPY is only used in the +//! MPI version for inside boundary) +enum BoundaryConditionType +{ + BC_UNDEFINED, + BC_REFLECTING, /*!< reflecting border condition */ + BC_ABSORBING, /*!< absorbing border condition */ + BC_PERIODIC, /*!< periodic border condition */ + BC_COPY /*!< only used in MPI parallelized version */ +}; + +// ======================================================================= +// ======================================================================= +//! enum component index +enum ComponentIndex3D +{ + IX = 0, + IY = 1, + IZ = 2 +}; + +// ======================================================================= +// ======================================================================= +//! enum for codirection +enum CoDir +{ + ALONG_Z = 0, + ALONG_Y = 1, + ALONG_X = 2 +}; + +// ======================================================================= +// ======================================================================= +//! direction used in directional splitting scheme +enum Direction +{ + XDIR = 1, + YDIR = 2, + ZDIR = 3, +}; + +// ======================================================================= +// ======================================================================= +//! location of the outside boundary +enum BoundaryLocation +{ + // clang-format off + XMIN = 0, + XMAX = 1, + YMIN = 2, + YMAX = 3, + ZMIN = 4, + ZMAX = 5 + // clang-format on +}; + +// ======================================================================= +// ======================================================================= +//! enum edge index (use in MHD - EMF computations) +enum EdgeIndex +{ + IRT = 0, /*!< RT (Right - Top ) */ + IRB = 1, /*!< RB (Right - Bottom) */ + ILT = 2, /*!< LT (Left - Top ) */ + ILB = 3 /*!< LB (Left - Bottom) */ +}; + +// ======================================================================= +// ======================================================================= +enum EdgeIndex2 +{ + ILL = 0, + IRL = 1, + ILR = 2, + IRR = 3 +}; + +// ======================================================================= +// ======================================================================= +//! enum used in MHD - EMF computations +enum EmfDir +{ + EMFX = 0, + EMFY = 1, + EMFZ = 2 +}; + +// ======================================================================= +// ======================================================================= +//! EMF indexes (EMFZ is first because in 2D, we only need EMFZ) +enum EmfIndex +{ + I_EMFZ = 0, + I_EMFY = 1, + I_EMFX = 2 +}; + +// ======================================================================= +// ======================================================================= +//! Enumerate edge of a cell (Morton order). +//! +//! 10 _____ 11 +//! | | +//! | | +//! | | +//! 00 -----01 +//! +enum CellEdgeLocation +{ + EDGE_00 = 0, + EDGE_01 = 1, + EDGE_10 = 2, + EDGE_11 = 3, + EDGE_INVALID = 4 +}; + +/** + * enum used to identify one the four states around a cell edge. + * + * This is useful when computing emf (electromotive forces) + */ +enum class MHDEdgeLocation : uint8_t +{ + LB, + RB, + LT, + RT +}; + +// ======================================================================= +// ======================================================================= +//! implementation version +enum ImplementationVersion +{ + IMPL_VERSION_0, + IMPL_VERSION_1, + IMPL_VERSION_2 +}; + +// ======================================================================= +// ======================================================================= +//! problem type (TODO: REFACTOR OR REMOVE) +enum ProblemType +{ + PROBLEM_IMPLODE, + PROBLEM_BLAST, + PROBLEM_ORSZAG_TANG +}; + +// ======================================================================= +// ======================================================================= +//! 2:1 ratios +//! Indicates if the neighbor is bigger or smaller +//! TODO: REFACTOR OR REMOVE +enum InterfaceType : uint16_t +{ + INTERFACE_NONE = 0, + INTERFACE_XMIN_SMALLER = 1, + INTERFACE_XMAX_SMALLER = 2, + INTERFACE_YMIN_SMALLER = 4, + INTERFACE_YMAX_SMALLER = 8, + INTERFACE_ZMIN_SMALLER = 16, + INTERFACE_ZMAX_SMALLER = 32, + INTERFACE_XMIN_BIGGER = 64, + INTERFACE_XMAX_BIGGER = 128, + INTERFACE_YMIN_BIGGER = 256, + INTERFACE_YMAX_BIGGER = 512, + INTERFACE_ZMIN_BIGGER = 1024, + INTERFACE_ZMAX_BIGGER = 2048, + + // Combinations: Non conformal interfaces + INTERFACE_XMIN_NC = 65, + INTERFACE_XMAX_NC = 130, + INTERFACE_YMIN_NC = 260, + INTERFACE_YMAX_NC = 520, + INTERFACE_ZMIN_NC = 1040, + INTERFACE_ZMAX_NC = 2080 +}; + +// ======================================================================= +// ======================================================================= +enum FACE_ID : uint8_t +{ + FACE_LEFT = 0, + FACE_RIGHT = 1, + FACE_BOTTOM = 2, + FACE_TOP = 3, + FACE_FRONT = 4, + FACE_REAR = 5 +}; + +enum class face_type_t : uint32_t +{ + LEFT = 0, + RIGHT = 1 +}; + +// ======================================================================= +// ======================================================================= +enum DIR_ID : uint8_t +{ + DIR_X = 0, + DIR_Y = 1, + DIR_Z = 2 +}; + +// ======================================================================= +// ======================================================================= +// TODO: refactor this for 3d: Clearer names for enum ? +// Corners extraction require codim = mdim, order is Z-order +enum CORNER_DIR : uint8_t +{ + CORNER_BOTTOM_LEFT = 0, + CORNER_BOTTOM_RIGHT = 1, + CORNER_TOP_LEFT = 2, + CORNER_TOP_RIGHT = 3, + + // Masks for fast testing + CORNER_RIGHT = 1, + CORNER_TOP = 2 +}; + +// ======================================================================= +// ======================================================================= +enum UPDATE_TYPE : uint8_t +{ + UPDATE_NON_CONSERVATIVE = 0, + UPDATE_CONSERVATIVE_SUM = 1 +}; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_ENUMS_H_ diff --git a/src/kalypsso/core/eos/IdealGasEos.h b/src/kalypsso/core/eos/IdealGasEos.h new file mode 100644 index 0000000..b93d384 --- /dev/null +++ b/src/kalypsso/core/eos/IdealGasEos.h @@ -0,0 +1,163 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file IdealGasEos.h + * + * Ideal gas equation of state is defined by: + * + * \f$ p = (\gamma-1) rho e \f$ + * + */ +#ifndef KALYPSSO_CORE_EOS_IDEAL_GAS_EOS_H_ +#define KALYPSSO_CORE_EOS_IDEAL_GAS_EOS_H_ + +#include +#include +#include +#include + +namespace kalypsso +{ + +namespace core +{ + +namespace eos +{ + +// ============================================================================== +// ============================================================================== +// ============================================================================== +// ========================================================================== +// ========================================================================== +/** + * Ideal gas equation of state is defined by: + * + * \f$ p = (\gamma-1) rho e \f$ + * + * This struct is a helper for converting internal energy to pressure and reverse. + * + */ +struct IdealGasEos +{ + //! specific heat ratio + real_t m_gamma; + + //! one over gamma minus one + real_t m_one_over_gammam1; + + KOKKOS_DEFAULTED_FUNCTION + IdealGasEos() = default; + + /** + * Initialize Eos for a given material by reading property from ini file. + * + * \param[in] i_mat material id (between 0 and nmat-1) + * \param[in] config_map input configuration map + */ + IdealGasEos(const size_t i_mat, const ConfigMap & config_map) + : m_gamma(get_gamma(i_mat, config_map)) + , m_one_over_gammam1(ONE_F / (m_gamma - ONE_F)) + {} + + /** + * Initialize Eos . + * + * Useful for monofluid (only one material). + * + * \param[in] config_map input configuration map + */ + IdealGasEos(const ConfigMap & config_map) + : IdealGasEos(0, config_map) + {} + + KOKKOS_INLINE_FUNCTION + auto const & + gamma() const + { + return m_gamma; + } + + KOKKOS_INLINE_FUNCTION + auto const & + one_over_gammam1() const + { + return m_one_over_gammam1; + } + + /** + * Compute pressure from volumic internal energy + */ + KOKKOS_INLINE_FUNCTION real_t + pressure_from_volumic_eint(real_t eint_volumic, [[maybe_unused]] real_t rho) const + { + return (m_gamma - ONE_F) * eint_volumic; + } + + /** + * Compute pressure from specific internal energy + */ + KOKKOS_INLINE_FUNCTION real_t + pressure_from_specific_eint(real_t eint_specific, real_t rho) const + { + return pressure_from_volumic_eint(rho * eint_specific, rho); + } + + /** + * Compute volumic internal energy from pressure + */ + KOKKOS_INLINE_FUNCTION real_t + volumic_eint_from_pressure(real_t pressure) const + { + return pressure * m_one_over_gammam1; + } + + /** + * Compute specific internal energy from pressure and density + */ + KOKKOS_INLINE_FUNCTION real_t + specific_eint_from_pressure(real_t pressure, real_t rho) const + { + return volumic_eint_from_pressure(pressure) / rho; + } + + /** + * By definition isentropic bulk modulus is \f$\kappa = -V \frac{dP}{dV}\f$, + * where the derivative is taken at constant entropy. + * + * In a fluid, one can show that sound speed is \f$c=\sqrt{\frac{\kappa}{\rho}}\f$, + * thus \f$\kappa=\rho c^2\f$. + */ + KOKKOS_INLINE_FUNCTION + real_t + sound_speed(real_t pressure, real_t rho) const + { + return sqrt(m_gamma * pressure / rho); + } + + /** + * By definition isentropic bulk modulus is \f$\kappa = -V \frac{dP}{dV}\f$, + * where the derivative is taken at constant entropy. + * + * In a fluid, one can show that sound speed is \f$c=\sqrt{\frac{\kappa}{\rho}}\f$, + * thus \f$\kappa=\rho c^2\f$. + */ + KOKKOS_INLINE_FUNCTION + real_t + bulk_modulus(real_t pressure, real_t rho) const + { + const auto c = sound_speed(pressure, rho); + return rho * c * c; + } + +}; // struct IdealGasEos + +} // namespace eos + +} // namespace core + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_EOS_IDEAL_GAS_EOS_H_ diff --git a/src/kalypsso/core/eos/IdealGasMixture.h b/src/kalypsso/core/eos/IdealGasMixture.h new file mode 100644 index 0000000..c0c9608 --- /dev/null +++ b/src/kalypsso/core/eos/IdealGasMixture.h @@ -0,0 +1,151 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file IdealGasMixture.h + * + * This is only useful when using at least two material to define pressure and + * internal energy in mixed cells. + */ +#ifndef KALYPSSO_CORE_EOS_IDEAL_GAS_MIXTURE_H_ +#define KALYPSSO_CORE_EOS_IDEAL_GAS_MIXTURE_H_ + +#include +#include +#include +#include + +#include + +namespace kalypsso +{ + +namespace core +{ + +namespace eos +{ + +//! type alias for an array of equation of state +template +using ig_eos_array_t = Kokkos::View; + +// ========================================================================== +// ========================================================================== +// ========================================================================== +/** + * Ideal gas mixture helper class. + * + * In a multifluid simulation, when each fluid obeys a ideal gas EOS, by weighting each eos by + * the the volume fraction \f$z_i\f$, summing all equations and using isobaric closure, one obtains + * the mixture pressure: + * + * \f$ p = \frac{\rho e}{\epsilon} \f$, + * + * where \f$ \epsilon = \sum_i \frac{z_i}{\gamma_i-1}\f$ + * + * The mixture quantities are: + * + * - volumic fraction conservation : \f$ \sum_i \phi_i = 1 \f$ + * - density : \f$ \rho = \sum_i \phi_i \rho_i \f$ + * - internal energy : \f$ e_{int} = \sum_i $\phi_i eint_i \f$ + * - pressure : \f$ \forall i, p_i = p \f$ + * + */ +template +class IdealGasMixture +{ + +public: + IdealGasMixture() = default; + + IdealGasMixture(const ConfigMap & config_map) + : m_num_material(static_cast(config_map.getInteger("run", "nmat", 2))) + , m_ig_eos("eos ideal gas eos array", m_num_material) + , m_small_p(config_map.getReal("ideal_gas", "mixture_smallp", KALYPSSO_NUM(1e-9))) + { + const auto eos_type = get_eos_type(config_map); + + // only initialize eos array if ideal gas is requested + if (eos_type == +EOS_TYPE::IDEAL_GAS) + { + // create ideal eos array + auto ig_eos_host = Kokkos::create_mirror_view(m_ig_eos); + for (size_t i_mat = 0; i_mat < m_num_material; i_mat++) + ig_eos_host(i_mat) = IdealGasEos(i_mat, config_map); + Kokkos::deep_copy(m_ig_eos, ig_eos_host); + } + } + + //! number of materials + size_t m_num_material; + + //! array of equation of states, one per material + ig_eos_array_t m_ig_eos; + + //! small pressure guard + const real_t m_small_p; + + /** + * Compute mixture pressure. + * + * To be used only when there are two materials + */ + KOKKOS_INLINE_FUNCTION + real_t + mixture_pressure(real_t rho, real_t eint, real_t phi0) const + { + const real_t epsilon = compute_epsilon(phi0); + const auto pressure = rho * eint / epsilon; + return pressure < m_small_p ? m_small_p : pressure; + } + + /** + * Compute mixture specific internal energy. + * + * To be used only when there are two materials + */ + KOKKOS_INLINE_FUNCTION + real_t + mixture_specific_eint(real_t pressure, real_t rho, real_t phi0) const + { + const real_t epsilon = compute_epsilon(phi0); + return pressure * epsilon / rho; + } + + /** + * Compute mixture speed of sound. + * + * To be used only when there are two materials + */ + KOKKOS_INLINE_FUNCTION + real_t + mixture_sound_speed(real_t pressure, real_t rho, real_t phi0) const + { + const real_t epsilon = compute_epsilon(phi0); + return sqrt(1 / rho * (pressure * (1 + 1 / epsilon))); + } + +private: + /** + * Compute epsilon (mixture gas) from volumic fraction (material 0, phi = phi_0). + * + * To be used only when there are two materials. + */ + KOKKOS_INLINE_FUNCTION + real_t + compute_epsilon(real_t phi0) const + { + return phi0 * m_ig_eos(0).one_over_gammam1() + (1 - phi0) * m_ig_eos(1).one_over_gammam1(); + } + +}; // struct IdealGasMixture + +} // namespace eos + +} // namespace core + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_EOS_IDEAL_GAS_MIXTURE_H_ diff --git a/src/kalypsso/core/eos/README.md b/src/kalypsso/core/eos/README.md new file mode 100644 index 0000000..1e6412a --- /dev/null +++ b/src/kalypsso/core/eos/README.md @@ -0,0 +1,90 @@ +# Notes about the Van der Waals equation of state + +The extensive form of the [van der Waals EOS](https://en.wikipedia.org/wiki/Van_der_Waals_equation) reads + +```math +(P + \frac{an^2}{V^2})(V - n b ) = n R T +``` + +or + +```math +P = \frac{n R T}{V - n b} - \frac{a n^2}{V^2} +``` + + +where + +- $`P`$ is thermal pressure, +- $`V`$ is volume, +- $`T`$ is temperature, +- $`R`$ is the universal perfect gas constant, +- $`n`$ is the amount of substance (in mole), +- $`a`$ is cohesion term, +- $`b`$ is molar covolume. + +## internal energy as a function of pressure and density + +In order to use it in kalypsso, we need to derive the internal energy (either specific or volumic). + +Let's start by computing partial derivative of internal energy with respect to volume at constant temperature : $`\frac{\partial U}{\partial V}\bigg|_T`$. + +From thermodynamics first principle applied to a gas system: $`dU=T dS -P dV`$ or in term of free energy $`dF=-S dT -P dV`$. So that + +```math +\frac{\partial U}{\partial V}\bigg|_T = \frac{\partial (F+TS)}{\partial V}\bigg|_T += \frac{\partial F}{\partial V}\bigg|_T + T \frac{\partial S}{\partial V}\bigg|_T += -P + T \frac{\partial P}{\partial T}\bigg|_V += T^2 \frac{\partial (P/T)}{\partial T}\bigg|_V +``` + +We can integrate the last equation using a reference state: +```math +U(T,V,N) = U_{ref}(T,V_{ref},N) + \int_{V_{ref}}^V T^2 \frac{\partial (P/T)}{\partial T}\bigg|_V dV +``` + +When volume goes to infinity, the gas behaves as a ideal gas, so + +```math +U(T,V,N) = U_{ideal}(T) + \int_{\infty}^V T^2 \frac{\partial (P/T)}{\partial T}\bigg|_V dV = n C_v T - \frac{a n^2}{V} = m c_v T - \frac{a n^2}{V} +``` +where $`c_v`$ is the specific heat capacity at constant volume. Using the definition of $`gamma`$ the specific heat ratio, one obtains $`c_v=\frac{R}{(\gamma-1)M}`$ + +Finally, one obtains the relation between pressure and volumic internal energy $`\rho e = \frac{U}{V}`$: + +```math +P = \frac{\gamma-1}{1-\rho \frac{b}{M}} (\rho e + \frac{a \rho^2}{M}) - \frac{a \rho^2}{M} +``` + +where: + +- $`M`$ is molar mass +- $`\rho`$ is volumic mass + +By renormalizing $`a`$ and $`b`$, one finally obtains (see equation (6) in [Numerical Simulations of Compressible Two-Component Flows with General Equation of State](https://www.researchgate.net/publication/381769124_NUMERICAL_SIMULATIONS_OF_COMPRESSIBLE_TWO-_COMPONENT_FLOWS_WITH_GENERAL_EQUATION_OF_STATE) ) + +```math +P = \frac{\gamma-1}{1-\rho b} (\rho e + a \rho^2) - a \rho^2 +``` + +## Speed of sound + +By definition of speed of sound $`c=\sqrt{\frac{\partial P}{\partial \rho}\bigg_S}`$. +In order to derive the actual expression pf $`c`$, one uses alternate expression (see book by Toro, equation 1.36): + +```math +c^2=\frac{\frac{P}{\rho^2}-\frac{\partial e}{\partial \rho}\bigg_P}{\frac{\partial e}{\partial P}\bigg_\rho} +``` + +For the van der Waals EOS, one obtains: +```mat +c^2=\frac{\gamma P + (\gamma-2) a \rho^2 + 2 a b \rho^3}{(1 - b \rho)\rho} +``` + +We obtains of course the same expression as the one given in Pantano et al (see below), but more straightforwardly. + +## References about van der Waals EOS + +- Thermokinetic model of compressible multiphase flows, Ehsan Reyhanian, Benedikt Dorschner, Ilya Karlin. https://arxiv.org/abs/2002.09217; see equation A.17 for speed of sound +- An oscillation free shock-capturing method for compressible van der Waals supercritical fluid flows, C. Pantano , R. Saurel, T. Schmitt. Journal of Computational Physics, +Volume 335, 2017, Pages 780-811, https://doi.org/10.1016/j.jcp.2017.01.057; see equation A.26 for speed of sound (after some rewriting it is exactly the same as previous reference) diff --git a/src/kalypsso/core/eos/StiffenedGas.py b/src/kalypsso/core/eos/StiffenedGas.py new file mode 100644 index 0000000..1dedd2f --- /dev/null +++ b/src/kalypsso/core/eos/StiffenedGas.py @@ -0,0 +1,59 @@ +# SPDX-FileCopyrightText: 2025 kalypsso contributors +# +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +# -*- coding: utf-8 -*- + +import numpy as np + +class StiffenedGas: + + def __init__(self, gamma0, gamma1, pinf0, pinf1): + self.setup(gamma0, gamma1, pinf0, pinf1) + + def __compute_epsilon(self, phi0): + return phi0 / (self.gamma0 - 1) + (1 - phi0) / (self.gamma1 - 1) + + def __compute_gamma_pinf(self, phi0): + epsilon = self.__compute_epsilon(phi0) + phi1 = 1.0 - phi0 + return ((phi0 * self.pinf0 * self.gamma0) / (self.gamma0 - 1.0) + + (phi1 * self.pinf1 * self.gamma1) / (self.gamma1 - 1.0)) / epsilon + + def mixture_pressure(self, rho, eint, phi0): + small_p = 1e-6 + epsilon = self.__compute_epsilon(phi0) + gamma_pinf = self.__compute_gamma_pinf(phi0) + pressure = rho * eint / epsilon - gamma_pinf + return small_p if pressure < small_p else pressure + + def mixture_specific_eint(self, pressure, rho, phi0): + epsilon = self.__compute_epsilon(phi0) + gamma_pinf = self.__compute_gamma_pinf(phi0) + return (pressure + gamma_pinf) * epsilon / rho + + def mixture_sound_speed(self, pressure, rho, phi0): + epsilon = self.compute_epsilon(phi0) + gamma_pinf = self.compute_gamma_pinf(phi0) + return sqrt(1 / rho * (gamma_pinf + pressure * (1 + 1 / epsilon))) + + def setup(self, gamma0, gamma1, pinf0, pinf1): + self.gamma0 = gamma0 + self.gamma1 = gamma1 + self.pinf0 = pinf0 + self.pinf1 = pinf1 + + +############################################################################### +if __name__ == "__main__": + # water + gamma0=4.4 + pinf0=6e8 + + # air + gamma1=1.4 + pinf1=0.0 + + sg = StiffenedGas(gamma0, gamma1, pinf0, pinf1) + + print("stiffened gas pressure {}".format(sg.mixture_pressure(1000.0, 1e8, 0.9))) diff --git a/src/kalypsso/core/eos/StiffenedGasEos.h b/src/kalypsso/core/eos/StiffenedGasEos.h new file mode 100644 index 0000000..4b1d475 --- /dev/null +++ b/src/kalypsso/core/eos/StiffenedGasEos.h @@ -0,0 +1,179 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file StiffenedGasEos.h + * + * Stiffened gas equation of state is defined by: + * + * \f$ p = (\gamma-1) rho e - gamma p_\infty \f$ + * + * Can be used for ideal gas by setting \f$ p_\infty=0\f$ + */ +#ifndef KALYPSSO_CORE_EOS_STIFFENED_GAS_EOS_H_ +#define KALYPSSO_CORE_EOS_STIFFENED_GAS_EOS_H_ + +#include +#include +#include +#include + +namespace kalypsso +{ + +namespace core +{ + +namespace eos +{ + +// ============================================================================== +// ============================================================================== +// ============================================================================== +// ========================================================================== +// ========================================================================== +/** + * Stiffened gas equation of state is defined by: + * + * \f$ p = (\gamma-1) rho e - gamma p_\infty \f$ + * + * This struct is a helper for converting internal energy to pressure and reverse. + * + * The stiffened gas eos is useful for modeling liquids (or any weakly compressible medium). + * + * This equation of state is sometimes called the Tammann (or Tammann-Tait) EOS; e.g. see + * https://arxiv.org/pdf/1604.08544 + */ +struct StiffenedGasEos +{ + //! specific heat ratio + real_t m_gamma; + + //! reference pressure (p infinity) + real_t m_pinf; + + //! one over gamma minus one + real_t m_one_over_gammam1; + + KOKKOS_DEFAULTED_FUNCTION + StiffenedGasEos() = default; + + /** + * Initialize Eos for a given material by reading property from ini file. + * + * \param[in] i_mat material id (between 0 and nmat-1) + * \param[in] config_map input configuration map + */ + StiffenedGasEos(const size_t i_mat, const ConfigMap & config_map) + : m_gamma(get_gamma(i_mat, config_map)) + , m_pinf(get_pinf(i_mat, config_map)) + , m_one_over_gammam1(ONE_F / (m_gamma - ONE_F)) + {} + + /** + * Initialize Eos when only one material is needed. + * + * Useful for monofluid (only one material). + * + * \param[in] config_map input configuration map + */ + StiffenedGasEos(const ConfigMap & config_map) + : StiffenedGasEos(0, config_map) + {} + + KOKKOS_INLINE_FUNCTION + auto const & + gamma() const + { + return m_gamma; + } + + KOKKOS_INLINE_FUNCTION + auto const & + pinf() const + { + return m_pinf; + } + + KOKKOS_INLINE_FUNCTION + auto const & + one_over_gammam1() const + { + return m_one_over_gammam1; + } + + /** + * Compute pressure from volumic internal energy + */ + KOKKOS_INLINE_FUNCTION real_t + pressure_from_volumic_eint(real_t volumic_eint, [[maybe_unused]] real_t rho) const + { + return (m_gamma - ONE_F) * volumic_eint - m_gamma * m_pinf; + } + + /** + * Compute pressure from specific internal energy + */ + KOKKOS_INLINE_FUNCTION real_t + pressure_from_specific_eint(real_t specific_eint, real_t rho) const + { + return pressure_from_volumic_eint(rho * specific_eint, rho); + } + + /** + * Compute volumic internal energy from pressure + */ + KOKKOS_INLINE_FUNCTION real_t + volumic_eint_from_pressure(real_t pressure) const + { + return (pressure + m_gamma * m_pinf) * m_one_over_gammam1; + } + + /** + * Compute specific internal energy from pressure and density + */ + KOKKOS_INLINE_FUNCTION real_t + specific_eint_from_pressure(real_t pressure, real_t rho) const + { + return volumic_eint_from_pressure(pressure) / rho; + } + + /** + * By definition isentropic bulk modulus is \f$\kappa = -V \frac{dP}{dV}\f$, + * where the derivative is taken at constant entropy. + * + * In a fluid, one can show that sound speed is \f$c=\sqrt{\frac{\kappa}{\rho}}\f$, + * thus \f$\kappa=\rho c^2\f$. + */ + KOKKOS_INLINE_FUNCTION + real_t + sound_speed(real_t pressure, real_t rho) const + { + return sqrt(m_gamma * (m_pinf + pressure) / rho); + } + + /** + * By definition isentropic bulk modulus is \f$\kappa = -V \frac{dP}{dV}\f$, + * where the derivative is taken at constant entropy. + * + * In a fluid, one can show that sound speed is \f$c=\sqrt{\frac{\kappa}{\rho}}\f$, + * thus \f$\kappa=\rho c^2\f$. + */ + KOKKOS_INLINE_FUNCTION + real_t + bulk_modulus(real_t pressure, real_t rho) const + { + const auto c = sound_speed(pressure, rho); + return rho * c * c; + } + +}; // struct StiffenedGasEos + +} // namespace eos + +} // namespace core + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_EOS_STIFFENED_GAS_EOS_H_ diff --git a/src/kalypsso/core/eos/StiffenedGasMixture.h b/src/kalypsso/core/eos/StiffenedGasMixture.h new file mode 100644 index 0000000..c4ee83f --- /dev/null +++ b/src/kalypsso/core/eos/StiffenedGasMixture.h @@ -0,0 +1,179 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file StiffenedGasMixture.h + * + * This is only useful when using at least two material to define pressure and + * internal energy in mixed cells. + */ +#ifndef KALYPSSO_CORE_EOS_STIFFENED_GAS_MIXTURE_H_ +#define KALYPSSO_CORE_EOS_STIFFENED_GAS_MIXTURE_H_ + +#include +#include +#include +#include + +#include + +namespace kalypsso +{ + +namespace core +{ + +namespace eos +{ + +//! type alias for an array of equation of state +template +using sg_eos_array_t = Kokkos::View; + +// ========================================================================== +// ========================================================================== +// ========================================================================== +/** + * Stiffened gas mixture helper class. + * + * In a multifluid simulation, when each fluid obeys a stiffened gas EOS, by weighting each eos by + * the the volume fraction \f$z_i\f$, summing all equations and using isobaric closure, one obtains + * the mixture pressure: + * + * \f$ p = \frac{\rho e}{\epsilon} - \gamma p_{\infty} \f$, + * + * where \f$ \epsilon = \sum_i \frac{z_i}{\gamma_i-1}\f$ and + * \f$ \gamma p_{\infty} = \frac{1}{\epsilon} \sum_i \frac{z_i p_{\infty,i} \gamma_i}{\gamma_i-1} + * \f$ + * + * The mixture quantities are: + * + * - volumic fraction conservation : \f$ \sum_i \phi_i = 1 \f$ + * - density : \f$ \rho = \sum_i \phi_i \rho_i \f$ + * - internal energy : \f$ e_{int} = \sum_i $\phi_i eint_i \f$ + * - pressure : \f$ \forall i, p_i = p \f$ + * + */ +template +class StiffenedGasMixture +{ + +public: + StiffenedGasMixture() = default; + + StiffenedGasMixture(const ConfigMap & config_map) + : m_num_material(static_cast(config_map.getInteger("run", "nmat", 2))) + , m_sg_eos("eos stiffened gas eos array", m_num_material) + , m_small_p(config_map.getReal("stiffened_gas", "mixture_smallp", KALYPSSO_NUM(1e-9))) + { + const auto eos_type = get_eos_type(config_map); + + // only initialize eos array if stiffened gas is requested + if (eos_type == +EOS_TYPE::STIFFENED_GAS) + { + // create stiffened eos array + auto sg_eos_host = Kokkos::create_mirror_view(m_sg_eos); + for (size_t i_mat = 0; i_mat < m_num_material; i_mat++) + sg_eos_host(i_mat) = StiffenedGasEos(i_mat, config_map); + Kokkos::deep_copy(m_sg_eos, sg_eos_host); + } + } + + //! number of materials + size_t m_num_material; + + //! array of equation of states, one per material + sg_eos_array_t m_sg_eos; + + //! small pressure guard + const real_t m_small_p; + + /** + * Compute mixture pressure. + * + * To be used only when there are two materials + */ + KOKKOS_INLINE_FUNCTION + real_t + mixture_pressure(real_t rho, real_t eint, real_t phi0) const + { + const real_t epsilon = compute_epsilon(phi0); + const real_t gamma_pinf = compute_gamma_pinf(phi0); + const auto pressure = rho * eint / epsilon - gamma_pinf; + return pressure < m_small_p ? m_small_p : pressure; + } + + /** + * Compute mixture specific internal energy. + * + * To be used only when there are two materials + */ + KOKKOS_INLINE_FUNCTION + real_t + mixture_specific_eint(real_t pressure, real_t rho, real_t phi0) const + { + const real_t epsilon = compute_epsilon(phi0); + const real_t gamma_pinf = compute_gamma_pinf(phi0); + return (pressure + gamma_pinf) * epsilon / rho; + } + + /** + * Compute mixture speed of sound. + * + * To be used only when there are two materials + */ + KOKKOS_INLINE_FUNCTION + real_t + mixture_sound_speed(real_t pressure, real_t rho, real_t phi0) const + { + const real_t epsilon = compute_epsilon(phi0); + const real_t gamma_pinf = compute_gamma_pinf(phi0); + return sqrt(1 / rho * (gamma_pinf + pressure * (1 + 1 / epsilon))); + } + +private: + /** + * Compute epsilon (mixture gas) from volumic fraction (material 0, phi = phi_0). + * + * To be used only when there are two materials. + */ + KOKKOS_INLINE_FUNCTION + real_t + compute_epsilon(real_t phi0) const + { + return phi0 * m_sg_eos(0).one_over_gammam1() + (1 - phi0) * m_sg_eos(1).one_over_gammam1(); + } + + /** + * Compute gamma times pinf for mixture gas. + * + * To be used only when there are two materials. + */ + KOKKOS_INLINE_FUNCTION + real_t + compute_gamma_pinf(real_t phi0) const + { + const real_t epsilon = compute_epsilon(phi0); + const real_t phi1 = 1 - phi0; + auto const & gamma0 = m_sg_eos(0).gamma(); + auto const & gamma1 = m_sg_eos(1).gamma(); + auto const & one_over_gamma0m1 = m_sg_eos(0).one_over_gammam1(); + auto const & one_over_gamma1m1 = m_sg_eos(1).one_over_gammam1(); + auto const & pinf0 = m_sg_eos(0).pinf(); + auto const & pinf1 = m_sg_eos(1).pinf(); + // clang-format off + return ((phi0 * pinf0 * gamma0) * one_over_gamma0m1 + + (phi1 * pinf1 * gamma1) * one_over_gamma1m1) / epsilon; + // clang-format on + } + +}; // struct StiffenedGasMixture + +} // namespace eos + +} // namespace core + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_EOS_STIFFENED_GAS_MIXTURE_H_ diff --git a/src/kalypsso/core/eos/VanDerWaalsGasEos.h b/src/kalypsso/core/eos/VanDerWaalsGasEos.h new file mode 100644 index 0000000..dda5aef --- /dev/null +++ b/src/kalypsso/core/eos/VanDerWaalsGasEos.h @@ -0,0 +1,213 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file VanDerWaalsGasEos.h + * + * Van der Waals gas equation of state is defined by: + * + * \f$ p = \frac{\gamma-1}{1-b\rho} (rho e + a \rho^2) -a \rho^2 \f$ + * + * some references: + * + * - Thermodynamic properties of the van der Waals Fluid, D.C. Johnston, + * https://arxiv.org/abs/1402.1205 + * - Thermokinetic model of compressible multiphase flows, Ehsan Reyhanian, Benedikt Dorschner, + * Ilya Karlin. https://arxiv.org/abs/2002.09217; see equation A.17 for speed of sound + * - An oscillation free shock-capturing method for compressible van der Waals supercritical + * fluid flows, C. Pantano , R. Saurel, T. Schmitt. Journal of Computational Physics, + * Volume 335, 2017, Pages 780-811, https://doi.org/10.1016/j.jcp.2017.01.057; + * see equation A.26 for speed of sound (after some rewriting it is exactly the same + * as previous reference) + * + */ +#ifndef KALYPSSO_CORE_EOS_VANDERWAALSGAS_EOS_H_ +#define KALYPSSO_CORE_EOS_VANDERWAALSGAS_EOS_H_ + +#include +#include +#include +#include + +namespace kalypsso +{ + +namespace core +{ + +namespace eos +{ + +// ============================================================================== +// ============================================================================== +// ============================================================================== +// ========================================================================== +// ========================================================================== +/** + * Van der Waals gas equation of state is defined by: + * + * \f$ p = \frac{\gamma-1}{1-b\rho} (rho e + a \rho^2) -a \rho^2 \f$ + * + * This struct is a helper for converting internal energy to pressure and reverse. + * + */ +struct VanDerWaalsGasEos +{ + //! specific heat ratio + real_t m_gamma; + + //! one over gamma minus one + real_t m_one_over_gammam1; + + //! cohesion parameter + real_t m_a; + + //! covolume + real_t m_b; + + /** + * Read cohesion parameter. + */ + static inline auto + get_a(const size_t i_mat, const ConfigMap & config_map) + { + const auto material_id = "material" + std::to_string(i_mat); + + return config_map.getReal(material_id, "a", KALYPSSO_NUM(0.0)); + } + + /** + * Read covolume parameter. + */ + static inline auto + get_b(const size_t i_mat, const ConfigMap & config_map) + { + const auto material_id = "material" + std::to_string(i_mat); + + return config_map.getReal(material_id, "b", KALYPSSO_NUM(0.0)); + } + + + KOKKOS_DEFAULTED_FUNCTION + VanDerWaalsGasEos() = default; + + /** + * Initialize Eos for a given material by reading property from ini file. + * + * \param[in] i_mat material id (between 0 and nmat-1) + * \param[in] config_map input configuration map + */ + VanDerWaalsGasEos(const size_t i_mat, const ConfigMap & config_map) + : m_gamma(get_gamma(i_mat, config_map)) + , m_one_over_gammam1(ONE_F / (m_gamma - ONE_F)) + , m_a(get_a(i_mat, config_map)) + , m_b(get_b(i_mat, config_map)) + {} + + /** + * Initialize Van der Waals Eos. + * + * Useful for monofluid (only one material). + * + * \param[in] config_map input configuration map + */ + VanDerWaalsGasEos(const ConfigMap & config_map) + : VanDerWaalsGasEos(0, config_map) + {} + + KOKKOS_INLINE_FUNCTION + auto const & + gamma() const + { + return m_gamma; + } + + KOKKOS_INLINE_FUNCTION + auto const & + one_over_gammam1() const + { + return m_one_over_gammam1; + } + + /** + * Compute pressure from volumic internal energy + */ + KOKKOS_INLINE_FUNCTION real_t + pressure_from_volumic_eint(real_t eint_volumic, real_t rho) const + { + const auto arho2 = m_a * rho * rho; + return (m_gamma - ONE_F) / (1 - m_b * rho) * (eint_volumic + arho2) - arho2; + } + + /** + * Compute pressure from specific internal energy + */ + KOKKOS_INLINE_FUNCTION real_t + pressure_from_specific_eint(real_t eint_specific, real_t rho) const + { + return pressure_from_volumic_eint(rho * eint_specific, rho); + } + + /** + * Compute volumic internal energy from pressure + */ + KOKKOS_INLINE_FUNCTION real_t + volumic_eint_from_pressure(real_t pressure, real_t rho) const + { + const auto arho2 = m_a * rho * rho; + return (pressure + arho2) * m_one_over_gammam1 * (1 - m_b * rho) - arho2; + } + + /** + * Compute specific internal energy from pressure and density + */ + KOKKOS_INLINE_FUNCTION real_t + specific_eint_from_pressure(real_t pressure, real_t rho) const + { + return volumic_eint_from_pressure(pressure, rho) / rho; + } + + /** + * By using the alternate definition of speed of sound + * + * \f$ \frac{\frac{P}{\rho^2}-\frac{\partial e}{\partial \rho}\bigg_P}{\frac{\partial e}{\partial + * P}\bigg_\rho}\f$ + * + * on obtains for the van der Waals EOS the following expression + * \f$ c = \sqrt{ \frac{\gamma P + (\gamma-2) a \rho^2 + 2 a b \rho^3}{\rho(1 -b \rho )} }\f$. + */ + KOKKOS_INLINE_FUNCTION + real_t + sound_speed(real_t pressure, real_t rho) const + { + const auto arho2 = m_a * rho * rho; + + return sqrt((m_gamma * pressure + (m_gamma - 2) * arho2 + 2 * m_b * arho2 * rho) / + (rho - m_b * rho * rho)); + } + + /** + * By definition isentropic bulk modulus is \f$\kappa = -V \frac{dP}{dV}\f$, + * where the derivative is taken at constant entropy. + * + * In a fluid, one can show that sound speed is \f$c=\sqrt{\frac{\kappa}{\rho}}\f$, + * thus \f$\kappa=\rho c^2\f$. + */ + KOKKOS_INLINE_FUNCTION + real_t + bulk_modulus(real_t pressure, real_t rho) const + { + const auto c = sound_speed(pressure, rho); + return rho * c * c; + } + +}; // struct VanDerWaalsGasEos + +} // namespace eos + +} // namespace core + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_EOS_VANDERWAALSGAS_EOS_H_ diff --git a/src/kalypsso/core/eos/eos_utils.h b/src/kalypsso/core/eos/eos_utils.h new file mode 100644 index 0000000..09f355b --- /dev/null +++ b/src/kalypsso/core/eos/eos_utils.h @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file StiffenedGasEos.h + */ +#ifndef KALYPSSO_GODUNOV_FIVE_EQ_MODELS_EOS_UTILS_H_ +#define KALYPSSO_GODUNOV_FIVE_EQ_MODELS_EOS_UTILS_H_ + +#include +#include + +#include <../../better-enums/enum.h> + +namespace kalypsso +{ + +namespace core +{ + +namespace eos +{ + +//! only useful for ideal gas EOS +KOKKOS_INLINE_FUNCTION real_t +gamma6(real_t const & gamma0) +{ + return (gamma0 + ONE_F) / (TWO_F * gamma0); +} + +// clang-format off +/** + * Enumerate types of stencil used for finite difference estimation first derivative. + */ +BETTER_ENUM(EOS_TYPE, int, + IDEAL_GAS, + STIFFENED_GAS, + VANDERWAALS_GAS, + MIE_GRUNEISEN) +// clang-format on + +/** + * Read eos type from input parameters file. + */ +inline EOS_TYPE +get_eos_type(ConfigMap const & config_map) +{ + auto eos_name = config_map.getString("eos", "name", "STIFFENED_GAS"); + auto maybe_value = EOS_TYPE::_from_string_nothrow(eos_name.c_str()); + if (maybe_value) + return *maybe_value; + return EOS_TYPE::STIFFENED_GAS; +} + +/** + * Read specific heat ratio. + */ +inline auto +get_gamma(const size_t i_mat, const ConfigMap & config_map) +{ + const auto material_id = "material" + std::to_string(i_mat); + + return config_map.getReal(material_id, "gamma", KALYPSSO_NUM(1.4)); +} + +/** + * Read pinf (stiffened gas parameter). + */ +inline auto +get_pinf(const size_t i_mat, const ConfigMap & config_map) +{ + const auto material_id = "material" + std::to_string(i_mat); + + return config_map.getReal(material_id, "pinf", KALYPSSO_NUM(0.0)); +} + +} // namespace eos + +} // namespace core + +} // namespace kalypsso + +#endif // KALYPSSO_GODUNOV_FIVE_EQ_MODELS_EOS_UTILS_H_ diff --git a/src/kalypsso/core/geometry_utils.h b/src/kalypsso/core/geometry_utils.h new file mode 100644 index 0000000..0066ce9 --- /dev/null +++ b/src/kalypsso/core/geometry_utils.h @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +/** + * \file geometry_utils.h + */ +#ifndef KALYPSSO_CORE_GEOMETRY_UTILS_H_ +#define KALYPSSO_CORE_GEOMETRY_UTILS_H_ + +#include +#include +#include +#include +#include + +namespace kalypsso +{ + +// ======================================================================================= +// ======================================================================================= +/** + * Return the coefficients of the equation of the tangent to a circle (2d) / sphere (3d). + * + * Given a circle or sphere (center C and radius), given a point M, computes the equation of the + * tangent line (2d) or tangent plane (3d) to that circle/sphere which normal vector is CM and such + * that the tangent point T verifies CT = alpha CM with alpha > 0. + * + * In 2d the equation of the tangent line is: \f$ n_x x + n_y y = \alpha\f$ + * + * In 3d the equation of the tangent plane is: \f$ n_x x + n_y y + n_z z = \alpha\f$ + * + * + * \param[in] O circle (or sphere) center coordinates vector + * \param[in] radius circle/sphere radius + * \param[in] M coordinates vector of point M + * \param[out] normal normal unit vector (nx, ny, nz) + * \param[out] alpha + * + */ +template +KOKKOS_INLINE_FUNCTION void +get_tangent_to_sphere(Kokkos::Array const & O, + real_t radius, + Kokkos::Array const & M, + Kokkos::Array & normal, + real_t & alpha) +{ + normal = M - O; + auto d = normal[IX] * normal[IX] + normal[IY] * normal[IY]; + if constexpr (dim == 3) + { + d += normal[IZ] * normal[IZ]; + } + d = sqrt(d); + + normal[IX] /= d; + normal[IY] /= d; + if constexpr (dim == 3) + { + normal[IZ] /= d; + } + + Kokkos::Array T; + T[IX] = O[IX] + radius * (normal[IX]); + T[IY] = O[IY] + radius * (normal[IY]); + if constexpr (dim == 3) + { + T[IZ] = O[IZ] + radius * (normal[IZ]); + } + + alpha = normal[IX] * T[IX] + normal[IY] * T[IY]; + if constexpr (dim == 3) + { + alpha += normal[IZ] * T[IZ]; + } + +} // get_tangent_to_sphere + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_GEOMETRY_UTILS_H_ diff --git a/src/kalypsso/core/init_func.h b/src/kalypsso/core/init_func.h new file mode 100644 index 0000000..54d5273 --- /dev/null +++ b/src/kalypsso/core/init_func.h @@ -0,0 +1,171 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * init_funct.h + * + * Some dummy init functor, mostly used for test purpose. + */ +#ifndef KALYPSSO_CORE_INIT_FUNC_H_ +#define KALYPSSO_CORE_INIT_FUNC_H_ + +#include // for KALYPSSO_ASSERT +#include +#include + +namespace kalypsso +{ + +// ============================================================================= +// ============================================================================= +//! a dummy pointwise init functor +struct InitFunc1 +{ + static constexpr bool has_face_averaged_values = false; + + KOKKOS_FUNCTION auto + operator()(real_t x, real_t y, int var) const + { + return x + y + static_cast(var); + } + + KOKKOS_FUNCTION auto + faverage([[maybe_unused]] real_t x, + [[maybe_unused]] real_t y, + [[maybe_unused]] real_t delta_x, + [[maybe_unused]] real_t delta_y, + [[maybe_unused]] int var) const + { + return ZERO_F; + } + + KOKKOS_FUNCTION auto + operator()(real_t x, real_t y, real_t z, int var) const + { + return x + y + z + static_cast(var); + } +}; + +// ============================================================================= +// ============================================================================= +//! another dummy pointwise init functor +//! should be periodic in all direction +struct InitFunc2 +{ + static constexpr bool has_face_averaged_values = false; + + KOKKOS_FUNCTION auto + operator()(real_t x, real_t y, int var) const + { + return sin(TWO_F * PI_F * x + TWO_F * PI_F * y) + static_cast(var); + } + + KOKKOS_FUNCTION auto + faverage([[maybe_unused]] real_t x, + [[maybe_unused]] real_t y, + [[maybe_unused]] real_t delta_x, + [[maybe_unused]] real_t delta_y, + [[maybe_unused]] int var) const + { + return ZERO_F; + } + + KOKKOS_FUNCTION auto + operator()(real_t x, real_t y, real_t z, int var) const + { + return sin(TWO_F * PI_F * x + TWO_F * PI_F * y + TWO_F * PI_F * z) + static_cast(var); + } +}; + +// ============================================================================= +// ============================================================================= +//! another dummy pointwise init functor. +//! +//! it is periodic in all directions and vector field is divergence-free. +//! +//! Let's call Bx(x,y,z), By(x,y,z), Bz(x,y,z) the vector field +//! +struct InitFunc3 +{ + static constexpr bool has_face_averaged_values = true; + + KOKKOS_FUNCTION real_t + operator()(real_t x, real_t y, int var) const + { + if (var == IX) + return cos(2 * PI_F * (x + y)) * cos(2 * PI_F * (x + y)); + else if (var == IY) + return sin(2 * PI_F * (x + y)) * sin(2 * PI_F * (x + y)); + else if (var == IZ) + return cos(2 * PI_F * (x + y)) + sin(2 * PI_F * (x + y)); + else + return ZERO_F; + } + + //! face average value + //! + //! + KOKKOS_FUNCTION real_t + faverage(real_t x, real_t y, real_t delta_x, real_t delta_y, int var) const + { + if (var == IX) + return HALF_F + ONE_F / PI_F * cos(PI_F * (x + y)) * sin(PI_F * delta_y / 2) / delta_y; + else if (var == IY) + return HALF_F - ONE_F / PI_F * cos(PI_F * (x + y)) * sin(PI_F * delta_x / 2) / delta_x; + else if (var == IZ) + return ONE_F / PI_F / PI_F * sin(PI_F * delta_x) * sin(PI_F * delta_y) * + (cos(TWO_F * PI_F * (x + y)) + sin(TWO_F * PI_F * (x + y))); + else + return ZERO_F; + } + + KOKKOS_FUNCTION real_t + operator()(real_t x, real_t y, real_t z, int var) const + { + if (var == IX) + return sin(2 * PI_F * z) * cos(2 * PI_F * (x + y)); + else if (var == IY) + return sin(2 * PI_F * z) * sin(2 * PI_F * (x + y)); + else if (var == IZ) + return cos(2 * PI_F * z) * (cos(2 * PI_F * (x + y)) - sin(2 * PI_F * (x + y))); + else + return ZERO_F; + } +}; + +// ============================================================================= +// ============================================================================= +//! a dummy pointwise init functor +//! +//! If used as a vector field, should be divergence free. +struct InitFunc4 +{ + static constexpr bool has_face_averaged_values = false; + + KOKKOS_FUNCTION auto + operator()(real_t x, real_t y, int var) const + { + return x - y + static_cast(var); + } + + KOKKOS_FUNCTION auto + faverage([[maybe_unused]] real_t x, + [[maybe_unused]] real_t y, + [[maybe_unused]] real_t delta_x, + [[maybe_unused]] real_t delta_y, + [[maybe_unused]] int var) const + { + return ZERO_F; + } + + KOKKOS_FUNCTION auto + operator()(real_t x, real_t y, real_t z, int var) const + { + return x + y - 2 * z + static_cast(var); + } +}; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_INIT_FUNC_H_ diff --git a/src/kalypsso/core/io_utils.cpp b/src/kalypsso/core/io_utils.cpp new file mode 100644 index 0000000..2ab3233 --- /dev/null +++ b/src/kalypsso/core/io_utils.cpp @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file io_utils.cpp + */ +#include + +namespace kalypsso +{ + +// ======================================================= +// ======================================================= +size_t +build_var_to_write_map(names2id_t & map, + const names2id_t & avail_names, + const ConfigMap & config_map) +{ + + // Read parameter file and get the list of variable field names + // we wish to write + // variable names must be comma-separated (no quotes !) + // e.g. write_variables=rho,vx,vy,unknown + std::string write_variables = config_map.getString("output", "write_variables", "rho,rho_vx"); + + // now tokenize + std::istringstream iss(write_variables); + std::string token; + while (std::getline(iss, token, ',')) + { + + // check if token is valid, i.e. present in avail_names + auto got = avail_names.find(token); + // std::cout << " " << token << " " << got->second << "\n"; + + // if token is valid, we insert it into map + if (got != avail_names.end()) + { + map[token] = got->second; + } + } + + return map.size(); + +} // build_var_to_write_map + +} // namespace kalypsso diff --git a/src/kalypsso/core/io_utils.h b/src/kalypsso/core/io_utils.h new file mode 100644 index 0000000..d937395 --- /dev/null +++ b/src/kalypsso/core/io_utils.h @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file io_utils.h + */ +#ifndef KALYPSSO_CORE_IO_UTILS_H_ +#define KALYPSSO_CORE_IO_UTILS_H_ + +#include +#include +#include +#include + +#include + +namespace kalypsso +{ + +/** + * Use config_map information to retrieve a list of scalar field to write, + * compute the their id the access them in DataArray. This routine returns + * a map with this information. + * + * \param[in,out] map this is the map to fill + * \param[in] avail_names list of available variable names + * \param[in] config_map parameters settings + * + * \return the map size (i.e. the number of valid variable names) + */ +size_t +build_var_to_write_map(names2id_t & map, + const names2id_t & avail_names, + const ConfigMap & config_map); + +/** + * Check if file exists. + * + * \param[in] filename file name to check. + */ +inline bool +file_exists(const std::string & filename) +{ + std::ifstream f(filename.c_str()); + return f.good(); +} + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_IO_UTILS_H_ diff --git a/src/kalypsso/core/kalypsso_comm_config.h b/src/kalypsso/core/kalypsso_comm_config.h new file mode 100644 index 0000000..86e51a6 --- /dev/null +++ b/src/kalypsso/core/kalypsso_comm_config.h @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file kalypsso_comm_config.h + */ +#ifndef KALYPSSO_CORE_KALYPSSOCOMMCONFIG_H_ +#define KALYPSSO_CORE_KALYPSSOCOMMCONFIG_H_ + +#include + +namespace kalypsso +{ + +//! Define list of MPI tags used by kalypsso exclusively. +//! Start at number above the last tag value used by p4est itself. +enum kalypsso_comm_tag +{ + KALYPSSO_COMM_MESH_PARTITIONER_TAG = P4EST_COMM_TAG_LAST + 1, + KALYPSSO_COMM_GHOST_EXCHANGE_TAG = P4EST_COMM_TAG_LAST + 2, +}; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_KALYPSSOCOMMCONFIG_H_ diff --git a/src/kalypsso/core/kalypsso_core_base.h b/src/kalypsso/core/kalypsso_core_base.h new file mode 100644 index 0000000..5206a5c --- /dev/null +++ b/src/kalypsso/core/kalypsso_core_base.h @@ -0,0 +1,195 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file kalypsso_core_base.h + * + * Define utility macros inspired by p4est. + * + * \sa Please note that for logging we should use spdlog-based macro, defined in kalypsso_log.h. + * spdlog-based macro should be preferred (they are more flexible (can be activated/deactivated at + * compile-time and runtime). + */ +#ifndef KALYPSSO_CORE_KALYPSSO_CORE_BASE_H_ +#define KALYPSSO_CORE_KALYPSSO_CORE_BASE_H_ + +#include +#include + +#include + +#ifndef assertm +# define assertm(exp, msg) assert(((void)msg, exp)) +#endif + +#ifdef KALYPSSO_CORE_ENABLE_DEBUG +# define KALYPSSO_ASSERT(c) SC_CHECK_ABORT((c), "Assertion '" #c "'") +# define KALYPSSO_EXECUTE_ASSERT_FALSE(expression) \ + do \ + { \ + int _kalypsso_i = (int)(expression); \ + SC_CHECK_ABORT(!_kalypsso_i, "Expected false: '" #expression "'"); \ + } while (0) +# define KALYPSSO_EXECUTE_ASSERT_TRUE(expression) \ + do \ + { \ + int _kalypsso_i = (int)(expression); \ + SC_CHECK_ABORT(_kalypsso_i, "Expected true: '" #expression "'"); \ + } while (0) +# define KALYPSSO_DEBUG_EXECUTE(expression) \ + do \ + { \ + (void)(expression); \ + } while (0) +#else +# define KALYPSSO_ASSERT(c) SC_NOOP() +# define KALYPSSO_EXECUTE_ASSERT_FALSE(expression) \ + do \ + { \ + (void)(expression); \ + } while (0) +# define KALYPSSO_EXECUTE_ASSERT_TRUE(expression) \ + do \ + { \ + (void)(expression); \ + } while (0) +# define KALYPSSO_DEBUG_EXECUTE(expression) SC_NOOP() +#endif + +/* macros for memory allocation, will abort if out of memory */ +/** allocate a \a t-array with \a n elements */ +#define KALYPSSO_ALLOC(t, n) (t *)sc_malloc(kalypsso_package_id, (n) * sizeof(t)) +/** allocate a \a t-array with \a n elements and zero */ +#define KALYPSSO_ALLOC_ZERO(t, n) (t *)sc_calloc(kalypsso_package_id, (size_t)(n), sizeof(t)) +/** reallocate the \a t-array \a p with \a n elements */ +#define KALYPSSO_REALLOC(p, t, n) (t *)sc_realloc(kalypsso_package_id, (p), (n) * sizeof(t)) +/** duplicate a string */ +#define KALYPSSO_STRDUP(s) sc_strdup(kalypsso_package_id, (s)) +/** free an allocated array */ +#define KALYPSSO_FREE(p) sc_free(kalypsso_package_id, (p)) + +/* log helper macros */ +#define KALYPSSO_GLOBAL_LOG(p, s) SC_GEN_LOG(kalypsso_package_id, SC_LC_GLOBAL, (p), (s)) +#define KALYPSSO_LOG(p, s) SC_GEN_LOG(kalypsso_package_id, SC_LC_NORMAL, (p), (s)) +void +KALYPSSO_GLOBAL_LOGF(int priority, const char * fmt, ...) __attribute__((format(printf, 2, 3))); +void +KALYPSSO_LOGF(int priority, const char * fmt, ...) __attribute__((format(printf, 2, 3))); +#ifndef __cplusplus +# define KALYPSSO_GLOBAL_LOGF(p, f, ...) \ + SC_GEN_LOGF(kalypsso_package_id, SC_LC_GLOBAL, (p), (f), __VA_ARGS__) +# define KALYPSSO_LOGF(p, f, ...) \ + SC_GEN_LOGF(kalypsso_package_id, SC_LC_NORMAL, (p), (f), __VA_ARGS__) +#endif + +/* convenience global log macros will only print if identifier <= 0 */ +#define KALYPSSO_GLOBAL_TRACE(s) KALYPSSO_GLOBAL_LOG(SC_LP_TRACE, (s)) +#define KALYPSSO_GLOBAL_LDEBUG(s) KALYPSSO_GLOBAL_LOG(SC_LP_DEBUG, (s)) +#define KALYPSSO_GLOBAL_VERBOSE(s) KALYPSSO_GLOBAL_LOG(SC_LP_VERBOSE, (s)) +#define KALYPSSO_GLOBAL_INFO(s) KALYPSSO_GLOBAL_LOG(SC_LP_INFO, (s)) +#define KALYPSSO_GLOBAL_STATISTICS(s) KALYPSSO_GLOBAL_LOG(SC_LP_STATISTICS, (s)) +#define KALYPSSO_GLOBAL_PRODUCTION(s) KALYPSSO_GLOBAL_LOG(SC_LP_PRODUCTION, (s)) +#define KALYPSSO_GLOBAL_ESSENTIAL(s) KALYPSSO_GLOBAL_LOG(SC_LP_ESSENTIAL, (s)) +#define KALYPSSO_GLOBAL_LERROR(s) KALYPSSO_GLOBAL_LOG(SC_LP_ERROR, (s)) +void +KALYPSSO_GLOBAL_TRACEF(const char * fmt, ...) __attribute__((format(printf, 1, 2))); +void +KALYPSSO_GLOBAL_LDEBUGF(const char * fmt, ...) __attribute__((format(printf, 1, 2))); +void +KALYPSSO_GLOBAL_VERBOSEF(const char * fmt, ...) __attribute__((format(printf, 1, 2))); +void +KALYPSSO_GLOBAL_INFOF(const char * fmt, ...) __attribute__((format(printf, 1, 2))); +void +KALYPSSO_GLOBAL_STATISTICSF(const char * fmt, ...) __attribute__((format(printf, 1, 2))); +void +KALYPSSO_GLOBAL_PRODUCTIONF(const char * fmt, ...) __attribute__((format(printf, 1, 2))); +void +KALYPSSO_GLOBAL_ESSENTIALF(const char * fmt, ...) __attribute__((format(printf, 1, 2))); +void +KALYPSSO_GLOBAL_LERRORF(const char * fmt, ...) __attribute__((format(printf, 1, 2))); +#ifndef __cplusplus +# define KALYPSSO_GLOBAL_TRACEF(f, ...) KALYPSSO_GLOBAL_LOGF(SC_LP_TRACE, (f), __VA_ARGS__) +# define KALYPSSO_GLOBAL_LDEBUGF(f, ...) KALYPSSO_GLOBAL_LOGF(SC_LP_DEBUG, (f), __VA_ARGS__) +# define KALYPSSO_GLOBAL_VERBOSEF(f, ...) KALYPSSO_GLOBAL_LOGF(SC_LP_VERBOSE, (f), __VA_ARGS__) +# define KALYPSSO_GLOBAL_INFOF(f, ...) KALYPSSO_GLOBAL_LOGF(SC_LP_INFO, (f), __VA_ARGS__) +# define KALYPSSO_GLOBAL_STATISTICSF(f, ...) \ + KALYPSSO_GLOBAL_LOGF(SC_LP_STATISTICS, (f), __VA_ARGS__) +# define KALYPSSO_GLOBAL_PRODUCTIONF(f, ...) \ + KALYPSSO_GLOBAL_LOGF(SC_LP_PRODUCTION, (f), __VA_ARGS__) +# define KALYPSSO_GLOBAL_ESSENTIALF(f, ...) KALYPSSO_GLOBAL_LOGF(SC_LP_ESSENTIAL, (f), __VA_ARGS__) +# define KALYPSSO_GLOBAL_LERRORF(f, ...) KALYPSSO_GLOBAL_LOGF(SC_LP_ERROR, (f), __VA_ARGS__) +#endif +#define KALYPSSO_GLOBAL_NOTICE KALYPSSO_GLOBAL_STATISTICS +#define KALYPSSO_GLOBAL_NOTICEF KALYPSSO_GLOBAL_STATISTICSF + +/* convenience log macros that are active on every processor */ +// #ifndef KALYPSSO_CORE_USE_SPDLOG +// # define KALYPSSO_TRACE(s) KALYPSSO_LOG(SC_LP_TRACE, (s)) +// # define KALYPSSO_LDEBUG(s) KALYPSSO_LOG(SC_LP_DEBUG, (s)) +// # define KALYPSSO_VERBOSE(s) KALYPSSO_LOG(SC_LP_VERBOSE, (s)) +// # define KALYPSSO_INFO(s) KALYPSSO_LOG(SC_LP_INFO, (s)) +// # define KALYPSSO_STATISTICS(s) KALYPSSO_LOG(SC_LP_STATISTICS, (s)) +// # define KALYPSSO_PRODUCTION(s) KALYPSSO_LOG(SC_LP_PRODUCTION, (s)) +// # define KALYPSSO_ESSENTIAL(s) KALYPSSO_LOG(SC_LP_ESSENTIAL, (s)) +// # define KALYPSSO_LERROR(s) KALYPSSO_LOG(SC_LP_ERROR, (s)) +// #endif + +void +KALYPSSO_TRACEF(const char * fmt, ...) __attribute__((format(printf, 1, 2))); +void +KALYPSSO_LDEBUGF(const char * fmt, ...) __attribute__((format(printf, 1, 2))); +void +KALYPSSO_VERBOSEF(const char * fmt, ...) __attribute__((format(printf, 1, 2))); +void +KALYPSSO_INFOF(const char * fmt, ...) __attribute__((format(printf, 1, 2))); +void +KALYPSSO_STATISTICSF(const char * fmt, ...) __attribute__((format(printf, 1, 2))); +void +KALYPSSO_PRODUCTIONF(const char * fmt, ...) __attribute__((format(printf, 1, 2))); +void +KALYPSSO_ESSENTIALF(const char * fmt, ...) __attribute__((format(printf, 1, 2))); +void +KALYPSSO_LERRORF(const char * fmt, ...) __attribute__((format(printf, 1, 2))); +#ifndef __cplusplus +# define KALYPSSO_TRACEF(f, ...) KALYPSSO_LOGF(SC_LP_TRACE, (f), __VA_ARGS__) +# define KALYPSSO_LDEBUGF(f, ...) KALYPSSO_LOGF(SC_LP_DEBUG, (f), __VA_ARGS__) +# define KALYPSSO_VERBOSEF(f, ...) KALYPSSO_LOGF(SC_LP_VERBOSE, (f), __VA_ARGS__) +# define KALYPSSO_INFOF(f, ...) KALYPSSO_LOGF(SC_LP_INFO, (f), __VA_ARGS__) +# define KALYPSSO_STATISTICSF(f, ...) KALYPSSO_LOGF(SC_LP_STATISTICS, (f), __VA_ARGS__) +# define KALYPSSO_PRODUCTIONF(f, ...) KALYPSSO_LOGF(SC_LP_PRODUCTION, (f), __VA_ARGS__) +# define KALYPSSO_ESSENTIALF(f, ...) KALYPSSO_LOGF(SC_LP_ESSENTIAL, (f), __VA_ARGS__) +# define KALYPSSO_LERRORF(f, ...) KALYPSSO_LOGF(SC_LP_ERROR, (f), __VA_ARGS__) +#endif +#define KALYPSSO_NOTICE KALYPSSO_STATISTICS +#define KALYPSSO_NOTICEF KALYPSSO_STATISTICSF + +/* extern declarations */ +/** the libsc package id for kalypsso (set in kalypsso_init()) */ +extern int kalypsso_package_id; + +static inline void +kalypsso_log_indent_push() +{ + sc_log_indent_push_count(kalypsso_package_id, 1); +} + +static inline void +kalypsso_log_indent_pop() +{ + sc_log_indent_pop_count(kalypsso_package_id, 1); +} + +/** Registers kalypsso with the SC Library and sets the logging behavior. + * This function is optional. + * This function must only be called before additional threads are created. + * If this function is not called or called with log_handler == NULL, + * the default SC log handler will be used. + * If this function is not called or called with log_threshold == SC_LP_DEFAULT, + * the default SC log threshold will be used. + * The default SC log settings can be changed with sc_set_log_defaults (). + */ +void +kalypsso_init(sc_log_handler_t log_handler, int log_threshold); + +#endif // KALYPSSO_CORE_KALYPSSO_CORE_BASE_H_ diff --git a/src/kalypsso/core/kalypsso_core_build_info.cpp b/src/kalypsso/core/kalypsso_core_build_info.cpp new file mode 100644 index 0000000..272236f --- /dev/null +++ b/src/kalypsso/core/kalypsso_core_build_info.cpp @@ -0,0 +1,164 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include // for KALYPSSO_CORE_USE_MPI +#include +#include + +#include + +#ifdef KALYPSSO_CORE_USE_MPI +# include +# if KALYPSSO_CORE_USE_MPI_EXT +# include +# endif +#endif + +namespace kalypsso +{ + +std::string +BuildInfo::system_processor() +{ + return KALYPSSO_CORE_SYSTEM_PROCESSOR; +} + +std::string +BuildInfo::system_name() +{ + return KALYPSSO_CORE_SYSTEM_NAME; +} + +std::string +BuildInfo::build_type() +{ + return KALYPSSO_CORE_BUILD_TYPE; +} + +std::string +BuildInfo::compiler_id() +{ + return KALYPSSO_CORE_BUILD_COMPILER_ID; +} + +std::string +BuildInfo::compiler_version() +{ + return KALYPSSO_CORE_BUILD_COMPILER_VERSION; +} + +std::string +BuildInfo::compile_date() +{ + return KALYPSSO_CORE_COMPILE_DATE; +} + +std::string +BuildInfo::compile_time() +{ + return KALYPSSO_CORE_COMPILE_TIME; +} + +std::string +BuildInfo::mpi_runtime_config() +{ +#ifdef KALYPSSO_CORE_USE_MPI + return []() { + int length; + char mpi_version[MPI_MAX_LIBRARY_VERSION_STRING]; + MPI_Get_library_version(mpi_version, &length); + return std::string(mpi_version); + }(); +#else + return "none"; +#endif // KALYPSSO_CORE_USE_MPI +} + +std::string +BuildInfo::mpi_runtime_cuda_support() +{ +#ifdef KALYPSSO_CORE_USE_MPI +# if KALYPSSO_CORE_MPI_HAS_QUERY_CUDA_SUPPORT + int result = MPIX_Query_cuda_support(); + return result == 1 ? std::string("yes") : std::string("no"); +# else + return "unknown - not queryable"; +# endif +#else + return "unknown - MPI not used"; +#endif +} + +std::string +BuildInfo::mpi_runtime_hip_support() +{ +#ifdef KALYPSSO_CORE_USE_MPI +# if KALYPSSO_CORE_MPI_HAS_QUERY_HIP_SUPPORT + int result = MPIX_Query_hip_support(); + return result == 1 ? std::string("yes") : std::string("no"); +# else + return "unknown - not queryable"; +# endif +#else + return "unknown - MPI not used"; +#endif +} + +std::string +BuildInfo::mpi_runtime_ze_support() +{ +#ifdef KALYPSSO_CORE_USE_MPI +# if KALYPSSO_CORE_MPI_HAS_QUERY_ZE_SUPPORT + int result = MPIX_Query_ze_support(); + return result == 1 ? std::string("yes") : std::string("no"); +# else + return "unknown - not queryable"; +# endif +#else + return "unknown - MPI not used"; +#endif +} + +bool +BuildInfo::hdf5_enabled() +{ +#ifdef KALYPSSO_CORE_USE_HDF5 + return true; +#else + return false; +#endif +} + +std::string +BuildInfo::hdf5_version() +{ +#ifdef KALYPSSO_CORE_USE_HDF5 + return KALYPSSO_CORE_USE_HDF5_VERSION; +#else + return "unknown"; +#endif +} + +void +BuildInfo::print() +{ + std::cout << "##############################################\n"; + std::cout << "kalypsso-core - build info\n"; + std::cout << "system name : " << system_name() << "\n"; + std::cout << "system processor : " << system_processor() << "\n"; + std::cout << "compile date : " << compile_date() << "\n"; + std::cout << "compile time : " << compile_time() << "\n"; + std::cout << "build type : " << build_type() << "\n"; + std::cout << "compiler id : " << compiler_id() << "\n"; + std::cout << "compiler version : " << compiler_version() << "\n"; + std::cout << "MPI runtime config : " << mpi_runtime_config() << "\n"; + std::cout << "MPI runtime cuda support : " << mpi_runtime_cuda_support() << "\n"; + std::cout << "MPI runtime hip support : " << mpi_runtime_hip_support() << "\n"; + std::cout << "MPI runtime ze support : " << mpi_runtime_ze_support() << "\n"; + if (hdf5_enabled()) + std::cout << "HDF5 version : " << hdf5_version() << "\n"; + std::cout << "##############################################\n"; +} + +} // namespace kalypsso diff --git a/src/kalypsso/core/kalypsso_core_build_info.h b/src/kalypsso/core/kalypsso_core_build_info.h new file mode 100644 index 0000000..48e3d90 --- /dev/null +++ b/src/kalypsso/core/kalypsso_core_build_info.h @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file kalypsso_core_build_info.h + */ +#ifndef KALYPSSO_CORE_KALYPSSO_CORE_BUILD_INFO_H_ +#define KALYPSSO_CORE_KALYPSSO_CORE_BUILD_INFO_H_ + +#include +#include + +#include + +namespace kalypsso +{ + +struct BuildInfo +{ + static std::string + system_processor(); + + static std::string + system_name(); + + static std::string + build_type(); + + static std::string + compiler_id(); + + static std::string + compiler_version(); + + static std::string + compile_date(); + static std::string + compile_time(); + + static std::string + mpi_runtime_config(); + + static std::string + mpi_runtime_cuda_support(); + static std::string + mpi_runtime_hip_support(); + static std::string + mpi_runtime_ze_support(); + + static bool + hdf5_enabled(); + static std::string + hdf5_version(); + + static void + print(); +}; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_KALYPSSO_CORE_BUILD_INFO_H_ diff --git a/src/kalypsso/core/kalypsso_core_config.h.cmake.in b/src/kalypsso/core/kalypsso_core_config.h.cmake.in new file mode 100644 index 0000000..ee149f9 --- /dev/null +++ b/src/kalypsso/core/kalypsso_core_config.h.cmake.in @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file kalypsso_core_config.h + */ +#ifndef KALYPSSO_CORE_KALYPSSO_CORE_CONFIG_H +#define KALYPSSO_CORE_KALYPSSO_CORE_CONFIG_H 1 + +/* kalypsso_core_config.h. Generated from kalypsso_core_config.h.cmake by cmake. */ + +/* Define to 1 when cmake build type is "Debug" */ +#cmakedefine KALYPSSO_CORE_ENABLE_DEBUG 1 + +/* Define to 1 when checking array bounds at runtime */ +#cmakedefine KALYPSSO_CORE_DEBUG_BOUNDS_CHECK 1 + +/* Define to 1 when spdlog is available */ +#cmakedefine KALYPSSO_CORE_USE_SPDLOG 1 + +/* Define to 1 when cnpy is available */ +#cmakedefine KALYPSSO_CORE_USE_CNPY 1 + +/* Define to 1 when NVTX3 is available */ +#cmakedefine KALYPSSO_CORE_USE_NVTX3 1 +#cmakedefine KALYPSSO_CORE_NVTX_ANNOTATION_ENABLED 1 + +/* floating point type: float or double ? */ +#cmakedefine KALYPSSO_CORE_USE_DOUBLE 1 + +/* debug floating point exception */ +#cmakedefine KALYPSSO_CORE_USE_FPE_DEBUG 1 + +/* timing measurements enabled ? */ +#cmakedefine KALYPSSO_CORE_TIMING_ENABLED 1 + +/* CPPTRACE helper executable path */ +#cmakedefine KALYPSSO_CORE_USE_CPPTRACE 1 +#define KALYPSSO_CORE_STACK_PRINTER_FULLPATH "@CMAKE_CURRENT_BINARY_DIR@/src/kalypsso/core/kalypsso_stack_printer" + +/* enforce instantiate some template on host */ +#cmakedefine KALYPSSO_CORE_INSTANTIATE_HOST_TEMPLATE 1 + +/* compiler information */ +#define KALYPSSO_CORE_BUILD_TYPE "@CMAKE_BUILD_TYPE@" +#define KALYPSSO_CORE_BUILD_COMPILER_ID "@CMAKE_CXX_COMPILER_ID@" +#define KALYPSSO_CORE_BUILD_COMPILER_VERSION "@CMAKE_CXX_COMPILER_VERSION@" + +/* system information */ +#define KALYPSSO_CORE_SYSTEM_PROCESSOR "@CMAKE_SYSTEM_PROCESSOR@" +#define KALYPSSO_CORE_SYSTEM_NAME "@CMAKE_SYSTEM_NAME@" + +/* add here any other useful symbols (e.g. MPI implementation, ...) */ +#cmakedefine KALYPSSO_CORE_USE_MPI @KALYPSSO_CORE_USE_MPI@ +#cmakedefine KALYPSSO_CORE_USE_MPI_EXT @KALYPSSO_CORE_USE_MPI_EXT@ + +/* is MPI GPU (Nvidia, AMD, Intel) support queryable at runtime ? */ +#cmakedefine KALYPSSO_CORE_MPI_HAS_QUERY_CUDA_SUPPORT @KALYPSSO_CORE_MPI_HAS_QUERY_CUDA_SUPPORT@ +#cmakedefine KALYPSSO_CORE_MPI_HAS_QUERY_HIP_SUPPORT @KALYPSSO_CORE_MPI_HAS_QUERY_HIP_SUPPORT@ +#cmakedefine KALYPSSO_CORE_MPI_HAS_QUERY_ZE_SUPPORT @KALYPSSO_CORE_MPI_HAS_QUERY_ZE_SUPPORT@ + +/* MPI vendor: OpenMPI, MPICH ? */ +#define KALYPSSO_CORE_MPI_VENDOR "@MPI_VENDOR@" + +/* HDF5 */ +#cmakedefine KALYPSSO_CORE_USE_HDF5 @KALYPSSO_CORE_USE_HDF5@ +#cmakedefine KALYPSSO_CORE_USE_HDF5_PARALLEL @KALYPSSO_CORE_USE_HDF5_PARALLEL@ +#cmakedefine KALYPSSO_CORE_USE_HDF5_VERSION "@KALYPSSO_CORE_USE_HDF5_VERSION@" + +#endif // KALYPSSO_CORE_KALYPSSO_CORE_CONFIG_H diff --git a/src/kalypsso/core/kalypsso_core_git_info.cpp b/src/kalypsso/core/kalypsso_core_git_info.cpp new file mode 100644 index 0000000..0f312b7 --- /dev/null +++ b/src/kalypsso/core/kalypsso_core_git_info.cpp @@ -0,0 +1,122 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +#include + +#include + +namespace kalypsso +{ + +std::string +GitRevisionInfo::version() +{ + return KALYPSSO_CORE_VERSION; +} + +bool +GitRevisionInfo::has_git_info() +{ +#ifdef KALYPSSO_HAS_GIT_INFO + return true; +#else + return false; +#endif +} + +std::string +GitRevisionInfo::git_tag() +{ +#ifdef KALYPSSO_HAS_GIT_INFO + return KALYPSSO_GIT_TAG; +#else + return "unknown tag"; +#endif +} + +std::string +GitRevisionInfo::git_head() +{ +#ifdef KALYPSSO_HAS_GIT_INFO + return KALYPSSO_GIT_HEAD; +#else + return "unknown head"; +#endif +} + +std::string +GitRevisionInfo::git_hash() +{ +#ifdef KALYPSSO_HAS_GIT_INFO + return KALYPSSO_GIT_HASH; +#else + return "unknown hash"; +#endif +} + +std::string +GitRevisionInfo::git_remote_url() +{ +#ifdef KALYPSSO_HAS_GIT_INFO + return KALYPSSO_GIT_REMOTE_URL; +#else + return "unknown remote url"; +#endif +} + +std::string +GitRevisionInfo::git_branch() +{ +#ifdef KALYPSSO_HAS_GIT_INFO + return KALYPSSO_GIT_BRANCH; +#else + return "unknown git branch"; +#endif +} + +bool +GitRevisionInfo::git_is_clean() +{ +#ifdef KALYPSSO_HAS_GIT_INFO + return KALYPSSO_GIT_IS_CLEAN; +#else + return false; +#endif +} + +void +GitRevisionInfo::print() +{ + if (has_git_info()) + { + std::cout << "#############################################\n"; + std::cout << "kalypsso_core - git information" << "\n"; + std::cout << "git remote url : " << git_remote_url() << "\n"; + std::cout << "git branch : " << git_branch() << "\n"; + std::cout << "git head : " << git_head() << "\n"; + std::cout << "git hash : " << git_hash() << " ("; + if (git_is_clean()) + { + std::cout << "clean"; + } + else + { + std::cout << "dirty"; + } + std::cout << ")\n"; + std::cout << "#############################################\n"; + } + else + { + std::cout << "#############################################\n"; + std::cout << "kalypsso - not built from a git repository \n"; + std::cout << "version : " << version() << "\n"; + std::cout << "#############################################\n"; + } + +} // GitRevisionInfo::print + +} // namespace kalypsso diff --git a/src/kalypsso/core/kalypsso_core_git_info.h b/src/kalypsso/core/kalypsso_core_git_info.h new file mode 100644 index 0000000..f70616c --- /dev/null +++ b/src/kalypsso/core/kalypsso_core_git_info.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file kalypsso_core_git_info.h + */ +#ifndef KALYPSSO_CORE_KALYPSSO_CORE_GIT_INFO_H_ +#define KALYPSSO_CORE_KALYPSSO_CORE_GIT_INFO_H_ + +#include + +namespace kalypsso +{ + +struct GitRevisionInfo +{ + //! cmake project version + static std::string + version(); + + static bool + has_git_info(); + + static std::string + git_tag(); + + static std::string + git_head(); + + static std::string + git_hash(); + + static std::string + git_remote_url(); + + static std::string + git_branch(); + + static bool + git_is_clean(); + + static void + print(); +}; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_KALYPSSO_CORE_GIT_INFO_H_ diff --git a/src/kalypsso/core/kalypsso_data_container.h b/src/kalypsso/core/kalypsso_data_container.h new file mode 100644 index 0000000..d108e87 --- /dev/null +++ b/src/kalypsso/core/kalypsso_data_container.h @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file kalypsso_data_container.h + */ +#ifndef KALYPSSO_CORE_KALYPSSODATACONTAINER_H_ +#define KALYPSSO_CORE_KALYPSSODATACONTAINER_H_ + +#include + +#include +#include +#include +#include // this is deprecated, will be deleted when all downwind devel are refactored +#include +#include +#include + +#endif // KALYPSSO_CORE_KALYPSSODATACONTAINER_H_ diff --git a/src/kalypsso/core/kalypsso_macros.h b/src/kalypsso/core/kalypsso_macros.h new file mode 100644 index 0000000..a5df6d9 --- /dev/null +++ b/src/kalypsso/core/kalypsso_macros.h @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file kalypsso_macros.h + */ +#ifndef KALYPSSO_CORE_KALYPSSO_MACROS_H_ +#define KALYPSSO_CORE_KALYPSSO_MACROS_H_ + +#include + +#ifndef assertm +# define assertm(exp, msg) assert(((void)msg, exp)) +#endif + +// clang-format off +#if defined(__GNUC__) || defined(__clang__) || defined(__NVCC__) +#define KALYPSSO_DISABLE_CONVERSION_WARNINGS_PUSH() \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wconversion\"") \ + _Pragma("GCC diagnostic ignored \"-Wsign-conversion\"") +#define KALYPSSO_DISABLE_CONVERSION_WARNINGS_POP() \ + _Pragma("GCC diagnostic pop") +#else +#define KALYPSSO_DISABLE_CONVERSION_WARNINGS_PUSH() +#define KALYPSSO_DISABLE_CONVERSION_WARNINGS_POP() +#endif + +// +// Disable -Wstringop-overflow (need for testing cpptrace) +// +// clang-format off +#if defined(__GNUC__) || defined(__clang__) || defined(__NVCC__) +#define KALYPSSO_DISABLE_STRINGOP_OVERFLOW_WARNINGS_PUSH() \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wstringop-overflow\"") +#define KALYPSSO_DISABLE_STRINGOP_OVERFLOW_WARNINGS_POP() \ + _Pragma("GCC diagnostic pop") +#else +#define KALYPSSO_DISABLE_STRINGOP_OVERFLOW_WARNINGS_PUSH() +#define KALYPSSO_DISABLE_STRINGOP_OVERFLOW_WARNINGS_POP() +#endif + +// +// macro to disable some warning check that we think are not relevant at some locations +// +#if defined(__NVCC__) +#define KALYPSSO_DISABLE_NVCC_WARNINGS_PUSH() \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wold-style-cast\"") +#define KALYPSSO_DISABLE_NVCC_WARNINGS_POP() \ + _Pragma("GCC diagnostic pop") +#else +#define KALYPSSO_DISABLE_NVCC_WARNINGS_PUSH() +#define KALYPSSO_DISABLE_NVCC_WARNINGS_POP() +#endif + +// clang-format on + +#endif // KALYPSSO_CORE_KALYPSSO_MACROS_H_ diff --git a/src/kalypsso/core/kalypsso_stack_printer.cpp b/src/kalypsso/core/kalypsso_stack_printer.cpp new file mode 100644 index 0000000..379c843 --- /dev/null +++ b/src/kalypsso/core/kalypsso_stack_printer.cpp @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file kalypsso_stack_printer.cpp + * + * The code is adapted from cpptrace sources : + * https://github.com/jeremy-rifkin/cpptrace/blob/main/test/signal_tracer.cpp + * provided by cpptrace under MIT license. + * + * See https://github.com/jeremy-rifkin/cpptrace/blob/main/docs/signal-safe-tracing.md + * + */ + +#include + +#include +#include + +#include + +int +main() +{ + cpptrace::object_trace trace; + while (true) + { + cpptrace::safe_object_frame frame; + std::size_t res = fread(&frame, sizeof(frame), 1, stdin); + if (res == 0) + { + break; + } + else if (res != 1) + { + std::cerr << "Oops, size mismatch " << res << " " << sizeof(frame) << std::endl; + break; + } + else + { + trace.frames.push_back(frame.resolve()); + } + } + trace.resolve().print(); +} diff --git a/src/kalypsso/core/kokkos_shared.h b/src/kalypsso/core/kokkos_shared.h new file mode 100644 index 0000000..5f29945 --- /dev/null +++ b/src/kalypsso/core/kokkos_shared.h @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file kokkos_shared.h + */ +#ifndef KALYPSSO_CORE_KOKKOS_SHARED_H_ +#define KALYPSSO_CORE_KOKKOS_SHARED_H_ + +#include +#include // for KOKKOS_ENABLE_XXX +#include + +#include +#include + +namespace kalypsso +{ + +//! Kokkos default device +using DefaultDevice = + Kokkos::Device; + +//! Kokkos host device +using HostDevice = Kokkos::Device; + +// ============================================================= +// ============================================================= +/** + * a dummy swap device routine. + */ +template +KOKKOS_INLINE_FUNCTION void +my_swap(T & a, T & b) +{ + T c{ std::move(a) }; + a = std::move(b); + b = std::move(c); +} // my_swap + +// ============================================================= +// ============================================================= +/** + * Convert a std::vector in a host Kokkos::View + */ +template +Kokkos::View +to_host_view(std::vector const & vec) +{ + const auto n = vec.size(); + const auto data = vec.data(); + + Kokkos::View res( + Kokkos::view_alloc(Kokkos::HostSpace{}, Kokkos::WithoutInitializing, ""), n); + + Kokkos::parallel_for( + "copy vector data", + Kokkos::RangePolicy(0, n), + KOKKOS_LAMBDA(size_t i) { res(i) = data[i]; }); + + return res; +} + +// ============================================================= +// ============================================================= +/** + * Convert a std::vector in a device Kokkos::View + */ +template +Kokkos::View +to_view(std::vector const & vec) +{ + auto res_h = to_host_view(vec); + + return Kokkos::create_mirror_view_and_copy(typename device_t::memory_space{}, res_h); +} + +} // namespace kalypsso + +// adapted from Kokkos +#if !defined(__NVCC__) && (!defined(KOKKOS_COMPILER_INTEL) || KOKKOS_COMPILER_INTEL >= 2021) +# define KALYPSSO_DEPRECATED [[deprecated]] +# define KALYPSSO_DEPRECATED_WITH_COMMENT(comment) [[deprecated(comment)]] +#else +# define KALYPSSO_DEPRECATED +# define KALYPSSO_DEPRECATED_WITH_COMMENT(comment) +#endif + +#endif // KALYPSSO_CORE_KOKKOS_SHARED_H_ diff --git a/src/kalypsso/core/mesh_utils.h b/src/kalypsso/core/mesh_utils.h new file mode 100644 index 0000000..a72d32c --- /dev/null +++ b/src/kalypsso/core/mesh_utils.h @@ -0,0 +1,350 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file mesh_utils.h + */ +#ifndef KALYPSSO_CORE_MESHUTILS_H_ +#define KALYPSSO_CORE_MESHUTILS_H_ + +#include +#include // for KOKKOS_ENABLE_XXX + +#include + +#include +#include + +namespace kalypsso +{ + +// ======================================================================= +// ======================================================================= +//! \struct Face +//! Don't change the values, don't insert new values unless you know what you're doing. +//! Values defined here are also used at some location to compute direction (integer division by 2) +struct Face +{ + using face_t = uint8_t; + static constexpr face_t XMIN = 0; + static constexpr face_t XMAX = 1; + static constexpr face_t YMIN = 2; + static constexpr face_t YMAX = 3; + static constexpr face_t ZMIN = 4; + static constexpr face_t ZMAX = 5; + static constexpr face_t INVALID = 16; + static constexpr face_t MIN = 0; + static constexpr face_t MAX = 1; + static constexpr face_t NUM_FACES_2D = 4; + static constexpr face_t NUM_FACES_3D = 6; + + //! return an array with all faces + template + KOKKOS_INLINE_FUNCTION static constexpr auto + get_all_faces() -> std::array + { + if constexpr (dim == 2) + return { Face::XMIN, Face::XMAX, Face::YMIN, Face::YMAX }; + else if constexpr (dim == 3) + return { Face::XMIN, Face::XMAX, Face::YMIN, Face::YMAX, Face::ZMIN, Face::ZMAX }; + } + + //! return number of faces + template + KOKKOS_INLINE_FUNCTION static constexpr auto + num_faces() + { + if constexpr (dim == 2) + return NUM_FACES_2D; + else if constexpr (dim == 3) + return NUM_FACES_3D; + } // num_faces + + //! return true if face is a left face + KOKKOS_INLINE_FUNCTION static constexpr bool + is_left_face(face_t face) + { + return face == Face::XMIN or face == Face::YMIN or face == Face::ZMIN; + } + + template + KOKKOS_INLINE_FUNCTION static auto + get_pair_of_faces(int dir) -> Kokkos::Array + { + if (dir == IX) + return Kokkos::Array{ Face::XMIN, Face::XMAX }; + else if (dir == IY) + return Kokkos::Array{ Face::YMIN, Face::YMAX }; + + if constexpr (dim == 3) + { + if (dir == IZ) + return Kokkos::Array{ Face::ZMIN, Face::ZMAX }; + } + + return Kokkos::Array{ Face::XMIN, Face::XMAX }; + } + +}; // struct Face + +// ======================================================================= +// ======================================================================= +//! \struct Edge. +struct Edge +{ + using edge_t = uint8_t; + static constexpr edge_t NUM_EDGES_2D = 0; + static constexpr edge_t NUM_EDGES_3D = 12; + + template + static constexpr uint8_t + num_edges() + { + if constexpr (dim == 2) + return NUM_EDGES_2D; + else if constexpr (dim == 3) + return NUM_EDGES_3D; + } +}; // struct Edge + +// ======================================================================= +// ======================================================================= +// \struct Corner. +struct Corner +{ + using corner_t = uint8_t; + static constexpr corner_t CORNER_0 = 0; + static constexpr corner_t CORNER_1 = 1; + static constexpr corner_t CORNER_2 = 2; + static constexpr corner_t CORNER_3 = 3; + static constexpr corner_t CORNER_4 = 4; + static constexpr corner_t CORNER_5 = 5; + static constexpr corner_t CORNER_6 = 6; + static constexpr corner_t CORNER_7 = 7; + + template + KOKKOS_INLINE_FUNCTION static constexpr uint8_t + num_corners() + { + return 1 << dim; + } +}; // struct Corner + +// ======================================================================= +// ======================================================================= +/** + * corner to faces - 2d. + * + * corners are identified by two faces. + * + * \param[in] corner is a corner id (from 0 to 3 in 2d) + * \param[out] face0 + * \param[out] face1 + * + * face0 and face1 identify a unique corner in 2d using the following mapping + * + * corner is encoded using 2 bits + * + */ +KOKKOS_INLINE_FUNCTION void +corner_to_faces(uint8_t corner, Face::face_t & face0, Face::face_t & face1) +{ + KOKKOS_ASSERT(corner < 4 && "WRONG VALUE, corner must be smaller than 4."); + + face0 = corner & 0x1 ? Face::XMAX : Face::XMIN; + face1 = corner & 0x2 ? Face::YMAX : Face::YMIN; + +} // corner_to_faces + +// ======================================================================= +// ======================================================================= +KOKKOS_INLINE_FUNCTION void +corner_to_faces(uint8_t corner, Face::face_t & face0, Face::face_t & face1, Face::face_t & face2) +{ + KOKKOS_ASSERT(corner < 8 && "WRONG VALUE, corner must be smaller than 8."); + + face0 = corner & 0x1 ? Face::XMAX : Face::XMIN; + face1 = corner & 0x2 ? Face::YMAX : Face::YMIN; + face2 = corner & 0x4 ? Face::ZMAX : Face::ZMIN; + +} // corner_to_faces + +// ======================================================================= +// ======================================================================= +template +KOKKOS_INLINE_FUNCTION Kokkos::Array + corner_to_faces(uint8_t corner) +{ + if constexpr (dim == 2) + { + KOKKOS_ASSERT(corner < 4 && "WRONG VALUE, corner must be smaller than 4."); + } + else if constexpr (dim == 3) + { + KOKKOS_ASSERT(corner < 8 && "WRONG VALUE, corner must be smaller than 8."); + } + + Kokkos::Array res; + res[IX] = corner & 0x1 ? Face::XMAX : Face::XMIN; + res[IY] = corner & 0x2 ? Face::YMAX : Face::YMIN; + if constexpr (dim == 3) + res[IZ] = corner & 0x4 ? Face::ZMAX : Face::ZMIN; + + return res; +} // corner_to_faces + +// =========================================================== +// =========================================================== +/** + * edge to faces. + * + * edges are identified by to faces. + * + * \param[in] edge is an edge id (from 0 to 11) + * \param[out] face0 + * \param[out] face1 + * + * face0 and face1 identify a unique edge using the following mapping + * + * edge is encoded using 4 bits + * + * we respect Morton order, with this choice, we always have dir0=face0/2 < dir1=face1/2 + */ +KOKKOS_INLINE_FUNCTION void +edge_to_faces(uint8_t edge, Face::face_t & face0, Face::face_t & face1) +{ + KOKKOS_ASSERT(edge < Edge::NUM_EDGES_3D && + "WRONG VALUE, edge must be smaller than NUM_EDGES_3D."); + + // edge along Z + if (edge < 4) + { + face0 = edge & 0x1 ? Face::XMAX : Face::XMIN; + face1 = edge & 0x2 ? Face::YMAX : Face::YMIN; + } + // edge along X + else if (edge < 8) + { + face0 = edge & 0x1 ? Face::YMAX : Face::YMIN; + face1 = edge & 0x2 ? Face::ZMAX : Face::ZMIN; + } + // edge along Y (not a circular permutation - respect Morton order) + else + { + face0 = edge & 0x1 ? Face::XMAX : Face::XMIN; + face1 = edge & 0x2 ? Face::ZMAX : Face::ZMIN; + } + + KOKKOS_ASSERT(face0 < face1 && "WRONG VALUE, face0,face1 is invalid."); + +} // edge_to_faces + +// =========================================================== +// =========================================================== +KOKKOS_INLINE_FUNCTION Kokkos::Array + edge_to_faces(Edge::edge_t edge) +{ + KOKKOS_ASSERT(edge < Edge::NUM_EDGES_3D && + "WRONG VALUE, edge must be smaller than NUM_EDGES_3D."); + + Kokkos::Array faces; + + // edge along Z + if (edge < 4) + { + faces[0] = edge & 0x1 ? Face::XMAX : Face::XMIN; + faces[1] = edge & 0x2 ? Face::YMAX : Face::YMIN; + } + // edge along X + else if (edge < 8) + { + faces[0] = edge & 0x1 ? Face::YMAX : Face::YMIN; + faces[1] = edge & 0x2 ? Face::ZMAX : Face::ZMIN; + } + // edge along Y (not a circular permutation - respect Morton order) + else + { + faces[0] = edge & 0x1 ? Face::XMAX : Face::XMIN; + faces[1] = edge & 0x2 ? Face::ZMAX : Face::ZMIN; + } + + KOKKOS_ASSERT(faces[0] < faces[1] && "WRONG VALUE, faces[0],face[1] is invalid."); + + return faces; + +} // edge_to_faces + +// ========================================================== +// ========================================================== +/** + * Get normal unit vector in a given direction. + * + * \param[in] direction specifies which component is non-zero + * + * \return a dim-dimensional Kokkos::Array, which is zero everywhere except one component identified + * by direction + */ +template +KOKKOS_INLINE_FUNCTION auto +get_unit_vector(int direction) +{ + Kokkos::Array v; + for (int i = 0; i < static_cast(dim); ++i) + { + v[i] = i == direction ? 1 : 0; + } + return v; +} // get_unit_vector + +// ========================================================== +// ========================================================== +/** + * Given an outside unit vector return the face Id. + */ +template +KOKKOS_INLINE_FUNCTION Face::face_t + face_normal_vector_to_face_id(Kokkos::Array const & v) +{ + + { + [[maybe_unused]] auto norm_v = v[IX] * v[IX] + v[IY] * v[IY]; + if constexpr (dim == 3) + norm_v += v[IZ] * v[IZ]; + KOKKOS_ASSERT(norm_v == 1 && "Error a unit vector must have norm 1 !"); + } + + if constexpr (dim == 2) + { + if (v[IX] == -1) + return Face::XMIN; + else if (v[IX] == 1) + return Face::XMAX; + else if (v[IY] == -1) + return Face::YMIN; + else if (v[IY] == 1) + return Face::YMAX; + } + else if constexpr (dim == 3) + { + if (v[IX] == -1) + return Face::XMIN; + else if (v[IX] == 1) + return Face::XMAX; + else if (v[IY] == -1) + return Face::YMIN; + else if (v[IY] == 1) + return Face::YMAX; + else if (v[IZ] == -1) + return Face::ZMIN; + else if (v[IZ] == 1) + return Face::ZMAX; + } + + return Face::INVALID; + +} // face_unit_vector_to_face_id + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_MESHUTILS_H_ diff --git a/src/kalypsso/core/misc_utils.cpp b/src/kalypsso/core/misc_utils.cpp new file mode 100644 index 0000000..df278e8 --- /dev/null +++ b/src/kalypsso/core/misc_utils.cpp @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file misc_utils.cpp + */ +#include + +#include // for std::time_t, std::tm, std::localtime +#include // string stream +#include // for std::put_time +#include + +namespace kalypsso +{ + +// ======================================================= +// ======================================================= +void +print_current_date(std::ostream & stream) +{ + + /* get current time */ + std::time_t now = std::time(nullptr); + + /* Format and print the time, "ddd yyyy-mm-dd hh:mm:ss zzz" */ + std::tm tm = *std::localtime(&now); + + // old versions of g++ don't have std::put_time, + // so we provide a slight work around +#if defined(__GNUC__) && (__GNUC__ < 5) + + char foo[64]; + + if (0 < std::strftime(foo, sizeof(foo), "%Y-%m-%d %H:%M:%S %Z", &tm)) + stream << "-- " << foo << "\n"; + +#else + + std::stringstream ss; + ss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S %Z"); + + const std::string tmp = ss.str(); + // const char *cstr = tmp.c_str(); + + stream << "-- " << tmp << "\n"; + +#endif + +} // print_current_date + +// ======================================================= +// ======================================================= +std::string +get_current_date() +{ + + /* get current time */ + std::time_t now = std::time(nullptr); + + /* Format and print the time, "ddd yyyy-mm-dd hh:mm:ss zzz" */ + std::tm tm = *std::localtime(&now); + + // old versions of g++ don't have std::put_time, + // so we provide a slight work around +#if defined(__GNUC__) && (__GNUC__ < 5) + + char foo[64]; + + if (0 < std::strftime(foo, sizeof(foo), "%Y-%m-%d %H:%M:%S %Z", &tm)) + return std::string(foo); + else + return std::string("undefined"); + +#else + + std::ostringstream oss; + oss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S %Z"); + + return oss.str(); + +#endif + +} // get_current_date + +} // namespace kalypsso diff --git a/src/kalypsso/core/misc_utils.h b/src/kalypsso/core/misc_utils.h new file mode 100644 index 0000000..2608ebc --- /dev/null +++ b/src/kalypsso/core/misc_utils.h @@ -0,0 +1,73 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file misc_utils.h + */ +#ifndef KALYPSSO_CORE_MISC_UTILS_H +#define KALYPSSO_CORE_MISC_UTILS_H + +#include +#include // string +#include + +namespace kalypsso +{ + +KALYPSSO_MATH_CONSTANT(FUZZY_THRESHOLD, 1e-12); + +template +KOKKOS_INLINE_FUNCTION T +FUZZY_THRESHOLD() +{ + if constexpr (std::is_same::value) + { + return 1e-12f; + } + else if constexpr (std::is_same::value) + { + return 1e-12; + } +} + + +template +KOKKOS_INLINE_FUNCTION bool +ISFUZZYNULL(T a) +{ + static_assert(std::is_floating_point_v, "Type T must be a floating-point type"); + + return (fabs(a) < FUZZY_THRESHOLD()); +} + +template +KOKKOS_INLINE_FUNCTION bool +FUZZYCOMPARE(T a, T b) +{ + return ((ISFUZZYNULL(a) && ISFUZZYNULL(b)) || + (fabs((a) - (b)) * 1000000000000. <= fmin(fabs(a), fabs(b)))); +} + +template +KOKKOS_INLINE_FUNCTION bool +FUZZYLIMITS(T x, T a, T b) +{ + return (((x) > ((a)-FUZZY_THRESHOLD())) && ((x) < ((b) + FUZZY_THRESHOLD()))); +} + +/** + * Print current date. + */ +void +print_current_date(std::ostream & stream); + +/** + * Get current data as a string. + */ +std::string +get_current_date(); + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_MISC_UTILS_H diff --git a/src/kalypsso/core/models/EulerEquations.h b/src/kalypsso/core/models/EulerEquations.h new file mode 100644 index 0000000..3e8609d --- /dev/null +++ b/src/kalypsso/core/models/EulerEquations.h @@ -0,0 +1,889 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file EulerEquations.h + */ +#ifndef KALYPSSO_CORE_MODELS_EULER_EQUATIONS_H_ +#define KALYPSSO_CORE_MODELS_EULER_EQUATIONS_H_ + +#include +#include + +#include +#include +#include + +namespace kalypsso +{ + +/** + * This structure gather useful information (variable names, + * flux functions, ...) for the compressible Euler equations system + * in both 2D / 3D. + * + * Inspired by code dflo (https://github.com/cpraveen/dflo) + */ +template +struct EulerEquations +{}; + +/** + * 2D specialization of the Euler Equation system. + */ +template <> +struct EulerEquations<2> +{ + + //! small pressure safe-guard + static constexpr real_t smallp = 1e-7; + + //! number of variables: density(1) + energy(1) + momentum(2) + static constexpr int nbvar = 2 + 2; + + //! type alias to a small array holding hydrodynamics state variables + using State = HydroState<2, nbvar>; + + //! enum + // static const int ID = 0; // density + // static const int IP = 1; // Pressure (when used in primitive variables) + // static const int IE = 1; // Energy + // static const int IU = 2; // momentum along X + // static const int IV = 3; // momentum along Y + + //! velocity gradient tensor number of components + static const int nbvar_grad = 2 * 2; + + static constexpr int U_X = (int)gradientV_IDS_2d::U_X; + static constexpr int U_Y = (int)gradientV_IDS_2d::U_Y; + + static constexpr int V_X = (int)gradientV_IDS_2d::V_X; + static constexpr int V_Y = (int)gradientV_IDS_2d::V_Y; + + //! alias typename to an array holding gradient velocity tensor components + using GradTensor = Kokkos::Array; + + //! just a dim-dimension vector + using Vector = Kokkos::Array; + + //! variables names as a std::map + static std::map + get_variable_names() + { + + std::map names; + + names[ID] = "rho"; + names[IP] = "energy"; + names[IU] = "mx"; // momentum component X + names[IV] = "my"; // momentum component Y + + return names; + + } // get_variable_names + + /** + * Compute pressure. + * + * \param[in] q vector of conservative variables. + * \param[in] gamma0 + * + * \return pressure + */ + static KOKKOS_INLINE_FUNCTION real_t + compute_pressure(const State & q, real_t gamma0) + { + + // 0.5 * rho * (u^2+v^2) + real_t ekin = 0.5 * (q[IU] * q[IU] + q[IV] * q[IV]) / q[ID]; + + // pressure + real_t pressure = (gamma0 - 1.0) * (q[IE] - ekin); + + return pressure > smallp * q[ID] ? pressure : smallp * q[ID]; + + } // compute_pressure + + /** + * Compute speed of sound. + * + * \param[in] vector of primitive variables + * \param[in] gamma0 + * + * \return speed of sound + */ + static KOKKOS_INLINE_FUNCTION real_t + compute_speed_of_sound(const State & w, real_t gamma0) + { + + return w[IP] * gamma0 / w[ID]; + + } // compute_speed_of_sound + + /** + * Convert from conservative to primitive variables. + * + * \param[in] q vector of conservative variables. + * \param[out] q vector of primitive variables. + */ + static KOKKOS_INLINE_FUNCTION void + convert_to_primitive(const State & q, State & w, real_t gamma0) + { + + const real_t rho = fmax(q[ID], 1e-8); + + // 0.5 * rho * (u^2+v^2) + const real_t ekin = 0.5 * (q[IU] * q[IU] + q[IV] * q[IV]) / rho; + + // pressure + real_t pressure = (gamma0 - 1.0) * (q[IE] - ekin); + + pressure = fmax(pressure, smallp * rho); + + w[ID] = q[ID]; + w[IU] = q[IU] / q[ID]; + w[IV] = q[IV] / q[ID]; + w[IP] = pressure; + + } // convert_to_primitive + + /** + * Flux expression in the Euler equations system written in conservative + * form along direction X. + * + * \param[in] q conservative variables \f$ (\rho, \rho u, \rho v, E) \f$ + * \param[in] p is pressure + * \param[out] flux vector \f$ (\rho u, \rho u^2+p, \rho u v, u(E+p) ) \f$ + */ + static KOKKOS_INLINE_FUNCTION void + flux_x(const State & q, real_t p, State & flux) + { + flux[ID] = q[IU]; // rho u + flux[IU] = q[IU] * q[IU] / q[ID] + p; // rho u^2 + p + flux[IV] = q[IU] * q[IV] / q[ID]; // rho u v + flux[IE] = q[IU] / q[ID] * (q[IE] + p); // u (E+p) + }; + + /** + * Flux expression in the Euler equations system written in conservative + * form along direction Y. + * + * \param[in] q conservative variables \f$ (\rho, \rho u, \rho v, E) \f$ + * \param[in] p is pressure + * \param[out] flux vector \f$ (\rho v, \rho v u, \rho v^2+p, v(E+p) ) \f$ + */ + static KOKKOS_INLINE_FUNCTION void + flux_y(const State & q, real_t p, State & flux) + { + flux[ID] = q[IV]; // rho v + flux[IU] = q[IV] * q[IU] / q[ID]; // rho v u + flux[IV] = q[IV] * q[IV] / q[ID] + p; // rho v^2 + p + flux[IE] = q[IV] / q[ID] * (q[IE] + p); // v (E+p) + }; + + /** + * Viscous term as a flux along direction X. + * + * \param[in] g is the velocity gradient tensor + * \param[in] v is the hydrodynamics velocity vector + * \param[in] f is a vector (gradient of diffusive term) + * \param[in] mu is dynamics viscosity (mu = rho * nu) + * \param[out] + * + * note that the diffusive term f represents thermal + entropy diffusion + * as in ASH / CHORUS code. + */ + static KOKKOS_INLINE_FUNCTION void + flux_visc_x(const GradTensor & g, const Vector & v, const Vector & f, real_t mu, State & flux) + { + real_t tau_xx = 2 * mu * (g[U_X] - HALF_F * (g[U_X] + g[V_Y])); + real_t tau_xy = mu * (g[V_X] + g[U_Y]); + + flux[ID] = 0.0; + flux[IU] = tau_xx; + flux[IV] = tau_xy; + flux[IE] = v[IX] * tau_xx + v[IY] * tau_xy + f[IX]; + }; + + /** + * Viscous term as a flux along direction Y. + * + * \param[in] g is the velocity gradient tensor + * \param[in] v is the hydrodynamics velocity vector + * \param[in] f is a vector (gradient of diffusive term) + * \param[in] mu is dynamics viscosity (mu = rho * nu) + * \param[out] + * + * note that the diffusive term f represents thermal + entropy diffusion + * as in ASH / CHORUS code. + */ + static KOKKOS_INLINE_FUNCTION void + flux_visc_y(const GradTensor & g, const Vector & v, const Vector & f, real_t mu, State & flux) + { + real_t tau_yy = 2 * mu * (g[V_Y] - HALF_F * (g[U_X] + g[V_Y])); + real_t tau_xy = mu * (g[V_X] + g[U_Y]); + + flux[ID] = 0.0; + flux[IU] = tau_xy; + flux[IV] = tau_yy; + flux[IE] = v[IX] * tau_xy + v[IY] * tau_yy + f[IY]; + }; + + /** + * Compute characteristic variables by multiply input vector + * by L (left eigenvalue matrix of Euler Jacobian). Computation is done in place + * + * The formulas defining eigen matrix are detailed in doc/euler/euler_equations.tex + * and also copied here using python syntax + * + * # left eigenvectors (R^-1) + * Lx = np.array([[beta*(phi2+u*c), -beta*(g1*u+c), -beta*g1*v, beta*g1], + * [1.0-phi2/c2, g1*u/c2, g1*v/c2, -g1/c2], + * [-v,0,1,0], + * [beta*(phi2-u*c), -beta*(g1*u-c), -beta*g1*v, beta*g1]]) + * + * Ly = np.array([[beta*(phi2+v*c), -beta*g1*u, -beta*(g1*v+c), beta*g1], + * [1.0-phi2/c2, g1*u/c2, g1*v/c2, -g1/c2], + * [-u,1,0,0], + * [beta*(phi2-v*c), -beta*g1*u, -beta*(g1*v-c), beta*g1]]) + * + * If \f$ \Lambda \f$ is the diagonal matrix with eigenvalues u-c, u, u, u+c, one has the + * following eigen decomposition: + * \f$ \Lambda = L A(U) R \f$ + * where \f$ A(U) = \partial F / \partial U \f$. + * + * \tparam dir allows to select which flux function / Jacobian to use : A(U) or B(U) + * + * \param[in,out] data input vector of conservative variables and characteristics var in output + * \param[in] q local hydro state vector of conservative variables + * \param[in] c speed of sound + * \param[in] gamma0 is the heat capacity ratio + * \param[out] out output vector of characteristics variables + */ + template + static KOKKOS_INLINE_FUNCTION void + cons_to_charac(State & data, const State & q, const real_t c, const real_t gamma0) + { + State tmp; + + // some useful intermediate values + const real_t u = q[IU] / q[ID]; + const real_t v = q[IV] / q[ID]; + const real_t c2 = c * c; + + const real_t g1 = gamma0 - 1.0; + + const real_t beta = 1.0 / 2 / c2; + + const real_t V2 = u * u + v * v; + + // enthalpy + const real_t H = 0.5 * V2 + c2 / g1; + + // also equal to g1*V2 + const real_t phi2 = g1 * H - c2; + + if (dir == IX) + { + // compute matrix vector multiply: tmp = Lx . data + tmp[ID] = data[ID] * beta * (phi2 + u * c) + data[IU] * (-beta * (g1 * u + c)) + + data[IV] * (-beta * g1 * v) + data[IE] * beta * g1; + tmp[IU] = data[ID] * (1.0 - phi2 / c2) + data[IU] * g1 * u / c2 + data[IV] * g1 * v / c2 + + data[IE] * (-g1 / c2); + tmp[IV] = data[ID] * (-v) + data[IV]; + tmp[IE] = data[ID] * beta * (phi2 - u * c) + data[IU] * (-beta * (g1 * u - c)) + + data[IV] * (-beta * g1 * v) + data[IE] * beta * g1; + } + else if (dir == IY) + { + + // compute matrix vector multiply: tmp = Ly . data + tmp[ID] = data[ID] * beta * (phi2 + v * c) + data[IU] * (-beta * g1 * u) + + data[IV] * (-beta * (g1 * v + c)) + data[IE] * beta * g1; + tmp[IU] = data[ID] * (1.0 - phi2 / c2) + data[IU] * g1 * u / c2 + data[IV] * g1 * v / c2 + + data[IE] * (-g1 / c2); + tmp[IV] = data[ID] * (-u) + data[IU]; + tmp[IE] = data[ID] * beta * (phi2 - v * c) + data[IU] * (-beta * g1 * u) + + data[IV] * (-beta * (g1 * v - c)) + data[IE] * beta * g1; + } + + data[ID] = tmp[ID]; + data[IE] = tmp[IE]; + data[IU] = tmp[IU]; + data[IV] = tmp[IV]; + + } // cons_to_charac + + /** + * Transform from characteristic variables to conservative by multiply input vector + * by R (right eigenvalue matrix of Euler Jacobian). Computation is done in place + * + * The formulas defining eigen matrix are detailed in doc/euler/euler_equations.tex + * and also copied here using python syntax + * + * # right eigenvectors + * Rx = np.array([[1, 1, 0, 1], + * [u-c, u, 0, u+c], + * [v, v, 1, v], + * [H-u*c,V2/2, v, H+u*c]]) + * + * Ry = np.array([[1, 1, 0, 1], + * [u, u, 1, u], + * [v-c, v, 0, v+c], + * [H-v*c,V2/2, u, H+v*c]]) + * + * + * + * If \f$ \Lambda \f$ is the diagonal matrix with eigenvalues u-c, u, u, u+c, one has the + * following eigen decomposition: + * \f$ \Lambda = L A(U) R \f$ + * where \f$ A(U) = \partial F / \partial U \f$. + * + * \tparam dir allows to select which flux function / Jacobian to use : A(U) or B(U) + * + * \param[in,out] on input vector of characteristics variables, on output conservative var + * \param[in] q local hydro state vector of conservative variables + * \param[in] c speed of sound + * \param[in] gamma0 is the heat capacity ratio + */ + template + static KOKKOS_INLINE_FUNCTION void + charac_to_cons(State & data, const State & q, const real_t c, const real_t gamma0) + { + + State tmp; + + // some useful intermediate values + const real_t u = q[IU] / q[ID]; + const real_t v = q[IV] / q[ID]; + const real_t c2 = c * c; + + const real_t g1 = gamma0 - 1.0; + + // const real_t beta = 1.0/2/c2; + + const real_t V2 = u * u + v * v; + + // enthalpy + const real_t H = 0.5 * V2 + c2 / g1; + + // also equal to g1*V2 + // const real_t phi2 = g1*H-c2; + + if (dir == IX) + { + + // compute matrix vector multiply: tmp = Rx . data + tmp[ID] = data[ID] + data[IU] + data[IE]; + tmp[IU] = data[ID] * (u - c) + data[IU] * u + data[IE] * (u + c); + tmp[IV] = data[ID] * v + data[IU] * v + data[IV] + data[IE] * v; + tmp[IE] = data[ID] * (H - u * c) + data[IU] * V2 / 2 + data[IV] * v + data[IE] * (H + u * c); + } + else if (dir == IY) + { + + // compute matrix vector multiply: tmp = Ry . data + tmp[ID] = data[ID] + data[IU] + data[IE]; + tmp[IU] = data[ID] * u + data[IU] * u + data[IV] + data[IE] * u; + tmp[IV] = data[ID] * (v - c) + data[IU] * v + data[IE] * (v + c); + tmp[IE] = data[ID] * (H - v * c) + data[IU] * V2 / 2 + data[IV] * u + data[IE] * (H + v * c); + } + + data[ID] = tmp[ID]; + data[IE] = tmp[IE]; + data[IU] = tmp[IU]; + data[IV] = tmp[IV]; + + } // charac_to_cons + +}; // struct EulerEquations<2> + +/** + * 3D specialization of the Euler Equation system. + */ +template <> +struct EulerEquations<3> +{ + //! small pressure safe-guard + static constexpr real_t smallp = 1e-7; + + //! number of variables: density(1) + energy(1) + momentum(3) + static constexpr int nbvar = 2 + 3; + + //! type alias to a small array holding hydrodynamics state variables + using State = HydroState<3, nbvar>; + + //! enum + // enum varIDS { + // ID = 0, // density + // IP = 1, // Pressure (when used in primitive variables) + // IE = 1, // Energy + // IU = 2, // momentum along X + // IV = 3, // momentum along Y + // IW = 4, // momentum along Z + // }; + + //! velocity gradient tensor number of components + static const int nbvar_grad = 3 * 3; + + static constexpr int U_X = (int)gradientV_IDS_3d::U_X; + static constexpr int U_Y = (int)gradientV_IDS_3d::U_Y; + static constexpr int U_Z = (int)gradientV_IDS_3d::U_Z; + + static constexpr int V_X = (int)gradientV_IDS_3d::V_X; + static constexpr int V_Y = (int)gradientV_IDS_3d::V_Y; + static constexpr int V_Z = (int)gradientV_IDS_3d::V_Z; + + static constexpr int W_X = (int)gradientV_IDS_3d::W_X; + static constexpr int W_Y = (int)gradientV_IDS_3d::W_Y; + static constexpr int W_Z = (int)gradientV_IDS_3d::W_Z; + + //! alias typename to an array holding gradient velocity tensor components + using GradTensor = Kokkos::Array; + + //! just a dim-dimension vector + using Vector = Kokkos::Array; + + //! variables names as a std::map + static std::map + get_variable_names() + { + + std::map names; + + names[ID] = "rho"; + names[IE] = "energy"; + names[IU] = "mx"; // momentum component X + names[IV] = "my"; // momentum component Y + names[IW] = "mz"; // momentum component Z + + return names; + + } // get_variable_names + + /** + * Compute pressure. + * + * \param[in] q vector of conservative variables. + * \param[in] gamma0 + * + * \return pressure + */ + static KOKKOS_INLINE_FUNCTION real_t + compute_pressure(const State & q, real_t gamma0) + { + + // 0.5 * rho * (u^2+v^2+w^2) + real_t ekin = 0.5 * (q[IU] * q[IU] + q[IV] * q[IV] + q[IW] * q[IW]) / q[ID]; + + // pressure + real_t pressure = (gamma0 - 1.0) * (q[IE] - ekin); + + return pressure > smallp * q[ID] ? pressure : smallp * q[ID]; + + } // compute_pressure + + /** + * Compute speed of sound. + * + * \param[in] vector of primitive variables + * \param[in] gamma0 + * + * \return speed of sound + */ + static KOKKOS_INLINE_FUNCTION real_t + compute_speed_of_sound(const State & w, real_t gamma0) + { + return w[IP] * gamma0 / w[ID]; + } // compute_speed_of_sound + + /** + * Convert from conservative to primitive variables. + * + * \param[in] q vector of conservative variables. + * \param[out] q vector of primitive variables. + */ + static KOKKOS_INLINE_FUNCTION void + convert_to_primitive(const State & q, State & w, real_t gamma0) + { + + // 0.5 * rho * (u^2+v^2+w^2) + real_t ekin = 0.5 * (q[IU] * q[IU] + q[IV] * q[IV] + q[IW] * q[IW]) / q[ID]; + + // pressure + real_t pressure = (gamma0 - 1.0) * (q[IE] - ekin); + + pressure = fmax(pressure, smallp * q[ID]); + + w[ID] = q[ID]; + w[IU] = q[IU] / q[ID]; + w[IV] = q[IV] / q[ID]; + w[IW] = q[IW] / q[ID]; + w[IP] = pressure; + + } // convert_to_primitive + + /** + * Flux expression in the Euler equations system written in conservative + * form along direction X. + * + * \param[in] q conservative var \f$ (\rho, \rho u, \rho v, \rho w, E) \f$ + * \param[in] p is pressure + * \param[out] flux \f$ (\rho u, \rho u^2+p, \rho u v, \rho u w, u(E+p) ) \f$ + * + */ + static KOKKOS_INLINE_FUNCTION void + flux_x(const State & q, real_t p, State & flux) + { + real_t u = q[IU] / q[ID]; + + flux[ID] = q[IU]; // rho u + flux[IU] = u * q[IU] + p; // u rho u + p + flux[IV] = u * q[IV]; // u rho v + flux[IW] = u * q[IW]; // u rho w + flux[IE] = u * (q[IE] + p); // u (E+p) + }; + + /** + * Flux expression in the Euler equations system written in conservative + * form along direction Y. + * + * \param[in] q conservative var \f$ (\rho, \rho u, \rho v, \rho w, E) \f$ + * \param[in] p is pressure + * \param[out] flux \f$ (\rho v, \rho v u, \rho v^2+p, \rho v w, v(E+p) ) \f$ + */ + static KOKKOS_INLINE_FUNCTION void + flux_y(const State & q, real_t p, State & flux) + { + real_t v = q[IV] / q[ID]; + + flux[ID] = q[IV]; // rho v + flux[IU] = v * q[IU]; // v rho u + flux[IV] = v * q[IV] + p; // v rho v + p + flux[IW] = v * q[IW]; // v rho w + flux[IE] = v * (q[IE] + p); // v (E+p) + }; + + /** + * Flux expression in the Euler equations system written in conservative + * form along direction Z. + * + * \param[in] q conservative var \f$ (\rho, \rho u, \rho v, \rho w, E) \f$ + * \param[in] p is pressure + * \param[out] flux \f$ (\rho v, \rho v u, \rho v^2+p, \rho v w, v(E+p) ) \f$ + */ + static KOKKOS_INLINE_FUNCTION void + flux_z(const State & q, real_t p, State & flux) + { + real_t w = q[IW] / q[ID]; + + flux[ID] = q[IW]; // rho w + flux[IU] = w * q[IU]; // w rho u + flux[IV] = w * q[IV]; // w rho v + flux[IW] = w * q[IW] + p; // w rho w + p + flux[IE] = w * (q[IE] + p); // w (E+p) + }; + + /** + * Viscous term as a flux along direction X. + * + * \param[in] g is the velocity gradient tensor + * \param[in] v is the hydrodynamics velocity vector + * \param[in] f is a vector (gradient of diffusive term) + * \param[in] mu is dynamics viscosity (mu = rho * nu) + * \param[out] + * + * note that the diffusive term f represents thermal + entropy diffusion + * as in ASH / CHORUS code. + */ + static KOKKOS_INLINE_FUNCTION void + flux_visc_x(const GradTensor & g, const Vector & v, const Vector & f, real_t mu, State & flux) + { + real_t tau_xx = 2 * mu * (g[U_X] - ONE_THIRD_F * (g[U_X] + g[V_Y] + g[W_Z])); + real_t tau_yx = mu * (g[V_X] + g[U_Y]); + real_t tau_zx = mu * (g[W_X] + g[U_Z]); + + flux[ID] = 0.0; + flux[IU] = tau_xx; + flux[IV] = tau_yx; + flux[IW] = tau_zx; + flux[IE] = v[IX] * tau_xx + v[IY] * tau_yx + v[IZ] * tau_zx + f[IX]; + }; + + /** + * Viscous term as a flux along direction Y. + * + * \param[in] g is the velocity gradient tensor + * \param[in] v is the hydrodynamics velocity vector + * \param[in] f is a vector (gradient of diffusive term) + * \param[in] mu is dynamics viscosity (mu = rho * nu) + * \param[out] + * + * note that the diffusive term f represents thermal + entropy diffusion + * as in ASH / CHORUS code. + */ + static KOKKOS_INLINE_FUNCTION void + flux_visc_y(const GradTensor & g, const Vector & v, const Vector & f, real_t mu, State & flux) + { + real_t tau_xy = mu * (g[U_Y] + g[V_X]); + real_t tau_yy = 2 * mu * (g[V_Y] - ONE_THIRD_F * (g[U_X] + g[V_Y] + g[W_Z])); + real_t tau_zy = mu * (g[W_Y] + g[V_Z]); + + flux[ID] = 0.0; + flux[IU] = tau_xy; + flux[IV] = tau_yy; + flux[IW] = tau_zy; + flux[IE] = v[IX] * tau_xy + v[IY] * tau_yy + v[IZ] * tau_zy + f[IY]; + }; + + /** + * Viscous term as a flux along direction Z. + * + * \param[in] g is the velocity gradient tensor + * \param[in] v is the hydrodynamics velocity vector + * \param[in] f is a vector (gradient of diffusive term) + * \param[in] mu is dynamics viscosity (mu = rho * nu) + * \param[out] + * + * note that the diffusive term f represents thermal + entropy diffusion + * as in ASH / CHORUS code. + */ + static KOKKOS_INLINE_FUNCTION void + flux_visc_z(const GradTensor & g, const Vector & v, const Vector & f, real_t mu, State & flux) + { + real_t tau_xz = mu * (g[U_Z] + g[W_X]); + real_t tau_yz = mu * (g[V_Z] + g[W_Y]); + real_t tau_zz = 2 * mu * (g[W_Z] - ONE_THIRD_F * (g[U_X] + g[V_Y] + g[W_Z])); + + flux[ID] = 0.0; + flux[IU] = tau_xz; + flux[IV] = tau_yz; + flux[IW] = tau_zz; + flux[IE] = v[IX] * tau_xz + v[IY] * tau_yz + v[IZ] * tau_zz + f[IZ]; + }; + + /** + * Compute characteristic variables by multiply input vector + * by L (left eigenvalue matrix of Euler Jacobian). Computation is done in place. + * + * The formulas defining eigen matrix are detailed in doc/euler/euler_equations.tex + * and also copied here using python syntax + * + * # left eigenvectors (R^-1) + * Lx = np.array([[beta*(phi2+u*c), -beta*(g1*u+c), -beta*g1*v, -beta*g1*w, beta*g1], + * [1.0-phi2/c2, g1*u/c2, g1*v/c2, g1*w/c2, -g1/c2], + * [-v, 0, 1, 0, 0], + * [-w, 0, 0, 1, 0], + * [beta*(phi2-u*c), -beta*(g1*u-c), -beta*g1*v, -beta*g1*w, beta*g1]]) + * + * Ly = np.array([[beta*(phi2+v*c), -beta*g1*u, -beta*(g1*v+c), -beta*g1*w, beta*g1], + * [1.0-phi2/c2, g1*u/c2, g1*v/c2, g1*w/c2, -g1/c2], + * [-u, 1, 0, 0, 0], + * [-w, 0, 0, 1, 0], + * [beta*(phi2-v*c), -beta*g1*u, -beta*(g1*v-c), -beta*g1*w, beta*g1]]) + * + * Lz = np.array([[beta*(phi2+w*c), -beta*g1*u, -beta*g1*v, -beta*(g1*w+c), beta*g1], + * [1.0-phi2/c2, g1*u/c2, g1*v/c2, g1*w/c2, -g1/c2], + * [-u, 1, 0, 0, 0], + * [-v, 0, 1, 0, 0], + * [beta*(phi2-w*c), -beta*g1*u, -beta*g1*v, -beta*(g1*w-c), beta*g1]]) + * + * If \f$ \Lambda \f$ is the diagonal matrix with eigenvalues u-c, u, u, u, u+c, one has the + * following eigen decomposition: + * \f$ \Lambda = L A(U) R \f$ + * where \f$ A(U) = \partial F / \partial U \f$. + * + * \tparam dir allows to select which flux function / Jacobian to use : Ax(U), Ay(U) or Az(U) + * + * \param[in,out] on input vector of conservative variables, on output characteristics var + * \param[in] q local hydro state vector of conservative variables + * \param[in] c speed of sound + * \param[in] gamma0 is the heat capacity ratio + */ + template + static KOKKOS_INLINE_FUNCTION void + cons_to_charac(State & data, const State & q, const real_t c, const real_t gamma0) + { + State tmp; + + // some useful intermediate values + const real_t u = q[IU] / q[ID]; + const real_t v = q[IV] / q[ID]; + const real_t w = q[IW] / q[ID]; + const real_t c2 = c * c; + + const real_t g1 = gamma0 - 1.0; + + const real_t beta = 1.0 / 2 / c2; + + const real_t V2 = u * u + v * v + w * w; + + // enthalpy + const real_t H = 0.5 * V2 + c2 / g1; + + // also equal to g1*V2 + const real_t phi2 = g1 * H - c2; + + if (dir == IX) + { + + // compute matrix vector multiply: tmp = Lx . data + tmp[ID] = data[ID] * beta * (phi2 + u * c) + data[IU] * (-beta * (g1 * u + c)) + + data[IV] * (-beta * g1 * v) + data[IW] * (-beta * g1 * w) + data[IE] * beta * g1; + tmp[IU] = data[ID] * (1.0 - phi2 / c2) + data[IU] * g1 * u / c2 + data[IV] * g1 * v / c2 + + data[IW] * g1 * w / c2 + data[IE] * (-g1 / c2); + tmp[IV] = data[ID] * (-v) + data[IV]; + tmp[IW] = data[ID] * (-w) + data[IW]; + tmp[IE] = data[ID] * beta * (phi2 - u * c) + data[IU] * (-beta * (g1 * u - c)) + + data[IV] * (-beta * g1 * v) + data[IW] * (-beta * g1 * w) + data[IE] * beta * g1; + } + else if (dir == IY) + { + + // compute matrix vector multiply: tmp = Ly . data + tmp[ID] = data[ID] * beta * (phi2 + v * c) + data[IU] * (-beta * g1 * u) + + data[IV] * (-beta * (g1 * v + c)) + data[IW] * (-beta * g1 * w) + + data[IE] * beta * g1; + tmp[IU] = data[ID] * (1.0 - phi2 / c2) + data[IU] * g1 * u / c2 + data[IV] * g1 * v / c2 + + data[IW] * g1 * w / c2 + data[IE] * (-g1 / c2); + tmp[IV] = data[ID] * (-u) + data[IU]; + tmp[IW] = data[ID] * (-w) + data[IW]; + tmp[IE] = data[ID] * beta * (phi2 - v * c) + data[IU] * (-beta * g1 * u) + + data[IV] * (-beta * (g1 * v - c)) + data[IW] * (-beta * g1 * w) + + data[IE] * beta * g1; + } + else if (dir == IZ) + { + + // compute matrix vector multiply: tmp = Lz . data + tmp[ID] = data[ID] * beta * (phi2 + w * c) + data[IU] * (-beta * g1 * u) + + data[IV] * (-beta * g1 * v) + data[IW] * (-beta * (g1 * w + c)) + + data[IE] * beta * g1; + tmp[IU] = data[ID] * (1.0 - phi2 / c2) + data[IU] * g1 * u / c2 + data[IV] * g1 * v / c2 + + data[IW] * g1 * w / c2 + data[IE] * (-g1 / c2); + tmp[IV] = data[ID] * (-u) + data[IU]; + tmp[IW] = data[ID] * (-v) + data[IV]; + tmp[IE] = data[ID] * beta * (phi2 - w * c) + data[IU] * (-beta * g1 * u) + + data[IV] * (-beta * g1 * v) + data[IW] * (-beta * (g1 * w - c)) + + data[IE] * beta * g1; + } + + data[ID] = tmp[ID]; + data[IE] = tmp[IE]; + data[IU] = tmp[IU]; + data[IV] = tmp[IV]; + data[IW] = tmp[IW]; + + } // cons_to_charac + + /** + * Transform from characteristic variables to conservative by multiply input vector + * by R (right eigenvalue matrix of Euler Jacobian). Computation done in place. + * + * The formulas defining eigen matrix are detailed in doc/euler/euler_equations.tex + * and also copied here using python syntax + * + * # right eigenvectors + * Rx = np.array([[1, 1, 0, 0, 1], + * [u-c, u, 0, 0, u+c], + * [v, v, 1, 0, v], + * [w, w, 0, 1, w], + * [H-u*c,V2/2, v, w, H+u*c]]) + * + * Ry = np.array([[1, 1, 0, 0, 1], + * [u, u, 1, 0, u], + * [v-c, v, 0, 0, v+c], + * [w, w, 0, 1, w], + * [H-v*c,V2/2, u, w, H+v*c]]) + * + * Rz = np.array([[1, 1, 0, 0, 1], + * [u, u, 1, 0, u], + * [v , v, 0, 1, v], + * [w-c, w, 0, 0, w+c], + * [H-w*c,V2/2, u, v, H+w*c]]) + * + * + * If \f$ \Lambda \f$ is the diagonal matrix with eigenvalues u-c, u, u, u, u+c, one has the + * following eigen decomposition: + * \f$ \Lambda = L A(U) R \f$ + * where \f$ A(U) = \partial F / \partial U \f$. + * + * \tparam dir allows to select which flux function / Jacobian to use : Ax(U), Ay(U) or Az(U) + * + * \param[in,out] on input vector of characteristics variables, on output conservative vars + * \param[in] q local hydro state vector of conservative variables + * \param[in] c speed of sound + * \param[in] gamma0 is the heat capacity ratio + */ + template + static KOKKOS_INLINE_FUNCTION void + charac_to_cons(State & data, const State & q, const real_t c, const real_t gamma0) + { + State tmp; + + // some useful intermediate values + const real_t u = q[IU] / q[ID]; + const real_t v = q[IV] / q[ID]; + const real_t w = q[IW] / q[ID]; + const real_t c2 = c * c; + + const real_t g1 = gamma0 - 1.0; + + // const real_t beta = 1.0/2/c2; + + const real_t V2 = u * u + v * v + w * w; + + // enthalpy + const real_t H = 0.5 * V2 + c2 / g1; + + // also equal to g1*V2 + // const real_t phi2 = g1*H-c2; + + if (dir == IX) + { + + // compute matrix vector multiply: tmp = Rx . data + tmp[ID] = data[ID] + data[IU] + data[IE]; + tmp[IU] = data[ID] * (u - c) + data[IU] * u + data[IE] * (u + c); + tmp[IV] = data[ID] * v + data[IU] * v + data[IV] + data[IE] * v; + tmp[IW] = data[ID] * w + data[IU] * w + data[IW] + data[IE] * w; + tmp[IE] = data[ID] * (H - u * c) + data[IU] * V2 / 2 + data[IV] * v + data[IW] * w + + data[IE] * (H + u * c); + } + else if (dir == IY) + { + + // compute matrix vector multiply: tmp = Ry . data + tmp[ID] = data[ID] + data[IU] + data[IE]; + tmp[IU] = data[ID] * u + data[IU] * u + data[IV] + data[IE] * u; + tmp[IV] = data[ID] * (v - c) + data[IU] * v + data[IE] * (v + c); + tmp[IW] = data[ID] * w + data[IU] * w + data[IW] + data[IE] * w; + tmp[IE] = data[ID] * (H - v * c) + data[IU] * V2 / 2 + data[IV] * u + data[IW] * w + + data[IE] * (H + v * c); + } + else if (dir == IZ) + { + + // compute matrix vector multiply: tmp = Rz . data + tmp[ID] = data[ID] + data[IU] + data[IE]; + tmp[IU] = data[ID] * u + data[IU] * u + data[IV] + data[IE] * u; + tmp[IV] = data[ID] * v + data[IU] * v + data[IW] + data[IE] * v; + tmp[IW] = data[ID] * (w - c) + data[IU] * w + data[IE] * (w + c); + tmp[IE] = data[ID] * (H - w * c) + data[IU] * V2 / 2 + data[IV] * u + data[IW] * v + + data[IE] * (H + w * c); + } + + data[ID] = tmp[ID]; + data[IE] = tmp[IE]; + data[IU] = tmp[IU]; + data[IV] = tmp[IV]; + data[IW] = tmp[IW]; + + } // charac_to_cons + +}; // struct EulerEquations<3> + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_MODELS_EULER_EQUATIONS_H_ diff --git a/src/kalypsso/core/models/Hydro.cpp b/src/kalypsso/core/models/Hydro.cpp new file mode 100644 index 0000000..8df6e71 --- /dev/null +++ b/src/kalypsso/core/models/Hydro.cpp @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file Hydro.cpp + */ +#include + +namespace kalypsso +{ + +namespace core +{ + +namespace models +{ + +// clang-format off +const id2names_t Hydro::m_id2names_all = { + { ID, "rho" }, + { IE, "e_tot" }, + { IU, "rho_vx" }, + { IV, "rho_vy" }, + { IW, "rho_vz" }, + { IGX, "grav_x" }, + { IGY, "grav_y" }, + { IGZ, "grav_z" } +}; +// clang-format on + +} // namespace models + +} // namespace core + +} // namespace kalypsso diff --git a/src/kalypsso/core/models/Hydro.h b/src/kalypsso/core/models/Hydro.h new file mode 100644 index 0000000..1b96b46 --- /dev/null +++ b/src/kalypsso/core/models/Hydro.h @@ -0,0 +1,185 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file Hydro.h + */ +#ifndef KALYPSSO_CORE_MODELS_HYDRO_H +#define KALYPSSO_CORE_MODELS_HYDRO_H + +#include + +#include +#include +#include + +namespace kalypsso +{ + +namespace core +{ + +namespace models +{ + +// ============================================================= +// ============================================================= +class Hydro +{ + +public: + struct Settings + { + size_t dim = 2; + + Settings() = default; + + Settings(size_t dim_) + : dim(dim_) + {} + + }; // struct Settings + + using Id_t = int32_t; + + //! hydro field ids + enum VarId : Id_t + { + ID = 0, /*!< ID Density field index */ + IP = 1, /*!< IP Pressure/Energy field index */ + IE = 1, /*!< IE Energy/Pressure field index */ + IU = 2, /*!< X velocity / momentum index */ + IV = 3, /*!< Y velocity / momentum index */ + IW = 4, /*!< Z velocity / momentum index */ + IGX = 5, /*!< X gravitational field index */ + IGY = 6, /*!< Y gravitational field index */ + IGZ = 7, /*!< Z gravitational field index */ + VARID_COUNT = 8 /*!< invalid index, just counting number of fields */ + }; + + enum GradTensorId : Id_t + { + IUX = 0, /*!< du/dx */ + IUY = 1, /*!< du/dy */ + IUZ = 2, /*!< du/dz */ + IVX = 3, /*!< dv/dx */ + IVY = 4, /*!< dv/dy */ + IVZ = 5, /*!< dv/dz */ + IWX = 6, /*!< dw/dx */ + IWY = 7, /*!< dw/dy */ + IWZ = 8, /*!< dw/dz */ + }; + + enum DivergenceId : Id_t + { + IXA = 0, /*!dA/dx*/ + IYB = 1, /*!dB/dy*/ + IZC = 2, /*!dC/dz*/ + }; + + //! a dictionary of all variables names and corresponding id (enum) + //! this map is initialized in Hydro.cpp + static const id2names_t m_id2names_all; + +public: + Hydro() + : m_settings() + { + setup(); + } + + //! constructor + Hydro(Settings settings) + : m_settings(settings) + { + setup(); + } + + //! constructor + Hydro(size_t dim) + : m_settings(dim) + { + setup(); + } + + //! initialize model enabled variable maps + void + setup() + { + + m_fieldmap.enable(ID); + m_fieldmap.enable(IE); + m_fieldmap.enable(IU); + m_fieldmap.enable(IV); + if (m_settings.dim == 3) + { + m_fieldmap.enable(IW); + } + + // insert some fields in names2id and id2names maps + for (int32_t varIdInt = 0; varIdInt != VARID_COUNT; ++varIdInt) + { + const VarId varId = static_cast(varIdInt); + if (m_fieldmap.enabled(varId)) + { + const auto iter = m_id2names_all.find(varId); + + if (iter != m_id2names_all.end()) + { + m_names2id[iter->second] = varId; + m_id2names[varId] = iter->second; + } + } + } // for varIdInt + + } // setup + + FieldMap + get_fieldmap() + { + return m_fieldmap; + } + + const FieldMap & + get_fieldmap() const + { + return m_fieldmap; + } + + //! get id to names map for enabled variables + const names2id_t & + get_names2id_map() const + { + return m_names2id; + } + + //! get names to id map for enabled variables + const id2names_t & + get_id2names_map() const + { + return m_id2names; + } + +private: + //! model settings + Settings m_settings; + + //! Kokkos::Array mapping enums to index of active variables + FieldMap m_fieldmap; + + //! map of ids to names + names2id_t m_names2id; + + //! map of ids to names + id2names_t m_id2names; + +}; // class Hydro + +} // namespace models + +} // namespace core + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_MODELS_HYDRO_H diff --git a/src/kalypsso/core/models/HydroSettings.cpp b/src/kalypsso/core/models/HydroSettings.cpp new file mode 100644 index 0000000..11106db --- /dev/null +++ b/src/kalypsso/core/models/HydroSettings.cpp @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file HydroSettings.cpp + */ +#include "HydroSettings.h" + +namespace kalypsso +{ + +// ======================================================= +// ======================================================= +HydroSettings::HydroSettings(ConfigMap const & config_map) + : gamma0(config_map.getReal("hydro", "gamma0", KALYPSSO_NUM(1.4))) + , cfl(config_map.getReal("hydro", "cfl", KALYPSSO_NUM(0.5))) + , slope_type(config_map.getReal("hydro", "slope_type", KALYPSSO_NUM(1.0))) + , smallr(config_map.getReal("hydro", "smallr", KALYPSSO_NUM(1e-10))) + , smallc(config_map.getReal("hydro", "smallc", KALYPSSO_NUM(1e-10))) + , smallp(smallc * smallc / gamma0) + , smallpp(smallr * smallp) + , cIso(0) + , niter_riemann(config_map.getInteger("hydro", "niter_riemann", 10)) + , riemannSolverType(get_riemann_solver_type(config_map)) + , abort_when_negative_eint(config_map.getBool("hydro", "abort_when_negative_eint", true)) +{} // HydroSettings::HydroSettings + +// ======================================================= +// ======================================================= +void +HydroSettings::print() const +{ + + KALYPSSO_INFO("##########################"); + KALYPSSO_INFO("Hydrodynamics parameters: "); + KALYPSSO_INFO("##########################"); + KALYPSSO_INFO("gamma0 : {}", gamma0); + KALYPSSO_INFO("cfl : {}", cfl); + KALYPSSO_INFO("smallr : {:12.10f}", smallr); + KALYPSSO_INFO("smallc : {:12.10f}", smallc); + KALYPSSO_INFO("smallp : {:12.10f}", smallp); + KALYPSSO_INFO("smallpp : {:12.10f}", smallpp); + KALYPSSO_INFO("niter_riemann : {}", niter_riemann); + KALYPSSO_INFO("slope_type : {}", slope_type); + KALYPSSO_INFO("riemann : {}", riemannSolverType._to_string()); + KALYPSSO_INFO("##########################"); + +} // HydroSettings::print + +} // namespace kalypsso diff --git a/src/kalypsso/core/models/HydroSettings.h b/src/kalypsso/core/models/HydroSettings.h new file mode 100644 index 0000000..e004f9b --- /dev/null +++ b/src/kalypsso/core/models/HydroSettings.h @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file HydroSettings.h + * \brief Hydrodynamics solver parameters. + */ +#ifndef KALYPSSO_CORE_MODELS_HYDROSETTINGS_H_ +#define KALYPSSO_CORE_MODELS_HYDROSETTINGS_H_ + +#include +#include +#include +#include + +namespace kalypsso +{ + +// =========================================================================== +// =========================================================================== +/** + * Parameters that can be passed by copy to a Kokkos device. + */ +struct HydroSettings +{ + + // hydro (numerical scheme) parameters + real_t gamma0; /*!< specific heat capacity ratio (adiabatic index) - DEPRECATED (use EosWrapper + instead) */ + real_t cfl; /*!< Courant-Friedrich-Lewy parameter.*/ + real_t slope_type; /*!< type of slope computation (2 for second order scheme).*/ + real_t smallr; /*!< small density cut-off*/ + real_t smallc; /*!< small speed of sound cut-off*/ + real_t smallp; /*!< small pressure cut-off*/ + real_t smallpp; /*!< smallp times smallr*/ + real_t cIso; /*!< if non zero, isothermal */ + int niter_riemann; /*!< number of iteration used in quasi-exact Riemann solver*/ + RiemannSolverType riemannSolverType; /*!< type of Riemann solver (LLF, HLLC, ...) */ + bool abort_when_negative_eint; /*!< abort if negative internal energy */ + + KALYPSSO_STATIC_MATH_CONSTANT(ONE_DOT_FOUR, 1.400000000000000000000000000000000000L); + KALYPSSO_STATIC_MATH_CONSTANT(SMALLR, 1e-8); + KALYPSSO_STATIC_MATH_CONSTANT(SMALLP, 1e-6); + + HydroSettings(ConfigMap const & config_map); + ~HydroSettings() = default; + + void + print() const; + +}; // struct HydroSettings + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_MODELS_HYDROSETTINGS_H_ diff --git a/src/kalypsso/core/models/HydroState.h b/src/kalypsso/core/models/HydroState.h new file mode 100644 index 0000000..a7b0b89 --- /dev/null +++ b/src/kalypsso/core/models/HydroState.h @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file HydroState.h + */ +#ifndef KALYPSSO_CORE_MODELS_HYDRO_STATE_H_ +#define KALYPSSO_CORE_MODELS_HYDRO_STATE_H_ + +#include +#include + +namespace kalypsso +{ + +constexpr int HYDRO_2D_NBVAR = 4; +constexpr int HYDRO_3D_NBVAR = 5; + +template +constexpr int +nbvar_hydro() +{ + if constexpr (dim == 2) + return HYDRO_2D_NBVAR; + else + return HYDRO_3D_NBVAR; +} + +template +using StateNd = Kokkos::Array; + +template +using HydroState = StateNd()>; + +template +using GradTensor = Kokkos::Array; + +using HydroState2d = HydroState<2>; +using HydroState3d = HydroState<3>; +using GravityState = Kokkos::Array; + +constexpr int HYDRO_NBVAR_CELL = 5; +} // namespace kalypsso + +#endif // KALYPSSO_CORE_MODELS_HYDRO_STATE_H_ diff --git a/src/kalypsso/core/models/MHD.cpp b/src/kalypsso/core/models/MHD.cpp new file mode 100644 index 0000000..33f5fe8 --- /dev/null +++ b/src/kalypsso/core/models/MHD.cpp @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file MHD.cpp + */ +#include + +namespace kalypsso +{ + +namespace core +{ + +namespace models +{ + +// clang-format off +const id2names_t MHD::m_id2names_all = { + { ID, "rho" }, + { IE, "e_tot" }, + { IU, "rho_vx" }, + { IV, "rho_vy" }, + { IW, "rho_vz" }, + { IBX, "Bx" }, + { IBY, "By" }, + { IBZ, "Bz" }, + { IA0, "B0x" }, + { IB0, "B0y" }, + { IC0, "B0z" }, + { IGX, "grav_x" }, + { IGY, "grav_y" }, + { IGZ, "grav_z" } +}; +// clang-format on + +} // namespace models + +} // namespace core + +} // namespace kalypsso diff --git a/src/kalypsso/core/models/MHD.h b/src/kalypsso/core/models/MHD.h new file mode 100644 index 0000000..68886a0 --- /dev/null +++ b/src/kalypsso/core/models/MHD.h @@ -0,0 +1,191 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file MHD.h + */ +#ifndef KALYPSSO_CORE_MODELS_MHD_H +#define KALYPSSO_CORE_MODELS_MHD_H + +#include +#include +#include + +#include +#include +#include + +namespace kalypsso +{ + +namespace core +{ + +namespace models +{ + +// ============================================================= +// ============================================================= +class MHD +{ + +public: + using Id_t = int32_t; + + //! MHD field ids + enum VarId : Id_t + { + ID = 0, /*!< ID Density field index */ + IP = 1, /*!< IP Pressure/Energy field index */ + IE = 1, /*!< IE Energy/Pressure field index */ + IU = 2, /*!< X velocity / momentum index */ + IV = 3, /*!< Y velocity / momentum index */ + IW = 4, /*!< Z velocity / momentum index */ + IA = 5, /*!< X magnetic field index */ + IB = 6, /*!< Y magnetic field index */ + IC = 7, /*!< Z magnetic field index */ + IBX = 5, /*!< X magnetic field index */ + IBY = 6, /*!< Y magnetic field index */ + IBZ = 7, /*!< Z magnetic field index */ + IAL = 5, /*!< X magnetic field index */ + IBL = 6, /*!< Y magnetic field index */ + ICL = 7, /*!< Z magnetic field index */ + IAR = 8, /*!< X magnetic field index */ + IBR = 9, /*!< Y magnetic field index */ + ICR = 10, /*!< Z magnetic field index */ + IGX = 11, /*!< X gravitational field index */ + IGY = 12, /*!< Y gravitational field index */ + IGZ = 13, /*!< Z gravitational field index */ + IA0 = 14, /*!< X zero order magnetic field index */ + IB0 = 15, /*!< Y zero order magnetic field index */ + IC0 = 16, /*!< Z zero order magnetic field index */ + VARID_COUNT = 17 /*!< invalid index, just counting number of fields */ + }; + + enum GradTensorId : Id_t + { + IUX = 0, /*!< du/dx */ + IUY = 1, /*!< du/dy */ + IUZ = 2, /*!< du/dz */ + IVX = 3, /*!< dv/dx */ + IVY = 4, /*!< dv/dy */ + IVZ = 5, /*!< dv/dz */ + IWX = 6, /*!< dw/dx */ + IWY = 7, /*!< dw/dy */ + IWZ = 8, /*!< dw/dz */ + }; + + enum DivergenceId : Id_t + { + IXA = 0, /*!dA/dx*/ + IYB = 1, /*!dB/dy*/ + IZC = 2, /*!dC/dz*/ + }; + + //! Face-centered magnetic field component enumeration. + //! To be used with FaceMagState. + enum FaceMagneticFieldId : Id_t + { + AL = IAL - IAL, /*!< left face along X magnetic field index */ + BL = IBL - IAL, /*!< left face along Y magnetic field index */ + CL = ICL - IAL, /*!< left face along Z magnetic field index */ + AR = IAR - IAL, /*!< right face along X magnetic field index */ + BR = IBR - IAL, /*!< right face along Y magnetic field index */ + CR = ICR - IAL, /*!< right face along Z magnetic field index */ + }; + + //! a dictionary of all variables names and corresponding id (enum) + //! this map is initialized in MHD.cpp + static const id2names_t m_id2names_all; + +public: + //! constructor + MHD(ConfigMap const & config_map) { setup(config_map); } + + //! initialize model enabled variable maps + void + setup(ConfigMap const & config_map) + { + + m_fieldmap.enable(ID); + m_fieldmap.enable(IE); + m_fieldmap.enable(IU); + m_fieldmap.enable(IV); + m_fieldmap.enable(IW); + m_fieldmap.enable(IA); + m_fieldmap.enable(IB); + m_fieldmap.enable(IC); + + const auto mhd_settings = MHDSettings(config_map); + + if (mhd_settings.mag_field_split_enabled) + { + m_fieldmap.enable(IA0); + m_fieldmap.enable(IB0); + m_fieldmap.enable(IC0); + } + + // insert some fields in names2id and id2names maps + for (int32_t varIdInt = 0; varIdInt != VARID_COUNT; ++varIdInt) + { + const VarId varId = static_cast(varIdInt); + if (m_fieldmap.enabled(varId)) + { + const auto iter = m_id2names_all.find(varId); + + if (iter != m_id2names_all.end()) + { + m_names2id[iter->second] = varId; + m_id2names[varId] = iter->second; + } + } + } // for varIdInt + + } // setup + + FieldMap + get_fieldmap() + { + return m_fieldmap; + } + + const FieldMap & + get_fieldmap() const + { + return m_fieldmap; + } + + //! get id to names map for enabled variables + const names2id_t & + get_names2id_map() const + { + return m_names2id; + } + + //! get names to id map for enabled variables + const id2names_t & + get_id2names_map() const + { + return m_id2names; + } + +private: + //! Kokkos::Array mapping enums to index of active variables + FieldMap m_fieldmap; + + //! map of ids to names + names2id_t m_names2id; + + //! map of ids to names + id2names_t m_id2names; + +}; // class MHD + +} // namespace models + +} // namespace core + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_MODELS_MHD_H diff --git a/src/kalypsso/core/models/MHDSettings.cpp b/src/kalypsso/core/models/MHDSettings.cpp new file mode 100644 index 0000000..5fd9027 --- /dev/null +++ b/src/kalypsso/core/models/MHDSettings.cpp @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file MHDSettings.cpp + */ +#include "MHDSettings.h" + +namespace kalypsso +{ + +// ======================================================= +// ======================================================= +MHDSettings::MHDSettings(ConfigMap const & config_map) + : hydro(config_map) + , mag_field_split_enabled(config_map.getBool("mhd", "mag_field_split_enabled", false)) + , correction_boris_enabled(config_map.getBool("mhd", "correction_boris_enabled", false)) + , cboris(config_map.getReal("mhd", "cboris", KALYPSSO_NUM(1.0))) + , correction_entropy_enabled(config_map.getBool("mhd", "correction_entropy_enabled", false)) + , small_beta(config_map.getReal("mhd", "small_beta", KALYPSSO_NUM(1e-3))) + , large_alfven(config_map.getReal("mhd", "large_alfven", KALYPSSO_NUM(10.0))) +{} + +// ======================================================= +// ======================================================= +void +MHDSettings::print() const +{ + hydro.print(); + + KALYPSSO_INFO("mag_field_split_enabled : {}", mag_field_split_enabled); + KALYPSSO_INFO("correction_boris_enabled : {}", correction_boris_enabled); + KALYPSSO_INFO("cboris (MHD-CC only) : {}", cboris); + KALYPSSO_INFO("correction_entropy_enabled : {}", correction_entropy_enabled); + KALYPSSO_INFO("small_beta : {}", small_beta); + KALYPSSO_INFO("large_alfven : {}", large_alfven); + +} // MHDSettings::print + +} // namespace kalypsso diff --git a/src/kalypsso/core/models/MHDSettings.h b/src/kalypsso/core/models/MHDSettings.h new file mode 100644 index 0000000..0e3ae34 --- /dev/null +++ b/src/kalypsso/core/models/MHDSettings.h @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file HydroSettings.h + * \brief Hydrodynamics solver parameters. + */ +#ifndef KALYPSSO_CORE_MODELS_MHDSETTINGS_H_ +#define KALYPSSO_CORE_MODELS_MHDSETTINGS_H_ + +#include + +namespace kalypsso +{ + +// =========================================================================== +// =========================================================================== +/** + * Parameters that can be passed by copy to a Kokkos device. + */ +struct MHDSettings +{ + //! Hydrodynamics settings + HydroSettings hydro; + + //! enable the splitting of magnetic field: B = B0 + B1 + bool mag_field_split_enabled; + + //! enable Boris correction + bool correction_boris_enabled; + + //! velocity used in the boris correction + real_t cboris; + + //! enable entropy correction + bool correction_entropy_enabled; + + //! Lower threshold value for plasma beta + real_t small_beta; + + //! Upper threshold value for alfven + real_t large_alfven; + + MHDSettings(ConfigMap const & config_map); + ~MHDSettings() = default; + + void + print() const; + +}; // struct MHDSettings + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_MODELS_MHDSETTINGS_H_ diff --git a/src/kalypsso/core/models/MHDState.h b/src/kalypsso/core/models/MHDState.h new file mode 100644 index 0000000..3ef6596 --- /dev/null +++ b/src/kalypsso/core/models/MHDState.h @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file MHDState.h + */ +#ifndef KALYPSSO_CORE_MODELS_MHD_STATE_H_ +#define KALYPSSO_CORE_MODELS_MHD_STATE_H_ + +#include +#include + +namespace kalypsso +{ + +template +using StateNd = Kokkos::Array; + +template +using GradTensor = Kokkos::Array; + +template +using DivergenceB = Kokkos::Array; + +using GravityState = Kokkos::Array; + +constexpr int HYDRO_NBVAR_CELL = 5; +constexpr int MHD_NBVAR_CELL = 8; +constexpr int MHD_NBVAR_FACE = 11; +constexpr int MHD_NBVAR_SPLIT = 11; + +template +constexpr int +nbvar_hydro_only() +{ + if constexpr (dim == 2) + return 4; + else if constexpr (dim == 3) + return 5; +} + +template +using HydroState = Kokkos::Array()>; + +//! state variable array with cell-centered magnetic field +using MHDStateCell = Kokkos::Array; + +//! state variable array with cell-centered and split magnetic field +using MHDSplitStateCell = Kokkos::Array; + +//! state variables array with cell-centered hydrodynamics var and +//! face-centered magnetic field (left and right) +using MHDStateFace = Kokkos::Array; + +//! Face-centered magnetic field as a 6-component vector. +//! left and right faces in all three direction (X,Y and Z). +//! +//! designed to be use with enum FaceMagneticFieldId +using FaceMagState = Kokkos::Array; + +//! magnetic field component array +using BField = Kokkos::Array; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_MODELS_MHD_STATE_H_ diff --git a/src/kalypsso/core/models/RiemannSolvers.h b/src/kalypsso/core/models/RiemannSolvers.h new file mode 100644 index 0000000..b1bc986 --- /dev/null +++ b/src/kalypsso/core/models/RiemannSolvers.h @@ -0,0 +1,818 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file RiemannSolvers.h + * + * Several Riemann solvers. + */ +#ifndef KALYPSSO_CORE_MODELS_RIEMANN_SOLVERS_H_ +#define KALYPSSO_CORE_MODELS_RIEMANN_SOLVERS_H_ + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace kalypsso +{ +/** + * Compute cell fluxes from the Godunov state + * \param[in] qgdnv input primitive variables Godunov state + * \param[out] flux output flux vector + */ +template +KOKKOS_INLINE_FUNCTION void +cmpflx(HydroState const & qgdnv, HydroState & flux, EosWrapper const & eos) +{ + // makes enum Hydro::VarId available + using Hydro = kalypsso::core::models::Hydro; + + // Compute fluxes + // Mass density + flux[Hydro::ID] = qgdnv[Hydro::ID] * qgdnv[Hydro::IU]; + + // Normal momentum + flux[Hydro::IU] = flux[Hydro::ID] * qgdnv[Hydro::IU] + qgdnv[Hydro::IP]; + + // Transverse momentum 1 + flux[Hydro::IV] = flux[Hydro::ID] * qgdnv[Hydro::IV]; + + if constexpr (dim == 3) + flux[Hydro::IW] = flux[Hydro::ID] * qgdnv[Hydro::IW]; + + // Total energy + real_t ekin; + ekin = HALF_F * qgdnv[Hydro::ID] * + (qgdnv[Hydro::IU] * qgdnv[Hydro::IU] + qgdnv[Hydro::IV] * qgdnv[Hydro::IV]); + if constexpr (dim == 3) + ekin += HALF_F * qgdnv[Hydro::ID] * (qgdnv[Hydro::IW] * qgdnv[Hydro::IW]); + + const auto etot = eos.volumic_eint_from_pressure(qgdnv[Hydro::IP], qgdnv[Hydro::ID]) + ekin; + flux[Hydro::IP] = qgdnv[Hydro::IU] * (etot + qgdnv[Hydro::IP]); + +} // cmpflx + +/** + * Riemann solver, equivalent to riemann_approx in RAMSES (see file + * godunov_utils.f90 in RAMSES). + * + * @param[in] qL : input left state (primitive variables) + * @param[in] qR : input right state (primitive variables) + * @param[out] qgdnv : output Godunov state + * @param[out] flux : output flux + */ +template +KOKKOS_INLINE_FUNCTION void +riemann_approx(HydroState const & qL, + HydroState const & qR, + HydroState & flux, + HydroSettings const & settings, + EosWrapper const & eos) +{ + // makes enum Hydro::VarId available + using Hydro = kalypsso::core::models::Hydro; + + HydroState qgdnv; + + // this is only valid for ideal gas eos + const auto gamma6 = core::eos::gamma6(eos.gamma()); + KOKKOS_ASSERT(gamma6 > 0); + + auto const & smallr = settings.smallr; + auto const & smallc = settings.smallc; + auto const & smallp = settings.smallp; + auto const & smallpp = settings.smallpp; + + // Pressure, density and velocity + const auto rl = fmax(qL[Hydro::ID], smallr); + const auto ul = qL[Hydro::IU]; + const auto pl = fmax(qL[Hydro::IP], rl * smallp); + const auto rr = fmax(qR[Hydro::ID], smallr); + const auto ur = qR[Hydro::IU]; + const auto pr = fmax(qR[Hydro::IP], rr * smallp); + + // Lagrangian sound speed + // const auto cl = gamma * pl * rl; + // const auto cr = gamma * pr * rr; + const auto ccl = eos.sound_speed(pl, rl); + const auto cl = ccl * ccl * rl * rl; + const auto ccr = eos.sound_speed(pr, rr); + const auto cr = ccr * ccr * rr * rr; + + // First guess + auto wl = sqrt(cl); + auto wr = sqrt(cr); + auto pstar = fmax(((wr * pl + wl * pr) + wl * wr * (ul - ur)) / (wl + wr), ZERO_F); + auto pold = pstar; + auto conv = ONE_F; + + // Newton-Raphson iterations to find pstar at the required accuracy + for (int iter = 0; (iter < 10 /*niter_riemann*/) && (conv > KALYPSSO_NUM(1e-6)); ++iter) + { + const auto wwl = sqrt(cl * (ONE_F + gamma6 * (pold - pl) / pl)); + const auto wwr = sqrt(cr * (ONE_F + gamma6 * (pold - pr) / pr)); + const auto ql = KALYPSSO_NUM(2.0) * wwl * wwl * wwl / (wwl * wwl + cl); + const auto qr = KALYPSSO_NUM(2.0) * wwr * wwr * wwr / (wwr * wwr + cr); + const auto usl = ul - (pold - pl) / wwl; + const auto usr = ur + (pold - pr) / wwr; + const auto delp = fmax(qr * ql / (qr + ql) * (usl - usr), -pold); + + pold = pold + delp; + conv = fabs(delp / (pold + smallpp)); // Convergence indicator + } + + // Star region pressure + // for a two-shock Riemann problem + pstar = pold; + wl = sqrt(cl * (ONE_F + gamma6 * (pstar - pl) / pl)); + wr = sqrt(cr * (ONE_F + gamma6 * (pstar - pr) / pr)); + + // Star region velocity + // for a two shock Riemann problem + const auto ustar = HALF_F * (ul + (pl - pstar) / wl + ur - (pr - pstar) / wr); + + // Left going or right going contact wave + real_t sgnm = COPYSIGN(ONE_F, ustar); + + // Left or right unperturbed state + real_t ro, uo, po, wo; + if (sgnm > ZERO_F) + { + ro = rl; + uo = ul; + po = pl; + wo = wl; + } + else + { + ro = rr; + uo = ur; + po = pr; + wo = wr; + } + + const auto co = fmax(smallc, eos.sound_speed(po, ro)); + + // Star region density (Shock, fmax prevents vacuum formation in star region) + const auto rstar = fmax((ro / (ONE_F + ro * (po - pstar) / (wo * wo))), (smallr)); + + // Star region sound speed + const auto cstar = fmax(smallc, eos.sound_speed(pstar, rstar)); + + // Compute rarefaction head and tail speed + auto spout = co - sgnm * uo; + auto spin = cstar - sgnm * ustar; + // Compute shock speed + const auto ushock = wo / ro - sgnm * uo; + + if (pstar >= po) + { + spin = ushock; + spout = ushock; + } + + // Sample the solution at x/t=0 + auto scr = fmax(spout - spin, smallc + fabs(spout + spin)); + auto frac = HALF_F * (ONE_F + (spout + spin) / scr); + + if (frac != frac) /* Not a Number */ + frac = KALYPSSO_NUM(0.0); + else + frac = frac >= KALYPSSO_NUM(1.0) ? KALYPSSO_NUM(1.0) + : frac <= KALYPSSO_NUM(0.0) ? KALYPSSO_NUM(0.0) + : frac; + + qgdnv[Hydro::ID] = frac * rstar + (ONE_F - frac) * ro; + qgdnv[Hydro::IU] = frac * ustar + (ONE_F - frac) * uo; + qgdnv[Hydro::IP] = frac * pstar + (ONE_F - frac) * po; + + if (spout < ZERO_F) + { + qgdnv[Hydro::ID] = ro; + qgdnv[Hydro::IU] = uo; + qgdnv[Hydro::IP] = po; + } + + if (spin > ZERO_F) + { + qgdnv[Hydro::ID] = rstar; + qgdnv[Hydro::IU] = ustar; + qgdnv[Hydro::IP] = pstar; + } + + // transverse velocity + if (sgnm > ZERO_F) + { + qgdnv[Hydro::IV] = qL[Hydro::IV]; + if constexpr (dim == 3) + qgdnv[Hydro::IW] = qL[Hydro::IW]; + } + else + { + qgdnv[Hydro::IV] = qR[Hydro::IV]; + if constexpr (dim == 3) + qgdnv[Hydro::IW] = qR[Hydro::IW]; + } + + cmpflx(qgdnv, flux, eos); + +} // riemann_approx + +/** + * Riemann solver, equivalent to riemann_llf in RAMSES (see file + * godunov_utils.f90 in RAMSES). + * + * LLF = Local Lax-Friedrich (also called Rusanov flux). + * + * Reference : E.F. Toro, Riemann solvers and numerical methods for + * fluid dynamics, Springer, chapter 10 (The HLL and HLLC Riemann solver). + * See section 10.5.1, equation 10.43 which gives the expression of S+ denoted here + * as cmax. + * + * @param[in] qL : input left state (primitive variables) + * @param[in] qR : input right state (primitive variables) + * @param[out] flux : output flux + */ +template +KOKKOS_INLINE_FUNCTION void +riemann_llf(HydroState const & qL, + HydroState const & qR, + HydroState & flux, + HydroSettings const & settings, + EosWrapper const & eos) +{ + // makes enum Hydro::VarId available + using Hydro = kalypsso::core::models::Hydro; + + // 1D LLF Riemann solver + + // constants + auto const & smallr = settings.smallr; + auto const & smallp = settings.smallp; + + //============================ + // Compute maximum wave speed + //============================ + auto rl = fmax(qL[Hydro::ID], smallr); + auto ul = qL[Hydro::IU]; + auto pl = fmax(qL[Hydro::IP], rl * smallp); + + auto rr = fmax(qR[Hydro::ID], smallr); + auto ur = qR[Hydro::IU]; + auto pr = fmax(qR[Hydro::IP], rr * smallp); + + auto cl = eos.sound_speed(pl, rl); + auto cr = eos.sound_speed(pr, rr); + + auto cmax = fmax(fabs(ul) + cl, fabs(ur) + cr); + + // Compute average velocity + // qgdnv[Hydro::IU] = HALF_F*(qL[Hydro::IU]+qR[Hydro::IU]); + + //================================ + // Compute conservative variables + //================================ + HydroState UL, UR; + // mass density + UL[Hydro::ID] = qL[Hydro::ID]; + UR[Hydro::ID] = qR[Hydro::ID]; + + // total energy + UL[Hydro::IP] = eos.volumic_eint_from_pressure(qL[Hydro::IP], qL[Hydro::ID]) + + HALF_F * qL[Hydro::ID] * qL[Hydro::IU] * qL[Hydro::IU]; + UR[Hydro::IP] = eos.volumic_eint_from_pressure(qR[Hydro::IP], qR[Hydro::ID]) + + HALF_F * qR[Hydro::ID] * qR[Hydro::IU] * qR[Hydro::IU]; + + UL[Hydro::IP] += HALF_F * qL[Hydro::ID] * qL[Hydro::IV] * qL[Hydro::IV]; + UR[Hydro::IP] += HALF_F * qR[Hydro::ID] * qR[Hydro::IV] * qR[Hydro::IV]; + + if constexpr (dim == 3) + { + UL[Hydro::IP] += HALF_F * qL[Hydro::ID] * qL[Hydro::IW] * qL[Hydro::IW]; + UR[Hydro::IP] += HALF_F * qR[Hydro::ID] * qR[Hydro::IW] * qR[Hydro::IW]; + } + + // normal momentum + UL[Hydro::IU] = qL[Hydro::ID] * qL[Hydro::IU]; + UR[Hydro::IU] = qR[Hydro::ID] * qR[Hydro::IU]; + + // transverse momentum + UL[Hydro::IV] = qL[Hydro::ID] * qL[Hydro::IV]; + UR[Hydro::IV] = qR[Hydro::ID] * qR[Hydro::IV]; + + if constexpr (dim == 3) + { + UL[Hydro::IW] = qL[Hydro::ID] * qL[Hydro::IW]; + UR[Hydro::IW] = qR[Hydro::ID] * qR[Hydro::IW]; + } + + //=============================== + // Compute left and right fluxes + //=============================== + HydroState FL, FR; + // mass density + FL[Hydro::ID] = UL[Hydro::ID] * qL[Hydro::IU]; + FR[Hydro::ID] = UR[Hydro::ID] * qR[Hydro::IU]; + + // total energy + FL[Hydro::IP] = qL[Hydro::IU] * (UL[Hydro::IP] + qL[Hydro::IP]); + FR[Hydro::IP] = qR[Hydro::IU] * (UR[Hydro::IP] + qR[Hydro::IP]); + + // normal momentum + FL[Hydro::IU] = qL[Hydro::IP] + UL[Hydro::IU] * qL[Hydro::IU]; + FR[Hydro::IU] = qR[Hydro::IP] + UR[Hydro::IU] * qR[Hydro::IU]; + + // transverse momentum + FL[Hydro::IV] = FL[Hydro::ID] * qL[Hydro::IV]; + FR[Hydro::IV] = FR[Hydro::ID] * qR[Hydro::IV]; + + if constexpr (dim == 3) + { + FL[Hydro::IW] = FL[Hydro::ID] * qL[Hydro::IW]; + FR[Hydro::IW] = FR[Hydro::ID] * qR[Hydro::IW]; + } + + //============================== + // Compute Lax-Friedrich fluxes + //============================== + flux = HALF_F * (FL + FR - cmax * (UR - UL)); + +} // riemann_llf + +/** + * Riemann solver, equivalent to riemann_hll in RAMSES (see file + * godunov_utils.f90 in RAMSES). + * + * This is the HYDRO only version. The MHD version is in file riemann_mhd.h + * + * Reference : E.F. Toro, Riemann solvers and numerical methods for + * fluid dynamics, Springer, chapter 10 (The HLL and HLLC Riemann solver). + * + * @param[in] qL : input left state (primitive variables) + * @param[in] qR : input right state (primitive variables) + * @param[out] flux : output flux + */ +template +KOKKOS_INLINE_FUNCTION void +riemann_hll(HydroState const & qL, + HydroState const & qR, + HydroState & flux, + HydroSettings const & settings, + EosWrapper const & eos) +{ + + // makes enum Hydro::VarId available + using Hydro = kalypsso::core::models::Hydro; + + // 1D HLL Riemann solver + + // constants + auto const & smallr = settings.smallr; + auto const & smallp = settings.smallp; + + // Maximum wave speed + const auto rl = fmax(qL[Hydro::ID], smallr); + const auto ul = qL[Hydro::IU]; + const auto pl = fmax(qL[Hydro::IP], rl * smallp); + + const auto rr = fmax(qR[Hydro::ID], smallr); + const auto ur = qR[Hydro::IU]; + const auto pr = fmax(qR[Hydro::IP], rr * smallp); + + const auto cl = eos.sound_speed(pl, rl); + const auto cr = eos.sound_speed(pr, rr); + + const auto SL = fmin(fmin(ul, ur) - fmax(cl, cr), ZERO_F); + const auto SR = fmax(fmax(ul, ur) + fmax(cl, cr), ZERO_F); + + // Compute average velocity + // qgdnv[Hydro::IU] = HALF_F*(qL[Hydro::IU]+qR[Hydro::IU]); + + // Compute conservative variables + HydroState UL, UR; + UL[Hydro::ID] = qL[Hydro::ID]; + UR[Hydro::ID] = qR[Hydro::ID]; + UL[Hydro::IP] = eos.volumic_eint_from_pressure(qL[Hydro::IP], qL[Hydro::ID]) + + HALF_F * qL[Hydro::ID] * qL[Hydro::IU] * qL[Hydro::IU]; + UR[Hydro::IP] = eos.volumic_eint_from_pressure(qR[Hydro::IP], qR[Hydro::ID]) + + HALF_F * qR[Hydro::ID] * qR[Hydro::IU] * qR[Hydro::IU]; + UL[Hydro::IP] += HALF_F * qL[Hydro::ID] * qL[Hydro::IV] * qL[Hydro::IV]; + UR[Hydro::IP] += HALF_F * qR[Hydro::ID] * qR[Hydro::IV] * qR[Hydro::IV]; + if constexpr (dim == 3) + { + UL[Hydro::IP] += HALF_F * qL[Hydro::ID] * qL[Hydro::IW] * qL[Hydro::IW]; + UR[Hydro::IP] += HALF_F * qR[Hydro::ID] * qR[Hydro::IW] * qR[Hydro::IW]; + } + UL[Hydro::IU] = qL[Hydro::ID] * qL[Hydro::IU]; + UR[Hydro::IU] = qR[Hydro::ID] * qR[Hydro::IU]; + + // Other advected quantities + UL[Hydro::IV] = qL[Hydro::ID] * qL[Hydro::IV]; + UR[Hydro::IV] = qR[Hydro::ID] * qR[Hydro::IV]; + if constexpr (dim == 3) + { + UL[Hydro::IW] = qL[Hydro::ID] * qL[Hydro::IW]; + UR[Hydro::IW] = qR[Hydro::ID] * qR[Hydro::IW]; + } + + // Compute left and right fluxes + HydroState FL, FR; + FL[Hydro::ID] = UL[Hydro::IU]; + FR[Hydro::ID] = UR[Hydro::IU]; + FL[Hydro::IP] = qL[Hydro::IU] * (UL[Hydro::IP] + qL[Hydro::IP]); + FR[Hydro::IP] = qR[Hydro::IU] * (UR[Hydro::IP] + qR[Hydro::IP]); + FL[Hydro::IU] = qL[Hydro::IP] + UL[Hydro::IU] * qL[Hydro::IU]; + FR[Hydro::IU] = qR[Hydro::IP] + UR[Hydro::IU] * qR[Hydro::IU]; + + // Other advected quantities + FL[Hydro::IV] = FL[Hydro::ID] * qL[Hydro::IV]; + FR[Hydro::IV] = FR[Hydro::ID] * qR[Hydro::IV]; + if constexpr (dim == 3) + { + FL[Hydro::IW] = FL[Hydro::ID] * qL[Hydro::IW]; + FR[Hydro::IW] = FR[Hydro::ID] * qR[Hydro::IW]; + } + + // Compute HLL fluxes + flux = (SR * FL - SL * FR + SR * SL * (UR - UL)) / (SR - SL); + +} // riemann_hll + +/** + * Riemann solver HLLC + * + * @param[in] qL : input left state (primitive variables) + * @param[in] qR : input right state (primitive variables) + * @param[out] flux : output flux + * + * Reference: + * - Riemann Solvers and Numerical Methods for Fluid Dynamics. A practical introduction. E. F. Toro, + * Springer (2009). https://link.springer.com/book/10.1007/b79761, chapter 10 + * - "On the Choice of Wavespeeds for the HLLC Riemann Solver", Batten et al., SIAM J. Sci. Comp., + * vol 18, issue 6, 1997, https://doi.org/10.1137/S1064827593260140 + */ +template +KOKKOS_INLINE_FUNCTION void +riemann_hllc(HydroState const & qL, + HydroState const & qR, + HydroState & flux, + HydroSettings const & settings, + EosWrapper const & eos) +{ + // makes enum Hydro::VarId available + using Hydro = kalypsso::core::models::Hydro; + + // UNUSED(qgdnv); + + auto const & smallr = settings.smallr; + auto const & smallp = settings.smallp; + auto const & smallc = settings.smallc; + + // Left variables + const auto rl = fmax(qL[Hydro::ID], smallr); + const auto pl = fmax(qL[Hydro::IP], rl * smallp); + const auto ul = qL[Hydro::IU]; + + auto ecinl = HALF_F * rl * ul * ul; + ecinl += HALF_F * rl * qL[Hydro::IV] * qL[Hydro::IV]; + if constexpr (dim == 3) + ecinl += HALF_F * rl * qL[Hydro::IW] * qL[Hydro::IW]; + + const auto etotl = eos.volumic_eint_from_pressure(pl, rl) + ecinl; + const auto ptotl = pl; + + // Right variables + const auto rr = fmax(qR[Hydro::ID], smallr); + const auto pr = fmax(qR[Hydro::IP], rr * smallp); + const auto ur = qR[Hydro::IU]; + + real_t ecinr = HALF_F * rr * ur * ur; + ecinr += HALF_F * rr * qR[Hydro::IV] * qR[Hydro::IV]; + if constexpr (dim == 3) + ecinr += HALF_F * rr * qR[Hydro::IW] * qR[Hydro::IW]; + + const auto etotr = eos.volumic_eint_from_pressure(pr, rr) + ecinr; + const auto ptotr = pr; + + // Find the largest eigenvalues in the normal direction to the interface + const auto cfastl = fmax(eos.sound_speed(pl, rl), smallc); + const auto cfastr = fmax(eos.sound_speed(pr, rr), smallc); + + // Compute HLL wave speed + const auto SL = fmin(ul, ur) - fmax(cfastl, cfastr); + const auto SR = fmax(ul, ur) + fmax(cfastl, cfastr); + + // Compute lagrangian sound speed + const auto rcl = rl * (ul - SL); + const auto rcr = rr * (SR - ur); + + // Compute acoustic star state + const auto ustar = (rcr * ur + rcl * ul + (ptotl - ptotr)) / (rcr + rcl); + const auto ptotstar = (rcr * ptotl + rcl * ptotr + rcl * rcr * (ul - ur)) / (rcr + rcl); + + // Left star region variables + const auto rstarl = rl * (SL - ul) / (SL - ustar); + const auto etotstarl = ((SL - ul) * etotl - ptotl * ul + ptotstar * ustar) / (SL - ustar); + + // Right star region variables + const auto rstarr = rr * (SR - ur) / (SR - ustar); + const auto etotstarr = ((SR - ur) * etotr - ptotr * ur + ptotstar * ustar) / (SR - ustar); + + // Sample the solution at x/t=0 + real_t ro, uo, ptoto, etoto; + if (SL > ZERO_F) + { + ro = rl; + uo = ul; + ptoto = ptotl; + etoto = etotl; + } + else if (ustar > ZERO_F) + { + ro = rstarl; + uo = ustar; + ptoto = ptotstar; + etoto = etotstarl; + } + else if (SR > ZERO_F) + { + ro = rstarr; + uo = ustar; + ptoto = ptotstar; + etoto = etotstarr; + } + else + { + ro = rr; + uo = ur; + ptoto = ptotr; + etoto = etotr; + } + + // Compute the Godunov flux + flux[Hydro::ID] = ro * uo; + flux[Hydro::IU] = ro * uo * uo + ptoto; + flux[Hydro::IP] = (etoto + ptoto) * uo; + if (flux[Hydro::ID] > ZERO_F) + { + flux[Hydro::IV] = flux[Hydro::ID] * qL[Hydro::IV]; + } + else + { + flux[Hydro::IV] = flux[Hydro::ID] * qR[Hydro::IV]; + } + + if constexpr (dim == 3) + { + if (flux[Hydro::ID] > ZERO_F) + { + flux[Hydro::IW] = flux[Hydro::ID] * qL[Hydro::IW]; + } + else + { + flux[Hydro::IW] = flux[Hydro::ID] * qR[Hydro::IW]; + } + } + +} // riemann_hllc + +/** + * Riemann solver HLLC-LM + * + * This variant of HLLC has a low Mach correction for removing carbuncle instability problem + * that arise when there is a strong axis-aligned shock. + * + * @param[in] qL : input left state (primitive variables) + * @param[in] qR : input right state (primitive variables) + * @param[out] flux : output flux + * + * References: + * - A low dissipation method to cure the grid-aligned shock instability. Nico + * Fleischmann and Stefan Adami and Xiangyu Y. Hu and Nikolaus A. Adams, Journal of Computational + * Physics Volume 401, 15 January 2020, 109004. + * https://doi.org/10.1016/j.jcp.2019.109004 + * - A shock-stable modification of the HLLC Riemann solver with reduced numerical dissipation, Nico + * Fleischmann and Stefan Adami and Nikolaus A. Adams, Journal of Computational Physics + * Volume 423, 15 December 2020, 109762. + * https://doi.org/10.1016/j.jcp.2020.109762 + * - Development of an accurate and robust HLLC-type Riemann solver for all Mach number flows, Lijun + * Hu, Kexin Zhu, Lielong Li, Communications in Nonlinear Science and Numerical Simulation + * Volume 152, Part A, January 2026, 109178. + * https://doi.org/10.1016/j.cnsns.2025.109178 + */ +template +KOKKOS_INLINE_FUNCTION void +riemann_hllc_lm(HydroState const & qL, + HydroState const & qR, + HydroState & flux, + HydroSettings const & settings, + EosWrapper const & eos) +{ + // makes enum Hydro::VarId available + using Hydro = kalypsso::core::models::Hydro; + + auto const & smallr = settings.smallr; + auto const & smallp = settings.smallp; + auto const & smallc = settings.smallc; + + + HydroState UL, UR; + + // + // Left variables + // + UL[Hydro::ID] = fmax(qL[Hydro::ID], smallr); + auto & rl = UL[Hydro::ID]; + const auto pl = fmax(qL[Hydro::IP], rl * smallp); + UL[Hydro::IU] = rl * qL[Hydro::IU]; + auto const & ul = qL[Hydro::IU]; + + auto ecinl = HALF_F * rl * ul * ul; + ecinl += HALF_F * rl * qL[Hydro::IV] * qL[Hydro::IV]; + if constexpr (dim == 3) + ecinl += HALF_F * rl * qL[Hydro::IW] * qL[Hydro::IW]; + + UL[Hydro::IE] = eos.volumic_eint_from_pressure(pl, rl) + ecinl; + + UL[Hydro::IV] = rl * qL[Hydro::IV]; + if constexpr (dim == 3) + UL[Hydro::IW] = rl * qL[Hydro::IW]; + + // + // Right variables + // + UR[Hydro::ID] = fmax(qR[Hydro::ID], smallr); + auto & rr = UR[Hydro::ID]; + const auto pr = fmax(qR[Hydro::IP], rr * smallp); + UR[Hydro::IU] = rr * qR[Hydro::IU]; + auto const & ur = qR[Hydro::IU]; + + auto ecinr = HALF_F * rr * ur * ur; + ecinr += HALF_F * rr * qR[Hydro::IV] * qR[Hydro::IV]; + if constexpr (dim == 3) + ecinr += HALF_F * rr * qR[Hydro::IW] * qR[Hydro::IW]; + + UR[Hydro::IE] = eos.volumic_eint_from_pressure(pr, rr) + ecinr; + + UR[Hydro::IV] = rr * qR[Hydro::IV]; + if constexpr (dim == 3) + UR[Hydro::IW] = rr * qR[Hydro::IW]; + + // Find the largest eigenvalues in the normal direction to the interface + const auto cfastl = fmax(eos.sound_speed(pl, rl), smallc); + const auto cfastr = fmax(eos.sound_speed(pr, rr), smallc); + + // Compute HLL wave speed + const auto SL = fmin(ul, ur) - fmax(cfastl, cfastr); + const auto SR = fmax(ul, ur) + fmax(cfastl, cfastr); + const auto Sstar = + (pr - pl + rl * ul * (SL - ul) - rr * ur * (SR - ur)) / (rl * (SL - ul) - rr * (SR - ur)); + + HydroState UstarL = UL; + HydroState UstarR = UR; + + UstarL[Hydro::IU] = rl * Sstar; + UstarL[Hydro::IE] = UL[Hydro::IE] + (Sstar - ul) * (rl * Sstar + pl / (SL - ul)); + UstarL *= (SL - ul) / (SL - Sstar); + + UstarR[Hydro::IU] = rr * Sstar; + UstarR[Hydro::IE] = UR[Hydro::IE] + (Sstar - ur) * (rr * Sstar + pr / (SR - ur)); + UstarR *= (SR - ur) / (SR - Sstar); + + // Compute left and right fluxes + HydroState FL, FR; + FL[Hydro::ID] = UL[Hydro::IU]; + FR[Hydro::ID] = UR[Hydro::IU]; + FL[Hydro::IP] = qL[Hydro::IU] * (UL[Hydro::IP] + qL[Hydro::IP]); + FR[Hydro::IP] = qR[Hydro::IU] * (UR[Hydro::IP] + qR[Hydro::IP]); + FL[Hydro::IU] = qL[Hydro::IP] + UL[Hydro::IU] * qL[Hydro::IU]; + FR[Hydro::IU] = qR[Hydro::IP] + UR[Hydro::IU] * qR[Hydro::IU]; + + // Other advected quantities + FL[Hydro::IV] = FL[Hydro::ID] * qL[Hydro::IV]; + FR[Hydro::IV] = FR[Hydro::ID] * qR[Hydro::IV]; + if constexpr (dim == 3) + { + FL[Hydro::IW] = FL[Hydro::ID] * qL[Hydro::IW]; + FR[Hydro::IW] = FR[Hydro::ID] * qR[Hydro::IW]; + } + + if (SL >= 0) + { + flux = FL; + } + else if (SR <= 0) + { + flux = FR; + } + else + { + // sound speed + const auto cl = eos.sound_speed(pl, rl); + const auto cr = eos.sound_speed(pr, rr); + + // low Mach correction + const auto Ma_lim = KALYPSSO_NUM(0.1); + const auto Ma_loc = fmax(fabs(ul / cl), fabs(ur / cr)); + + // low Mach correction + const auto phi = sin(fmin(ONE_F, Ma_loc / Ma_lim) * PI_F * HALF_F); + + flux = + HALF_F * (FL + FR) + HALF_F * (phi * SL * (UstarL - UL) + fabs(Sstar) * (UstarL - UstarR) + + phi * SR * (UstarR - UR)); + } + +} // riemann_hllc_lm + +/** + * Wrapper function calling the actual riemann solver. + */ +template +KOKKOS_INLINE_FUNCTION void +riemann_hydro(HydroState const & qL, + HydroState const & qR, + HydroState & flux, + HydroSettings const & settings, + EosWrapper const & eos) +{ + + if (settings.riemannSolverType == +RiemannSolverType::APPROX) + { + riemann_approx(qL, qR, flux, settings, eos); + } + else if (settings.riemannSolverType == +RiemannSolverType::HLL) + { + riemann_hll(qL, qR, flux, settings, eos); + } + else if (settings.riemannSolverType == +RiemannSolverType::HLLC) + { + riemann_hllc(qL, qR, flux, settings, eos); + } + else if (settings.riemannSolverType == +RiemannSolverType::HLLC_LM) + { + riemann_hllc_lm(qL, qR, flux, settings, eos); + } + else if (settings.riemannSolverType == +RiemannSolverType::LLF) + { + riemann_llf(qL, qR, flux, settings, eos); + } + else + { + Kokkos::abort("Unknown Riemann solver type"); + } + +} // riemann_hydro + +/** + * Another wrapper function calling the actual riemann solver. + */ +template +KOKKOS_INLINE_FUNCTION HydroState + riemann_hydro(HydroState const & qL, + HydroState const & qR, + HydroSettings const & settings, + EosWrapper const & eos) +{ + + HydroState flux; + + if (settings.riemannSolverType == +RiemannSolverType::APPROX) + { + riemann_approx(qL, qR, flux, settings, eos); + } + else if (settings.riemannSolverType == +RiemannSolverType::HLL) + { + riemann_hll(qL, qR, flux, settings, eos); + } + else if (settings.riemannSolverType == +RiemannSolverType::HLLC) + { + riemann_hllc(qL, qR, flux, settings, eos); + } + else if (settings.riemannSolverType == +RiemannSolverType::HLLC_LM) + { + riemann_hllc_lm(qL, qR, flux, settings, eos); + } + else if (settings.riemannSolverType == +RiemannSolverType::LLF) + { + riemann_llf(qL, qR, flux, settings, eos); + } + else + { + Kokkos::abort("Unknown Riemann solver type"); + } + + return flux; + +} // riemann_hydro + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_MODELS_RIEMANN_SOLVERS_H_ diff --git a/src/kalypsso/core/models/RiemannSolvers_MHD.h b/src/kalypsso/core/models/RiemannSolvers_MHD.h new file mode 100644 index 0000000..dd0952a --- /dev/null +++ b/src/kalypsso/core/models/RiemannSolvers_MHD.h @@ -0,0 +1,2115 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file RiemannSolvers_MHD.h + * All possible Riemann solvers or so for MHD. + */ +#ifndef KALYPSSO_CORE_MODELS_RIEMANN_SOLVERS_MHD_H_ +#define KALYPSSO_CORE_MODELS_RIEMANN_SOLVERS_MHD_H_ + +#include + +#include +#include +#include +#include +#include +#include + +namespace kalypsso +{ + +using MagneticField_t = Kokkos::Array; + +// ==================================================================== +// ==================================================================== +/** + * MHD HLL Riemann solver + * + * qleft, qright and flux have now NVAR_MHD=8 components. + * + * \param[in] qleft : input left state + * \param[in] qright : input right state + * \param[out] flux : output flux + * \param[in] params : hydro parameters + * + */ +KOKKOS_INLINE_FUNCTION +void +riemann_hll(MHDStateCell & qleft, + MHDStateCell & qright, + MHDStateCell & flux, + MHDSettings const & settings) +{ + + // makes enum Hydro::VarId available + using MHD = kalypsso::core::models::MHD; + + // enforce continuity of normal component + real_t bx_mean = HALF_F * (qleft[MHD::IA] + qright[MHD::IA]); + + qleft[MHD::IA] = bx_mean; + qright[MHD::IA] = bx_mean; + + MHDStateCell uleft, fleft; + MHDStateCell uright, fright; + + core::models::mhd::find_mhd_flux(qleft, uleft, fleft, settings); + core::models::mhd::find_mhd_flux(qright, uright, fright, settings); + + // find the largest eigenvalue in the normal direction to the interface + real_t cfleft = core::models::mhd::find_speed_fast(qleft, settings); + real_t cfright = core::models::mhd::find_speed_fast(qright, settings); + + real_t vleft = qleft[MHD::IU]; + real_t vright = qright[MHD::IU]; + real_t sl = fmin(fmin(vleft, vright) - fmax(cfleft, cfright), ZERO_F); + real_t sr = fmax(fmax(vleft, vright) + fmax(cfleft, cfright), ZERO_F); + + // the hll flux + flux[MHD::ID] = + (sr * fleft[MHD::ID] - sl * fright[MHD::ID] + sr * sl * (uright[MHD::ID] - uleft[MHD::ID])) / + (sr - sl); + flux[MHD::IP] = + (sr * fleft[MHD::IP] - sl * fright[MHD::IP] + sr * sl * (uright[MHD::IP] - uleft[MHD::IP])) / + (sr - sl); + flux[MHD::IU] = + (sr * fleft[MHD::IU] - sl * fright[MHD::IU] + sr * sl * (uright[MHD::IU] - uleft[MHD::IU])) / + (sr - sl); + flux[MHD::IV] = + (sr * fleft[MHD::IV] - sl * fright[MHD::IV] + sr * sl * (uright[MHD::IV] - uleft[MHD::IV])) / + (sr - sl); + flux[MHD::IW] = + (sr * fleft[MHD::IW] - sl * fright[MHD::IW] + sr * sl * (uright[MHD::IW] - uleft[MHD::IW])) / + (sr - sl); + flux[MHD::IA] = + (sr * fleft[MHD::IA] - sl * fright[MHD::IA] + sr * sl * (uright[MHD::IA] - uleft[MHD::IA])) / + (sr - sl); + flux[MHD::IB] = + (sr * fleft[MHD::IB] - sl * fright[MHD::IB] + sr * sl * (uright[MHD::IB] - uleft[MHD::IB])) / + (sr - sl); + flux[MHD::IC] = + (sr * fleft[MHD::IC] - sl * fright[MHD::IC] + sr * sl * (uright[MHD::IC] - uleft[MHD::IC])) / + (sr - sl); + +} // riemann_hll + +// ==================================================================== +// ==================================================================== +/* + * MHD LLF (Local Lax-Friedrich) Riemann solver + * + * qleft, qright and flux have now NVAR_MHD=8 components. + * + * The following code is adapted from Dumses. + * + * \param[in] qleft : input left state + * \param[in] qright : input right state + * \param[out] flux : output flux + * \param[in] settings : hydro settings + * + */ +KOKKOS_INLINE_FUNCTION +void +riemann_llf(MHDStateCell & qleft, + MHDStateCell & qright, + MHDStateCell & flux, + MHDSettings const & settings) +{ + + // makes enum Hydro::VarId available + using MHD = kalypsso::core::models::MHD; + + // enforce continuity of normal component + real_t bx_mean = HALF_F * (qleft[MHD::IA] + qright[MHD::IA]); + qleft[MHD::IA] = bx_mean; + qright[MHD::IA] = bx_mean; + + MHDStateCell uleft, fleft; + MHDStateCell uright, fright; + + core::models::mhd::find_mhd_flux(qleft, uleft, fleft, settings); + core::models::mhd::find_mhd_flux(qright, uright, fright, settings); + + // compute mean flux + for (uint32_t iVar = 0; iVar < MHDStateCell::size(); iVar++) + flux[iVar] = (fleft[iVar] + fright[iVar]) / 2; + + // find the largest eigenvalue in the normal direction to the interface + real_t cleft = core::models::mhd::find_speed_info(qleft, settings); + real_t cright = core::models::mhd::find_speed_info(qright, settings); + + real_t vel_info = fmax(cleft, cright); + + // the Local Lax-Friedrich flux + for (uint32_t iVar = 0; iVar < MHDStateCell::size(); iVar++) + flux[iVar] -= vel_info * (uright[iVar] - uleft[iVar]) / 2; + +} // riemann_llf + +// ==================================================================== +// ==================================================================== +/** + * Riemann solver HLLC + * + * @param[in] qleft : input left state (primitive variables) + * @param[in] qright : input right state (primitive variables) + * @param[out] flux : output flux + */ +KOKKOS_INLINE_FUNCTION void +riemann_hllc(MHDStateCell const & qleft, + MHDStateCell const & qright, + MHDStateCell & flux, + MHDSettings const & settings) +{ + // makes enum Hydro::VarId available + using MHD = kalypsso::core::models::MHD; + + // UNUSED(qgdnv); + + real_t const & gamma0 = settings.hydro.gamma0; + real_t const & smallr = settings.hydro.smallr; + real_t const & smallp = settings.hydro.smallp; + real_t const & smallc = settings.hydro.smallc; + + real_t const entho = ONE_F / (gamma0 - ONE_F); + + // Left variables + real_t rl = fmax(qleft[MHD::ID], smallr); + real_t pl = fmax(qleft[MHD::IP], rl * smallp); + real_t ul = qleft[MHD::IU]; + + real_t ecinl = HALF_F * rl * ul * ul; + ecinl += HALF_F * rl * qleft[MHD::IV] * qleft[MHD::IV]; + ecinl += HALF_F * rl * qleft[MHD::IW] * qleft[MHD::IW]; + + real_t etotl = pl * entho + ecinl; + real_t ptotl = pl; + + // Right variables + real_t rr = fmax(qright[MHD::ID], smallr); + real_t pr = fmax(qright[MHD::IP], rr * smallp); + real_t ur = qright[MHD::IU]; + + real_t ecinr = HALF_F * rr * ur * ur; + ecinr += HALF_F * rr * qright[MHD::IV] * qright[MHD::IV]; + ecinr += HALF_F * rr * qright[MHD::IW] * qright[MHD::IW]; + + real_t etotr = pr * entho + ecinr; + real_t ptotr = pr; + + // Find the largest eigenvalues in the normal direction to the interface + real_t cfastl = sqrt(fmax(gamma0 * pl / rl, smallc * smallc)); + real_t cfastr = sqrt(fmax(gamma0 * pr / rr, smallc * smallc)); + + // Compute HLL wave speed + real_t SL = fmin(ul, ur) - fmax(cfastl, cfastr); + real_t SR = fmax(ul, ur) + fmax(cfastl, cfastr); + + // Compute lagrangian sound speed + real_t rcl = rl * (ul - SL); + real_t rcr = rr * (SR - ur); + + // Compute acoustic star state + real_t ustar = (rcr * ur + rcl * ul + (ptotl - ptotr)) / (rcr + rcl); + real_t ptotstar = (rcr * ptotl + rcl * ptotr + rcl * rcr * (ul - ur)) / (rcr + rcl); + + // Left star region variables + real_t rstarl = rl * (SL - ul) / (SL - ustar); + real_t etotstarl = ((SL - ul) * etotl - ptotl * ul + ptotstar * ustar) / (SL - ustar); + + // Right star region variables + real_t rstarr = rr * (SR - ur) / (SR - ustar); + real_t etotstarr = ((SR - ur) * etotr - ptotr * ur + ptotstar * ustar) / (SR - ustar); + + // Sample the solution at x/t=0 + real_t ro, uo, ptoto, etoto; + if (SL > ZERO_F) + { + ro = rl; + uo = ul; + ptoto = ptotl; + etoto = etotl; + } + else if (ustar > ZERO_F) + { + ro = rstarl; + uo = ustar; + ptoto = ptotstar; + etoto = etotstarl; + } + else if (SR > ZERO_F) + { + ro = rstarr; + uo = ustar; + ptoto = ptotstar; + etoto = etotstarr; + } + else + { + ro = rr; + uo = ur; + ptoto = ptotr; + etoto = etotr; + } + + // Compute the Godunov flux + flux[MHD::ID] = ro * uo; + flux[MHD::IU] = ro * uo * uo + ptoto; + flux[MHD::IP] = (etoto + ptoto) * uo; + if (flux[MHD::ID] > ZERO_F) + { + flux[MHD::IV] = flux[MHD::ID] * qleft[MHD::IV]; + } + else + { + flux[MHD::IV] = flux[MHD::ID] * qright[MHD::IV]; + } + + { + if (flux[MHD::ID] > ZERO_F) + { + flux[MHD::IW] = flux[MHD::ID] * qleft[MHD::IW]; + } + else + { + flux[MHD::IW] = flux[MHD::ID] * qright[MHD::IW]; + } + } + +} // riemann_hllc + +// ==================================================================== +// ==================================================================== +/** + * Riemann solver, equivalent to riemann_hlld in RAMSES/DUMSES (see file + * godunov_utils.f90 in RAMSES/DUMSES). + * + * Reference : + * + * Miyoshi & Kusano, 2005, JCP, 208, 315 + * + * \warning This version of HLLD integrates the pressure term in + * flux[MHD::IU] (as in RAMSES). This will need to be modified in the + * future (as it is done in DUMSES) to handle cylindrical / spherical + * coordinate systems. For example, one could add a new output named qStar + * to store star state, and that could be used to compute geometrical terms + * outside this routine. + * + * \param[in] qleft : input left state + * \param[in] qright : input right state + * \param[out] flux : output flux + * \param[in] params : hydro parameters + */ +KOKKOS_INLINE_FUNCTION +void +riemann_hlld(MHDStateCell & qleft, + MHDStateCell & qright, + MHDStateCell & flux, + MHDSettings const & settings) +{ + + // makes enum Hydro::VarId available + using MHD = kalypsso::core::models::MHD; + + // Constants + const real_t gamma0 = settings.hydro.gamma0; + const real_t entho = ONE_F / (gamma0 - ONE_F); + + // Enforce continuity of normal component of magnetic field + real_t a = HALF_F * (qleft[MHD::IA] + qright[MHD::IA]); + real_t sgnm = (a >= 0) ? ONE_F : -ONE_F; + + qleft[MHD::IA] = a; + qright[MHD::IA] = a; + + // ISOTHERMAL + real_t cIso = settings.hydro.cIso; + if (cIso > 0) + { + // recompute pressure + qleft[MHD::IP] = qleft[MHD::ID] * cIso * cIso; + qright[MHD::IP] = qright[MHD::ID] * cIso * cIso; + } // end ISOTHERMAL + + // left variables + real_t rl, pl, ul, vl, wl, bl, cl; + rl = qleft[MHD::ID]; // rl = fmax(qleft[MHD::ID], static_cast(gParams.smallr) ); + pl = qleft[MHD::IP]; // pl = fmax(qleft[MHD::IP], static_cast(rl*gParams.smallp) ); + ul = qleft[MHD::IU]; + vl = qleft[MHD::IV]; + wl = qleft[MHD::IW]; + bl = qleft[MHD::IB]; + cl = qleft[MHD::IC]; + real_t ecinl = HALF_F * (ul * ul + vl * vl + wl * wl) * rl; + real_t emagl = HALF_F * (a * a + bl * bl + cl * cl); + real_t etotl = pl * entho + ecinl + emagl; + real_t ptotl = pl + emagl; + real_t vdotbl = ul * a + vl * bl + wl * cl; + + // right variables + real_t rr, pr, ur, vr, wr, br, cr; + rr = qright[MHD::ID]; // rr = fmax(qright[MHD::ID], static_cast( gParams.smallr) ); + pr = qright[MHD::IP]; // pr = fmax(qright[MHD::IP], static_cast( rr*gParams.smallp) ); + ur = qright[MHD::IU]; + vr = qright[MHD::IV]; + wr = qright[MHD::IW]; + br = qright[MHD::IB]; + cr = qright[MHD::IC]; + real_t ecinr = HALF_F * (ur * ur + vr * vr + wr * wr) * rr; + real_t emagr = HALF_F * (a * a + br * br + cr * cr); + real_t etotr = pr * entho + ecinr + emagr; + real_t ptotr = pr + emagr; + real_t vdotbr = ur * a + vr * br + wr * cr; + + // find the largest eigenvalues in the normal direction to the interface + real_t cfastl = core::models::mhd::find_speed_fast(qleft, settings); + real_t cfastr = core::models::mhd::find_speed_fast(qright, settings); + + // compute hll wave speed + real_t sl = fmin(ul, ur) - fmax(cfastl, cfastr); + real_t sr = fmax(ul, ur) + fmax(cfastl, cfastr); + + // compute lagrangian sound speed + real_t rcl = rl * (ul - sl); + real_t rcr = rr * (sr - ur); + + // compute acoustic star state + real_t ustar = (rcr * ur + rcl * ul + (ptotl - ptotr)) / (rcr + rcl); + real_t ptotstar = (rcr * ptotl + rcl * ptotr + rcl * rcr * (ul - ur)) / (rcr + rcl); + + // left star region variables + real_t estar; + real_t rstarl, el; + rstarl = rl * (sl - ul) / (sl - ustar); + estar = rl * (sl - ul) * (sl - ustar) - a * a; + el = rl * (sl - ul) * (sl - ul) - a * a; + real_t vstarl, wstarl; + real_t bstarl, cstarl; + + if (fabs(estar) < KALYPSSO_NUM(1e-4) * (a * a)) + { + vstarl = vl; + bstarl = bl; + wstarl = wl; + cstarl = cl; + } + else + { + vstarl = vl - a * bl * (ustar - ul) / estar; + bstarl = bl * el / estar; + wstarl = wl - a * cl * (ustar - ul) / estar; + cstarl = cl * el / estar; + } + real_t vdotbstarl = ustar * a + vstarl * bstarl + wstarl * cstarl; + real_t etotstarl = + ((sl - ul) * etotl - ptotl * ul + ptotstar * ustar + a * (vdotbl - vdotbstarl)) / (sl - ustar); + real_t sqrrstarl = sqrt(rstarl); + real_t calfvenl = fabs(a) / sqrrstarl; /* sqrrstarl should never be zero, but it might happen if + border conditions are not OK !!!!!! */ + real_t sal = ustar - calfvenl; + + // right star region variables + real_t rstarr, er; + rstarr = rr * (sr - ur) / (sr - ustar); + estar = rr * (sr - ur) * (sr - ustar) - a * a; + er = rr * (sr - ur) * (sr - ur) - a * a; + real_t vstarr, wstarr; + real_t bstarr, cstarr; + + if (fabs(estar) < KALYPSSO_NUM(1e-4) * (a * a)) + { + vstarr = vr; + bstarr = br; + wstarr = wr; + cstarr = cr; + } + else + { + vstarr = vr - a * br * (ustar - ur) / estar; + bstarr = br * er / estar; + wstarr = wr - a * cr * (ustar - ur) / estar; + cstarr = cr * er / estar; + } + real_t vdotbstarr = ustar * a + vstarr * bstarr + wstarr * cstarr; + real_t etotstarr = + ((sr - ur) * etotr - ptotr * ur + ptotstar * ustar + a * (vdotbr - vdotbstarr)) / (sr - ustar); + real_t sqrrstarr = sqrt(rstarr); + real_t calfvenr = fabs(a) / sqrrstarr; /* sqrrstarr should never be zero, but it might happen if + border conditions are not OK !!!!!! */ + real_t sar = ustar + calfvenr; + + // double star region variables + real_t vstarstar = + (sqrrstarl * vstarl + sqrrstarr * vstarr + sgnm * (bstarr - bstarl)) / (sqrrstarl + sqrrstarr); + real_t wstarstar = + (sqrrstarl * wstarl + sqrrstarr * wstarr + sgnm * (cstarr - cstarl)) / (sqrrstarl + sqrrstarr); + real_t bstarstar = + (sqrrstarl * bstarr + sqrrstarr * bstarl + sgnm * sqrrstarl * sqrrstarr * (vstarr - vstarl)) / + (sqrrstarl + sqrrstarr); + real_t cstarstar = + (sqrrstarl * cstarr + sqrrstarr * cstarl + sgnm * sqrrstarl * sqrrstarr * (wstarr - wstarl)) / + (sqrrstarl + sqrrstarr); + real_t vdotbstarstar = ustar * a + vstarstar * bstarstar + wstarstar * cstarstar; + real_t etotstarstarl = etotstarl - sgnm * sqrrstarl * (vdotbstarl - vdotbstarstar); + real_t etotstarstarr = etotstarr + sgnm * sqrrstarr * (vdotbstarr - vdotbstarstar); + + // sample the solution at x/t=0 + real_t ro, uo, vo, wo, bo, co, ptoto, etoto, vdotbo; + if (sl > 0) + { // flow is supersonic, return upwind variables + ro = rl; + uo = ul; + vo = vl; + wo = wl; + bo = bl; + co = cl; + ptoto = ptotl; + etoto = etotl; + vdotbo = vdotbl; + } + else if (sal > 0) + { + ro = rstarl; + uo = ustar; + vo = vstarl; + wo = wstarl; + bo = bstarl; + co = cstarl; + ptoto = ptotstar; + etoto = etotstarl; + vdotbo = vdotbstarl; + } + else if (ustar > 0) + { + ro = rstarl; + uo = ustar; + vo = vstarstar; + wo = wstarstar; + bo = bstarstar; + co = cstarstar; + ptoto = ptotstar; + etoto = etotstarstarl; + vdotbo = vdotbstarstar; + } + else if (sar > 0) + { + ro = rstarr; + uo = ustar; + vo = vstarstar; + wo = wstarstar; + bo = bstarstar; + co = cstarstar; + ptoto = ptotstar; + etoto = etotstarstarr; + vdotbo = vdotbstarstar; + } + else if (sr > 0) + { + ro = rstarr; + uo = ustar; + vo = vstarr; + wo = wstarr; + bo = bstarr; + co = cstarr; + ptoto = ptotstar; + etoto = etotstarr; + vdotbo = vdotbstarr; + } + else + { // flow is supersonic, return upwind variables + ro = rr; + uo = ur; + vo = vr; + wo = wr; + bo = br; + co = cr; + ptoto = ptotr; + etoto = etotr; + vdotbo = vdotbr; + } + + // compute the godunov flux + flux[MHD::ID] = ro * uo; + flux[MHD::IP] = (etoto + ptoto) * uo - a * vdotbo; + flux[MHD::IU] = + ro * uo * uo - a * a + + ptoto; /* *** WARNING *** : ptoto used here (this is only valid for cartesian geometry) ! */ + flux[MHD::IV] = ro * uo * vo - a * bo; + flux[MHD::IW] = ro * uo * wo - a * co; + flux[MHD::IA] = ZERO_F; + flux[MHD::IB] = bo * uo - a * vo; + flux[MHD::IC] = co * uo - a * wo; + +} // riemann_hlld + +// ==================================================================== +// ==================================================================== +/** + * Riemann solver 5 waves + * + * Reference : + * https://www.sciencedirect.com/science/article/pii/S0021999124007034 + * Tremblin, Pascal, et al. "A multi-dimensional, robust, and cell-centered finite-volume scheme for + * the ideal MHD equations. " Journal of Computational Physics 519 (2024): 113455. + * + * \param[in] qleft : input left state + * \param[in] qright : input right state + * \param[out] flux : output flux + * \param[in] params : hydro parameters + */ +KOKKOS_INLINE_FUNCTION +void +riemann_5_waves(MHDStateCell & qleft, + MHDStateCell & qright, + MHDStateCell & flux, + MHDSettings const & settings) +{ + + // makes enum Hydro::VarId available + using MHD = kalypsso::core::models::MHD; + // Constants + const real_t gamma0 = settings.hydro.gamma0; + const real_t entho = ONE_F / (gamma0 - ONE_F); + + // left variables + real_t rl, pl, ul, vl, wl, al, bl, cl; + rl = qleft[MHD::ID]; + pl = qleft[MHD::IP]; + ul = qleft[MHD::IU]; + vl = qleft[MHD::IV]; + wl = qleft[MHD::IW]; + al = qleft[MHD::IA]; + bl = qleft[MHD::IB]; + cl = qleft[MHD::IC]; + real_t ecinl = HALF_F * (ul * ul + vl * vl + wl * wl) * rl; + real_t emagl = HALF_F * (al * al + bl * bl + cl * cl); + real_t etotl = pl * entho + ecinl + emagl; + real_t ptotl = pl + emagl; + real_t cs2l = gamma0 * (pl / rl); // square sound speed + + + // Definition and calculation of the relaxed pressure and the relaxation parameters + Kokkos::Array pRelaxL, cRelaxL; + + pRelaxL[IX] = ptotl - al * al; + pRelaxL[IY] = -al * bl; + pRelaxL[IZ] = -al * cl; + + cRelaxL[IX] = sqrt(rl * (rl * cs2l + ((bl * bl) + (cl * cl) + emagl))); + cRelaxL[IY] = sqrt(rl * ((al * al) + emagl)) + 1e-14; + cRelaxL[IZ] = sqrt(rl * ((al * al) + emagl)) + 1e-14; + + + // right variables + real_t rr, pr, ur, vr, wr, ar, br, cr; + rr = qright[MHD::ID]; + pr = qright[MHD::IP]; + ur = qright[MHD::IU]; + vr = qright[MHD::IV]; + wr = qright[MHD::IW]; + ar = qright[MHD::IA]; + br = qright[MHD::IB]; + cr = qright[MHD::IC]; + real_t ecinr = HALF_F * (ur * ur + vr * vr + wr * wr) * rr; + real_t emagr = HALF_F * (ar * ar + br * br + cr * cr); + real_t etotr = pr * entho + ecinr + emagr; + real_t ptotr = pr + emagr; + real_t cs2r = gamma0 * (pr / rr); // square sound speed + + // Definition and calculation of the relaxed pressure and the relaxation parameters + Kokkos::Array pRelaxR, cRelaxR; + + pRelaxR[IX] = ptotr - ar * ar; + pRelaxR[IY] = -ar * br; + pRelaxR[IZ] = -ar * cr; + + cRelaxR[IX] = sqrt(rr * (rr * cs2r + ((br * br) + (cr * cr) + emagr))); + cRelaxR[IY] = sqrt(rr * ((ar * ar) + emagr)) + 1e-14; + cRelaxR[IZ] = sqrt(rr * ((ar * ar) + emagr)) + 1e-14; + + // Calculation of ustar and pstar + Kokkos::Array ustar, pstar; + + ustar[IX] = + (cRelaxL[IX] * ul + cRelaxR[IX] * ur + pRelaxL[IX] - pRelaxR[IX]) / (cRelaxL[IX] + cRelaxR[IX]); + ustar[IY] = + (cRelaxL[IY] * vl + cRelaxR[IY] * vr + pRelaxL[IY] - pRelaxR[IY]) / (cRelaxL[IY] + cRelaxR[IY]); + ustar[IZ] = + (cRelaxL[IZ] * wl + cRelaxR[IZ] * wr + pRelaxL[IZ] - pRelaxR[IZ]) / (cRelaxL[IZ] + cRelaxR[IZ]); + + // const real_t Ma_norm = fabs(ustar[IX])/fmin(sqrt(cs2l), sqrt(cs2r)); + + // const real_t theta_norm = ONE_F;//fmin(Ma_norm, ONE_F); + + pstar[IX] = (cRelaxR[IX] * pRelaxL[IX] + cRelaxL[IX] * pRelaxR[IX] + + cRelaxL[IX] * cRelaxR[IX] * (ul - ur)) / + (cRelaxL[IX] + cRelaxR[IX]); + pstar[IY] = (cRelaxR[IY] * pRelaxL[IY] + cRelaxL[IY] * pRelaxR[IY] + + cRelaxL[IY] * cRelaxR[IY] * (vl - vr)) / + (cRelaxL[IY] + cRelaxR[IY]); + pstar[IZ] = (cRelaxR[IZ] * pRelaxL[IZ] + cRelaxL[IZ] * pRelaxR[IZ] + + cRelaxL[IZ] * cRelaxR[IZ] * (wl - wr)) / + (cRelaxL[IZ] + cRelaxR[IZ]); + + + real_t pdotu = ustar[IX] * pstar[IX] + ustar[IY] * pstar[IY] + ustar[IZ] * pstar[IZ]; + + MHDStateCell qStencil; + real_t B_next; + real_t E_next; + + if (ustar[IX] > ZERO_F) + { + qStencil = qleft; + B_next = ar; + E_next = etotl; + } + else + { + qStencil = qright; + B_next = al; + E_next = etotr; + } + + // compute the godunov flux + flux[MHD::ID] = qStencil[MHD::ID] * ustar[IX]; + flux[MHD::IU] = qStencil[MHD::ID] * qStencil[MHD::IU] * ustar[IX] + pstar[IX]; + flux[MHD::IV] = qStencil[MHD::ID] * qStencil[MHD::IV] * ustar[IX] + pstar[IY]; + flux[MHD::IW] = qStencil[MHD::ID] * qStencil[MHD::IW] * ustar[IX] + pstar[IZ]; + flux[MHD::IA] = qStencil[MHD::IA] * ustar[IX] - B_next * ustar[IX]; + flux[MHD::IB] = qStencil[MHD::IB] * ustar[IX] - B_next * ustar[IY]; + flux[MHD::IC] = qStencil[MHD::IC] * ustar[IX] - B_next * ustar[IZ]; + flux[MHD::IP] = E_next * ustar[IX] + pdotu; + + +} // riemann_5_waves + +// ==================================================================== +// ==================================================================== +/** + * Riemann solver 5 waves with entropy correction + * + * Reference : + * https://www.sciencedirect.com/science/article/pii/S0021999124007034 + * Tremblin, Pascal, et al. "A multi-dimensional, robust, and cell-centered finite-volume scheme for + * the ideal MHD equations. " Journal of Computational Physics 519 (2024): 113455. + * + * \param[in] qleft : input left state + * \param[in] qright : input right state + * \param[out] flux : output flux + * \param[in] params : hydro parameters + */ +KOKKOS_INLINE_FUNCTION +void +riemann_5_waves_entropy(MHDStateCell & qleft, + MHDStateCell & qright, + MHDStateCell & flux, + MHDSettings const & settings) +{ + // makes enum Hydro::VarId available + using MHD = kalypsso::core::models::MHD; + // Constants + const real_t gamma0 = settings.hydro.gamma0; + const real_t entho = ONE_F / (gamma0 - ONE_F); + + // left variables + real_t rl, pl, ul, vl, wl, al, bl, cl; + rl = qleft[MHD::ID]; + pl = qleft[MHD::IP]; + ul = qleft[MHD::IU]; + vl = qleft[MHD::IV]; + wl = qleft[MHD::IW]; + al = qleft[MHD::IA]; + bl = qleft[MHD::IB]; + cl = qleft[MHD::IC]; + real_t ecinl = HALF_F * (ul * ul + vl * vl + wl * wl) * rl; + real_t emagl = HALF_F * (al * al + bl * bl + cl * cl); + real_t etotl = pl * entho + ecinl + emagl; + real_t ptotl = pl + emagl; + real_t cs2l = gamma0 * (pl / rl); // square sound speed + + + // Definition and calculation of the relaxed pressure and the relaxation parameters + Kokkos::Array pRelaxL, cRelaxL; + + pRelaxL[IX] = ptotl - (al * al); + pRelaxL[IY] = -(al * bl); + pRelaxL[IZ] = -(al * cl); + + cRelaxL[IX] = sqrt(rl * (rl * cs2l + ((bl * bl) + (cl * cl) + emagl))); + cRelaxL[IY] = sqrt(rl * ((al * al) + emagl)) + 1e-14; + cRelaxL[IZ] = sqrt(rl * ((al * al) + emagl)) + 1e-14; + + + // right variables + real_t rr, pr, ur, vr, wr, ar, br, cr; + rr = qright[MHD::ID]; + pr = qright[MHD::IP]; + ur = qright[MHD::IU]; + vr = qright[MHD::IV]; + wr = qright[MHD::IW]; + ar = qright[MHD::IA]; + br = qright[MHD::IB]; + cr = qright[MHD::IC]; + real_t ecinr = HALF_F * (ur * ur + vr * vr + wr * wr) * rr; + real_t emagr = HALF_F * (ar * ar + br * br + cr * cr); + real_t etotr = pr * entho + ecinr + emagr; + real_t ptotr = pr + emagr; + real_t cs2r = gamma0 * (pr / rr); // square sound speed + + // Definition and calculation of the relaxed pressure and the relaxation parameters + Kokkos::Array pRelaxR, cRelaxR; + + pRelaxR[IX] = ptotr - (ar * ar); + pRelaxR[IY] = -(ar * br); + pRelaxR[IZ] = -(ar * cr); + + cRelaxR[IX] = sqrt(rr * (rr * cs2r + ((br * br) + (cr * cr) + emagr))); + cRelaxR[IY] = sqrt(rr * ((ar * ar) + emagr)) + 1e-14; + cRelaxR[IZ] = sqrt(rr * ((ar * ar) + emagr)) + 1e-14; + + // Calculation of ustar and pstar + Kokkos::Array ustar, pstar; + + ustar[IX] = + (cRelaxL[IX] * ul + cRelaxR[IX] * ur + pRelaxL[IX] - pRelaxR[IX]) / (cRelaxL[IX] + cRelaxR[IX]); + ustar[IY] = + (cRelaxL[IY] * vl + cRelaxR[IY] * vr + pRelaxL[IY] - pRelaxR[IY]) / (cRelaxL[IY] + cRelaxR[IY]); + ustar[IZ] = + (cRelaxL[IZ] * wl + cRelaxR[IZ] * wr + pRelaxL[IZ] - pRelaxR[IZ]) / (cRelaxL[IZ] + cRelaxR[IZ]); + + // const real_t Ma_norm = fabs(ustar[IX])/fmin(sqrt(cs2l), sqrt(cs2r)); + + // const real_t theta_norm = ONE_F;//fmin(Ma_norm, ONE_F); + + pstar[IX] = (cRelaxR[IX] * pRelaxL[IX] + cRelaxL[IX] * pRelaxR[IX] + + cRelaxL[IX] * cRelaxR[IX] * (ul - ur)) / + (cRelaxL[IX] + cRelaxR[IX]); + pstar[IY] = (cRelaxR[IY] * pRelaxL[IY] + cRelaxL[IY] * pRelaxR[IY] + + cRelaxL[IY] * cRelaxR[IY] * (vl - vr)) / + (cRelaxL[IY] + cRelaxR[IY]); + pstar[IZ] = (cRelaxR[IZ] * pRelaxL[IZ] + cRelaxL[IZ] * pRelaxR[IZ] + + cRelaxL[IZ] * cRelaxR[IZ] * (wl - wr)) / + (cRelaxL[IZ] + cRelaxR[IZ]); + + + real_t pdotu = ustar[IX] * pstar[IX] + ustar[IY] * pstar[IY] + ustar[IZ] * pstar[IZ]; + + MHDStateCell qStencil; + real_t B_next; + real_t E_next; + + if (ustar[IX] > ZERO_F) + { + qStencil = qleft; + B_next = ar; + E_next = etotl; + } + else + { + qStencil = qright; + B_next = al; + E_next = etotr; + } + + // auto beta = pr / emagr; + // auto alfven = sqrt(abs(ecinr) / abs(emagr)); + const auto beta = fmin(pl / emagl, pr / emagr); + const auto alfven = fmax(sqrt(ecinl / emagl), sqrt(ecinr / emagr)); + + if (beta < settings.small_beta || alfven > settings.large_alfven) + { + + // compute the godunov flux + flux[MHD::ID] = qStencil[MHD::ID] * ustar[IX]; + flux[MHD::IU] = qStencil[MHD::ID] * qStencil[MHD::IU] * ustar[IX] + pstar[IX]; + flux[MHD::IV] = qStencil[MHD::ID] * qStencil[MHD::IV] * ustar[IX] + pstar[IY]; + flux[MHD::IW] = qStencil[MHD::ID] * qStencil[MHD::IW] * ustar[IX] + pstar[IZ]; + flux[MHD::IA] = qStencil[MHD::IA] * ustar[IX]; + flux[MHD::IB] = qStencil[MHD::IB] * ustar[IX]; + flux[MHD::IC] = qStencil[MHD::IC] * ustar[IX]; + flux[MHD::IP] = E_next * ustar[IX] + pdotu; + } + else + { + // compute the godunov flux + flux[MHD::ID] = qStencil[MHD::ID] * ustar[IX]; + flux[MHD::IU] = qStencil[MHD::ID] * qStencil[MHD::IU] * ustar[IX] + pstar[IX]; + flux[MHD::IV] = qStencil[MHD::ID] * qStencil[MHD::IV] * ustar[IX] + pstar[IY]; + flux[MHD::IW] = qStencil[MHD::ID] * qStencil[MHD::IW] * ustar[IX] + pstar[IZ]; + flux[MHD::IA] = qStencil[MHD::IA] * ustar[IX] - B_next * ustar[IX]; + flux[MHD::IB] = qStencil[MHD::IB] * ustar[IX] - B_next * ustar[IY]; + flux[MHD::IC] = qStencil[MHD::IC] * ustar[IX] - B_next * ustar[IZ]; + flux[MHD::IP] = E_next * ustar[IX] + pdotu; + } +} // riemann_5_waves_entropy + +// ==================================================================== +// ==================================================================== +/** + * Riemann solver, HLLD with Boris correction + * + * Reference : [Matsumoto2019] + * + * Matsumoto, T., Miyoshi, T., & Takasao, S. (2019). + * + * \param[in] qleft : input left state + * \param[in] qright : input right state + * \param[out] flux : output flux + * \param[in] params : hydro parameters + */ +KOKKOS_INLINE_FUNCTION +void +riemann_hlld_boris(MHDStateCell & qleft, + MHDStateCell & qright, + MHDStateCell & flux, + MHDSettings const & settings) +{ + + // makes enum Hydro::VarId available + using MHD = kalypsso::core::models::MHD; + + // Constants + const real_t gamma0 = settings.hydro.gamma0; + const real_t entho = ONE_F / (gamma0 - ONE_F); + const real_t cboris2 = settings.cboris * settings.cboris; + + // Enforce continuity of normal component of magnetic field + real_t a = HALF_F * (qleft[MHD::IA] + qright[MHD::IA]); + real_t sgnm = (a >= 0) ? ONE_F : -ONE_F; + + qleft[MHD::IA] = a; + qright[MHD::IA] = a; + + // ISOTHERMAL + real_t cIso = settings.hydro.cIso; + if (cIso > 0) + { + // recompute pressure + qleft[MHD::IP] = qleft[MHD::ID] * cIso * cIso; + qright[MHD::IP] = qright[MHD::ID] * cIso * cIso; + } // end ISOTHERMAL + + // left variables + real_t rl, pl, ul, vl, wl, bl, cl, ga2l; + ga2l = core::models::mhd::compute_Boris(qleft, settings); + rl = qleft[MHD::ID]; // rl = fmax(qleft[Hydro::ID], static_cast(gParams.smallr) ); + pl = qleft[MHD::IP]; // pl = fmax(qleft[Hydro::IP], static_cast(rl*gParams.smallp) ); + ul = qleft[MHD::IU] * ga2l; + vl = qleft[MHD::IV] * ga2l; + wl = qleft[MHD::IW] * ga2l; + bl = qleft[MHD::IB]; + cl = qleft[MHD::IC]; + + real_t ecinl = HALF_F * (ul * ul + vl * vl + wl * wl) * rl; + real_t emagl = HALF_F * (a * a + bl * bl + cl * cl); + real_t etotl = pl * entho + ecinl + emagl; + real_t ptotl = pl + emagl; + real_t vdotbl = ul * a + vl * bl + wl * cl; + + // right variables + real_t rr, pr, ur, vr, wr, br, cr, ga2r; + ga2r = core::models::mhd::compute_Boris(qright, settings); + rr = qright[MHD::ID]; // rr = fmax(qright[Hydro::ID], static_cast( gParams.smallr) ); + pr = qright[MHD::IP]; // pr = fmax(qright[Hydro::IP], static_cast( rr*gParams.smallp) ); + ur = qright[MHD::IU] * ga2r; + vr = qright[MHD::IV] * ga2r; + wr = qright[MHD::IW] * ga2r; + br = qright[MHD::IB]; + cr = qright[MHD::IC]; + real_t ecinr = HALF_F * (ur * ur + vr * vr + wr * wr) * rr; + real_t emagr = HALF_F * (a * a + br * br + cr * cr); + real_t etotr = pr * entho + ecinr + emagr; + real_t ptotr = pr + emagr; + real_t vdotbr = ur * a + vr * br + wr * cr; + + // find the largest eigenvalues in the normal direction to the interface + real_t cfastl = core::models::mhd::find_speed_fast_boris(qleft, settings); + real_t cfastr = core::models::mhd::find_speed_fast_boris(qright, settings); + + // compute hll wave speed + real_t sl = fmin(ul, ur) - fmax(cfastl, cfastr); + real_t sr = fmax(ul, ur) + fmax(cfastl, cfastr); + + // HLL average used to calculate Sm equation (41) in [Matsumoto2019] + real_t rhll = ((sr * rr) - (sl * rl) - (rr * ur) + (rl * ul)) / (sr - sl); + real_t ahll = a; + + real_t Fbl = ul * bl - vl * a; + real_t Fbr = ur * br - vr * a; + real_t Fcl = ul * cl - wl * a; + real_t Fcr = ur * cr - wr * a; + + real_t bhll = ((sr * br) - (sl * bl) - Fbr + Fbl) / (sr - sl); + real_t chll = ((sr * cr) - (sl * cl) - Fcr + Fcl) / (sr - sl); + real_t Vahll = ((ahll * ahll) + (bhll * bhll) + (chll * chll)) / rhll; + + real_t ga2hll = ONE_F / (ONE_F + Vahll / cboris2); + + // compute acoustic star state + real_t sm = (((sr / ga2r - ur) * rr * ur - (sl / ga2l - ul) * rl * ul - ptotr + ptotl) * ga2hll) / + ((sr - ur) * rr - (sl - ul) * rl); + + real_t ptotstarl = + ptotl - (sl / ga2l - ul) * rl * ul + (sl / ga2l - sm) * rl * sm * (sl - ul) / (sl - sm); + real_t ptotstarr = + ptotr - (sr / ga2r - ur) * rr * ur + (sr / ga2r - sm) * rr * sm * (sr - ur) / (sr - sm); + + // left star region variable + real_t rstarl = rl * (sl - ul) / (sl - sm); + real_t estar, estarup, el; + estar = rl * (sl - ul) * (sl / ga2l - sm) - a * a; + estarup = rl * (sl / ga2l - ul) * (sl - sm) - a * a; + el = rl * (sl - ul) * (sl - ul) * (sl / ga2l - sm) / (sl - sm) - a * a; + + real_t vstarl, wstarl; + real_t bstarl, cstarl; + if (fabs(estar) < KALYPSSO_NUM(1e-4) * (a * a)) + { + vstarl = vl; + bstarl = bl; + wstarl = wl; + cstarl = cl; + } + else + { + vstarl = (estarup * vl - a * bl * (sm - ul)) / estar; + wstarl = (estarup * wl - a * cl * (sm - ul)) / estar; + bstarl = bl * el / estar + (ONE_F - estarup / estar) * vl * a / (sl - sm); + cstarl = cl * el / estar + (ONE_F - estarup / estar) * wl * a / (sl - sm); + } + real_t vdotbstarl = sm * a + vstarl * bstarl + wstarl * cstarl; + real_t etotstarl = + ((sl - ul) * etotl - ptotl * ul + ptotstarl * sm + a * (vdotbl - vdotbstarl)) / (sl - sm); + // real_t sqrrstarl = sqrt(ga2l / rstarl); + // real_t calfvenl = fabs(a) / sqrrstarl; + real_t sal = sm - fabs(a) * sqrt(ga2l / rstarl); + + + // right star region variable + real_t rstarr = rr * (sr - ur) / (sr - sm); + real_t er; + estar = rr * (sr - ur) * (sr / ga2r - sm) - a * a; + estarup = rr * (sr / ga2r - ur) * (sr - sm) - a * a; + er = rr * (sr - ur) * (sr - ur) * (sr / ga2r - sm) / (sr - sm) - a * a; + + real_t vstarr, wstarr; + real_t bstarr, cstarr; + if (fabs(estar) < KALYPSSO_NUM(1e-4) * (a * a)) + { + vstarr = vr; + bstarr = br; + wstarr = wr; + cstarr = cr; + } + else + { + vstarr = (estarup * vr - a * br * (sm - ur)) / estar; + wstarr = (estarup * wr - a * cr * (sm - ur)) / estar; + bstarr = br * er / estar + (ONE_F - estarup / estar) * vr * a / (sr - sm); + cstarr = cr * er / estar + (ONE_F - estarup / estar) * wr * a / (sr - sm); + } + real_t vdotbstarr = sm * a + vstarr * bstarr + wstarr * cstarr; + real_t etotstarr = + ((sr - ur) * etotr - ptotr * ur + ptotstarr * sm + a * (vdotbr - vdotbstarr)) / (sr - sm); + // real_t sqrrstarl = sqrt(ga2l / rstarl); + // real_t calfvenl = fabs(a) / sqrrstarl; + + real_t sar = sm + fabs(a) * sqrt(ga2r / rstarr); + + // double star region variables + real_t vstarstar = + (sqrt(rstarl / ga2l) * vstarl + sqrt(rstarr / ga2r) * vstarr + sgnm * (bstarr - bstarl) + + (ONE_F - ONE_F / ga2l) * sm * rstarl * vstarl / fabs(a) - + (ONE_F - ONE_F / ga2r) * sm * rstarr * vstarr / fabs(a)) / + (sqrt(rstarl / ga2l) + sqrt(rstarr / ga2r)); + + real_t wstarstar = + (sqrt(rstarl / ga2l) * wstarl + sqrt(rstarr / ga2r) * wstarr + sgnm * (cstarr - cstarl) + + (ONE_F - ONE_F / ga2l) * sm * rstarl * wstarl / fabs(a) - + (ONE_F - ONE_F / ga2r) * sm * rstarr * wstarr / fabs(a)) / + (sqrt(rstarl / ga2l) + sqrt(rstarr / ga2r)); + + real_t bstarstar = (sqrt(ga2r * rstarl) * bstarr + sqrt(ga2l * rstarr) * bstarl + + sqrt(rstarl * rstarr) * (vstarr - vstarl) * sgnm) / + (sqrt(ga2r * rstarl) + sqrt(ga2l * rstarr)); + + real_t cstarstar = (sqrt(ga2r * rstarl) * cstarr + sqrt(ga2l * rstarr) * cstarl + + sqrt(rstarl * rstarr) * (wstarr - wstarl) * sgnm) / + (sqrt(ga2r * rstarl) + sqrt(ga2l * rstarr)); + + real_t vdotbstarstar = sm * a + vstarstar * bstarstar + wstarstar * cstarstar; + + real_t etotstarstarl = etotstarl - sgnm * sqrt(rstarl / ga2l) * (vdotbstarl - vdotbstarstar); + real_t etotstarstarr = etotstarr + sgnm * sqrt(rstarr / ga2r) * (vdotbstarr - vdotbstarstar); + + // sample the solution at x/t=0 + real_t ro, uo, vo, wo, bo, co, ptoto, etoto, vdotbo; + if (sl > 0) + { // flow is supersonic, return upwind variables + ro = rl; + uo = ul; + vo = vl; + wo = wl; + bo = bl; + co = cl; + ptoto = ptotl; + etoto = etotl; + vdotbo = vdotbl; + } + else if (sal > 0) + { + ro = rstarl; + uo = sm; + vo = vstarl; + wo = wstarl; + bo = bstarl; + co = cstarl; + ptoto = ptotstarl; + etoto = etotstarl; + vdotbo = vdotbstarl; + } + else if (sm > 0) + { + ro = rstarl; + uo = sm; + vo = vstarstar; + wo = wstarstar; + bo = bstarstar; + co = cstarstar; + ptoto = ptotstarl; + etoto = etotstarstarl; + vdotbo = vdotbstarstar; + } + else if (sar > 0) + { + ro = rstarr; + uo = sm; + vo = vstarstar; + wo = wstarstar; + bo = bstarstar; + co = cstarstar; + ptoto = ptotstarr; + etoto = etotstarstarr; + vdotbo = vdotbstarstar; + } + else if (sr > 0) + { + ro = rstarr; + uo = sm; + vo = vstarr; + wo = wstarr; + bo = bstarr; + co = cstarr; + ptoto = ptotstarr; + etoto = etotstarr; + vdotbo = vdotbstarr; + } + else + { // flow is supersonic, return upwind variables + ro = rr; + uo = ur; + vo = vr; + wo = wr; + bo = br; + co = cr; + ptoto = ptotr; + etoto = etotr; + vdotbo = vdotbr; + } + + // compute the godunov flux + flux[MHD::ID] = ro * uo; + flux[MHD::IP] = (etoto + ptoto) * uo - a * vdotbo; + flux[MHD::IU] = ro * uo * uo - a * a + ptoto; + flux[MHD::IV] = ro * uo * vo - a * bo; + flux[MHD::IW] = ro * uo * wo - a * co; + flux[MHD::IA] = ZERO_F; + flux[MHD::IB] = bo * uo - a * vo; + flux[MHD::IC] = co * uo - a * wo; + +} // riemann_hlld_boris + +// ==================================================================== +// ==================================================================== +/** + * Wrapper function calling the actual riemann solver for MHD. + */ +KOKKOS_INLINE_FUNCTION +void +riemann_mhd(MHDStateCell & qleft, + MHDStateCell & qright, + MHDStateCell & flux, + MHDSettings const & settings) +{ + + if (settings.hydro.riemannSolverType == +RiemannSolverType::HLLD) + { + riemann_hlld(qleft, qright, flux, settings); + } + else if (settings.hydro.riemannSolverType == +RiemannSolverType::HLL) + { + riemann_hll(qleft, qright, flux, settings); + } + else if (settings.hydro.riemannSolverType == +RiemannSolverType::LLF) + { + riemann_llf(qleft, qright, flux, settings); + } + else if (settings.hydro.riemannSolverType == +RiemannSolverType::HLLC) + { + // only useful for debug + riemann_hllc(qleft, qright, flux, settings); + } + else if (settings.hydro.riemannSolverType == +RiemannSolverType::FIVE_WAVES) + { + riemann_5_waves(qleft, qright, flux, settings); + } + else if (settings.hydro.riemannSolverType == +RiemannSolverType::HLLD_BORIS) + { + riemann_hlld_boris(qleft, qright, flux, settings); + } + else if (settings.hydro.riemannSolverType == +RiemannSolverType::FIVE_WAVES_ENTROPY) + { + riemann_5_waves_entropy(qleft, qright, flux, settings); + } + else + { + Kokkos::abort("Unknown Riemann solver type"); + } + +} // riemann_mhd + +/** + * Another wrapper function calling the actual riemann solver. + */ +KOKKOS_INLINE_FUNCTION +MHDStateCell +riemann_mhd(MHDStateCell & qleft, MHDStateCell & qright, MHDSettings const & settings) +{ + MHDStateCell flux; + + if (settings.hydro.riemannSolverType == +RiemannSolverType::HLLD) + { + riemann_hlld(qleft, qright, flux, settings); + } + else if (settings.hydro.riemannSolverType == +RiemannSolverType::HLL) + { + riemann_hll(qleft, qright, flux, settings); + } + else if (settings.hydro.riemannSolverType == +RiemannSolverType::LLF) + { + riemann_llf(qleft, qright, flux, settings); + } + else if (settings.hydro.riemannSolverType == +RiemannSolverType::HLLC) + { + // only useful for debug + riemann_hllc(qleft, qright, flux, settings); + } + else if (settings.hydro.riemannSolverType == +RiemannSolverType::FIVE_WAVES) + { + riemann_5_waves(qleft, qright, flux, settings); + } + else if (settings.hydro.riemannSolverType == +RiemannSolverType::HLLD_BORIS) + { + riemann_hlld_boris(qleft, qright, flux, settings); + } + else if (settings.hydro.riemannSolverType == +RiemannSolverType::FIVE_WAVES_ENTROPY) + { + riemann_5_waves_entropy(qleft, qright, flux, settings); + } + else + { + Kokkos::abort("Unknown Riemann solver type"); + } + + return flux; +} // riemann_mhd + +// ==================================================================== +// =================== CASE SPLIT MAGNETIC FIELD ====================== +// ==================================================================== + +/** + * \param[in] qleft : input left state + * \param[in] qright : input right state + * \param[in] MFL : input left state zero order magnetic field + * \param[in] MFR : input right state zero order magnetic field + * \param[out] flux : output flux + * \param[in] params : hydro parameters + */ + +KOKKOS_INLINE_FUNCTION +void +riemann_hlld(MHDSplitStateCell & qleft, + MHDSplitStateCell & qright, + MagneticField_t & MFL, + MagneticField_t & MFR, + MHDStateCell & flux, + MHDSettings const & settings) +{ + + // makes enum Hydro::VarId available + using MHD = kalypsso::core::models::MHD; + + // Constants + const real_t gamma0 = settings.hydro.gamma0; + const real_t entho = ONE_F / (gamma0 - ONE_F); + + real_t a0, b0, c0; + + a0 = HALF_F * (MFL[IX] + MFR[IX]); + b0 = HALF_F * (MFL[IY] + MFR[IY]); + c0 = HALF_F * (MFL[IZ] + MFR[IZ]); + + // Enforce continuity of normal component of magnetic field + real_t a = HALF_F * (qleft[MHD::IA] + qright[MHD::IA]); + + // KALYPSSO_INFO("VALEUR DE A0={} ET DE A1={}",a0, a); + + real_t sgnm = ((a + a0) >= 0) ? ONE_F : -ONE_F; + + // KALYPSSO_INFO("VALEUR DE A = {} ET sgnm = {} ", a, sgnm); + + qleft[MHD::IA] = a; + qright[MHD::IA] = a; + + // ISOTHERMAL + real_t cIso = settings.hydro.cIso; + if (cIso > 0) + { + // recompute pressure + qleft[MHD::IP] = qleft[MHD::ID] * cIso * cIso; + qright[MHD::IP] = qright[MHD::ID] * cIso * cIso; + } // end ISOTHERMAL + + // left variables + real_t rl, pl, ul, vl, wl, bl, cl; + rl = qleft[MHD::ID]; // rl = fmax(qleft[MHD::ID], static_cast(gParams.smallr) ); + pl = qleft[MHD::IP]; // pl = fmax(qleft[MHD::IP], static_cast(rl*gParams.smallp) ); + ul = qleft[MHD::IU]; + vl = qleft[MHD::IV]; + wl = qleft[MHD::IW]; + bl = qleft[MHD::IB]; + cl = qleft[MHD::IC]; + + real_t ecinl = HALF_F * (ul * ul + vl * vl + wl * wl) * rl; + real_t emagl = HALF_F * ((a * a) + (bl * bl) + (cl * cl)); + real_t etotl = pl * entho + ecinl + emagl; + real_t vdotbl = (ul * a) + (vl * bl) + (wl * cl); + // Eq. 7 from [Miyoshi2010] for total pressure + real_t ptotl = pl + emagl + ((a * a0) + (bl * b0) + (cl * c0)); + + + // right variables + real_t rr, pr, ur, vr, wr, br, cr; + rr = qright[MHD::ID]; // rr = fmax(qright[MHD::ID], static_cast( gParams.smallr) ); + pr = qright[MHD::IP]; // pr = fmax(qright[MHD::IP], static_cast( rr*gParams.smallp) ); + ur = qright[MHD::IU]; + vr = qright[MHD::IV]; + wr = qright[MHD::IW]; + br = qright[MHD::IB]; + cr = qright[MHD::IC]; + + real_t ecinr = HALF_F * (ur * ur + vr * vr + wr * wr) * rr; + real_t emagr = HALF_F * ((a * a) + (br * br) + (cr * cr)); + real_t etotr = pr * entho + ecinr + emagr; + real_t vdotbr = (ur * a) + (vr * br) + (wr * cr); + // Eq. 7 from [Miyoshi2010] for total pressure + real_t ptotr = pr + emagr + ((a * a0) + (br * b0) + (cr * c0)); + + // find the largest eigenvalues in the normal direction to the interface + real_t cfastl = core::models::mhd::find_speed_fast(qleft, MFL, settings); + real_t cfastr = core::models::mhd::find_speed_fast(qright, MFR, settings); + + // compute hll wave speed + real_t sl = fmin(ul, ur) - fmax(cfastl, cfastr); + real_t sr = fmax(ul, ur) + fmax(cfastl, cfastr); + + // compute lagrangian sound speed + real_t rcl = rl * (ul - sl); + real_t rcr = rr * (sr - ur); + + // compute acoustic star state + real_t ustar = (rcr * ur + rcl * ul + (ptotl - ptotr)) / (rcr + rcl); + real_t ptotstar = (rcr * ptotl + rcl * ptotr + rcl * rcr * (ul - ur)) / (rcr + rcl); + + // left star region variables + real_t estarl; + real_t rstarl, el; + // clang-format off + rstarl = rl * (sl - ul) / (sl - ustar); + estarl = rl * (sl - ul) * (sl - ustar) - ((a + a0) * (a + a0)); + el = rl * (sl - ul) * (sl - ul) - ((a + a0) * (a + a0)); + // clang-format on + real_t vstarl, wstarl; + real_t bstarl, cstarl; + + // if (fabs(estarl) < KALYPSSO_NUM(1e-4) * ((a + a0) * (a + a0))) + if (fabs(estarl) < KALYPSSO_NUM(1e-99)) + { + vstarl = vl; + bstarl = -b0; // In [Miyoshi2010] after equation 44 + wstarl = wl; + cstarl = -c0; // In [Miyoshi2010] after equation 44 + } + else + { + vstarl = vl - ((a + a0) * (bl + b0) * (ustar - ul) / estarl); + bstarl = ((bl + b0) * (el / estarl)) - b0; + wstarl = wl - ((a + a0) * (cl + c0) * (ustar - ul) / estarl); + cstarl = ((cl + c0) * (el / estarl)) - c0; + } + real_t vdotbstarl = ustar * a + vstarl * bstarl + wstarl * cstarl; + real_t etotstarl = + ((sl - ul) * etotl - ptotl * ul + ptotstar * ustar + (a + a0) * (vdotbl - vdotbstarl)) / + (sl - ustar); + real_t sqrrstarl = sqrt(rstarl); + real_t calfvenl = + fabs((a + a0)) / sqrrstarl; // sqrrstarl should never be zero, but it might happen if + // border conditions are not OK !!!!!! + real_t sal = ustar - calfvenl; + + // right star region variables + real_t rstarr, er, estarr; + // clang-format off + rstarr = rr * (sr - ur) / (sr - ustar); + estarr = rr * (sr - ur) * (sr - ustar) - ((a + a0) * (a + a0)); + er = rr * (sr - ur) * (sr - ur) - ((a + a0) * (a + a0)); + // clang-format on + real_t vstarr, wstarr; + real_t bstarr, cstarr; + + // if (fabs(estarr) < KALYPSSO_NUM(1e-4) * ((a + a0) * (a + a0))) + if (fabs(estarr) < KALYPSSO_NUM(1e-99)) + { + vstarr = vr; + bstarr = -b0; // In [Miyoshi2010] after equation 44 + wstarr = wr; + cstarr = -c0; // In [Miyoshi2010] after equation 44 + } + else + { + vstarr = vr - ((a + a0) * (br + b0) * (ustar - ur) / estarr); + bstarr = ((br + b0) * (er / estarr)) - b0; + wstarr = wr - ((a + a0) * (cr + c0) * (ustar - ur) / estarr); + cstarr = ((cr + c0) * (er / estarr)) - c0; + } + real_t vdotbstarr = ustar * a + vstarr * bstarr + wstarr * cstarr; + real_t etotstarr = + ((sr - ur) * etotr - ptotr * ur + ptotstar * ustar + (a + a0) * (vdotbr - vdotbstarr)) / + (sr - ustar); + real_t sqrrstarr = sqrt(rstarr); + real_t calfvenr = fabs((a + a0)) / sqrrstarr; // sqrrstarr should never be zero, but it might + // happen if border conditions are not OK !!!!!! + real_t sar = ustar + calfvenr; + + // double star region variables + real_t vstarstar = + (sqrrstarl * vstarl + sqrrstarr * vstarr + sgnm * (bstarr - bstarl)) / (sqrrstarl + sqrrstarr); + real_t wstarstar = + (sqrrstarl * wstarl + sqrrstarr * wstarr + sgnm * (cstarr - cstarl)) / (sqrrstarl + sqrrstarr); + real_t bstarstar = + (sqrrstarl * bstarr + sqrrstarr * bstarl + sgnm * sqrrstarl * sqrrstarr * (vstarr - vstarl)) / + (sqrrstarl + sqrrstarr); + real_t cstarstar = + (sqrrstarl * cstarr + sqrrstarr * cstarl + sgnm * sqrrstarl * sqrrstarr * (wstarr - wstarl)) / + (sqrrstarl + sqrrstarr); + real_t vdotbstarstar = ustar * a + vstarstar * bstarstar + wstarstar * cstarstar; + real_t etotstarstarl = etotstarl - sgnm * sqrrstarl * (vdotbstarl - vdotbstarstar); + real_t etotstarstarr = etotstarr + sgnm * sqrrstarr * (vdotbstarr - vdotbstarstar); + + // sample the solution at x/t=0 + real_t ro, uo, vo, wo, bo, co, ptoto, etoto, vdotbo; + if (sl > 0) + { // flow is supersonic, return upwind variables + ro = rl; + uo = ul; + vo = vl; + wo = wl; + bo = bl; + co = cl; + ptoto = ptotl; + etoto = etotl; + vdotbo = vdotbl; + } + else if (sal > 0) + { + ro = rstarl; + uo = ustar; + vo = vstarl; + wo = wstarl; + bo = bstarl; + co = cstarl; + ptoto = ptotstar; + etoto = etotstarl; + vdotbo = vdotbstarl; + } + else if (ustar > 0) + { + ro = rstarl; + uo = ustar; + vo = vstarstar; + wo = wstarstar; + bo = bstarstar; + co = cstarstar; + ptoto = ptotstar; + etoto = etotstarstarl; + vdotbo = vdotbstarstar; + } + else if (sar > 0) + { + ro = rstarr; + uo = ustar; + vo = vstarstar; + wo = wstarstar; + bo = bstarstar; + co = cstarstar; + ptoto = ptotstar; + etoto = etotstarstarr; + vdotbo = vdotbstarstar; + } + else if (sr > 0) + { + ro = rstarr; + uo = ustar; + vo = vstarr; + wo = wstarr; + bo = bstarr; + co = cstarr; + ptoto = ptotstar; + etoto = etotstarr; + vdotbo = vdotbstarr; + } + else + { // flow is supersonic, return upwind variables + ro = rr; + uo = ur; + vo = vr; + wo = wr; + bo = br; + co = cr; + ptoto = ptotr; + etoto = etotr; + vdotbo = vdotbr; + } + + // compute the godunov flux + + // clang-format off + flux[MHD::ID] = ro * uo; + flux[MHD::IP] = (etoto + ptoto) * uo - (a + a0) * vdotbo; + flux[MHD::IU] = ro * uo * uo + ptoto - (a + a0) * a - (a * a0); + flux[MHD::IV] = ro * uo * vo - (a + a0) * bo - (a * b0); + flux[MHD::IW] = ro * uo * wo - (a + a0) * co - (a * c0); + flux[MHD::IA] = ZERO_F; + flux[MHD::IB] = (bo + b0) * uo - (a + a0) * vo; + flux[MHD::IC] = (co + c0) * uo - (a + a0) * wo; + // clang-format on + +} // riemann_hlld + +KOKKOS_INLINE_FUNCTION +MHDStateCell +riemann_mhd(MHDSplitStateCell & qleft, + MHDSplitStateCell & qright, + MagneticField_t & MFL, + MagneticField_t & MFR, + MHDSettings const & settings) +{ + MHDStateCell flux; + + if (settings.hydro.riemannSolverType == +RiemannSolverType::HLLD) + { + riemann_hlld(qleft, qright, MFL, MFR, flux, settings); + } + else + { + Kokkos::abort("Unknown Riemann solver type"); + } + + return flux; +} // riemann_mhd + +/** + * Riemann solver 5 waves : compute ustar + * + * Reference : + * https://www.sciencedirect.com/science/article/pii/S0021999124007034 + * Tremblin, Pascal, et al. "A multi-dimensional, robust, and cell-centered finite-volume scheme for + * the ideal MHD equations. " Journal of Computational Physics 519 (2024): 113455. + * + * \param[in] qleft : input left state + * \param[in] qright : input right state + * \param[out] ustar : output star velocity + * \param[in] settings : MHD settings + */ +KOKKOS_INLINE_FUNCTION +Kokkos::Array +ustar_5_waves(MHDStateCell & qleft, MHDStateCell & qright, MHDSettings const & settings) +{ + // makes enum Hydro::VarId available + using MHD = kalypsso::core::models::MHD; + + // Constants + auto const & gamma0 = settings.hydro.gamma0; + + // left variables + // real_t rl, pl, ul, vl, wl, al, bl, cl; + auto const & rl = qleft[MHD::ID]; + auto const & pl = qleft[MHD::IP]; + auto const & ul = qleft[MHD::IU]; + auto const & vl = qleft[MHD::IV]; + auto const & wl = qleft[MHD::IW]; + auto const & al = qleft[MHD::IA]; + auto const & bl = qleft[MHD::IB]; + auto const & cl = qleft[MHD::IC]; + const auto ecinl = HALF_F * (ul * ul + vl * vl + wl * wl) * rl; + const auto emagl = HALF_F * (al * al + bl * bl + cl * cl); + const auto ptotl = pl + emagl; + const auto cs2l = gamma0 * (pl / rl); // square sound speed + + // Definition and calculation of the relaxed pressure and the relaxation parameters + Kokkos::Array pRelaxL, cRelaxL; + + pRelaxL[IX] = ptotl - (al * al); + pRelaxL[IY] = -(al * bl); + pRelaxL[IZ] = -(al * cl); + + cRelaxL[IX] = sqrt(rl * (rl * cs2l + ((bl * bl) + (cl * cl) + emagl))); + cRelaxL[IY] = sqrt(rl * ((al * al) + emagl)) + 1e-14; + cRelaxL[IZ] = sqrt(rl * ((al * al) + emagl)) + 1e-14; + + // right variables + // real_t rr, pr, ur, vr, wr, ar, br, cr; + auto const & rr = qright[MHD::ID]; + auto const & pr = qright[MHD::IP]; + auto const & ur = qright[MHD::IU]; + auto const & vr = qright[MHD::IV]; + auto const & wr = qright[MHD::IW]; + auto const & ar = qright[MHD::IA]; + auto const & br = qright[MHD::IB]; + auto const & cr = qright[MHD::IC]; + const auto ecinr = HALF_F * (ur * ur + vr * vr + wr * wr) * rr; + const auto emagr = HALF_F * (ar * ar + br * br + cr * cr); + const auto ptotr = pr + emagr; + const auto cs2r = gamma0 * (pr / rr); // square sound speed + + // Definition and calculation of the relaxed pressure and the relaxation parameters + Kokkos::Array pRelaxR, cRelaxR; + + pRelaxR[IX] = ptotr - (ar * ar); + pRelaxR[IY] = -(ar * br); + pRelaxR[IZ] = -(ar * cr); + + cRelaxR[IX] = sqrt(rr * (rr * cs2r + ((br * br) + (cr * cr) + emagr))); + cRelaxR[IY] = sqrt(rr * ((ar * ar) + emagr)) + 1e-14; + cRelaxR[IZ] = sqrt(rr * ((ar * ar) + emagr)) + 1e-14; + + const auto beta = fmin(pl / emagl, pr / emagr); + const auto alfven = fmax(sqrt(ecinl / emagl), sqrt(ecinr / emagr)); + + Kokkos::Array ustar{ ZERO_F, ZERO_F, ZERO_F }; + + if (beta < settings.small_beta || alfven > settings.large_alfven) + { + ustar[IX] = (cRelaxL[IX] * ul + cRelaxR[IX] * ur + pRelaxL[IX] - pRelaxR[IX]) / + (cRelaxL[IX] + cRelaxR[IX]); + ustar[IY] = (cRelaxL[IY] * vl + cRelaxR[IY] * vr + pRelaxL[IY] - pRelaxR[IY]) / + (cRelaxL[IY] + cRelaxR[IY]); + ustar[IZ] = (cRelaxL[IZ] * wl + cRelaxR[IZ] * wr + pRelaxL[IZ] - pRelaxR[IZ]) / + (cRelaxL[IZ] + cRelaxR[IZ]); + } + + return ustar; + +} // ustar_5_waves + +KOKKOS_INLINE_FUNCTION +Kokkos::Array +compute_ustar(MHDStateCell & qleft, MHDStateCell & qright, MHDSettings const & settings) +{ + return ustar_5_waves(qleft, qright, settings); +} + +/** + * 2D magnetic riemann solver of type HLLD + * + */ +KOKKOS_INLINE_FUNCTION +real_t +mag_riemann2d_hlld(const MHDStateCell (&qLLRR)[4], real_t eLLRR[4], MHDSettings const & settings) +{ + + // makes enum Hydro::VarId available + using MHD = kalypsso::core::models::MHD; + + // alias reference to input arrays + const MHDStateCell & qLL = qLLRR[ILL]; + const MHDStateCell & qRL = qLLRR[IRL]; + const MHDStateCell & qLR = qLLRR[ILR]; + const MHDStateCell & qRR = qLLRR[IRR]; + + real_t & ELL = eLLRR[ILL]; + real_t & ERL = eLLRR[IRL]; + real_t & ELR = eLLRR[ILR]; + real_t & ERR = eLLRR[IRR]; + // real_t ELL,ERL,ELR,ERR; + + const real_t & rLL = qLL[MHD::ID]; + const real_t & pLL = qLL[MHD::IP]; + const real_t & uLL = qLL[MHD::IU]; + const real_t & vLL = qLL[MHD::IV]; + const real_t & aLL = qLL[MHD::IA]; + const real_t & bLL = qLL[MHD::IB]; + const real_t & cLL = qLL[MHD::IC]; + + const real_t & rLR = qLR[MHD::ID]; + const real_t & pLR = qLR[MHD::IP]; + const real_t & uLR = qLR[MHD::IU]; + const real_t & vLR = qLR[MHD::IV]; + const real_t & aLR = qLR[MHD::IA]; + const real_t & bLR = qLR[MHD::IB]; + const real_t & cLR = qLR[MHD::IC]; + + const real_t & rRL = qRL[MHD::ID]; + const real_t & pRL = qRL[MHD::IP]; + const real_t & uRL = qRL[MHD::IU]; + const real_t & vRL = qRL[MHD::IV]; + const real_t & aRL = qRL[MHD::IA]; + const real_t & bRL = qRL[MHD::IB]; + const real_t & cRL = qRL[MHD::IC]; + + const real_t & rRR = qRR[MHD::ID]; + const real_t & pRR = qRR[MHD::IP]; + const real_t & uRR = qRR[MHD::IU]; + const real_t & vRR = qRR[MHD::IV]; + const real_t & aRR = qRR[MHD::IA]; + const real_t & bRR = qRR[MHD::IB]; + const real_t & cRR = qRR[MHD::IC]; + + // Compute 4 fast magnetosonic velocity relative to x direction + real_t cFastLLx = core::models::mhd::find_speed_fast(qLL, settings); + real_t cFastLRx = core::models::mhd::find_speed_fast(qLR, settings); + real_t cFastRLx = core::models::mhd::find_speed_fast(qRL, settings); + real_t cFastRRx = core::models::mhd::find_speed_fast(qRR, settings); + + // Compute 4 fast magnetosonic velocity relative to y direction + real_t cFastLLy = core::models::mhd::find_speed_fast(qLL, settings); + real_t cFastLRy = core::models::mhd::find_speed_fast(qLR, settings); + real_t cFastRLy = core::models::mhd::find_speed_fast(qRL, settings); + real_t cFastRRy = core::models::mhd::find_speed_fast(qRR, settings); + + // TODO : write a find_speed that computes the 2 speeds together (in + // a single routine -> factorize computation of cFastLLx and cFastLLy + + using core::models::mhd::FMIN4; + using core::models::mhd::FMAX4; + + real_t SL = FMIN4(uLL, uLR, uRL, uRR) - FMAX4(cFastLLx, cFastLRx, cFastRLx, cFastRRx); + real_t SR = FMAX4(uLL, uLR, uRL, uRR) + FMAX4(cFastLLx, cFastLRx, cFastRLx, cFastRRx); + real_t SB = FMIN4(vLL, vLR, vRL, vRR) - FMAX4(cFastLLy, cFastLRy, cFastRLy, cFastRRy); + real_t ST = FMAX4(vLL, vLR, vRL, vRR) + FMAX4(cFastLLy, cFastLRy, cFastRLy, cFastRRy); + + /*ELL = uLL*bLL - vLL*aLL; + ELR = uLR*bLR - vLR*aLR; + ERL = uRL*bRL - vRL*aRL; + ERR = uRR*bRR - vRR*aRR;*/ + + real_t PtotLL = pLL + HALF_F * (aLL * aLL + bLL * bLL + cLL * cLL); + real_t PtotLR = pLR + HALF_F * (aLR * aLR + bLR * bLR + cLR * cLR); + real_t PtotRL = pRL + HALF_F * (aRL * aRL + bRL * bRL + cRL * cRL); + real_t PtotRR = pRR + HALF_F * (aRR * aRR + bRR * bRR + cRR * cRR); + + real_t rcLLx = rLL * (uLL - SL); + real_t rcRLx = rRL * (SR - uRL); + real_t rcLRx = rLR * (uLR - SL); + real_t rcRRx = rRR * (SR - uRR); + real_t rcLLy = rLL * (vLL - SB); + real_t rcLRy = rLR * (ST - vLR); + real_t rcRLy = rRL * (vRL - SB); + real_t rcRRy = rRR * (ST - vRR); + + real_t ustar = + (rcLLx * uLL + rcLRx * uLR + rcRLx * uRL + rcRRx * uRR + (PtotLL - PtotRL + PtotLR - PtotRR)) / + (rcLLx + rcLRx + rcRLx + rcRRx); + real_t vstar = + (rcLLy * vLL + rcLRy * vLR + rcRLy * vRL + rcRRy * vRR + (PtotLL - PtotLR + PtotRL - PtotRR)) / + (rcLLy + rcLRy + rcRLy + rcRRy); + + real_t rstarLLx = rLL * (SL - uLL) / (SL - ustar); + real_t BstarLL = bLL * (SL - uLL) / (SL - ustar); + real_t rstarLLy = rLL * (SB - vLL) / (SB - vstar); + real_t AstarLL = aLL * (SB - vLL) / (SB - vstar); + real_t rstarLL = rLL * (SL - uLL) / (SL - ustar) * (SB - vLL) / (SB - vstar); + real_t EstarLLx = ustar * BstarLL - vLL * aLL; + real_t EstarLLy = uLL * bLL - vstar * AstarLL; + real_t EstarLL = ustar * BstarLL - vstar * AstarLL; + + real_t rstarLRx = rLR * (SL - uLR) / (SL - ustar); + real_t BstarLR = bLR * (SL - uLR) / (SL - ustar); + real_t rstarLRy = rLR * (ST - vLR) / (ST - vstar); + real_t AstarLR = aLR * (ST - vLR) / (ST - vstar); + real_t rstarLR = rLR * (SL - uLR) / (SL - ustar) * (ST - vLR) / (ST - vstar); + real_t EstarLRx = ustar * BstarLR - vLR * aLR; + real_t EstarLRy = uLR * bLR - vstar * AstarLR; + real_t EstarLR = ustar * BstarLR - vstar * AstarLR; + + real_t rstarRLx = rRL * (SR - uRL) / (SR - ustar); + real_t BstarRL = bRL * (SR - uRL) / (SR - ustar); + real_t rstarRLy = rRL * (SB - vRL) / (SB - vstar); + real_t AstarRL = aRL * (SB - vRL) / (SB - vstar); + real_t rstarRL = rRL * (SR - uRL) / (SR - ustar) * (SB - vRL) / (SB - vstar); + real_t EstarRLx = ustar * BstarRL - vRL * aRL; + real_t EstarRLy = uRL * bRL - vstar * AstarRL; + real_t EstarRL = ustar * BstarRL - vstar * AstarRL; + + real_t rstarRRx = rRR * (SR - uRR) / (SR - ustar); + real_t BstarRR = bRR * (SR - uRR) / (SR - ustar); + real_t rstarRRy = rRR * (ST - vRR) / (ST - vstar); + real_t AstarRR = aRR * (ST - vRR) / (ST - vstar); + real_t rstarRR = rRR * (SR - uRR) / (SR - ustar) * (ST - vRR) / (ST - vstar); + real_t EstarRRx = ustar * BstarRR - vRR * aRR; + real_t EstarRRy = uRR * bRR - vstar * AstarRR; + real_t EstarRR = ustar * BstarRR - vstar * AstarRR; + + using core::models::mhd::FMAX5; + + real_t calfvenL = FMAX5(fabs(aLR) / sqrt(rstarLRx), + fabs(AstarLR) / sqrt(rstarLR), + fabs(aLL) / sqrt(rstarLLx), + fabs(AstarLL) / sqrt(rstarLL), + settings.hydro.smallc); + real_t calfvenR = FMAX5(fabs(aRR) / sqrt(rstarRRx), + fabs(AstarRR) / sqrt(rstarRR), + fabs(aRL) / sqrt(rstarRLx), + fabs(AstarRL) / sqrt(rstarRL), + settings.hydro.smallc); + real_t calfvenB = FMAX5(fabs(bLL) / sqrt(rstarLLy), + fabs(BstarLL) / sqrt(rstarLL), + fabs(bRL) / sqrt(rstarRLy), + fabs(BstarRL) / sqrt(rstarRL), + settings.hydro.smallc); + real_t calfvenT = FMAX5(fabs(bLR) / sqrt(rstarLRy), + fabs(BstarLR) / sqrt(rstarLR), + fabs(bRR) / sqrt(rstarRRy), + fabs(BstarRR) / sqrt(rstarRR), + settings.hydro.smallc); + + real_t SAL = fmin(ustar - calfvenL, ZERO_F); + real_t SAR = fmax(ustar + calfvenR, ZERO_F); + real_t SAB = fmin(vstar - calfvenB, ZERO_F); + real_t SAT = fmax(vstar + calfvenT, ZERO_F); + + real_t AstarT = (SAR * AstarRR - SAL * AstarLR) / (SAR - SAL); + real_t AstarB = (SAR * AstarRL - SAL * AstarLL) / (SAR - SAL); + + real_t BstarR = (SAT * BstarRR - SAB * BstarRL) / (SAT - SAB); + real_t BstarL = (SAT * BstarLR - SAB * BstarLL) / (SAT - SAB); + + // finally get emf E + real_t E = 0, tmpE = 0; + + // the following part is slightly different from the original fortran + // code since it has to much different branches + // which generate to much branch divergence in CUDA !!! + + // compute sort of boolean (don't know if signbit is available) + real_t SB_pos = (1 + COPYSIGN(ONE_F, SB)) / 2, SB_neg = 1 - SB_pos; + real_t ST_pos = (1 + COPYSIGN(ONE_F, ST)) / 2, ST_neg = 1 - ST_pos; + real_t SL_pos = (1 + COPYSIGN(ONE_F, SL)) / 2, SL_neg = 1 - SL_pos; + real_t SR_pos = (1 + COPYSIGN(ONE_F, SR)) / 2, SR_neg = 1 - SR_pos; + + // else + tmpE = (SAL * SAB * EstarRR - SAL * SAT * EstarRL - SAR * SAB * EstarLR + SAR * SAT * EstarLL) / + (SAR - SAL) / (SAT - SAB) - + SAT * SAB / (SAT - SAB) * (AstarT - AstarB) + SAR * SAL / (SAR - SAL) * (BstarR - BstarL); + E += (SB_neg * ST_pos * SL_neg * SR_pos) * tmpE; + + // SB>0 + tmpE = (SAR * EstarLLx - SAL * EstarRLx + SAR * SAL * (bRL - bLL)) / (SAR - SAL); + tmpE = SL_pos * ELL + SL_neg * SR_neg * ERL + SL_neg * SR_pos * tmpE; + E += SB_pos * tmpE; + + // ST<0 + tmpE = (SAR * EstarLRx - SAL * EstarRRx + SAR * SAL * (bRR - bLR)) / (SAR - SAL); + tmpE = SL_pos * ELR + SL_neg * SR_neg * ERR + SL_neg * SR_pos * tmpE; + E += (SB_neg * ST_neg) * tmpE; + + // SL>0 + tmpE = (SAT * EstarLLy - SAB * EstarLRy - SAT * SAB * (aLR - aLL)) / (SAT - SAB); + E += (SB_neg * ST_pos * SL_pos) * tmpE; + + // SR<0 + tmpE = (SAT * EstarRLy - SAB * EstarRRy - SAT * SAB * (aRR - aRL)) / (SAT - SAB); + E += (SB_neg * ST_pos * SL_neg * SR_neg) * tmpE; + + + /* + if(SB>ZERO_F) { + if(SL>ZERO_F) { + E=ELL; + } else if(SRZERO_F) { + E=ELR; + } else if(SRZERO_F) { + E=(SAT*EstarLLy-SAB*EstarLRy-SAT*SAB*(aLR-aLL))/(SAT-SAB); + } else if (SR +KOKKOS_INLINE_FUNCTION real_t +compute_emf(MHDStateCell (&qEdge)[4], + MHDSettings const & settings, + [[maybe_unused]] real_t xPos = 0) +{ + + // makes enum Hydro::VarId available + using MHD = kalypsso::core::models::MHD; + + // define alias reference to input arrays + MHDStateCell & qRT = qEdge[IRT]; + MHDStateCell & qLT = qEdge[ILT]; + MHDStateCell & qRB = qEdge[IRB]; + MHDStateCell & qLB = qEdge[ILB]; + + // defines alias reference to intermediate state before applying a + // magnetic Riemann solver + MHDStateCell qLLRR[4]; + MHDStateCell & qLL = qLLRR[ILL]; + MHDStateCell & qRL = qLLRR[IRL]; + MHDStateCell & qLR = qLLRR[ILR]; + MHDStateCell & qRR = qLLRR[IRR]; + + // density + qLL[MHD::ID] = qRT[MHD::ID]; + qRL[MHD::ID] = qLT[MHD::ID]; + qLR[MHD::ID] = qRB[MHD::ID]; + qRR[MHD::ID] = qLB[MHD::ID]; + + // pressure + // ISOTHERMAL + real_t cIso = settings.hydro.cIso; + if (cIso > 0) + { + qLL[MHD::IP] = qLL[MHD::ID] * cIso * cIso; + qRL[MHD::IP] = qRL[MHD::ID] * cIso * cIso; + qLR[MHD::IP] = qLR[MHD::ID] * cIso * cIso; + qRR[MHD::IP] = qRR[MHD::ID] * cIso * cIso; + } + else + { + qLL[MHD::IP] = qRT[MHD::IP]; + qRL[MHD::IP] = qLT[MHD::IP]; + qLR[MHD::IP] = qRB[MHD::IP]; + qRR[MHD::IP] = qLB[MHD::IP]; + } + + // iu, iv : parallel velocity indexes + // iw : orthogonal velocity index + // ia, ib, ic : idem for magnetic field + // int iu, iv, iw, ia, ib, ic; + if (emfDir == EMFZ) + { + + // iu = MHD::IU; iv = MHD::IV; iw = MHD::IW; + // ia = MHD::IA; ib = MHD::IB, ic = MHD::IC; + + // First parallel velocity + qLL[MHD::IU] = qRT[MHD::IU]; + qRL[MHD::IU] = qLT[MHD::IU]; + qLR[MHD::IU] = qRB[MHD::IU]; + qRR[MHD::IU] = qLB[MHD::IU]; + + // Second parallel velocity + qLL[MHD::IV] = qRT[MHD::IV]; + qRL[MHD::IV] = qLT[MHD::IV]; + qLR[MHD::IV] = qRB[MHD::IV]; + qRR[MHD::IV] = qLB[MHD::IV]; + + // First parallel magnetic field (enforce continuity) + qLL[MHD::IA] = HALF_F * (qRT[MHD::IA] + qLT[MHD::IA]); + qRL[MHD::IA] = HALF_F * (qRT[MHD::IA] + qLT[MHD::IA]); + qLR[MHD::IA] = HALF_F * (qRB[MHD::IA] + qLB[MHD::IA]); + qRR[MHD::IA] = HALF_F * (qRB[MHD::IA] + qLB[MHD::IA]); + + // Second parallel magnetic field (enforce continuity) + qLL[MHD::IB] = HALF_F * (qRT[MHD::IB] + qRB[MHD::IB]); + qRL[MHD::IB] = HALF_F * (qLT[MHD::IB] + qLB[MHD::IB]); + qLR[MHD::IB] = HALF_F * (qRT[MHD::IB] + qRB[MHD::IB]); + qRR[MHD::IB] = HALF_F * (qLT[MHD::IB] + qLB[MHD::IB]); + + // Orthogonal velocity + qLL[MHD::IW] = qRT[MHD::IW]; + qRL[MHD::IW] = qLT[MHD::IW]; + qLR[MHD::IW] = qRB[MHD::IW]; + qRR[MHD::IW] = qLB[MHD::IW]; + + // Orthogonal magnetic Field + qLL[MHD::IC] = qRT[MHD::IC]; + qRL[MHD::IC] = qLT[MHD::IC]; + qLR[MHD::IC] = qRB[MHD::IC]; + qRR[MHD::IC] = qLB[MHD::IC]; + } + else if (emfDir == EMFY) + { + + // iu = MHD::IW; iv = MHD::IU; iw = MHD::IV; + // ia = MHD::IC; ib = MHD::IA, ic = MHD::IB; + + // First parallel velocity + qLL[MHD::IU] = qRT[MHD::IW]; + qRL[MHD::IU] = qLT[MHD::IW]; + qLR[MHD::IU] = qRB[MHD::IW]; + qRR[MHD::IU] = qLB[MHD::IW]; + + // Second parallel velocity + qLL[MHD::IV] = qRT[MHD::IU]; + qRL[MHD::IV] = qLT[MHD::IU]; + qLR[MHD::IV] = qRB[MHD::IU]; + qRR[MHD::IV] = qLB[MHD::IU]; + + // First parallel magnetic field (enforce continuity) + qLL[MHD::IA] = HALF_F * (qRT[MHD::IC] + qLT[MHD::IC]); + qRL[MHD::IA] = HALF_F * (qRT[MHD::IC] + qLT[MHD::IC]); + qLR[MHD::IA] = HALF_F * (qRB[MHD::IC] + qLB[MHD::IC]); + qRR[MHD::IA] = HALF_F * (qRB[MHD::IC] + qLB[MHD::IC]); + + // Second parallel magnetic field (enforce continuity) + qLL[MHD::IB] = HALF_F * (qRT[MHD::IA] + qRB[MHD::IA]); + qRL[MHD::IB] = HALF_F * (qLT[MHD::IA] + qLB[MHD::IA]); + qLR[MHD::IB] = HALF_F * (qRT[MHD::IA] + qRB[MHD::IA]); + qRR[MHD::IB] = HALF_F * (qLT[MHD::IA] + qLB[MHD::IA]); + + // Orthogonal velocity + qLL[MHD::IW] = qRT[MHD::IV]; + qRL[MHD::IW] = qLT[MHD::IV]; + qLR[MHD::IW] = qRB[MHD::IV]; + qRR[MHD::IW] = qLB[MHD::IV]; + + // Orthogonal magnetic Field + qLL[MHD::IC] = qRT[MHD::IB]; + qRL[MHD::IC] = qLT[MHD::IB]; + qLR[MHD::IC] = qRB[MHD::IB]; + qRR[MHD::IC] = qLB[MHD::IB]; + } + else + { // emfDir == EMFX + + // iu = MHD::IV; iv = MHD::IW; iw = MHD::IU; + // ia = MHD::IB; ib = MHD::IC, ic = MHD::IA; + + // First parallel velocity + qLL[MHD::IU] = qRT[MHD::IV]; + qRL[MHD::IU] = qLT[MHD::IV]; + qLR[MHD::IU] = qRB[MHD::IV]; + qRR[MHD::IU] = qLB[MHD::IV]; + + // Second parallel velocity + qLL[MHD::IV] = qRT[MHD::IW]; + qRL[MHD::IV] = qLT[MHD::IW]; + qLR[MHD::IV] = qRB[MHD::IW]; + qRR[MHD::IV] = qLB[MHD::IW]; + + // First parallel magnetic field (enforce continuity) + qLL[MHD::IA] = HALF_F * (qRT[MHD::IB] + qLT[MHD::IB]); + qRL[MHD::IA] = HALF_F * (qRT[MHD::IB] + qLT[MHD::IB]); + qLR[MHD::IA] = HALF_F * (qRB[MHD::IB] + qLB[MHD::IB]); + qRR[MHD::IA] = HALF_F * (qRB[MHD::IB] + qLB[MHD::IB]); + + // Second parallel magnetic field (enforce continuity) + qLL[MHD::IB] = HALF_F * (qRT[MHD::IC] + qRB[MHD::IC]); + qRL[MHD::IB] = HALF_F * (qLT[MHD::IC] + qLB[MHD::IC]); + qLR[MHD::IB] = HALF_F * (qRT[MHD::IC] + qRB[MHD::IC]); + qRR[MHD::IB] = HALF_F * (qLT[MHD::IC] + qLB[MHD::IC]); + + // Orthogonal velocity + qLL[MHD::IW] = qRT[MHD::IU]; + qRL[MHD::IW] = qLT[MHD::IU]; + qLR[MHD::IW] = qRB[MHD::IU]; + qRR[MHD::IW] = qLB[MHD::IU]; + + // Orthogonal magnetic Field + qLL[MHD::IC] = qRT[MHD::IA]; + qRL[MHD::IC] = qLT[MHD::IA]; + qLR[MHD::IC] = qRB[MHD::IA]; + qRR[MHD::IC] = qLB[MHD::IA]; + } + + // Compute final fluxes + + // vx*by - vy*bx at the four edge centers + real_t eLLRR[4]; + real_t & ELL = eLLRR[ILL]; + real_t & ERL = eLLRR[IRL]; + real_t & ELR = eLLRR[ILR]; + real_t & ERR = eLLRR[IRR]; + + ELL = qLL[MHD::IU] * qLL[MHD::IB] - qLL[MHD::IV] * qLL[MHD::IA]; + ERL = qRL[MHD::IU] * qRL[MHD::IB] - qRL[MHD::IV] * qRL[MHD::IA]; + ELR = qLR[MHD::IU] * qLR[MHD::IB] - qLR[MHD::IV] * qLR[MHD::IA]; + ERR = qRR[MHD::IU] * qRR[MHD::IB] - qRR[MHD::IV] * qRR[MHD::IA]; + + real_t emf = 0; + // mag_riemann2d<> + // if (params.magRiemannSolver == MAG_HLLD) { + emf = mag_riemann2d_hlld(qLLRR, eLLRR, settings); + // } else if (params.magRiemannSolver == MAG_HLLA) { + // emf = mag_riemann2d_hlla(qLLRR, eLLRR); + // } else if (params.magRiemannSolver == MAG_HLLF) { + // emf = mag_riemann2d_hllf(qLLRR, eLLRR); + // } else if (params.magRiemannSolver == MAG_LLF) { + // emf = mag_riemann2d_llf(qLLRR, eLLRR); + // } + + return emf; + +} // compute_emf + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_MODELS_RIEMANN_SOLVERS_MHD_H_ diff --git a/src/kalypsso/core/models/mhd_utils.h b/src/kalypsso/core/models/mhd_utils.h new file mode 100644 index 0000000..be10d3b --- /dev/null +++ b/src/kalypsso/core/models/mhd_utils.h @@ -0,0 +1,923 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file mhd_utils.h + * \brief Small MHD related utilities common to CPU / GPU code. + * + * These utility functions (find_speed_fast, etc...) are directly + * adapted from Fortran original code found in RAMSES/DUMSES. + * + */ +#ifndef KALYPSSO_CORE_MODELS_MHD_UTILS_H_ +#define KALYPSSO_CORE_MODELS_MHD_UTILS_H_ + +#include +#include +#include + +namespace kalypsso +{ + +namespace core +{ + +namespace models +{ + +namespace mhd +{ +using MagneticField_t = Kokkos::Array; +/** + * max value out of 4 + */ +KOKKOS_INLINE_FUNCTION +real_t +FMAX4(real_t a0, real_t a1, real_t a2, real_t a3) +{ + real_t returnVal = a0; + returnVal = (a1 > returnVal) ? a1 : returnVal; + returnVal = (a2 > returnVal) ? a2 : returnVal; + returnVal = (a3 > returnVal) ? a3 : returnVal; + + return returnVal; +} // FMAX4 + +/** + * min value out of 4 + */ +KOKKOS_INLINE_FUNCTION +real_t +FMIN4(real_t a0, real_t a1, real_t a2, real_t a3) +{ + real_t returnVal = a0; + returnVal = (a1 < returnVal) ? a1 : returnVal; + returnVal = (a2 < returnVal) ? a2 : returnVal; + returnVal = (a3 < returnVal) ? a3 : returnVal; + + return returnVal; +} // FMIN4 + +/** + * max value out of 5 + */ +KOKKOS_INLINE_FUNCTION +real_t +FMAX5(real_t a0, real_t a1, real_t a2, real_t a3, real_t a4) +{ + real_t returnVal = a0; + returnVal = (a1 > returnVal) ? a1 : returnVal; + returnVal = (a2 > returnVal) ? a2 : returnVal; + returnVal = (a3 > returnVal) ? a3 : returnVal; + returnVal = (a4 > returnVal) ? a4 : returnVal; + + return returnVal; +} // FMAX5 + +/** + * Compute the fast magnetosonic velocity. + * + * IU is index to Vnormal + * IA is index to Bnormal + * + * IV, IW are indexes to Vtransverse1, Vtransverse2, + * IB, IC are indexes to Btransverse1, Btransverse2 + * + * \param[in] qvar array of (cell-centered) primitive variables + * \param[in] mhd_settings struct used to retrieve gamma0 (ideal gas law) + * + * \return fast magnetosonic wave speed along given direction. + */ +template +KOKKOS_INLINE_FUNCTION real_t +find_speed_fast(MHDStateCell const & qvar, MHDSettings const & mhd_settings) +{ + + // makes enum MHD::VarId available + using MHD = kalypsso::core::models::MHD; + + const real_t & gamma0 = mhd_settings.hydro.gamma0; + real_t cf = ZERO_F; + + auto const & d = qvar[MHD::ID]; + auto const & p = qvar[MHD::IP]; + auto const & a = qvar[MHD::IA]; + auto const & b = qvar[MHD::IB]; + auto const & c = qvar[MHD::IC]; + + const auto b2 = a * a + b * b + c * c; + const auto c2 = gamma0 * p / d; + const auto d2 = HALF_F * (b2 / d + c2); + + if constexpr (dir == IX) + cf = sqrt(d2 + sqrt(d2 * d2 - c2 * a * a / d)); + + else if constexpr (dir == IY) + cf = sqrt(d2 + sqrt(d2 * d2 - c2 * b * b / d)); + + else if constexpr (dir == IZ) + cf = sqrt(d2 + sqrt(d2 * d2 - c2 * c * c / d)); + + return cf; + +} // find_speed_fast + +template +KOKKOS_INLINE_FUNCTION real_t +find_speed_fast(MHDSplitStateCell const & qvar, + MagneticField_t const & MF, + MHDSettings const & mhd_settings) +{ + + // makes enum MHD::VarId available + using MHD = kalypsso::core::models::MHD; + + const real_t & gamma0 = mhd_settings.hydro.gamma0; + real_t cf = ZERO_F; + + auto const & d = qvar[MHD::ID]; + auto const & p = qvar[MHD::IP]; + auto const & a = qvar[MHD::IA]; + auto const & b = qvar[MHD::IB]; + auto const & c = qvar[MHD::IC]; + + auto const & a0 = MF[IX]; + auto const & b0 = MF[IY]; + auto const & c0 = MF[IZ]; + + const auto b2 = (a + a0) * (a + a0) + (b + b0) * (b + b0) + (c + c0) * (c + c0); + const auto c2 = gamma0 * p / d; + const auto d2 = HALF_F * (b2 / d + c2); + + + if constexpr (dir == IX) + cf = sqrt(d2 + sqrt(d2 * d2 - c2 * (a + a0) * (a + a0) / d)); + + else if constexpr (dir == IY) + cf = sqrt(d2 + sqrt(d2 * d2 - c2 * (b + b0) * (b + b0) / d)); + + else if constexpr (dir == IZ) + cf = sqrt(d2 + sqrt(d2 * d2 - c2 * (c + c0) * (c + c0) / d)); + + return cf; + +} // find_speed_fast + + +/** + * Compute the Alfven velocity. + * + * The structure of qvar is : + * rho, pressure, + * vnormal, vtransverse1, vtransverse2, + * bnormal, btransverse1, btransverse2 + * + * \param[in] qvar array of (cell-centered) primitive variables + */ +KOKKOS_INLINE_FUNCTION +real_t +find_speed_alfven(MHDStateCell const & qvar) +{ + + // makes enum MHD::VarId available + using MHD = kalypsso::core::models::MHD; + + real_t d = qvar[MHD::ID]; + real_t a = qvar[MHD::IA]; + + return sqrt(a * a / d); + +} // find_speed_alfven + +/** + * Compute the Alfven velocity. + * + * Simpler interface. + * \param[in] d density + * \param[in] a normal magnetic field\ + * + */ +KOKKOS_INLINE_FUNCTION +real_t +find_speed_alfven(real_t d, real_t a) +{ + + return sqrt(a * a / d); + +} // find_speed_alfven + +/** + * Compute the 1d mhd fluxes from the conservative. + * + * Only used in Riemann solver HLL (probably cartesian only + * compatible, since gas pressure is included). + * + * variables. The structure of qvar is : + * rho, pressure, + * vnormal, vtransverse1, vtransverse2, + * bnormal, btransverse1, btransverse2. + * + * @param[in] qvar state vector (primitive variables) + * @param[out] cvar state vector (conservative variables) + * @param[out] ff flux vector + * + */ +KOKKOS_INLINE_FUNCTION +void +find_mhd_flux(MHDStateCell const & qvar, + MHDStateCell & cvar, + MHDStateCell & ff, + MHDSettings const & mhd_settings) +{ + + // makes enum MHD::VarId available + using MHD = kalypsso::core::models::MHD; + + real_t const & gamma0 = mhd_settings.hydro.gamma0; + + // ISOTHERMAL + const real_t & cIso = mhd_settings.hydro.cIso; + real_t p; + if (cIso > 0) + { + // recompute pressure + p = qvar[MHD::ID] * cIso * cIso; + } + else + { + p = qvar[MHD::IP]; + } + // end ISOTHERMAL + + // local variables + auto const entho = ONE_F / (gamma0 - ONE_F); + + auto const & d = qvar[MHD::ID]; + auto const & u = qvar[MHD::IU]; + auto const & v = qvar[MHD::IV]; + auto const & w = qvar[MHD::IW]; + auto const & a = qvar[MHD::IA]; + auto const & b = qvar[MHD::IB]; + auto const & c = qvar[MHD::IC]; + + auto const ecin = HALF_F * (u * u + v * v + w * w) * d; + auto const emag = HALF_F * (a * a + b * b + c * c); + auto const etot = p * entho + ecin + emag; + auto const ptot = p + emag; + + // compute conservative variables + cvar[MHD::ID] = d; + cvar[MHD::IP] = etot; + cvar[MHD::IU] = d * u; + cvar[MHD::IV] = d * v; + cvar[MHD::IW] = d * w; + cvar[MHD::IA] = a; + cvar[MHD::IB] = b; + cvar[MHD::IC] = c; + + // compute fluxes + ff[MHD::ID] = d * u; + ff[MHD::IP] = (etot + ptot) * u - a * (a * u + b * v + c * w); + ff[MHD::IU] = d * u * u - a * a + ptot; /* *** WARNING pressure included *** */ + ff[MHD::IV] = d * u * v - a * b; + ff[MHD::IW] = d * u * w - a * c; + ff[MHD::IA] = ZERO_F; + ff[MHD::IB] = b * u - a * v; + ff[MHD::IC] = c * u - a * w; + +} // find_mhd_flux + +/** + * Computes fast magnetosonic wave for each direction. + * + * \param[in] qState primitive variables state vector + * \param[out] fastMagSpeed array containing fast magnetosonic speed along + * x, y, and z direction. + * + * \tparam dim if dim==2, only computes magnetosonic speed along x + * and y. + */ +template +KOKKOS_INLINE_FUNCTION auto +fast_mhd_speed(const MHDStateCell & qState, MHDSettings const & mhd_settings) +{ + + // makes enum MHD::VarId available + using MHD = kalypsso::core::models::MHD; + + Kokkos::Array fastMagSpeed; + + const real_t & gamma0 = mhd_settings.hydro.gamma0; + + const real_t & rho = qState[MHD::ID]; + const real_t & p = qState[MHD::IP]; + /*const real_t& vx = qState[MHD::IU]; + const real_t& vy = qState[MHD::IV]; + const real_t& vz = qState[MHD::IW];*/ + const real_t & bx = qState[MHD::IA]; + const real_t & by = qState[MHD::IB]; + const real_t & bz = qState[MHD::IC]; + + real_t mag_perp, alfv, vit_son, som_vit, som_vit2, delta, fast_speed; + + // compute fast magnetosonic speed along X + mag_perp = (by * by + bz * bz) / rho; // bt ^2 / rho + alfv = bx * bx / rho; // bx / sqrt(4pi*rho) + vit_son = gamma0 * p / rho; // sonic contribution : gamma*P / rho + + som_vit = mag_perp + alfv + vit_son; // whatever direction, + // always the same + som_vit2 = som_vit * som_vit; + + delta = fmax(ZERO_F, som_vit2 - 4 * vit_son * alfv); + + fast_speed = HALF_F * (som_vit + sqrt(delta)); + fast_speed = sqrt(fast_speed); + + fastMagSpeed[IX] = fast_speed; + + // compute fast magnetosonic speed along Y + mag_perp = (bx * bx + bz * bz) / rho; + alfv = by * by / rho; + + delta = fmax(ZERO_F, som_vit2 - 4 * vit_son * alfv); + + fast_speed = HALF_F * (som_vit + sqrt(delta)); + fast_speed = sqrt(fast_speed); + + fastMagSpeed[IY] = fast_speed; + + // compute fast magnetosonic speed along Z + if constexpr (dim == 3) + { + mag_perp = (bx * bx + by * by) / rho; + alfv = bz * bz / rho; + + delta = fmax(ZERO_F, som_vit2 - 4 * vit_son * alfv); + + fast_speed = HALF_F * (som_vit + sqrt(delta)); + fast_speed = sqrt(fast_speed); + + fastMagSpeed[IZ] = fast_speed; + } + + return fastMagSpeed; + +} // fast_mhd_speed + +/** + * Computes fastest signal speed for each direction. + * + * \param[in] qState primitive variables state vector + * \param[out] fastInfoSpeed array containing fastest information speed along + * x, y, and z direction. + * + * Directional information speed being defined as : + * directional fast magneto speed + fabs(velocity component) + * + * \warning This routine uses gamma ! You need to set gamma to something very near to 1 + * + * \tparam dim if dim==2, only computes information speed along x + * and y. + */ +template +KOKKOS_INLINE_FUNCTION auto +find_speed_info(MHDStateCell const & qState, MHDSettings const & mhd_settings) +{ + // makes enum MHD::VarId available + using MHD = kalypsso::core::models::MHD; + + Kokkos::Array fastInfoSpeed; + + const real_t & gamma0 = mhd_settings.hydro.gamma0; + + const auto & d = qState[MHD::ID]; + const auto & p = qState[MHD::IP]; + const auto & u = qState[MHD::IU]; + const auto & v = qState[MHD::IV]; + const auto & w = qState[MHD::IW]; + const auto & a = qState[MHD::IA]; + const auto & b = qState[MHD::IB]; + const auto & c = qState[MHD::IC]; + + // square norm of magnetic field + const auto b2 = a * a + b * b + c * c; + + // square speed of sound + const auto c2 = gamma0 * p / d; + + const auto d2 = HALF_F * (b2 / d + c2); + + /* + * compute fastest info speed along X + */ + real_t cf = sqrt(d2 + sqrt(d2 * d2 - c2 * a * a / d)); + + fastInfoSpeed[IX] = cf + fabs(u); + + /* + * compute fastest info speed along Y + */ + cf = sqrt(d2 + sqrt(d2 * d2 - c2 * b * b / d)); + + fastInfoSpeed[IY] = cf + fabs(v); + + /* + * compute fastest info speed along Z + */ + if constexpr (dim == 3) + { + cf = sqrt(d2 + sqrt(d2 * d2 - c2 * c * c / d)); + + fastInfoSpeed[IZ] = cf + fabs(w); + } // end THREE_D + + return fastInfoSpeed; + +} // find_speed_info + +/** + * Computes fastest signal speed for each direction. + * + * \param[in] qState primitive variables state vector + * \param[out] fastInfoSpeed fastest information speed along x + * + * \warning This routine uses gamma ! You need to set gamma to something very near to 1 + * + */ +KOKKOS_INLINE_FUNCTION +real_t +find_speed_info(MHDStateCell const & qState, MHDSettings const & mhd_settings) +{ + + // makes enum MHD::VarId available + using MHD = kalypsso::core::models::MHD; + + const real_t & gamma0 = mhd_settings.hydro.gamma0; + const real_t & u = qState[MHD::IU]; + // const real_t& v = qState[MHD::IV]; + // const real_t& w = qState[MHD::IW]; + + auto const & d = qState[MHD::ID]; + auto const & p = qState[MHD::IP]; + auto const & a = qState[MHD::IA]; + auto const & b = qState[MHD::IB]; + auto const & c = qState[MHD::IC]; + + // compute fastest info speed along X + const auto b2 = a * a + b * b + c * c; + const auto c2 = gamma0 * p / d; + const auto d2 = HALF_F * (b2 / d + c2); + const auto cf = sqrt(d2 + sqrt(d2 * d2 - c2 * a * a / d)); + + // return value + return cf + fabs(u); + +} // find_speed_info + +// ================================================================================ +// ================================================================================ +/** + * Compute Boris factor (square) + * Return the square of the Boris factor: + * \gamma_A^2 = 1 / (1 + va^2 / c^2) + * = 1 / (1 + B^2 / (\mu_0 \rho c^2)) + * \param[in] u conservative variables array (cell-centered) + * \param[in] mhd_settings struct used to retrieve cboris + * + * \return ga2_Boris real_t + */ +KOKKOS_INLINE_FUNCTION auto +compute_Boris(MHDStateCell const & u, MHDSettings const & mhd_settings) +{ + using MHD = kalypsso::core::models::MHD; + + const auto & cboris2 = mhd_settings.cboris * mhd_settings.cboris; + + auto const & d = u[MHD::ID]; + auto const & A = u[MHD::IA]; + auto const & B = u[MHD::IB]; + auto const & C = u[MHD::IC]; + + const auto Va2 = ((A * A) + (B * B) + (C * C)) / d; + + // return ga2_Boris + return ONE_F / (ONE_F + Va2 / cboris2); + +} // compute_Boris + +// ========================================================================================== +// ========================================================================================== +/** + * Compute the fast magnetosonic velocity. + * + * IU is index to Vnormal + * IA is index to Bnormal + * + * IV, IW are indexes to Vtransverse1, Vtransverse2, + * IB, IC are indexes to Btransverse1, Btransverse2 + * + * \param[in] qvar array of (cell-centered) primitive variables + * \param[in] mhd_settings struct used to retrieve gamma0 (ideal gas law) + * + * \return fast magnetosonic wave speed along given direction. + */ +template +KOKKOS_INLINE_FUNCTION real_t +find_speed_fast_boris(MHDStateCell const & qvar, MHDSettings const & mhd_settings) +{ + + // makes enum MHD::VarId available + using MHD = kalypsso::core::models::MHD; + + const real_t & gamma0 = mhd_settings.hydro.gamma0; + real_t cf = ZERO_F; + + real_t ga2 = compute_Boris(qvar, mhd_settings); + + auto const & d = qvar[MHD::ID]; + auto const & p = qvar[MHD::IP]; + auto const & a = qvar[MHD::IA]; + auto const & b = qvar[MHD::IB]; + auto const & c = qvar[MHD::IC]; + + const auto b2 = a * a + b * b + c * c; + const auto c2 = gamma0 * p / d; + const auto d2 = HALF_F * (b2 / d + c2); + + if constexpr (dir == IX) + cf = sqrt(ga2 * d2 + sqrt(d2 * d2 - c2 * a * a / d)); + + else if constexpr (dir == IY) + cf = sqrt(ga2 * d2 + sqrt(d2 * d2 - c2 * b * b / d)); + + else if constexpr (dir == IZ) + cf = sqrt(ga2 * d2 + sqrt(d2 * d2 - c2 * c * c / d)); + + return cf; + +} // find_speed_fast_boris + +// ====================================================================================== +// ====================================================================================== +/** + * Computes fastest signal speed for each direction. + * + * \param[in] qState primitive variables state vector + * \param[out] fastInfoSpeed array containing fastest information speed along + * x, y, and z direction. + * + * Directional information speed being defined as : + * directional fast magneto speed + fabs(velocity component) + * + * \warning This routine uses gamma ! You need to set gamma to something very near to 1 + * + * \tparam dim if dim==2, only computes information speed along x + * and y. + */ +template +KOKKOS_INLINE_FUNCTION auto +find_speed_info_boris(MHDStateCell const & qState, MHDSettings const & mhd_settings) +{ + // makes enum Hydro::VarId available + using MHD = kalypsso::core::models::MHD; + + Kokkos::Array fastInfoSpeed; + + const real_t & gamma0 = mhd_settings.hydro.gamma0; + real_t ga2 = compute_Boris(qState, mhd_settings); + + const auto & d = qState[MHD::ID]; + const auto & p = qState[MHD::IP]; + const auto & u = qState[MHD::IU]; + const auto & v = qState[MHD::IV]; + const auto & w = qState[MHD::IW]; + const auto & a = qState[MHD::IA]; + const auto & b = qState[MHD::IB]; + const auto & c = qState[MHD::IC]; + + // square norm of magnetic field + const auto b2 = a * a + b * b + c * c; + + // square speed of sound + const auto c2 = gamma0 * p / d; + + const auto d2 = HALF_F * (b2 / d + c2); + + /* + * compute fastest info speed along X + */ + real_t cf = sqrt(ga2 * d2 + sqrt(d2 * d2 - c2 * a * a / d)); + + fastInfoSpeed[IX] = cf + fabs(u); + + /* + * compute fastest info speed along Y + */ + cf = sqrt(ga2 * d2 + sqrt(d2 * d2 - c2 * b * b / d)); + + fastInfoSpeed[IY] = cf + fabs(v); + + /* + * compute fastest info speed along Z + */ + if constexpr (dim == 3) + { + cf = sqrt(ga2 * d2 + sqrt(d2 * d2 - c2 * c * c / d)); + + fastInfoSpeed[IZ] = cf + fabs(w); + } // end THREE_D + + return fastInfoSpeed; + +} // find_speed_info_boris + +// ================================================================================ +// ================================================================================ +/** + * + * Convert conservative variables with boris correction: + * - (rho, rho*u / ga2, rho*v/ ga2, rho*w /ga2, e, bxc, byc, bzc) + * to primitive variables with boris correction: + * - (rho, u/ga2, v/ga2, w/ga2, p, bxc, byc, bzc) + * + * \param[in] u conservative variables array (cell-centered) + * \param[out] c speed of sound + * \param[in] mhd_settings struct used to retrieve gamma0 (ideal gas law) + * + * \return q primitive variables array (cell-centered) + */ +KOKKOS_INLINE_FUNCTION auto +computePrimitives_Boris(MHDStateCell const & u, real_t & c, MHDSettings const & mhd_settings) +{ + // makes enum Hydro::VarId available + using MHD = kalypsso::core::models::MHD; + + const auto & gamma0 = mhd_settings.hydro.gamma0; + const auto & smallr = mhd_settings.hydro.smallr; + const auto & smallp = mhd_settings.hydro.smallp; + + MHDStateCell q; + + real_t ga2 = compute_Boris(u, mhd_settings); + + // density + q[MHD::ID] = fmax(u[MHD::ID], smallr); + + // velocity + q[MHD::IU] = u[MHD::IU] / q[MHD::ID]; + q[MHD::IV] = u[MHD::IV] / q[MHD::ID]; + q[MHD::IW] = u[MHD::IW] / q[MHD::ID]; + + // compute cell-centered magnetic field + q[MHD::IA] = u[MHD::IA]; + q[MHD::IB] = u[MHD::IB]; + q[MHD::IC] = u[MHD::IC]; + + // + // kinetic energy, must be calculated without the boris correction + // + // clang-format off + const auto eken = HALF_F * ((u[MHD::IU] * ga2 / q[MHD::ID] ) * (u[MHD::IU] * ga2 / q[MHD::ID] ) + + (u[MHD::IV] * ga2 / q[MHD::ID] ) * (u[MHD::IV] * ga2 / q[MHD::ID] ) + + (u[MHD::IW] * ga2 / q[MHD::ID] ) * (u[MHD::IW] * ga2 / q[MHD::ID] )); + // clang-format on + + + // + // magnetic energy + // + // clang-format off + const auto emag = HALF_F * (q[MHD::IA] * q[MHD::IA] + + q[MHD::IB] * q[MHD::IB] + + q[MHD::IC] * q[MHD::IC]); + // clang-format on + + // internal energy + const auto eint = (u[MHD::IE] - emag) / q[MHD::ID] - eken; + + // compute pressure and speed of sound + if (mhd_settings.hydro.cIso > 0) + { + // isothermal + q[MHD::IP] = q[MHD::ID] * (mhd_settings.hydro.cIso) * (mhd_settings.hydro.cIso); + c = mhd_settings.hydro.cIso; + } + else + { + // perfect gas + q[MHD::IP] = fmax((gamma0 - ONE_F) * q[MHD::ID] * eint, q[MHD::ID] * smallp); + c = sqrt(mhd_settings.hydro.gamma0 * q[MHD::IP] / q[MHD::ID]); + } + + return q; + +} // computePrimitives_Boris + +// ================================================================================ +// ================================================================================ +/** + * Convert conservative variables: + * - (rho, rho*u, rho*v, rho*w, e, , bxc, byc, bzc) + * to primitive variables: + * - (rho, u, v, w, p, bxc, byc, bzc) + * + * \param[in] u conservative variables array (cell-centered) + * \param[out] c speed of sound + * \param[in] mhd_settings struct used to retrieve gamma0 (ideal gas law) + * + * \return q primitive variables array (cell-centered) + */ +KOKKOS_INLINE_FUNCTION auto +computePrimitives(MHDStateCell const & u, real_t & c, MHDSettings const & mhd_settings) +{ + // makes enum MHD::VarId available + using MHD = kalypsso::core::models::MHD; + + const auto & gamma0 = mhd_settings.hydro.gamma0; + const auto & smallr = mhd_settings.hydro.smallr; + const auto & smallp = mhd_settings.hydro.smallp; + + MHDStateCell q; + + // density + q[MHD::ID] = fmax(u[MHD::ID], smallr); + + // velocity + q[MHD::IU] = u[MHD::IU] / q[MHD::ID]; + q[MHD::IV] = u[MHD::IV] / q[MHD::ID]; + q[MHD::IW] = u[MHD::IW] / q[MHD::ID]; + + // compute cell-centered magnetic field + q[MHD::IA] = u[MHD::IA]; + q[MHD::IB] = u[MHD::IB]; + q[MHD::IC] = u[MHD::IC]; + + // + // kinetic energy + // + // clang-format off + const auto eken = HALF_F * (q[MHD::IU] * q[MHD::IU] + + q[MHD::IV] * q[MHD::IV] + + q[MHD::IW] * q[MHD::IW]); + // clang-format on + + // + // magnetic energy + // + // clang-format off + const auto emag = HALF_F * (q[MHD::IA] * q[MHD::IA] + + q[MHD::IB] * q[MHD::IB] + + q[MHD::IC] * q[MHD::IC]); + // clang-format on + + // internal energy + const auto eint = (u[MHD::IE] - emag) / q[MHD::ID] - eken; + if (mhd_settings.hydro.abort_when_negative_eint and eint < 0) + { + Kokkos::abort("Negative internal energy detected - can't proceed further"); + } + + // compute pressure and speed of sound + if (mhd_settings.hydro.cIso > 0) + { + // isothermal + q[MHD::IP] = q[MHD::ID] * (mhd_settings.hydro.cIso) * (mhd_settings.hydro.cIso); + c = mhd_settings.hydro.cIso; + } + else + { + // perfect gas + q[MHD::IP] = fmax((gamma0 - ONE_F) * q[MHD::ID] * eint, q[MHD::ID] * smallp); + c = sqrt(mhd_settings.hydro.gamma0 * q[MHD::IP] / q[MHD::ID]); + } + + return q; + +} // computePrimitives + +// ================================================================================ +// ================================================================================ + +KOKKOS_INLINE_FUNCTION auto +computePrimitives(MHDSplitStateCell const & u, + real_t & c, + MHDSettings const & mhd_settings, + FieldMap fm) +{ + // makes enum MHD::VarId available + using MHD = kalypsso::core::models::MHD; + + const auto & gamma0 = mhd_settings.hydro.gamma0; + const auto & smallr = mhd_settings.hydro.smallr; + const auto & smallp = mhd_settings.hydro.smallp; + + MHDSplitStateCell q; + + // density + q[fm[MHD::ID]] = fmax(u[fm[MHD::ID]], smallr); + + // velocity + q[fm[MHD::IU]] = u[fm[MHD::IU]] / q[fm[MHD::ID]]; + q[fm[MHD::IV]] = u[fm[MHD::IV]] / q[fm[MHD::ID]]; + q[fm[MHD::IW]] = u[fm[MHD::IW]] / q[fm[MHD::ID]]; + + // compute cell-centered magnetic field + q[fm[MHD::IA]] = u[fm[MHD::IA]]; + q[fm[MHD::IB]] = u[fm[MHD::IB]]; + q[fm[MHD::IC]] = u[fm[MHD::IC]]; + + // compute cell-centered zer-order magnetic field + q[fm[MHD::IA0]] = u[fm[MHD::IA0]]; + q[fm[MHD::IB0]] = u[fm[MHD::IB0]]; + q[fm[MHD::IC0]] = u[fm[MHD::IC0]]; + + // + // kinetic energy + // + // clang-format off + const auto eken = HALF_F * (q[fm[MHD::IU]] * q[fm[MHD::IU]] + + q[fm[MHD::IV]] * q[fm[MHD::IV]] + + q[fm[MHD::IW]] * q[fm[MHD::IW]]); + // clang-format on + + // + // magnetic energy + // + // clang-format off + const auto emag = HALF_F * (q[fm[MHD::IA]] * q[fm[MHD::IA]] + + q[fm[MHD::IB]] * q[fm[MHD::IB]] + + q[fm[MHD::IC]] * q[fm[MHD::IC]]); + + // clang-format on + + // internal energy + const auto eint = (u[MHD::IE] - emag) / q[MHD::ID] - eken; + + // compute pressure and speed of sound + if (mhd_settings.hydro.cIso > 0) + { + // isothermal + q[MHD::IP] = q[MHD::ID] * (mhd_settings.hydro.cIso) * (mhd_settings.hydro.cIso); + c = mhd_settings.hydro.cIso; + } + else + { + // perfect gas + q[MHD::IP] = fmax((gamma0 - ONE_F) * q[MHD::ID] * eint, q[MHD::ID] * smallp); + c = sqrt(mhd_settings.hydro.gamma0 * q[MHD::IP] / q[MHD::ID]); + } + + return q; + +} // computePrimitives + +// ================================================================================ +// ================================================================================ +/** + * Convert conservative variables: + * - (rho, rho*u, rho*v, rho*w, e, , bxc, byc, bzc) + * to primitive variables: + * - (rho, u, v, w, p, bxc, byc, bzc) + * + * \param[in] u conservative variables array (cell-centered) + * \param[out] c speed of sound + * \param[in] mhd_settings struct used to retrieve gamma0 (ideal gas law) + * + * \return q primitive variables array (cell-centered) + * + * \note here we don't care about returning the speed of sound + */ +KOKKOS_INLINE_FUNCTION auto +computePrimitives_Boris(MHDStateCell const & u, MHDSettings const & mhd_settings) +{ + [[maybe_unused]] real_t c; + return computePrimitives_Boris(u, c, mhd_settings); +} // computePrimitives + +KOKKOS_INLINE_FUNCTION auto +computePrimitives(MHDStateCell const & u, MHDSettings const & mhd_settings) +{ + [[maybe_unused]] real_t c; + return computePrimitives(u, c, mhd_settings); +} // computePrimitives + +KOKKOS_INLINE_FUNCTION auto +computePrimitives(MHDSplitStateCell const & u, + MHDSettings const & mhd_settings, + FieldMap fm) +{ + [[maybe_unused]] real_t c; + return computePrimitives(u, c, mhd_settings, fm); +} // computePrimitives + +} // namespace mhd + +} // namespace models + +} // namespace core + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_MODELS_MHD_UTILS_H_ diff --git a/src/kalypsso/core/models/riemann_solver_types.h b/src/kalypsso/core/models/riemann_solver_types.h new file mode 100644 index 0000000..f9fa88e --- /dev/null +++ b/src/kalypsso/core/models/riemann_solver_types.h @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file riemann_solver_types.h + */ +#ifndef KALYPSSO_CORE_MODELS_RIEMANNSOLVERTYPES_H_ +#define KALYPSSO_CORE_MODELS_RIEMANNSOLVERTYPES_H_ + +#include <../better-enums/enum.h> +#include +#include +#include + +namespace kalypsso +{ + +// clang-format off +/** + * Enumerate Riemann solver types. + */ +BETTER_ENUM(RiemannSolverType, + int, + UNKNOWN, /*!< invalid value */ + APPROX, /*!< quasi-exact Riemann solver (hydro-only) */ + LLF, /*!< LLF Local Lax-Friedrich */ + HLL, /*!< HLL hydro and MHD Riemann solver */ + HLLC, /*!< HLLC hydro-only Riemann solver */ + HLLC_LM, /*!< HLLC_LM hydro-only Riemann solver */ + HLLD, /*!< HLLD MHD-only Riemann solver */ + HLLD_BORIS, /*!< HLLD MHD-only Riemann solver with Boris Correction */ + FIVE_WAVES, /*! 5+1 waves solver */ + FIVE_WAVES_ENTROPY /*!< 5+1 waves solver with entropy correction */ +) +// clang-format on + +inline RiemannSolverType +get_riemann_solver_type(ConfigMap const & config_map) +{ + auto riemann_solver_name = config_map.getString("hydro", "riemann", "UNKNOWN"); + auto maybe_value = RiemannSolverType::_from_string_nothrow(riemann_solver_name.c_str()); + if (maybe_value) + { + return *maybe_value; + } + + KALYPSSO_ERROR("Invalid riemann_solver_type : {}", riemann_solver_name); + return RiemannSolverType::UNKNOWN; +} + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_MODELS_RIEMANNSOLVERTYPES_H_ diff --git a/src/kalypsso/core/models/utils_hydro.h b/src/kalypsso/core/models/utils_hydro.h new file mode 100644 index 0000000..a942e03 --- /dev/null +++ b/src/kalypsso/core/models/utils_hydro.h @@ -0,0 +1,327 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file utils_hydro.h + */ +#ifndef KALYPSSO_CORE_UTILS_HYDRO_H_ +#define KALYPSSO_CORE_UTILS_HYDRO_H_ + +#include + +#include +#include +#include +#include +#include + +namespace kalypsso +{ + +namespace core +{ + +namespace models +{ + +// ================================================================================ +// ================================================================================ +/** + * Convert conservative variables (rho, rho*u, rho*v, e) to + * primitive variables (rho,u,v,p) + * \param[in] u conservative variables array + * \param[in] settings + * \param[out] valid will be true is pressure and internal energy are positive + * \return q primitive variables array + */ +KOKKOS_INLINE_FUNCTION +HydroState2d +computePrimitives(const HydroState2d & u, const HydroSettings & settings, bool & valid) +{ + real_t gamma0 = settings.gamma0; + real_t smallr = settings.smallr; + real_t smallp = settings.smallp; + + const auto d = fmax(u[Hydro::ID], smallr); + const auto ux = u[Hydro::IU] / d; + const auto uy = u[Hydro::IV] / d; + + // kinetic energy + const auto eken = HALF_F * (ux * ux + uy * uy); + + // internal energy + const auto e = u[Hydro::IP] / d - eken; + + valid = e >= 0; + + if (settings.abort_when_negative_eint and !valid) + { + Kokkos::abort("Negative internal energy detected - can't proceed further"); + } + + // compute pressure + const auto p = fmax((gamma0 - ONE_F) * d * e, d * smallp); + + return { d, p, ux, uy }; + +} // computePrimitives - 2d + +// ================================================================================ +// ================================================================================ +/** + * Convert conservative variables (rho, rho*u, rho*v, e) to + * primitive variables (rho,u,v,p) + * \param[in] u conservative variables array + * \param[in] settings + * \return q primitive variables array + * + * \note discard argument "valid" + */ +KOKKOS_INLINE_FUNCTION +HydroState2d +computePrimitives(const HydroState2d & u, const HydroSettings & settings) +{ + + [[maybe_unused]] bool valid = true; + return computePrimitives(u, settings, valid); + +} // computePrimitives - 2d + +// ================================================================================ +// ================================================================================ +/** + * Convert conservative variables (rho, rho*u, rho*v, e) to + * primitive variables (rho,u,v,p) + * \param[in] u conservative variables array + * \return q primitive variables array + * nbvar) + */ +KOKKOS_INLINE_FUNCTION +HydroState3d +computePrimitives(const HydroState3d & u, const HydroSettings & settings, bool & valid) +{ + real_t gamma0 = settings.gamma0; + real_t smallr = settings.smallr; + real_t smallp = settings.smallp; + + const auto d = fmax(u[Hydro::ID], smallr); + const auto ux = u[Hydro::IU] / d; + const auto uy = u[Hydro::IV] / d; + const auto uz = u[Hydro::IW] / d; + + // kinetic energy + const auto eken = HALF_F * (ux * ux + uy * uy + uz * uz); + + // internal energy + const auto e = u[Hydro::IP] / d - eken; + + valid = e >= 0; + + if (settings.abort_when_negative_eint and !valid) + { + Kokkos::abort("Negative internal energy detected - can't proceed further"); + } + + // compute pressure + const auto p = fmax((gamma0 - ONE_F) * d * e, d * smallp); + + return { d, p, ux, uy, uz }; + +} // computePrimitives - 3d + +// ================================================================================ +// ================================================================================ +/** + * Convert conservative variables (rho, rho*u, rho*v, rho*w, e) to + * primitive variables (rho,u,v,w,p) + * \param[in] u conservative variables array + * \param[in] settings + * \return q primitive variables array + * + * \note discard argument "valid" + */ +KOKKOS_INLINE_FUNCTION +HydroState3d +computePrimitives(const HydroState3d & u, const HydroSettings & settings) +{ + + [[maybe_unused]] bool valid = true; + return computePrimitives(u, settings, valid); + +} // computePrimitives - 3d + +// ================================================================================ +// ================================================================================ +/** + * Compute speed of sound from pressure and density. + * + * \param[in] p pressure + * \param[in] d density + * \param[in] gamma0 heat capacity ratio + * + * \return speed of sound + */ +KOKKOS_INLINE_FUNCTION +auto +compute_speed_of_sound(real_t const & p, real_t const & d, real_t const & gamma0) +{ + return sqrt(gamma0 * (p) / d); +} + +// ================================================================================ +// ================================================================================ +/** + * Compute speed of sound (ideal gas equation of state). + * + * \param[in] u conservative variables array + * \param[out] p pressure + * \param[out] c speed of sound + * \param[out] valid boolean status indicating if the state is valid from thermodynamics point of + * view (non negative internal energy) + * + */ +KOKKOS_INLINE_FUNCTION +void +compute_Pressure_and_SpeedOfSound(const HydroState2d & u, + real_t & pressure, + real_t & c, + const HydroSettings & settings, + bool & valid) +{ + const real_t gamma0 = settings.gamma0; + const real_t smallr = settings.smallr; + const real_t smallp = settings.smallp; + + real_t d, ux, uy; + + d = fmax(u[Hydro::ID], smallr); + ux = u[Hydro::IU] / d; + uy = u[Hydro::IV] / d; + + // kinetic energy + const real_t eken = HALF_F * (ux * ux + uy * uy); + + // internal energy + const real_t eint = u[Hydro::IP] / d - eken; + + valid = eint >= 0; + + if (settings.abort_when_negative_eint and !valid) + { + Kokkos::abort("Negative internal energy detected - can't proceed further"); + } + + // compute pressure + pressure = fmax((gamma0 - ONE_F) * d * eint, d * smallp); + + // compute speed of sound + c = sqrt(gamma0 * (pressure) / d); + +} // computePressure_and_SpeedOfSound - 2d + +// ================================================================================ +// ================================================================================ +/** + * Compute speed of sound (ideal gas equation of state). + * + * \param[in] u conservative variables array + * \param[out] p pressure + * \param[out] c speed of sound + * + */ +KOKKOS_INLINE_FUNCTION +void +compute_Pressure_and_SpeedOfSound(const HydroState2d & u, + real_t & pressure, + real_t & c, + const HydroSettings & settings) +{ + + bool valid = true; + compute_Pressure_and_SpeedOfSound(u, pressure, c, settings, valid); + +} // computePressure_and_SpeedOfSound - 2d + +// ================================================================================ +// ================================================================================ +/** + * Compute speed of sound (ideal gas equation of state). + * + * \param[in] u conservative variables array + * \param[out] p pressure + * \param[out] c speed of sound + * \param[out] valid boolean status indicating if the state is valid from thermodynamics point of + * view (non negative internal energy) + * + */ +KOKKOS_INLINE_FUNCTION +void +compute_Pressure_and_SpeedOfSound(const HydroState3d & u, + real_t & pressure, + real_t & c, + const HydroSettings & settings, + bool & valid) +{ + const real_t gamma0 = settings.gamma0; + const real_t smallr = settings.smallr; + const real_t smallp = settings.smallp; + + real_t d, ux, uy, uz; + + d = fmax(u[Hydro::ID], smallr); + ux = u[Hydro::IU] / d; + uy = u[Hydro::IV] / d; + uz = u[Hydro::IW] / d; + + // volumic kinetic energy + const real_t eken = HALF_F * (ux * ux + uy * uy + uz * uz); + + // volumic internal energy + const real_t eint = u[Hydro::IP] / d - eken; + + valid = eint >= 0; + + if (settings.abort_when_negative_eint and !valid) + { + Kokkos::abort("Negative internal energy detected - can't proceed further"); + } + + // compute pressure + pressure = fmax((gamma0 - ONE_F) * d * eint, d * smallp); + + // compute speed of sound + c = sqrt(gamma0 * (pressure) / d); + +} // computePressure_and_SpeedOfSound - 3d + +// ================================================================================ +// ================================================================================ +/** + * Compute speed of sound (ideal gas equation of state). + * + * \param[in] u conservative variables array + * \param[out] p pressure + * \param[out] c speed of sound + * + */ +KOKKOS_INLINE_FUNCTION +void +compute_Pressure_and_SpeedOfSound(const HydroState3d & u, + real_t & pressure, + real_t & c, + const HydroSettings & settings) +{ + + bool valid = true; + compute_Pressure_and_SpeedOfSound(u, pressure, c, settings, valid); + +} // computePressure_and_SpeedOfSound - 3d + +} // namespace models + +} // namespace core + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_UTILS_HYDRO_H_ diff --git a/src/kalypsso/core/morton_utils.h b/src/kalypsso/core/morton_utils.h new file mode 100644 index 0000000..7df3eaa --- /dev/null +++ b/src/kalypsso/core/morton_utils.h @@ -0,0 +1,527 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file morton_utils.h + * Some useful routines to compute / handle Morton indexes. + * + * Morton curve (or Z-curve) is a space filling curve, + * i.e. a mapping from an n-dimensional space to 1d). + * + * \sa https://en.wikipedia.org/wiki/Z-order_curve + * + * + * Some of the following routines are adapted from: + * http://www.forceflow.be/2013/10/07/morton-encodingdecoding-through-bit-interleaving-implementations/ + * + * This blog provides performances comparison between 3 methods: + * - "For loop" method + * - "Magic bits" method + * - "Lookup table" method + * + * Here, we only use the "Magic bits" method, also used PABLO. + * + * Other interesting references: + * - libmorton, https://github.com/Forceflow/libmorton + * - mortonlib, https://github.com/aavenel/mortonlib + * - http://bitmath.blogspot.fr/2012/11/tesseral-arithmetic-useful-snippets.html + * + */ +#ifndef KALYPSSO_CORE_MORTON_UTILS_H +#define KALYPSSO_CORE_MORTON_UTILS_H + +#include // for uint32_t, uint64_t, etc... +#include // for std::size_t + +#include // for ComponentIndex3D +#include + +#include // for KOKKOS_INLINE_FUNCTION + +namespace kalypsso +{ + +/** + * Helper method to encode a Morton key from the cartesian coordinates. + * Separate bits from a given integer "dim" positions apart and + * inserting zeros in between. + * + * e.g. "100" becomes "01|00|00" in 2D + * "100" becomes "001|000|000" in 3D + */ +template +KOKKOS_INLINE_FUNCTION uint64_t +splitBy3(uint32_t a); + +/* 1D version */ +template <> +KOKKOS_INLINE_FUNCTION uint64_t +splitBy3<1>(uint32_t a) +{ + + uint64_t x = a; + + return x; + +} // splitBy3<1> + +/* 2D version */ +template <> +KOKKOS_INLINE_FUNCTION uint64_t +splitBy3<2>(uint32_t a) +{ + + // we take all 32 bits (this is different in 3D) + // because the result must hold in an uint64_t ( 2x21=42 bits --> uint64_t is enough ) + uint64_t x = a & 0xffffffff; + x = (x | x << 16) & 0xffff0000ffff; + x = (x | x << 8) & 0xff00ff00ff00ff; + x = (x | x << 4) & 0xf0f0f0f0f0f0f0f; + x = (x | x << 2) & 0x3333333333333333; + x = (x | x << 1) & 0x5555555555555555; + + return x; + +} // splitBy3<2> + +/* 3D version */ +template <> +KOKKOS_INLINE_FUNCTION uint64_t +splitBy3<3>(uint32_t a) +{ + + // we only look at the first 21 bits + // because the result must hold in an uint64_t : + // 3x21=63 bits --> uint64_t is enough ) + + uint64_t x = a & 0x1fffff; + + // clang-format off + x = (x | x << 32) & 0x1f00000000ffff; // shift left 32 bits, OR with self, and 00011111000000000000000000000000000000001111111111111111 + x = (x | x << 16) & 0x1f0000ff0000ff; // shift left 32 bits, OR with self, and 00011111000000000000000011111111000000000000000011111111 + x = (x | x << 8) & 0x100f00f00f00f00f; // shift left 32 bits, OR with self, and 0001000000001111000000001111000000001111000000001111000000000000 + x = (x | x << 4) & 0x10c30c30c30c30c3; // shift left 32 bits, OR with self, and 0001000011000011000011000011000011000011000011000011000100000000 + x = (x | x << 2) & 0x1249249249249249; + // clang-format on + + return x; + +} // splitBy3<3> + +/** + * A simple class holding several integers. + */ +template +struct index_t; + +template <> +struct index_t<2> +{ + + static constexpr int ndim = 2; + + uint32_t data[ndim]; + + KOKKOS_INLINE_FUNCTION + index_t() {} + + KOKKOS_INLINE_FUNCTION + index_t(int i, int j) + { + data[0] = i; + data[1] = j; + } + + KOKKOS_INLINE_FUNCTION + uint32_t + operator[](std::size_t i) const + { + return data[i]; + } + + KOKKOS_INLINE_FUNCTION + uint32_t & + operator[](std::size_t i) + { + return data[i]; + } + +}; // index_t<2> + +template <> +struct index_t<3> +{ + + static constexpr int ndim = 3; + + uint32_t data[ndim]; + + KOKKOS_INLINE_FUNCTION + index_t() {} + + KOKKOS_INLINE_FUNCTION + index_t(int i, int j, int k) + { + data[0] = i; + data[1] = j; + data[2] = k; + } + + KOKKOS_INLINE_FUNCTION + uint32_t + operator[](std::size_t i) const + { + return data[i]; + } + + KOKKOS_INLINE_FUNCTION + uint32_t & + operator[](std::size_t i) + { + return data[i]; + } + +}; // index_t<3> + + +/** + * Encode / compute Morton key from integer cartesian coordinate (i,j,k). + * + * In 2D, cartesian coordinates x,y can be as large as 2^32 (about 4e9) + * In 3D, cartesian coordinates x,y,z must be lower than 2^21 = 2097152 + * + * \param[in] index cartesian coordinates (x,y,z) + * \return the Morton key. + */ +template +KOKKOS_INLINE_FUNCTION uint64_t +compute_morton_key(const index_t & index) +{ + uint64_t key = 0; + if (dim == 1) + { + key = index[IX]; + } + else if (dim == 2) + { + key |= splitBy3(index[IX]) | splitBy3(index[IY]) << 1; + } + else if (dim == 3) + { + key |= splitBy3(index[IX]) | splitBy3(index[IY]) << 1 | splitBy3(index[IZ]) << 2; + } + + return key; + +} // compute_morton_key + +/** another 2d version of morton key */ +KOKKOS_INLINE_FUNCTION +uint64_t +compute_morton_key(const uint32_t ix, const uint32_t iy) +{ + uint64_t key = 0; + key |= splitBy3<2>(ix) | splitBy3<2>(iy) << 1; + return key; + +} // compute_morton_key - 2d + +/** another 3d version of morton key */ +KOKKOS_INLINE_FUNCTION +uint64_t +compute_morton_key(const uint32_t ix, const uint32_t iy, const uint32_t iz) +{ + uint64_t key = 0; + key |= splitBy3<3>(ix) | splitBy3<3>(iy) << 1 | splitBy3<3>(iz) << 2; + return key; + +} // compute_morton_key - 3d + + +/** + * Helper method for Magicbits Morton key decoding. + * + * Returned value is encoded on 32 bits. + * In 2D, all bits can be significant. + * In 3D, only the last 21 bits are significant, i.e. the returned value + * must be lower than 2^21=2097152. + * + * Template parameter allows to extract either x,y or z coordinate. + * \tparam dim should be 2 or 3 + * \tparam coord should be IX, IY or IZ (from enums) + */ +template +KOKKOS_INLINE_FUNCTION uint32_t +morton_extract_bits(uint64_t key) +{ + // shift bit by dimension and thus select which coordinate to extract + key = key >> coord; + + if constexpr (dim == 2) + { + // 5 is "0101" in binary + // just mask bits to zero out bits at odd positions + // so that one can extract one out of 2 bits + key &= 0x5555555555555555; + + // clang-format off + key = (key ^ (key >> 1)) & 0x3333333333333333; + key = (key ^ (key >> 2)) & 0x0f0f0f0f0f0f0f0f; + key = (key ^ (key >> 4)) & 0x00ff00ff00ff00ff; + key = (key ^ (key >> 8)) & 0x0000ffff0000ffff; + key = (key ^ (key >> 16)) & 0x00000000ffffffff; + // clang-format on + } + else if constexpr (dim == 3) + { + // 249 pattern is |0010|0100|1001| in binary notation + // so that on can extract one out of 3 bits + key &= 0x1249249249249249; + + // clang-format off + key = (key ^ (key >> 2)) & 0x30c30c30c30c30c3; + key = (key ^ (key >> 4)) & 0xf00f00f00f00f00f; + key = (key ^ (key >> 8)) & 0x00ff0000ff0000ff; + key = (key ^ (key >> 16)) & 0x00ff00000000ffff; + key = (key ^ (key >> 32)) & 0x1fffff; + // clang-format on + } + + return static_cast(key); + +} // morton_extract_bits + +/** + * Get morton key of a face neighbor at same level + * given current octant Morton key. + * + * \param[in] key is the morton key of current octant + * \param[in] level AMR level of current octant + * \param[in] face is faceId (FACE_XMIN, FACE_XMAX, ....) + * + * \return neighbor morton key + * + * \todo maybe try to extract only the coordinate of interest not all + * three coordinates. + * \todo all the addition could probably be implemented more efficiently + * with bit manipulation + * + */ +KOKKOS_INLINE_FUNCTION +uint64_t +get_neighbor_morton(uint64_t key, uint8_t level, uint8_t face) +{ + auto x = morton_extract_bits<3, IX>(key); + auto y = morton_extract_bits<3, IY>(key); + auto z = morton_extract_bits<3, IZ>(key); + + constexpr int MAX_LEVEL = 20; + auto length = uint32_t(1) << (MAX_LEVEL - level); + + // domain length + auto total_length = uint32_t(1) << MAX_LEVEL; + + if (face == 0) + { + if (x < length) + x += total_length; + x -= length; + } + + if (face == 1) + { + x += length; + if (x >= total_length) + x -= total_length; + } + + if (face == 2) + { + if (y < length) + y += total_length; + y -= length; + } + + if (face == 3) + { + y += length; + if (y >= total_length) + y -= total_length; + } + + if (face == 4) + { + if (z < length) + z += total_length; + z -= length; + } + + if (face == 5) + { + z += length; + if (z >= total_length) + z -= total_length; + } + + return compute_morton_key(x, y, z); + +} // get_neighbor_morton + +/** + * Get morton key of a face neighbor at same level + * given current octant Morton key. + * + * \param[in] key is the morton key of current octant + * \param[in] level AMR level of current octant + * \param[in] level_n AMR level of neigh octant + * \param[in] face is faceId (FACE_XMIN, FACE_XMAX, ....) + * \param[in] neigh_id is neighbor id + * + * \return neighbor morton key + * + * \note neigh_id should 0 or 1 in 2D + * \note neigh_id should 0, 1, 2 or 3 in 3D + * + * \todo maybe try to extract only the coordinate of interest not all + * three coordinates. + * \todo all the addition could probably be implemented more efficiently + * with bit manipulation + * + */ +KOKKOS_INLINE_FUNCTION +uint64_t +get_neighbor_morton(uint64_t key, uint8_t level, uint8_t level_n, uint8_t face, uint8_t neigh_id) +{ + + uint32_t xyz[3]; + xyz[IX] = morton_extract_bits<3, IX>(key); + xyz[IY] = morton_extract_bits<3, IY>(key); + xyz[IZ] = morton_extract_bits<3, IZ>(key); + + constexpr int MAX_LEVEL = 20; + + // length of current octant + auto length = uint32_t(1) << (MAX_LEVEL - level); + + // length of neighbor + auto length_n = uint32_t(1) << (MAX_LEVEL - level_n); + + // domain length + auto total_length = uint32_t(1) << MAX_LEVEL; + + // get direction and left/right face + auto dir = face >> 1; + auto iface = face & 0x1; + + uint32_t b0 = neigh_id & 0x1; + uint32_t b1 = (neigh_id >> 1) & 0x1; + + // ===================================================== + // neighbor must be smaller or larger but not same size + // ===================================================== + if (level_n != level) + { + + // ====================================== + if (dir == IX) + { + + if (iface == 0) + { + xyz[IX] = (xyz[IX] < length_n) ? xyz[IX] + total_length - length_n : xyz[IX] - length_n; + } + else + { + xyz[IX] += length; + if (xyz[IX] >= total_length) + xyz[IX] -= total_length; + } + + + if (level_n > level) + { + xyz[IY] = b0 == 0 ? xyz[IY] : xyz[IY] + length_n; + xyz[IZ] = b1 == 0 ? xyz[IZ] : xyz[IZ] + length_n; + } + else + { + xyz[IY] = b0 == 0 ? xyz[IY] : xyz[IY] - length; + xyz[IZ] = b1 == 0 ? xyz[IZ] : xyz[IZ] - length; + } + } + + // ====================================== + if (dir == IY) + { + if (iface == 0) + { + xyz[IY] = (xyz[IY] < length_n) ? xyz[IY] + total_length - length_n : xyz[IY] - length_n; + } + else + { + xyz[IY] += length; + if (xyz[IY] >= total_length) + xyz[IY] -= total_length; + } + + if (level_n > level) + { + xyz[IX] = b0 == 0 ? xyz[IX] : xyz[IX] + length_n; + xyz[IZ] = b1 == 0 ? xyz[IZ] : xyz[IZ] + length_n; + } + else + { + xyz[IX] = b0 == 0 ? xyz[IX] : xyz[IX] - length; + xyz[IZ] = b1 == 0 ? xyz[IZ] : xyz[IZ] - length; + } + } + + // ====================================== + if (dir == IZ) + { + if (iface == 0) + { + xyz[IZ] = (xyz[IZ] < length_n) ? xyz[IZ] + total_length - length_n : xyz[IZ] - length_n; + } + else + { + xyz[IZ] += length; + if (xyz[IZ] >= total_length) + xyz[IZ] -= total_length; + } + + if (level_n > level) + { + xyz[IX] = b0 == 0 ? xyz[IX] : xyz[IX] + length_n; + xyz[IY] = b1 == 0 ? xyz[IY] : xyz[IY] + length_n; + } + else + { + xyz[IX] = b0 == 0 ? xyz[IX] : xyz[IX] - length; + xyz[IY] = b1 == 0 ? xyz[IY] : xyz[IY] - length; + } + } + + return compute_morton_key(xyz[IX], xyz[IY], xyz[IZ]); + + } // end neighbor is smaller or larger + + // should never reach here + return compute_morton_key(0, 0, 0); + +} // get_neighbor_morton + +// ==================================================================== +// ==================================================================== +KOKKOS_INLINE_FUNCTION +real_t +levelToSize(uint8_t level) +{ + + return 1.0 / (1 << level); +} + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_MORTON_UTILS_H diff --git a/src/kalypsso/core/multimaterial_utils.h b/src/kalypsso/core/multimaterial_utils.h new file mode 100644 index 0000000..dcb4dd2 --- /dev/null +++ b/src/kalypsso/core/multimaterial_utils.h @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file multimaterial_utils.h + */ +#ifndef KALYPSSO_CORE_MULTIMATERIAL_UTILS_H_ +#define KALYPSSO_CORE_MULTIMATERIAL_UTILS_H_ + +#include // for assertm +#include + +namespace kalypsso +{ + +/** + * Read material id in region. + * + * \param[in] i_region region id + * \param[in] config_map application's configuration parameters + */ +inline int +get_material_id_in_region(const int32_t i_region, const ConfigMap & config_map) +{ + const auto section = "region" + std::to_string(i_region); + const int i_mat = config_map.getInteger(section, "material_id", -1); + return i_mat; +} + +/** + * Get region to material mapping (number of regions is known at compile time). + */ +template +Kokkos::Array +get_region_to_material_id_mapping(ConfigMap const & config_map) +{ + Kokkos::Array region_to_material_id; + for (int i_region = 0; i_region < static_cast(NB_REGIONS); ++i_region) + { + region_to_material_id[i_region] = get_material_id_in_region(i_region, config_map); + } + + return region_to_material_id; + +} // get_region_to_material_id_mapping + +/** + * Get region to material mapping (number of regions is only known at runtime time). + */ +template +Kokkos::View +get_region_to_material_id_mapping_view(ConfigMap const & config_map, int nb_regions) +{ + Kokkos::View region_to_material_id("region_to_material_id", + static_cast(nb_regions)); + auto region_to_material_id_host = Kokkos::create_mirror_view(region_to_material_id); + + for (int i_region = 0; i_region < nb_regions; ++i_region) + { + region_to_material_id_host(i_region) = get_material_id_in_region(i_region, config_map); + } + Kokkos::deep_copy(region_to_material_id, region_to_material_id_host); + + return region_to_material_id; + +} // get_region_to_material_id_mapping_view + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_MULTIMATERIAL_UTILS_H_ diff --git a/src/kalypsso/core/myKokkos_Sort.h b/src/kalypsso/core/myKokkos_Sort.h new file mode 100644 index 0000000..b43714b --- /dev/null +++ b/src/kalypsso/core/myKokkos_Sort.h @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file myKokkos_Sort.h + * A custom variation for 2D bucket sort (not available right now in Kokkos). + */ + +#ifndef KALYPSSO_CORE_MY_KOKKOS_SORT_HPP_ +#define KALYPSSO_CORE_MY_KOKKOS_SORT_HPP_ + +#include + +#include + +// ===================================================================== +// ===================================================================== +// ===================================================================== +namespace Kokkos +{ + +template +struct BinOp2D +{ + int max_bins_[2]; + double mul_[2]; + typename KeyViewType::non_const_value_type range_[2]; + typename KeyViewType::non_const_value_type min_[2]; + + BinOp2D() {} + + BinOp2D(int max_bins__[], + typename KeyViewType::const_value_type min[], + typename KeyViewType::const_value_type max[]) + { + max_bins_[0] = max_bins__[0]; + max_bins_[1] = max_bins__[1]; + mul_[0] = 1.0 * max_bins__[0] / (max[0] - min[0]); + mul_[1] = 1.0 * max_bins__[1] / (max[1] - min[1]); + range_[0] = max[0] - min[0]; + range_[1] = max[1] - min[1]; + min_[0] = min[0]; + min_[1] = min[1]; + } + + template + KOKKOS_INLINE_FUNCTION int + bin(ViewType & keys, const int & i) const + { + return int(((int(mul_[0] * (keys(i, 0) - min_[0])) * max_bins_[1]) + + int(mul_[1] * (keys(i, 1) - min_[1])))); + } + + KOKKOS_INLINE_FUNCTION + int + max_bins() const + { + return max_bins_[0] * max_bins_[1]; + } + + template + KOKKOS_INLINE_FUNCTION bool + operator()(ViewType & keys, iType1 & i1, iType2 & i2) const + { + if (keys(i1, 0) > keys(i2, 0)) + return true; + else if (keys(i1, 0) == keys(i2, 0)) + { + if (keys(i1, 1) > keys(i2, 1)) + return true; + } + return false; + } +}; + +} // namespace Kokkos + +#endif // KALYPSSO_CORE_MY_KOKKOS_SORT_HPP_ diff --git a/src/kalypsso/core/orchard_key.h b/src/kalypsso/core/orchard_key.h new file mode 100644 index 0000000..bb22159 --- /dev/null +++ b/src/kalypsso/core/orchard_key.h @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file orchard_key.h + * \brief + * Design a 64-bit "key" type that can be used as an entry to our adaptive mesh refinement + * hash-table data structure. + * + * This is adapted from reference: + * An octree-based, cartesian navier–stokes solver for modern cluster architectures, + * Dylan Jude, Jay Sitaraman and, Andrew Wissink, The journal of Supercomputing, 78 (3), + * June 2022. + * + * This article only describe 3D; here we also define an equivalent for 2D. See below orchard_key_t. + * + */ +#ifndef KALYPSSO_CORE_ORCHARD_KEY_H +#define KALYPSSO_CORE_ORCHARD_KEY_H + +#include +#include +#include +#include +#include + +#include + +#include + +namespace kalypsso +{ + +/** + * \struct orchard_key_t + * + * Define type used as a key to a Kokkos::UnorderedMap data structure, adapted from + * Jude etal, J. of Supercomputing, 78(3), June 2022. + * + * Bit signification: + * + * ======================================================================== + * 2D: + * + * 12 bits 44 bits 6 bits 2 bits + * <---------><-----------------><----------><------------------> + * treeId Morton Id level outside status + * + * This means + * - up to 2^6=64 trees in each direction (X,Y) + * - up to 23 (from 0 to 22) levels of refinement + * - level is encode on 6 bits (more than enough) + * - outside status is encoded on 2 bits, 1 bit for each type of face; e.g. + * a bulk quadrant will be encode as 00, a quadrant touching only face will be either 01 or 10, + * and a corner will be encoded as 11. + * + * ======================================================================== + * 3D: + * + * 15 bits 42 bits 4 bits 3 bits + * <---------><-----------------><----------><------------------> + * treeId Morton Id level outside status + * + * This means + * - up to 2^5=32 trees in each direction (X,Y,Z) + * - up to 15 (from 0 14) levels of refinement + * - level is encoded on 4 bits (just enough) + * - outside status is encoded on 3 bits, 1 bit for each type of face; e.g. + * a bulk quadrant will be encode as 000, a quadrant touching only face will be either 001 or 010 + * or 100, en edge by 011, 101, or 110 and a corner will be encoded as 111. + */ +template +struct orchard_key_t; + +#include "orchard_key_impl_2d.h" +#include "orchard_key_impl_3d.h" + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_ORCHARD_KEY_H diff --git a/src/kalypsso/core/orchard_key_base.h b/src/kalypsso/core/orchard_key_base.h new file mode 100644 index 0000000..d7272ac --- /dev/null +++ b/src/kalypsso/core/orchard_key_base.h @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file orchard_key_base.h + */ +#ifndef KALYPSSO_CORE_ORCHARD_KEY_BASE_H +#define KALYPSSO_CORE_ORCHARD_KEY_BASE_H + +#include +#include + +#include + +namespace kalypsso +{ + +//! base type used internally in orchard key implementation +using key_t = uint64_t; + +//! type alias for storing octant memory index in hashmap +using iOct_t = int64_t; + + +/** + * Kind of namespace struct used to define type alias for Kokkos view of orchard keys. + */ +template +struct orchard_key_base_t +{ + using view_t = typename Kokkos::View; + + using view_host_t = typename Kokkos::View; +}; + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_ORCHARD_KEY_BASE_H diff --git a/src/kalypsso/core/orchard_key_impl_2d.h b/src/kalypsso/core/orchard_key_impl_2d.h new file mode 100644 index 0000000..2261c2c --- /dev/null +++ b/src/kalypsso/core/orchard_key_impl_2d.h @@ -0,0 +1,1352 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +KALYPSSO_DISABLE_CONVERSION_WARNINGS_PUSH() +KALYPSSO_DISABLE_NVCC_WARNINGS_PUSH() + +/** + * \file orchard_key_impl_2d.h + */ +// ==================================================================== +// ==================================================================== +// ==================================================================== +template <> +struct orchard_key_t<2> : public BitFieldInteger +{ + orchard_key_t(const orchard_key_t &) = default; + + orchard_key_t(orchard_key_t &&) = default; + + orchard_key_t & + operator=(const orchard_key_t &) = default; + + orchard_key_t & + operator=(orchard_key_t &&) = default; + + static constexpr uint8_t ONE_U = 1; + + /** dimension */ + static constexpr uint8_t DIM = 2; + + /** number of bits used to encode outside status */ + static constexpr uint8_t OUTSIDE_BITWIDTH = 2; + + /** number of bits used to encode level */ + static constexpr uint8_t LEVEL_BITWIDTH = 6; + + /** number of bits used to encode morton octant */ + static constexpr uint8_t MORTON_OCTANT_BITWIDTH = 44; + + /** number of bits used to encode morton tree */ + static constexpr uint8_t MORTON_TREE_BITWIDTH = 12; + + /** bit offset to the first bit where outside status bits start */ + static constexpr uint8_t OUTSIDE_OFFSET = 0; + + /** bit offset to the first bit where level bits start : 0 + 2 = 2 */ + static constexpr uint8_t LEVEL_OFFSET = OUTSIDE_OFFSET + OUTSIDE_BITWIDTH; + + /** bit offset to the first bit where the morton octant bits start : 2 + 6 = 8 */ + static constexpr uint8_t MORTON_OCTANT_OFFSET = LEVEL_OFFSET + LEVEL_BITWIDTH; + + /** bit offset to the first bit where tree bits start : 8 + 44 = 52 */ + static constexpr uint8_t MORTON_TREE_OFFSET = MORTON_OCTANT_OFFSET + MORTON_OCTANT_BITWIDTH; + + /** bits for outside status */ + static constexpr uint64_t OUTSIDE_MASK = 0x0000000000000003; + + /** bit mask to extract LEVEL (6 bits right after outside status) */ + static constexpr uint64_t LEVEL_MASK = 0x00000000000000FC; + + /** bit mask to extract LEVEL (the middle 44 bits) */ + static constexpr uint64_t MORTON_OCTANT_MASK = 0x000FFFFFFFFFFF00; + + /** bit mask to extract MORTON_TREE (the upper 12 bits) */ + static constexpr uint64_t MORTON_TREE_MASK = 0xFFF0000000000000; + + /** level is in range [0, MAX_LEVEL] */ + static constexpr uint16_t MAX_LEVEL = MORTON_OCTANT_BITWIDTH / 2; + + /** number of levels : level is in range 0 to MAX_LEVELS */ + static constexpr uint16_t NUM_LEVELS = MAX_LEVEL + 1; + + /** length in logical units of the largest (root) quadrant */ + static constexpr uint32_t ROOT_LENGTH = 1 << MAX_LEVEL; + + /** logical tree size */ + static constexpr uint32_t TREE_SIZE = 1 << MAX_LEVEL; + + /** bit mask to keep only relevant bits for checking tree size */ + static constexpr uint32_t TREE_SIZE_MASK = TREE_SIZE - 1; + + /** maximum number of trees per direction */ + static constexpr uint16_t MAX_NB_TREES_PER_DIR = 1 << (MORTON_TREE_BITWIDTH / DIM); + + /** number of children */ + static constexpr uint32_t NB_CHILDREN = 1 << DIM; + + /** number of neighbors across a face at fine level */ + static constexpr uint32_t NB_FACE_NEIGHBORS_FINE = NB_CHILDREN / 2; + + /** number of neighbors across an edge at fine level (no edge in 2d) */ + static constexpr uint32_t NB_EDGE_NEIGHBORS_FINE = 0; + + /** maximum number of neighbors */ + static constexpr uint32_t MAX_NUM_NEIGHBORS = NB_CHILDREN / 2; + + using BitFieldInteger::BitFieldInteger; + DECLARE_BIT(is_touching_face_X, OUTSIDE_OFFSET) + DECLARE_BIT(is_touching_face_Y, OUTSIDE_OFFSET + 1) + DECLARE_CASTED_FIELD(outside_status, OUTSIDE_OFFSET, OUTSIDE_BITWIDTH, uint8_t) + DECLARE_CASTED_FIELD(level, LEVEL_OFFSET, LEVEL_BITWIDTH, uint8_t) + DECLARE_CASTED_FIELD(morton_octant, MORTON_OCTANT_OFFSET, MORTON_OCTANT_BITWIDTH, uint64_t) + DECLARE_CASTED_FIELD(morton_tree, MORTON_TREE_OFFSET, MORTON_TREE_BITWIDTH, uint16_t) + + // ==================================================================== + //! get the outside status of a given key / quadrant + KOKKOS_INLINE_FUNCTION static bool + is_outside(uint64_t const & key) + { + + return is_touching_face_X(key) or is_touching_face_Y(key); + + } // is_outside + + // ==================================================================== + //! return true only if key is outside and "touching" inside domain by face along given "dir" + //! \note a corner outside is touching inside domain by 2 faces. + KOKKOS_INLINE_FUNCTION static bool + is_outside_dir(uint64_t const & key, Dir::dir_t dir) + { + + return ((dir == Dir::X) and is_touching_face_X(key)) or + ((dir == Dir::Y) and is_touching_face_Y(key)); + + } // is_outside_dir + + // ==================================================================== + //! modify outside status of a given key. + KOKKOS_INLINE_FUNCTION static void + set_outside_bit(uint64_t & key, Face::face_t const & face) + { + + if (face == Face::XMIN or face == Face::XMAX) + set_is_touching_face_X(key, true); + else if (face == Face::YMIN or face == Face::YMAX) + set_is_touching_face_Y(key, true); + + } // set_outside_bit + + // ==================================================================== + //! change outside status. By resetting these bits, key becomes an inside key + KOKKOS_INLINE_FUNCTION static void + reset_outside_bits(uint64_t & key) + { + set_is_touching_face_X(key, false); + set_is_touching_face_Y(key, false); + } + + // // ==================================================================== + // /** extract Morton tree */ + // KOKKOS_INLINE_FUNCTION static uint16_t + // get_tree_morton_index(uint64_t key) + // { + // return (key & MORTON_TREE_MASK); + // } // get_tree_morton_index + + // // ==================================================================== + // /** extract Morton quadrant */ + // KOKKOS_INLINE_FUNCTION + // static uint64_t + // get_octant_morton_index(uint64_t key) + // { + // return (key & MORTON_OCTANT_MASK) >> MORTON_OCTANT_OFFSET; + // } // get_octant_morton_index + + // // ==================================================================== + // /** extract level */ + // KOKKOS_INLINE_FUNCTION + // static uint8_t + // get_level(uint64_t key) + // { + // return (key & LEVEL_MASK) >> LEVEL_OFFSET; + // } // get_level + + // ==================================================================== + template + KOKKOS_INLINE_FUNCTION static uint16_t + get_tree_coord(uint64_t key) + { + static_assert(direction == IX || direction == IY, "Only X and Y directions are possible in 2D"); + + auto tree_coord = morton_tree(key); + + // select direction + tree_coord = tree_coord >> direction; + + // only 7 bits are significant + tree_coord &= 0x1555; + + tree_coord = (tree_coord ^ (tree_coord >> 1)) & 0x3333; + tree_coord = (tree_coord ^ (tree_coord >> 2)) & 0x0f0f; + tree_coord = (tree_coord ^ (tree_coord >> 4)) & 0x00ff; + + return static_cast(tree_coord); + + } // get_tree_coord + + // ==================================================================== + KOKKOS_INLINE_FUNCTION static Kokkos::Array + get_tree_coords(uint64_t key) + { + // clang-format off + return Kokkos::Array{ get_tree_coord(key), + get_tree_coord(key) }; + // clang-format on + } // get_tree_coords + + // ==================================================================== + /** + * get octant x,y or z coordinate from an orchard key. + * + * \param[in] key the orchard key + */ + template + KOKKOS_INLINE_FUNCTION static uint32_t + get_octant_coord(uint64_t key) + { + static_assert(direction == IX || direction == IY, "Only X and Y directions are possible in 2D"); + + // extract bits corresponding to morton octant + key = morton_octant(key); + + // select direction + key = key >> direction; + + // only 22 bits are significant, so apply a 44 bits wide mask + key &= 0x55555555555; + + // clang-format off + key = (key ^ (key >> 1)) & 0x3333333333333333; + key = (key ^ (key >> 2)) & 0x0f0f0f0f0f0f0f0f; + key = (key ^ (key >> 4)) & 0x00ff00ff00ff00ff; + key = (key ^ (key >> 8)) & 0x0000ffff0000ffff; + key = (key ^ (key >> 16)) & 0x00000000ffffffff; + // clang-format on + + return static_cast(key); + + } // get_octant_coord + + // ==================================================================== + /** + * get octant x,y or z coordinate from an orchard key. + * + * \param[in] key the orchard key + */ + KOKKOS_INLINE_FUNCTION static Kokkos::Array + get_octant_coords(uint64_t key) + { + // clang-format off + return Kokkos::Array{ get_octant_coord(key), + get_octant_coord(key) }; + // clang-format on + } // get_octant_coords + + // ==================================================================== + /** + * Take an integer representing one of the tree coordinate, and modify its + * binary representation as preliminary step before encoding morton key. + * + * \param[in] coord must be in range [0,63] (only 6 bits are significant) + * + * \return an integer with interleaved 0 in between each bits of input. + */ + KOKKOS_INLINE_FUNCTION static uint16_t + split_morton_tree(uint16_t coord) + { + // extract the last 6 bits + uint16_t x = coord & 0x003F; + + x = (x | x << 4) & 0x0f0f; + x = (x | x << 2) & 0x3333; + x = (x | x << 1) & 0x5555; + + return x; + + } // split_morton_tree + + // ==================================================================== + /** + * Take an integer representing one of the octant coordinate, and modify its + * binary representation as preliminary step before encoding morton key. + * + * \param[in] coord must be in range [0,2^22-1] (only 22 bits are significant) + * + * the result is stored on a 64 bits integer because 2x22 bits = 44 bits (minimum) + * + * \return an integer with interleaved 0 in between each bits of input. + */ + KOKKOS_INLINE_FUNCTION static uint64_t + split_morton_octant(uint32_t coord) + { + // extract the last 22 bits + uint64_t x = coord & 0x3FFFFF; + + // clang-format off + x = (x | x << 16) & 0xffff0000ffff; + x = (x | x << 8) & 0xff00ff00ff00ff; + x = (x | x << 4) & 0xf0f0f0f0f0f0f0f; + x = (x | x << 2) & 0x3333333333333333; + x = (x | x << 1) & 0x5555555555555555; + // clang-format on + + return x; + + } // split_morton_octant + + // ==================================================================== + /** + * encode morton tree index from x,y coordinates + * \param[in] treeCoord is an an array of tree coordinates in the p4est brick connectivity + */ + KOKKOS_INLINE_FUNCTION static uint16_t + encode_morton_tree(Kokkos::Array treeCoord) + { + uint16_t key = 0; + key |= split_morton_tree(treeCoord[IX]) | split_morton_tree(treeCoord[IY]) << 1; + + return key; + } // encode_morton_tree + + // ==================================================================== + /** + * encode morton tree index from x,y coordinates + * \param[in] tree_x is tree cartesian coordinate along X in the p4est brick connectivity + * \param[in] tree_y is tree cartesian coordinate along Y in the p4est brick connectivity + */ + KOKKOS_INLINE_FUNCTION static uint16_t + encode_morton_tree(uint16_t tree_x, uint16_t tree_y) + { + uint16_t key = 0; + key |= split_morton_tree(tree_x) | split_morton_tree(tree_y) << 1; + + return key; + } // encode_morton_tree + + // ==================================================================== + /** + * encode morton tree index from x,y coordinates + * + * \param[in,out] orchard_key + * \param[in] treeCoord is an an array of tree coordinates in the p4est brick connectivity + */ + KOKKOS_INLINE_FUNCTION static void + encode_morton_tree(uint64_t & orchard_key, Kokkos::Array treeCoord) + { + uint16_t tree_key = 0; + tree_key |= split_morton_tree(treeCoord[IX]) | split_morton_tree(treeCoord[IY]) << 1; + + set_morton_tree(orchard_key, tree_key); + + } // encode_morton_tree + + // ==================================================================== + /** + * encode morton octant index from octant x,y coordinates + * \param[in] octCoord must be in [0,2**22-1]^2 + */ + KOKKOS_INLINE_FUNCTION static void + encode_morton_octant(uint64_t & orchard_key, Kokkos::Array octCoord) + { + uint64_t key = 0; + key |= split_morton_octant(octCoord[IX]) | split_morton_octant(octCoord[IY]) << 1; + + set_morton_octant(orchard_key, key); + + } // encode_morton_octant + + // ==================================================================== + /** + * encode orchard key from octant x,y coordinates, tree x,y coords and level + * \param[in] octCoord must be in [0,2**22-1]^2 + * + */ + KOKKOS_INLINE_FUNCTION static uint64_t + encode_orchard(Kokkos::Array treeCoord, + Kokkos::Array octCoord, + uint8_t level, + uint8_t outside_status) + { + uint64_t key = 0; + + set_outside_status(key, outside_status); + set_level(key, level); + encode_morton_octant(key, octCoord); + encode_morton_tree(key, treeCoord); + + return key; + + } // encode_orchard + + // ==================================================================== + /** + * encode orchard key from octant x,y coordinates, tree x,y coords and level + * \param[in] octCoord must be in [0,2**22-1]^2 + * + * key is always zero initialized, the outside status bits will be zero (i.e. inside quadrant). + */ + KOKKOS_INLINE_FUNCTION static uint64_t + encode_orchard(Kokkos::Array treeCoord, + Kokkos::Array octCoord, + uint8_t level) + { + uint64_t key = 0; + + set_level(key, level); + encode_morton_octant(key, octCoord); + encode_morton_tree(key, treeCoord); + + return key; + + } // encode_orchard + + // ==================================================================== + /** + * encode orchard key from octant x,y coordinates, tree id and level + * \param[in] octCoord must be in [0,2**22-1]^2 + * + * key is always zero initialized, the outside bit will be zero. + */ + KOKKOS_INLINE_FUNCTION static uint64_t + encode_orchard(uint16_t treeid, + Kokkos::Array octCoord, + uint8_t level, + uint8_t outside_status = 0) + { + uint64_t key = 0; + + set_outside_status(key, outside_status); + set_level(key, level); + encode_morton_octant(key, octCoord); + set_morton_tree(key, treeid); + + return key; + + } // encode_orchard + + /** + * Octant logical length from level. + * + * \input[in] an AMR level + * + * \return octant length + */ + KOKKOS_INLINE_FUNCTION + static uint32_t + octantLength_from_level(uint8_t level) + { + return (1 << (MAX_LEVEL - level)); + } // octantLength_from_level + + /** + * Octant logical length. + * + * \input[in] a orchard key + * + * \return octant length + */ + KOKKOS_INLINE_FUNCTION + static uint32_t + octantLength(uint64_t orchard_key) + { + return octantLength_from_level(level(orchard_key)); + } // octantLength + + /** + * Last offset returns the logical coordinate (x, y, or z) + * of an octant that "touches" the tree border on right. + * + * Just as a reminder, an octant that "touches" the tree border on left, e.g. along x axis, will + * have x logical coordinate equal to zero. + * + */ + KOKKOS_INLINE_FUNCTION + static uint32_t + last_offset(uint64_t orchard_key) + { + return ROOT_LENGTH - octantLength(orchard_key); + } // last_offset + + KOKKOS_INLINE_FUNCTION + static uint32_t + last_offset(uint8_t level) + { + return ROOT_LENGTH - octantLength_from_level(level); + } // last_offset + + /** + * Get the orchard key of the oldest brother of a given key. + * + * The oldest brother is defined as the first octant of a family of 2^dim octants. + * + * \param[in] key a given orchard key + * + * \return orchard key of the oldest brother. + */ + KOKKOS_INLINE_FUNCTION + static uint64_t + oldest_brother(uint64_t key) + { + // oldest brother must have coordinates that are divisible by + // 2*octantLength we just need to zero-out the bits of the remainder + + const auto lev = level(key); + + // 1. remove all the bits + uint64_t key_new = key >> (DIM * (NUM_LEVELS - lev) + LEVEL_BITWIDTH + OUTSIDE_BITWIDTH); + + key_new = (key_new << (DIM * (NUM_LEVELS - lev) + LEVEL_BITWIDTH + OUTSIDE_BITWIDTH)); + + set_level(key_new, level(key)); + set_outside_status(key_new, outside_status(key)); + + return key_new; + + } // oldest_brother + + /** + * Get the orchard key of the oldest brother of a given key (old version). + * + * The oldest brother is defined as the first octant of a family of 2^dim octants. + * + * \param[in] key is a given orchard key + * + * \return orchard key of the oldest brother. + */ + KOKKOS_INLINE_FUNCTION + static uint64_t + oldest_brother_old(uint64_t key) + { + + // octant logical length + const auto length = octantLength(key); + + // twice octant size minus 1 + const uint32_t mask = (1 << (NUM_LEVELS - level(key))) - 1; + + // input key octant coordinates + auto x = get_octant_coord(key); + auto y = get_octant_coord(key); + + // get oldest brother octant input key + x = (x & mask) == 0 ? x : x - length; + y = (y & mask) == 0 ? y : y - length; + + // oldest brother has the same level, and belongs to the same tree + const auto oct_level = level(key); + const auto tree_id = morton_tree(key); + + return encode_orchard(tree_id, { x, y }, oct_level, outside_status(key)); + + } // oldest_brother_old + + /** + * Is the first octant of a family of 2^dim octants ? + * + * \param[in] orchard_key + */ + KOKKOS_INLINE_FUNCTION + static bool + isFirstOctantOfFamily(uint64_t orchard_key) + { + + // // twice octant size minus 1 + // const uint32_t mask = (1 << (NUM_LEVELS - level(orchard_key))) - 1; + + // // check if x,y,z are integral multiple of mask+1 (= 2x octant length) + // return (((get_octant_coord(orchard_key) & mask) == 0) and + // ((get_octant_coord(orchard_key) & mask) == 0)); + + return orchard_key == oldest_brother(orchard_key); + + } // isFirstOctantOfFamily + + /** + * Get the orchard key of the father of a given key. + * + * The father is defined as the octant at level l-1 (coarse) with the same coordinate as the + * oldest brother. + * + * \param[in] key a given orchard key + * + * \return orchard key of the father + */ + KOKKOS_INLINE_FUNCTION + static uint64_t + father(uint64_t key) + { + + // octant logical length + const auto length = octantLength(key); + + // twice octant size minus 1 + const uint32_t mask = (1 << (NUM_LEVELS - level(key))) - 1; + + // input key octant coordinates + auto x = get_octant_coord(key); + auto y = get_octant_coord(key); + + // get oldest brother octant input key + x = (x & mask) == 0 ? x : x - length; + y = (y & mask) == 0 ? y : y - length; + + // father octant is at coarse level, and belongs to the same tree + const auto oct_level = level(key) - 1; + const auto tree_id = morton_tree(key); + + return encode_orchard(tree_id, { x, y }, oct_level, outside_status(key)); + + } // father + + /** + * Compute child id of given key with respect to its father octant. + * + * The father is defined as the octant at level l-1 (coarse) with the same coordinate as the + * oldest brother. + * + * \param[in] key a given orchard key + * + * \return child id + */ + KOKKOS_INLINE_FUNCTION + static uint8_t + child_id(uint64_t key) + { + uint8_t id = 0; + + // twice octant size minus 1 + const uint32_t mask = (1 << (NUM_LEVELS - level(key))) - 1; + + // input key octant coordinates + auto x = get_octant_coord(key); + auto y = get_octant_coord(key); + + // get oldest brother octant input key + id = (x & mask) == 0 ? id : id + 1; + id = (y & mask) == 0 ? id : id + 2; + + return id; + + } // child_id + + /** + * Get the orchard key of the eldest child of a given key. + * + * The eldest child is defined as the octant at level l+1 (refined) with the same coordinate as + * the father. + * + * \param[in] key a given orchard key + * + * \return orchard key of the eldest child + */ + KOKKOS_INLINE_FUNCTION + static uint64_t + eldest_child(uint64_t key) + { + + uint64_t child_key = key; + + set_level(child_key, level(key) + 1); + + return child_key; + + } // eldest_child + + /** + * Get the orchard keys of all the children of a given key. + * + * \param[in] key a given orchard key + * + * \return array of orchard keys of all the children + */ + KOKKOS_INLINE_FUNCTION + static auto + all_children(uint64_t key) -> Kokkos::Array + { + + Kokkos::Array res; + + // child octant logical length + const auto length = octantLength(key) >> 1; + + // children belong to the same tree + const auto tree_id = morton_tree(key); + + const auto oct_level = level(key) + 1; + + // input key octant coordinates + auto x = get_octant_coord(key); + auto y = get_octant_coord(key); + + // eldest child + // res[0] = eldest_child(key); + + // sweep children in Morton order + for (uint32_t i = 0; i < NB_CHILDREN; ++i) + { + // uint8_t ix = i & 0x1; + // uint8_t iy = (i & 0x2) >> 1; + res[i] = encode_orchard(tree_id, + { x + (i & 0x1) * length, y + ((i & 0x2) >> 1) * length }, + oct_level, + outside_status(key)); + } + + return res; + + } // all_children + + /** + * Get the orchard key of the nth child of a given key. + * Remember the are NB_CHILDREN child octant, enumerated in Morton order. + * + * \param[in] key a given orchard key + * + * \return orchard key of the nth child + */ + KOKKOS_INLINE_FUNCTION + static uint64_t + child(uint64_t key, uint8_t iChild) + { + + KOKKOS_ASSERT(iChild < NB_CHILDREN && "iChild is not less than NB_CHILDREN"); + + // child octant logical length + const auto length = octantLength(key) >> 1; + + // children belong to the same tree + const auto tree_id = morton_tree(key); + + const auto oct_level = level(key) + 1; + + // input key octant coordinates + auto x = get_octant_coord(key); + auto y = get_octant_coord(key); + + return encode_orchard(tree_id, + { x + (iChild & 0x1) * length, y + ((iChild & 0x2) >> 1) * length }, + oct_level, + outside_status(key)); + + } // child + + /** + * + * For a given face (Face::XMIN, etc...), return the smallest child of a finer neighbor that + * "touches" current quadrant through that given face. + * + * In the drawing below, the numbers are child ids we are talking about. + * + * This function is used when, under the assumption neighbor is at finer level, that actually + * there is a neighbor at finer level by probing the hashmap. + * + * + * -------- + * | | | + * |___|___| + * | | | + * ___ ___|_0_|___|________ + * | | | | | | + * |___|___| |___|___| + * | | | | | | + * |___|_1_|_______|_0_|___| + * | 2 | | + * |___|___| + * | | | + * |___|___| + */ + KOKKOS_INLINE_FUNCTION + static uint8_t + get_face_neighbor_smallest_child_id(Face::face_t face) + { + // if (face == Face::XMIN) + // return 1; + // else if (face == Face::XMAX) + // return 0; + // else if (face == Face::YMIN) + // return 2; + // else if (face == Face::YMAX) + // return 0; + + // if face is a right face (odd number) then return 0 + // else 2**(face/2) + return (face & 0x1) == 1 ? 0 : (1 << (face / 2)); + + } // get_face_neighbor_smallest_child_id + + /** + * + * For a given edge/corner (identified by intersection of two faces), + * return the smallest child of a finer neighbor that + * "touches" current quadrant through that given edge/corner. + * + * In the drawing below, the numbers are child ids we are talking about. + * + * This function is used when, under the assumption neighbor is at finer level, that actually + * there is a neighbor at finer level by probing the hashmap. + * + * + * ________ ________ + * | | | | | | + * |___|___| |___|___| + * | | 1 | | 0 | | + * |___|___|_______|___|___| + * | | + * | | + * | | + * _______|_______|________ + * | | 3 | | 2 | | + * |___|___| |___|___| + * | | | | | | + * |___|___| |___|___| + */ + KOKKOS_INLINE_FUNCTION + static uint8_t + get_edge_neighbor_smallest_child_id(Face::face_t face0, Face::face_t face1) + { + KOKKOS_ASSERT(((face0 == Face::XMIN) or (face0 == Face::XMAX)) && "wrong face id"); + KOKKOS_ASSERT(((face1 == Face::YMIN) or (face1 == Face::YMAX)) && "wrong face id"); + + return ((~face0) & 0x1) + (((~face1) & 0x1) << 1); + + } // get_edge_neighbor_smallest_child_id + + // ============================================== + /** + * For compatibility with 3d. + * + * only face0 and face1 are used. + */ + KOKKOS_INLINE_FUNCTION + static uint8_t + get_corner_neighbor_smallest_child_id(Face::face_t face0, + Face::face_t face1, + [[maybe_unused]] Face::face_t face2) + { + KOKKOS_ASSERT(((face0 == Face::XMIN) or (face0 == Face::XMAX)) && "wrong face id"); + KOKKOS_ASSERT(((face1 == Face::YMIN) or (face1 == Face::YMAX)) && "wrong face id"); + + return get_edge_neighbor_smallest_child_id(face0, face1); + + } // get_corner_neighbor_smallest_child_id + + /** + * Compute the family id of a given orchard key. + * + * Family id is a number in [0, (2**dim)-1] is the local Morton index in a family of octant + * (neighbors at the same level). + * + * \param[in] key a given orchard key + * + * \return family + */ + KOKKOS_INLINE_FUNCTION + static uint32_t + family_id(uint64_t key) + { + + return static_cast( + (key ^ oldest_brother(key)) >> + (DIM * (NUM_LEVELS - level(key) - 1) + LEVEL_BITWIDTH + OUTSIDE_BITWIDTH)); + + } // family_id + + /** + * Returns true if current octant (identified by an orchard key) touches the border of current + * tree through given face. + * + * \param[in] key is the orchard key of an octant to probe + * \param[in] face identifies the face we query about being at domain border + */ + KOKKOS_INLINE_FUNCTION + static bool + is_at_tree_border(uint64_t orchard_key, Face::face_t face) + { + + auto x = get_octant_coord(orchard_key); + auto y = get_octant_coord(orchard_key); + + auto lev = level(orchard_key); + + // get octant logical size minus 1 + const uint32_t OCTANT_SIZE_M1 = (1 << (NUM_LEVELS - lev - 1)) - 1; + + if (face == Face::XMIN and x == 0) + { + return true; + } + + if (face == Face::YMIN and y == 0) + { + return true; + } + + if (face == Face::XMAX and (((x ^ OCTANT_SIZE_M1) & TREE_SIZE_MASK) == TREE_SIZE_MASK)) + { + return true; + } + + if (face == Face::YMAX and (((y ^ OCTANT_SIZE_M1) & TREE_SIZE_MASK) == TREE_SIZE_MASK)) + { + return true; + } + + // returning default value + return false; + + } // is_at_tree_border + + /** + * Returns true if current octant (identified by an orchard key) touches the border of current + * tree through given face. + * + * \param[in] key is the orchard key of an octant to probe + * \param[in] face identifies the face we query about being at domain border + */ + KOKKOS_INLINE_FUNCTION + static bool + is_at_any_tree_border(uint64_t orchard_key) + { + + // clang-format off + return is_at_tree_border(orchard_key, Face::XMIN) or + is_at_tree_border(orchard_key, Face::XMAX) or + is_at_tree_border(orchard_key, Face::YMIN) or + is_at_tree_border(orchard_key, Face::YMAX); + // clang-format on + + } // is_at_any_tree_border + + /** + * Returns true if current octant (identified by an orchard key) touches the outer domain boundary + * through given face. + * + * \param[in] key is the orchard key of an octant to probe + * \param[in] face identifies the face we query about being at domain border + * \param[in] brick_sizes is an array of sizes of the p4est brick connectivity + * + */ + KOKKOS_INLINE_FUNCTION + static bool + is_at_domain_border(uint64_t key, Face::face_t face, brick_size_t const & brick_sizes) + { + + auto tree_x = get_tree_coord(key); + auto tree_y = get_tree_coord(key); + + if (face == Face::XMIN and tree_x == 0 and is_at_tree_border(key, face)) + return true; + + if (face == Face::YMIN and tree_y == 0 and is_at_tree_border(key, face)) + return true; + + if (face == Face::XMAX and (tree_x == (brick_sizes[IX] - 1)) and is_at_tree_border(key, face)) + return true; + + if (face == Face::YMAX and (tree_y == (brick_sizes[IY] - 1)) and is_at_tree_border(key, face)) + return true; + + // returning default value + return false; + + } // is_at_domain_border + + /** + * Returns true if current octant (identified by an orchard key) touches any outer domain + * boundary. + * + * \param[in] key is the orchard key of an octant to probe + * \param[in] brick_sizes is an array of sizes of the p4est brick connectivity + * + */ + KOKKOS_INLINE_FUNCTION + static bool + is_at_any_domain_border(uint64_t key, brick_size_t const & brick_sizes) + { + + auto tree_x = get_tree_coord(key); + auto tree_y = get_tree_coord(key); + + bool result = false; + + if ((tree_x == 0 and is_at_tree_border(key, Face::XMIN)) or + (tree_y == 0 and is_at_tree_border(key, Face::YMIN)) or + ((tree_x == (brick_sizes[IX] - 1)) and is_at_tree_border(key, Face::XMAX)) or + ((tree_y == (brick_sizes[IY] - 1)) and is_at_tree_border(key, Face::YMAX))) + result = true; + + return result; + + } // is_at_any_domain_border + + /** + * Returns true if current octant (identified by an orchard key) touches the border of current + * tree through given face1 and face2. + * + * Being at corner means we touch two faces border. + * + * \param[in] key is the orchard key of an octant to probe + * \param[in] face1 identifies the first face + * \param[in] face2 identifies the second face + */ + KOKKOS_INLINE_FUNCTION + static bool + is_at_tree_corner(uint64_t orchard_key, Face::face_t face1, Face::face_t face2) + { + + return is_at_tree_border(orchard_key, face1) and is_at_tree_border(orchard_key, face2); + + } // is_at_tree_corner + + /** + * Returns true if current octant (identified by an orchard key) touches the border of current + * tree through a corner. + * + * \param[in] key is the orchard key of an octant to probe + */ + KOKKOS_INLINE_FUNCTION + static bool + is_at_any_tree_corner(uint64_t orchard_key) + { + + return is_at_tree_corner(orchard_key, Face::XMIN, Face::YMIN) or + is_at_tree_corner(orchard_key, Face::XMIN, Face::YMAX) or + is_at_tree_corner(orchard_key, Face::XMAX, Face::YMIN) or + is_at_tree_corner(orchard_key, Face::XMAX, Face::YMAX); + } // is_at_any_tree_corner + + /** + * Return a DIM-dimensional integer vector which direction is the outside normal vector. + * + * - this vector is only non-zero for key that touches the external border. + * - for keys that represent a block that is at corner of domain, (e.g. lower left corner, normal + * is {-1, -1}), the norm of the vector is not one. + * - for keys that represent a block that touches a face but not a corner, the norm of this vector + * is one. + * + * \note in early version, if brick connectivity was not periodic, normal vector was null, but in + * current version normal vector at border is always not null. + * + * \param[in] key is the orchard key of an octant to probe + * \param[in] brick_sizes is an array of sizes of the p4est brick connectivity + * \param[in] is_brick_periodic is array of bool (one per direction) to specify if p4est + * connectivity is periodic + */ + KOKKOS_INLINE_FUNCTION + static Kokkos::Array + get_outside_normal(uint64_t orchard_key, + brick_size_t const & brick_sizes, + [[maybe_unused]] Kokkos::Array const & is_brick_periodic) + { + + Kokkos::Array normal{ 0, 0 }; + + // if (!is_brick_periodic[IX]) + { + if (is_at_domain_border(orchard_key, Face::XMIN, brick_sizes)) + normal[IX] = -1; + if (is_at_domain_border(orchard_key, Face::XMAX, brick_sizes)) + normal[IX] = 1; + } + + // if (!is_brick_periodic[IY]) + { + if (is_at_domain_border(orchard_key, Face::YMIN, brick_sizes)) + normal[IY] = -1; + if (is_at_domain_border(orchard_key, Face::YMAX, brick_sizes)) + normal[IY] = 1; + } + + return normal; + + } // get_outside_normal + + /** + * Get orchard key of a neighbor across a corner + * a given input octant orchard key, assuming neighbor is at same level. + * + * A corner is identified by (intersection of) 2 orthogonal faces + * + * \param[in] key is the orchard key of current octant + * \param[in] face_x is a faceId (Face::XMIN, Face::XMAX, ....) + * \param[in] face_y is a faceId (Face::YMIN, Face::YMAX, ....) + * \param[in] brick_sizes is a array containing number of trees per dimension for each dimension + * + * \return neighbor across a corner orchard key + * + * \deprecated { should prefer using get_neighbor_key_same_level } + */ + KOKKOS_INLINE_FUNCTION + static uint64_t + get_corner_neighbor_key(uint64_t key, + Face::face_t face_x, + Face::face_t face_y, + brick_size_t const & brick_sizes) + { + + const auto lev = level(key); + const auto length = octantLength(key); + + const auto xy = get_octant_coords(key); + + const auto tree = morton_tree(key); + const auto tree_xy = get_tree_coords(key); + + auto tree_neigh = tree; + + // neighbor tree coordinates + auto tree_neigh_xy = tree_xy; + + // neighbor octant coordinates + auto xy_neigh = xy; + + // face_x + if (face_x == Face::XMIN) + { + xy_neigh[IX] = (xy[IX] == 0) ? last_offset(lev) : xy[IX] - length; + + // is neighbor octant in a different tree ? + if (xy[IX] == 0) + { + tree_neigh_xy[IX] = tree_xy[IX] == 0 ? brick_sizes[IX] - 1 : tree_xy[IX] - 1; + } + } + else if (face_x == Face::XMAX) + { + xy_neigh[IX] = (xy[IX] == last_offset(lev)) ? 0 : xy[IX] + length; + + // is neighbor octant in a different tree ? + if (xy[IX] == last_offset(lev)) + { + tree_neigh_xy[IX] = tree_xy[IX] == brick_sizes[IX] - 1 ? 0 : tree_xy[IX] + 1; + } + } + + // face_y + if (face_y == Face::YMIN) + { + xy_neigh[IY] = (xy[IY] == 0) ? last_offset(lev) : xy[IY] - length; + + // is neighbor octant in a different tree ? + if (xy[IY] == 0) + { + tree_neigh_xy[IY] = tree_xy[IY] == 0 ? brick_sizes[IY] - 1 : tree_xy[IY] - 1; + } + } + else if (face_y == Face::YMAX) + { + xy_neigh[IY] = (xy[IY] == last_offset(lev)) ? 0 : xy[IY] + length; + + // is neighbor octant in a different tree ? + if (xy[IY] == last_offset(lev)) + { + tree_neigh_xy[IY] = tree_xy[IY] == brick_sizes[IY] - 1 ? 0 : tree_xy[IY] + 1; + } + } + + tree_neigh = encode_morton_tree(tree_neigh_xy); + + return encode_orchard(tree_neigh, xy_neigh, lev, outside_status(key)); + + } // get_corner_neighbor_key + + /** + * Get orchard key of a face neighbor of + * a given input octant orchard key, assuming neighbor is at same level. + * + * \param[in] key is the orchard key of current octant + * \param[in] face is faceId (Face::XMIN, Face::XMAX, ....) + * \param[in] brick_sizes is a array containing number of trees per dimension for each dimension + * \param[in] level_neigh neighbor level + * + * Current octant (identified by key) and neighbor octant must have at most 1 level difference. + * + * \return neighbor orchard key + */ + KOKKOS_INLINE_FUNCTION + static uint64_t + get_face_neighbor_key(uint64_t key, Face::face_t face, brick_size_t const & brick_sizes) + { + + return get_corner_neighbor_key(key, face, face, brick_sizes); + + } // get_face_neighbor_key + + /** + * Get orchard key of a neighbor of a given octant, in a given direction at same level. + * + * \param[in] key is the orchard key of current octant + * \param[in] direction is a vector pointing to one of the 3x3-1 neighbors, component are -1,0 or + * 1 + * \param[in] brick_sizes is an array containing number of trees per dimension for each dimension + * \param[in] is_brick_periodic is an array of boolean (one value per direction) stating if mesh + * is periodic + * + * \return neighbor orchard key + */ + KOKKOS_INLINE_FUNCTION + static uint64_t + get_neighbor_key_same_level(uint64_t key, + Kokkos::Array const & direction, + brick_size_t const & brick_sizes, + Kokkos::Array const & is_brick_periodic) + { + + const auto lev = level(key); + const auto length = octantLength(key); + + const auto xy = get_octant_coords(key); + + const auto tree = morton_tree(key); + const auto tree_xy = get_tree_coords(key); + + auto tree_neigh = tree; + + // neighbor tree coordinates + auto tree_neigh_xy = tree_xy; + + // neighbor octant coordinates + auto xy_neigh = xy; + + auto outside = outside_status(key); + auto outside_neigh = outside; + + for (int dir = 0; dir < DIM; ++dir) + { + + if (direction[dir] == -1) + { + xy_neigh[dir] = (xy[dir] == 0) ? last_offset(lev) : xy[dir] - length; + + // is neighbor octant in a different tree ? + if (xy[dir] == 0) + { + tree_neigh_xy[dir] = tree_xy[dir] == 0 ? brick_sizes[dir] - 1 : tree_xy[dir] - 1; + } + + // if mesh not periodic, we need to set outside status bits + if (!is_brick_periodic[dir]) + { + if (xy[dir] == 0 and tree_xy[dir] == 0) + { + outside_neigh = outside_neigh ^ (ONE_U << dir); + } + } + } + else if (direction[dir] == 1) + { + xy_neigh[dir] = (xy[dir] == last_offset(lev)) ? 0 : xy[dir] + length; + + // is neighbor octant in a different tree ? + if (xy[dir] == last_offset(lev)) + { + tree_neigh_xy[dir] = tree_xy[dir] == brick_sizes[dir] - 1 ? 0 : tree_xy[dir] + 1; + } + + // if mesh not periodic, we need to set outside status bits + if (!is_brick_periodic[dir]) + { + if (xy[dir] == last_offset(lev) and tree_xy[dir] == brick_sizes[dir] - 1) + { + outside_neigh = outside_neigh ^ (ONE_U << dir); + } + } + } + + } // end for dir + + tree_neigh = encode_morton_tree(tree_neigh_xy); + + return encode_orchard(tree_neigh, xy_neigh, lev, outside_neigh); + + } // get_neighbor_key_same_level + + KOKKOS_INLINE_FUNCTION + static void + face_to_displacement(Kokkos::Array & displacement, Face::face_t face) + { + + if (face == Face::XMIN) + displacement[IX] = -1; + else if (face == Face::XMAX) + displacement[IX] = 1; + else if (face == Face::YMIN) + displacement[IY] = -1; + else if (face == Face::YMAX) + displacement[IY] = 1; + + } // face_to_displacement + + /** + * return a displacement vector from a face id. + */ + KOKKOS_INLINE_FUNCTION + static Kokkos::Array + face_to_displacement(Face::face_t face) + { + + Kokkos::Array displacement{ 0, 0 }; + + face_to_displacement(displacement, face); + + return displacement; + + } // face_to_displacement + + /** + * return a displacement vector from a pair of face id. + */ + KOKKOS_INLINE_FUNCTION + static Kokkos::Array + face_to_displacement(Face::face_t face0, Face::face_t face1) + { + + Kokkos::Array displacement{ 0, 0 }; + + face_to_displacement(displacement, face0); + face_to_displacement(displacement, face1); + + return displacement; + + } // face_to_displacement + + /** + * decode and print orchard key (for debug). + */ + KOKKOS_INLINE_FUNCTION + static void + print(uint64_t key, const char * str = "") + { + auto tree_coord = get_tree_coords(key); + auto oct_coord = get_octant_coords(key); + + printf("[%s] key: %ld | tree_morton : %d | tree_coord : %d %d | octant coord: %d %d | outside: " + "%d%d | level: %d\n", + str, + key, + morton_tree(key), + tree_coord[IX], + tree_coord[IY], + oct_coord[IX], + oct_coord[IY], + is_touching_face_X(key), + is_touching_face_Y(key), + level(key)); + } // print + +}; // struct orchard_key_t<2> + +KALYPSSO_DISABLE_CONVERSION_WARNINGS_POP() +KALYPSSO_DISABLE_NVCC_WARNINGS_POP() diff --git a/src/kalypsso/core/orchard_key_impl_3d.h b/src/kalypsso/core/orchard_key_impl_3d.h new file mode 100644 index 0000000..63c724b --- /dev/null +++ b/src/kalypsso/core/orchard_key_impl_3d.h @@ -0,0 +1,1555 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +KALYPSSO_DISABLE_CONVERSION_WARNINGS_PUSH() +KALYPSSO_DISABLE_NVCC_WARNINGS_PUSH() + +/** + * \file orchard_key_impl_3d.h + */ +// ==================================================================== +// ==================================================================== +// ==================================================================== +template <> +struct orchard_key_t<3> : public BitFieldInteger +{ + orchard_key_t(const orchard_key_t &) = default; + + orchard_key_t(orchard_key_t &&) = default; + + orchard_key_t & + operator=(const orchard_key_t &) = default; + + orchard_key_t & + operator=(orchard_key_t &&) = default; + + static constexpr uint8_t ONE_U = 1; + + /** dimension */ + static constexpr uint8_t DIM = 3; + + /** number of bits used to encode outside status */ + static constexpr uint8_t OUTSIDE_BITWIDTH = 3; + + /** number of bits used to encode level */ + static constexpr uint8_t LEVEL_BITWIDTH = 4; + + /** number of bits used to encode morton octant */ + static constexpr uint8_t MORTON_OCTANT_BITWIDTH = 42; + + /** number of bits used to encode morton tree */ + static constexpr uint8_t MORTON_TREE_BITWIDTH = 15; + + /** bit offset to the first bit where outside status bits start */ + static constexpr uint8_t OUTSIDE_OFFSET = 0; + + /** bit offset to the first bit where level bits start : 0 + 3 = 3 */ + static constexpr uint8_t LEVEL_OFFSET = OUTSIDE_OFFSET + OUTSIDE_BITWIDTH; + + /** bit offset to the first bit where the morton octant bits start : 3 + 4 = 7 */ + static constexpr uint8_t MORTON_OCTANT_OFFSET = LEVEL_OFFSET + LEVEL_BITWIDTH; + + /** bit offset to the first bit where tree bits start : 7 + 42 = 49 */ + static constexpr uint8_t MORTON_TREE_OFFSET = MORTON_OCTANT_OFFSET + MORTON_OCTANT_BITWIDTH; + + /** bits for outside status */ + static constexpr uint64_t OUTSIDE_MASK = 0x0000000000000007; + + /** bit mask to extract LEVEL (6 bits right after outside status) */ + static constexpr uint64_t LEVEL_MASK = 0x0000000000000078; + + /** bit mask to extract LEVEL (the middle 42 bits) */ + static constexpr uint64_t MORTON_OCTANT_MASK = 0x0001FFFFFFFFFF80; + + /** bit mask to extract MORTON_TREE (the upper 15 bits) */ + static constexpr uint64_t MORTON_TREE_MASK = 0xFFFE000000000000; + + /** level is in range [0, MAX_LEVEL] */ + static constexpr uint16_t MAX_LEVEL = MORTON_OCTANT_BITWIDTH / 3; + + /** number of levels : level is in range 0 to MAX_LEVELS */ + static constexpr uint16_t NUM_LEVELS = MAX_LEVEL + 1; + + /** length in logical units of the largest (root) quadrant */ + static constexpr uint32_t ROOT_LENGTH = 1 << MAX_LEVEL; + + /** logical tree size */ + static constexpr uint32_t TREE_SIZE = 1 << MAX_LEVEL; + + /** bit mask to keep only relevant bits for checking tree size */ + static constexpr uint32_t TREE_SIZE_MASK = TREE_SIZE - 1; + + /** maximum number of trees per direction */ + static constexpr uint16_t MAX_NB_TREES_PER_DIR = 1 << (MORTON_TREE_BITWIDTH / DIM); + + /** number of children */ + static constexpr uint32_t NB_CHILDREN = 1 << DIM; + + /** number of neighbors across a face at fine level */ + static constexpr uint32_t NB_FACE_NEIGHBORS_FINE = NB_CHILDREN / 2; + + /** number of neighbors across an edge at fine level */ + static constexpr uint32_t NB_EDGE_NEIGHBORS_FINE = 2; + + /** maximum number of neighbors */ + static constexpr uint32_t MAX_NUM_NEIGHBORS = NB_CHILDREN / 2; + + using BitFieldInteger::BitFieldInteger; + DECLARE_BIT(is_touching_face_X, OUTSIDE_OFFSET) + DECLARE_BIT(is_touching_face_Y, OUTSIDE_OFFSET + 1) + DECLARE_BIT(is_touching_face_Z, OUTSIDE_OFFSET + 2) + DECLARE_CASTED_FIELD(outside_status, OUTSIDE_OFFSET, OUTSIDE_BITWIDTH, uint8_t) + DECLARE_CASTED_FIELD(level, LEVEL_OFFSET, LEVEL_BITWIDTH, uint8_t) + DECLARE_CASTED_FIELD(morton_octant, MORTON_OCTANT_OFFSET, MORTON_OCTANT_BITWIDTH, uint64_t) + DECLARE_CASTED_FIELD(morton_tree, MORTON_TREE_OFFSET, MORTON_TREE_BITWIDTH, uint16_t) + + // ==================================================================== + //! get the outside status of a given key / quadrant + KOKKOS_INLINE_FUNCTION static bool + is_outside(uint64_t const & key) + { + + return is_touching_face_X(key) or is_touching_face_Y(key) or is_touching_face_Z(key); + + } // is_outside + + // ==================================================================== + //! return true only if key is outside and "touching" inside domain by face along given "dir" + //! \note a corner outside is touching inside domain by 2 faces. + KOKKOS_INLINE_FUNCTION static bool + is_outside_dir(uint64_t const & key, Dir::dir_t dir) + { + + return ((dir == Dir::X) and is_touching_face_X(key)) or + ((dir == Dir::Y) and is_touching_face_Y(key)) or + ((dir == Dir::Z) and is_touching_face_Z(key)); + + } // is_outside_dir + + // ==================================================================== + //! modify outside status of a given key. + KOKKOS_INLINE_FUNCTION static void + set_outside_bit(uint64_t & key, Face::face_t const & face) + { + + if (face == Face::XMIN or face == Face::XMAX) + set_is_touching_face_X(key, true); + else if (face == Face::YMIN or face == Face::YMAX) + set_is_touching_face_Y(key, true); + else if (face == Face::ZMIN or face == Face::ZMAX) + set_is_touching_face_Z(key, true); + + } // set_outside_bit + + // ==================================================================== + //! change outside status. By resetting these bits, key becomes an inside key + KOKKOS_INLINE_FUNCTION static void + reset_outside_bits(uint64_t & key) + { + set_is_touching_face_X(key, false); + set_is_touching_face_Y(key, false); + set_is_touching_face_Z(key, false); + } + + // // ==================================================================== + // /** extract Morton tree */ + // KOKKOS_INLINE_FUNCTION static uint16_t + // get_tree_morton_index(uint64_t key) + // { + // return (key & MORTON_TREE_MASK); + // } // get_tree_morton_index + + // // ==================================================================== + // /** extract Morton quadrant */ + // KOKKOS_INLINE_FUNCTION + // static uint64_t + // get_octant_morton_index(uint64_t key) + // { + // return (key & MORTON_OCTANT_MASK) >> MORTON_OCTANT_OFFSET; + // } // get_octant_morton_index + + // // ==================================================================== + // /** extract level */ + // KOKKOS_INLINE_FUNCTION + // static uint8_t + // get_level(uint64_t key) + // { + // return (key & LEVEL_MASK) >> LEVEL_OFFSET; + // } // get_level + + // ==================================================================== + template + KOKKOS_INLINE_FUNCTION static uint16_t + get_tree_coord(uint64_t key) + { + static_assert(direction == IX || direction == IY || direction == IZ, + "Only X,Y and Z directions are possible in 3D"); + + auto tree_coord = morton_tree(key); + + // select direction + tree_coord = tree_coord >> direction; + + // only 5 bits are significant (= 001001001001001) + tree_coord &= 0x1249; + + tree_coord = (tree_coord ^ (tree_coord >> 2)) & 0x70c3; + tree_coord = (tree_coord ^ (tree_coord >> 4)) & 0xf00f; + tree_coord = (tree_coord ^ (tree_coord >> 8)) & 0x00ff; + + return static_cast(tree_coord); + + } // get_tree_coord + + // ==================================================================== + KOKKOS_INLINE_FUNCTION static Kokkos::Array + get_tree_coords(uint64_t key) + { + return Kokkos::Array{ get_tree_coord(key), + get_tree_coord(key), + get_tree_coord(key) }; + } // get_tree_coords + + // ==================================================================== + /** + * get octant x,y or z coordinate from an orchard key. + * + * \param[in] key the orchard key + */ + template + KOKKOS_INLINE_FUNCTION static uint32_t + get_octant_coord(uint64_t key) + { + static_assert(direction == IX || direction == IY || direction == IZ, + "Only X,Y and Z directions are possible in 3D"); + + // extract bits corresponding to morton octant + key = morton_octant(key); + + // select direction + key = key >> direction; + + // only 14 bits are significant, so applying a 42 bits wide mask should be enough + // key &= 0x249249249249; + key &= 0x9249249249; + + // clang-format off + key = (key ^ (key >> 2)) & 0x30c30c30c30c30c3; + key = (key ^ (key >> 4)) & 0xf00f00f00f00f00f; + key = (key ^ (key >> 8)) & 0x00ff0000ff0000ff; + key = (key ^ (key >> 16)) & 0x00ff00000000ffff; + key = (key ^ (key >> 32)) & 0x1fffff; + // clang-format on + + return static_cast(key); + + } // get_octant_coord + + // ==================================================================== + /** + * get octant x,y or z coordinate from an orchard key. + * + * \param[in] key the orchard key + */ + KOKKOS_INLINE_FUNCTION static Kokkos::Array + get_octant_coords(uint64_t key) + { + return Kokkos::Array{ get_octant_coord(key), + get_octant_coord(key), + get_octant_coord(key) }; + } // get_octant_coords + + // ==================================================================== + /** + * Take an integer representing one of the tree coordinate, and modify its + * binary representation as preliminary step before encoding morton key. + * + * \param[in] coord must be in range [0,31] (only 5 bits are significant per direction) + * + * \return an integer with interleaved 0 in between each bits of input. + */ + KOKKOS_INLINE_FUNCTION static uint16_t + split_morton_tree(uint16_t coord) + { + // extract the last 5 bits + uint16_t x = coord & 0x001F; + + x = (x | x << 8) & 0xf00f; + x = (x | x << 4) & 0x70c3; + x = (x | x << 2) & 0x1249; + + return x; + + } // split_morton_tree + + // ==================================================================== + /** + * Take an integer representing one of the octant coordinate, and modify its + * binary representation as preliminary step before encoding morton key. + * + * \param[in] coord must be in range [0,2^14-1] (only 14 bits are significant per direction) + * + * the result is stored on a 64 bits integer because 3x14 bits = 42 bits (minimum) + * + * \return an integer with interleaved 0 in between each bits of input. + */ + KOKKOS_INLINE_FUNCTION static uint64_t + split_morton_octant(uint32_t coord) + { + // extract the last 14 bits + uint64_t x = coord & 0x3FFF; + + // clang-format off + x = (x | x << 32) & 0x1f00000000ffff; + x = (x | x << 16) & 0x1f0000ff0000ff; + x = (x | x << 8) & 0x100f00f00f00f00f; + x = (x | x << 4) & 0x10c30c30c30c30c3; + x = (x | x << 2) & 0x1249249249249249; + // clang-format on + + return x; + + } // split_morton_octant + + // ==================================================================== + /** + * encode morton tree index from x,y,z coordinates + * \param[in] treeCoord is an an array of tree coordinates in the p4est brick connectivity + */ + KOKKOS_INLINE_FUNCTION static uint16_t + encode_morton_tree(Kokkos::Array treeCoord) + { + uint16_t key = 0; + // clang-format off + key |= split_morton_tree(treeCoord[IX]) | + split_morton_tree(treeCoord[IY]) << 1 | + split_morton_tree(treeCoord[IZ]) << 2; + // clang-format on + + return key; + } // encode_morton_tree + + // ==================================================================== + /** + * encode morton tree index from x,y coordinates + * \param[in] tree_x is tree cartesian coordinate along X in the p4est brick connectivity + * \param[in] tree_y is tree cartesian coordinate along Y in the p4est brick connectivity + * \param[in] tree_z is tree cartesian coordinate along Z in the p4est brick connectivity + */ + KOKKOS_INLINE_FUNCTION static uint16_t + encode_morton_tree(uint16_t tree_x, uint16_t tree_y, uint16_t tree_z) + { + uint16_t key = 0; + // clang-format off + key |= split_morton_tree(tree_x) | + split_morton_tree(tree_y) << 1 | + split_morton_tree(tree_z) << 2; + // clang-format on + + return key; + } // encode_morton_tree + + // ==================================================================== + /** + * encode morton tree index from x,y,z coordinates + * \param[in] treeCoord is an an array of tree coordinates in the p4est brick connectivity + */ + KOKKOS_INLINE_FUNCTION static void + encode_morton_tree(uint64_t & orchard_key, Kokkos::Array treeCoord) + { + uint16_t tree_key = 0; + + // clang-format off + tree_key |= split_morton_tree(treeCoord[IX]) | + split_morton_tree(treeCoord[IY]) << 1 | + split_morton_tree(treeCoord[IZ]) << 2; + // clang-format on + + set_morton_tree(orchard_key, tree_key); + + } // encode_morton_tree + + // ==================================================================== + /** + * encode morton octant index from octant x,y,z coordinates + * \param[in] octCoord must be in [0,2**14-1] + */ + KOKKOS_INLINE_FUNCTION static uint64_t + encode_morton_octant(Kokkos::Array octCoord) + { + uint64_t key = 0; + + // clang-format off + key |= split_morton_octant(octCoord[IX]) | + split_morton_octant(octCoord[IY]) << 1 | + split_morton_octant(octCoord[IZ]) << 2; + // clang-format on + + return key; + } // encode_morton_octant + + // ==================================================================== + /** + * encode morton octant index from octant x,y,z coordinates + * \param[in] octCoord must be in [0,2**14-1]^3 + */ + KOKKOS_INLINE_FUNCTION static void + encode_morton_octant(uint64_t & orchard_key, Kokkos::Array octCoord) + { + uint64_t key = 0; + + // clang-format off + key |= split_morton_octant(octCoord[IX]) | + split_morton_octant(octCoord[IY]) << 1 | + split_morton_octant(octCoord[IZ]) << 2; + // clang-format on + + set_morton_octant(orchard_key, key); + + } // encode_morton_octant + + // ==================================================================== + /** + * encode orchard key from octant x,y,z coordinates, tree x,y,z coords and level + * \param[in] octCoord must be in [0,2**14-1]^3 + * + */ + KOKKOS_INLINE_FUNCTION static uint64_t + encode_orchard(Kokkos::Array treeCoord, + Kokkos::Array octCoord, + uint8_t level, + uint8_t outside_status) + { + uint64_t key = 0; + + set_outside_status(key, outside_status); + set_level(key, level); + encode_morton_octant(key, octCoord); + encode_morton_tree(key, treeCoord); + + return key; + + } // encode_orchard + + // ==================================================================== + /** + * encode orchard key from octant x,y,z coordinates, tree x,y,z coords and level + * \param[in] octCoord must be in [0,2**14-1]^3 + * + * key is always zero initialized, the outside status bits will be zero (i.e. inside quadrant). + */ + KOKKOS_INLINE_FUNCTION static uint64_t + encode_orchard(Kokkos::Array treeCoord, + Kokkos::Array octCoord, + uint8_t level) + { + uint64_t key = 0; + + set_level(key, level); + encode_morton_octant(key, octCoord); + encode_morton_tree(key, treeCoord); + + return key; + + } // encode_orchard + + // ==================================================================== + /** + * encode orchard key from octant x,y,z coordinates, tree id and level + * \param[in] octCoord must be in [0,2**14-1]^3 + * + * key is always zero initialized, the outside bit will be zero. + */ + KOKKOS_INLINE_FUNCTION static uint64_t + encode_orchard(uint16_t treeid, + Kokkos::Array octCoord, + uint8_t level, + uint8_t outside_status = 0) + { + uint64_t key = 0; + + set_outside_status(key, outside_status); + set_level(key, level); + encode_morton_octant(key, octCoord); + set_morton_tree(key, treeid); + + return key; + + } // encode_orchard + + /** + * Octant logical length from level. + * + * \input[in] an AMR level + * + * \return octant length + */ + KOKKOS_INLINE_FUNCTION + static uint32_t + octantLength_from_level(uint8_t level) + { + return (1 << (MAX_LEVEL - level)); + } // octantLength_from_level + + /** + * Octant logical length. + * + * \input[in] a orchard key + * + * \return octant length + */ + KOKKOS_INLINE_FUNCTION + static uint32_t + octantLength(uint64_t orchard_key) + { + return octantLength_from_level(level(orchard_key)); + } // octantLength + + /** + * Last offset returns the logical coordinate (x, y, or z) + * of an octant that "touches" the tree border on right. + * + * Just as a reminder, an octant that "touches" the tree border on left, e.g. along x axis, will + * have x logical coordinate equal to zero. + * + */ + KOKKOS_INLINE_FUNCTION + static uint32_t + last_offset(uint64_t orchard_key) + { + return ROOT_LENGTH - octantLength(orchard_key); + } // last_offset + + KOKKOS_INLINE_FUNCTION + static uint32_t + last_offset(uint8_t level) + { + return ROOT_LENGTH - octantLength_from_level(level); + } // last_offset + + /** + * Get the orchard key of the oldest brother of a given key. + * + * The oldest brother is defined as the first octant of a family of 2^dim octants. + * + * \param[in] key a given orchard key + * + * \return orchard key of the oldest brother. + */ + KOKKOS_INLINE_FUNCTION + static uint64_t + oldest_brother(uint64_t key) + { + // oldest brother must have coordinates that are divisible by + // 2*octantLength we just need to zero-out the bits of the remainder + + const auto lev = level(key); + + // 1. remove all the bits + uint64_t key_new = key >> (DIM * (NUM_LEVELS - lev) + LEVEL_BITWIDTH + OUTSIDE_BITWIDTH); + + key_new = (key_new << (DIM * (NUM_LEVELS - lev) + LEVEL_BITWIDTH + OUTSIDE_BITWIDTH)); + + set_level(key_new, level(key)); + set_outside_status(key_new, outside_status(key)); + + return key_new; + + } // oldest_brother + + /** + * Get the orchard key of the oldest brother of a given key (old version). + * + * The oldest brother is defined as the first octant of a family of 2^dim octants. + * + * \param[in] key is a given orchard key + * + * \return orchard key of the oldest brother. + */ + KOKKOS_INLINE_FUNCTION + static uint64_t + oldest_brother_old(uint64_t key) + { + + // octant logical length + const auto length = octantLength(key); + + // twice octant size minus 1 + const uint32_t mask = (1 << (NUM_LEVELS - level(key))) - 1; + + // input key octant coordinates + auto x = get_octant_coord(key); + auto y = get_octant_coord(key); + auto z = get_octant_coord(key); + + // get oldest brother octant input key + x = (x & mask) == 0 ? x : x - length; + y = (y & mask) == 0 ? y : y - length; + z = (z & mask) == 0 ? z : z - length; + + // oldest brother has the same level, and belongs to the same tree + const auto oct_level = level(key); + const auto tree_id = morton_tree(key); + + return encode_orchard(tree_id, { x, y, z }, oct_level, outside_status(key)); + + } // oldest_brother_old + + /** + * Is the first octant of a family of 2^dim octants ? + * + * \param[in] orchard_key + */ + KOKKOS_INLINE_FUNCTION + static bool + isFirstOctantOfFamily(uint64_t orchard_key) + { + + // // twice octant size minus 1 + // const uint32_t mask = (1 << (NUM_LEVELS - level(orchard_key))) - 1; + + // // check if x,y,z are integral multiple of mask+1 (= 2x octant length) + // return (((get_octant_coord(orchard_key) & mask) == 0) and + // ((get_octant_coord(orchard_key) & mask) == 0) and + // ((get_octant_coord(orchard_key) & mask) == 0)); + + return orchard_key == oldest_brother(orchard_key); + + } // isFirstOctantOfFamily + + /** + * Get the orchard key of the father of a given key. + * + * The father is defined as the octant at level l-1 (coarse) with the same coordinate as the + * oldest brother. + * + * \param[in] key a given orchard key + * + * \return orchard key of the father + */ + KOKKOS_INLINE_FUNCTION + static uint64_t + father(uint64_t key) + { + + // octant logical length + const auto length = octantLength(key); + + // twice octant size minus 1 + const uint32_t mask = (1 << (NUM_LEVELS - level(key))) - 1; + + // input key octant coordinates + auto x = get_octant_coord(key); + auto y = get_octant_coord(key); + auto z = get_octant_coord(key); + + // get oldest brother octant input key + x = (x & mask) == 0 ? x : x - length; + y = (y & mask) == 0 ? y : y - length; + z = (z & mask) == 0 ? z : z - length; + + // father octant is at coarse level, and belongs to the same tree + const auto oct_level = level(key) - 1; + const auto tree_id = morton_tree(key); + + return encode_orchard(tree_id, { x, y, z }, oct_level, outside_status(key)); + + } // father + + /** + * Compute child id of given key with respect to its father octant. + * + * The father is defined as the octant at level l-1 (coarse) with the same coordinate as the + * oldest brother. + * + * \param[in] key a given orchard key + * + * \return child id + */ + KOKKOS_INLINE_FUNCTION + static uint8_t + child_id(uint64_t key) + { + uint8_t id = 0; + + // twice octant size minus 1 + const uint32_t mask = (1 << (NUM_LEVELS - level(key))) - 1; + + // input key octant coordinates + auto x = get_octant_coord(key); + auto y = get_octant_coord(key); + auto z = get_octant_coord(key); + + // get oldest brother octant input key + id = (x & mask) == 0 ? id : id + 1; + id = (y & mask) == 0 ? id : id + 2; + id = (z & mask) == 0 ? id : id + 4; + + return id; + + } // child_id + + /** + * Get the orchard key of the eldest child of a given key. + * + * The eldest child is defined as the octant at level l+1 (refined) with the same coordinate as + * the father. + * + * \param[in] key a given orchard key + * + * \return orchard key of the eldest child + */ + KOKKOS_INLINE_FUNCTION + static uint64_t + eldest_child(uint64_t key) + { + + uint64_t child_key = key; + + set_level(child_key, level(key) + 1); + + return child_key; + + } // eldest_child + + /** + * Get the orchard keys of all the children of a given key. + * + * \param[in] key a given orchard key + * + * \return array of orchard keys of all the children + */ + KOKKOS_INLINE_FUNCTION + static auto + all_children(uint64_t key) -> Kokkos::Array + { + + Kokkos::Array res; + + // child octant logical length + const auto length = octantLength(key) >> 1; + + // children belong to the same tree + const auto tree_id = morton_tree(key); + + const auto oct_level = level(key) + 1; + + // input key octant coordinates + auto x = get_octant_coord(key); + auto y = get_octant_coord(key); + auto z = get_octant_coord(key); + + // eldest child + // res[0] = eldest_child(key); + + // sweep children in Morton order + for (uint32_t i = 0; i < NB_CHILDREN; ++i) + { + // uint8_t ix = i & 0x1; + // uint8_t iy = (i & 0x2) >> 1; + // uint8_t iz = (i & 0x4) >> 2; + res[i] = encode_orchard( + tree_id, + { x + (i & 0x1) * length, y + ((i & 0x2) >> 1) * length, z + ((i & 0x4) >> 2) * length }, + oct_level, + outside_status(key)); + } + + return res; + + } // all_children + + /** + * Get the orchard key of the nth child of a given key. + * Remember the are NB_CHILDREN child octant, enumerated in Morton order. + * + * \param[in] key a given orchard key + * + * \return orchard key of the nth child + */ + KOKKOS_INLINE_FUNCTION + static uint64_t + child(uint64_t key, uint8_t iChild) + { + + KOKKOS_ASSERT(iChild < NB_CHILDREN && "iChild is not less than NB_CHILDREN"); + + // child octant logical length + const auto length = octantLength(key) >> 1; + + // children belong to the same tree + const auto tree_id = morton_tree(key); + + const auto oct_level = level(key) + 1; + + // input key octant coordinates + auto x = get_octant_coord(key); + auto y = get_octant_coord(key); + auto z = get_octant_coord(key); + + return encode_orchard(tree_id, + { x + (iChild & 0x1) * length, + y + ((iChild & 0x2) >> 1) * length, + z + ((iChild & 0x4) >> 2) * length }, + oct_level, + outside_status(key)); + + } // child + + /** + * + * For a given face (Face::XMIN, etc...), return the smallest child of a finer neighbor that + * "touches" current quadrant through that given face. + * + * In the drawing below (2D), the numbers are child ids we are talking about. + * + * This function is used when, under the assumption neighbor is at finer level, that actually + * there is a neighbor at finer level by probing the hashmap. + * + * -------- + * | | | + * |___|___| + * | | | + * ___ ___|_0_|___|________ + * | | | | | | + * |___|___| |___|___| + * | | | | | | + * |___|_1_|_______|_0_|___| + * | 2 | | + * |___|___| + * | | | + * |___|___| + */ + KOKKOS_INLINE_FUNCTION + static uint8_t + get_face_neighbor_smallest_child_id(Face::face_t face) + { + // if (face == Face::XMIN) + // return 1; + // else if (face == Face::XMAX) + // return 0; + // else if (face == Face::YMIN) + // return 2; + // else if (face == Face::YMAX) + // return 0; + // else if (face == Face::ZMIN) + // return 4; + // else if (face == Face::ZMAX) + // return 0; + + // if face is a right face (odd number) then return 0 + // else 2**(face/2) + return (face & 0x1) == 1 ? 0 : (1 << (face / 2)); + + } // get_face_neighbor_smallest_child_id + + /** + * + * For a given edge/corner (identified by intersection of two faces), + * return the smallest child of a finer neighbor that + * "touches" current quadrant through that given edge/corner. + * + * In the drawing below, the numbers are child ids we are talking about. + * + * This function is used when, under the assumption neighbor is at finer level, that actually + * there is a neighbor at finer level by probing the hashmap. + * + * + * ________ ________ + * | | | | | | + * |___|___| |___|___| + * | | 1 | | 0 | | + * |___|___|_______|___|___| + * | | + * | | + * | | + * _______|_______|________ + * | | 3 | | 2 | | + * |___|___| |___|___| + * | | | | | | + * |___|___| |___|___| + */ + KOKKOS_INLINE_FUNCTION + static uint8_t + get_edge_neighbor_smallest_child_id(Face::face_t face0, Face::face_t face1) + { + const auto dir0 = face0 / 2; + const auto dir1 = face1 / 2; + if (dir0 == 0 and dir1 == 1) + { + return ((~face0) & 0x1) + (((~face1) & 0x1) << 1); + } + else if (dir0 == 1 and dir1 == 2) + { + return (((~face0) & 0x1) + (((~face1) & 0x1) << 1)) << 1; + } + else if (dir0 == 0 and dir1 == 2) + { + return (((~face0) & 0x1) + (((~face1) & 0x1) << 2)); + } + else + { + // we should not be here + KOKKOS_ASSERT(false && "There is obviously something wrong in mesh connectivity...."); + return 0; + } + + } // get_edge_neighbor_smallest_child_id + + KOKKOS_INLINE_FUNCTION + static uint8_t + get_corner_neighbor_smallest_child_id(Face::face_t face0, Face::face_t face1, Face::face_t face2) + { + return ((~face0) & 0x1) + (((~face1) & 0x1) << 1) + (((~face2) & 0x1) << 2); + } + + /** + * Compute the family id of a given orchard key. + * + * Family id is a number in [0, (2**dim)-1] is the local Morton index in a family of octant + * (neighbors at the same level). + * + * \param[in] key a given orchard key + * + * \return family + */ + KOKKOS_INLINE_FUNCTION static uint32_t + family_id(uint64_t key) + { + + return static_cast( + (key ^ oldest_brother(key)) >> + (DIM * (NUM_LEVELS - level(key) - 1) + LEVEL_BITWIDTH + OUTSIDE_BITWIDTH)); + + } // family_id + + /** + * Returns true if current octant (identified by an orchard key) touches the border of current + * tree through given face. + * + * \param[in] key is the orchard key of an octant to probe + * \param[in] face identifies the face we query about being at domain border + */ + KOKKOS_INLINE_FUNCTION + static bool + is_at_tree_border(uint64_t orchard_key, Face::face_t face) + { + + auto x = get_octant_coord(orchard_key); + auto y = get_octant_coord(orchard_key); + auto z = get_octant_coord(orchard_key); + + auto lev = level(orchard_key); + + // get octant logical size minus 1 + const uint32_t OCTANT_SIZE_M1 = (1 << (NUM_LEVELS - lev - 1)) - 1; + + if (face == Face::XMIN and x == 0) + { + return true; + } + + if (face == Face::YMIN and y == 0) + { + return true; + } + + if (face == Face::ZMIN and z == 0) + { + return true; + } + + if (face == Face::XMAX and (((x ^ OCTANT_SIZE_M1) & TREE_SIZE_MASK) == TREE_SIZE_MASK)) + { + return true; + } + + if (face == Face::YMAX and (((y ^ OCTANT_SIZE_M1) & TREE_SIZE_MASK) == TREE_SIZE_MASK)) + { + return true; + } + + if (face == Face::ZMAX and (((z ^ OCTANT_SIZE_M1) & TREE_SIZE_MASK) == TREE_SIZE_MASK)) + { + return true; + } + + // returning default value + return false; + + } // is_at_tree_border + + /** + * Returns true if current octant (identified by an orchard key) touches the border of current + * tree through given face. + * + * \param[in] key is the orchard key of an octant to probe + * \param[in] face identifies the face we query about being at domain border + */ + KOKKOS_INLINE_FUNCTION + static bool + is_at_any_tree_border(uint64_t orchard_key) + { + + // clang-format off + return is_at_tree_border(orchard_key, Face::XMIN) or + is_at_tree_border(orchard_key, Face::XMAX) or + is_at_tree_border(orchard_key, Face::YMIN) or + is_at_tree_border(orchard_key, Face::YMAX) or + is_at_tree_border(orchard_key, Face::ZMIN) or + is_at_tree_border(orchard_key, Face::ZMAX); + // clang-format on + + } // is_at_any_tree_border + + /** + * Returns true if current octant (identified by an orchard key) touches the outer domain boundary + * through given face. + * + * \param[in] key is the orchard key of an octant to probe + * \param[in] face identifies the face we query about being at domain border + * \param[in] brick_sizes is an array of sizes of the p4est brick connectivity + * + */ + KOKKOS_INLINE_FUNCTION + static bool + is_at_domain_border(uint64_t key, Face::face_t face, brick_size_t const & brick_sizes) + { + + auto tree_x = get_tree_coord(key); + auto tree_y = get_tree_coord(key); + auto tree_z = get_tree_coord(key); + + if (face == Face::XMIN and tree_x == 0 and is_at_tree_border(key, face)) + return true; + + if (face == Face::YMIN and tree_y == 0 and is_at_tree_border(key, face)) + return true; + + if (face == Face::ZMIN and tree_z == 0 and is_at_tree_border(key, face)) + return true; + + if (face == Face::XMAX and (tree_x == (brick_sizes[IX] - 1)) and is_at_tree_border(key, face)) + return true; + + if (face == Face::YMAX and (tree_y == (brick_sizes[IY] - 1)) and is_at_tree_border(key, face)) + return true; + + if (face == Face::ZMAX and (tree_z == (brick_sizes[IZ] - 1)) and is_at_tree_border(key, face)) + return true; + + // returning default value + return false; + + } // is_at_domain_border + + /** + * Returns true if current octant (identified by an orchard key) touches any outer domain + * boundary. + * + * \param[in] key is the orchard key of an octant to probe + * \param[in] brick_sizes is an array of sizes of the p4est brick connectivity + * + */ + KOKKOS_INLINE_FUNCTION + static bool + is_at_any_domain_border(uint64_t key, brick_size_t const & brick_sizes) + { + + auto tree_x = get_tree_coord(key); + auto tree_y = get_tree_coord(key); + auto tree_z = get_tree_coord(key); + + bool result = false; + + if ((tree_x == 0 and is_at_tree_border(key, Face::XMIN)) or + (tree_y == 0 and is_at_tree_border(key, Face::YMIN)) or + (tree_z == 0 and is_at_tree_border(key, Face::ZMIN)) or + ((tree_x == (brick_sizes[IX] - 1)) and is_at_tree_border(key, Face::XMAX)) or + ((tree_y == (brick_sizes[IY] - 1)) and is_at_tree_border(key, Face::YMAX)) or + ((tree_z == (brick_sizes[IZ] - 1)) and is_at_tree_border(key, Face::ZMAX))) + result = true; + + return result; + + } // is_at_any_domain_border + + /** + * Returns true if current octant (identified by an orchard key) touches the border of current + * tree through two given face: face1 and face2. + * + * Being at edge means we touch two faces border. + * + * \param[in] key is the orchard key of an octant to probe + * \param[in] face1 identifies the first face + * \param[in] face2 identifies the second face + */ + KOKKOS_INLINE_FUNCTION + static bool + is_at_tree_edge(uint64_t orchard_key, Face::face_t face1, Face::face_t face2) + { + + return is_at_tree_border(orchard_key, face1) and is_at_tree_border(orchard_key, face2); + + } // is_at_tree_edge + + /** + * Returns true if current octant (identified by an orchard key) touches the border of current + * tree through any edge. + */ + KOKKOS_INLINE_FUNCTION + static bool + is_at_any_tree_edge(uint64_t orchard_key) + { + + return is_at_tree_edge(orchard_key, Face::XMIN, Face::YMIN) or + is_at_tree_edge(orchard_key, Face::XMIN, Face::YMAX) or + is_at_tree_edge(orchard_key, Face::XMAX, Face::YMIN) or + is_at_tree_edge(orchard_key, Face::XMAX, Face::YMAX) or + is_at_tree_edge(orchard_key, Face::YMIN, Face::ZMIN) or + is_at_tree_edge(orchard_key, Face::YMIN, Face::ZMAX) or + is_at_tree_edge(orchard_key, Face::YMAX, Face::ZMIN) or + is_at_tree_edge(orchard_key, Face::YMAX, Face::ZMAX) or + is_at_tree_edge(orchard_key, Face::ZMIN, Face::XMIN) or + is_at_tree_edge(orchard_key, Face::ZMIN, Face::XMAX) or + is_at_tree_edge(orchard_key, Face::ZMAX, Face::XMIN) or + is_at_tree_edge(orchard_key, Face::ZMAX, Face::XMAX); + + } // is_at_any_tree_edge + + /** + * Returns true if current octant (identified by an orchard key) touches the border of current + * tree through three given face: face1, face2 and face3. + * + * Being at corner means we touch three faces border. + * + * \param[in] key is the orchard key of an octant to probe + * \param[in] face1 identifies the first face + * \param[in] face2 identifies the second face + * \param[in] face3 identifies the third face + */ + KOKKOS_INLINE_FUNCTION + static bool + is_at_tree_corner(uint64_t orchard_key, + Face::face_t face1, + Face::face_t face2, + Face::face_t face3) + { + + return is_at_tree_border(orchard_key, face1) and is_at_tree_border(orchard_key, face2) and + is_at_tree_border(orchard_key, face3); + + } // is_at_tree_corner + + /** + * Returns true if current octant (identified by an orchard key) touches the border of current + * tree through a corner. + * + * \param[in] key is the orchard key of an octant to probe + */ + KOKKOS_INLINE_FUNCTION + static bool + is_at_any_tree_corner(uint64_t orchard_key) + { + + return is_at_tree_corner(orchard_key, Face::XMIN, Face::YMIN, Face::ZMIN) or + is_at_tree_corner(orchard_key, Face::XMIN, Face::YMIN, Face::ZMAX) or + is_at_tree_corner(orchard_key, Face::XMIN, Face::YMAX, Face::ZMIN) or + is_at_tree_corner(orchard_key, Face::XMIN, Face::YMAX, Face::ZMAX) or + is_at_tree_corner(orchard_key, Face::XMAX, Face::YMIN, Face::ZMIN) or + is_at_tree_corner(orchard_key, Face::XMAX, Face::YMIN, Face::ZMAX) or + is_at_tree_corner(orchard_key, Face::XMAX, Face::YMAX, Face::ZMIN) or + is_at_tree_corner(orchard_key, Face::XMAX, Face::YMAX, Face::ZMAX); + + } // is_at_any_tree_corner + + /** + * Return a DIM-dimensional integer vector which direction is the outside normal vector. + * + * - this vector is only non-zero for key that touches the external border. + * - for keys that represent a block that is at corner of domain, (e.g. lower left corner, normal + * is {-1, -1}), the norm of the vector is not one. + * - for keys that represent a block that touches a face but not a corner, the norm of this vector + * is one. + * + * \note in early version, if brick connectivity was not periodic, normal vector was null, but in + * current version normal vector at border is always not null. + * + * \param[in] key is the orchard key of an octant to probe + * \param[in] brick_sizes is an array of sizes of the p4est brick connectivity + * \param[in] is_brick_periodic is array of bool (one per direction) to specify if p4est + * connectivity is periodic + */ + KOKKOS_INLINE_FUNCTION + static Kokkos::Array + get_outside_normal(uint64_t orchard_key, + brick_size_t const & brick_sizes, + [[maybe_unused]] Kokkos::Array const & is_brick_periodic) + { + + Kokkos::Array normal{ 0, 0, 0 }; + + // if (!is_brick_periodic[IX]) + { + if (is_at_domain_border(orchard_key, Face::XMIN, brick_sizes)) + normal[IX] = -1; + if (is_at_domain_border(orchard_key, Face::XMAX, brick_sizes)) + normal[IX] = 1; + } + + // if (!is_brick_periodic[IY]) + { + if (is_at_domain_border(orchard_key, Face::YMIN, brick_sizes)) + normal[IY] = -1; + if (is_at_domain_border(orchard_key, Face::YMAX, brick_sizes)) + normal[IY] = 1; + } + + // if (!is_brick_periodic[IZ]) + { + if (is_at_domain_border(orchard_key, Face::ZMIN, brick_sizes)) + normal[IZ] = -1; + if (is_at_domain_border(orchard_key, Face::ZMAX, brick_sizes)) + normal[IZ] = 1; + } + + return normal; + + } // get_outside_normal + + /** + * Get orchard key of a neighbor across a corner + * a given input octant orchard key, assuming neighbor is at same level. + * + * A corner is identified by (intersection of) 3 orthogonal faces + * + * \param[in] key is the orchard key of current octant + * \param[in] face_x is a faceId (Face::XMIN, Face::XMAX, ....) + * \param[in] face_y is a faceId (Face::YMIN, Face::YMAX, ....) + * \param[in] face_z is a faceId (Face::ZMIN, Face::ZMAX, ....) + * \param[in] brick_sizes is a array containing number of trees per dimension for each dimension + * + * \return neighbor across a corner orchard key + * + * \deprecated { should prefer using get_neighbor_key_same_level } + */ + KOKKOS_INLINE_FUNCTION + static uint64_t + get_corner_neighbor_key(uint64_t key, + Face::face_t face_x, + Face::face_t face_y, + Face::face_t face_z, + brick_size_t const & brick_sizes) + { + + const auto lev = level(key); + + const auto x = get_octant_coord(key); + const auto y = get_octant_coord(key); + const auto z = get_octant_coord(key); + + const auto tree = morton_tree(key); + const auto tree_x = get_tree_coord(key); + const auto tree_y = get_tree_coord(key); + const auto tree_z = get_tree_coord(key); + + auto tree_neigh = tree; + + // neighbor tree coordinates + uint16_t tree_neigh_x = tree_x; + uint16_t tree_neigh_y = tree_y; + uint16_t tree_neigh_z = tree_z; + + // neighbor octant coordinates + uint32_t x_neigh = x; + uint32_t y_neigh = y; + uint32_t z_neigh = z; + + const auto length = octantLength(key); + + // face_x + if (face_x == Face::XMIN) + { + x_neigh = (x == 0) ? last_offset(lev) : x - length; + + // is neighbor octant in a different tree ? + if (x == 0) + { + tree_neigh_x = tree_x == 0 ? brick_sizes[IX] - 1 : tree_x - 1; + } + } + else if (face_x == Face::XMAX) + { + x_neigh = (x == last_offset(lev)) ? 0 : x + length; + + // is neighbor octant in a different tree ? + if (x == last_offset(lev)) + { + tree_neigh_x = tree_x == brick_sizes[IX] - 1 ? 0 : tree_x + 1; + } + } + + // face_y + if (face_y == Face::YMIN) + { + y_neigh = (y == 0) ? last_offset(lev) : y - length; + + // is neighbor octant in a different tree ? + if (y == 0) + { + tree_neigh_y = tree_y == 0 ? brick_sizes[IY] - 1 : tree_y - 1; + } + } + else if (face_y == Face::YMAX) + { + y_neigh = (y == last_offset(lev)) ? 0 : y + length; + + // is neighbor octant in a different tree ? + if (y == last_offset(lev)) + { + tree_neigh_y = tree_y == brick_sizes[IY] - 1 ? 0 : tree_y + 1; + } + } + + // face_z + if (face_z == Face::ZMIN) + { + z_neigh = (z == 0) ? last_offset(lev) : z - length; + + // is neighbor octant in a different tree ? + if (z == 0) + { + tree_neigh_z = tree_z == 0 ? brick_sizes[IZ] - 1 : tree_z - 1; + } + } + else if (face_z == Face::ZMAX) + { + z_neigh = (z == last_offset(lev)) ? 0 : z + length; + + // is neighbor octant in a different tree ? + if (z == last_offset(lev)) + { + tree_neigh_z = tree_z == brick_sizes[IZ] - 1 ? 0 : tree_z + 1; + } + } + + tree_neigh = encode_morton_tree(tree_neigh_x, tree_neigh_y, tree_neigh_z); + + return encode_orchard(tree_neigh, { x_neigh, y_neigh, z_neigh }, lev, outside_status(key)); + + } // get_corner_neighbor_key + + /** + * Get orchard key of a face neighbor of + * a given input octant orchard key, assuming neighbor is at same level. + * + * \param[in] key is the orchard key of current octant + * \param[in] face is faceId (Face::XMIN, Face::XMAX, ....) + * \param[in] brick_sizes is a array containing number of trees per dimension for each dimension + * \param[in] level_neigh neighbor level + * + * Current octant (identified by key) and neighbor octant must have at most 1 level difference. + * + * \return neighbor orchard key + */ + KOKKOS_INLINE_FUNCTION + static uint64_t + get_face_neighbor_key(uint64_t key, Face::face_t face, brick_size_t const & brick_sizes) + { + + return get_corner_neighbor_key(key, face, face, face, brick_sizes); + + } // get_face_neighbor_key + + /** + * Get orchard key of a neighbor of a given octant, in a given direction at same level. + * + * \param[in] key is the orchard key of current octant + * \param[in] direction is a vector pointing to one of the 3x3-1 neighbors, component are -1,0 or + * 1 + * \param[in] brick_sizes is an array containing number of trees per dimension for each dimension + * \param[in] is_brick_periodic is an array of boolean (one value per direction) stating if mesh + * is periodic + * + * \return neighbor orchard key + */ + KOKKOS_INLINE_FUNCTION + static uint64_t + get_neighbor_key_same_level(uint64_t key, + Kokkos::Array const & direction, + brick_size_t const & brick_sizes, + Kokkos::Array const & is_brick_periodic) + { + + const auto lev = level(key); + const auto length = octantLength(key); + + const auto xy = get_octant_coords(key); + + const auto tree = morton_tree(key); + const auto tree_xy = get_tree_coords(key); + + auto tree_neigh = tree; + + // neighbor tree coordinates + auto tree_neigh_xy = tree_xy; + + // neighbor octant coordinates + auto xy_neigh = xy; + + auto outside = outside_status(key); + auto outside_neigh = outside; + + for (int dir = 0; dir < DIM; ++dir) + { + + if (direction[dir] == -1) + { + xy_neigh[dir] = (xy[dir] == 0) ? last_offset(lev) : xy[dir] - length; + + // is neighbor octant in a different tree ? + if (xy[dir] == 0) + { + tree_neigh_xy[dir] = tree_xy[dir] == 0 ? brick_sizes[dir] - 1 : tree_xy[dir] - 1; + } + + // if mesh not periodic, we need to set outside status bits + if (!is_brick_periodic[dir]) + { + if (xy[dir] == 0 and tree_xy[dir] == 0) + { + outside_neigh = outside_neigh ^ (ONE_U << dir); + } + } + } + else if (direction[dir] == 1) + { + xy_neigh[dir] = (xy[dir] == last_offset(lev)) ? 0 : xy[dir] + length; + + // is neighbor octant in a different tree ? + if (xy[dir] == last_offset(lev)) + { + tree_neigh_xy[dir] = tree_xy[dir] == brick_sizes[dir] - 1 ? 0 : tree_xy[dir] + 1; + } + + // if mesh not periodic, we need to set outside status bits + if (!is_brick_periodic[dir]) + { + if (xy[dir] == last_offset(lev) and tree_xy[dir] == brick_sizes[dir] - 1) + { + outside_neigh = outside_neigh ^ (ONE_U << dir); + } + } + } + + } // end for dir + + tree_neigh = encode_morton_tree(tree_neigh_xy); + + return encode_orchard(tree_neigh, xy_neigh, lev, outside_neigh); + + } // get_neighbor_key_same_level + + KOKKOS_INLINE_FUNCTION + static void + face_to_displacement(Kokkos::Array & displacement, Face::face_t face) + { + + if (face == Face::XMIN) + displacement[IX] = -1; + else if (face == Face::XMAX) + displacement[IX] = 1; + else if (face == Face::YMIN) + displacement[IY] = -1; + else if (face == Face::YMAX) + displacement[IY] = 1; + else if (face == Face::ZMIN) + displacement[IZ] = -1; + else if (face == Face::ZMAX) + displacement[IZ] = 1; + + } // face_to_displacement + + /** + * return a displacement vector from a face id. + */ + KOKKOS_INLINE_FUNCTION + static Kokkos::Array + face_to_displacement(Face::face_t face) + { + + Kokkos::Array displacement{ 0, 0, 0 }; + + face_to_displacement(displacement, face); + + return displacement; + + } // face_to_displacement + + /** + * return a displacement vector from a pair of face id. + */ + KOKKOS_INLINE_FUNCTION + static Kokkos::Array + face_to_displacement(Face::face_t face0, Face::face_t face1) + { + + Kokkos::Array displacement{ 0, 0, 0 }; + + face_to_displacement(displacement, face0); + face_to_displacement(displacement, face1); + + return displacement; + + } // face_to_displacement + + /** + * return a displacement vector from a pair of face id. + */ + KOKKOS_INLINE_FUNCTION + static Kokkos::Array + face_to_displacement(Face::face_t face0, Face::face_t face1, Face::face_t face2) + { + + Kokkos::Array displacement{ 0, 0, 0 }; + + face_to_displacement(displacement, face0); + face_to_displacement(displacement, face1); + face_to_displacement(displacement, face2); + + return displacement; + + } // face_to_displacement + + /** + * decode and print orchard key (for debug). + */ + KOKKOS_INLINE_FUNCTION + static void + print(uint64_t key, const char * str = "") + { + auto tree_coord = get_tree_coords(key); + auto oct_coord = get_octant_coords(key); + + printf("[%s] key: %ld | tree_morton : %d | tree_coord : %d %d %d | octant coord: %d %d %d | " + "outside: %d%d%d | level: %d\n", + str, + key, + morton_tree(key), + tree_coord[IX], + tree_coord[IY], + tree_coord[IZ], + oct_coord[IX], + oct_coord[IY], + oct_coord[IZ], + is_touching_face_X(key), + is_touching_face_Y(key), + is_touching_face_Z(key), + level(key)); + } // print + +}; // struct orchard_key_t<3> + +KALYPSSO_DISABLE_CONVERSION_WARNINGS_POP() +KALYPSSO_DISABLE_NVCC_WARNINGS_POP() diff --git a/src/kalypsso/core/orchard_key_utils.h b/src/kalypsso/core/orchard_key_utils.h new file mode 100644 index 0000000..4a212ad --- /dev/null +++ b/src/kalypsso/core/orchard_key_utils.h @@ -0,0 +1,762 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file orchard_key_utils.h + * + */ +#ifndef KALYPSSO_CORE_ORCHARD_KEY_UTILS_H +#define KALYPSSO_CORE_ORCHARD_KEY_UTILS_H + +#include // for assertm +#include +#include +#include // for block_size_t, face_multiindex_t +#include + +#include +#include // for CHAR_BIT + +namespace kalypsso +{ + +// ============================================================================= +// ============================================================================= +/** + * convert an orchard key to vertex absolutes coordinates (same units as in the p4est + * connectivity) of the lower left corner of quadrant, assuming a brick connectivity. + * + * implementation: extract logical x,y,z and rescale them to unit cube [0,1]^dim; then used tree + * coordinates, to have the final vertex coordinates. + * + * remember that logical values of x,y,z are integers in range [0, ROOT_LENGTH-1], where ROOT_LENGTH + * is \f$2^{NUM_LEVELS - 1}\f$. + * + * \param[in] key is an orchard key identifying in a unique way a quadrant (over all MPI process) + * \param[in] brick_sizes provides the sizes of the brick (p4est) connectivity + * \param[in] centering if true, returns coordinates of the center of the quadrant + * + * \return array of absolute vertex coordinates of the lower left corner of quadrant/octant assuming + * brick p4est connectivity. + */ +template +KOKKOS_INLINE_FUNCTION Kokkos::Array + orchard_key_to_vertex_coord(uint64_t key, bool centering) +{ + + Kokkos::Array XYZ; + + auto center_offset = centering ? orchard_key_t::octantLength(key) / 2 : 0; + + real_t x = + static_cast(orchard_key_t::template get_octant_coord(key) + center_offset) / + orchard_key_t::ROOT_LENGTH; + real_t y = + static_cast(orchard_key_t::template get_octant_coord(key) + center_offset) / + orchard_key_t::ROOT_LENGTH; + + XYZ[IX] = x + static_cast(orchard_key_t::template get_tree_coord(key)); + XYZ[IY] = y + static_cast(orchard_key_t::template get_tree_coord(key)); + + if constexpr (dim == 3) + { + real_t z = + static_cast(orchard_key_t::template get_octant_coord(key) + center_offset) / + orchard_key_t::ROOT_LENGTH; + XYZ[IZ] = z + static_cast(orchard_key_t::template get_tree_coord(key)); + } + + return XYZ; + +} // orchard_key_to_vertex_coord + +// ===================================================================== +// ===================================================================== +//! Same as orchard_key_to_vertex_coord but for orchard key corresponding to +//! an outside quadrant. +//! +template +KOKKOS_INLINE_FUNCTION Kokkos::Array +outside_key_to_vertex_coord(uint64_t key, bool centering, brick_size_t brick_sizes) +{ + + // first check that input key is actually an "outside" key + KOKKOS_ASSERT(orchard_key_t::is_outside(key) && "key is not an outside key !"); + + KOKKOS_ASSERT(orchard_key_t::is_at_any_domain_border(key, brick_sizes) && + "orchard key is not associated to a quadrant touching external border.") + + // get coordinate of the "inside" quadrant + auto coord = orchard_key_to_vertex_coord(key, centering); + + // when computing outside quadrant key, we always use a "virtual" key computed as the + // periodic image of the outside quadrant; so here we need is_periodic to be array of + // "true" + constexpr auto is_periodic = get_bool_array(true); + + // quadrant is at domain border, compute outside normal + const auto outside_normal = orchard_key_t::get_outside_normal(key, brick_sizes, is_periodic); + + if (orchard_key_t::is_touching_face_X(key)) + coord[IX] -= static_cast(brick_sizes[IX] * outside_normal[IX]); + if (orchard_key_t::is_touching_face_Y(key)) + coord[IY] -= static_cast(brick_sizes[IY] * outside_normal[IY]); + if constexpr (dim == 3) + { + if (orchard_key_t::is_touching_face_Z(key)) + coord[IZ] -= static_cast(brick_sizes[IZ] * outside_normal[IZ]); + } + + return coord; + +} // outside_key_to_vertex_coord + +// ============================================================================= +// ============================================================================= +template +KOKKOS_INLINE_FUNCTION Kokkos::Array +orchard_key_to_cell_coord(uint64_t key, Kokkos::Array cell_indexes, int block_size) +{ + + // 1. compute lower left corner coordinates in real space + constexpr bool centering = false; + auto XYZ_corner = orchard_key_to_vertex_coord(key, centering); + + // 2. compute cell length in real space + real_t dx_cell = static_cast(orchard_key_t::octantLength(key)) / + static_cast(orchard_key_t::ROOT_LENGTH) / + static_cast(block_size); + + // 3. compute cell center coordinates + Kokkos::Array res; + + res[IX] = XYZ_corner[IX] + static_cast(cell_indexes[IX]) * dx_cell + HALF_F * dx_cell; + res[IY] = XYZ_corner[IY] + static_cast(cell_indexes[IY]) * dx_cell + HALF_F * dx_cell; + if constexpr (dim == 3) + { + res[IZ] = XYZ_corner[IZ] + static_cast(cell_indexes[IZ]) * dx_cell + HALF_F * dx_cell; + } + + return res; + +} // orchard_key_to_cell_coord + +// ============================================================================= +// ============================================================================= +/** + * Get coordinates in vertex (p4est connectivity) space of the corner of a cell. + * + * Corners are identifid in Z-order. + */ +template +KOKKOS_INLINE_FUNCTION Kokkos::Array + orchard_key_to_corner_coord(uint64_t key, + Kokkos::Array cell_indexes, + int block_size, + uint8_t i_corner) +{ + + // 1. compute lower left corner coordinates in real space + constexpr bool centering = false; + auto XYZ_corner = orchard_key_to_vertex_coord(key, centering); + + // 2. compute cell length in real space + real_t dx_cell = static_cast(orchard_key_t::octantLength(key)) / + static_cast(orchard_key_t::ROOT_LENGTH) / + static_cast(block_size); + + // 3. compute corner coordinates + Kokkos::Array res; + + res[IX] = XYZ_corner[IX] + static_cast(cell_indexes[IX]) * dx_cell + + ((i_corner >> 0) & 1) * dx_cell; + res[IY] = XYZ_corner[IY] + static_cast(cell_indexes[IY]) * dx_cell + + ((i_corner >> 1) & 1) * dx_cell; + if constexpr (dim == 3) + { + res[IZ] = XYZ_corner[IZ] + static_cast(cell_indexes[IZ]) * dx_cell + + ((i_corner >> 2) & 1) * dx_cell; + } + + return res; + +} // orchard_key_to_cell_coord + +// ============================================================================= +// ============================================================================= +/** + * This utility is mostly useful when dealing with a FaceDataArrayBlock. + */ +template +KOKKOS_INLINE_FUNCTION Kokkos::Array +orchard_key_to_face_coord(uint64_t key, face_multiindex_t face_indexes, int block_size) +{ + + // 1. compute lower left corner coordinates in real space + constexpr bool centering = false; + auto XYZ_corner = orchard_key_to_vertex_coord(key, centering); + + // 2. compute cell length in real space + real_t dx_cell = static_cast(orchard_key_t::octantLength(key)) / + static_cast(orchard_key_t::ROOT_LENGTH) / + static_cast(block_size); + + // 3. compute cell center coordinates + Kokkos::Array res; + + const auto & dir = face_indexes[dim]; + + res[IX] = XYZ_corner[IX] + static_cast(face_indexes[IX]) * dx_cell; + res[IX] += (dir == IX) ? 0 : HALF_F * dx_cell; + + res[IY] = XYZ_corner[IY] + static_cast(face_indexes[IY]) * dx_cell; + res[IY] += (dir == IY) ? 0 : HALF_F * dx_cell; + + if constexpr (dim == 3) + { + res[IZ] = XYZ_corner[IZ] + static_cast(face_indexes[IZ]) * dx_cell; + res[IZ] += (dir == IZ) ? 0 : HALF_F * dx_cell; + } + + return res; + +} // orchard_key_to_face_coord + +// ============================================================================= +// ============================================================================= +/** + * This utility is mostly useful when dealing with an EdgeDataArrayBlock. + */ +template +KOKKOS_INLINE_FUNCTION Kokkos::Array +orchard_key_to_edge_coord(uint64_t key, edge_multiindex_t edge_indexes, int block_size) +{ + + // 1. compute lower left corner coordinates in real space + constexpr bool centering = false; + auto XYZ_corner = orchard_key_to_vertex_coord(key, centering); + + // 2. compute cell length in real space + real_t dx_cell = static_cast(orchard_key_t::octantLength(key)) / + static_cast(orchard_key_t::ROOT_LENGTH) / + static_cast(block_size); + + // 3. compute cell center coordinates + Kokkos::Array res; + + const auto & dir = edge_indexes[dim]; + + res[IX] = XYZ_corner[IX] + static_cast(edge_indexes[IX]) * dx_cell; + res[IX] += (dir == IX) ? HALF_F * dx_cell : 0; + + res[IY] = XYZ_corner[IY] + static_cast(edge_indexes[IY]) * dx_cell; + res[IY] += (dir == IY) ? HALF_F * dx_cell : 0; + + if constexpr (dim == 3) + { + res[IZ] = XYZ_corner[IZ] + static_cast(edge_indexes[IZ]) * dx_cell; + res[IZ] += (dir == IZ) ? HALF_F * dx_cell : 0; + } + + return res; + +} // orchard_key_to_edge_coord + +// ============================================================================= +// ============================================================================= +/** + * This utility is mostly useful when dealing with a FaceDataArrayBlock. + */ +template +KOKKOS_INLINE_FUNCTION Kokkos::Array + orchard_key_to_face_coord(uint64_t key, + face_multiindex_t face_indexes, + block_size_t const & block_sizes) +{ + + // 1. compute lower left corner coordinates in real space + constexpr bool centering = false; + auto XYZ_corner = orchard_key_to_vertex_coord(key, centering); + + // 2. compute cell center coordinates + Kokkos::Array res; + + const auto & dir = face_indexes[dim]; + + { + const real_t dx_cell = static_cast(orchard_key_t::octantLength(key)) / + static_cast(orchard_key_t::ROOT_LENGTH) / + static_cast(block_sizes[IX]); + + res[IX] = XYZ_corner[IX] + static_cast(face_indexes[IX]) * dx_cell; + res[IX] += (dir == IX) ? KALYPSSO_NUM(0.0) : HALF_F * dx_cell; + } + + { + const real_t dy_cell = static_cast(orchard_key_t::octantLength(key)) / + static_cast(orchard_key_t::ROOT_LENGTH) / + static_cast(block_sizes[IY]); + + res[IY] = XYZ_corner[IY] + static_cast(face_indexes[IY]) * dy_cell; + res[IY] += (dir == IY) ? KALYPSSO_NUM(0.0) : HALF_F * dy_cell; + } + + if constexpr (dim == 3) + { + const real_t dz_cell = static_cast(orchard_key_t::octantLength(key)) / + static_cast(orchard_key_t::ROOT_LENGTH) / + static_cast(block_sizes[IZ]); + + res[IZ] = XYZ_corner[IZ] + static_cast(face_indexes[IZ]) * dz_cell; + res[IZ] += (dir == IZ) ? KALYPSSO_NUM(0.0) : HALF_F * dz_cell; + } + + return res; + +} // orchard_key_to_face_coord + +// ============================================================================= +// ============================================================================= +/** + * This utility is mostly useful when dealing with a EdgeDataArrayBlock. + */ +template +KOKKOS_INLINE_FUNCTION Kokkos::Array + orchard_key_to_edge_coord(uint64_t key, + edge_multiindex_t edge_indexes, + block_size_t const & block_sizes) +{ + + // 1. compute lower left corner coordinates in real space + constexpr bool centering = false; + auto XYZ_corner = orchard_key_to_vertex_coord(key, centering); + + // 2. compute cell center coordinates + Kokkos::Array res; + + const auto & dir = edge_indexes[dim]; + + const auto octant_length = static_cast(orchard_key_t::octantLength(key)); + + { + const real_t dx_cell = octant_length / static_cast(orchard_key_t::ROOT_LENGTH) / + static_cast(block_sizes[IX]); + + res[IX] = XYZ_corner[IX] + static_cast(edge_indexes[IX]) * dx_cell; + res[IX] += (dir == IX) ? HALF_F * dx_cell : ZERO_F; + } + + { + const real_t dy_cell = octant_length / static_cast(orchard_key_t::ROOT_LENGTH) / + static_cast(block_sizes[IY]); + + res[IY] = XYZ_corner[IY] + static_cast(edge_indexes[IY]) * dy_cell; + res[IY] += (dir == IY) ? HALF_F * dy_cell : ZERO_F; + } + + if constexpr (dim == 3) + { + const real_t dz_cell = octant_length / static_cast(orchard_key_t::ROOT_LENGTH) / + static_cast(block_sizes[IZ]); + + res[IZ] = XYZ_corner[IZ] + static_cast(edge_indexes[IZ]) * dz_cell; + res[IZ] += (dir == IZ) ? HALF_F * dz_cell : ZERO_F; + } + + return res; + +} // orchard_key_to_edge_coord + +/** + * Transform from (p4est connectivity space) vertex coordinates to real space coordinates. + * + * Just apply a linear rescaling to domain [x_min, x_max] x [y_min, y_max] x [z_min, z_max] + */ +template +KOKKOS_INLINE_FUNCTION auto +vertex_coord_to_real_space(Kokkos::Array const & vertex_coords, + real_t const & scaling_factor, + Kokkos::Array const & xyz_min) +{ + if constexpr (dim == 2) + { + Kokkos::Array real_coords; + real_coords[IX] = xyz_min[IX] + scaling_factor * vertex_coords[IX]; + real_coords[IY] = xyz_min[IY] + scaling_factor * vertex_coords[IY]; + return real_coords; + } + else if constexpr (dim == 3) + { + Kokkos::Array real_coords; + real_coords[IX] = xyz_min[IX] + scaling_factor * vertex_coords[IX]; + real_coords[IY] = xyz_min[IY] + scaling_factor * vertex_coords[IY]; + real_coords[IZ] = xyz_min[IZ] + scaling_factor * vertex_coords[IZ]; + return real_coords; + } +} // vertex_coord_to_real_space + +// ============================================================================= +// ============================================================================= +/** + * Given an orchard key, given a cell indexes array (integers relative to a block), compute + * cell-center coordinates in real-space. + */ +template +KOKKOS_INLINE_FUNCTION Kokkos::Array + orchard_key_to_cellcenter_real_space(uint64_t key, + Kokkos::Array cell_indexes, + int block_size, + real_t const & scaling_factor, + Kokkos::Array const & xyz_min) +{ + const auto xyz_center_vertex = orchard_key_to_cell_coord(key, cell_indexes, block_size); + + return vertex_coord_to_real_space(xyz_center_vertex, scaling_factor, xyz_min); + +} // orchard_key_to_cellcenter_real_space + +// ============================================================================= +// ============================================================================= +/** + * Given an orchard key, given a cell indexes array (integers relative to a block), compute + * face-center coordinates in real-space. + * + * Block of cells is assumed to be a cube (same size along all direction). + */ +template +KOKKOS_INLINE_FUNCTION Kokkos::Array + orchard_key_to_facecenter_real_space(uint64_t key, + face_multiindex_t face_indexes, + int block_size, + real_t const & scaling_factor, + Kokkos::Array const & xyz_min) +{ + const auto xyz_face_vertex = orchard_key_to_face_coord(key, face_indexes, block_size); + + return vertex_coord_to_real_space(xyz_face_vertex, scaling_factor, xyz_min); + +} // orchard_key_to_facecenter_real_space + +// ============================================================================= +// ============================================================================= +/** + * Given an orchard key, given a cell indexes array (integers relative to a block), compute + * edge-center coordinates in real-space. + * + * Block of cells is assumed to be a cube (same size along all direction). + */ +template +KOKKOS_INLINE_FUNCTION Kokkos::Array + orchard_key_to_edgecenter_real_space(uint64_t key, + edge_multiindex_t edge_indexes, + int block_size, + real_t const & scaling_factor, + Kokkos::Array const & xyz_min) +{ + const auto xyz_edge_vertex = orchard_key_to_edge_coord(key, edge_indexes, block_size); + + return vertex_coord_to_real_space(xyz_edge_vertex, scaling_factor, xyz_min); + +} // orchard_key_to_edgecenter_real_space + +// ============================================================================= +// ============================================================================= +/** + * Given an orchard key, given a cell indexes array (integers relative to a block), compute + * face-center coordinates in real-space. + */ +template +KOKKOS_INLINE_FUNCTION Kokkos::Array + orchard_key_to_facecenter_real_space(uint64_t key, + face_multiindex_t face_indexes, + block_size_t const & block_sizes, + real_t const & scaling_factor, + Kokkos::Array const & xyz_min) +{ + const auto xyz_face_vertex = orchard_key_to_face_coord(key, face_indexes, block_sizes); + + return vertex_coord_to_real_space(xyz_face_vertex, scaling_factor, xyz_min); + +} // orchard_key_to_facecenter_real_space + +// ============================================================================= +// ============================================================================= +/** + * Given an orchard key, given a cell indexes array (integers relative to a block), compute + * edge-center coordinates in real-space. + */ +template +KOKKOS_INLINE_FUNCTION Kokkos::Array + orchard_key_to_edgecenter_real_space(uint64_t key, + edge_multiindex_t edge_indexes, + block_size_t const & block_sizes, + real_t const & scaling_factor, + Kokkos::Array const & xyz_min) +{ + const auto xyz_edge_vertex = orchard_key_to_edge_coord(key, edge_indexes, block_sizes); + + return vertex_coord_to_real_space(xyz_edge_vertex, scaling_factor, xyz_min); + +} // orchard_key_to_edgecenter_real_space + +// ============================================================================= +// ============================================================================= +/** + * Compute cell length for a cell that belongs to a quadrant at a given level, given also the number + * of cells per block direction. + * + * Remember we assume all block have the same number of cells per direction, hence the cell length + * is same along all direction (X, Y or Z). + */ +template +KOKKOS_INLINE_FUNCTION real_t +compute_cell_length(uint8_t level, int block_size) +{ + + return static_cast(orchard_key_t::octantLength_from_level(level)) / + static_cast(orchard_key_t::ROOT_LENGTH) / static_cast(block_size); + +} // compute_cell_length + +// ============================================================================= +// ============================================================================= +/** + * Compute cell length for a cell that belongs to a quadrant at a given level, given also the number + * of cells per block direction. + * + * Remember we assume all block have the same number of cells per direction, hence the cell length + * is same along all direction (X, Y or Z). + */ +template +KOKKOS_INLINE_FUNCTION real_t +compute_cell_length(uint64_t key, int block_size) +{ + + return static_cast(orchard_key_t::octantLength(key)) / + static_cast(orchard_key_t::ROOT_LENGTH) / static_cast(block_size); + +} // compute_cell_length + +// ============================================================================= +// ============================================================================= +/** + * Compute block length for a quadrant at a given level. + * + * Here we assume all block have the same number of cells per direction, hence the cell length + * is same along all direction (X, Y or Z). + */ +template +KOKKOS_INLINE_FUNCTION real_t +compute_block_length(uint8_t level) +{ + + return static_cast(orchard_key_t::octantLength_from_level(level)) / + static_cast(orchard_key_t::ROOT_LENGTH); + +} // compute_block_length + +// ============================================================================= +// ============================================================================= +/** + * Compute absolute real space coordinates of a cell center. + * + * \param[in] level is the AMR level of a quadrant / block. + * + * \param[in] XYZ_corner are real space coordinates of lower left corner of the block. + * They can be obtained by a previous call to orchard_key_to_vertex + * \param[in] cell_indexes is an array of local integer coordinates identifying a cell + * inside a block in range [0, bx-1] x [0, by-1] x [0, bz-1] + * where bx,by,bz are defined in the input ini parameter file + * \param[in] block_size the number of cells per dimension in a block of cells + * + * \return array of physical coordinates of the cell center. + */ +template +KOKKOS_INLINE_FUNCTION Kokkos::Array + compute_cell_coordinates(uint8_t level, + Kokkos::Array XYZ_corner, + Kokkos::Array cell_indexes, + block_size_t block_sizes) +{ + + // index_t is supposed to be either int32_t or uint32_t + static_assert(std::is_integral::value, "Integral type required."); + + // 1. compute cell length in real space + real_t dx_cell = compute_cell_length(level, block_sizes[IX]); + real_t dy_cell = compute_cell_length(level, block_sizes[IY]); + + // 2. compute cell center coordinates + Kokkos::Array res; + + res[IX] = XYZ_corner[IX] + static_cast(cell_indexes[IX]) * dx_cell + HALF_F * dx_cell; + res[IY] = XYZ_corner[IY] + static_cast(cell_indexes[IY]) * dy_cell + HALF_F * dy_cell; + if constexpr (dim == 3) + { + real_t dz_cell = compute_cell_length(level, block_sizes[IZ]); + res[IZ] = XYZ_corner[IZ] + static_cast(cell_indexes[IZ]) * dz_cell + HALF_F * dz_cell; + } + + return res; + +} // compute_cell_coordinates + +// ======================================================= +// ======================================================= +/** + * input : linear cell index inside a block of size (bSize,bSize,bSize) + * output : extract integer coordinates + */ +template +KOKKOS_INLINE_FUNCTION Kokkos::Array + icell_to_icoord(int32_t icell, int bSize) +{ + + Kokkos::Array res; + + if constexpr (dim == 2) + { + res[IY] = (icell / bSize); + res[IX] = (icell - bSize * res[IY]); + } + else + { + res[IZ] = (icell / (bSize * bSize)); + int32_t icell2 = icell - bSize * bSize * res[IZ]; + res[IY] = (icell2 / bSize); + res[IX] = (icell2 - bSize * res[IY]); + } + + return res; + +} // icell_to_icoord + +// ======================================================= +// ======================================================= +/** + * input : linear cell index inside a block of size (bSize[IX],bSize[IY],bSize[IZ]) + * output : extract integer coordinates + */ +template +KOKKOS_INLINE_FUNCTION Kokkos::Array + icell_to_icoord(int32_t icell, Kokkos::Array bSize) +{ + + Kokkos::Array res; + + if constexpr (dim == 2) + { + res[IY] = static_cast(icell / bSize[IX]); + res[IX] = static_cast(icell - bSize[IX] * res[IY]); + } + else + { + res[IZ] = static_cast(icell / (bSize[IX] * bSize[IY])); + int32_t icell2 = icell - bSize[IX] * bSize[IY] * res[IZ]; + res[IY] = static_cast(icell2 / bSize[IX]); + res[IX] = static_cast(icell2 - bSize[IX] * res[IY]); + } + + return res; + +} // icell_to_icoord + +// ======================================================= +// ======================================================= +/** + * input : extract integer coordinates + * output : linear cell index inside a block of size (bSize[IX],bSize[IY],bSize[IZ]) + */ +template +KOKKOS_INLINE_FUNCTION uint32_t +icoord_to_icell(Kokkos::Array icoord, int bSize) +{ + + return dim == 2 ? icoord[IX] + bSize * icoord[IY] + : icoord[IX] + bSize * icoord[IY] + bSize * bSize * icoord[IZ]; + +} // icoord_to_icell + +// ======================================================= +// ======================================================= +/** + * input : extract integer coordinates + * output : linear cell index inside a block of size (bSize[IX],bSize[IY],bSize[IZ]) + */ +template +KOKKOS_INLINE_FUNCTION uint32_t +icoord_to_icell(Kokkos::Array icoord, Kokkos::Array bSize) +{ + + return dim == 2 ? icoord[IX] + bSize[IX] * icoord[IY] + : icoord[IX] + bSize[IX] * icoord[IY] + bSize[IX] * bSize[IY] * icoord[IZ]; + +} // icoord_to_icell + +// // ======================================================= +// // ======================================================= +// KOKKOS_INLINE_FUNCTION uint32_t +// icoord_to_icell(uint32_t ix, uint32_t iy, int bSize) +// { + +// return ix + bSize * iy; + +// } // icoord_to_icell + +// // ======================================================= +// // ======================================================= +// KOKKOS_INLINE_FUNCTION uint32_t +// icoord_to_icell(uint32_t ix, uint32_t iy, uint32_t iz, int bSize) +// { + +// return ix + bSize * iy + bSize * bSize * iz; + +// } // icoord_to_icell + +// // ======================================================= +// // ======================================================= +// KOKKOS_INLINE_FUNCTION uint32_t +// icoord_to_icell(uint32_t ix, uint32_t iy, uint32_t iz, int bx, int by) +// { + +// return ix + bx * iy + bx * by * iz; + +// } // icoord_to_icell + +// ======================================================= +// ======================================================= +//! \brief swap two bits in an integer. +//! see https://www.geeksforgeeks.org/how-to-swap-two-bits-in-a-given-integer/ +//! +//! \param[in] n integer to consider +//! \param[in] p1 bit position 1 +//! \param[in] p2 bit position 2 +//! \return new integer computed as n where bit b1 (at position p1) and b2 (at position p2) are +//! swapped +template +KOKKOS_INLINE_FUNCTION T +swapBits(T n, int p1, int p2) +{ + static_assert(std::is_integral::value, "Integral required."); + + // make sure p1 and p2 are valid bit positions + assertm(p1 >= 0 and p2 >= 0, "swapBits: invalid bit position."); + [[maybe_unused]] constexpr auto bitwidth = sizeof(T) * CHAR_BIT; + assertm(p1 < static_cast(bitwidth) and p2 < static_cast(bitwidth), + "swapBits: invalid bit position."); + + // left-shift 1 p1 and p2 times + // and using XOR + if (((n & (1 << p1)) >> p1) ^ ((n & (1 << p2)) >> p2)) + { + n ^= 1 << p1; + n ^= 1 << p2; + } + return n; +} // swapBits + + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_ORCHARD_KEY_UTILS_H diff --git a/src/kalypsso/core/p4est_utils.h b/src/kalypsso/core/p4est_utils.h new file mode 100644 index 0000000..f958aec --- /dev/null +++ b/src/kalypsso/core/p4est_utils.h @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file p4est_utils.h + * + */ +#ifndef KALYPSSO_CORE_P4EST_UTILS_H_ +#define KALYPSSO_CORE_P4EST_UTILS_H_ + +#include +#include +#include + +namespace kalypsso +{ + +// ========================================================================= +// ========================================================================= +/** + * Create a Kokkos::View on host containing the quadrant Id for each local tree. + * + * The result view has view equal to the number of trees present in current MPI process. + * \param[in] the p4est object. + * + * \note this function was written at some early stage, but is currently not used. delete ? + * + */ +template +decltype(auto) +init_first_quadid_per_tree(forest_t * forest) +{ + using quadid_view_t = typename Kokkos::View; + using quadid_view_host_t = + typename Kokkos::View; + + uint32_t first_tree = forest->first_local_tree; + uint32_t last_tree = forest->last_local_tree; + + quadid_view_host_t local_first_quad_id_per_tree_host( + Kokkos::view_alloc(Kokkos::WithoutInitializing, "local_first_quad_id_per_tree_host"), + last_tree - first_tree + 1); + + // loop over all local trees, to accumulate (scan exclusive scan) + // the number of quadrant + for (size_t treeid = first_tree; treeid <= last_tree; ++treeid) + { + tree_t * tree = p4est::Wrapper::tree_array_index(forest->trees, treeid); + if (treeid == first_tree) + { + local_first_quad_id_per_tree_host(treeid - first_tree) = tree->quadrants.elem_counts; + } + else + { + local_first_quad_id_per_tree_host(treeid - first_tree) = + local_first_quad_id_per_tree_host(treeid - first_tree - 1) + tree->quadrants.elem_counts; + } + } + + return local_first_quad_id_per_tree_host; + +} // init_first_quadid_per_tree + +} // namespace kalypsso +#endif // KALYPSSO_CORE_P4EST_UTILS_H_ diff --git a/src/kalypsso/core/physical_constants.h b/src/kalypsso/core/physical_constants.h new file mode 100644 index 0000000..11f5587 --- /dev/null +++ b/src/kalypsso/core/physical_constants.h @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * physical_constants.h + */ +#ifndef KALYPSSO_CORE_PHYSICAL_CONSTANTS_H_ +#define KALYPSSO_CORE_PHYSICAL_CONSTANTS_H_ + +#include + +namespace kalypsso +{ + +namespace constants +{ + +/** + * The Avogadro and Boltzmann constants are slightly different from the exact values + * defined by NIST since 2019: + * - https://physics.nist.gov/cgi-bin/cuu/Value?na|search_for=avogadro + * - https://physics.nist.gov/cgi-bin/cuu/Value?k + * + * \todo see if it would be interesting support also the exact values. + */ + +static constexpr real_t AVOGADRO = KALYPSSO_NUM(6.02214179e23); //!< Avogadro constant +static constexpr real_t BOLTZMANN = KALYPSSO_NUM(1.3806488e-23); //!< Boltzmann constant +static constexpr real_t MU0 = 4 * PI_F * KALYPSSO_NUM(1e-7); //!< vacuum magnetic permeability + +} // namespace constants + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_PHYSICAL_CONSTANTS_H_ diff --git a/src/kalypsso/core/problems/AlfvenParams.h b/src/kalypsso/core/problems/AlfvenParams.h new file mode 100644 index 0000000..1388f6e --- /dev/null +++ b/src/kalypsso/core/problems/AlfvenParams.h @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file AlfvenParams.h + */ +#ifndef KALYPSSO_CORE_PROBLEMS_ALFVEN_PARAMS_H_ +#define KALYPSSO_CORE_PROBLEMS_ALFVEN_PARAMS_H_ + +#include +#include + +namespace kalypsso +{ + +/* +* Propagation of an Alfven wave test parameters. +* +* see [Tóth2011] Reducing numerical diffusion in magnetospheric simulations. + Journal of Geophysical Research: Space Physics, 116(A7). +*/ + +struct AlfvenParams +{ + + // Alfven wave parameters: + + // Density + real_t rho; + + // Background magnetic field + real_t B0x; + + // Pressure + real_t p; + + // Intensity of the perturbation + real_t Amp; + + // Range of the perturbation + real_t xrange; + + AlfvenParams(ConfigMap const & config_map) + { + + rho = config_map.getReal("alfven", "rho", KALYPSSO_NUM(1.0)); + p = config_map.getReal("alfven", "p", KALYPSSO_NUM(5.0)); + B0x = config_map.getReal("alfven", "B0x", KALYPSSO_NUM(3.0)); + Amp = config_map.getReal("alfven", "Amp", KALYPSSO_NUM(-0.1)); + xrange = config_map.getReal("alfven", "xrange", KALYPSSO_NUM(-1.5)); + } + +}; // struct AlfvenParams + +} // namespace kalypsso + + +#endif // KALYPSSO_CORE_PROBLEMS_ALFVEN_PARAMS_H_ diff --git a/src/kalypsso/core/problems/BlastParams.h b/src/kalypsso/core/problems/BlastParams.h new file mode 100644 index 0000000..5ed6337 --- /dev/null +++ b/src/kalypsso/core/problems/BlastParams.h @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file BlastParams.h + */ +#ifndef KALYPSSO_CORE_PROBLEMS_BLAST_PARAMS_H_ +#define KALYPSSO_CORE_PROBLEMS_BLAST_PARAMS_H_ + +#include +#include + +namespace kalypsso +{ + +/** + * (Sedov) Blast test parameters. + * + * We provide two types of initializations: + * + * - if total_energy_inside is greater than 0, then we initialize pressure by + * (gamma-1)*e/volume_inside where volume_inside is total volume of the inside ball + * - if total_energy_inside is zero, then we simply used blast_pressure_in to initialize pressure + * + * When performing a quantitative Sedov test, you need to use the first initialization to properly + * control the energy deposit inside the ball, and chose the ball radius as small as possible (e.g. + * 1/100th of the box size). + * + * \note this test can also be run in MHD using a uniform magnetic field. + * Reference: + * "The Athena++ Adaptive Mesh Refinement Framework: Design and Magnetohydrodynamic Solvers", James + * M. Stone et al 2020 ApJS 249 4, https://iopscience.iop.org/article/10.3847/1538-4365/ab929b + * section 3.4.4 + * DOI 10.3847/1538-4365/ab929b + * + */ +struct BlastParams +{ + + // blast problem parameters + real_t blast_radius; + real_t blast_center_x; + real_t blast_center_y; + real_t blast_center_z; + real_t blast_density_in; + real_t blast_density_out; + real_t blast_pressure_in; + real_t blast_pressure_out; + real_t total_energy_inside = ZERO_F; + real_t bx; + real_t by; + real_t bz; + + BlastParams(ConfigMap const & config_map) + { + + const auto xmin = config_map.getReal("mesh", "xmin", ZERO_F); + const auto ymin = config_map.getReal("mesh", "ymin", ZERO_F); + const auto zmin = config_map.getReal("mesh", "zmin", ZERO_F); + + const auto nbrick_x = config_map.getInteger("p4est_connectivity", "nbrick_x", 1); + const auto nbrick_y = config_map.getInteger("p4est_connectivity", "nbrick_y", 1); + const auto nbrick_z = config_map.getInteger("p4est_connectivity", "nbrick_z", 1); + + const auto scaling_factor = config_map.getReal("mesh", "scaling_factor", KALYPSSO_NUM(1.0)); + + const auto xmax = xmin + static_cast(nbrick_x) * scaling_factor; + const auto ymax = ymin + static_cast(nbrick_y) * scaling_factor; + const auto zmax = zmin + static_cast(nbrick_z) * scaling_factor; + + blast_radius = config_map.getReal("blast", "radius", (xmin + xmax) / TWO_F / 10); + blast_center_x = config_map.getReal("blast", "center_x", (xmin + xmax) / 2); + blast_center_y = config_map.getReal("blast", "center_y", (ymin + ymax) / 2); + blast_center_z = config_map.getReal("blast", "center_z", (zmin + zmax) / 2); + + blast_density_in = config_map.getReal("blast", "density_in", KALYPSSO_NUM(1.0)); + blast_density_out = config_map.getReal("blast", "density_out", KALYPSSO_NUM(1.2)); + blast_pressure_in = config_map.getReal("blast", "pressure_in", KALYPSSO_NUM(10.0)); + blast_pressure_out = config_map.getReal("blast", "pressure_out", KALYPSSO_NUM(0.1)); + total_energy_inside = config_map.getReal("blast", "total_energy_inside", ZERO_F); + bx = config_map.getReal("blast", "bx", ONE_F); + by = config_map.getReal("blast", "by", SQRT_3_F); + bz = config_map.getReal("blast", "bz", ZERO_F); + } // BlastParams + +}; // struct BlastParams + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_PROBLEMS_BLAST_PARAMS_H_ diff --git a/src/kalypsso/core/problems/BreakingWaveParams.h b/src/kalypsso/core/problems/BreakingWaveParams.h new file mode 100644 index 0000000..8a496d4 --- /dev/null +++ b/src/kalypsso/core/problems/BreakingWaveParams.h @@ -0,0 +1,104 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file BreakingWaveParams.h + */ +#ifndef KALYPSSO_CORE_PROBLEMS_BREAKING_WAVE_PARAMS_H_ +#define KALYPSSO_CORE_PROBLEMS_BREAKING_WAVE_PARAMS_H_ + +#include +#include + +namespace kalypsso +{ + +/** + * breaking waves test case parameters definition. + */ +struct BreakingWaveParams +{ + //! reference density + const real_t rho0; + + //! reference pressure + const real_t p0; + + //! relative amplitude of the sine wave + const real_t alpha; + + //! specific heat ratio + const real_t gamma; + + //! reference sound speed + const real_t c0; + + //! compute density + KOKKOS_INLINE_FUNCTION + auto + rho(real_t x) const + { + return rho0 * (1 + alpha * sin(2 * PI_F * x)); + } + + //! compute pressure from density + KOKKOS_INLINE_FUNCTION + auto + p(real_t _rho) const + { + return p0 * pow(_rho / rho0, gamma); + } + + //! compute sound speed + KOKKOS_INLINE_FUNCTION + real_t + c(real_t _rho) const + { + return c0 * pow(_rho / rho0, (gamma - 1) / 2); + } + + //! compute initial velocity + KOKKOS_INLINE_FUNCTION + real_t + u(real_t _c) const + { + return 2 / (gamma - 1) * (c0 - _c); + } + + //! compute time at which shock is formed + KOKKOS_INLINE_FUNCTION real_t + t_shock() const + { + return 1 / ((gamma + 1) * PI_F * alpha * c0); + } + + //! For a given x,t find x0 such that u(x0,0) = u(x,t). + //! x is the image of x0 by the advection at speed u-c as explained in article by Cook and Cabot. + //! u-c is an invariant, so given an x, u-c can be computed at (x,t) or (x0,0). + //! + //! \param[in] x is the target point where we want to compute u(x,t) \param[in] t is time + //! \param[in] uf velocity at final time + //! \param[in] cf speed of sound at final time + //! + //! \return x0 the preimage of x + KOKKOS_INLINE_FUNCTION real_t + compute_x0(real_t x, real_t t, real_t uf, real_t cf) const + { + return x - (uf - cf) * t; + } + + //! constructor + BreakingWaveParams(ConfigMap const & config_map) + : rho0(config_map.getReal("breaking_wave", "rho0", KALYPSSO_NUM(1e-3))) + , p0(config_map.getReal("breaking_wave", "p0", KALYPSSO_NUM(1e6))) + , alpha(config_map.getReal("breaking_wave", "alpha", KALYPSSO_NUM(0.1))) + , gamma(config_map.getReal("hydro", "gamma0", KALYPSSO_NUM(5.0) / 3)) + , c0(sqrt(gamma * p0 / rho0)) + {} + +}; // struct BreakingWaveParams + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_PROBLEMS_BREAKING_WAVE_PARAMS_H_ diff --git a/src/kalypsso/core/problems/DiamagCavityParams.h b/src/kalypsso/core/problems/DiamagCavityParams.h new file mode 100644 index 0000000..f6ed974 --- /dev/null +++ b/src/kalypsso/core/problems/DiamagCavityParams.h @@ -0,0 +1,176 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file DiamagCavityParams.h + */ +#ifndef KALYPSSO_CORE_PROBLEMS_DIAMAG_CAVITY_PARAMS_H_ +#define KALYPSSO_CORE_PROBLEMS_DIAMAG_CAVITY_PARAMS_H_ + +#include +#include +#include + +#include +#include <../../better-enums/enum.h> + +namespace kalypsso +{ + +/** + * Cavity shape. + */ +// clang-format off +BETTER_ENUM(DiamagCavityShape, int, SPHERE, CYLINDER) +// clang-format on + +/** + * Density profile type. + */ +// clang-format off +BETTER_ENUM(RhoProfile, int, BASE = 1, MODIFIED = 2) +// clang-format on + + +/** + * Provide all parameters to init the diamagnetic cavity test case. + */ +struct DiamagCavityParams +{ + + //! get shape + static inline DiamagCavityShape + get_shape(ConfigMap const & config_map) + { + auto shapeStr = config_map.getString("cavity", "shape", "SPHERE"); + std::transform(shapeStr.begin(), shapeStr.end(), shapeStr.begin(), ::toupper); + + auto maybe_value = DiamagCavityShape::_from_string_nothrow(shapeStr.c_str()); + if (maybe_value) + { + return *maybe_value; + } + + // default + return DiamagCavityShape::SPHERE; + } + + //! get shape + static inline RhoProfile + get_rho_profile(ConfigMap const & config_map) + { + auto rhoProfileStr = config_map.getString("cavity", "rho_profile", "BASE"); + std::transform(rhoProfileStr.begin(), rhoProfileStr.end(), rhoProfileStr.begin(), ::toupper); + + auto maybe_value = RhoProfile::_from_string_nothrow(rhoProfileStr.c_str()); + if (maybe_value) + { + return *maybe_value; + } + + // default + return RhoProfile::BASE; + } + + // Diamagnetic cavity parameters + real_t center_x; //!< Center of the cavity in the x axis + real_t center_y; //!< Center of the cavity in the y axis + real_t center_z; //!< Center of the cavity in the z axis + + real_t t0; //!< Physical starting time [s] + + real_t Vmin; //!< Minimal Value of the initial debris velocity [m/s] + real_t Vmax; //!< Maximal value of the initial debris Velocity [m/s] + real_t dV; //!< Velocity variation in the initial debris cloud [s^(-1)] + + real_t rmin; //!< Minimal radius of the initial spread of the debris [m] + real_t rmax; //!< Minimal radius of the initial spread of the debris [m] + + real_t M_d; //!< Debris molar mass (default is Carbon) [kg/mol] + real_t m_d; //!< Debris mass of one atom/ion [kg] + real_t N_d; //!< Debris ion number [#] + real_t T_d; //!< Debris initial temperature equivalent to Ti 1eV=11300K [K] + + real_t M_a; //!< Ambient molar mass (default is Hydrogen) [kg/mol] + real_t m_a; //!< Ambient mass of one atom/ion [kg] + real_t n_a; //!< Ambient ion density [#/m^(-3)] + real_t N_a; //!< Ambient ion number [#] + real_t rho_a; //!< Ambient ion mass density [kg/m^3] + + real_t B0; //!< Ambient amplitude of magnetic field [T] + real_t Bx; + real_t By; + real_t Bz; + + real_t amplitude_perturb; //! Amplitude of the perturbation of initial density + real_t wave_number; //! wave number of the perturbation of initial density + bool perturb; //! add a sinusoidinis perturbation on initial density contour + + RhoProfile rho_profile; + + DiamagCavityShape shape; + + DiamagCavityParams(ConfigMap const & config_map) + : center_x(ZERO_F) + , center_y(ZERO_F) + , center_z(ZERO_F) + , t0(config_map.getReal("cavity", "t0", KALYPSSO_NUM(2e-7))) + , Vmin(config_map.getReal("cavity", "Vmin", ZERO_F)) + , Vmax(config_map.getReal("cavity", "Vmax", KALYPSSO_NUM(260e3))) + , dV(ZERO_F) + , rmin(config_map.getReal("cavity", "rmin", ZERO_F)) + , rmax(config_map.getReal("cavity", "rmax", Vmax * t0)) + , M_d(config_map.getReal("cavity", "M_d", KALYPSSO_NUM(0.012011))) + , m_d(ZERO_F) + , N_d(config_map.getReal("cavity", "N_d", KALYPSSO_NUM(1.5608816704478538e+18))) + , T_d(config_map.getReal("cavity", "T_d", KALYPSSO_NUM(11604.0))) + , M_a(config_map.getReal("cavity", "M_a", KALYPSSO_NUM(1.008e-3))) + , m_a(ZERO_F) + , n_a(config_map.getReal("cavity", "n_a", KALYPSSO_NUM(5e18))) + , N_a(ZERO_F) + , rho_a(ZERO_F) + , B0(config_map.getReal("cavity", "B0", KALYPSSO_NUM(0.02))) + , Bx(config_map.getReal("cavity", "Bx", ZERO_F)) + , By(config_map.getReal("cavity", "By", ZERO_F)) + , Bz(config_map.getReal("cavity", "Bz", B0 / sqrt(constants::MU0))) + , amplitude_perturb(config_map.getReal("cavity", "amplitude_perturb", KALYPSSO_NUM(0.0025))) + , wave_number(config_map.getReal("cavity", "wave_number", KALYPSSO_NUM(10.0))) + , perturb(config_map.getBool("cavity", "perturb", false)) + , rho_profile(get_rho_profile(config_map)) + , shape(get_shape(config_map)) + { + const auto xmin = config_map.getReal("mesh", "xmin", ZERO_F); + const auto ymin = config_map.getReal("mesh", "ymin", ZERO_F); + const auto zmin = config_map.getReal("mesh", "zmin", ZERO_F); + + const auto nbrick_x = config_map.getInteger("p4est_connectivity", "nbrick_x", 1); + const auto nbrick_y = config_map.getInteger("p4est_connectivity", "nbrick_y", 1); + const auto nbrick_z = config_map.getInteger("p4est_connectivity", "nbrick_z", 1); + + const auto scaling_factor = config_map.getReal("mesh", "scaling_factor", 1.0); + + const auto xmax = xmin + nbrick_x * scaling_factor; + const auto ymax = ymin + nbrick_y * scaling_factor; + const auto zmax = zmin + nbrick_z * scaling_factor; + + center_x = config_map.getReal("cavity", "center_x", (xmin + xmax) / 2); + center_y = config_map.getReal("cavity", "center_y", (ymin + ymax) / 2); + center_z = config_map.getReal("cavity", "center_z", (zmin + zmax) / 2); + + // Values deduced from inputs + dV = (Vmax - Vmin) / (rmax - rmin); + + m_d = M_d / constants::AVOGADRO; + + m_a = M_a / constants::AVOGADRO; + N_a = n_a * PI_F * rmax * rmax; + rho_a = m_a * n_a; + + }; // DiamagCavityParams + +}; // struct DiamagCavityParams + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_PROBLEMS_DIAMAG_CAVITY_PARAMS_H_ diff --git a/src/kalypsso/core/problems/DoubleMachReflectionParams.h b/src/kalypsso/core/problems/DoubleMachReflectionParams.h new file mode 100644 index 0000000..470f6c7 --- /dev/null +++ b/src/kalypsso/core/problems/DoubleMachReflectionParams.h @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file DoubleMachReflectionParams.h + */ +#ifndef KALYPSSO_CORE_PROBLEMS_DOUBLE_MACH_REFLECTION_PARAMS_H_ +#define KALYPSSO_CORE_PROBLEMS_DOUBLE_MACH_REFLECTION_PARAMS_H_ + +#include +#include + +namespace kalypsso +{ + +/** + * DoubleMachReflection test parameters. + * + * A shock wave making an angle with a reflecting wall + * + * see reference + * Woodward, P. and Colella, P., "The Numerical Simulation of Two-Dimensional + * Fluid Flow with Strong Shocks", J. Computational Physics, 54, 115-173 (1984). + * https://doi.org/10.1016/0021-9991(84)90142-6 + * + * This configuration is also some time called wedge; see e.g. + * http://amroc.sourceforge.net/examples/euler/2d/html/ramp_n.htm + * + * This test is only available for monofluid, using ideal gas EOS. + * + */ +struct DoubleMachReflectionParams +{ + + // double Mach reflection problem parameters: left and right states + real_t angle; // angle between the shock front and the vertical in degree + real_t x0; // initial position (lower left) of the shock + + real_t rhoL; + real_t uL; + real_t vL; + real_t pL; + + real_t rhoR; + real_t uR; + real_t vR; + real_t pR; + + real_t shock_speed; + + DoubleMachReflectionParams(ConfigMap const & config_map) + : angle(config_map.getReal("double_mach_reflection", "angle", KALYPSSO_NUM(30.0))) + , x0(config_map.getReal("double_mach_reflection", "x0", KALYPSSO_NUM(1.0) / 6)) + , rhoL(config_map.getReal("double_mach_reflection", "rhoL", KALYPSSO_NUM(8.0))) + , uL(config_map.getReal("double_mach_reflection", + "uL", + KALYPSSO_NUM(8.25) * cos(PI_F * angle / 180))) + , vL(config_map.getReal("double_mach_reflection", + "vL", + -KALYPSSO_NUM(8.25) * sin(PI_F * angle / 180))) + , pL(config_map.getReal("double_mach_reflection", "pL", KALYPSSO_NUM(116.5))) + , rhoR(config_map.getReal("double_mach_reflection", "rhoR", KALYPSSO_NUM(1.4))) + , uR(config_map.getReal("double_mach_reflection", "uR", KALYPSSO_NUM(0.0))) + , vR(config_map.getReal("double_mach_reflection", "vR", KALYPSSO_NUM(0.0))) + , pR(config_map.getReal("double_mach_reflection", "pR", KALYPSSO_NUM(1.0))) + { + // TODO: replace the initialization of gamma0 when lag_remap_hydro is refactored + const auto gamma0 = config_map.getReal("hydro", "gamma0", KALYPSSO_NUM(1.4)); + // const auto gamma0 = config_map.getReal("material0", "gamma", KALYPSSO_NUM(1.4)); + + // soud speed + const auto aR = sqrt(gamma0 * pR / rhoR); + + // shock speed (S3 in Toro, section 3.1.3, page 100) + // should be 10 with default value (as in the original Woodward Collela article) + shock_speed = + uR + aR * sqrt((gamma0 + ONE_F) / (2 * gamma0) * pL / pR + (gamma0 - ONE_F) / (2 * gamma0)); + } + + KOKKOS_FUNCTION bool + is_point_in_post_shock_region(real_t x, real_t y, real_t t) const + { + const real_t x1 = x - shock_speed / cos(PI_F * angle / 180) * t; + return y >= (x1 - x0) / tan(PI_F * angle / 180); + } + + KOKKOS_FUNCTION real_t + initial_shock_position_x(real_t y) const + { + return x0 + y * tan(PI_F * angle / 180); + } + +}; // struct DoubleMachReflectionParams + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_PROBLEMS_DOUBLE_MACH_REFLECTION_PARAMS_H_ diff --git a/src/kalypsso/core/problems/DropletAdvectionParams.h b/src/kalypsso/core/problems/DropletAdvectionParams.h new file mode 100644 index 0000000..221e902 --- /dev/null +++ b/src/kalypsso/core/problems/DropletAdvectionParams.h @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file DropletAdvectionParams.h + */ +#ifndef KALYPSSO_GODUNOV_FIVE_EQ_DROPLET_ADVECTION_PARAMS_H_ +#define KALYPSSO_GODUNOV_FIVE_EQ_DROPLET_ADVECTION_PARAMS_H_ + +#include +#include + +namespace kalypsso +{ + +/** + * Droplet advection test parameters. + * + * A liquid droplet inside a gas advected with constant velocity. + * + * references : + * + * An interface capturing scheme for modeling atomization in compressible flows, Garrick et al., + * Journal of Computational Physics Volume 344, 1 September 2017, Pages 260-280. + * https://doi.org/10.1016/j.jcp.2017.04.079 + * + * A hybrid WENO5IS-THINC reconstruction scheme for compressible multiphase flows, Zhand et al., + * Journal of Computational Physics Volume 498, 1 February 2024, 112672. + * https://doi.org/10.1016/j.jcp.2023.112672 + */ +struct DropletAdvectionParams +{ + + // droplet advection problem parameters + + //! droplet radius + real_t radius; + + //! droplet center, x coordinate. + real_t x; + + //! droplet center, y coordinate. + real_t y; + + //! droplet center, z coordinate. + real_t z; + + DropletAdvectionParams(ConfigMap const & config_map) + { + const auto xmin = config_map.getReal("mesh", "xmin", KALYPSSO_NUM(-1.0)); + const auto ymin = config_map.getReal("mesh", "ymin", KALYPSSO_NUM(-1.0)); + const auto zmin = config_map.getReal("mesh", "zmin", KALYPSSO_NUM(-1.0)); + + const auto nbrick_x = config_map.getInteger("p4est_connectivity", "nbrick_x", 1); + const auto nbrick_y = config_map.getInteger("p4est_connectivity", "nbrick_y", 1); + const auto nbrick_z = config_map.getInteger("p4est_connectivity", "nbrick_z", 1); + + const auto scaling_factor = config_map.getReal("mesh", "scaling_factor", KALYPSSO_NUM(2.0)); + + const auto xmax = xmin + static_cast(nbrick_x) * scaling_factor; + const auto ymax = ymin + static_cast(nbrick_y) * scaling_factor; + const auto zmax = zmin + static_cast(nbrick_z) * scaling_factor; + + radius = config_map.getReal("droplet_advection", "radius", KALYPSSO_NUM(1.0)); + + x = config_map.getReal("droplet_advection", "x", (xmin + xmax) / 2); + y = config_map.getReal("droplet_advection", "y", (ymin + ymax) / 2); + z = config_map.getReal("droplet_advection", "z", (zmin + zmax) / 2); + } + +}; // struct DropletAdvectionParams + +} // namespace kalypsso + +#endif // KALYPSSO_GODUNOV_FIVE_EQ_DROPLET_ADVECTION_PARAMS_H_ diff --git a/src/kalypsso/core/problems/FieldLoopAdvectionParams.h b/src/kalypsso/core/problems/FieldLoopAdvectionParams.h new file mode 100644 index 0000000..68aa287 --- /dev/null +++ b/src/kalypsso/core/problems/FieldLoopAdvectionParams.h @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file FieldLoopParamsParams.h + */ +#ifndef KALYPSSO_CORE_PROBLEMS_FIELDLOOPADVECTION_PARAMS_H_ +#define KALYPSSO_CORE_PROBLEMS_FIELDLOOPADVECTION_PARAMS_H_ + +#include +#include + +namespace kalypsso +{ + +/** + * Field loop advection problem. + * + * + * Reference: + * - T. Gardiner & J.M. Stone, "An unsplit Godunov method for ideal MHD + * via constrained transport", JCP, 205, 509 (2005) + * - http://www.astro.princeton.edu/~jstone/Athena/tests/field-loop/Field-loop.html + */ +struct FieldLoopAdvectionParams +{ + + real_t radius; + real_t density; + real_t pressure; + real_t B0; + real_t vflow; + + //! field loop center coordinates at t=0 + real_t xc, yc, zc; + + // ============================================================================ + // ============================================================================ + FieldLoopAdvectionParams(ConfigMap const & config_map) + : radius(config_map.getReal("FieldLoop", "radius", KALYPSSO_NUM(1.0))) + , density(config_map.getReal("FieldLoop", "density", KALYPSSO_NUM(1.0))) + , pressure(config_map.getReal("FieldLoop", "pressure", KALYPSSO_NUM(1.0))) + , B0(config_map.getReal("FieldLoop", "B0", KALYPSSO_NUM(0.001))) + , vflow(config_map.getReal("FieldLoop", "vflow", SQRT_5_F)) + { + const auto xmin = config_map.getReal("mesh", "xmin", KALYPSSO_NUM(0.0)); + const auto ymin = config_map.getReal("mesh", "ymin", KALYPSSO_NUM(0.0)); + const auto zmin = config_map.getReal("mesh", "zmin", KALYPSSO_NUM(0.0)); + + const auto nbrick_x = config_map.getInteger("p4est_connectivity", "nbrick_x", 1); + const auto nbrick_y = config_map.getInteger("p4est_connectivity", "nbrick_y", 1); + const auto nbrick_z = config_map.getInteger("p4est_connectivity", "nbrick_z", 1); + + const auto scaling_factor = config_map.getReal("mesh", "scaling_factor", KALYPSSO_NUM(1.0)); + + const auto xmax = xmin + static_cast(nbrick_x) * scaling_factor; + const auto ymax = ymin + static_cast(nbrick_y) * scaling_factor; + const auto zmax = zmin + static_cast(nbrick_z) * scaling_factor; + + // default value for field loop center coordinates are the box center coordinates + xc = config_map.getReal("FieldLoop", "xc", (xmin + xmax) / 2); + yc = config_map.getReal("FieldLoop", "yc", (ymin + ymax) / 2); + zc = config_map.getReal("FieldLoop", "zc", (zmin + zmax) / 2); + } + +}; // struct FieldLoopParams + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_PROBLEMS_FIELDLOOPADVECTION_PARAMS_H_ diff --git a/src/kalypsso/core/problems/FourQuadrantParams.h b/src/kalypsso/core/problems/FourQuadrantParams.h new file mode 100644 index 0000000..abfc65b --- /dev/null +++ b/src/kalypsso/core/problems/FourQuadrantParams.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file FourQuadrantParams.h + */ +#ifndef KALYPSSO_CORE_PROBLEMS_FOURQUADRANT_PARAMS_H_ +#define KALYPSSO_CORE_PROBLEMS_FOURQUADRANT_PARAMS_H_ + +#include +#include + +namespace kalypsso +{ + +/** + * Four-Quadrant problem test parameters + **/ +struct FourQuadrantParams +{ + + real_t xt, yt; + int configNumber; + + FourQuadrantParams(ConfigMap const & config_map) + { + xt = config_map.getReal("four_quadrant", "x", KALYPSSO_NUM(0.8)); + yt = config_map.getReal("four_quadrant", "y", KALYPSSO_NUM(0.8)); + configNumber = config_map.getInteger("four_quadrant", "config_number", 0); + } + +}; // struct FourQuadrantParameters + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_PROBLEMS_FOURQUADRANT_PARAMS_H_ diff --git a/src/kalypsso/core/problems/GreshoVortexParams.h b/src/kalypsso/core/problems/GreshoVortexParams.h new file mode 100644 index 0000000..621458f --- /dev/null +++ b/src/kalypsso/core/problems/GreshoVortexParams.h @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file GreshoVortexParams.h + */ +#ifndef KALYPSSO_CORE_PROBLEMS_GRESHO_VORTEX_PARAMS_H_ +#define KALYPSSO_CORE_PROBLEMS_GRESHO_VORTEX_PARAMS_H_ + +#include +#include + +namespace kalypsso +{ + +/** + * The Gresho problem is a rotating vortex problem independent of time + * for the case of inviscid flow (Euler equations). + * + * reference : https://www.cfd-online.com/Wiki/Gresho_vortex + */ +struct GreshoVortexParams +{ + + real_t rho0; + real_t Ma; + + // advection velocity (optional) + real_t u, v, w; + + GreshoVortexParams(ConfigMap & config_map) + { + + rho0 = config_map.getReal("Gresho", "rho0", KALYPSSO_NUM(1.0)); + Ma = config_map.getReal("Gresho", "Ma", KALYPSSO_NUM(0.1)); + + u = config_map.getReal("Gresho", "u", KALYPSSO_NUM(0.0)); + v = config_map.getReal("Gresho", "v", KALYPSSO_NUM(0.0)); + w = config_map.getReal("Gresho", "w", KALYPSSO_NUM(0.0)); + + } // GreshoVortexParams + +}; // struct GreshoVortexParams + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_PROBLEMS_GRESHO_VORTEX_PARAMS_H_ diff --git a/src/kalypsso/core/problems/ImplodeParams.h b/src/kalypsso/core/problems/ImplodeParams.h new file mode 100644 index 0000000..9356a26 --- /dev/null +++ b/src/kalypsso/core/problems/ImplodeParams.h @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ImplodeParams.h + * + * Hydrodynamical Implosion Test. + * + * Implosion test references: + * - https://www.astro.princeton.edu/~jstone/Athena/tests/implode/Implode.html + * - https://www.sciencedirect.com/science/article/pii/S0021999199962952 + * - http://www-troja.fjfi.cvut.cz/~liska/CompareEuler/compare8/ + * - https://www.sciencedirect.com/science/article/pii/S0045793021003364#b46 + * + * \note It can be used for both hydro and MHD test cases. + */ +#ifndef KALYPSSO_CORE_PROBLEMS_IMPLODE_PARAMS_H_ +#define KALYPSSO_CORE_PROBLEMS_IMPLODE_PARAMS_H_ + +#include +#include + +#include <../../better-enums/enum.h> + +namespace kalypsso +{ + +// clang-format off +BETTER_ENUM(ImplodeShape, int, DIAGONAL, DIAMOND) +// clang-format on + +/** + * Implode test parameters. + */ +struct ImplodeParams +{ + + //! get shape + static inline ImplodeShape + get_shape(ConfigMap const & config_map) + { + auto shapeStr = config_map.getString("implode", "shape", "DIAGONAL"); + std::transform(shapeStr.begin(), shapeStr.end(), shapeStr.begin(), ::toupper); + + auto maybe_value = ImplodeShape::_from_string_nothrow(shapeStr.c_str()); + if (maybe_value) + { + return *maybe_value; + } + + // default + return ImplodeShape::DIAGONAL; + } + + // outer parameters + real_t rho_out; + real_t p_out; + real_t u_out; + real_t v_out; + real_t w_out; + real_t Bx_out; + real_t By_out; + real_t Bz_out; + + // inner parameters + real_t rho_in; + real_t p_in; + real_t u_in; + real_t v_in; + real_t w_in; + real_t Bx_in; + real_t By_in; + real_t Bz_in; + + //! shape: diagonal or diamond + ImplodeShape shape; + + // if true slightly change init condition to be non trivial + // gradient along domain diagonal + bool debug; + + ImplodeParams(ConfigMap const & config_map) + : rho_out(config_map.getReal("implode", "rho_out", KALYPSSO_NUM(1.0))) + , p_out(config_map.getReal("implode", "p_out", KALYPSSO_NUM(1.0))) + , u_out(config_map.getReal("implode", "u_out", KALYPSSO_NUM(0.0))) + , v_out(config_map.getReal("implode", "v_out", KALYPSSO_NUM(0.0))) + , w_out(config_map.getReal("implode", "w_out", KALYPSSO_NUM(0.0))) + , Bx_out(config_map.getReal("implode", "Bx_out", KALYPSSO_NUM(0.0))) + , By_out(config_map.getReal("implode", "By_out", KALYPSSO_NUM(0.0))) + , Bz_out(config_map.getReal("implode", "Bz_out", KALYPSSO_NUM(0.0))) + , rho_in(config_map.getReal("implode", "rho_in", KALYPSSO_NUM(0.125))) + , p_in(config_map.getReal("implode", "p_in", KALYPSSO_NUM(0.14))) + , u_in(config_map.getReal("implode", "u_in", KALYPSSO_NUM(0.0))) + , v_in(config_map.getReal("implode", "v_in", KALYPSSO_NUM(0.0))) + , w_in(config_map.getReal("implode", "w_in", KALYPSSO_NUM(0.0))) + , Bx_in(config_map.getReal("implode", "Bx_in", KALYPSSO_NUM(0.0))) + , By_in(config_map.getReal("implode", "By_in", KALYPSSO_NUM(0.0))) + , Bz_in(config_map.getReal("implode", "Bz_in", KALYPSSO_NUM(0.0))) + , shape(get_shape(config_map)) + , debug(config_map.getBool("implode", "debug", false)) + {} + +}; // struct ImplodeParams + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_PROBLEMS_IMPLODE_PARAMS_H_ diff --git a/src/kalypsso/core/problems/IsentropicVortexParams.h b/src/kalypsso/core/problems/IsentropicVortexParams.h new file mode 100644 index 0000000..1ede329 --- /dev/null +++ b/src/kalypsso/core/problems/IsentropicVortexParams.h @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file IsentropicVortexParams.h + */ +#ifndef KALYPSSO_CORE_PROBLEMS_ISENTROPIC_VORTEX_PARAMS_H_ +#define KALYPSSO_CORE_PROBLEMS_ISENTROPIC_VORTEX_PARAMS_H_ + +#include +#include + +namespace kalypsso +{ + +/** + * isentropic vortex advection test parameters. + */ +struct IsentropicVortexParams +{ + + //! isentropic vortex ambient flow hydrodynamic state variables + real_t rho_a; + real_t p_a; + real_t T_a; + real_t u_a; + real_t v_a; + real_t w_a; + + //! vortex center + real_t vortex_x; + real_t vortex_y; + real_t vortex_z; + + //! vortex strength + real_t beta; + + //! vortex scale factor + real_t scale; + + //! number of quadrature points (used to compute initial cell-averaged values) + int nQuadPts; + + //! useful to compute solution at final time + bool use_tEnd; + real_t tEnd; + + IsentropicVortexParams(ConfigMap const & config_map) + { + + const auto xmin = config_map.getReal("mesh", "xmin", KALYPSSO_NUM(0.0)); + const auto ymin = config_map.getReal("mesh", "ymin", KALYPSSO_NUM(0.0)); + const auto zmin = config_map.getReal("mesh", "zmin", KALYPSSO_NUM(0.0)); + + const auto nbrick_x = config_map.getInteger("p4est_connectivity", "nbrick_x", 1); + const auto nbrick_y = config_map.getInteger("p4est_connectivity", "nbrick_y", 1); + const auto nbrick_z = config_map.getInteger("p4est_connectivity", "nbrick_z", 1); + + const auto scaling_factor = config_map.getReal("mesh", "scaling_factor", KALYPSSO_NUM(1.0)); + + const auto xmax = xmin + static_cast(nbrick_x) * scaling_factor; + const auto ymax = ymin + static_cast(nbrick_y) * scaling_factor; + const auto zmax = zmin + static_cast(nbrick_z) * scaling_factor; + + rho_a = config_map.getReal("isentropic_vortex", "density_ambient", KALYPSSO_NUM(1.0)); + p_a = config_map.getReal("isentropic_vortex", "pressure_ambient", KALYPSSO_NUM(1.0)); + T_a = config_map.getReal("isentropic_vortex", "temperature_ambient", KALYPSSO_NUM(1.0)); + u_a = config_map.getReal("isentropic_vortex", "vx_ambient", KALYPSSO_NUM(1.0)); + v_a = config_map.getReal("isentropic_vortex", "vy_ambient", KALYPSSO_NUM(1.0)); + w_a = config_map.getReal("isentropic_vortex", "vz_ambient", KALYPSSO_NUM(1.0)); + + vortex_x = config_map.getReal("isentropic_vortex", "center_x", (xmin + xmax) / 2); + vortex_y = config_map.getReal("isentropic_vortex", "center_y", (ymin + ymax) / 2); + vortex_z = config_map.getReal("isentropic_vortex", "center_z", (zmin + zmax) / 2); + + beta = config_map.getReal("isentropic_vortex", "strength", KALYPSSO_NUM(5.0)); + scale = config_map.getReal("isentropic_vortex", "scale", KALYPSSO_NUM(1.0)); + + nQuadPts = config_map.getInteger("isentropic_vortex", "num_quadrature_points", 4); + + // default value is false, meaning we compute the initial value (t=0) + use_tEnd = config_map.getBool("isentropic_vortex", "use_tEnd", false); + tEnd = config_map.getReal("run", "tEnd", KALYPSSO_NUM(1.0)); + } + +}; // struct IsentropicVortexParams + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_PROBLEMS_ISENTROPIC_VORTEX_PARAMS_H_ diff --git a/src/kalypsso/core/problems/KHParams.h b/src/kalypsso/core/problems/KHParams.h new file mode 100644 index 0000000..2efff02 --- /dev/null +++ b/src/kalypsso/core/problems/KHParams.h @@ -0,0 +1,115 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file KHParams.h + */ +#ifndef KALYPSSO_CORE_PROBLEMS_KELVIN_HELMHOLTZ_PARAMS_H_ +#define KALYPSSO_CORE_PROBLEMS_KELVIN_HELMHOLTZ_PARAMS_H_ + +#include // for KALYPSSO_CORE_USE_MPI +#include +#include + +namespace kalypsso +{ + +/** + * A small structure to hold parameters passed to a Kokkos functor, + * for initializing the Kelvin-Helmholtz instability init condition. + * + * p_sine, p_sine_robertson and p_rand specify which type of perturbation is + * used to seed the instability. + * + * references: + * - "Computational Eulerian hydrodynamics and Galilean invariance", Robertson et al, MNRAS Volume + * 401, Issue 4, February 2010; https://doi.org/10.1111/j.1365-2966.2009.15823.x + * - "The Athena++ Adaptive Mesh Refinement Framework: Design and Magnetohydrodynamic Solvers", J. + * stone et al, 2020 ApJS 249 4; https://doi.org/10.3847/1538-4365/ab929b + */ +struct KHParams +{ + + // Kelvin-Helmholtz problem parameters + real_t d_in; //! density in + real_t d_out; //! density out + real_t pressure; + bool p_sine; //! sinus perturbation + bool p_sine_rob; //! sinus perturbation "à la Robertson" + bool p_sine_stone; //! sinus perturbation "à la Stone 2020" + bool p_rand; //! random perturbation + + real_t vflow_in; + real_t vflow_out; + + uint64_t seed; + real_t amplitude; //! perturbation amplitude + real_t outer_size; + real_t inner_size; + + // for sine perturbation "a la Robertson" + int mode; + real_t w0; + real_t delta; + + real_t bx; + real_t by; + real_t bz; + + KHParams(ConfigMap const & config_map) + { + + d_in = config_map.getReal("kelvin_helmholtz", "d_in", KALYPSSO_NUM(1.0)); + d_out = config_map.getReal("kelvin_helmholtz", "d_out", KALYPSSO_NUM(2.0)); + + pressure = config_map.getReal("kelvin_helmholtz", "pressure", KALYPSSO_NUM(10.0)); + + p_sine = config_map.getBool("kelvin_helmholtz", "perturbation_sine", false); + p_sine_rob = config_map.getBool("kelvin_helmholtz", "perturbation_sine_robertson", true); + p_sine_stone = config_map.getBool("kelvin_helmholtz", "perturbation_sine_stone", false); + p_rand = config_map.getBool("kelvin_helmholtz", "perturbation_rand", false); + + + vflow_in = config_map.getReal("kelvin_helmholtz", "vflow_in", KALYPSSO_NUM(-0.5)); + vflow_out = config_map.getReal("kelvin_helmholtz", "vflow_out", KALYPSSO_NUM(0.5)); + + if (p_rand) + { + // choose a different random seed per mpi rank + seed = static_cast(config_map.getInteger("kelvin_helmholtz", "rand_seed", 12)); + +#ifdef KALYPSSO_CORE_USE_MPI + // srand( seed * (mpiRank+1) ); + + // get MPI rank in MPI_COMM_WORLD + // TODO : pass communicator to the constructor (?) + int mpiRank = 1; + MPI_Comm_rank(MPI_COMM_WORLD, &mpiRank); + seed *= static_cast(mpiRank + 1); +#endif // KALYPSSO_CORE_USE_MPI + } + + amplitude = config_map.getReal("kelvin_helmholtz", "amplitude", KALYPSSO_NUM(0.1)); + + if (p_sine_rob or p_sine) + { + // perturbation mode number + inner_size = config_map.getReal("kelvin_helmholtz", "inner_size", KALYPSSO_NUM(0.2)); + + mode = config_map.getInteger("kelvin_helmholtz", "mode", 2); + w0 = config_map.getReal("kelvin_helmholtz", "w0", KALYPSSO_NUM(0.1)); + delta = config_map.getReal("kelvin_helmholtz", "delta", KALYPSSO_NUM(0.03)); + } + + bx = config_map.getReal("kelvin_helmholtz", "bx", KALYPSSO_NUM(0.0)); + by = config_map.getReal("kelvin_helmholtz", "by", KALYPSSO_NUM(0.0)); + bz = config_map.getReal("kelvin_helmholtz", "bz", KALYPSSO_NUM(0.0)); + + } // KHParams + +}; // struct KHParams + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_PROBLEMS_KELVIN_HELMHOLTZ_PARAMS_H_ diff --git a/src/kalypsso/core/problems/MHDShockTubeParams.h b/src/kalypsso/core/problems/MHDShockTubeParams.h new file mode 100644 index 0000000..1f65d6d --- /dev/null +++ b/src/kalypsso/core/problems/MHDShockTubeParams.h @@ -0,0 +1,112 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file MHDShockTubeParams.h + */ +#ifndef KALYPSSO_CORE_PROBLEMS_MHDSHOCKTUBE_PARAMS_H_ +#define KALYPSSO_CORE_PROBLEMS_MHDSHOCKTUBE_PARAMS_H_ + +#include +#include + +namespace kalypsso +{ + +/** + * MHD shock tube problem (Dai-Woodward, Brio-Wu, ...). + * + * Reference: + * - https://flash.rochester.edu/site/flashcode/user_support/flash_ug_devel/node192.html + * - https://www.astro.princeton.edu/~jstone/Athena/tests/brio-wu/Brio-Wu.html + * + * For reference tests input parameters, see table 4.1 in + * Bouchut, F., Klingenberg, C. & Waagan, K. A multiwave approximate Riemann solver for + * ideal MHD based on relaxation II: numerical implementation with 3 and 5 waves. + * Numer. Math. 115, 647–679 (2010). https://doi.org/10.1007/s00211-010-0289-4 + */ +struct MHDShockTubeParams +{ + + enum Direction : int + { + X = 0, + Y = 1, + Z = 2 + }; + + // Shock-Tube problem parameters (left and right state) + real_t rhoL; + real_t pL; + real_t uL; + real_t vL; + real_t wL; + Kokkos::Array BL; + + real_t rhoR; + real_t pR; + real_t uR; + real_t vR; + real_t wR; + Kokkos::Array BR; + + Direction direction; + + real_t xd; // discontinuity location + + MHDShockTubeParams(ConfigMap const & config_map) + { + + rhoL = config_map.getReal("shock-tube", "rhoL", KALYPSSO_NUM(1.0)); + pL = config_map.getReal("shock-tube", "pL", KALYPSSO_NUM(1.0)); + uL = config_map.getReal("shock-tube", "uL", KALYPSSO_NUM(0.0)); + vL = config_map.getReal("shock-tube", "vL", KALYPSSO_NUM(0.0)); + wL = config_map.getReal("shock-tube", "wL", KALYPSSO_NUM(0.0)); + BL[IX] = config_map.getReal("shock-tube", "BxL", KALYPSSO_NUM(0.75)); + BL[IY] = config_map.getReal("shock-tube", "ByL", KALYPSSO_NUM(1.0)); + BL[IZ] = config_map.getReal("shock-tube", "BzL", KALYPSSO_NUM(0.0)); + + rhoR = config_map.getReal("shock-tube", "rhoR", KALYPSSO_NUM(0.125)); + pR = config_map.getReal("shock-tube", "pR", KALYPSSO_NUM(0.1)); + uR = config_map.getReal("shock-tube", "uR", KALYPSSO_NUM(0.0)); + vR = config_map.getReal("shock-tube", "vR", KALYPSSO_NUM(0.0)); + wR = config_map.getReal("shock-tube", "wR", KALYPSSO_NUM(0.0)); + BR[IX] = config_map.getReal("shock-tube", "BxR", KALYPSSO_NUM(0.75)); + BR[IY] = config_map.getReal("shock-tube", "ByR", KALYPSSO_NUM(-1.0)); + BR[IZ] = config_map.getReal("shock-tube", "BzR", KALYPSSO_NUM(0.0)); + + direction = + static_cast(config_map.getInteger("shock-tube", "direction", Direction::X)); + + if (direction == Direction::X) + { + const auto xmin = config_map.getReal("mesh", "xmin", 0.0); + const auto nbrick_x = config_map.getInteger("p4est_connectivity", "nbrick_x", 1); + const auto scaling_factor = config_map.getReal("mesh", "scaling_factor", 1.0); + const auto xmax = xmin + static_cast(nbrick_x) * scaling_factor; + xd = config_map.getReal("shock-tube", "xd", (xmin + xmax) / 2); + } + else if (direction == Direction::Y) + { + const auto ymin = config_map.getReal("mesh", "ymin", 0.0); + const auto nbrick_y = config_map.getInteger("p4est_connectivity", "nbrick_y", 1); + const auto scaling_factor = config_map.getReal("mesh", "scaling_factor", 1.0); + const auto ymax = ymin + static_cast(nbrick_y) * scaling_factor; + xd = config_map.getReal("shock-tube", "xd", (ymin + ymax) / 2); + } + else + { + const auto zmin = config_map.getReal("mesh", "zmin", 0.0); + const auto nbrick_z = config_map.getInteger("p4est_connectivity", "nbrick_z", 1); + const auto scaling_factor = config_map.getReal("mesh", "scaling_factor", 1.0); + const auto zmax = zmin + static_cast(nbrick_z) * scaling_factor; + xd = config_map.getReal("shock-tube", "xd", (zmin + zmax) / 2); + } + } + +}; // struct MHDShockTubeParams + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_PROBLEMS_MHDSHOCKTUBE_PARAMS_H_ diff --git a/src/kalypsso/core/problems/OrszagTangParams.h b/src/kalypsso/core/problems/OrszagTangParams.h new file mode 100644 index 0000000..2249262 --- /dev/null +++ b/src/kalypsso/core/problems/OrszagTangParams.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file OrszagTangParams.h + */ +#ifndef KALYPSSO_SAHRED_PROBLEMS_ORSZAG_TANG_PARAMS_H_ +#define KALYPSSO_SAHRED_PROBLEMS_ORSZAG_TANG_PARAMS_H_ + +#include +#include + +namespace kalypsso +{ + +/** + * Orszag-Tang vortex parameters. + */ +struct OrszagTangParams +{ + enum VortexDir : int + { + X, + Y, + Z + }; + + //! transverse wave vector + real_t kt; + + //! vortex direction (only use full for cross-checking and debug) + int vortex_dir; + + OrszagTangParams(ConfigMap const & config_map) + { + kt = config_map.getReal("OrszagTang", "kt", KALYPSSO_NUM(0.0)); + vortex_dir = config_map.getInteger("OrszagTang", "vortex_dir", VortexDir::Z); + } + +}; // struct OrszagTangParams + +} // namespace kalypsso + +#endif // KALYPSSO_SAHRED_PROBLEMS_ORSZAG_TANG_PARAMS_H_ diff --git a/src/kalypsso/core/problems/RayleighTaylorParams.h b/src/kalypsso/core/problems/RayleighTaylorParams.h new file mode 100644 index 0000000..5a0169f --- /dev/null +++ b/src/kalypsso/core/problems/RayleighTaylorParams.h @@ -0,0 +1,112 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file RayleighTaylorParams.h + */ +#ifndef KALYPSSO_CORE_PROBLEMS_RAYLEIGH_TAYLOR_PARAMS_H_ +#define KALYPSSO_CORE_PROBLEMS_RAYLEIGH_TAYLOR_PARAMS_H_ + +#include // for KALYPSSO_CORE_USE_MPI +#include +#include +#include <../../better-enums/enum.h> + +#include // for std::transform +#include // for ::toupper + +namespace kalypsso +{ + +// clang-format off +BETTER_ENUM(RayleighTaylorPerturbationType, int, UNDEFINED, SINE, RANDOM) +// clang-format on + +//! Rayleigh-Taylor problem parameters. +//! +//! There are 2 possibles initializations: +//! - regular initialization: two fluids one above the other, each with uniform density +//! - isothermal initialization: two fluids one above the other, each with uniform temperature +//! +//! For each of these initialization, the initial transverse velocity is perturbed either using a +//! sine mode or random values. +struct RayleighTaylorParams +{ + //! get initital perturbation type (SINE or RANDOM) + static inline RayleighTaylorPerturbationType + get_perturbation_type(ConfigMap const & config_map) + { + auto perturbationTypeStr = config_map.getString("rayleigh_taylor", "perturbation", "SINE"); + std::transform(perturbationTypeStr.begin(), + perturbationTypeStr.end(), + perturbationTypeStr.begin(), + ::toupper); + + auto maybe_value = + RayleighTaylorPerturbationType::_from_string_nothrow(perturbationTypeStr.c_str()); + if (maybe_value) + { + return *maybe_value; + } + else + { + return RayleighTaylorPerturbationType::SINE; + } + } + + real_t amplitude; //!< perturbation amplitude + int nx; //!< mode number along x, use nx > 1 for a multi mode perturbation + int ny; //!< mode number along y, use ny > 1 for a multi mode perturbation + int nz; //!< mode number along z, use nz > 1 for a multi mode perturbation (3D only) + real_t rho_up; //!< fluid density above interface + real_t rho_down; //!< fluid density below interface + real_t P0; //!< reference pressure + RayleighTaylorPerturbationType perturb_type; //!< perturbation type (sine or random) + uint64_t seed; //!< random seed + real_t bx; //!< magnetic field along x + real_t by; //!< magnetic field along y + real_t bz; //!< magnetic field along z + + bool use_isothermal_init; //!< use isothermal equilibrium or not (iso-density) + real_t T_up; //!< fluid temperature above interface (only valid with isothermal init) + real_t T_down; //!< fluid temperature below interface (only valid with isothermal init) + + + RayleighTaylorParams(ConfigMap const & config_map) + : amplitude(config_map.getReal("rayleigh_taylor", "amplitude", KALYPSSO_NUM(0.01))) + , nx(config_map.getInteger("rayleigh_taylor", "nx", 1)) + , ny(config_map.getInteger("rayleigh_taylor", "ny", 1)) + , nz(config_map.getInteger("rayleigh_taylor", "nz", 1)) + , rho_up(config_map.getReal("rayleigh_taylor", "rho_up", KALYPSSO_NUM(2.0))) + , rho_down(config_map.getReal("rayleigh_taylor", "rho_down", KALYPSSO_NUM(1.0))) + , P0(config_map.getReal("rayleigh_taylor", "P0", KALYPSSO_NUM(2.5))) + , perturb_type(get_perturbation_type(config_map)) + , seed(static_cast(config_map.getInteger("rayleigh_taylor", "rand_seed", 12))) + , bx(config_map.getReal("rayleigh_taylor", "bx", KALYPSSO_NUM(0.0))) + , by(config_map.getReal("rayleigh_taylor", "by", KALYPSSO_NUM(0.0))) + , bz(config_map.getReal("rayleigh_taylor", "bz", KALYPSSO_NUM(0.0))) + , use_isothermal_init(config_map.getBool("rayleigh_taylor", "use_isothermal_init", false)) + , T_up(config_map.getReal("rayleigh_taylor", "T_up", KALYPSSO_NUM(1.0))) + , T_down(config_map.getReal("rayleigh_taylor", "T_down", KALYPSSO_NUM(2.0))) + { + + // choose a different random seed per mpi rank + seed = static_cast(config_map.getInteger("rayleigh_taylor", "rand_seed", 12)); + +#ifdef KALYPSSO_CORE_USE_MPI + // srand( seed * (mpiRank+1) ); + + // get MPI rank in MPI_COMM_WORLD + // TODO : pass communicator to the constructor (?) + int mpiRank = 1; + MPI_Comm_rank(MPI_COMM_WORLD, &mpiRank); + seed *= static_cast(mpiRank + 1); +#endif // KALYPSSO_CORE_USE_MPI + } + +}; // struct RayleighTaylorParams + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_PROBLEMS_RAYLEIGH_TAYLOR_PARAMS_H_ diff --git a/src/kalypsso/core/problems/RichtmyerMeshkovParams.h b/src/kalypsso/core/problems/RichtmyerMeshkovParams.h new file mode 100644 index 0000000..204a535 --- /dev/null +++ b/src/kalypsso/core/problems/RichtmyerMeshkovParams.h @@ -0,0 +1,115 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file RichtmyerMeshkovParams.h + */ +#ifndef KALYPSSO_CORE_PROBLEMS_RICHTMYER_MESHKOV_PARAMS_H_ +#define KALYPSSO_CORE_PROBLEMS_RICHTMYER_MESHKOV_PARAMS_H_ + +#include +#include + +namespace kalypsso +{ + +/** + * Richtmyer-Meshkov instability instability test parameters. + * + * reference : https://doi.org/10.1016/j.compfluid.2021.105158 + * + * 3 regions: + * + * location of material interface + * with sine wave shape + * || + * \/ + * + * +--------------------+----------------+--------------+ + * | \ | | + * | \ | | + * | | | | + * | region 2 __/ region 1 | region 0 | + * | (material 1) / (material 0) | (material 0) | + * | | | | + * | \ | | + * | \ | | + * +-----------------+-------------------+--------------+ + */ +struct RichtmyerMeshkovParams +{ + + // shock-bubble problem parameters + + //! initial shock location + real_t x_shock; + + //! average material interface location + real_t x_interface; + + //! center box (along the y axis) + real_t y_center; + + //! material interface parameter + //! epsilon is the amplitude of the spatial sine perturbation + real_t epsilon; + + RichtmyerMeshkovParams(ConfigMap const & config_map) + { + x_shock = config_map.getReal("richtmyer_meshkov", "x_shock", KALYPSSO_NUM(3.2)); + x_interface = config_map.getReal("richtmyer_meshkov", "x_interface", KALYPSSO_NUM(2.9)); + + const auto ymin = config_map.getReal("mesh", "ymin", KALYPSSO_NUM(0.0)); + const auto scaling_factor = config_map.getReal("mesh", "scaling_factor", KALYPSSO_NUM(1.0)); + const auto nbrick_y = config_map.getInteger("p4est_connectivity", "nbrick_y", 1); + const auto ymax = ymin + static_cast(nbrick_y) * scaling_factor; + y_center = config_map.getReal("richtmyer_meshkov", "y_center", (ymin + ymax) / 2); + + epsilon = config_map.getReal("richtmyer_meshkov", "epsilon", KALYPSSO_NUM(0.2)); + } + + KOKKOS_INLINE_FUNCTION + real_t + x_material(real_t y) const + { + return x_interface - epsilon * sin(2 * PI_F * (y + y_center)); + } + + /** + * Compute tangent line 2d (or plane in 3d). + * + * In 2d the equation of the tangent line is: \f$ n_x x + n_y y = \alpha\f$ + * In 3d the equation of the tangent plane is: \f$ n_x x + n_y y + n_z z = \alpha\f$ + * + * In pout we return the normal vector to tangent (line or plane) oriented from region 1 to + * region 2. + * + * \param[in] xyz a given point (usually a cell center) + * \param[out] normal tangent normal unit vector (nx, ny, nz) + * \param[out] alpha + */ + template + KOKKOS_INLINE_FUNCTION void + get_interface_tangent(Kokkos::Array const & xyz, + Kokkos::Array & normal, + real_t & alpha) const + { + // compute interface derivative along y + real_t dxdy = -epsilon * 2 * PI_F * cos(2 * PI_F * (xyz[IY] + y_center)); + + normal[IX] = -dxdy; + normal[IY] = KALYPSSO_NUM(1.0); + if constexpr (dim == 3) + normal[IZ] = KALYPSSO_NUM(0.0); + + auto x_interf = x_material(xyz[IY]); + + alpha = normal[IX] * x_interf + normal[IY] * xyz[IY]; + } + +}; // struct RichtmyerMeshkovParams + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_PROBLEMS_RICHTMYER_MESHKOV_PARAMS_H_ diff --git a/src/kalypsso/core/problems/RotorParams.h b/src/kalypsso/core/problems/RotorParams.h new file mode 100644 index 0000000..3ab0157 --- /dev/null +++ b/src/kalypsso/core/problems/RotorParams.h @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file RotorParams.h + */ +#ifndef KALYPSSO_CORE_PROBLEMS_ROTOR_PARAMS_H_ +#define KALYPSSO_CORE_PROBLEMS_ROTOR_PARAMS_H_ + +#include +#include + +namespace kalypsso +{ + +/** + * + * Rotor problem. + * + * Reference: + * + * - "The div(B)=0 constraint in shock-capturing MHD codes", G. Toth, JCP, 161, 605 (2000). + * https://doi.org/10.1006/jcph.2000.6519 + * + */ +struct RotorParams +{ + + // rotor problem parameters + + //! inner radius + real_t r0; + + //! outer radius + real_t r1; + + //! velocity at radius r0 + real_t u0; + + //! density inside inner disk + real_t rho0; + + //! density outside outer disk + real_t rho1; + + //! pressure + real_t p0; + + //! magnetic field components + real_t bx, by, bz; + + //! center coordinates + real_t xc, yc, zc; + + RotorParams(ConfigMap const & config_map) + { + + r0 = config_map.getReal("rotor", "r0", KALYPSSO_NUM(0.1)); + r1 = config_map.getReal("rotor", "r1", KALYPSSO_NUM(0.115)); + u0 = config_map.getReal("rotor", "u0", KALYPSSO_NUM(2.0)); + rho0 = config_map.getReal("rotor", "rho0", KALYPSSO_NUM(10.0)); + rho1 = config_map.getReal("rotor", "rho1", KALYPSSO_NUM(1.0)); + p0 = config_map.getReal("rotor", "p0", KALYPSSO_NUM(1.0)); + bx = config_map.getReal("rotor", "bx", KALYPSSO_NUM(5.0) / sqrt(4 * PI_F)); + by = config_map.getReal("rotor", "by", KALYPSSO_NUM(0.0)); + bz = config_map.getReal("rotor", "bz", KALYPSSO_NUM(0.0)); + + const auto xmin = config_map.getReal("mesh", "xmin", KALYPSSO_NUM(0.0)); + const auto ymin = config_map.getReal("mesh", "ymin", KALYPSSO_NUM(0.0)); + const auto zmin = config_map.getReal("mesh", "zmin", KALYPSSO_NUM(0.0)); + + const auto nbrick_x = config_map.getInteger("p4est_connectivity", "nbrick_x", 1); + const auto nbrick_y = config_map.getInteger("p4est_connectivity", "nbrick_y", 1); + const auto nbrick_z = config_map.getInteger("p4est_connectivity", "nbrick_z", 1); + + const auto scaling_factor = config_map.getReal("mesh", "scaling_factor", KALYPSSO_NUM(1.0)); + + const auto xmax = xmin + static_cast(nbrick_x) * scaling_factor; + const auto ymax = ymin + static_cast(nbrick_y) * scaling_factor; + const auto zmax = zmin + static_cast(nbrick_z) * scaling_factor; + + // default value for rotor center coordinates are the box center coordinates + xc = config_map.getReal("rotor", "xc", (xmin + xmax) / 2); + yc = config_map.getReal("rotor", "yc", (ymin + ymax) / 2); + zc = config_map.getReal("rotor", "zc", (zmin + zmax) / 2); + } + +}; // struct RotorParams + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_PROBLEMS_ROTOR_PARAMS_H_ diff --git a/src/kalypsso/core/problems/ShockBubbleParams.h b/src/kalypsso/core/problems/ShockBubbleParams.h new file mode 100644 index 0000000..335564a --- /dev/null +++ b/src/kalypsso/core/problems/ShockBubbleParams.h @@ -0,0 +1,152 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ShockBubbleParams.h + */ +#ifndef KALYPSSO_CORE_PROBLEMS_SHOCK_BUBBLE_PARAMS_H_ +#define KALYPSSO_CORE_PROBLEMS_SHOCK_BUBBLE_PARAMS_H_ + +#include +#include +#include + +namespace kalypsso +{ + +/** + * Shock-bubble interaction test parameters. + * + * Here we can handle multiple bubbles. + * + * references : + * - single bubble: http://amroc.sourceforge.net/examples/euler/2d/html/shbubble_n.htm + * - multiple bubbles: Triangular metric-based mesh adaptation for compressible multi-material flows + * in semi-Lagrangian coordinates, Stéphane Del Pino and Isabelle Marmajou, Journal of Computational + * Physics Volume 478, 1 April 2023, 111975. https://doi.org/10.1016/j.jcp.2023.111975 + */ +template +struct ShockBubbleParams +{ + + // shock-bubble problem parameters + + //! initial front location + real_t x_front; + + //! post-shock fluid state + real_t post_rho; + real_t post_pressure; + real_t post_u; + real_t post_v; + real_t post_w; + + //! pre-shock fluid state + real_t pre_rho; + real_t pre_pressure; + real_t pre_u; + real_t pre_v; + real_t pre_w; + + //! number of bubbles + int num_bubbles; + + //! bubble fluid states + Kokkos::View bubble_rho; + Kokkos::View bubble_pressure; + Kokkos::View bubble_u; + Kokkos::View bubble_v; + Kokkos::View bubble_w; + + //! bubble center locations + Kokkos::View bubble_x; + Kokkos::View bubble_y; + Kokkos::View bubble_z; + + //! bubble radius + Kokkos::View bubble_radius; + + //! inlet border conditions enable + bool use_inlet_bc; + + ShockBubbleParams(ConfigMap const & config_map) + { + x_front = config_map.getReal("shock_bubble", "x_front", KALYPSSO_NUM(1.0)); + + post_rho = config_map.getReal("shock_bubble", "post_rho", KALYPSSO_NUM(3.81)); + post_pressure = config_map.getReal("shock_bubble", "post_p", KALYPSSO_NUM(10.0)); + post_u = config_map.getReal("shock_bubble", "post_u", KALYPSSO_NUM(2.85)); + post_v = config_map.getReal("shock_bubble", "post_v", KALYPSSO_NUM(0.0)); + post_w = config_map.getReal("shock_bubble", "post_w", KALYPSSO_NUM(0.0)); + + pre_rho = config_map.getReal("shock_bubble", "pre_rho", KALYPSSO_NUM(1.0)); + pre_pressure = config_map.getReal("shock_bubble", "pre_p", KALYPSSO_NUM(1.0)); + pre_u = config_map.getReal("shock_bubble", "pre_u", KALYPSSO_NUM(0.0)); + pre_v = config_map.getReal("shock_bubble", "pre_v", KALYPSSO_NUM(0.0)); + pre_w = config_map.getReal("shock_bubble", "pre_w", KALYPSSO_NUM(0.0)); + + num_bubbles = config_map.getInteger("shock_bubble", "num_bubbles", 1); + + auto bubble_rho_h = config_map.getRealVector( + "shock_bubble", "bubble_rho", std::vector{ KALYPSSO_NUM(0.1) }); + assertm(bubble_rho_h.size() == static_cast(num_bubbles), + "[ShockBubbleParams] wrong size."); + bubble_rho = to_view(bubble_rho_h); + + auto bubble_pressure_h = config_map.getRealVector( + "shock_bubble", "bubble_p", std::vector{ KALYPSSO_NUM(1.0) }); + assertm(bubble_pressure_h.size() == static_cast(num_bubbles), + "[ShockBubbleParams] wrong size."); + bubble_pressure = to_view(bubble_pressure_h); + + auto bubble_u_h = config_map.getRealVector( + "shock_bubble", "bubble_u", std::vector{ KALYPSSO_NUM(0.0) }); + assertm(bubble_u_h.size() == static_cast(num_bubbles), + "[ShockBubbleParams] wrong size."); + bubble_u = to_view(bubble_u_h); + + auto bubble_v_h = config_map.getRealVector( + "shock_bubble", "bubble_v", std::vector{ KALYPSSO_NUM(0.0) }); + assertm(bubble_v_h.size() == static_cast(num_bubbles), + "[ShockBubbleParams] wrong size."); + bubble_v = to_view(bubble_v_h); + + auto bubble_w_h = config_map.getRealVector( + "shock_bubble", "bubble_w", std::vector{ KALYPSSO_NUM(0.0) }); + assertm(bubble_w_h.size() == static_cast(num_bubbles), + "[ShockBubbleParams] wrong size."); + bubble_w = to_view(bubble_w_h); + + auto bubble_x_h = config_map.getRealVector( + "shock_bubble", "bubble_x", std::vector{ KALYPSSO_NUM(0.4) }); + assertm(bubble_x_h.size() == static_cast(num_bubbles), + "[ShockBubbleParams] wrong size."); + bubble_x = to_view(bubble_x_h); + + auto bubble_y_h = config_map.getRealVector( + "shock_bubble", "bubble_y", std::vector{ KALYPSSO_NUM(0.0) }); + assertm(bubble_y_h.size() == static_cast(num_bubbles), + "[ShockBubbleParams] wrong size."); + bubble_y = to_view(bubble_y_h); + + auto bubble_z_h = config_map.getRealVector( + "shock_bubble", "bubble_z", std::vector{ KALYPSSO_NUM(0.0) }); + assertm(bubble_z_h.size() == static_cast(num_bubbles), + "[ShockBubbleParams] wrong size."); + bubble_z = to_view(bubble_z_h); + + auto bubble_radius_h = config_map.getRealVector( + "shock_bubble", "bubble_radius", std::vector{ KALYPSSO_NUM(0.1) }); + assertm(bubble_radius_h.size() == static_cast(num_bubbles), + "[ShockBubbleParams] wrong size."); + bubble_radius = to_view(bubble_radius_h); + + use_inlet_bc = config_map.getBool("shock_bubble", "use_inlet_bc", false); + } + +}; // struct ShockBubbleParams + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_PROBLEMS_SHOCK_BUBBLE_PARAMS_H_ diff --git a/src/kalypsso/core/problems/ShuOsherParams.h b/src/kalypsso/core/problems/ShuOsherParams.h new file mode 100644 index 0000000..54c2611 --- /dev/null +++ b/src/kalypsso/core/problems/ShuOsherParams.h @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ShuOsherParams.h + */ +#ifndef KALYPSSO_CORE_PROBLEMS_SHUOSHER_PARAMS_H_ +#define KALYPSSO_CORE_PROBLEMS_SHUOSHER_PARAMS_H_ + +#include +#include + +namespace kalypsso +{ + +/** + * Shu-Osher test parameters. + * + * A moving shock interacting with a density sine wave. + * + * Reference: + * Efficient implementation of essentially non-oscillatory shock-capturing schemes, II, + * C.-W. Shu and S. Osher, JCP vol. 83, pp 32-78 (1989). + * https://doi.org/10.1016/0021-9991(89)90222-2 + */ +struct ShuOsherParams +{ + + // Shu-Osher problem parameters: left and right state + real_t rhoL; + real_t uL; + real_t pL; + + real_t rhoR; + real_t uR; + real_t pR; + + real_t A; // density wave amplitude + real_t k; // density wave number + real_t x0; // initial position of interface + + ShuOsherParams(ConfigMap const & config_map) + : rhoL(config_map.getReal("shu_osher", "rhoL", KALYPSSO_NUM(27.0) / 7)) + , uL(config_map.getReal("shu_osher", "uL", 4 * sqrt(KALYPSSO_NUM(35.0)) / KALYPSSO_NUM(9.0))) + , pL(config_map.getReal("shu_osher", "pL", KALYPSSO_NUM(31.0) / 3)) + , rhoR(config_map.getReal("shu_osher", "rhoR", KALYPSSO_NUM(1.0))) + , uR(config_map.getReal("shu_osher", "uR", KALYPSSO_NUM(0.0))) + , pR(config_map.getReal("shu_osher", "pR", KALYPSSO_NUM(1.0))) + , A(config_map.getReal("shu_osher", "A", KALYPSSO_NUM(0.2))) + , k(config_map.getReal("shu_osher", "k", KALYPSSO_NUM(5.0))) + , x0(config_map.getReal("shu_osher", "x0", KALYPSSO_NUM(-4.0))) + {} + +}; // struct ShuOsherParams + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_PROBLEMS_SHUOSHER_PARAMS_H_ diff --git a/src/kalypsso/core/problems/SodParams.h b/src/kalypsso/core/problems/SodParams.h new file mode 100644 index 0000000..73452fb --- /dev/null +++ b/src/kalypsso/core/problems/SodParams.h @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file SodParams.h + */ +#ifndef KALYPSSO_CORE_PROBLEMS_SOD_PARAMS_H_ +#define KALYPSSO_CORE_PROBLEMS_SOD_PARAMS_H_ + +#include +#include + +namespace kalypsso +{ + +/** + * Sod shock tube test parameters. + * + * see https://en.wikipedia.org/wiki/Sod_shock_tube + */ +struct SodParams +{ + + // sod problem parameters: left and right state + real_t rhoL; + real_t uL; + real_t pL; + + real_t rhoR; + real_t uR; + real_t pR; + + real_t xd; // discontinuity location + + SodParams(ConfigMap const & config_map) + : rhoL(config_map.getReal("sod", "rhoL", KALYPSSO_NUM(1.0))) + , uL(config_map.getReal("sod", "uL", KALYPSSO_NUM(0.0))) + , pL(config_map.getReal("sod", "pL", KALYPSSO_NUM(1.0))) + , rhoR(config_map.getReal("sod", "rhoR", KALYPSSO_NUM(0.125))) + , uR(config_map.getReal("sod", "uR", KALYPSSO_NUM(0.0))) + , pR(config_map.getReal("sod", "pR", KALYPSSO_NUM(0.1))) + { + + const auto xmin = config_map.getReal("mesh", "xmin", KALYPSSO_NUM(0.0)); + + const auto nbrick_x = config_map.getInteger("p4est_connectivity", "nbrick_x", 1); + + const auto scaling_factor = config_map.getReal("mesh", "scaling_factor", KALYPSSO_NUM(1.0)); + + const auto xmax = xmin + static_cast(nbrick_x) * scaling_factor; + + xd = config_map.getReal("sod", "xd", (xmin + xmax) / TWO_F); + } + +}; // struct SodParams + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_PROBLEMS_SOD_PARAMS_H_ diff --git a/src/kalypsso/core/problems/StaticDropletParams.h b/src/kalypsso/core/problems/StaticDropletParams.h new file mode 100644 index 0000000..5fbd557 --- /dev/null +++ b/src/kalypsso/core/problems/StaticDropletParams.h @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file StaticDropletParams.h + */ +#ifndef KALYPSSO_CORE_PROBLEMS_STATIC_DROPLET_PARAMS_H_ +#define KALYPSSO_CORE_PROBLEMS_STATIC_DROPLET_PARAMS_H_ + +#include +#include + +namespace kalypsso +{ + +/** + * Static droplet test parameters. + * + * A liquid droplet inside a gas at equilibrium (pressure difference between liquid and gas is + * balanced by surface force). + * + * reference : + * A finite-volume HLLC-based scheme for compressible interfacial flows with surface tension, + * Garrick Owkes and Regele, Journal of Computational Physics Volume 339, 15 June 2017, Pages 46-67. + * https://doi.org/10.1016/j.jcp.2017.03.007 + */ +struct StaticDropletParams +{ + + // static droplet problem parameters + + //! parameter used to control interface thickness + real_t epsilon_coef; + + //! another parameter used to control interface thickness + real_t Delta; + + //! droplet radius + real_t radius; + + //! droplet center, x coordinate. + real_t x; + + //! droplet center, y coordinate. + real_t y; + + //! droplet center, z coordinate. + real_t z; + + StaticDropletParams(ConfigMap const & config_map) + { + const auto xmin = config_map.getReal("mesh", "xmin", KALYPSSO_NUM(0.0)); + const auto ymin = config_map.getReal("mesh", "ymin", KALYPSSO_NUM(0.0)); + const auto zmin = config_map.getReal("mesh", "zmin", KALYPSSO_NUM(0.0)); + + const auto nbrick_x = config_map.getInteger("p4est_connectivity", "nbrick_x", 1); + const auto nbrick_y = config_map.getInteger("p4est_connectivity", "nbrick_y", 1); + const auto nbrick_z = config_map.getInteger("p4est_connectivity", "nbrick_z", 1); + + const auto scaling_factor = config_map.getReal("mesh", "scaling_factor", KALYPSSO_NUM(1.0)); + + const auto xmax = xmin + static_cast(nbrick_x) * scaling_factor; + const auto ymax = ymin + static_cast(nbrick_y) * scaling_factor; + const auto zmax = zmin + static_cast(nbrick_z) * scaling_factor; + + epsilon_coef = config_map.getReal("static_droplet", "epsilon_coef", KALYPSSO_NUM(0.72)); + Delta = config_map.getReal("static_droplet", "Delta", KALYPSSO_NUM(2.0)); + + radius = config_map.getReal("static_droplet", "radius", KALYPSSO_NUM(1.0)); + + x = config_map.getReal("static_droplet", "x", (xmin + xmax) / 2); + y = config_map.getReal("static_droplet", "y", (ymin + ymax) / 2); + z = config_map.getReal("static_droplet", "z", (zmin + zmax) / 2); + } + +}; // struct StaticDropletParams + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_PROBLEMS_STATIC_DROPLET_PARAMS_H_ diff --git a/src/kalypsso/core/problems/TriplePointParams.h b/src/kalypsso/core/problems/TriplePointParams.h new file mode 100644 index 0000000..6abb7a8 --- /dev/null +++ b/src/kalypsso/core/problems/TriplePointParams.h @@ -0,0 +1,92 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file TriplePointParams.h + * + * A two-fluid flow parameter file, to be used with our Five-Equation solver. + * + * Each fluid has its own equation of state (e.g. stiffened gas, ...) + */ +#ifndef KALYPSSO_CORE_PROBLEMS_TRIPLEPOINT_PARAMS_H_ +#define KALYPSSO_CORE_PROBLEMS_TRIPLEPOINT_PARAMS_H_ + +#include +#include + +namespace kalypsso +{ + +/** + * TwoFluidShockTube test parameters. + * + */ +struct TriplePointParams +{ + + // left state: material 0 is pure + real_t rho0; + real_t p0; + real_t u0; + + // bottom-right state: material 1 is pure + real_t rho1; + real_t p1; + real_t u1; + + // upper-right state: material 0 is pure + real_t rho2; + real_t p2; + real_t u2; + + //! vertical discontinuity location + real_t xd; + + //! horizontal discontinuity location + real_t yd; + + TriplePointParams(ConfigMap const & config_map) + { + + const auto xmin = config_map.getReal("mesh", "xmin", KALYPSSO_NUM(0.0)); + const auto ymin = config_map.getReal("mesh", "ymin", KALYPSSO_NUM(0.0)); + // const auto zmin = config_map.getReal("mesh", "zmin", KALYPSSO_NUM(0.0)); + + const auto nbrick_x = config_map.getInteger("p4est_connectivity", "nbrick_x", 1); + const auto nbrick_y = config_map.getInteger("p4est_connectivity", "nbrick_y", 1); + // const auto nbrick_z = config_map.getInteger("p4est_connectivity", "nbrick_z", 1); + + const auto scaling_factor = config_map.getReal("mesh", "scaling_factor", KALYPSSO_NUM(1.0)); + + const auto xmax = xmin + static_cast(nbrick_x) * scaling_factor; + const auto ymax = ymin + static_cast(nbrick_y) * scaling_factor; + // const auto zmax = zmin + static_cast(nbrick_z) * scaling_factor; + + // left state: material 0 only + rho0 = config_map.getReal("triple_point", "rho0", KALYPSSO_NUM(1.0)); + p0 = config_map.getReal("triple_point", "p0", KALYPSSO_NUM(100000.0)); + u0 = config_map.getReal("triple_point", "u0", KALYPSSO_NUM(0.0)); + + // bottom-right state: material 1 only + rho1 = config_map.getReal("triple_point", "rho1", KALYPSSO_NUM(0.125)); + p1 = config_map.getReal("triple_point", "p1", KALYPSSO_NUM(10000.0)); + u1 = config_map.getReal("triple_point", "u1", KALYPSSO_NUM(0.0)); + + // upper-right state: material 0 only + rho2 = config_map.getReal("triple_point", "rho2", KALYPSSO_NUM(0.125)); + p2 = config_map.getReal("triple_point", "p2", KALYPSSO_NUM(10000.0)); + u2 = config_map.getReal("triple_point", "u2", KALYPSSO_NUM(0.0)); + + // discontinuity along vertical axis + xd = config_map.getReal("triple_point", "xd", xmin + (xmax - xmin) / KALYPSSO_NUM(7.0)); + + // discontinuity along horizontal axis + yd = config_map.getReal("triple_point", "yd", (ymin + ymax) / KALYPSSO_NUM(2.0)); + } + +}; // struct TriplePointParams + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_PROBLEMS_TRIPLEPOINT_PARAMS_H_ diff --git a/src/kalypsso/core/problems/TwoFluidShockTubeParams.h b/src/kalypsso/core/problems/TwoFluidShockTubeParams.h new file mode 100644 index 0000000..87be998 --- /dev/null +++ b/src/kalypsso/core/problems/TwoFluidShockTubeParams.h @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file TwoFluidShockTubeParams.h + * + * All parameters for a two-fluid shock tube problem. + * + * Each fluid has its own equation of state (e.g. stiffened gas, ...) + */ +#ifndef KALYPSSO_CORE_PROBLEMS_TWOFLUIDSHOCKTUBE_PARAMS_H_ +#define KALYPSSO_CORE_PROBLEMS_TWOFLUIDSHOCKTUBE_PARAMS_H_ + +#include +#include + +namespace kalypsso +{ + +/** + * TwoFluidShockTube test parameters. + * + */ +struct TwoFluidShockTubeParams +{ + + //! discontinuity location + real_t xd; + + TwoFluidShockTubeParams(ConfigMap const & config_map) + { + + const auto xmin = config_map.getReal("mesh", "xmin", KALYPSSO_NUM(0.0)); + // const auto ymin = config_map.getReal("mesh", "ymin", KALYPSSO_NUM(0.0)); + // const auto zmin = config_map.getReal("mesh", "zmin", KALYPSSO_NUM(0.0)); + + const auto nbrick_x = config_map.getInteger("p4est_connectivity", "nbrick_x", 1); + // const auto nbrick_y = config_map.getInteger("p4est_connectivity", "nbrick_y", 1); + // const auto nbrick_z = config_map.getInteger("p4est_connectivity", "nbrick_z", 1); + + const auto scaling_factor = config_map.getReal("mesh", "scaling_factor", KALYPSSO_NUM(1.0)); + + const auto xmax = xmin + static_cast(nbrick_x) * scaling_factor; + // const auto ymax = ymin + static_cast(nbrick_y) * scaling_factor; + // const auto zmax = zmin + static_cast(nbrick_z) * scaling_factor; + + // discontinuity along x axis + xd = config_map.getReal("two_fluid_shock_tube", "xd", (xmin + xmax) / KALYPSSO_NUM(2.0)); + } + +}; // struct TwoFluidShockTubeParams + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_PROBLEMS_TWOFLUIDSHOCKTUBE_PARAMS_H_ diff --git a/src/kalypsso/core/problems/UnderwaterExplosionParams.h b/src/kalypsso/core/problems/UnderwaterExplosionParams.h new file mode 100644 index 0000000..b00a421 --- /dev/null +++ b/src/kalypsso/core/problems/UnderwaterExplosionParams.h @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file UnderwaterExplosionParams.h + */ +#ifndef KALYPSSO_CORE_PROBLEMS_UNDERWATER_EXPLOSION_PARAMS_H_ +#define KALYPSSO_CORE_PROBLEMS_UNDERWATER_EXPLOSION_PARAMS_H_ + +#include +#include + +namespace kalypsso +{ + +/** + * Underwater explosion test parameters. + * + * reference : + * + * An interface capturing scheme for modeling atomization in compressible flows, Garrick et al., + * Journal of Computational Physics Volume 344, 1 September 2017, Pages 260-280. + * https://doi.org/10.1016/j.jcp.2017.04.079 + */ +struct UnderwaterExplosionParams +{ + + // shock-bubble problem parameters + + //! interface location + real_t interface_loc; + + //! bubble center location + real_t bubble_x; + real_t bubble_y; + real_t bubble_z; + + //! bubble radius + real_t bubble_radius; + + UnderwaterExplosionParams(ConfigMap const & config_map) + { + interface_loc = + config_map.getReal("underwater_explosion", "interface_location ", KALYPSSO_NUM(0.0)); + + bubble_x = config_map.getReal("underwater_explosion", "bubble_x", KALYPSSO_NUM(0.0)); + bubble_y = config_map.getReal("underwater_explosion", "bubble_y", KALYPSSO_NUM(-0.3)); + bubble_z = config_map.getReal("underwater_explosion", "bubble_z", KALYPSSO_NUM(0.0)); + + bubble_radius = config_map.getReal("underwater_explosion", "bubble_radius", KALYPSSO_NUM(0.12)); + } + +}; // struct UnderwaterExplosionParams + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_PROBLEMS_UNDERWATER_EXPLOSION_PARAMS_H_ diff --git a/src/kalypsso/core/problems/WoodwardParams.h b/src/kalypsso/core/problems/WoodwardParams.h new file mode 100644 index 0000000..30a0238 --- /dev/null +++ b/src/kalypsso/core/problems/WoodwardParams.h @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file WoodwardParams.h + */ +#ifndef KALYPSSO_CORE_PROBLEMS_WOODWARD_PARAMS_H_ +#define KALYPSSO_CORE_PROBLEMS_WOODWARD_PARAMS_H_ + +#include +#include + +namespace kalypsso +{ + +/** + * Woodward test (two interacting blast waves) parameters. + * + * see reference + * Woodward, P. and Colella, P., "The Numerical Simulation of Two-Dimensional + * Fluid Flow with Strong Shocks", J. Computational Physics, 54, 115-173 (1984). + * https://doi.org/10.1016/0021-9991(84)90142-6 + * + */ +struct WoodwardParams +{ + + // woodward problem parameters: left, center and right states + real_t rhoL; + real_t uL; + real_t pL; + + real_t rhoC; + real_t uC; + real_t pC; + + real_t rhoR; + real_t uR; + real_t pR; + + real_t xdL; // left discontinuity location + real_t xdR; // right discontinuity location + + WoodwardParams(ConfigMap const & config_map) + : rhoL(config_map.getReal("woodward", "rhoL", KALYPSSO_NUM(1.0))) + , uL(config_map.getReal("woodward", "uL", KALYPSSO_NUM(0.0))) + , pL(config_map.getReal("woodward", "pL", KALYPSSO_NUM(1000.0))) + , rhoC(config_map.getReal("woodward", "rhoC", KALYPSSO_NUM(1.0))) + , uC(config_map.getReal("woodward", "uC", KALYPSSO_NUM(0.0))) + , pC(config_map.getReal("woodward", "pC", KALYPSSO_NUM(0.01))) + , rhoR(config_map.getReal("woodward", "rhoR", KALYPSSO_NUM(1.0))) + , uR(config_map.getReal("woodward", "uR", KALYPSSO_NUM(0.0))) + , pR(config_map.getReal("woodward", "pR", KALYPSSO_NUM(100.0))) + { + + const auto xmin = config_map.getReal("mesh", "xmin", KALYPSSO_NUM(0.0)); + + const auto nbrick_x = config_map.getInteger("p4est_connectivity", "nbrick_x", 1); + + const auto scaling_factor = config_map.getReal("mesh", "scaling_factor", KALYPSSO_NUM(1.0)); + + const auto xmax = xmin + static_cast(nbrick_x) * scaling_factor; + + + xdL = config_map.getReal("woodward", "xd_L", xmin + KALYPSSO_NUM(0.1) * (xmax - xmin)); + xdR = config_map.getReal("woodward", "xd_R", xmin + KALYPSSO_NUM(0.9) * (xmax - xmin)); + } + +}; // struct WoodwardParams + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_PROBLEMS_WOODWARD_PARAMS_H_ diff --git a/src/kalypsso/core/problems/initRiemannConfig2d.cpp b/src/kalypsso/core/problems/initRiemannConfig2d.cpp new file mode 100644 index 0000000..44f2573 --- /dev/null +++ b/src/kalypsso/core/problems/initRiemannConfig2d.cpp @@ -0,0 +1,681 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include + +namespace kalypsso +{ + +// ===================================================================== +// ===================================================================== +KOKKOS_FUNCTION +RiemannConfig<2>::HydroStates_t +getRiemannConfig2d(int numConfig) +{ + constexpr auto ID = core::models::Hydro::ID; + constexpr auto IP = core::models::Hydro::IP; + constexpr auto IU = core::models::Hydro::IU; + constexpr auto IV = core::models::Hydro::IV; + // constexpr auto IW = core::models::Hydro::IW; + + RiemannConfig<2>::HydroStates_t Us; + RiemannConfig<2>::HydroState_t & U0 = Us[0]; + RiemannConfig<2>::HydroState_t & U1 = Us[1]; + RiemannConfig<2>::HydroState_t & U2 = Us[2]; + RiemannConfig<2>::HydroState_t & U3 = Us[3]; + + switch (numConfig) + { + + case 0: + // Config 1 + U0[ID] = KALYPSSO_NUM(1.0); + U0[IU] = KALYPSSO_NUM(0.0); + U0[IV] = KALYPSSO_NUM(0.0); + U0[IP] = KALYPSSO_NUM(1.0); + + U1[ID] = KALYPSSO_NUM(0.5197); + U1[IU] = KALYPSSO_NUM(-0.7259); + U1[IV] = KALYPSSO_NUM(0.0); + U1[IP] = KALYPSSO_NUM(0.4); + + U2[ID] = KALYPSSO_NUM(0.1072); + U2[IU] = KALYPSSO_NUM(-0.7259); + U2[IV] = KALYPSSO_NUM(-1.4045); + U2[IP] = KALYPSSO_NUM(0.0439); + + U3[ID] = KALYPSSO_NUM(0.2579); + U3[IU] = KALYPSSO_NUM(0.0); + U3[IV] = KALYPSSO_NUM(-1.4045); + U3[IP] = KALYPSSO_NUM(0.15); + + break; + case 1: + // Config 2 + U0[ID] = KALYPSSO_NUM(1.0); + U0[IU] = KALYPSSO_NUM(0.0); + U0[IV] = KALYPSSO_NUM(0.0); + U0[IP] = KALYPSSO_NUM(1.0); + + U1[ID] = KALYPSSO_NUM(0.5197); + U1[IU] = KALYPSSO_NUM(-0.7259); + U1[IV] = KALYPSSO_NUM(0.0); + U1[IP] = KALYPSSO_NUM(0.4); + + U2[ID] = KALYPSSO_NUM(1.0); + U2[IU] = KALYPSSO_NUM(-0.7259); + U2[IV] = KALYPSSO_NUM(-0.7259); + U2[IP] = KALYPSSO_NUM(1.0); + + U3[ID] = KALYPSSO_NUM(0.5197); + U3[IU] = KALYPSSO_NUM(0.0); + U3[IV] = KALYPSSO_NUM(-0.7259); + U3[IP] = KALYPSSO_NUM(0.4); + + break; + case 2: + // Config 3 + U0[ID] = KALYPSSO_NUM(1.5); + U0[IU] = KALYPSSO_NUM(0.0); + U0[IV] = KALYPSSO_NUM(0.0); + U0[IP] = KALYPSSO_NUM(1.5); + + U1[ID] = KALYPSSO_NUM(0.5323); + U1[IU] = KALYPSSO_NUM(1.206); + U1[IV] = KALYPSSO_NUM(0.0); + U1[IP] = KALYPSSO_NUM(0.3); + + U2[ID] = KALYPSSO_NUM(0.138); + U2[IU] = KALYPSSO_NUM(1.206); + U2[IV] = KALYPSSO_NUM(1.206); + U2[IP] = KALYPSSO_NUM(0.029); + + U3[ID] = KALYPSSO_NUM(0.5323); + U3[IU] = KALYPSSO_NUM(0.0); + U3[IV] = KALYPSSO_NUM(1.206); + U3[IP] = KALYPSSO_NUM(0.3); + + break; + case 3: + // Config 4 + U0[ID] = KALYPSSO_NUM(1.1); + U0[IU] = KALYPSSO_NUM(0.0); + U0[IV] = KALYPSSO_NUM(0.0); + U0[IP] = KALYPSSO_NUM(1.1); + + U1[ID] = KALYPSSO_NUM(0.5065); + U1[IU] = KALYPSSO_NUM(0.8939); + U1[IV] = KALYPSSO_NUM(0.0); + U1[IP] = KALYPSSO_NUM(0.35); + + U2[ID] = KALYPSSO_NUM(1.1); + U2[IU] = KALYPSSO_NUM(0.8939); + U2[IV] = KALYPSSO_NUM(0.8939); + U2[IP] = KALYPSSO_NUM(1.1); + + U3[ID] = KALYPSSO_NUM(0.5065); + U3[IU] = KALYPSSO_NUM(0.0); + U3[IV] = KALYPSSO_NUM(0.8939); + U3[IP] = KALYPSSO_NUM(0.35); + + break; + case 4: + // Config 5 + U0[ID] = KALYPSSO_NUM(1.0); + U0[IU] = KALYPSSO_NUM(-0.75); + U0[IV] = KALYPSSO_NUM(-0.5); + U0[IP] = KALYPSSO_NUM(1.0); + + U1[ID] = KALYPSSO_NUM(2.0); + U1[IU] = KALYPSSO_NUM(-0.75); + U1[IV] = KALYPSSO_NUM(0.5); + U1[IP] = KALYPSSO_NUM(1.0); + + U2[ID] = KALYPSSO_NUM(1.0); + U2[IU] = KALYPSSO_NUM(0.75); + U2[IV] = KALYPSSO_NUM(0.5); + U2[IP] = KALYPSSO_NUM(1.0); + + U3[ID] = KALYPSSO_NUM(3.0); + U3[IU] = KALYPSSO_NUM(0.75); + U3[IV] = KALYPSSO_NUM(-0.5); + U3[IP] = KALYPSSO_NUM(1.0); + + break; + case 5: + // Config 6 + U0[ID] = KALYPSSO_NUM(1.0); + U0[IU] = KALYPSSO_NUM(0.75); + U0[IV] = KALYPSSO_NUM(-0.5); + U0[IP] = KALYPSSO_NUM(1.0); + + U1[ID] = KALYPSSO_NUM(2.0); + U1[IU] = KALYPSSO_NUM(0.75); + U1[IV] = KALYPSSO_NUM(0.5); + U1[IP] = KALYPSSO_NUM(0.5); + + U2[ID] = KALYPSSO_NUM(1.0); + U2[IU] = KALYPSSO_NUM(-0.75); + U2[IV] = KALYPSSO_NUM(0.5); + U2[IP] = KALYPSSO_NUM(1.0); + + U3[ID] = KALYPSSO_NUM(3.0); + U3[IU] = KALYPSSO_NUM(-0.75); + U3[IV] = KALYPSSO_NUM(-0.5); + U3[IP] = KALYPSSO_NUM(1.0); + + break; + case 6: + // Config 7 + U0[ID] = KALYPSSO_NUM(1.0); + U0[IU] = KALYPSSO_NUM(0.1); + U0[IV] = KALYPSSO_NUM(0.1); + U0[IP] = KALYPSSO_NUM(1.0); + + U1[ID] = KALYPSSO_NUM(0.5197); + U1[IU] = KALYPSSO_NUM(-0.6259); + U1[IV] = KALYPSSO_NUM(0.1); + U1[IP] = KALYPSSO_NUM(0.4); + + U2[ID] = KALYPSSO_NUM(0.8); + U2[IU] = KALYPSSO_NUM(0.1); + U2[IV] = KALYPSSO_NUM(0.1); + U2[IP] = KALYPSSO_NUM(0.4); + + U3[ID] = KALYPSSO_NUM(0.5197); + U3[IU] = KALYPSSO_NUM(0.1); + U3[IV] = KALYPSSO_NUM(-0.6259); + U3[IP] = KALYPSSO_NUM(0.4); + + break; + case 7: + // Config 8 + U0[ID] = KALYPSSO_NUM(0.5197); + U0[IU] = KALYPSSO_NUM(0.1); + U0[IV] = KALYPSSO_NUM(0.1); + U0[IP] = KALYPSSO_NUM(0.4); + + U1[ID] = KALYPSSO_NUM(1.0); + U1[IU] = KALYPSSO_NUM(-0.6259); + U1[IV] = KALYPSSO_NUM(0.1); + U1[IP] = KALYPSSO_NUM(1.0); + + U2[ID] = KALYPSSO_NUM(0.8); + U2[IU] = KALYPSSO_NUM(0.1); + U2[IV] = KALYPSSO_NUM(0.1); + U2[IP] = KALYPSSO_NUM(1.0); + + U3[ID] = KALYPSSO_NUM(1.0); + U3[IU] = KALYPSSO_NUM(0.1); + U3[IV] = KALYPSSO_NUM(-0.6259); + U3[IP] = KALYPSSO_NUM(1.0); + + break; + case 8: + // Config 9 + U0[ID] = KALYPSSO_NUM(1.0); + U0[IU] = KALYPSSO_NUM(0.0); + U0[IV] = KALYPSSO_NUM(0.3); + U0[IP] = KALYPSSO_NUM(1.0); + + U1[ID] = KALYPSSO_NUM(2.0); + U1[IU] = KALYPSSO_NUM(0.0); + U1[IV] = KALYPSSO_NUM(-0.3); + U1[IP] = KALYPSSO_NUM(1.0); + + U2[ID] = KALYPSSO_NUM(1.039); + U2[IU] = KALYPSSO_NUM(0.0); + U2[IV] = KALYPSSO_NUM(-0.8133); + U2[IP] = KALYPSSO_NUM(0.4); + + U3[ID] = KALYPSSO_NUM(0.5197); + U3[IU] = KALYPSSO_NUM(0.0); + U3[IV] = KALYPSSO_NUM(-0.4259); + U3[IP] = KALYPSSO_NUM(0.4); + + break; + case 9: + // Config 10 + U0[ID] = KALYPSSO_NUM(1.0); + U0[IU] = KALYPSSO_NUM(0.0); + U0[IV] = KALYPSSO_NUM(0.4297); + U0[IP] = KALYPSSO_NUM(1.0); + + U1[ID] = KALYPSSO_NUM(0.5); + U1[IU] = KALYPSSO_NUM(0.0); + U1[IV] = KALYPSSO_NUM(0.6076); + U1[IP] = KALYPSSO_NUM(1.0); + + U2[ID] = KALYPSSO_NUM(0.2281); + U2[IU] = KALYPSSO_NUM(0.0); + U2[IV] = KALYPSSO_NUM(-0.6076); + U2[IP] = KALYPSSO_NUM(0.3333); + + U3[ID] = KALYPSSO_NUM(0.4562); + U3[IU] = KALYPSSO_NUM(0.0); + U3[IV] = KALYPSSO_NUM(-0.4259); + U3[IP] = KALYPSSO_NUM(0.3333); + + break; + case 10: + // Config 11 + U0[ID] = KALYPSSO_NUM(1.0); + U0[IU] = KALYPSSO_NUM(0.1); + U0[IV] = KALYPSSO_NUM(0.0); + U0[IP] = KALYPSSO_NUM(1.0); + + U1[ID] = KALYPSSO_NUM(0.5313); + U1[IU] = KALYPSSO_NUM(0.8276); + U1[IV] = KALYPSSO_NUM(0.0); + U1[IP] = KALYPSSO_NUM(0.4); + + U2[ID] = KALYPSSO_NUM(0.8); + U2[IU] = KALYPSSO_NUM(0.1); + U2[IV] = KALYPSSO_NUM(0.0); + U2[IP] = KALYPSSO_NUM(0.4); + + U3[ID] = KALYPSSO_NUM(0.5313); + U3[IU] = KALYPSSO_NUM(0.1); + U3[IV] = KALYPSSO_NUM(0.7276); + U3[IP] = KALYPSSO_NUM(0.4); + + break; + case 11: + // Config 12 + U0[ID] = KALYPSSO_NUM(0.5313); + U0[IU] = KALYPSSO_NUM(0.0); + U0[IV] = KALYPSSO_NUM(0.0); + U0[IP] = KALYPSSO_NUM(0.4); + + U1[ID] = KALYPSSO_NUM(1.0); + U1[IU] = KALYPSSO_NUM(0.7276); + U1[IV] = KALYPSSO_NUM(0.0); + U1[IP] = KALYPSSO_NUM(1.0); + + U2[ID] = KALYPSSO_NUM(0.8); + U2[IU] = KALYPSSO_NUM(0.0); + U2[IV] = KALYPSSO_NUM(0.0); + U2[IP] = KALYPSSO_NUM(1.0); + + U3[ID] = KALYPSSO_NUM(1.0); + U3[IU] = KALYPSSO_NUM(0.0); + U3[IV] = KALYPSSO_NUM(0.7276); + U3[IP] = KALYPSSO_NUM(1.0); + + break; + case 12: + // Config 13 + U0[ID] = KALYPSSO_NUM(1.0); + U0[IU] = KALYPSSO_NUM(0.0); + U0[IV] = KALYPSSO_NUM(-0.3); + U0[IP] = KALYPSSO_NUM(1.0); + + U1[ID] = KALYPSSO_NUM(2.0); + U1[IU] = KALYPSSO_NUM(0.0); + U1[IV] = KALYPSSO_NUM(0.3); + U1[IP] = KALYPSSO_NUM(1.0); + + U2[ID] = KALYPSSO_NUM(1.0625); + U2[IU] = KALYPSSO_NUM(0.0); + U2[IV] = KALYPSSO_NUM(0.8145); + U2[IP] = KALYPSSO_NUM(0.4); + + U3[ID] = KALYPSSO_NUM(0.5313); + U3[IU] = KALYPSSO_NUM(0.0); + U3[IV] = KALYPSSO_NUM(0.4276); + U3[IP] = KALYPSSO_NUM(0.4); + + break; + case 13: + // Config 14 + U0[ID] = KALYPSSO_NUM(2.0); + U0[IU] = KALYPSSO_NUM(0.0); + U0[IV] = KALYPSSO_NUM(-0.5606); + U0[IP] = KALYPSSO_NUM(8.0); + + U1[ID] = KALYPSSO_NUM(1.0); + U1[IU] = KALYPSSO_NUM(0.0); + U1[IV] = KALYPSSO_NUM(-1.2172); + U1[IP] = KALYPSSO_NUM(8.0); + + U2[ID] = KALYPSSO_NUM(0.4736); + U2[IU] = KALYPSSO_NUM(0.0); + U2[IV] = KALYPSSO_NUM(1.2172); + U2[IP] = KALYPSSO_NUM(2.6667); + + U3[ID] = KALYPSSO_NUM(0.9474); + U3[IU] = KALYPSSO_NUM(0.0); + U3[IV] = KALYPSSO_NUM(1.1606); + U3[IP] = KALYPSSO_NUM(2.6667); + + break; + case 14: + // Config 15 + U0[ID] = KALYPSSO_NUM(1.0); + U0[IU] = KALYPSSO_NUM(0.1); + U0[IV] = KALYPSSO_NUM(-0.3); + U0[IP] = KALYPSSO_NUM(1.0); + + U1[ID] = KALYPSSO_NUM(0.5197); + U1[IU] = KALYPSSO_NUM(-0.6259); + U1[IV] = KALYPSSO_NUM(-0.3); + U1[IP] = KALYPSSO_NUM(0.4); + + U2[ID] = KALYPSSO_NUM(0.8); + U2[IU] = KALYPSSO_NUM(0.1); + U2[IV] = KALYPSSO_NUM(-0.3); + U2[IP] = KALYPSSO_NUM(0.4); + + U3[ID] = KALYPSSO_NUM(0.5313); + U3[IU] = KALYPSSO_NUM(0.1); + U3[IV] = KALYPSSO_NUM(0.4276); + U3[IP] = KALYPSSO_NUM(0.4); + + break; + case 15: + // Config 16 + U0[ID] = KALYPSSO_NUM(0.5313); + U0[IU] = KALYPSSO_NUM(0.1); + U0[IV] = KALYPSSO_NUM(0.1); + U0[IP] = KALYPSSO_NUM(0.4); + + U1[ID] = KALYPSSO_NUM(1.0222); + U1[IU] = KALYPSSO_NUM(-0.6179); + U1[IV] = KALYPSSO_NUM(0.1); + U1[IP] = KALYPSSO_NUM(1.0); + + U2[ID] = KALYPSSO_NUM(0.8); + U2[IU] = KALYPSSO_NUM(0.1); + U2[IV] = KALYPSSO_NUM(0.1); + U2[IP] = KALYPSSO_NUM(1.0); + + U3[ID] = KALYPSSO_NUM(1.0); + U3[IU] = KALYPSSO_NUM(0.1); + U3[IV] = KALYPSSO_NUM(0.8276); + U3[IP] = KALYPSSO_NUM(1.0); + + break; + case 16: + // Config 17 + U0[ID] = KALYPSSO_NUM(1.0); + U0[IU] = KALYPSSO_NUM(0.0); + U0[IV] = KALYPSSO_NUM(-0.4); + U0[IP] = KALYPSSO_NUM(1.0); + + U1[ID] = KALYPSSO_NUM(2.0); + U1[IU] = KALYPSSO_NUM(0.0); + U1[IV] = KALYPSSO_NUM(-0.3); + U1[IP] = KALYPSSO_NUM(1.0); + + U2[ID] = KALYPSSO_NUM(1.0625); + U2[IU] = KALYPSSO_NUM(0.0); + U2[IV] = KALYPSSO_NUM(0.2145); + U2[IP] = KALYPSSO_NUM(0.4); + + U3[ID] = KALYPSSO_NUM(0.5197); + U3[IU] = KALYPSSO_NUM(0.0); + U3[IV] = KALYPSSO_NUM(-1.1259); + U3[IP] = KALYPSSO_NUM(0.4); + + break; + case 17: + // Config 18 + U0[ID] = KALYPSSO_NUM(1.0); + U0[IU] = KALYPSSO_NUM(0.0); + U0[IV] = KALYPSSO_NUM(1.0); + U0[IP] = KALYPSSO_NUM(1.0); + + U1[ID] = KALYPSSO_NUM(2.0); + U1[IU] = KALYPSSO_NUM(0.0); + U1[IV] = KALYPSSO_NUM(-0.3); + U1[IP] = KALYPSSO_NUM(1.0); + + U2[ID] = KALYPSSO_NUM(1.0625); + U2[IU] = KALYPSSO_NUM(0.0); + U2[IV] = KALYPSSO_NUM(0.2145); + U2[IP] = KALYPSSO_NUM(0.4); + + U3[ID] = KALYPSSO_NUM(0.5197); + U3[IU] = KALYPSSO_NUM(0.0); + U3[IV] = KALYPSSO_NUM(0.2741); + U3[IP] = KALYPSSO_NUM(0.4); + + break; + case 18: + // Config 19 + U0[ID] = KALYPSSO_NUM(1.0); + U0[IU] = KALYPSSO_NUM(0.0); + U0[IV] = KALYPSSO_NUM(0.3); + U0[IP] = KALYPSSO_NUM(1.0); + + U1[ID] = KALYPSSO_NUM(2.0); + U1[IU] = KALYPSSO_NUM(0.0); + U1[IV] = KALYPSSO_NUM(-0.3); + U1[IP] = KALYPSSO_NUM(1.0); + + U2[ID] = KALYPSSO_NUM(1.0625); + U2[IU] = KALYPSSO_NUM(0.0); + U2[IV] = KALYPSSO_NUM(0.2145); + U2[IP] = KALYPSSO_NUM(0.4); + + U3[ID] = KALYPSSO_NUM(0.5197); + U3[IU] = KALYPSSO_NUM(0.0); + U3[IV] = KALYPSSO_NUM(-0.4259); + U3[IP] = KALYPSSO_NUM(0.4); + + break; + default: + // Config 1 + U0[ID] = KALYPSSO_NUM(1.0); + U0[IU] = KALYPSSO_NUM(0.0); + U0[IV] = KALYPSSO_NUM(0.0); + U0[IP] = KALYPSSO_NUM(1.0); + + U1[ID] = KALYPSSO_NUM(0.5197); + U1[IU] = KALYPSSO_NUM(-0.7259); + U1[IV] = KALYPSSO_NUM(0.0); + U1[IP] = KALYPSSO_NUM(0.4); + + U2[ID] = KALYPSSO_NUM(0.1072); + U2[IU] = KALYPSSO_NUM(-0.7259); + U2[IV] = KALYPSSO_NUM(-1.4045); + U2[IP] = KALYPSSO_NUM(0.0439); + + U3[ID] = KALYPSSO_NUM(0.2579); + U3[IU] = KALYPSSO_NUM(0.0); + U3[IV] = KALYPSSO_NUM(-1.4045); + U3[IP] = KALYPSSO_NUM(0.15); + } + + return Us; + +} // getRiemannConfig2d + +// ===================================================================== +// ===================================================================== +KOKKOS_FUNCTION +RiemannConfig<3>::HydroStates_t +getRiemannConfig3d(int numConfig) +{ + constexpr auto ID = core::models::Hydro::ID; + constexpr auto IP = core::models::Hydro::IP; + constexpr auto IU = core::models::Hydro::IU; + constexpr auto IV = core::models::Hydro::IV; + constexpr auto IW = core::models::Hydro::IW; + + RiemannConfig<3>::HydroStates_t Us; + RiemannConfig<3>::HydroState_t & U0 = Us[0]; + RiemannConfig<3>::HydroState_t & U1 = Us[1]; + RiemannConfig<3>::HydroState_t & U2 = Us[2]; + RiemannConfig<3>::HydroState_t & U3 = Us[3]; + RiemannConfig<3>::HydroState_t & U4 = Us[4]; + RiemannConfig<3>::HydroState_t & U5 = Us[5]; + RiemannConfig<3>::HydroState_t & U6 = Us[6]; + RiemannConfig<3>::HydroState_t & U7 = Us[7]; + + // only 1 config supported for now + // if you feel an interesting config is missing, please help implement + + switch (numConfig) + { + + case 2: + // Config 3 + U0[ID] = KALYPSSO_NUM(0.5323); + U0[IU] = KALYPSSO_NUM(0.0); + U0[IV] = KALYPSSO_NUM(0.0); + U0[IW] = KALYPSSO_NUM(1.206); + U0[IP] = KALYPSSO_NUM(0.3); + + U1[ID] = KALYPSSO_NUM(0.138); + U1[IU] = KALYPSSO_NUM(1.206); + U1[IV] = KALYPSSO_NUM(0.0); + U1[IW] = KALYPSSO_NUM(1.206); + U1[IP] = KALYPSSO_NUM(0.029); + + U2[ID] = KALYPSSO_NUM(0.138); + U2[IU] = KALYPSSO_NUM(1.206); + U2[IV] = KALYPSSO_NUM(1.206); + U2[IW] = KALYPSSO_NUM(1.206); + U2[IP] = KALYPSSO_NUM(0.029); + + U3[ID] = KALYPSSO_NUM(0.138); + U3[IU] = KALYPSSO_NUM(0.0); + U3[IV] = KALYPSSO_NUM(1.206); + U3[IW] = KALYPSSO_NUM(1.206); + U3[IP] = KALYPSSO_NUM(0.029); + + U4[ID] = KALYPSSO_NUM(1.5); + U4[IU] = KALYPSSO_NUM(0.0); + U4[IV] = KALYPSSO_NUM(0.0); + U4[IW] = KALYPSSO_NUM(0.0); + U4[IP] = KALYPSSO_NUM(1.5); + + U5[ID] = KALYPSSO_NUM(0.5323); + U5[IU] = KALYPSSO_NUM(1.206); + U5[IV] = KALYPSSO_NUM(0.0); + U5[IW] = KALYPSSO_NUM(0.0); + U5[IP] = KALYPSSO_NUM(0.3); + + U6[ID] = KALYPSSO_NUM(0.138); + U6[IU] = KALYPSSO_NUM(1.206); + U6[IV] = KALYPSSO_NUM(1.206); + U6[IW] = KALYPSSO_NUM(0.0); + U6[IP] = KALYPSSO_NUM(0.029); + + U7[ID] = KALYPSSO_NUM(0.5323); + U7[IU] = KALYPSSO_NUM(0.0); + U7[IV] = KALYPSSO_NUM(1.206); + U7[IW] = KALYPSSO_NUM(0.0); + U7[IP] = KALYPSSO_NUM(0.3); + break; + + case 3: + // Config 4 + U0[ID] = KALYPSSO_NUM(1.1); + U0[IU] = KALYPSSO_NUM(0.0); + U0[IV] = KALYPSSO_NUM(0.0); + U0[IW] = KALYPSSO_NUM(0.0); + U0[IP] = KALYPSSO_NUM(1.1); + + U1[ID] = KALYPSSO_NUM(0.5065); + U1[IU] = KALYPSSO_NUM(0.8939); + U1[IV] = KALYPSSO_NUM(0.0); + U1[IW] = KALYPSSO_NUM(0.0); + U1[IP] = KALYPSSO_NUM(0.35); + + U2[ID] = KALYPSSO_NUM(1.1); + U2[IU] = KALYPSSO_NUM(0.8939); + U2[IV] = KALYPSSO_NUM(0.8939); + U2[IW] = KALYPSSO_NUM(0.0); + U2[IP] = KALYPSSO_NUM(1.1); + + U3[ID] = KALYPSSO_NUM(0.5065); + U3[IU] = KALYPSSO_NUM(0.0); + U3[IV] = KALYPSSO_NUM(0.8939); + U3[IW] = KALYPSSO_NUM(0.0); + U3[IP] = KALYPSSO_NUM(0.35); + + U4[ID] = KALYPSSO_NUM(0.5065); + U4[IU] = KALYPSSO_NUM(0.0); + U4[IV] = KALYPSSO_NUM(0.0); + U4[IW] = KALYPSSO_NUM(0.8939); + U4[IP] = KALYPSSO_NUM(0.35); + + U5[ID] = KALYPSSO_NUM(1.1); + U5[IU] = KALYPSSO_NUM(0.8939); + U5[IV] = KALYPSSO_NUM(0.0); + U5[IW] = KALYPSSO_NUM(0.8939); + U5[IP] = KALYPSSO_NUM(1.1); + + U6[ID] = KALYPSSO_NUM(1.1); + U6[IU] = KALYPSSO_NUM(0.8939); + U6[IV] = KALYPSSO_NUM(0.8939); + U6[IW] = KALYPSSO_NUM(0.8939); + U6[IP] = KALYPSSO_NUM(1.1); + + U7[ID] = KALYPSSO_NUM(0.5065); + U7[IU] = KALYPSSO_NUM(0.0); + U7[IV] = KALYPSSO_NUM(0.8939); + U7[IW] = KALYPSSO_NUM(0.8939); + U7[IP] = KALYPSSO_NUM(0.35); + break; + + default: + // Config 4 + U0[ID] = KALYPSSO_NUM(1.1); + U0[IU] = KALYPSSO_NUM(0.0); + U0[IV] = KALYPSSO_NUM(0.0); + U0[IW] = KALYPSSO_NUM(0.0); + U0[IP] = KALYPSSO_NUM(1.1); + + U1[ID] = KALYPSSO_NUM(0.5065); + U1[IU] = KALYPSSO_NUM(0.8939); + U1[IV] = KALYPSSO_NUM(0.0); + U1[IW] = KALYPSSO_NUM(0.0); + U1[IP] = KALYPSSO_NUM(0.35); + + U2[ID] = KALYPSSO_NUM(1.1); + U2[IU] = KALYPSSO_NUM(0.8939); + U2[IV] = KALYPSSO_NUM(0.8939); + U2[IW] = KALYPSSO_NUM(0.0); + U2[IP] = KALYPSSO_NUM(1.1); + + U3[ID] = KALYPSSO_NUM(0.5065); + U3[IU] = KALYPSSO_NUM(0.0); + U3[IV] = KALYPSSO_NUM(0.8939); + U3[IW] = KALYPSSO_NUM(0.0); + U3[IP] = KALYPSSO_NUM(0.35); + + U4[ID] = KALYPSSO_NUM(0.5065); + U4[IU] = KALYPSSO_NUM(0.0); + U4[IV] = KALYPSSO_NUM(0.0); + U4[IW] = KALYPSSO_NUM(0.8939); + U4[IP] = KALYPSSO_NUM(0.35); + + U5[ID] = KALYPSSO_NUM(1.1); + U5[IU] = KALYPSSO_NUM(0.8939); + U5[IV] = KALYPSSO_NUM(0.0); + U5[IW] = KALYPSSO_NUM(0.8939); + U5[IP] = KALYPSSO_NUM(1.1); + + U6[ID] = KALYPSSO_NUM(1.1); + U6[IU] = KALYPSSO_NUM(0.8939); + U6[IV] = KALYPSSO_NUM(0.8939); + U6[IW] = KALYPSSO_NUM(0.8939); + U6[IP] = KALYPSSO_NUM(1.1); + + U7[ID] = KALYPSSO_NUM(0.5065); + U7[IU] = KALYPSSO_NUM(0.0); + U7[IV] = KALYPSSO_NUM(0.8939); + U7[IW] = KALYPSSO_NUM(0.8939); + U7[IP] = KALYPSSO_NUM(0.35); + } + + return Us; + +} // getRiemannConfig3d + +} // namespace kalypsso diff --git a/src/kalypsso/core/problems/initRiemannConfig2d.h b/src/kalypsso/core/problems/initRiemannConfig2d.h new file mode 100644 index 0000000..a17e916 --- /dev/null +++ b/src/kalypsso/core/problems/initRiemannConfig2d.h @@ -0,0 +1,136 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file initRiemannConfig2d.h + * \brief Implement initialization routine to solve a four quadrant 2D Riemann + * problem. + * + * In the 2D case, there are 19 different possible configurations (see + * article by Lax and Liu, "Solution of two-dimensional riemann + * problems of gas dynamics by positive schemes",SIAM journal on + * scientific computing, 1998, vol. 19, no2, pp. 319-340). + * + */ +#ifndef KALYPSSO_CORE_PROBLEMS_INIT_RIEMANN_CONFIG_2D_H_ +#define KALYPSSO_CORE_PROBLEMS_INIT_RIEMANN_CONFIG_2D_H_ + +#include +#include +#include + +namespace kalypsso +{ + +// ===================================================================== +// ===================================================================== +template +constexpr uint32_t +nb_riemann_states() +{ + if constexpr (dim == 2) + { + return 4; + } + else if constexpr (dim == 3) + { + return 8; + } +} // nb_riemann_states + +// ===================================================================== +// ===================================================================== +// ===================================================================== +template +struct RiemannConfig +{ + static constexpr uint32_t NB_STATES = nb_riemann_states(); + + using HydroState_t = HydroState; + + using HydroStates_t = Kokkos::Array()>; +}; + +// ===================================================================== +// ===================================================================== +/** + * [DEPRECATED] Convert primitive to conservative variables. + * + * Each numerical scheme should provide this routine using an EOS wrapper for computing internal + * energy from pressure. + */ +template +KOKKOS_FUNCTION void +primToCons(HydroState & U, real_t gamma0) +{ + + constexpr auto ID = core::models::Hydro::ID; + constexpr auto IP = core::models::Hydro::IP; + constexpr auto IU = core::models::Hydro::IU; + constexpr auto IV = core::models::Hydro::IV; + constexpr auto IW = core::models::Hydro::IW; + + real_t rho = U[ID]; + real_t p = U[IP]; + real_t u = U[IU]; + real_t v = U[IV]; + + if constexpr (dim == 2) + { + U[IU] *= rho; // rho*u + U[IV] *= rho; // rho*v + U[IP] = p / (gamma0 - ONE_F) + rho * (u * u + v * v) * HALF_F; + } + else if constexpr (dim == 3) + { + real_t w = U[IW]; + U[IU] *= rho; // rho*u + U[IV] *= rho; // rho*v + U[IW] *= rho; // rho*w + U[IP] = p / (gamma0 - ONE_F) + rho * (u * u + v * v + w * w) * HALF_F; + } + +} // primToCons + +// ===================================================================== +// ===================================================================== +/** + * Return an array of HydroStates, one per region, in primitive variables + */ +KOKKOS_FUNCTION +RiemannConfig<2>::HydroStates_t +getRiemannConfig2d(int numConfig); + +// ===================================================================== +// ===================================================================== +/** + * Return an array of HydroStates, one per region, in primitive variables + */ +KOKKOS_FUNCTION +RiemannConfig<3>::HydroStates_t +getRiemannConfig3d(int numConfig); + +// ===================================================================== +// ===================================================================== +/** + * Return an array of HydroStates, one per region, in primitive variables + */ +template +KOKKOS_INLINE_FUNCTION auto +getRiemannConfig(int numConfig) +{ + + if constexpr (dim == 2) + { + return getRiemannConfig2d(numConfig); + } + else if constexpr (dim == 3) + { + return getRiemannConfig3d(numConfig); + } +} // getRiemannConfig + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_PROBLEMS_INIT_RIEMANN_CONFIG_2D_H_ diff --git a/src/kalypsso/core/problems/init_cond_utils.cpp b/src/kalypsso/core/problems/init_cond_utils.cpp new file mode 100644 index 0000000..62b7047 --- /dev/null +++ b/src/kalypsso/core/problems/init_cond_utils.cpp @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +#include + +namespace kalypsso +{ +namespace core +{ + +InitConditionsIndicator +get_init_indicator(ConfigMap const & config_map) +{ + const auto indicator_name = + config_map.getString("amr", "refine_criterion_at_init", "SAME_AS_REGULAR_DYNAMICS"); + auto maybe_value = InitConditionsIndicator::_from_string_nothrow(indicator_name.c_str()); + if (maybe_value) + return *maybe_value; + return InitConditionsIndicator::SAME_AS_REGULAR_DYNAMICS; +} + +} // namespace core +} // namespace kalypsso diff --git a/src/kalypsso/core/problems/init_cond_utils.h b/src/kalypsso/core/problems/init_cond_utils.h new file mode 100644 index 0000000..02b6f5b --- /dev/null +++ b/src/kalypsso/core/problems/init_cond_utils.h @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file init_cond_utils.h + */ +#ifndef KALYPSSO_CORE_PROBLEMS_INITCONDUTILS_H_ +#define KALYPSSO_CORE_PROBLEMS_INITCONDUTILS_H_ + +#include +#include <../../better-enums/enum.h> + +// forward declaration +class ConfigMap; + +namespace kalypsso +{ + +namespace core +{ +// clang-format off +/** + * An enum type to represent all possible refine indicator types used during initial condition. + * + * A given test case may only support a subset of this enum. + * The default value is to use the same refine criterion as the regular dynamics (t>0). + */ +BETTER_ENUM(InitConditionsIndicator, uint8_t, + ALWAYS_REFINE = 0, // always refine up to level max + GEOMETRIC = 1, // refine near interface + SAME_AS_REGULAR_DYNAMICS = 2 // use the same refine criterion as specified with + // amr/refine_criterion in ini inputfile + ) +// clang-format on + +/** + * Retrieve refine criterion type to use inside initial conditions. + */ +InitConditionsIndicator +get_init_indicator(ConfigMap const & config_map); + +} // namespace core + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_PROBLEMS_INITCONDUTILS_H_ diff --git a/src/kalypsso/core/prolongation.h b/src/kalypsso/core/prolongation.h new file mode 100644 index 0000000..ba0c832 --- /dev/null +++ b/src/kalypsso/core/prolongation.h @@ -0,0 +1,262 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file prolongation.h + * + * Define parameters used in prologation operators (from coarse to fine AMR level interpolation). + */ +#ifndef KALYPSSO_CORE_PROLONGATION_H_ +#define KALYPSSO_CORE_PROLONGATION_H_ + +#include <../better-enums/enum.h> +#include +#include +#include + +namespace kalypsso +{ + +// clang-format off +/** + * Enumerate types of prolongation operator for cell centered values. + * + * - SIMPLE_COPY: just copy the coarse cell value into the fine cell + * - EXTRAPOLATE_LINEAR_MINMOD: use limiting slopes to extrapolate the coarse value to fine + * - CONSERVATIVE_INTERPOLATION_ORDER_2: use a 3x3 stencil at coarse level to compute + * a conservative interpolate value in the fine cells; interpolation is done in + * a directionally split way + * - CONSERVATIVE_INTERPOLATION_ORDER_4: use a 5x5 stencil at coarse level to compute + * a conservative interpolate value in the fine cells; interpolation is done in + * a directionally split way + * + * \note conservative interpolation coefficients are computed using python script : + * src/shared/utils/conservative_polynomial_interpolation_order_2.py + */ +BETTER_ENUM(CellCenteredProlongationType, int, + SIMPLE_COPY, + EXTRAPOLATE_LINEAR_MINMOD, + CONSERVATIVE_INTERPOLATION_ORDER_2, + CONSERVATIVE_INTERPOLATION_ORDER_4) +// clang-format on + +inline CellCenteredProlongationType +get_cell_prolongation_type(ConfigMap const & config_map) +{ + auto prolongation_name = + config_map.getString("amr", "cell_centered_prolongation_type", "SIMPLE_COPY"); + auto maybe_value = CellCenteredProlongationType::_from_string_nothrow(prolongation_name.c_str()); + if (maybe_value) + return *maybe_value; + + KALYPSSO_ERROR("Invalid cell_centered_prolongation_type : {}; using SIMPLE_COPY instead.", + prolongation_name.c_str()); + return CellCenteredProlongationType::SIMPLE_COPY; +} + +/** + * Enumerate types of prolongation operator for face centered values (magnetic field) on internal + * faces. + * + * Doing prolongation means provide an algorithm to fill the new fine AMR level face-centered + * values. The number of new fine AMR level faces is: + * - in 2d, 2^(dim-1) * 3 * dim = 2 * 3 * 2 = 12 new faces + * - in 3d, 2^(dim-1) * 3 * dim = 4 * 3 * 3 = 36 new faces + * + * We must also separate the new fine face into 2 classes: + * - external faces: the ones that are collocated with faces of the parent coarse cells + * - internal faces: the ones that are not collocated with faces of parent cells + * + * Number of internal/external faces: + * - in 2d, 2 * dim = 4 internal faces + 8 external faces + * - in 3d, 4 * dim = 12 internal faces + 24 external faces + * + * Generally speaking, we will face-centered values of the fine cells in two steps: + * + * 1. fill the external faces from values of the corresponding coarse cell face, doing, e.g.: + * - a simple copy + * - a linear extrapolation using limited transverse slopes + * + * 2. fill the internal faces using a divergence preserving algorithm (see reference + * below): + * - use formulas from Toth and Roe (2002) + * - use divergence-preserving B-spline interpolation from Schroeder et al, 2022 + * - other ideas adapted from CEA AMR internal code. + * + * The following drawing illustrates internal versus external faces. + * - on the left In 2d, there are 4 internal faces, the ones marked with "+", while all other + * faces, are faces collocated with faces of the old coarse cells. + * + * + * ________________ _________________ + * | | | + | + * | | | + | + * | | | + | + * | | ==> ++++++++ ++++++++ + * | | | + | + * | | | + | + * |______________| |_______+______ | + * + * Currently implemented: + * + * - formulas based on Toth and Roe, JCP, 180, 746-759, 2002: Divergence- and curl-preserving + * prolongation and restriction formulas. https://doi.org/10.1006/jcph.2002.7120 + * + * Possible other implementations: use B-spline interpolation approach described in + * + * - "Higher order divergence-free and curl-free interpolation on MAC grids", Ray-Chowdurry et al, + * JCP, vol 503, 112831, 2024 + * - "Local divergence-free polynomial interpolation on MAC grids", Schroeder et al, JCP, vol 468, + * 111500, 2022 + */ +// clang-format off +BETTER_ENUM(FaceCenteredProlongationInternalType, int, + TOTH_AND_ROE) +// clang-format on + +// clang-format off +BETTER_ENUM(FaceCenteredProlongationExternalType, int, + SIMPLE_COPY, + EXTRAPOLATE_LINEAR_MINMOD) +// clang-format on + +inline FaceCenteredProlongationInternalType +get_internal_face_prolongation_type(ConfigMap const & config_map) +{ + auto prolongation_name = + config_map.getString("amr", "face_centered_prolongation_internal_type", "TOTH_AND_ROE"); + auto maybe_value = + FaceCenteredProlongationInternalType::_from_string_nothrow(prolongation_name.c_str()); + if (maybe_value) + return *maybe_value; + return FaceCenteredProlongationInternalType::TOTH_AND_ROE; +} + +inline FaceCenteredProlongationExternalType +get_external_face_prolongation_type(ConfigMap const & config_map) +{ + auto prolongation_name = + config_map.getString("amr", "face_centered_prolongation_external_type", "SIMPLE_COPY"); + auto maybe_value = + FaceCenteredProlongationExternalType::_from_string_nothrow(prolongation_name.c_str()); + if (maybe_value) + return *maybe_value; + return FaceCenteredProlongationExternalType::SIMPLE_COPY; +} + +// ============================================================== +// ============================================================== +struct ProlongationParam +{ + + CellCenteredProlongationType m_cell; + FaceCenteredProlongationInternalType m_face_internal; + FaceCenteredProlongationExternalType m_face_external; + + ProlongationParam(ConfigMap const & config_map) + : m_cell(get_cell_prolongation_type(config_map)) + , m_face_internal(get_internal_face_prolongation_type(config_map)) + , m_face_external(get_external_face_prolongation_type(config_map)) + {} + +}; // struct ProlongationParam + +// ============================================================== +// ============================================================== +KOKKOS_INLINE_FUNCTION real_t +minmod_scalar(real_t q, real_t qPlus, real_t qMinus, real_t slope_type) +{ + real_t dq = 0; + + if (slope_type == 1 or slope_type == 2) + { + const real_t dlft = slope_type * (q - qMinus); + const real_t drgt = slope_type * (qPlus - q); + const real_t dcen = HALF_F * (qPlus - qMinus); + const real_t dsgn = (dcen >= ZERO_F) ? ONE_F : -ONE_F; + const real_t slop = fmin(fabs(dlft), fabs(drgt)); + const real_t dlim = (dlft * drgt) <= ZERO_F ? ZERO_F : slop; + dq = dsgn * fmin(dlim, fabs(dcen)); + } + + return dq; + +} // minmod_scalar + +/** + * A companion structure for performing conservative interpolation. + */ +struct ConservativeInterpolation +{ + const Kokkos::Array COEFS2_L{ KALYPSSO_NUM(0.125), + KALYPSSO_NUM(1.0), + KALYPSSO_NUM(-0.125) }; + + const Kokkos::Array COEFS2_R{ KALYPSSO_NUM(-0.125), + KALYPSSO_NUM(1.0), + KALYPSSO_NUM(0.125) }; + + + //! Perform second order conservative interpolation. + //! + //! Consider threa integral values corresponding to three consecutive cells: + //! + //! | Im1 | I0 | Ip1 | + //! | | | | + //! | | L | R | | + //! + //! we want to interpolate conservative values in the fine cell (central cell refined) + //! + //! interpolation coefficients are obtained from + //! utils/conservative_polynomial_interpolation_order_2.py + KOKKOS_INLINE_FUNCTION real_t + order2(real_t const & Im1, real_t const & I0, real_t const & Ip1, bool is_left) const + { + return is_left ? COEFS2_L[0] * Im1 + COEFS2_L[1] * I0 + COEFS2_L[2] * Ip1 + : COEFS2_R[0] * Im1 + COEFS2_R[1] * I0 + COEFS2_R[2] * Ip1; + } + + const Kokkos::Array COEFS4_L{ KALYPSSO_NUM(-3.0) / 128, + KALYPSSO_NUM(11.0) / 64, + KALYPSSO_NUM(1.0), + KALYPSSO_NUM(-11.0) / 64, + KALYPSSO_NUM(3.0) / 128 }; + + const Kokkos::Array COEFS4_R{ KALYPSSO_NUM(3.0) / 128, + KALYPSSO_NUM(-11.0) / 64, + KALYPSSO_NUM(1.0), + KALYPSSO_NUM(11.0) / 64, + KALYPSSO_NUM(-3.0) / 128 }; + + //! Perform fourth order conservative interpolation. + //! + //! Consider threa integral values corresponding to three consecutive cells: + //! + //! | Im2 | Im1 | I0 | Ip1 | Ip2 | + //! | | | | | | + //! | | | L | R | | | + //! + //! we want to interpolate conservative values in the fine cell (central cell refined) + //! + //! interpolation coefficients are obtained from + //! utils/conservative_polynomial_interpolation_order_4.py + KOKKOS_INLINE_FUNCTION real_t + order4(real_t const & Im2, + real_t const & Im1, + real_t const & I0, + real_t const & Ip1, + real_t const & Ip2, + bool is_left) const + { + return is_left ? COEFS4_L[0] * Im2 + COEFS4_L[1] * Im1 + COEFS4_L[2] * I0 + COEFS4_L[3] * Ip1 + + COEFS4_L[4] * Ip2 + : COEFS4_R[0] * Im2 + COEFS4_R[1] * Im1 + COEFS4_R[2] * I0 + COEFS4_R[3] * Ip1 + + COEFS4_R[4] * Ip2; + } + +}; // struct ConservativeInterpolation + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_PROLONGATION_H_ diff --git a/src/kalypsso/core/real_type.h b/src/kalypsso/core/real_type.h new file mode 100644 index 0000000..f5cc7e8 --- /dev/null +++ b/src/kalypsso/core/real_type.h @@ -0,0 +1,167 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file real_type.h + * \brief Define macros to switch single/double precision. + * + */ +#ifndef KALYPSSO_CORE_REAL_TYPE_H_ +#define KALYPSSO_CORE_REAL_TYPE_H_ + +#include + +#include +#include +#include +#include +#include +#include +#include + +KALYPSSO_DISABLE_NVCC_WARNINGS_PUSH() + +namespace kalypsso +{ + +/** + * \typedef real_t (alias to float or double) + */ +#ifdef KALYPSSO_CORE_USE_DOUBLE +using real_t = double; +#else +using real_t = float; +#endif // KALYPSSO_CORE_USE_DOUBLE + +/** + * Helper macro to define portable numeric values. + */ +#ifdef KALYPSSO_CORE_USE_DOUBLE +# define KALYPSSO_NUM(x) x +#else +# define KALYPSSO_NUM(x) x##f +#endif // KALYPSSO_DOUBLE + +// math function +#if KOKKOS_VERSION_MAJOR > 3 +using Kokkos::clamp; +using Kokkos::exp; +using Kokkos::fmax; +using Kokkos::fmin; +using Kokkos::sqrt; +using Kokkos::fabs; +using Kokkos::fmod; +using Kokkos::isnan; +using Kokkos::fmod; +using Kokkos::exp; +using Kokkos::log; +using Kokkos::pow; +// trigonometric functions +using Kokkos::sin; +using Kokkos::cos; +using Kokkos::tan; +using Kokkos::asin; +using Kokkos::acos; +using Kokkos::atan; +using Kokkos::atan2; +// hyperbolic functions +using Kokkos::sinh; +using Kokkos::cosh; +using Kokkos::tanh; +using Kokkos::asinh; +using Kokkos::acosh; +using Kokkos::atanh; +#else +using Kokkos::Experimental::clamp; +using Kokkos::Experimental::exp; +using Kokkos::Experimental::fmax; +using Kokkos::Experimental::fmin; +using Kokkos::Experimental::sqrt; +using Kokkos::Experimental::fabs; +using Kokkos::Experimental::fmod; +using Kokkos::Experimental::isnan; +using Kokkos::Experimental::fmod; +using Kokkos::Experimental::exp; +using Kokkos::Experimental::log; +using Kokkos::Experimental::pow; +// trigonometric functions +using Kokkos::Experimental::sin; +using Kokkos::Experimental::cos; +using Kokkos::Experimental::tan; +using Kokkos::Experimental::asin; +using Kokkos::Experimental::acos; +using Kokkos::Experimental::atan; +using Kokkos::Experimental::atan2; +// hyperbolic functions +using Kokkos::Experimental::sinh; +using Kokkos::Experimental::cosh; +using Kokkos::Experimental::tanh; +using Kokkos::Experimental::asinh; +using Kokkos::Experimental::acosh; +using Kokkos::Experimental::atanh; +#endif + +// this macro is slightly adapted from Kokkos +#define KALYPSSO_STATIC_MATH_CONSTANT(TRAIT, VALUE) \ + template \ + static constexpr auto TRAIT##_v = std::enable_if_t, T>(VALUE); \ + static constexpr auto TRAIT##_F = TRAIT##_v + +#define KALYPSSO_MATH_CONSTANT(TRAIT, VALUE) \ + template \ + inline constexpr auto TRAIT##_v = std::enable_if_t, T>(VALUE); \ + inline constexpr auto TRAIT##_F = TRAIT##_v + +// clang-format off +KALYPSSO_MATH_CONSTANT(ZERO, 0.000000000000000000000000000000000000L); +KALYPSSO_MATH_CONSTANT(HALF, 0.500000000000000000000000000000000000L); +KALYPSSO_MATH_CONSTANT(ONE, 1.000000000000000000000000000000000000L); +KALYPSSO_MATH_CONSTANT(TWO, 2.000000000000000000000000000000000000L); +KALYPSSO_MATH_CONSTANT(THREE, 3.000000000000000000000000000000000000L); +KALYPSSO_MATH_CONSTANT(FOUR, 4.000000000000000000000000000000000000L); +KALYPSSO_MATH_CONSTANT(ONE_THIRD, 0.333333333333333333333333333333333333L); +KALYPSSO_MATH_CONSTANT(ONE_FOURTH, 0.250000000000000000000000000000000000L); +KALYPSSO_MATH_CONSTANT(PI, 3.141592653589793238462643383279502884L); +KALYPSSO_MATH_CONSTANT(SQRT_2, 1.414213562373095048763788073031832937L); +KALYPSSO_MATH_CONSTANT(SQRT_3, 1.732050807568877293573725295594556428L); +KALYPSSO_MATH_CONSTANT(SQRT_5, 2.236067977499789696414420059333849622L); +// clang-format on + +// math function +#if defined(KALYPSSO_CORE_USE_DOUBLE) +# define COPYSIGN(x, y) copysign(x, y) +#else +# define COPYSIGN(x, y) copysignf(x, y) +#endif // KALYPSSO_CORE_USE_DOUBLE + +// other useful macros +#define SQR(x) ((x) * (x)) + +//! Compute the maximum of two values. +template +KOKKOS_INLINE_FUNCTION constexpr T const & +max(T const & a, T const & b) +{ + return (a > b) ? a : b; +} + +//! Compute the minimum of two values. +template +KOKKOS_INLINE_FUNCTION constexpr T const & +min(T const & a, T const & b) +{ + return (a < b) ? a : b; +} + +#if defined(KALYPSSO_CORE_USE_DOUBLE) +# define STATIC_CAST_REAL_T(x) (x) +#else +# define STATIC_CAST_REAL_T(x) (static_cast(x)) +#endif + +} // namespace kalypsso + +KALYPSSO_DISABLE_NVCC_WARNINGS_POP() + +#endif // KALYPSSO_CORE_REAL_TYPE_H_ diff --git a/src/kalypsso/core/region_utils.h b/src/kalypsso/core/region_utils.h new file mode 100644 index 0000000..2649842 --- /dev/null +++ b/src/kalypsso/core/region_utils.h @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +/** + * \file region_utils.h + */ +#ifndef KALYPSSO_CORE_REGION_UTILS_H_ +#define KALYPSSO_CORE_REGION_UTILS_H_ + +#include +#include +#include +#include +#include + +#include +#include + +namespace kalypsso +{ + +// ========================================================================== +// ========================================================================== +/** + * Return true if cell is pure. + * + * A cell is considered pure if all corners belong to the same region. + * + * \param[in] regions array of regions (one per corner) + */ +template +KOKKOS_INLINE_FUNCTION bool +is_cell_fully_inside_region(Kokkos::Array()> const & regions) +{ + const auto region = regions[0]; + for (uint8_t i_corner = 1; i_corner < Corner::num_corners(); i_corner++) + { + if (region != regions[i_corner]) + return false; + } + return true; +} + +// ========================================================================== +// ========================================================================== +/** + * Return true if cell partially overlaps with a given region. + * + * \param[in] regions array of regions (one per corner) + */ +template +KOKKOS_INLINE_FUNCTION bool +does_cell_overlap_with_region(Kokkos::Array()> const & regions, + int region) +{ + for (uint8_t i_corner = 0; i_corner < Corner::num_corners(); i_corner++) + { + if (region == regions[i_corner]) + return true; + } + return false; +} + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_REGION_UTILS_H_ diff --git a/src/kalypsso/core/scan_utils.h b/src/kalypsso/core/scan_utils.h new file mode 100644 index 0000000..62c1d7e --- /dev/null +++ b/src/kalypsso/core/scan_utils.h @@ -0,0 +1,209 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file scan_utils.h + */ +#ifndef KALYPSSO_CORE_SCANUTILS_H_ +#define KALYPSSO_CORE_SCANUTILS_H_ + +#include +#include +#include +#include +#include + +namespace kalypsso +{ + +// ================================================================================ +// ================================================================================ +/** + * A Kokkos functor for performing a segmented parallel scan. + * + * \note this is actually an exclusive scan + * + */ +template +struct ScanIndexByLevel +{ + // typedef's + using exec_space = typename device_t::execution_space; + + using key_view_t = typename orchard_key_base_t::view_t; + + using res_view_t = Kokkos::View; + + // data + key_view_t m_keys; + res_view_t m_indexes; + + uint8_t m_level_start; + uint8_t m_level_max; + + ScanIndexByLevel(key_view_t keys, res_view_t indexes, uint8_t level_start, uint8_t level_max) + : m_keys(keys) + , m_indexes(indexes) + , m_level_start(level_start) + , m_level_max(level_max) + {} + + static void + run(key_view_t keys, res_view_t indexes, uint8_t level_start, uint8_t level_max) + { + auto functor = + ScanIndexByLevel(keys, indexes, level_start, level_max); + const auto num_keys = keys.size(); + + Kokkos::parallel_scan( + "ScanIndexByLevel", Kokkos::RangePolicy(0, num_keys), functor); + } + + using ScanValue = Kokkos::Array(num_levels)>; + + KOKKOS_INLINE_FUNCTION + void + init(ScanValue & update) const + { + for (int i = 0; i < num_levels; ++i) + { + update[i] = 0; + } + } // init + + KOKKOS_INLINE_FUNCTION + void + join(ScanValue & update, const ScanValue & input) const + { + for (int i = 0; i < num_levels; ++i) + { + update[i] += input[i]; + } + } // join + + KOKKOS_INLINE_FUNCTION + void + operator()(const size_t i, ScanValue & update, const bool is_final) const + { + + const auto key = m_keys(i); + const auto level = orchard_key_t::level(key); + + // clang-format off + if (level >= m_level_start and level < m_level_start + num_levels and level <= m_level_max) + { + if (is_final) + { + m_indexes(i) = static_cast(update[level - m_level_start]); + } + update[level - m_level_start]++; + } + // clang-format on + + } // operator() + +}; // struct ScanIndexByLevel + +// ================================================================================ +// ================================================================================ +/** + * Perform a segmented parallel scan to compute index by level. + * + * Example of application: when one want to fill a vtkNonOverlappingAMR object, the AMR blocks are + * grouped by level, so each block must be attributed an id, going from 0 to N(l)-1, where N(l) is + * the number of blocks at AMR level l. + * + * \param[in] keys is an array of orchard keys + * \param[in] level_min is the minimum AMR level allowed + * \param[in] level_max is the maximum AMR level allowed + * \param[in] local_num_quadrants (number of owned quadrants) - MPI only + * \param[in] par_env is MPI parallel environment - MPI only + * + * \return array of index (same size as input array), for each key in input, + * this is the nth key at level "l", so return "n". So output is the array of all those "n". + * + * \note (June 8, 2024) we can't use auto as the return type here because nvcc is complaining + */ +template +Kokkos::View +compute_index_by_level(typename orchard_key_base_t::view_t keys, + uint8_t level_min, + uint8_t level_max, + [[maybe_unused]] int32_t local_num_quadrants, + [[maybe_unused]] const ParallelEnv & par_env) +{ + using res_view_t = Kokkos::View; + + auto res = + res_view_t(Kokkos::view_alloc(Kokkos::WithoutInitializing, "Index by level"), keys.size()); + + // here we chose a maximum number of levels that will be scanned at once; this number + // is used as dimension of a Kokkos::Array data structured used during scan. + // TODO: study if we could increase this number, and study how it impact performance when running + // on GPU (using Kokkos::CUDA device) + constexpr int num_levels = 5; + + // perform scan piece by piece (i.e. by group of num_levels levels). + auto level_start = level_min; + while (level_start < level_max) + { + ScanIndexByLevel::run(keys, res, level_start, level_max); + level_start += num_levels; + } + +#ifdef KALYPSSO_CORE_USE_MPI + // now we need to update indexes when the number of MPI processes is larger than 1 + if (par_env.size() > 1) + { + + // number of AMR levels + const auto total_num_levels = level_max - level_min + 1; + + // compute number of keys per level in current MPI process (histogram) + // number of octants per level will have to be MPI_Scan'ed to fix indexes + using level_histo_view_t = Kokkos::View; + auto level_histo = level_histo_view_t("level_histo", static_cast(total_num_levels)); + + using exec_space = typename device_t::execution_space; + + Kokkos::parallel_for( + "compute_level_histogram", + Kokkos::RangePolicy(0, local_num_quadrants), + KOKKOS_LAMBDA(const int iOct) { + // get AMR level + const auto level = orchard_key_t::level(keys(iOct)); + KOKKOS_ASSERT(level >= level_min and level <= level_max and + "level wrong value: not in valid range."); + + Kokkos::atomic_add(&level_histo(level - level_min), 1); + }); + + // now we need to integrate that over all MPI process + auto level_histo_scanned = + level_histo_view_t("level_histo_scanned", static_cast(total_num_levels)); + par_env.comm().MPI_Exscan( + level_histo.data(), level_histo_scanned.data(), total_num_levels); + + // now fix indexes with offsets computed just above + Kokkos::parallel_for( + "compute_level_histogram", + Kokkos::RangePolicy(0, keys.size()), + KOKKOS_LAMBDA(const int iOct) { + // get AMR level + const auto level = orchard_key_t::level(keys(iOct)); + KOKKOS_ASSERT(level >= level_min and level <= level_max and + "level wrong value: not in valid range."); + + res(iOct) += level_histo_scanned(level - level_min); + }); + } // par_env.size() > 1 +#endif // KALYPSSO_CORE_USE_MPI + + return res; + +} // compute_index_by_level + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_SCANUTILS_H_ diff --git a/src/kalypsso/core/utils/Readme.md b/src/kalypsso/core/utils/Readme.md new file mode 100644 index 0000000..499a6e9 --- /dev/null +++ b/src/kalypsso/core/utils/Readme.md @@ -0,0 +1,3 @@ +# Finite difference approximation of first and second order derivative + +`Stencil_coefs_helper.py` is a simple script to help computing the coefficients that must be used in a N-points finite difference approximation of a 1st or 2nd order derivative when the point are non equispaced. diff --git a/src/kalypsso/core/utils/conservative_polynomial_interpolation_order_2.py b/src/kalypsso/core/utils/conservative_polynomial_interpolation_order_2.py new file mode 100755 index 0000000..dab98c2 --- /dev/null +++ b/src/kalypsso/core/utils/conservative_polynomial_interpolation_order_2.py @@ -0,0 +1,60 @@ +# SPDX-FileCopyrightText: 2025 kalypsso-core authors +# +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +# -*- coding: utf-8 -*- + +import sys, getopt +import argparse + +import numpy as np + +import sympy as sym +from fractions import Fraction +import math + +sym.init_printing() + +x = sym.symbols('x') +a,b,c = sym.symbols('a b c') +h = sym.symbols('h') + + +# +# integrate 2nd order polynomial ax^2+bx+c +# + +P = (a*x**2+b*x+c)/h + +# coarse cells (left, center and right) +sym.integrate(P, (x,-3*h/2, -h/2)) +sym.integrate(P, (x, -h/2, h/2)) +sym.integrate(P, (x, h/2,3*h/2)) + +m = sym.Matrix([[13/12*h**2, -h, 1], + [ 1/12*h**2, 0, 1], + [13/12*h**2, h, 1]]) +m + +# inverse matrix to get a,b,c +m_inv=m.inv() +m_inv +Fraction(-0.0416666666666667).limit_denominator(1_000_000) +Fraction(1.08333333333333).limit_denominator(1_000_000) + +# there are the conservative values in left, center and right cells +Im1, I0, Ip1 = sym.symbols('Im1 I0 Ip1') +I = sym.Matrix([Im1,I0,Ip1]) + +# compute a,b,c +I2=m_inv*I + +aa=I2[0] +bb=I2[1] +cc=I2[2] + +P2 = (aa*x**2+bb*x+cc)/(h/2) + +# integrate over child cells to get fine prolongated values +sym.integrate(P2, (x, -h/2, 0 )) +sym.integrate(P2, (x, 0, h/2)) diff --git a/src/kalypsso/core/utils/conservative_polynomial_interpolation_order_4.py b/src/kalypsso/core/utils/conservative_polynomial_interpolation_order_4.py new file mode 100755 index 0000000..cc6c5ea --- /dev/null +++ b/src/kalypsso/core/utils/conservative_polynomial_interpolation_order_4.py @@ -0,0 +1,66 @@ +# SPDX-FileCopyrightText: 2025 kalypsso-core authors +# +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +# -*- coding: utf-8 -*- + +import sys, getopt +import argparse + +import numpy as np + +import sympy as sym +from fractions import Fraction +import math + +sym.init_printing() + +x = sym.symbols('x') +a,b,c,d,e = sym.symbols('a b c d e') +h = sym.symbols('h') + + +# +# integrate 4th order polynomial ax^4+bx^3+cx^2+dx+e +# + +P = (a*x**4 + b*x**3 + c*x**2 + d*x + e)/h + +# coarse cells (2 on the left, center and two on the right) +sym.integrate(P, (x,-5*h/2, -3*h/2)) +sym.integrate(P, (x,-3*h/2, -h/2)) +sym.integrate(P, (x, -h/2, h/2)) +sym.integrate(P, (x, h/2, 3*h/2)) +sym.integrate(P, (x, 3*h/2, 5*h/2)) + +m = sym.Matrix([[1441/80*h**4, -17/2*h**3, 49/12*h**2, -2*h, 1], + [ 121/80*h**4, -5/4*h**3, 13/12*h**2, -h, 1], + [ 1/80*h**4, 0, 1/12*h**2, 0, 1], + [ 121/80*h**4, 5/4*h**3, 13/12*h**2, h, 1], + [1441/80*h**4, 17/2*h**3, 49/12*h**2, 2*h, 1]]) +m + +# inverse matrix to get a,b,c,d,e +m_inv=m.inv() +m_inv +Fraction(-0.0416666666666667).limit_denominator(1_000_000) +Fraction(1.08333333333333).limit_denominator(1_000_000) + +# there are the conservative values in left, center and right cells +Im2, Im1, I0, Ip1, Ip2 = sym.symbols('Im2 Im1 I0 Ip1 Ip2') +I = sym.Matrix([Im2,Im1,I0,Ip1,Ip2]) + +# compute a,b,c,d,e +I2=m_inv*I + +aa=I2[0] +bb=I2[1] +cc=I2[2] +dd=I2[3] +ee=I2[4] + +P2 = (aa*x**4 + bb*x**3 + cc*x**2 + dd*x + ee)/(h/2) + +# integrate over child cells to get fine prolongated values +sym.simplify(sym.integrate(P2, (x, -h/2, 0 ))) +sym.simplify(sym.integrate(P2, (x, 0, h/2))) diff --git a/src/kalypsso/core/utils/stencil_coefs_helper.py b/src/kalypsso/core/utils/stencil_coefs_helper.py new file mode 100755 index 0000000..84360ce --- /dev/null +++ b/src/kalypsso/core/utils/stencil_coefs_helper.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 + +# SPDX-FileCopyrightText: 2025 kalypsso-core authors +# +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +# -*- coding: utf-8 -*- + +import sys, getopt +import argparse + +import numpy as np + +from fractions import Fraction +import math + +def cross_check(x0, delta_h, order, offsets, coeffs): + ''' cross-check finite difference approximation + ''' + + # define a function f and its 1st and 2nd derivative + def f(x): + return 7*x**7 + 2*x**3 - 6*x + 1 + def d1f(x): + return 49*x**6 + 6*x**2 - 6 + def d2f(x): + return 294*x**5 + 12*x + def d3f(x): + return 1470*x**4 + 12 + def d4f(x): + return 5880*x**3 + def d5f(x): + return 17640*x**2 + def d6f(x): + return 35280*x + def d7f(x): + return 35280 + + def eval_finite_diff(x, dh): + value = 0.0 + for i in range(coeffs.size): + value += coeffs[i]*(f(x+offsets[i]*dh)-f(x))/(dh**order) + return value + + # compare exact derivative at x0 with finite difference approximation + if order == 1: + exact_value = d1f(x0) + elif order == 2: + exact_value = d2f(x0) + elif order == 3: + exact_value = d3f(x0) + elif order == 4: + exact_value = d4f(x0) + else: + print("Warning checking only available up to order 4") + return + + approx_value = eval_finite_diff(x0, delta_h) + abs_error = np.abs(exact_value-approx_value) + rel_error = abs_error/exact_value + normalized_error = (exact_value-approx_value)/delta_h**(coeffs.size) + + print("exact derivative : {}".format(exact_value)) + print("approximate value: {}".format(approx_value)) + print("absolute error : {}".format(abs_error)) + print("relative error : {}".format(rel_error)) + print("normalized error : {}".format(normalized_error)) + +def compute_finite_difference_coefficients(order, offsets, x0, delta_h): + '''Compute finite difference coefficients needed to estimate a first or second order derivative. + Coefficients are computed by solving a linear system obtained from the Taylor expansion about the given points. + + Taking as example the finite difference estimation of the first order derivative, using three points located at [alpha, 0, beta]. Doing the Taylor expansion about 0 (central point) of function f up to order 2: + f(alpha) = f(0) + alpha f'(0) + alpha^2/2 f''(0) + ... + f(beta) = f(0) + beta f'(0) + beta^2/2 f''(0) + ... + + to estimate f'(0) we need to find coefficients a and b such that: + a * alpha + b * beta = 1 + a * alpha^2/2 + b * beta^2/2 = 0 + + We use a linear system solver to obtain coefficient a and b + ''' + + # solve M * X = Y + M = np.array([offsets**i for i in range(1,offsets.size+1)]) + Y = np.zeros(offsets.size) + Y[order-1] = math.factorial(order) + + X = np.linalg.solve(M, Y) + + print("Given offsets {}, finite difference coefficients to estimate order {} derivative are {}".format(offsets,order,X)) + print("Trying to get fractions:") + for i in range(X.size): + print("coeff[{}]={}={}".format(i,X[i],Fraction(X[i]).limit_denominator(1_000_000))) + + cross_check(x0, delta_h, order, offsets, X) + + +############################################################################### +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description='Compute finite difference coefficients.', + epilog="Example: %(prog)s -o 1 -p -1.0 1.0") + parser.add_argument('-o', '--order', metavar="order", type=int, default=1) + parser.add_argument('-p', '--pos', metavar="pos", type=float, nargs='+', help="Points relative positions to center point") + parser.add_argument('-x', metavar="x0", type=float, default=2.0, help="Abscissa of the point where derivatie is estimated") + parser.add_argument('-d', metavar="delta_h", type=float, default=0.01, help="Cell size of central point") + parser.add_argument('-f', default=False, action="store_true", help="print full help") + args = parser.parse_args() + + if args.f: + parser.print_help() + print(compute_finite_difference_coefficients.__doc__) + + else: + # check that pos array contains at least order values + if not len(args.pos) >= args.order: + print("Error option -p / --pos should have at least \"order\" arguments!\n") + parser.print_help() + exit() + + compute_finite_difference_coefficients(args.order, np.array(args.pos), args.x, args.d) diff --git a/src/kalypsso/core/utils_block.h b/src/kalypsso/core/utils_block.h new file mode 100644 index 0000000..ab53789 --- /dev/null +++ b/src/kalypsso/core/utils_block.h @@ -0,0 +1,829 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file utils_block.h + */ +#ifndef KALYPSSO_CORE_UTILS_BLOCK_H_ +#define KALYPSSO_CORE_UTILS_BLOCK_H_ + +#include +#include +#include // for KALYPSSO_CORE_ENABLE_DEBUG +#include + +namespace kalypsso +{ + +//! A type alias to use uniformly in kalypsso for holding block size, ghost sizes, etc... +template +using block_size_t = Kokkos::Array; + +/** + * Type alias to hold + * - (i, j, ivar, iOct) in 2d, + * - (i, j, k, ivar, iOct) in 3d, + * + * i.e. a multi-index locating a cell in a DataArrayBlock. + * + */ +template +using block_multiindex_t = Kokkos::Array; + +/** + * Type alias to hold (i,j,ivar) in 2d or (i,j,k,ivar) in 3d, i.e. a multi-index locating a face in + * a FaceDataArrayBlock. + * + * ivar can only take 3 values: IX, IY and IZ representing respectively Y-face, Y-face or Z-face + * data. + * + * \note note about valid range of values for i,j,k + * In 2d, there are + * - (block_size[IX]+1)*(block_sizes[IY]) vertical faces, aka X-faces (normal to X direction) + * - (block_size[IX])*(block_sizes[IY]+1) horizontal faces, aka Y-faces (normal to Y direction) + * + */ +template +using face_multiindex_t = Kokkos::Array; + +/** + * Type alias to hold (i,j,dir) in 2d or (i,j,k,dir) in 3d, i.e. a multi-index locating an edge. + * + * dir can only take + * - 1 value in 2d : IZ + * - 3 values in 3d : IX, IY or IZ representing edge direction + * + * This is class is designed to help manipulating EMF (line-integrated electric field in MHD). + * + * In 2d, it only makes sense to have edge along Z axis, because edges along X or Y are considered + * as faces. + * + */ +template +using edge_multiindex_t = Kokkos::Array; + +/** + * return true if input face multiindex corresponds to an external face (i.e. has even index + * along given direction). + * + * \note ivar=IZ is always an external face in 2d. + * + */ +template +KOKKOS_INLINE_FUNCTION bool +is_external_face(face_multiindex_t const & face_indexes) +{ + if constexpr (dim == 2) + { + auto const & i = face_indexes[IX]; + auto const & j = face_indexes[IY]; + const auto & ivar = face_indexes[dim]; + + return (ivar == IX and (i & 0x1) == 0) or (ivar == IY and (j & 0x1) == 0) or (ivar == IZ); + } + else if constexpr (dim == 3) + { + auto const & i = face_indexes[IX]; + auto const & j = face_indexes[IY]; + auto const & k = face_indexes[IZ]; + const auto & ivar = face_indexes[dim]; + + return (ivar == IX and (i & 0x1) == 0) or (ivar == IY and (j & 0x1) == 0) or + (ivar == IZ and (k & 0x1) == 0); + } +} // is_external_face + +/** + * return true if input face multiindex corresponds to an internal face (i.e. has odd index + * along given direction). + * + * \note ivar=IZ is never an internal face in 2d. + */ +template +KOKKOS_INLINE_FUNCTION bool +is_internal_face(face_multiindex_t const & face_indexes) +{ + if constexpr (dim == 2) + { + auto const & i = face_indexes[IX]; + auto const & j = face_indexes[IY]; + const auto & ivar = face_indexes[dim]; + + return (ivar == IX and (i & 0x1) == 1) or (ivar == IY and (j & 0x1) == 1); + } + else if constexpr (dim == 3) + { + auto const & i = face_indexes[IX]; + auto const & j = face_indexes[IY]; + auto const & k = face_indexes[IZ]; + const auto & ivar = face_indexes[dim]; + + return (ivar == IX and (i & 0x1) == 1) or (ivar == IY and (j & 0x1) == 1) or + (ivar == IZ and (k & 0x1) == 1); + } + +} // is_internal_face + +/** + * create a face_multiindex_t from i,j,k and direction + */ +template +KOKKOS_INLINE_FUNCTION face_multiindex_t +to_face_multiindex(Kokkos::Array const & ijk, int32_t direction) +{ + face_multiindex_t res; + res[IX] = ijk[IX]; + res[IY] = ijk[IY]; + if constexpr (dim == 3) + { + res[IZ] = ijk[IZ]; + } + res[dim] = direction; + + return res; +} // to_face_multiindex + +//! create uniform block size +template +auto +get_block_size(int32_t value) +{ + if constexpr (dim == 2) + { + return block_size_t<2>{ value, value }; + } + else if constexpr (dim == 3) + { + return block_size_t<3>{ value, value, value }; + } +} + + +//! A type alias for representing coordinates of a cell inside a block of cells (on the tree +//! leaves). +template +using coord_t = Kokkos::Array; + +//! a type alias used in StencilHelper interface +template +using shift_t = coord_t; + +//! create uniform shift +template +KOKKOS_INLINE_FUNCTION auto +get_shift(int32_t value) +{ + if constexpr (dim == 2) + { + return shift_t<2>{ value, value }; + } + else if constexpr (dim == 3) + { + return shift_t<3>{ value, value, value }; + } +} + +// ======================================================= +// ======================================================= +/** + * input : index to a given cell inside a block of data of size (bx,by,bz) + * output : extract integer coordinates + */ +template +KOKKOS_INLINE_FUNCTION coord_t + cellindex_to_coord(int32_t index, block_size_t const & bSizes) +{ + KOKKOS_ASSERT(index >= 0); + + coord_t res; + + if constexpr (dim == 2) + { + const auto & bx = bSizes[IX]; + res[IY] = (index / bx); + res[IX] = (index - bx * res[IY]); + } + else if constexpr (dim == 3) + { + const auto & bx = bSizes[IX]; + const auto & by = bSizes[IY]; + + res[IZ] = (index / (bx * by)); + int32_t index2 = index - bx * by * res[IZ]; + res[IY] = (index2 / bx); + res[IX] = (index2 - bx * res[IY]); + } + + return res; + +} // cellindex_to_coord + +// ======================================================= +// ======================================================= +/** + * input : flat index to a given cell inside a block of data of size (bx,by,bz) + * output : extract integer coordinates + * + * \note this variant is only useful for computing coordinates of a DataArrayGhostedBlock cell. + */ +template +KOKKOS_INLINE_FUNCTION coord_t + cellindex_to_coord(int32_t flatindex, + block_size_t const & bSizes, + coord_t const shift) +{ + KOKKOS_ASSERT(flatindex >= 0); + KOKKOS_ASSERT(flatindex < Kokkos::dim_prod(bSizes)); + + coord_t res; + + if constexpr (dim == 2) + { + const auto & bx = bSizes[IX]; + res[IY] = (flatindex / bx); + res[IX] = (flatindex - bx * res[IY]); + + res[IY] += shift[IY]; + res[IX] += shift[IX]; + } + else if constexpr (dim == 3) + { + const auto & bx = bSizes[IX]; + const auto & by = bSizes[IY]; + + res[IZ] = (flatindex / (bx * by)); + int32_t index2 = flatindex - bx * by * res[IZ]; + res[IY] = (index2 / bx); + res[IX] = (index2 - bx * res[IY]); + + res[IZ] += shift[IZ]; + res[IY] += shift[IY]; + res[IX] += shift[IX]; + } + + return res; + +} // cellindex_to_coord + +// ======================================================= +// ======================================================= +/** + * Compute cell coordinates modulo inner block size. + * + * Consider a block of cells with a ghost layer of width 1, the lower left cells has coordinates + * (-1, -1), so it belongs to a neighbor block of cell. He were want to retrieve the coordinates + * from the neighbor point of view (without ghost cells), that is we just want to compute + * coordinates modulo the inner block size. + * + * Convert cell coordinates (ghosted) into cell coordinates (non ghost) using modulo operation. + * + * \param[out] cell_coords_inner coordinates of the source cell (non-ghosted coordinates) + * \param[in] cell_coords_ghosted coordinates of the destination cell (ghosted coordinates) + * \param[in] b block sizes + * + * \return relative direction to neighbor block where to look for data to copy (array of integer, + * one by direction). + * + * - when cell_coords_out corresponds to a cell inside current, then direction is (0,0) in 2d + * - when cell_coords_out corresponds to a cell outside current, then direction correspond to one + * of the 8 sectors around current block. + * + * + * | | + * dir=(-1,1) | dir=(0,1) | dir=(1,1) + * _____|_____________________|_______ + * | | + * | | + * | | + * | Current block | + * dir=(-1,0) | dir=(0,0) | dir=(1,0) + * | | + * | | + * | | + * | | + * _____|_____________________|_______ + * | | + * dir=(-1,-1) | dir=(0,-1) | dir=(1,-1) + * | | + * + */ +template +KOKKOS_INLINE_FUNCTION Kokkos::Array + ghosted_coords_to_inner_coords(coord_t & cell_coords_inner, + coord_t const & cell_coords_ghosted, + block_size_t const & b) +{ + auto dir = []() { + if constexpr (dim == 2) + return Kokkos::Array{ 0, 0 }; + else if constexpr (dim == 3) + return Kokkos::Array{ 0, 0, 0 }; + }(); + + // check if source cell is inside current block, or in neighbor block + if (cell_coords_ghosted[IX] < 0) + { + dir[IX] = -1; + cell_coords_inner[IX] = cell_coords_ghosted[IX] + b[IX]; + } + else if (cell_coords_ghosted[IX] >= b[IX]) + { + dir[IX] = 1; + cell_coords_inner[IX] = cell_coords_ghosted[IX] - b[IX]; + } + else + { + cell_coords_inner[IX] = cell_coords_ghosted[IX]; + } + + if (cell_coords_ghosted[IY] < 0) + { + dir[IY] = -1; + cell_coords_inner[IY] = cell_coords_ghosted[IY] + b[IY]; + } + else if (cell_coords_ghosted[IY] >= b[IY]) + { + dir[IY] = 1; + cell_coords_inner[IY] = cell_coords_ghosted[IY] - b[IY]; + } + else + { + cell_coords_inner[IY] = cell_coords_ghosted[IY]; + } + + if constexpr (dim == 3) + { + if (cell_coords_ghosted[IZ] < 0) + { + dir[IZ] = -1; + cell_coords_inner[IZ] = cell_coords_ghosted[IZ] + b[IZ]; + } + else if (cell_coords_ghosted[IZ] >= b[IZ]) + { + dir[IZ] = 1; + cell_coords_inner[IZ] = cell_coords_ghosted[IZ] - b[IZ]; + } + else + { + cell_coords_inner[IZ] = cell_coords_ghosted[IZ]; + } + } + + return dir; + +} // ghosted_coords_to_inner_coords + +// ======================================================= +// ======================================================= +/** + * input : index to a given cell inside a block of data of size (bx,by,bz) + * output : extract integer coordinates, add shift + */ +template +KOKKOS_INLINE_FUNCTION coord_t + cellindex_to_shifted_coord(uint32_t index, + block_size_t const & bSizes, + shift_t const & shift) +{ + coord_t res; + + if constexpr (dim == 2) + { + const auto & bx = bSizes[IX]; + res[IY] = (index / bx); + res[IX] = (index - bx * res[IY]); + + res[IX] += shift[IX]; + res[IY] += shift[IY]; + } + else if constexpr (dim == 3) + { + const auto & bx = bSizes[IX]; + const auto & by = bSizes[IY]; + + res[IZ] = (index / (bx * by)); + int32_t index2 = index - bx * by * res[IZ]; + res[IY] = (index2 / bx); + res[IX] = (index2 - bx * res[IY]); + + res[IX] += shift[IX]; + res[IY] += shift[IY]; + res[IZ] += shift[IZ]; + } + + return res; + +} // cellindex_to_shifted_coord + +// ======================================================= +// ======================================================= +/** + * output : extract integer coordinates + * input : index to a given cell inside a block of data of size (bx,by,bz) + */ +template +KOKKOS_INLINE_FUNCTION int32_t +coord_to_cellindex(coord_t const & coord, block_size_t const & bSizes) +{ + int32_t index = 0; + + if constexpr (dim == 2) + { + return coord[IX] + bSizes[IX] * coord[IY]; + } + else if constexpr (dim == 3) + { + return coord[IX] + bSizes[IX] * coord[IY] + bSizes[IX] * bSizes[IY] * coord[IZ]; + } + + return index; + +} // coord_to_cellindex + +// ======================================================= +// ======================================================= +/** + * output : extract integer coordinates + * input : index to a given cell inside a block of data of size (bx,by,bz) + */ +template +KOKKOS_INLINE_FUNCTION int32_t +coord_to_cellindex(coord_t const & coord, + coord_t const & shift, + block_size_t const & bSizes) +{ + int32_t index = 0; + + if constexpr (dim == 2) + { + return (coord[IX] + shift[IX]) + bSizes[IX] * (coord[IY] + shift[IY]); + } + else if constexpr (dim == 3) + { + return (coord[IX] + shift[IX]) + bSizes[IX] * (coord[IY] + shift[IY]) + + bSizes[IX] * bSizes[IY] * (coord[IZ] + shift[IZ]); + } + + return index; + +} // coord_to_cellindex + +// ======================================================= +// ======================================================= +/** + * \param[in] cellindex is index to a given cell inside a block of data of size (bx,by,bz) + * \param[in] shift specify a displacement inside current block + * \param[in] bSizes specify the block sizes + * + * \return the shifted cellindex + * + * \note in release build, we do not check that the return cellindex is valid, it would require to + * convert cellindex into cell coordinates. We only do it in debug build + */ +template +KOKKOS_INLINE_FUNCTION int32_t +shifted_cellindex(int32_t const & cellindex, + coord_t const shift, + block_size_t const & bSizes) +{ + KOKKOS_ASSERT(cellindex < dim_prod(bSizes)); + +#ifdef KALYPSSO_CORE_ENABLE_DEBUG + // check shifted cellindex is valid + coord_t coord = cellindex_to_coord(cellindex, bSizes); + for (size_t dir = 0; dir < dim; ++dir) + { + int32_t coord_dir = coord[dir] + shift[dir]; + KOKKOS_ASSERT(coord_dir >= 0 && coord_dir < static_cast(bSizes[dir]) && + "Wrong shift, new cell is outside block"); + } +#endif + + int32_t index = 0; + + if constexpr (dim == 2) + { + return cellindex + shift[IX] + bSizes[IX] * shift[IY]; + } + else if constexpr (dim == 3) + { + return cellindex + shift[IX] + bSizes[IX] * shift[IY] + bSizes[IX] * bSizes[IY] * shift[IZ]; + } + + return index; + +} // shifted_cellindex + +// ======================================================= +// ======================================================= +/** + * \param[in] coords coordinates of a face + * \param[in] fbSizes is a face block sizes (block size plus 1) + * + * \return true if multi-index coords correspond to an actual face multi-index + */ +template +KOKKOS_INLINE_FUNCTION bool +are_face_coords_valid(coord_t const & coords, block_size_t const & fbSizes) +{ + + if constexpr (dim == 2) + { + if constexpr (dir == IX) + { + return (coords[IY] < (fbSizes[IY] - 1)); + } + else if constexpr (dir == IY) + { + return (coords[IX] < (fbSizes[IX] - 1)); + } + else if constexpr (dir == IZ) + { + return (coords[IX] < (fbSizes[IX] - 1)) and (coords[IY] < (fbSizes[IY] - 1)); + } + } + else if constexpr (dim == 3) + { + if constexpr (dir == IX) + { + return (coords[IY] < (fbSizes[IY] - 1)) and (coords[IZ] < (fbSizes[IZ] - 1)); + } + else if constexpr (dir == IY) + { + return (coords[IZ] < (fbSizes[IZ] - 1)) and (coords[IX] < (fbSizes[IX] - 1)); + } + else if constexpr (dir == IZ) + { + return (coords[IX] < (fbSizes[IX] - 1)) and (coords[IY] < (fbSizes[IY] - 1)); + } + } + + return false; + +} // are_face_coords_valid + +// ======================================================= +// ======================================================= +/** + * \param[in] coords coordinates of a face + * \param[in] fbSizes is a face block sizes (block size plus 1) + * + * \return a multi-index coords corresponding to a cell center to which current face is a left face, + * except for the last face index which is a right face to last cell in block. + */ +template +KOKKOS_INLINE_FUNCTION auto +face_to_center(coord_t const & face_coords, block_size_t const & fbSizes) +{ + + coord_t center_coords = face_coords; + + center_coords[IX] = + (face_coords[IX] == (fbSizes[IX] - 1)) ? face_coords[IX] - 1 : face_coords[IX]; + + center_coords[IY] = + (face_coords[IY] == (fbSizes[IY] - 1)) ? face_coords[IY] - 1 : face_coords[IY]; + + if constexpr (dim == 3) + { + center_coords[IZ] = + (face_coords[IZ] == (fbSizes[IZ] - 1)) ? face_coords[IZ] - 1 : face_coords[IZ]; + } + + return center_coords; + +} // face_to_center + +// ======================================================= +// ======================================================= +/** + * create an edge_multiindex_t from i,j,k and direction + */ +template +KOKKOS_INLINE_FUNCTION edge_multiindex_t +to_edge_multiindex(Kokkos::Array const & ijk, int32_t direction) +{ + edge_multiindex_t res; + res[IX] = ijk[IX]; + res[IY] = ijk[IY]; + if constexpr (dim == 3) + { + res[IZ] = ijk[IZ]; + } + res[dim] = direction; + + return res; +} // to_edge_multiindex + +/** + * Create a shift to move along dir from one cell to another on the left. + */ +template +KOKKOS_INLINE_FUNCTION constexpr decltype(auto) +get_shift_left() +{ + if constexpr (dim == 2) + { + if constexpr (dir == IX) + return shift_t<2>{ -1, 0 }; + else + return shift_t<2>{ 0, -1 }; + } + else + { + if constexpr (dir == IX) + return shift_t<3>{ -1, 0, 0 }; + else if constexpr (dir == IY) + return shift_t<3>{ 0, -1, 0 }; + else + return shift_t<3>{ 0, 0, -1 }; + } +}; + +/** + * Create a shift to move along dir from one cell to another on the right. + */ +template +KOKKOS_INLINE_FUNCTION constexpr decltype(auto) +get_shift_right() +{ + if constexpr (dim == 2) + { + if constexpr (dir == IX) + return shift_t<2>{ 1, 0 }; + else + return shift_t<2>{ 0, 1 }; + } + else + { + if constexpr (dir == IX) + return shift_t<3>{ 1, 0, 0 }; + else if constexpr (dir == IY) + return shift_t<3>{ 0, 1, 0 }; + else + return shift_t<3>{ 0, 0, 1 }; + } +}; + +// ======================================================= +// ======================================================= +/** + * return arg of min of 3 values. + */ +template +KOKKOS_INLINE_FUNCTION int +ARG_MIN2(T a0, T a1) +{ + + T minval = a0; + int arg_min = IX; + + if (a1 < minval) + { + minval = a1; + arg_min = IY; + } + + return arg_min; + +} // ARG_MIN2 + +// ======================================================= +// ======================================================= +/** + * return arg of min of 3 values. + */ +template +KOKKOS_INLINE_FUNCTION int +ARG_MIN3(T a0, T a1, T a2) +{ + + T minval = a0; + int arg_min = IX; + + if (a1 < minval) + { + minval = a1; + arg_min = IY; + } + + if (a2 < minval) + { + minval = a2; + arg_min = IZ; + } + + return arg_min; + +} // ARG_MIN3 + +// ======================================================= +// ======================================================= +template +KOKKOS_INLINE_FUNCTION auto +max_flux_block_sizes(block_size_t const & bSizes) +{ + block_size_t res = bSizes; + + + if constexpr (dim == 2) + { + const auto dir = ARG_MIN2(res[IX], res[IY]); + res[dir]++; + } + else if constexpr (dim == 3) + { + const auto dir = ARG_MIN3(res[IX], res[IY], res[IZ]); + res[dir]++; + } + + return res; + +} // max_flux_block_sizes + +// ======================================================= +// ======================================================= +// determine local position of current cell inside virtual parent cell using integer +// coordinates in {0, 1} +// +// -------------------------- +// | | | +// | | | +// | 0,1 | 1,1 | +// | | | +// |___________|____________| +// | | | +// | | | +// | 0,0 | 1,0 | +// | | | +// |___________|____________| +template +KOKKOS_INLINE_FUNCTION auto +get_local_position(coord_t const & coordinates) +{ + if constexpr (dim == 2) + { + return Kokkos::Array{ + // clang-format off + coordinates[IX] - 2 * (coordinates[IX] / 2), + coordinates[IY] - 2 * (coordinates[IY] / 2) + // clang-format on + }; + } + else if constexpr (dim == 3) + { + return Kokkos::Array{ + // clang-format off + coordinates[IX] - 2 * (coordinates[IX] / 2), + coordinates[IY] - 2 * (coordinates[IY] / 2), + coordinates[IZ] - 2 * (coordinates[IZ] / 2) + // clang-format on + }; + } +} // get_local_position + +// ======================================================= +// ======================================================= +// determine local position of current cell inside virtual parent cell using integer +// coordinates in {-1, 1} +// +// -------------------------- +// | | | +// | | | +// | -1,1 | 1,1 | +// | | | +// |___________|____________| +// | | | +// | | | +// | -1,-1 | 1,-1 | +// | | | +// |___________|____________| +template +KOKKOS_INLINE_FUNCTION auto +get_local_position2(coord_t const & coordinates) +{ + if constexpr (dim == 2) + { + return Kokkos::Array{ + // clang-format off + 2 * (coordinates[IX] - 2 * (coordinates[IX] / 2)) - 1, + 2 * (coordinates[IY] - 2 * (coordinates[IY] / 2)) - 1 + // clang-format on + }; + } + else if constexpr (dim == 3) + { + return Kokkos::Array{ + // clang-format off + 2 * (coordinates[IX] - 2 * (coordinates[IX] / 2)) - 1, + 2 * (coordinates[IY] - 2 * (coordinates[IY] / 2)) - 1, + 2 * (coordinates[IZ] - 2 * (coordinates[IZ] / 2)) - 1 + // clang-format on + }; + } +} // get_local_position2 + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_UTILS_BLOCK_H_ diff --git a/src/kalypsso/core/vof/interface_tracking_utils.cpp b/src/kalypsso/core/vof/interface_tracking_utils.cpp new file mode 100644 index 0000000..17ef965 --- /dev/null +++ b/src/kalypsso/core/vof/interface_tracking_utils.cpp @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +namespace kalypsso +{ + +namespace vof +{ + +// =============================================================================== +// =============================================================================== +KOKKOS_FUNCTION real_t +compute_plane_rhs_pentagonal_newton(const real_t rhs, + real_t guess, + const Kokkos::Array normal) +{ + static constexpr int MAX_ITERATIONS = KALYPSSO_YOUNGS_NEWTON_MAX_ITERATIONS; + static constexpr real_t PRECISION = KALYPSSO_YOUNGS_NEWTON_PRECISION; + const auto [nx, ny, _] = normal; + + real_t prev_guess; + for (int i = 0; i < MAX_ITERATIONS; i++) + { + // clang-format off + const real_t err = guess * guess * guess + - (guess - nx) * (guess - nx) * (guess - nx) + - (guess - ny) * (guess - ny) * (guess - ny) + - rhs; + const real_t deriv = 3 * (guess * guess + - (guess - nx) * (guess - nx) + - (guess - ny) * (guess - ny)); + // clang-format on + prev_guess = guess; + guess = guess - err / deriv; + + if (abs(guess - prev_guess) < PRECISION) + break; + } + + return guess; + +} // compute_plane_rhs_pentagonal_newton + +// =============================================================================== +// =============================================================================== +KOKKOS_FUNCTION real_t +compute_plane_rhs_hexagonal_newton(const real_t rhs, + real_t guess, + const Kokkos::Array normal) +{ + static constexpr int MAX_ITERATIONS = KALYPSSO_YOUNGS_NEWTON_MAX_ITERATIONS; + static constexpr real_t PRECISION = KALYPSSO_YOUNGS_NEWTON_PRECISION; + const auto [nx, ny, nz] = normal; + + real_t prev_guess; + for (int i = 0; i < MAX_ITERATIONS; i++) + { + // clang-format off + const real_t err = guess * guess * guess + - (guess - nx) * (guess - nx) * (guess - nx) + - (guess - ny) * (guess - ny) * (guess - ny) + - (guess - nz) * (guess - nz) * (guess - nz) + - rhs; + const real_t deriv = 3 * (guess * guess + - (guess - nx) * (guess - nx) + - (guess - ny) * (guess - ny) + - (guess - nz) * (guess - nz)); + // clang-format on + prev_guess = guess; + guess = guess - err / deriv; + + if (abs(guess - prev_guess) < PRECISION) + break; + } + + return guess; + +} // compute_plane_rhs_hexagonal_newton + +} // namespace vof + +} // namespace kalypsso diff --git a/src/kalypsso/core/vof/interface_tracking_utils.h b/src/kalypsso/core/vof/interface_tracking_utils.h new file mode 100644 index 0000000..9ec5523 --- /dev/null +++ b/src/kalypsso/core/vof/interface_tracking_utils.h @@ -0,0 +1,598 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +/** + * \file interfacetracking_utils.h + * + * Define helper routines associated to equations (11) and (13) of the following reference + * Volume-of-Fluid Interface Tracking with Smoothed Surface Stress Methods for Three-Dimensional + * Flows, Gueyffier et al, JCP 152, (1999) 423-456. + * https://doi.org/10.1006/jcph.1998.6168 + * + * This article describes how to compute area (2d) and volume (3d) of the intersection of o + * rectangular box (or mesh cell) with a half-space defined by cartesian inequation \f$ nx*x + ny*y + * + nz*z <= alpha\f$. This area (resp. volume) can be recasted/normalized to be a volume fraction. + * + * Here we define routine for + * - the forward evaluation : compute area/volume from the half-space inequation RHS (noted alpha in + * Gueffier) + * - the backward evaluation : compute half-space inequation RHS from the area/volume + */ +#ifndef KALYPSSO_CORE_INTERFACE_TRACKING_UTILS_H_ +#define KALYPSSO_CORE_INTERFACE_TRACKING_UTILS_H_ + +#include +#include +#include +#include +#include + +namespace kalypsso +{ + +namespace vof +{ + +// ================================================================================================ +// ================================================================================================ +// ================================================================================================ +// +// Forward evaluation of equation (11) and (13) of Gueyffier et al +// +// ================================================================================================ +// ================================================================================================ +// ================================================================================================ + + +// ======================================================================================= +// ======================================================================================= +/** + * Compute the volume fraction of a unit cube (centered at origin) intersected by a half-space of + * equation (nx*x + ny*y + nz*z >= alpha). + * + * the normal vector n (nx, ny, nz) doesn't need to be a unit vector + * + * Given a planar interface between 2 regions (labelled "0" and "1"), and defining + * n (nx,ny,nz) as the normal vector oriented from 0 (inside) to 1 (outside), compute the volume + * fraction of a cube overlapped by region 0. + * + * ____________ + * 0.5 | \ | + * | \ 1 | + * | \ | + * | 0 \ | + * | \| + * | | + * -0.5 |___________| + * + * -0.5 0.5 + * + * Reference: + * Volume-of-Fluid Interface Tracking with Smoothed Surface Stress Methods for Three-Dimensional + * Flows, Gueyffier et al, JCP 152, (1999) 423-456. + * https://doi.org/10.1006/jcph.1998.6168 + * + * See Fig 1 (2d case) and Fig 2. (3d case). + */ +template +KOKKOS_INLINE_FUNCTION real_t +compute_volume_fraction_of_unit_cube_below_plane(Kokkos::Array normal, real_t alpha) +{ + if constexpr (dim == 2) + { + auto & nx = normal[IX]; + auto & ny = normal[IY]; + + real_t area; + + // apply change of frame (translation) + // so that origin is the lower left corner of the unit cube, i.e. + // x' = x + 1/2 + // y' = y + 1/2 + alpha += (nx + ny) / KALYPSSO_NUM(2.0); + + // apply symmetry x' = 1 - x + if (nx < KALYPSSO_NUM(0.0)) + { + alpha -= nx; + nx = -nx; + } + + // apply symmetry y' = 1 - y + if (ny < KALYPSSO_NUM(0.0)) + { + alpha -= ny; + ny = -ny; + } + + // now we are left with the canonical situation where both nx and ny are positive or zero + + const auto na = min(nx, ny); + const auto nb = max(nx, ny); + + // Formulations derived from eq 11 of Gueyffier et al. rewritten to limit floating point + // operations imprecisions + + // alpha negative means the line is below the unit square lower left corner, so no intersection + // the lower left half space + if (alpha <= KALYPSSO_NUM(0.0)) + { + area = KALYPSSO_NUM(0.0); + } + // alpha smaller that both of the normal coordinates, the line crosses the left and bottom + // sides, creating a triangle + else if (KALYPSSO_NUM(0.0) <= alpha && alpha <= na) + { + area = alpha * alpha / (2 * nx * ny); + } + // alpha between the normal coordinates, the line crosses either top-bottom (ny < nx) or + // left-right (ny > nx) creating a trapezoid + else if (na <= alpha && alpha <= nb) + { + area = (alpha - 0.5 * na) / nb; + } + // alpha larger than the largest normal coordinate but smaller than the sum, the line crosses + // the top and right sides, creating a 5-sided polygon (whose complementary is a triangle) + else if (nb <= alpha && alpha <= nx + ny) + { + area = KALYPSSO_NUM(1.0) - (nx + ny - alpha) * (nx + ny - alpha) / (2 * nx * ny); + } + // alpha larger than nx+ny means the line is above the unit square upper right corner, so the + // unit square is fully contained in the lower left half space + else + { + area = KALYPSSO_NUM(1.0); + } + + // return volume fraction (between 0 and 1) + return clamp(area, KALYPSSO_NUM(0.0), KALYPSSO_NUM(1.0)); + } + else if constexpr (dim == 3) + { + // see equation (13) in Gueyffier et al. + + auto & nx = normal[IX]; + auto & ny = normal[IY]; + auto & nz = normal[IZ]; + + // apply change of frame (translation) + // so that origin is the lower left corner of the unit cube, i.e. + // x' = x + 1/2 + // y' = y + 1/2 + // apply symmetries: + // x-> 1-x + // y-> 1-y + // z-> 1-z + real_t al = alpha + (nx + ny + nz) / KALYPSSO_NUM(2.0) + max(KALYPSSO_NUM(0.0), -nx) + + max(KALYPSSO_NUM(0.0), -ny) + max(KALYPSSO_NUM(0.0), -nz); + + // no intersection at all + if (al <= KALYPSSO_NUM(0.0)) + return KALYPSSO_NUM(0.0); + + // maximum value of nx*x+ny*y+nz*z inside the unit cube + // noted alpha_max in Gueyffier et al. + real_t tmp = fabs(nx) + fabs(ny) + fabs(nz); + if (al >= tmp) + return KALYPSSO_NUM(1.0); + if (tmp < KALYPSSO_NUM(1e-10)) + return KALYPSSO_NUM(0.0); + + real_t n1 = fabs(nx) / tmp; + real_t n2 = fabs(ny) / tmp; + real_t n3 = fabs(nz) / tmp; + al = max(KALYPSSO_NUM(0.0), min(KALYPSSO_NUM(1.0), al / tmp)); + + real_t al0 = min(al, KALYPSSO_NUM(1.0) - al); + real_t b1 = min(n1, n2); + real_t b3 = max(n1, n2); + real_t b2 = n3; + + // apply circular permutation to make sure to end in canonical situation where + // b1 <= b2 <= b3 + if (b2 < b1) + { + tmp = b1; + b1 = b2; + b2 = tmp; + } + else if (b2 > b3) + { + tmp = b3; + b3 = b2; + b2 = tmp; + } + + real_t b12 = b1 + b2; + real_t bm = min(b12, b3); +#ifdef KALYPSSO_CORE_USE_DOUBLE + real_t pr = max(KALYPSSO_NUM(6.0) * b1 * b2 * b3, KALYPSSO_NUM(1e-50)); +#else + real_t pr = max(KALYPSSO_NUM(6.0) * b1 * b2 * b3, KALYPSSO_NUM(1e-20)); +#endif + + // develop equation (13) in Gueyffier et al. + + if (al0 < b1) + { + // case 1 + tmp = al0 * al0 * al0 / pr; + } + else if (al0 < b2) + { + // case 2 + tmp = KALYPSSO_NUM(0.5) * al0 * (al0 - b1) / (b2 * b3) + b1 * b1 * b1 / pr; + } + else if (al0 < bm) + { + // case 3 + + // clang-format off + tmp = (al0 * al0 * (KALYPSSO_NUM(3.0) * b12 - al0) + + b1 * b1 * (b1 - KALYPSSO_NUM(3.0) * al0) + + b2 * b2 * (b2 - KALYPSSO_NUM(3.0) * al0)) / + pr; + // clang-format on + } + else if (b12 < b3) // we also have al0 >= bm (= b1+b2) + { + // case 4 + tmp = (al0 - KALYPSSO_NUM(0.5) * bm) / b3; + } + else if (al0 >= KALYPSSO_NUM(0.5) /*and b2 >= 0.5 and b3 >= 0.5*/) + { + // we have b1+b2 >= b3 = 1 - (b1+b2) + // thus al0 >= b1+b2 >= 0.5 + // and actually al0 = 0.5 (can't be strictly larger than 0.5) + // in that case, the plane is passing through the center of the unit cube + + // case 5 + tmp = 0.5; + } + else + { + // last case + // clang-format off + tmp = (al0 * al0 * (KALYPSSO_NUM(3.0) - KALYPSSO_NUM(2.0) * al0) + + b1 * b1 * (b1 - KALYPSSO_NUM(3.0) * al0) + + b2 * b2 * (b2 - KALYPSSO_NUM(3.0) * al0) + + b3 * b3 * (b3 - KALYPSSO_NUM(3.0) * al0)) / + pr; + // clang-format on + } + + real_t volume = al <= KALYPSSO_NUM(0.5) ? tmp : KALYPSSO_NUM(1.0) - tmp; + + return clamp(volume, KALYPSSO_NUM(0.0), KALYPSSO_NUM(1.0)); + } +} // compute_volume_fraction_of_unit_cube_below_plane + +// ======================================================================================= +// ======================================================================================= +/** + * Compute the volume fraction of a rectangle (2D or 3D) intersected by a semi-space of + * equation (nx*x + ny*y + nz*z >= alpha). + * + * the normal vector n (nx, ny, nz) doesn't need to be a unit vector + * + * Given a planar interface between 2 regions (labelled "0" and "1"), and defining + * n (nx,ny,nz) as the normal vector oriented from 0 (inside) to 1 (outside), compute the volume + * fraction of a rectangular prism overlapped by region 0. + * + * ____________ + * y1 | \ | + * | \ 1 | + * | \ | + * | 0 \ | + * | \| + * | | + * y0 |___________| + * + * x0 x1 + * + * This transforms the volume into its unit counterpart and then calls + * `compute_volume_fraction_of_unit_cube_below_plane` + */ +template +KOKKOS_INLINE_FUNCTION real_t +compute_volume_fraction_of_rect_below_plane(Kokkos::Array normal, + real_t alpha, + Kokkos::Array p0, + Kokkos::Array p1) +{ + // We want x', y' and z' to vary between -0.5 and 0.5 such that nx'*x' + ny'*y' + nz'*z' = alpha' + // corresponds to the same semi-space as nx*x + ny*y + nz*z >= alpha but translated and properly + // scaled. + + // We first translate it so that each coordinate c varies between -(c1 - c0)/2 and (c1 - c0)/2 + const auto translation = (p0 + p1) * 0.5; + for (uint i = 0; i < dim; i++) + alpha -= normal[i] * translation[i]; + + // We then rescale the normal so that each coordinate varies between -0.5 and 0.5 + const auto scale = p1 - p0; + for (uint i = 0; i < dim; i++) + normal[i] *= scale[i]; + + // Then we compute + return compute_volume_fraction_of_unit_cube_below_plane(normal, alpha); + + // Because we compute a volume fraction, there are no extra computations to be done. +} // compute_volume_fraction_of_rect_below_plane + +// ================================================================================================ +// ================================================================================================ +// ================================================================================================ +// +// Backward evaluation of equation (11) and (13) of Gueyffier et al +// +// ================================================================================================ +// ================================================================================================ +// ================================================================================================ + +// ================================================================================================ +// PLANE FINDER +// ================================================================================================ + +#define KALYPSSO_YOUNGS_NEWTON_MAX_ITERATIONS 50 +#define KALYPSSO_YOUNGS_NEWTON_PRECISION FUZZY_THRESHOLD_F + +/** + * \brief Finds the value of C (right-hand-side of plane inequation) in the pentagonal case using + * Newton's method (solution of eq. (13)) + */ +KOKKOS_FUNCTION real_t +compute_plane_rhs_pentagonal_newton(const real_t rhs, + real_t guess, + const Kokkos::Array normal); +/** + * \brief Finds the value of C (right-hand-side of plane inequation) in the hexagonal case using + * Newton's method (solution of eq. (13)) + */ +KOKKOS_FUNCTION real_t +compute_plane_rhs_hexagonal_newton(const real_t rhs, + real_t guess, + const Kokkos::Array normal); + +/** + * \brief Computes C such that half-space nx.x + ny.y + nz.z <= C intersected with a unit cube gives + * a volume of (volumic fraction) alpha. + * + * \param[in] alpha volume fraction (of the upwind material to the interface, opposite direction of + * the normal vector) + * \param[in] normal Interface unit normal vector + * + * / + * +------------x-----+ + * | / | + * | n _ / | + * | |\ / | + * | X | + * | / alpha | + * | / | + * | / | + * +----x-------------+ + * / + */ +template +KOKKOS_FUNCTION real_t +compute_plane_rhs(const real_t alpha, const Kokkos::Array normal) +{ + static constexpr int sdim = static_cast(dim); + + // If the normal is null, it is impossible to compute C + if constexpr (dim == 2) + { + if (ISFUZZYNULL(normal[0]) && ISFUZZYNULL(normal[1])) + return 0.; + } + else if constexpr (dim == 3) + { + if (ISFUZZYNULL(normal[0]) && ISFUZZYNULL(normal[1]) && ISFUZZYNULL(normal[2])) + return 0.; + } + + // Values on which the computation will be done + real_t alpha_ = alpha; + Kokkos::Array normal_ = normal; + Kokkos::Array syms = init_kokkos_array(false); + + // Changing alpha_ and normal_ so we work in an easier state (alpha' < 1/2 and 0 < nx' < ny' < + // nz') + { + // Removing half of possible cases + if (alpha > KALYPSSO_NUM(0.5)) + { + alpha_ = ONE_F - alpha_; + normal_ = -ONE_F * normal_; + } + + // Symmetries along axes + for (int i = 0; i < sdim; i++) + if (normal_[i] < 0) + { + syms[i] = true; + normal_[i] *= -1; + } + + // Symmetries along diagonals to get the proper axis order + if constexpr (dim == 2) + { + const int i_min = normal_[0] < normal_[1] ? 0 : 1; + + normal_ = { normal_[i_min], normal_[1 - i_min] }; + syms = { syms[i_min], syms[1 - i_min] }; + } + else if constexpr (dim == 3) + { + const int i_min = normal_[0] < normal_[1] ? (normal_[0] < normal_[2] ? 0 : 2) + : (normal_[1] < normal_[2] ? 1 : 2); + const int i0 = (i_min + 1) % sdim; + const int i1 = (i_min + 2) % sdim; + const int i_max = normal_[i0] > normal_[i1] ? i0 : i1; + + normal_ = { normal_[i_min], normal_[3 - i_min - i_max], normal_[i_max] }; + syms = { syms[i_min], syms[3 - i_min - i_max], syms[i_max] }; + } + } + + // We now have an easier state to work with and can now properly compute C + // This is done by checking thresholds indicating in which case we are located, as defined in: + // Volume-of-Fluid Interface Tracking with Smoothed Surface Stress Methods for Three-Dimensional + // Flows, Gueyffier et al, JCP 152, (1999) 423-456. + // https://doi.org/10.1006/jcph.1998.6168 + real_t C; + + if constexpr (dim == 2) // 2D cases + { + const auto [nx, ny] = normal_; + + /** + * CASES: + * + * +-------+ +-------+ +-------+ + * | | | | ¨-_ | + * ----------- -_ | | ¨-_ | + * | | | ¨-_ | | ¨-_ + * +-------+ +----¨--+ +-------+ + * horizontal triangular trapezoid + */ + + // Everything is in relation with eq. (11) + const real_t rhs_area = 2 * nx * ny * alpha_; // Right hand side + const real_t lhs_tri_lim = nx * nx; // Max value of left hand side to be in the triangular case + + // clang-format off + if (ISFUZZYNULL(nx)) // horizontal interface case + C = ny * alpha_; // + else if (rhs_area < lhs_tri_lim) // triangular case + C = sqrt(rhs_area); // + else // trapezoid case + C = nx * HALF_F + ny * alpha_; // + // no extra cases because the pentagon case is computed + // only if alpha > 0.5, and the vertical interface is + // impossible (the normal is not null and ny >= nx > 0) + // clang-format on + } + else if constexpr (dim == 3) // 3D cases, see fig. 3 and 4 + { + const auto [nx, ny, nz] = normal_; + + /** + * CASES: + * + * triangular: quadrilateral A: pentagonal: + * pre fig. 3-a between fig. 3-a and 3-b between fig. 3-b and 3-c + * pre fig. 4-a between fig. 4-a and 4-b between fig. 4-b and 4-c + * + * hexagonal: quadrilateral B: + * between fig. 4-c and 4-d between fig. 3-c and 3-d + * + * 2D cases (degenerate cases): + * nx = 0 + */ + + // Everything is in relation with eq. (13) + const real_t rhs_area = 6 * nx * ny * nz * alpha_; // Right hand side + const real_t lhs_tri_lim = nx * nx * nx; // Max value of left hand side to be in triangular case + const real_t lhs_quadA_lim = // Max value of left hand side to be in quadrilateral A case + ny * ny * ny - (ny - nx) * (ny - nx) * (ny - nx); + + // clang-format off + if (ISFUZZYNULL(nx)) // 2D degenerate cases + { // + // Reduced to 2D cases // + const real_t rhs_area_2d = 2 * ny * nz * alpha_; // + const real_t lhs_tri_lim_2d = ny * ny; // + // + if (ISFUZZYNULL(ny)) // 2D horizontal case + C = nz * alpha_; // + else if (rhs_area_2d < lhs_tri_lim_2d) // 2D triangular case + C = sqrt(rhs_area_2d); // + else // 2D trapezoid case + C = ny * HALF_F + nz * alpha_; // + } // + else if (rhs_area < lhs_tri_lim) // triangular case + C = pow(rhs_area, ONE_THIRD_F); // + else if (rhs_area < lhs_quadA_lim) // quadrilateral A case + { // + // This is computable by finding the area using 2 methods: // + // - using the normal // + // - using the normal with nx = 0 // + // and solving for C // + C = (3 * nx + sqrt(72 * ny * nz * alpha_ - 3 * nx * nx)) / KALYPSSO_NUM(6.0); // + } // + else if (nx + ny < nz) // fig. 3 extra cases + { // + const real_t C_quadB_lim = nx + ny; // Limit to be in quadB case // + C = nx * HALF_F + ny * HALF_F + nz * alpha_; // quadrilateral B case + // + if (C < C_quadB_lim) // pentagonal case + C = compute_plane_rhs_pentagonal_newton(rhs_area, nx + ny, normal_); // + } // + else // fig. 4 extra cases + { // + const real_t lhs_pent4_lim = // Limit to be in pentagonal case // + nz * nz * nz - (nz - nx) * (nz - nx) * (nz - nx) // + - (nz - ny) * (nz - ny) * (nz - ny); // + // + if (rhs_area < lhs_pent4_lim) // pentagonal case + C = compute_plane_rhs_pentagonal_newton(rhs_area, nz, normal_); // + else // hexagonal case + C = compute_plane_rhs_hexagonal_newton(rhs_area, (nx + ny + nz) / KALYPSSO_NUM(3.0), normal_); // + } // + // clang-format on + } + + // We revert the changes of the final value in reverse order + { + // Symmetries along the diagonal do not change C + + // Symmetries along an axis change C + for (int i = 0; i < sdim; i++) + if (syms[i]) + C -= normal_[i]; + + // If we computed the other side, we need to revert it as well + if (alpha > KALYPSSO_NUM(0.5)) + C *= -ONE_F; + } + + return C; + +} // compute_plane_rhs + +/** + * \brief Computes C such that nx.x + ny.y + nz.z <= C intersected with a rectangular prism gives a + * fractional volume of alpha + * + * / + * +------------x-----+ < + * | / | + * | n _ / | + * | |\ / | + * | X | d[1] + * | / alpha | + * | / | + * | / | + * +----x-------------+ < + * / + * ^ d[0] ^ + */ +template +KOKKOS_INLINE_FUNCTION real_t +compute_plane_rhs(const real_t alpha, + const Kokkos::Array normal, + const Kokkos::Array d) +{ + // We want x, y and z to vary from 0 to 1 instead of 0 to dx, dy or dz. To change that without + // modifying C, one can simply multiply each coordinate of the normal with the cell size. + // nx.x + ny.y + nz.z = nx'.x' + ny'.y' + nz'.z' = nx.dx.x' + ny.dy.y' + nz.dz.z' + return compute_plane_rhs(alpha, normal * d); +} + +} // namespace vof + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_INTERFACE_TRACKING_UTILS_H_ diff --git a/src/kalypsso/core/vof/youngs.h b/src/kalypsso/core/vof/youngs.h new file mode 100644 index 0000000..1a60557 --- /dev/null +++ b/src/kalypsso/core/vof/youngs.h @@ -0,0 +1,389 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file youngs.h + * + * \brief Collection of methods to help in computing youngs interface reconstruction + * + * Reference: + * - An interface tracking method for a 3D Eulerian hydrodynamics code, D. L. Youngs, AWRE technical + * report /44/92/35. + * https://www.researchgate.net/publication/245345562_An_interface_tracking_method_for_a_3D_Eulerian_hydrodynamics_code + */ + +#ifndef KALYPSSO_CORE_VOF_YOUNGS_H_ +#define KALYPSSO_CORE_VOF_YOUNGS_H_ + +#include +#include +#include +#include + +#include + +namespace kalypsso +{ + +namespace vof +{ + +// ================================================================================================ +// ================================================================================================ +// NORMAL +// ================================================================================================ +// ================================================================================================ + +/** + * \brief Indicates the list of neighbors in a 3x3 or 3x3x3 stencil used in normal computation + */ +template +using NormalNeighbors = Kokkos::Array; + +/** + * \brief Computes the interface normal vector from a size-3 stencil (9 cells in 2D or 27 in 3D) + * using (Parker-)Youngs method. + * + * \param f The volume fraction values of current cell and its neighbors listed in a X - Y - Z order + * \param dxyz Vector of cell sizes + * \param ex Optional extra arguments that influence the values over faces or edges + * + * +------+------+------+ + * | | | | + * | f[6] | f[7] | f[8] | + * | | | | + * +------+------+------+ + * | | | | + * | f[3] | f[4] | f[5] | + * | | | | + * +------+------+------+ < y + * | | | | ^ + * | f[0] | f[1] | f[2] | d[1] | + * | | | | | + * +------+------+------+ < +----> x + * ^ d[0] ^ + * + * References: + * + * - An interface tracking method for a 3D Eulerian hydrodynamics code, D. L. Youngs, AWRE technical + * report /44/92/35. + * +https://www.researchgate.net/publication/245345562_An_interface_tracking_method_for_a_3D_Eulerian_hydrodynamics_code + * here is explained how parameter alpha (2D) and beta/gamma (3D) should be chosen. + * + * - Second order accurate volume-of-fluid algorithms for tracking material interfaces, J.E. Pilliod + * and E. G. Puckett, JCP, vol 199, 2 (2004), pp. 465-502. + * https://doi.org/10.1016/j.jcp.2003.12.023. + * See section 2.4 + * + * - Volume of fluid interface reconstruction methods for multi-material problems, D. J. Benson, + * Appl. Mech. Rev. Mar 2002, 55(2): 151-165 (15 pages) + * https://doi.org/10.1115/1.1448524 + * See section 5.2 + * + * - Interface reconstruction with least-square fit and split Eulerian–Lagrangian advection, R. + * Scardovelli and S. Zaleski, INTERNATIONAL JOURNAL FOR NUMERICAL METHODS IN FLUIDS Int. J. +Numer. + * Meth. Fluids 2003; 41:251–274. https://doi.org/10.1002/fld.431 + * See section 2.1 + */ +template +KOKKOS_FUNCTION Kokkos::Array + youngs_normal(const NormalNeighbors & f, const Kokkos::Array dxyz) +{ + Kokkos::Array normal; + + if constexpr (dim == 2) + { + static constexpr real_t alpha = KALYPSSO_NUM(2.0); + const auto [dx, dy] = dxyz; + const real_t fact = ONE_F / (KALYPSSO_NUM(2.0) + alpha); + + const real_t f_east = fact * (f[2] + alpha * f[5] + f[8]); + const real_t f_west = fact * (f[0] + alpha * f[3] + f[6]); + + const real_t f_north = fact * (f[6] + alpha * f[7] + f[8]); + const real_t f_south = fact * (f[0] + alpha * f[1] + f[2]); + + normal = { -(f_east - f_west) / (2 * dx), -(f_north - f_south) / (2 * dy) }; + } + else if constexpr (dim == 3) + { + static constexpr real_t beta = KALYPSSO_NUM(2.0); + static constexpr real_t gamma = KALYPSSO_NUM(4.0); + const auto [dx, dy, dz] = dxyz; + const real_t fact = ONE_F / (FOUR_F + FOUR_F * beta + gamma); + + // clang-format off + const real_t f_east = fact * + (f[2] + f[8] + f[20] + f[26] + beta * (f[5] + f[11] + f[17] + f[23]) + gamma * f[14]); + const real_t f_west = fact * + (f[0] + f[6] + f[18] + f[24] + beta * (f[3] + f[9] + f[15] + f[21]) + gamma * f[12]); + + const real_t f_north = fact * + (f[6] + f[8] + f[24] + f[26] + beta * (f[7] + f[15] + f[17] + f[25]) + gamma * f[16]); + const real_t f_south = fact * + (f[0] + f[2] + f[18] + f[20] + beta * (f[1] + f[9] + f[11] + f[19]) + gamma * f[10]); + + const real_t f_top = fact * + (f[18] + f[20] + f[24] + f[26] + beta * (f[19] + f[21] + f[23] + f[25]) + gamma * f[22]); + const real_t f_bottom = fact * + (f[0] + f[2] + f[6] + f[8] + beta * (f[1] + f[3] + f[5] + f[7]) + gamma * f[4]); + // clang-format on + + normal = { -(f_east - f_west) / (2 * dx), + -(f_north - f_south) / (2 * dy), + -(f_top - f_bottom) / (2 * dz) }; + } + + auto norm = normal[IX] * normal[IX] + normal[IY] * normal[IY]; + if constexpr (dim == 3) + norm += normal[IZ] * normal[IZ]; + norm = sqrt(norm); + + if (norm < FUZZY_THRESHOLD_F) + return init_kokkos_array(0); + else + normal = normal / norm; + + for (uint i = 0; i < dim; i++) + { + if (abs(normal[i]) < FUZZY_THRESHOLD_F) + normal[i] = 0.; + else if (abs(normal[i]) > KALYPSSO_NUM(1.0) - FUZZY_THRESHOLD_F) + { + const real_t dir = normal[i] >= 0 ? 1. : -1.; + normal = init_kokkos_array(0); + normal[i] = dir; + } + } + + return normal; +} + +/** + * \brief Computes the Youngs normal from inside a DataArrayBlock-like object + */ +template +KOKKOS_FUNCTION Kokkos::Array + youngs_normal(const DAB & f, + const coord_t ijk, + const int i_var, + const int i_oct, + const Kokkos::Array dxyz) +{ + NormalNeighbors n; + + if constexpr (dim == 2) + { + static constexpr coord_t ix = { 1, 0 }; + static constexpr coord_t iy = { 0, 1 }; + + for (int v = 0; v < static_cast(n.size()); v++) + { + const auto ijk2 = ijk + (v % 3 - 1) * ix + (v / 3 - 1) * iy; + n[v] = f(ijk2, i_var, i_oct); + } + } + else if constexpr (dim == 3) + { + static constexpr coord_t ix = { 1, 0, 0 }; + static constexpr coord_t iy = { 0, 1, 0 }; + static constexpr coord_t iz = { 0, 0, 1 }; + + for (int v = 0; v < static_cast(n.size()); v++) + { + const auto ijk2 = ijk + (v % 3 - 1) * ix + ((v / 3) % 3 - 1) * iy + (v / 9 - 1) * iz; + n[v] = f(ijk2, i_var, i_oct); + } + } + + return youngs_normal(n, dxyz); + +} // youngs_normal + +/** + * \brief Computes the Youngs normal from inside a DataArrayBlock-like object (in-place + * implementation). + */ +template +KOKKOS_FUNCTION Kokkos::Array + youngs_normal_inplace(const DAB & f, + const coord_t ijk, + const int i_var, + const int i_oct, + const Kokkos::Array dxyz) +{ + Kokkos::Array normal; + + if constexpr (dim == 2) + { + static constexpr real_t alpha = KALYPSSO_NUM(2.0); + const auto [dx, dy] = dxyz; + const auto [i, j] = ijk; + const real_t fact = ONE_F / (KALYPSSO_NUM(2.0) + alpha); + +#define F(a, b) f(i + a, j + b, i_var, i_oct) + + // clang-format off + const real_t f_east = fact * (F( 1, -1) + alpha * F( 1, 0) + F( 1, 1)); + const real_t f_west = fact * (F(-1, -1) + alpha * F(-1, 0) + F(-1, 1)); + + const real_t f_north = fact * (F(-1, 1) + alpha * F(0, 1) + F(1, 1)); + const real_t f_south = fact * (F(-1, -1) + alpha * F(0, -1) + F(1, -1)); + // clang-format on + +#undef F + + normal = { -(f_west - f_east) / (2 * dx), -(f_north - f_south) / (2 * dy) }; + } + else if constexpr (dim == 3) + { + static constexpr real_t beta = KALYPSSO_NUM(2.0); + static constexpr real_t gamma = KALYPSSO_NUM(4.0); + const auto [dx, dy, dz] = dxyz; + const auto [i, j, k] = ijk; + const real_t fact = ONE_F / (FOUR_F + FOUR_F * beta + gamma); + +#define F(a, b, c) f(i + a, j + b, k + c, i_var, i_oct) + // clang-format off + const real_t f_east = + fact * ( F(+1, -1, -1) + F(+1, +1, -1) + F(+1, -1, +1) + F(+1, +1, +1) + + beta * (F(+1, +0, -1) + F(+1, -1, +0) + F(+1, +1, +0) + F(+1, +0, +1)) + + gamma * F(+1, +0, +0)); + const real_t f_west = + fact * ( F(-1, -1, -1) + F(-1, +1, -1) + F(-1, -1, +1) + F(-1, +1, +1) + + beta * (F(-1, +0, -1) + F(-1, -1, +0) + F(-1, +1, +0) + F(-1, +0, +1)) + + gamma * F(-1, +0, +0)); + + const real_t f_north = + fact * ( F(-1, +1, -1) + F(+1, +1, -1) + F(-1, +1, +1) + F(+1, +1, +1) + + beta * (F(+0, +1, -1) + F(-1, +1, +0) + F(+1, +1, +0) + F(+0, +1, +1)) + + gamma * F(+0, +1, +0)); + const real_t f_south = + fact * ( F(-1, -1, -1) + F(+1, -1, -1) + F(-1, -1, +1) + F(+1, -1, +1) + + beta * (F(+0, -1, -1) + F(-1, -1, +0) + F(+1, -1, +0) + F(+0, -1, +1)) + + gamma * F(+0, -1, 0)); + + const real_t f_top = + fact * ( F(-1, -1, +1) + F(+1, -1, +1) + F(-1, +1, +1) + F(+1, +1, +1) + + beta * (F(+0, -1, +1) + F(-1, +0, +1) + F(+1, +0, +1) + F(+0, +1, +1)) + + gamma * F(+0, +0, +1)); + const real_t f_bottom = + fact * ( F(-1, -1, -1) + F(+1, -1, -1) + F(-1, +1, -1) + F(+1, +1, -1) + + beta * (F(+0, -1, -1) + F(-1, +0, -1) + F(+1, +0, -1) + F(+0, +1, -1)) + + gamma * F(+0, +0, -1)); + // clang-format on +#undef F + + // clang-format off + normal = { -(f_east - f_west) / (2 * dx), + -(f_north - f_south) / (2 * dy), + -(f_top - f_bottom) / (2 * dz) }; + // clang-format on + } + + auto norm = normal[IX] * normal[IX] + normal[IY] * normal[IY]; + if constexpr (dim == 3) + norm += normal[IZ] * normal[IZ]; + norm = sqrt(norm); + + if (norm < FUZZY_THRESHOLD_F) + return init_kokkos_array(0); + else + normal = normal / norm; + + for (uint i = 0; i < dim; i++) + { + if (abs(normal[i]) < FUZZY_THRESHOLD_F) + normal[i] = 0.; + else if (abs(normal[i]) > KALYPSSO_NUM(1.0) - FUZZY_THRESHOLD_F) + { + const real_t dir = normal[i] >= 0 ? 1. : -1.; + normal = init_kokkos_array(0); + normal[i] = dir; + } + } + + return normal; + +} // youngs_normal_inplace + +// ================================================================================================ +// ================================================================================================ +// VOLUME ADVECTION +// ================================================================================================ +// ================================================================================================ + +/** + * \brief Returns the volume fraction of the advected volume that gets actually advected though the + * interface + * + * / + * +---------------+-x-----+ < +---------------+-------+ < < + * | |/ | | | | gamma * d[1] + * | n _ / | | +---------------+ | < + * | |\ /| | | | | | | + * | X | d[1] | | alpha | | d[1] + * | / | | | | | | | + * | / | | +---------------+ | + * | / | | | | | + * +---------x-----+-------+ < +---------------+-------+ < + * / ^ d[0] ^ + * ^ d[0] ^ ^ ^ gamma * d[0] + */ +template +KOKKOS_FUNCTION real_t +youngs_advect(const real_t alpha, + const real_t C, + const Kokkos::Array normal, + const Kokkos::Array d, + const ComponentIndex3D dir, + const bool right_side, + const real_t full_advected_fraction) +{ + if (ISFUZZYNULL(full_advected_fraction) || ISFUZZYNULL(alpha)) // No volume is actually advected + return 0.; + + const bool is_null = [&normal]() { + if constexpr (dim == 2) + return ISFUZZYNULL(normal[0]) && ISFUZZYNULL(normal[1]); + else if constexpr (dim == 3) + return ISFUZZYNULL(normal[0]) && ISFUZZYNULL(normal[1]) && ISFUZZYNULL(normal[2]); + }(); + + if (is_null) + { + // Case normal is null. If the volume is scaled down as a unit cube, then the material's volume + // would be a smaller unit cube 'gamma' away from the cell's border. + const real_t gamma = HALF_F * (ONE_F - pow(alpha, ONE_F / dim)); + const real_t frac = ONE_F / full_advected_fraction; + + if (full_advected_fraction < gamma) // No intersection + return 0.; + else if (1 - full_advected_fraction < gamma) // Entirely inside + return alpha * frac; + else // Intersection + { + if constexpr (dim == 2) + return (1 - 2 * gamma) * (full_advected_fraction - gamma) * frac; + else if constexpr (dim == 3) + return (1 - 2 * gamma) * (1 - 2 * gamma) * (full_advected_fraction - gamma) * frac; + } + } + + Kokkos::Array p_bottom_left = {}; + Kokkos::Array p_top_right = d; + p_bottom_left[dir] = d[dir] * (right_side ? ONE_F - full_advected_fraction : ZERO_F); + p_top_right[dir] = d[dir] * (right_side ? ONE_F : full_advected_fraction); + + return compute_volume_fraction_of_rect_below_plane(normal, C, p_bottom_left, p_top_right); + +} // youngs_advect + +} // namespace vof + +} // namespace kalypsso + +#endif // KALYPSSO_CORE_VOF_YOUNGS_H_ diff --git a/src/kalypsso/utils/CMakeLists.txt b/src/kalypsso/utils/CMakeLists.txt new file mode 100644 index 0000000..c125f9b --- /dev/null +++ b/src/kalypsso/utils/CMakeLists.txt @@ -0,0 +1,6 @@ +add_subdirectory(config) +add_subdirectory(monitoring) +add_subdirectory(io) +add_subdirectory(log) +add_subdirectory(p4est) +add_subdirectory(mpi) diff --git a/src/kalypsso/utils/config/CMakeLists.txt b/src/kalypsso/utils/config/CMakeLists.txt new file mode 100644 index 0000000..5b24f47 --- /dev/null +++ b/src/kalypsso/utils/config/CMakeLists.txt @@ -0,0 +1,42 @@ +add_library(kalypsso_config ConfigMap.cpp ConfigMap.h inih/ini.c inih/ini.h) + +target_include_directories( + kalypsso_config + PUBLIC $ + $ + $) + +target_link_libraries(kalypsso_config PUBLIC Kokkos::kokkos) +target_link_libraries(kalypsso_config PRIVATE kalypsso_warnings kalypsso_core_config) + +if(KALYPSSO_CORE_USE_MPI) + target_link_libraries(kalypsso_config PUBLIC MPI::MPI_CXX) +endif(KALYPSSO_CORE_USE_MPI) + +# Add an alias so that library can be used inside the build tree +add_library(kalypsso::config ALIAS kalypsso_config) + +# +# Get prepared for install +# +include(${PROJECT_SOURCE_DIR}/cmake/setup_install.cmake) + +# +# install libraries and headers +# +install( + TARGETS kalypsso_config + EXPORT kalypsso-targets + LIBRARY DESTINATION ${INSTALL_LIBDIR} + ARCHIVE DESTINATION ${INSTALL_LIBDIR} + RUNTIME DESTINATION ${INSTALL_BINDIR}) + +# +# install headers (exclude cpp files) +# +install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DESTINATION ${INSTALL_INCLUDEDIR}/kalypsso/utils + FILES_MATCHING + PATTERN "*.h" + PATTERN "*.cpp" EXCLUDE) diff --git a/src/kalypsso/utils/config/ConfigMap.cpp b/src/kalypsso/utils/config/ConfigMap.cpp new file mode 100644 index 0000000..ec5b7f5 --- /dev/null +++ b/src/kalypsso/utils/config/ConfigMap.cpp @@ -0,0 +1,669 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ConfigMap.cpp + * \brief Implement ConfigMap, essentially a INIReader with additional get methods. + * + */ +#include "ConfigMap.h" +#include // for strtof +#include +#include +#include +#include // for std::transform +#include // for std::accumulate +#include +#include "ini.h" + +#include // for std::tolower + +#ifdef KALYPSSO_CORE_USE_MPI +# include +#endif + +namespace kalypsso +{ + +// ======================================================= +// ======================================================= +ConfigMap::ConfigMap(std::string filename) +{ + m_parse_error = ini_parse(filename.c_str(), valueHandler, this); + + if (this->m_parse_error == -1) + { + printf("Error : can't read input parameter file \"%s\"\nPlease check file actually exists !\n", + filename.c_str()); + std::abort(); + } + +} // ConfigMap::ConfigMap + +// ======================================================= +// ======================================================= +ConfigMap::ConfigMap(char *& buffer, size_t buffer_size) +{ + m_parse_error = ini_parse_string_length(buffer, buffer_size, valueHandler, this); +} // ConfigMap::ConfigMap + +// ======================================================= +// ======================================================= +int +ConfigMap::parse_error() +{ + return m_parse_error; +} + +// ======================================================= +// ======================================================= +std::string +ConfigMap::getString(std::string section, std::string name, std::string default_value) const +{ + const std::string key = makeKey(section, name); + // for const correctness use method at instead of operator[] + return m_values.count(key) ? m_values.at(key) : default_value; +} + +// ======================================================= +// ======================================================= +void +ConfigMap::setString(std::string section, std::string name, std::string value) +{ + std::string key = makeKey(section, name); + m_values[key] = value; +} + +// ======================================================= +// ======================================================= +std::vector +ConfigMap::getStringVector(std::string section, + std::string name, + std::vector default_value) const +{ + const std::string key = makeKey(section, name); + + if (m_values.count(key)) + { + const auto tmp = m_values.at(key); + + // tokenize tmp using a regular expression + // delimiter is whitespace or comma + const std::regex re(R"([\s|,]+)"); + + return tokenize(tmp, re); + } + + return default_value; +} + +// ======================================================= +// ======================================================= +void +ConfigMap::setStringVector(std::string section, std::string name, std::vector value) +{ + std::string key = makeKey(section, name); + auto dash_fold = [](std::string a, std::string b) { return std::move(a) + ',' + b; }; + + m_values[key] = std::accumulate(std::next(value.begin()), value.end(), value[0], dash_fold); +} + +// ======================================================= +// ======================================================= +int +ConfigMap::getInteger(std::string section, std::string name, int default_value) const +{ + std::string valstr = getString(section, name, ""); + const char * value = valstr.c_str(); + char * end; + // This parses "1234" (decimal) and also "0x4D2" (hex) + int n = static_cast(strtol(value, &end, 0)); + if (errno == ERANGE) + { + std::cerr << "Unable to convert string \"" << valstr << "\" to integer" << "\n"; + Kokkos::abort("Invalid config map."); + } + return end > value ? n : default_value; +} + +// ======================================================= +// ======================================================= +void +ConfigMap::setInteger(std::string section, std::string name, int value) +{ + std::stringstream ss; + ss << value; + + setString(section, name, ss.str()); +} + +// ======================================================= +// ======================================================= +std::vector +ConfigMap::getIntegerVector(std::string section, + std::string name, + std::vector default_value) const +{ + auto str_vec = getStringVector(section, name, std::vector{}); + + if (str_vec.size() == 0) + { + return default_value; + } + + std::vector result; + result.reserve(str_vec.size()); + std::transform( + str_vec.begin(), str_vec.end(), std::back_inserter(result), [&default_value](const auto & str) { + const char * value = str.c_str(); + char * end; + // This parses "1234" (decimal) and also "0x4D2" (hex) + int n = static_cast(strtol(value, &end, 0)); + if (errno == ERANGE) + { + std::cerr << "Unable to convert string \"" << str << "\" to integer" << "\n"; + Kokkos::abort("Invalid config map."); + } + return end > value ? n : default_value[0]; + }); + + return result; +} + +// ======================================================= +// ======================================================= +void +ConfigMap::setIntegerVector(std::string section, std::string name, std::vector value) +{ + std::vector strVector; + strVector.reserve(value.size()); + std::transform(value.begin(), value.end(), std::back_inserter(strVector), [](const auto & val) { + std::stringstream ss; + ss << val; + return ss.str(); + }); + + setStringVector(section, name, strVector); +} + +// ======================================================= +// ======================================================= +int64_t +ConfigMap::getInteger64(std::string section, std::string name, int64_t default_value) const +{ + std::string valstr = getString(section, name, ""); + const char * value = valstr.c_str(); + char * end; + // This parses "1234" (decimal) and also "0x4D2" (hex) + int64_t n = static_cast(strtoll(value, &end, 0)); + if (errno == ERANGE) + { + std::cerr << "Unable to convert string \"" << valstr << "\" to integer (64 bits)" << "\n"; + Kokkos::abort("Invalid config map."); + } + return end > value ? n : default_value; +} + +// ======================================================= +// ======================================================= +void +ConfigMap::setInteger64(std::string section, std::string name, int64_t value) +{ + std::stringstream ss; + ss << value; + + setString(section, name, ss.str()); +} + +// ======================================================= +// ======================================================= +std::vector +ConfigMap::getInteger64Vector(std::string section, + std::string name, + std::vector default_value) const +{ + auto str_vec = getStringVector(section, name, std::vector{}); + + if (str_vec.size() == 0) + { + return default_value; + } + + std::vector result; + result.reserve(str_vec.size()); + std::transform( + str_vec.begin(), str_vec.end(), std::back_inserter(result), [&default_value](const auto & str) { + const char * value = str.c_str(); + char * end; + // This parses "1234" (decimal) and also "0x4D2" (hex) + int64_t n = static_cast(strtoll(value, &end, 0)); + if (errno == ERANGE) + { + std::cerr << "Unable to convert string \"" << str << "\" to 64 bits integer" << "\n"; + Kokkos::abort("Invalid config map."); + } + return end > value ? n : default_value[0]; + }); + + return result; +} + +// ======================================================= +// ======================================================= +void +ConfigMap::setInteger64Vector(std::string section, std::string name, std::vector value) +{ + std::vector strVector; + strVector.reserve(value.size()); + std::transform(value.begin(), value.end(), std::back_inserter(strVector), [](const auto & val) { + std::stringstream ss; + ss << val; + return ss.str(); + }); + + setStringVector(section, name, strVector); +} + +// ======================================================= +// ======================================================= +float +ConfigMap::getFloat(std::string section, std::string name, float default_value) const +{ + std::string valstr = getString(section, name, ""); + const char * value = valstr.c_str(); + char * end; + // This parses "1234" (decimal) and also "0x4D2" (hex) + float valFloat = strtof(value, &end); + if (errno == ERANGE) + { + std::cerr << "Unable to convert string \"" << valstr << "\" to float" << "\n"; + Kokkos::abort("Invalid config map."); + } + return end > value ? valFloat : default_value; +} // ConfigMap::getFloat + +// ======================================================= +// ======================================================= +void +ConfigMap::setFloat(std::string section, std::string name, float value) +{ + + std::stringstream ss; + ss << value; + + setString(section, name, ss.str()); + +} // ConfigMap::setFloat + +// ======================================================= +// ======================================================= +std::vector +ConfigMap::getFloatVector(std::string section, + std::string name, + std::vector default_value) const +{ + auto str_vec = getStringVector(section, name, std::vector{}); + + if (str_vec.size() == 0) + { + return default_value; + } + + std::vector result; + result.reserve(str_vec.size()); + std::transform( + str_vec.begin(), str_vec.end(), std::back_inserter(result), [&default_value](const auto & str) { + const char * value = str.c_str(); + char * end; + // This parses "1234" (decimal) and also "0x4D2" (hex) + float valf = strtof(value, &end); + if (errno == ERANGE) + { + std::cerr << "Unable to convert string \"" << str << "\" to float" << "\n"; + Kokkos::abort("Invalid config map."); + } + return end > value ? valf : default_value[0]; + }); + + return result; +} + +// ======================================================= +// ======================================================= +void +ConfigMap::setFloatVector(std::string section, std::string name, std::vector value) +{ + std::vector strVector; + strVector.reserve(value.size()); + std::transform(value.begin(), value.end(), std::back_inserter(strVector), [](const float & val) { + std::stringstream ss; + ss << val; + return ss.str(); + }); + + setStringVector(section, name, strVector); +} + +// ======================================================= +// ======================================================= +double +ConfigMap::getDouble(std::string section, std::string name, double default_value) const +{ + std::string valstr = getString(section, name, ""); + const char * value = valstr.c_str(); + char * end; + // This parses "1234" (decimal) and also "0x4D2" (hex) + double valDouble = strtod(value, &end); + if (errno == ERANGE) + { + std::cerr << "Unable to convert string \"" << valstr << "\" to double" << "\n"; + Kokkos::abort("Invalid config map."); + } + return end > value ? valDouble : default_value; +} // ConfigMap::getDouble + +// ======================================================= +// ======================================================= +void +ConfigMap::setDouble(std::string section, std::string name, double value) +{ + + std::stringstream ss; + ss << value; + + setString(section, name, ss.str()); + +} // ConfigMap::setDouble + +// ======================================================= +// ======================================================= +std::vector +ConfigMap::getDoubleVector(std::string section, + std::string name, + std::vector default_value) const +{ + auto str_vec = getStringVector(section, name, std::vector{}); + + if (str_vec.size() == 0) + { + return default_value; + } + + std::vector result; + result.reserve(str_vec.size()); + std::transform( + str_vec.begin(), str_vec.end(), std::back_inserter(result), [&default_value](const auto & str) { + const char * value = str.c_str(); + char * end; + // This parses "1234" (decimal) and also "0x4D2" (hex) + double valf = strtod(value, &end); + if (errno == ERANGE) + { + std::cerr << "Unable to convert string \"" << str << "\" to double" << "\n"; + Kokkos::abort("Invalid config map."); + } + return end > value ? valf : default_value[0]; + }); + + return result; +} + +// ======================================================= +// ======================================================= +void +ConfigMap::setDoubleVector(std::string section, std::string name, std::vector value) +{ + std::vector strVector; + strVector.reserve(value.size()); + std::transform(value.begin(), value.end(), std::back_inserter(strVector), [](const double & val) { + std::stringstream ss; + ss << val; + return ss.str(); + }); + + setStringVector(section, name, strVector); +} + +#ifdef KALYPSSO_CORE_USE_DOUBLE +// ======================================================= +// ======================================================= +double +ConfigMap::getReal(std::string section, std::string name, double default_value) const +{ + return getDouble(section, name, default_value); +} // ConfigMap::getReal +// ======================================================= +// ======================================================= +std::vector +ConfigMap::getRealVector(std::string section, + std::string name, + std::vector default_value) const +{ + return getDoubleVector(section, name, default_value); +} // ConfigMap::getRealVector +#else +// ======================================================= +// ======================================================= +float +ConfigMap::getReal(std::string section, std::string name, float default_value) const +{ + return getFloat(section, name, default_value); +} // ConfigMap::getReal +// ======================================================= +// ======================================================= +std::vector +ConfigMap::getRealVector(std::string section, + std::string name, + std::vector default_value) const +{ + return getFloatVector(section, name, default_value); +} // ConfigMap::getRealVector +#endif // KALYPSSO_CORE_USE_DOUBLE + +// ======================================================= +// ======================================================= +bool +ConfigMap::getBool(std::string section, std::string name, bool default_value) const +{ + bool val = default_value; + std::string valstr = getString(section, name, ""); + + if (!valstr.compare("1") or !valstr.compare("yes") or !valstr.compare("true") or + !valstr.compare("on")) + val = true; + if (!valstr.compare("0") or !valstr.compare("no") or !valstr.compare("false") or + !valstr.compare("off")) + val = false; + + // if valstr is empty, return the default value + if (!valstr.size()) + val = default_value; + + return val; + +} // ConfigMap::getBool + +// ======================================================= +// ======================================================= +void +ConfigMap::setBool(std::string section, std::string name, bool value) +{ + + if (value) + setString(section, name, "true"); + else + setString(section, name, "false"); + +} // ConfigMap::setBool + +// ======================================================= +// ======================================================= +std::vector +ConfigMap::getBoolVector(std::string section, + std::string name, + std::vector default_value) const +{ + auto str_vec = getStringVector(section, name, std::vector{}); + + if (str_vec.size() == 0) + { + return default_value; + } + + std::vector result; + result.reserve(str_vec.size()); + std::transform(str_vec.begin(), + str_vec.end(), + std::back_inserter(result), + [&default_value](const auto & valstr) { + bool val = default_value.size() > 0 ? default_value[0] : false; + if (!valstr.compare("1") or !valstr.compare("yes") or !valstr.compare("true") or + !valstr.compare("on")) + val = true; + if (!valstr.compare("0") or !valstr.compare("no") or !valstr.compare("false") or + !valstr.compare("off")) + val = false; + return val; + }); + + return result; +} + +// ======================================================= +// ======================================================= +void +ConfigMap::setBoolVector(std::string section, std::string name, std::vector value) +{ + std::vector strVector; + strVector.reserve(value.size()); + std::transform(value.begin(), value.end(), std::back_inserter(strVector), [](const bool & val) { + std::stringstream ss; + ss << (val ? "true" : "false"); + return ss.str(); + }); + + setStringVector(section, name, strVector); +} + +// ======================================================= +// ======================================================= +std::ostream & +operator<<(std::ostream & os, const ConfigMap & cfg) +{ + std::map::const_iterator it; + for (it = cfg.m_values.begin(); it != cfg.m_values.end(); it++) + { + os << (*it).first << " = " << (*it).second << "\n"; + } + return os; +} + +// ======================================================= +// ======================================================= +std::string +ConfigMap::makeKey(std::string section, std::string name) +{ + std::string key = section + "." + name; + // Convert to lower case to make lookups case-insensitive + std::transform( + key.begin(), key.end(), key.begin(), [](unsigned char c) { return std::tolower(c); }); + return key; +} + +// ======================================================= +// ======================================================= +int +ConfigMap::valueHandler(void * user, const char * section, const char * name, const char * value) +{ + if (!name) // Happens when INI_CALL_HANDLER_ON_NEW_SECTION enabled + return 1; + ConfigMap * reader = static_cast(user); + const auto key = makeKey(section, name); + if (reader->m_values[key].size() > 0) + reader->m_values[key] += "\n"; + reader->m_values[key] += value ? value : ""; + return 1; +} + +// ======================================================= +// ======================================================= +ConfigMap +broadcast_parameters(std::string filename) +{ +#ifdef KALYPSSO_CORE_USE_MPI + + int myRank; + int nTasks; + MPI_Comm_rank(MPI_COMM_WORLD, &myRank); + MPI_Comm_size(MPI_COMM_WORLD, &nTasks); + + char * buffer = nullptr; + int buffer_size = 0; + + // MPI rank 0 reads parameter file + if (myRank == 0) + { + + // open file and go to the end to get file size in bytes + std::ifstream filein(filename.c_str(), std::ifstream::ate); + if (filein.good() and filein.is_open()) + { + // file size must be smaller than 2^32 bytes (should be ok for a config file) + int file_size = static_cast(filein.tellg()); + + filein.seekg(0); // rewind + + buffer_size = file_size; + buffer = new char[buffer_size]; + + filein.read(buffer, buffer_size); + } + else + { + printf( + "Error : can't read input parameter file \"%s\"\nPlease check file actually exists !\n", + filename.c_str()); + std::abort(); + } + } + + // broadcast buffer size (collective) + MPI_Bcast(&buffer_size, 1, MPI_INT, 0, MPI_COMM_WORLD); + + if (buffer_size > 0) + { + + // all other MPI task need to allocate buffer + if (myRank > 0) + { + // printf("I'm rank %d allocating buffer of size %d\n",myRank,buffer_size); + buffer = new char[buffer_size]; + } + + // broastcast buffer itself (collective) + MPI_Bcast(&buffer[0], buffer_size, MPI_CHAR, 0, MPI_COMM_WORLD); + + // now all MPI rank should have buffer filled, try to build a ConfigMap + ConfigMap config_map(buffer, static_cast(buffer_size)); + + if (buffer) + delete[] buffer; + + return config_map; + } + + // return default config_map (surely not what we wanted) + char * null_buffer = nullptr; + ConfigMap defaultConfig(null_buffer, 0); + + return defaultConfig; + +#else + ConfigMap config_map(filename); + return config_map; +#endif + +} // broadcast_parameters + +} // namespace kalypsso diff --git a/src/kalypsso/utils/config/ConfigMap.h b/src/kalypsso/utils/config/ConfigMap.h new file mode 100644 index 0000000..9974ed9 --- /dev/null +++ b/src/kalypsso/utils/config/ConfigMap.h @@ -0,0 +1,397 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ConfigMap.h + * \brief Define an object will allow to easily retrieve parameter from a dictionary. + * + */ +#ifndef KALYPSSO_UTILS_CONFIGMAP_CONFIGMAP_H_ +#define KALYPSSO_UTILS_CONFIGMAP_CONFIGMAP_H_ + +#include // for KALYPSSO_CORE_USE_DOUBLE, ... + +#include +#include +#include +#include +#include +#include + +#include +#include // for KOKKOS_ENABLE_XXX + +namespace kalypsso +{ + +/** + * check if a given item is present in a vector of items. + */ +template +bool +is_present(std::vector const & vec, T const & item) +{ + return std::find(vec.begin(), vec.end(), item) != vec.end(); +} + +/** + * \class ConfigMap ConfigMap.h + * \brief This is a specialized version of ConfigMap which reads and parses a INI + * file into a key-value map (implemented using std::map). This class + * is useful to gather parameters. + */ +class ConfigMap +{ +public: + ConfigMap(std::string filename); + ConfigMap(char *& buffer, size_t buffer_size); + ~ConfigMap() = default; + + /** + * Return the result of ini_parse(), i.e., 0 on success, line number of + * first error on parse error, or -1 on file open error. + */ + int + parse_error(); + + // ================================================================================= + + //! Get a string value from INI file, returning default_value if not found. + std::string + getString(std::string section, std::string name, std::string default_value) const; + + //! Set a string value to section/name. + void + setString(std::string section, std::string name, std::string value); + + //! Get a vector value from INI file, returning default_value if not found. + std::vector + getStringVector(std::string section, + std::string name, + std::vector default_value) const; + + //! Set a vector of strings value to section/name. + void + setStringVector(std::string section, std::string name, std::vector value); + + // ================================================================================= + + //! Get an integer (int) value from INI file, returning default_value if not found. + int + getInteger(std::string section, std::string name, int default_value) const; + + //! Set an integer value to an section/name, reverse operation of set getInteger. + void + setInteger(std::string section, std::string name, int value); + + //! Get a vector of integer (int) value from INI file, returning default_value if not found. + std::vector + getIntegerVector(std::string section, std::string name, std::vector default_value) const; + + //! Set a vector of integer to an section/name, reverse operation of set getIntegerVector. + void + setIntegerVector(std::string section, std::string name, std::vector value); + + //! Get a vector of integer (int) value from INI file, returning default_value if not found. + //! Interesting when we know at compile time how many values are required + template + auto + getIntegerVector(std::string section, + std::string name, + Kokkos::Array default_value) const -> Kokkos::Array + + { + auto vec_int = getIntegerVector(section, name, std::vector{}); + + if (vec_int.size() >= N) + { + Kokkos::Array res; + + for (size_t i = 0; i < N; ++i) + { + res[i] = vec_int[i]; + } + + return res; + } + + return default_value; + } + + // ================================================================================= + + //! Get an integer (64 bits) value from INI file, returning default_value if not found. + int64_t + getInteger64(std::string section, std::string name, int64_t default_value) const; + + //! Set an integer value to an section/name, reverse operation of set getInteger. + void + setInteger64(std::string section, std::string name, int64_t value); + + //! Get a vector of 64 bit integers (int) value from INI file, returning default_value if not + //! found. + std::vector + getInteger64Vector(std::string section, + std::string name, + std::vector default_value) const; + + //! Set a vector of 64 bits integers to an section/name, reverse operation of set + //! getInteger64Vector. + void + setInteger64Vector(std::string section, std::string name, std::vector value); + + //! Get a vector of 64 bits integer (int) value from INI file, returning default_value if not + //! found. Interesting when we know at compile time how many values are required + template + auto + getInteger64Vector(std::string section, + std::string name, + Kokkos::Array default_value) const -> Kokkos::Array + + { + auto vec_int = getInteger64Vector(section, name, std::vector{}); + + if (vec_int.size() >= N) + { + Kokkos::Array res; + + for (size_t i = 0; i < N; ++i) + { + res[i] = vec_int[i]; + } + + return res; + } + + return default_value; + } + + // ================================================================================= + + //! Get a floating point value from the map. + float + getFloat(std::string section, std::string name, float default_value) const; + + //! Set a floating point value to a section/name. + void + setFloat(std::string section, std::string name, float value); + + //! Get a vector of float values from INI file, returning default_value if not found. + std::vector + getFloatVector(std::string section, std::string name, std::vector default_value) const; + + //! Set a vector of float to an section/name, reverse operation of set getFloatVector. + void + setFloatVector(std::string section, std::string name, std::vector value); + + //! Get a vector of float values from INI file, returning default_value if not found. + //! Interesting when we know at compile time how many values are required + template + auto + getFloatVector(std::string section, + std::string name, + Kokkos::Array default_value) const -> Kokkos::Array + + { + auto vec_float = getFloatVector(section, name, std::vector{}); + + if (vec_float.size() >= N) + { + Kokkos::Array res; + + for (size_t i = 0; i < N; ++i) + { + res[i] = vec_float[i]; + } + + return res; + } + + return default_value; + } + + // ================================================================================= + + //! Get a floating point value from the map. + double + getDouble(std::string section, std::string name, double default_value) const; + + //! Set a floating point value to a section/name. + void + setDouble(std::string section, std::string name, double value); + + //! Get a vector of double values from INI file, returning default_value if not found. + std::vector + getDoubleVector(std::string section, std::string name, std::vector default_value) const; + + //! Set a vector of double to an section/name, reverse operation of set getDoubleVector. + void + setDoubleVector(std::string section, std::string name, std::vector value); + + //! Get a vector of double values from INI file, returning default_value if not found. + //! Interesting when we know at compile time how many values are required + template + auto + getDoubleVector(std::string section, + std::string name, + Kokkos::Array default_value) const -> Kokkos::Array + { + auto vec_double = getDoubleVector(section, name, std::vector{}); + + if (vec_double.size() >= N) + { + Kokkos::Array res; + + for (size_t i = 0; i < N; ++i) + { + res[i] = vec_double[i]; + } + + return res; + } + + return default_value; + } + +#ifdef KALYPSSO_CORE_USE_DOUBLE + //! Get a floating point value from the map. + double + getReal(std::string section, std::string name, double default_value) const; + + //! Get a vector of floating point values from the map. + std::vector + getRealVector(std::string section, std::string name, std::vector default_value) const; + + //! Get a vector of real values from INI file, returning default_value if not found. + //! Interesting when we know at compile time how many values are required + template + auto + getRealVector(std::string section, + std::string name, + Kokkos::Array default_value) const -> Kokkos::Array + { + return getDoubleVector(section, name, default_value); + } +#else + //! Get a floating point value from the map. + float + getReal(std::string section, std::string name, float default_value) const; + + //! Get a vector of floating point values from the map. + std::vector + getRealVector(std::string section, std::string name, std::vector default_value) const; + + //! Get a vector of real values from INI file, returning default_value if not found. + //! Interesting when we know at compile time how many values are required + template + auto + getRealVector(std::string section, + std::string name, + Kokkos::Array default_value) const -> Kokkos::Array + { + return getFloatVector(section, name, default_value); + } +#endif // KALYPSSO_CORE_USE_DOUBLE + + // ================================================================================= + + //! Get a boolean value from the map. + bool + getBool(std::string section, std::string name, bool default_value) const; + + //! Set a boolean value to a section/name. + void + setBool(std::string section, std::string name, bool value); + + //! Get a vector of boolean values from the map. + std::vector + getBoolVector(std::string section, std::string name, std::vector default_value) const; + + //! Set a vector of boolean values to a section/name. + void + setBoolVector(std::string section, std::string name, std::vector value); + + //! Get a vector of bool values from INI file, returning default_value if not found. + //! Interesting when we know at compile time how many values are required + template + auto + getBoolVector(std::string section, + std::string name, + Kokkos::Array default_value) const -> Kokkos::Array + { + auto vec_bool = getBoolVector(section, name, std::vector{}); + + if (vec_bool.size() >= N) + { + Kokkos::Array res; + + for (size_t i = 0; i < N; ++i) + { + res[i] = vec_bool[i]; + } + + return res; + } + + return default_value; + } + + // ================================================================================= + + //! Print the content of a ConfigMap object + friend std::ostream & + operator<<(std::ostream & os, const ConfigMap & cfg); + +private: + int m_parse_error; + + std::map m_values; + + static std::string + makeKey(std::string section, std::string name); + + static int + valueHandler(void * user, const char * section, const char * name, const char * value); + + /** + * \brief Tokenize a given string according to the regex + * and remove the empty tokens. + * + * \param str + * \param re + * \return std::vector + */ + std::vector + tokenize(const std::string str, const std::regex re) const + { + std::sregex_token_iterator it{ str.begin(), str.end(), re, -1 }; + std::vector tokenized{ it, {} }; + + // Additional check to remove empty strings + tokenized.erase(std::remove_if(tokenized.begin(), + tokenized.end(), + [](std::string const & s) { return s.size() == 0; }), + tokenized.end()); + + return tokenized; + } + + template + std::vector + tokenize_as(const std::string str) + {} + +}; // class ConfigMap + +/** + * Builds a ConfigMap object from the input parameter file. + * + * ConfigMap is return by value here. + */ +ConfigMap +broadcast_parameters(std::string filename); + +} // namespace kalypsso + +#endif // KALYPSSO_UTILS_CONFIGMAP_CONFIGMAP_H_ diff --git a/src/kalypsso/utils/config/inih/LICENSE.txt b/src/kalypsso/utils/config/inih/LICENSE.txt new file mode 100644 index 0000000..cb7ee2d --- /dev/null +++ b/src/kalypsso/utils/config/inih/LICENSE.txt @@ -0,0 +1,27 @@ + +The "inih" library is distributed under the New BSD license: + +Copyright (c) 2009, Ben Hoyt +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Ben Hoyt nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY BEN HOYT ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BEN HOYT BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/kalypsso/utils/config/inih/ParameterValue.h b/src/kalypsso/utils/config/inih/ParameterValue.h new file mode 100644 index 0000000..b409661 --- /dev/null +++ b/src/kalypsso/utils/config/inih/ParameterValue.h @@ -0,0 +1,65 @@ +/** + * \file ParameterValue.h + * \brief a simple class to represent a runtime configurable parameter. + * + * This source code is currently not used but was considered as useful for + * potential refactoring. + * + */ +#ifndef KALYPSSO_UTILS_CONFIG_PARAMETERVALUE_H_ +#define KALYPSSO_UTILS_CONFIG_PARAMETERVALUE_H_ + +#include // for KALYPSSO_CORE_USE_DOUBLE, ... + +#include +#include +#include + +namespace kalypsso +{ + +/** + * A data structure holding data associated to runtime modifiable parameters. + * + * All parameters are stored in a std::map. + */ +struct ParameterValue +{ + using param_t = std::variant; + + //! Holded value (string, integer, float, double, etc ...) + param_t value; + + //! A minimal doc string for this parameter + std::string doc_string; + + //! Status boolean variable set to true if parameter was initialized from the default value. + //! By default is is true, and reset to false if parameter is present in input file. + bool defaulted; + + //! Set to true the first time API touch this parameter. + //! Should be useful + bool used; + + ParameterValue() + : value(false) + , doc_string("No documentation available") + , defaulted(true) + , used(false) + {} + + template + ParameterValue(T _value, std::string _doc_string) + : value(_value) + , doc_string(_doc_string) + , defaulted(true) + , used(false) + {} + + ~ParameterValue() = default; + +}; // ParameterValue + +} // namespace kalypsso + +#endif // KALYPSSO_UTILS_CONFIG_PARAMETERVALUE_H_ diff --git a/src/kalypsso/utils/config/inih/README.txt b/src/kalypsso/utils/config/inih/README.txt new file mode 100644 index 0000000..8e5f8b1 --- /dev/null +++ b/src/kalypsso/utils/config/inih/README.txt @@ -0,0 +1,5 @@ + +inih is a simple .INI file parser written in C, released under the New BSD +license (see LICENSE.txt). Go to the project home page for more info: + +http://code.google.com/p/inih/ diff --git a/src/kalypsso/utils/config/inih/ini.c b/src/kalypsso/utils/config/inih/ini.c new file mode 100644 index 0000000..ff566e8 --- /dev/null +++ b/src/kalypsso/utils/config/inih/ini.c @@ -0,0 +1,329 @@ +/* inih -- simple .INI file parser + +SPDX-License-Identifier: BSD-3-Clause + +Copyright (C) 2009-2020, Ben Hoyt + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include +#include +#include + +#include "ini.h" + +#if !INI_USE_STACK +#if INI_CUSTOM_ALLOCATOR +#include +void* ini_malloc(size_t size); +void ini_free(void* ptr); +void* ini_realloc(void* ptr, size_t size); +#else +#include +#define ini_malloc malloc +#define ini_free free +#define ini_realloc realloc +#endif +#endif + +#define MAX_SECTION 50 +#define MAX_NAME 50 + +/* Used by ini_parse_string() to keep track of string parsing state. */ +typedef struct { + const char* ptr; + size_t num_left; +} ini_parse_string_ctx; + +/* Strip whitespace chars off end of given string, in place. Return s. */ +static char* ini_rstrip(char* s) +{ + char* p = s + strlen(s); + while (p > s && isspace((unsigned char)(*--p))) + *p = '\0'; + return s; +} + +/* Return pointer to first non-whitespace char in given string. */ +static char* ini_lskip(const char* s) +{ + while (*s && isspace((unsigned char)(*s))) + s++; + return (char*)s; +} + +/* Return pointer to first char (of chars) or inline comment in given string, + or pointer to NUL at end of string if neither found. Inline comment must + be prefixed by a whitespace character to register as a comment. */ +static char* ini_find_chars_or_comment(const char* s, const char* chars) +{ +#if INI_ALLOW_INLINE_COMMENTS + int was_space = 0; + while (*s && (!chars || !strchr(chars, *s)) && + !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { + was_space = isspace((unsigned char)(*s)); + s++; + } +#else + while (*s && (!chars || !strchr(chars, *s))) { + s++; + } +#endif + return (char*)s; +} + +/* Similar to strncpy, but ensures dest (size bytes) is + NUL-terminated, and doesn't pad with NULs. */ +static char* ini_strncpy0(char* dest, const char* src, size_t size) +{ + /* Could use strncpy internally, but it causes gcc warnings (see issue #91) */ + size_t i; + for (i = 0; i < size - 1 && src[i]; i++) + dest[i] = src[i]; + dest[i] = '\0'; + return dest; +} + +/* See documentation in header file. */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user) +{ + /* Uses a fair bit of stack (use heap instead if you need to) */ +#if INI_USE_STACK + char line[INI_MAX_LINE]; + size_t max_line = INI_MAX_LINE; +#else + char* line; + size_t max_line = INI_INITIAL_ALLOC; +#endif +#if INI_ALLOW_REALLOC && !INI_USE_STACK + char* new_line; +#endif + char section[MAX_SECTION] = ""; +#if INI_ALLOW_MULTILINE + char prev_name[MAX_NAME] = ""; +#endif + + size_t offset; + char* start; + char* end; + char* name; + char* value; + int lineno = 0; + int error = 0; + char abyss[16]; /* Used to consume input when a line is too long. */ + +#if !INI_USE_STACK + line = (char*)ini_malloc(INI_INITIAL_ALLOC); + if (!line) { + return -2; + } +#endif + +#if INI_HANDLER_LINENO +#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) +#else +#define HANDLER(u, s, n, v) handler(u, s, n, v) +#endif + + /* Scan through stream line by line */ + while (reader(line, (int)max_line, stream) != NULL) { + offset = strlen(line); + +#if INI_ALLOW_REALLOC && !INI_USE_STACK + while (offset == max_line - 1 && line[offset - 1] != '\n') { + max_line *= 2; + if (max_line > INI_MAX_LINE) + max_line = INI_MAX_LINE; + new_line = ini_realloc(line, max_line); + if (!new_line) { + ini_free(line); + return -2; + } + line = new_line; + if (reader(line + offset, (int)(max_line - offset), stream) == NULL) + break; + offset += strlen(line + offset); + if (max_line >= INI_MAX_LINE) + break; + } +#endif + + lineno++; + + /* If line exceeded INI_MAX_LINE bytes, discard till end of line. */ + if (offset == max_line - 1 && line[offset - 1] != '\n') { + while (reader(abyss, sizeof(abyss), stream) != NULL) { + if (!error) + error = lineno; + if (abyss[strlen(abyss) - 1] == '\n') + break; + } + } + + start = line; +#if INI_ALLOW_BOM + if (lineno == 1 && (unsigned char)start[0] == 0xEF && + (unsigned char)start[1] == 0xBB && + (unsigned char)start[2] == 0xBF) { + start += 3; + } +#endif + start = ini_rstrip(ini_lskip(start)); + + if (strchr(INI_START_COMMENT_PREFIXES, *start)) { + /* Start-of-line comment */ + } +#if INI_ALLOW_MULTILINE + else if (*prev_name && *start && start > line) { +#if INI_ALLOW_INLINE_COMMENTS + end = ini_find_chars_or_comment(start, NULL); + if (*end) + *end = '\0'; + ini_rstrip(start); +#endif + /* Non-blank line with leading whitespace, treat as continuation + of previous name's value (as per Python configparser). */ + if (!HANDLER(user, section, prev_name, start) && !error) + error = lineno; + } +#endif + else if (*start == '[') { + /* A "[section]" line */ + end = ini_find_chars_or_comment(start + 1, "]"); + if (*end == ']') { + *end = '\0'; + ini_strncpy0(section, start + 1, sizeof(section)); +#if INI_ALLOW_MULTILINE + *prev_name = '\0'; +#endif +#if INI_CALL_HANDLER_ON_NEW_SECTION + if (!HANDLER(user, section, NULL, NULL) && !error) + error = lineno; +#endif + } + else if (!error) { + /* No ']' found on section line */ + error = lineno; + } + } + else if (*start) { + /* Not a comment, must be a name[=:]value pair */ + end = ini_find_chars_or_comment(start, "=:"); + if (*end == '=' || *end == ':') { + *end = '\0'; + name = ini_rstrip(start); + value = end + 1; +#if INI_ALLOW_INLINE_COMMENTS + end = ini_find_chars_or_comment(value, NULL); + if (*end) + *end = '\0'; +#endif + value = ini_lskip(value); + ini_rstrip(value); + +#if INI_ALLOW_MULTILINE + ini_strncpy0(prev_name, name, sizeof(prev_name)); +#endif + /* Valid name[=:]value pair found, call handler */ + if (!HANDLER(user, section, name, value) && !error) + error = lineno; + } + else { + /* No '=' or ':' found on name[=:]value line */ +#if INI_ALLOW_NO_VALUE + *end = '\0'; + name = ini_rstrip(start); + if (!HANDLER(user, section, name, NULL) && !error) + error = lineno; +#else + if (!error) + error = lineno; +#endif + } + } + +#if INI_STOP_ON_FIRST_ERROR + if (error) + break; +#endif + } + +#if !INI_USE_STACK + ini_free(line); +#endif + + return error; +} + +/* See documentation in header file. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user) +{ + return ini_parse_stream((ini_reader)fgets, file, handler, user); +} + +/* See documentation in header file. */ +int ini_parse(const char* filename, ini_handler handler, void* user) +{ + FILE* file; + int error; + + file = fopen(filename, "r"); + if (!file) + return -1; + error = ini_parse_file(file, handler, user); + fclose(file); + return error; +} + +/* An ini_reader function to read the next line from a string buffer. This + is the fgets() equivalent used by ini_parse_string(). */ +static char* ini_reader_string(char* str, int num, void* stream) { + ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream; + const char* ctx_ptr = ctx->ptr; + size_t ctx_num_left = ctx->num_left; + char* strp = str; + char c; + + if (ctx_num_left == 0 || num < 2) + return NULL; + + while (num > 1 && ctx_num_left != 0) { + c = *ctx_ptr++; + ctx_num_left--; + *strp++ = c; + if (c == '\n') + break; + num--; + } + + *strp = '\0'; + ctx->ptr = ctx_ptr; + ctx->num_left = ctx_num_left; + return str; +} + +/* See documentation in header file. */ +int ini_parse_string(const char* string, ini_handler handler, void* user) { + return ini_parse_string_length(string, strlen(string), handler, user); +} + +/* See documentation in header file. */ +int ini_parse_string_length(const char* string, size_t length, + ini_handler handler, void* user) { + ini_parse_string_ctx ctx; + + ctx.ptr = string; + ctx.num_left = length; + return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler, + user); +} diff --git a/src/kalypsso/utils/config/inih/ini.h b/src/kalypsso/utils/config/inih/ini.h new file mode 100644 index 0000000..a2e2877 --- /dev/null +++ b/src/kalypsso/utils/config/inih/ini.h @@ -0,0 +1,198 @@ +/* inih -- simple .INI file parser + +SPDX-License-Identifier: BSD-3-Clause + +Copyright (C) 2009-2020, Ben Hoyt + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#ifndef INI_H +#define INI_H + +/* Make this header file easier to include in C++ code */ +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +/* Nonzero if ini_handler callback should accept lineno parameter. */ +#ifndef INI_HANDLER_LINENO +# define INI_HANDLER_LINENO 0 +#endif + +/* Visibility symbols, required for Windows DLLs */ +#ifndef INI_API +# if defined _WIN32 || defined __CYGWIN__ +# ifdef INI_SHARED_LIB +# ifdef INI_SHARED_LIB_BUILDING +# define INI_API __declspec(dllexport) +# else +# define INI_API __declspec(dllimport) +# endif +# else +# define INI_API +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define INI_API __attribute__((visibility("default"))) +# else +# define INI_API +# endif +# endif +#endif + +/* Typedef for prototype of handler function. + + Note that even though the value parameter has type "const char*", the user + may cast to "char*" and modify its content, as the value is not used again + after the call to ini_handler. This is not true of section and name -- + those must not be modified. +*/ +#if INI_HANDLER_LINENO + typedef int (*ini_handler)(void * user, + const char * section, + const char * name, + const char * value, + int lineno); +#else +typedef int (*ini_handler)(void * user, + const char * section, + const char * name, + const char * value); +#endif + + /* Typedef for prototype of fgets-style reader function. */ + typedef char * (*ini_reader)(char * str, int num, void * stream); + + /* Parse given INI-style file. May have [section]s, name=value pairs + (whitespace stripped), and comments starting with ';' (semicolon). Section + is "" if name=value pair parsed before any section heading. name:value + pairs are also supported as a concession to Python's configparser. + + For each name=value pair parsed, call handler function with given user + pointer as well as section, name, and value (data only valid for duration + of handler call). Handler should return nonzero on success, zero on error. + + Returns 0 on success, line number of first error on parse error (doesn't + stop on first error), -1 on file open error, or -2 on memory allocation + error (only when INI_USE_STACK is zero). + */ + INI_API int + ini_parse(const char * filename, ini_handler handler, void * user); + + /* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't + close the file when it's finished -- the caller must do that. */ + INI_API int + ini_parse_file(FILE * file, ini_handler handler, void * user); + + /* Same as ini_parse(), but takes an ini_reader function pointer instead of + filename. Used for implementing custom or string-based I/O (see also + ini_parse_string). */ + INI_API int + ini_parse_stream(ini_reader reader, void * stream, ini_handler handler, void * user); + + /* Same as ini_parse(), but takes a zero-terminated string with the INI data + instead of a file. Useful for parsing INI data from a network socket or + which is already in memory. */ + INI_API int + ini_parse_string(const char * string, ini_handler handler, void * user); + + /* Same as ini_parse_string(), but takes a string and its length, avoiding + strlen(). Useful for parsing INI data from a network socket or which is + already in memory, or interfacing with C++ std::string_view. */ + INI_API int + ini_parse_string_length(const char * string, size_t length, ini_handler handler, void * user); + +/* Nonzero to allow multi-line value parsing, in the style of Python's + configparser. If allowed, ini_parse() will call the handler with the same + name for each subsequent line parsed. */ +#ifndef INI_ALLOW_MULTILINE +# define INI_ALLOW_MULTILINE 1 +#endif + +/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of + the file. See https://github.com/benhoyt/inih/issues/21 */ +#ifndef INI_ALLOW_BOM +# define INI_ALLOW_BOM 1 +#endif + +/* Chars that begin a start-of-line comment. Per Python configparser, allow + both ; and # comments at the start of a line by default. */ +#ifndef INI_START_COMMENT_PREFIXES +# define INI_START_COMMENT_PREFIXES ";#" +#endif + +/* Nonzero to allow inline comments (with valid inline comment characters + specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match + Python 3.2+ configparser behaviour. */ +#ifndef INI_ALLOW_INLINE_COMMENTS +# define INI_ALLOW_INLINE_COMMENTS 1 +#endif +#ifndef INI_INLINE_COMMENT_PREFIXES +# define INI_INLINE_COMMENT_PREFIXES ";" +#endif + +/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */ +#ifndef INI_USE_STACK +# define INI_USE_STACK 1 +#endif + +/* Maximum line length for any line in INI file (stack or heap). Note that + this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */ +#ifndef INI_MAX_LINE +# define INI_MAX_LINE 200 +#endif + +/* Nonzero to allow heap line buffer to grow via realloc(), zero for a + fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is + zero. */ +#ifndef INI_ALLOW_REALLOC +# define INI_ALLOW_REALLOC 0 +#endif + +/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK + is zero. */ +#ifndef INI_INITIAL_ALLOC +# define INI_INITIAL_ALLOC 200 +#endif + +/* Stop parsing on first error (default is to keep parsing). */ +#ifndef INI_STOP_ON_FIRST_ERROR +# define INI_STOP_ON_FIRST_ERROR 0 +#endif + +/* Nonzero to call the handler at the start of each new section (with + name and value NULL). Default is to only call the handler on + each name=value pair. */ +#ifndef INI_CALL_HANDLER_ON_NEW_SECTION +# define INI_CALL_HANDLER_ON_NEW_SECTION 0 +#endif + +/* Nonzero to allow a name without a value (no '=' or ':' on the line) and + call the handler with value NULL in this case. Default is to treat + no-value lines as an error. */ +#ifndef INI_ALLOW_NO_VALUE +# define INI_ALLOW_NO_VALUE 0 +#endif + +/* Nonzero to use custom ini_malloc, ini_free, and ini_realloc memory + allocation functions (INI_USE_STACK must also be 0). These functions must + have the same signatures as malloc/free/realloc and behave in a similar + way. ini_realloc is only needed if INI_ALLOW_REALLOC is set. */ +#ifndef INI_CUSTOM_ALLOCATOR +# define INI_CUSTOM_ALLOCATOR 0 +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* INI_H */ diff --git a/src/kalypsso/utils/io/CMakeLists.txt b/src/kalypsso/utils/io/CMakeLists.txt new file mode 100644 index 0000000..b660979 --- /dev/null +++ b/src/kalypsso/utils/io/CMakeLists.txt @@ -0,0 +1,47 @@ +add_library(kalypsso_io_c libb64/cencode.c) + +add_library( + kalypsso_io + FileHandler.h + FileHandler.cpp + FileHandlerVtk.h + FileHandlerVtk.cpp + VTKWriter.h + VTKWriter.cpp + IO_VTK_shared.h + IO_VTK_shared.cpp + libb64/libb64.h) + +target_include_directories(kalypsso_io PUBLIC $ + $) + +target_link_libraries(kalypsso_io PRIVATE kalypsso_warnings kalypsso_core_config) + +target_link_libraries(kalypsso_io PUBLIC kalypsso::config Kokkos::kokkos kalypsso_io_c) + +if(KALYPSSO_CORE_USE_MPI) + target_link_libraries(kalypsso_io PUBLIC MPI::MPI_CXX) +endif() + +# Add an alias so that library can be used inside the build tree +add_library(kalypsso::io ALIAS kalypsso_io) + +# +# install libraries and headers +# +install( + TARGETS kalypsso_io kalypsso_io_c + EXPORT kalypsso-targets + LIBRARY DESTINATION ${INSTALL_LIBDIR} + ARCHIVE DESTINATION ${INSTALL_LIBDIR} + RUNTIME DESTINATION ${INSTALL_BINDIR}) + +# +# install headers (exclude cpp files) +# +install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DESTINATION ${INSTALL_INCLUDEDIR}/kalypsso/utils + FILES_MATCHING + PATTERN "*.h" + PATTERN "*.cpp" EXCLUDE) diff --git a/src/kalypsso/utils/io/FileHandler.cpp b/src/kalypsso/utils/io/FileHandler.cpp new file mode 100644 index 0000000..1bf6053 --- /dev/null +++ b/src/kalypsso/utils/io/FileHandler.cpp @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +#include + +namespace kalypsso +{ +namespace io +{ + +// ======================================================= +// ======================================================= +FileHandler::FileHandler() + : directory("./") + , name("data") + , suffix("txt") +{} // FileHandler::FileHandler + +// ======================================================= +// ======================================================= +FileHandler::FileHandler(std::string directory_, std::string name_, std::string suffix_) + : directory(directory_) + , name(name_) + , suffix(suffix_) +{} // FileHandler::FileHandler + +// ======================================================= +// ======================================================= +FileHandler::~FileHandler() {} // FileHandler::~FileHandler + +// ======================================================= +// ======================================================= +std::string +FileHandler::getFullPath() +{ + + std::stringstream filename; + + if (!directory.empty()) + filename << directory << "/"; + filename << name; + + filename << "." << suffix; + + return filename.str(); + +} // FileHandler::getFullPath + +} // namespace io + +} // namespace kalypsso diff --git a/src/kalypsso/utils/io/FileHandler.h b/src/kalypsso/utils/io/FileHandler.h new file mode 100644 index 0000000..8783cd0 --- /dev/null +++ b/src/kalypsso/utils/io/FileHandler.h @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef KALYPSSO_UTILS_IO_FILEHANDLER_H +#define KALYPSSO_UTILS_IO_FILEHANDLER_H + +#include + +namespace kalypsso +{ +namespace io +{ + +// ================================================================== +// ================================================================== +/** + * \class FileHandler + * \brief Create file name. + * + * Does not handle the file descriptor, only the client code can do that. + * This class is just a simple structure, holding a few parameters + * to ease building the full filename. + * + */ +class FileHandler +{ + +protected: + std::string directory; /**< name of directory where file resides. */ + std::string name; /**< name of file. */ + std::string suffix; /**< suffix. */ + +public: + FileHandler(); + FileHandler(std::string directory_, std::string name_, std::string suffix_); + virtual ~FileHandler(); + + void + setDirectory(std::string the_directory) + { + directory = the_directory; + }; + void + setName(std::string the_name) + { + name = the_name; + }; + void + setSuffix(std::string the_suffix) + { + suffix = the_suffix; + }; + + /** + * This is where the full path is build. + * It will be overloaded in derived class, taking into account + * specific file numbering for a time series. + */ + virtual std::string + getFullPath(); + + std::string + getName() const + { + return name; + }; + std::string + getDirectory() const + { + return directory; + }; + std::string + getSuffix() const + { + return suffix; + }; + +}; // class FileHandler + +} // namespace io + +} // namespace kalypsso + +#endif // KALYPSSO_UTILS_IO_FILEHANDLER_H diff --git a/src/kalypsso/utils/io/FileHandlerVtk.cpp b/src/kalypsso/utils/io/FileHandlerVtk.cpp new file mode 100644 index 0000000..d1f61d2 --- /dev/null +++ b/src/kalypsso/utils/io/FileHandlerVtk.cpp @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +#include + +namespace kalypsso +{ +namespace io +{ + +// ======================================================= +// ======================================================= +FileHandlerVtk::FileHandlerVtk() + : FileHandler() + , timeStep(0) + , isParallel(false) + , mpiRank(0) +{ + + suffix = "vtu"; + +} // FileHandlerVtk::FileHandlerVtk + +// ======================================================= +// ======================================================= +FileHandlerVtk::FileHandlerVtk(std::string directory_, std::string name_, std::string suffix_) + : FileHandler(directory_, name_, suffix_) + , timeStep(0) + , isParallel(false) + , mpiRank(0) +{} // FileHandlerVtk::FileHandlerVtk + +// ======================================================= +// ======================================================= +FileHandlerVtk::~FileHandlerVtk() {} // FileHandlerVtk::~FileHandlerVtk + +// ======================================================= +// ======================================================= +std::string +FileHandlerVtk::getFullPath() +{ + + std::stringstream filename; + + if (!directory.empty()) + filename << directory << "/"; + filename << name; + + // write timeStep in string timeFormat + std::ostringstream timeFormat; + timeFormat.width(7); + timeFormat.fill('0'); + timeFormat << timeStep; + filename << "_time"; + filename << timeFormat.str(); + + if (isParallel) + { + // write MPI rank in string rankFormat + std::ostringstream rankFormat; + rankFormat.width(5); + rankFormat.fill('0'); + rankFormat << mpiRank; + filename << "_mpi"; + filename << rankFormat.str(); + } + + filename << "." << suffix; + + return filename.str(); + +} // FileHandlerVtk::getFullPath + +} // namespace io + +} // namespace kalypsso diff --git a/src/kalypsso/utils/io/FileHandlerVtk.h b/src/kalypsso/utils/io/FileHandlerVtk.h new file mode 100644 index 0000000..8e6935c --- /dev/null +++ b/src/kalypsso/utils/io/FileHandlerVtk.h @@ -0,0 +1,78 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef KALYPSSO_UTILS_IO_FILEHANDLERVTK_H +#define KALYPSSO_UTILS_IO_FILEHANDLERVTK_H + +#include + +namespace kalypsso +{ +namespace io +{ + +// ======================================================= +// ======================================================= +/** + * \class FileHandlerVtk + */ +class FileHandlerVtk : public FileHandler +{ + +protected: + int timeStep; /**< identify a time step number */ + bool isParallel; /**< if true, the file name will contain the MPI rank */ + int mpiRank; /**< only used if isParallel is true */ + +public: + FileHandlerVtk(); + FileHandlerVtk(std::string directory_, std::string name_, std::string suffix_); + virtual ~FileHandlerVtk(); + + void + setTimeStep(int the_timeStep) + { + timeStep = the_timeStep; + }; + + void + setIsParallel(bool the_isParallel) + { + isParallel = the_isParallel; + }; + + void + setRank(int the_rank) + { + mpiRank = the_rank; + }; + + std::string + getFullPath(); + + bool + IsParallel() const + { + return isParallel; + }; + + std::string + getTimeStep() const + { + return directory; + }; + + int + getRank() const + { + return mpiRank; + }; + +}; // class FileHandlerVtk + +} // namespace io + +} // namespace kalypsso + +#endif // KALYPSSO_UTILS_IO_FILEHANDLERVTK_H diff --git a/src/kalypsso/utils/io/IO_VTK_shared.cpp b/src/kalypsso/utils/io/IO_VTK_shared.cpp new file mode 100644 index 0000000..795c1a7 --- /dev/null +++ b/src/kalypsso/utils/io/IO_VTK_shared.cpp @@ -0,0 +1,228 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "IO_VTK_shared.h" + +#include // for ComponentIndex3D (IX,IY,IZ) +#include // for get_current_date + +#include +#include +#include + +namespace kalypsso +{ +namespace io +{ + +// ======================================================= +// ======================================================= +static bool +isBigEndian() +{ + const int i = 1; + return ((*reinterpret_cast(&i)) == 0); +} + +// ======================================================= +// ======================================================= +void +write_vtu_header(std::ostream & outFile, ConfigMap & config_map) +{ + + bool outputVtkAscii = config_map.getBool("output", "outputVtkAscii", false); + bool outputVtkAppended = config_map.getBool("output", "outputVtkAppended", false); + bool outputVtkBinary = config_map.getBool("output", "outputVtkBinary", false); + bool outputDateAndTime = config_map.getBool("output", "outputDateAndTime", false); + + // if writing raw binary data (file does not respect XML standard) + if (outputVtkAscii or outputVtkBinary) + outFile << "\n"; + + // print data and time + outFile << "\n"; + + // write xml data header + + outFile << "\n"; + + outFile << " \n"; + +} // write_vtu_header + +// ======================================================= +// ======================================================= +void +write_vtk_metadata(std::ostream & outFile, int iStep, real_t time) +{ + + outFile << " \n"; + + outFile << " " + << iStep << " \n"; + + outFile << " " + << time << " \n"; + + outFile << " \n"; + +} // write_vtk_metadata + +// ======================================================= +// ======================================================= +void +close_vtu_grid(std::ostream & outFile) +{ + + outFile << " \n"; + +} // close_vtu_grid + +// ======================================================= +// ======================================================= +void +write_vtu_footer(std::ostream & outFile) +{ + + outFile << "\n"; + +} // write_vtu_footer + +#ifdef KALYPSSO_CORE_USE_MPI +/* + * write pvtu header in a separate file. + */ +// ======================================================= +// ======================================================= +void +write_pvtu_header(std::string headerFilename, + std::string outputPrefix, + const int nProcs, + ConfigMap & config_map, + const std::map & varNames, + const int iStep) +{ + // file handler + std::fstream outHeader; + + bool outputDateAndTime = config_map.getBool("output", "outputDateAndTime", false); + + const int nbvar = static_cast(varNames.size()); + + // dummy string here, when using the full VTK API, data can be compressed + // here, no compression used + std::string compressor(""); + + // check scalar data type + bool useDouble = sizeof(real_t) == sizeof(double) ? true : false; + const char * dataType = useDouble ? "Float64" : "Float32"; + + // write iStep in string timeFormat + std::ostringstream timeFormat; + timeFormat.width(7); + timeFormat.fill('0'); + timeFormat << iStep; + + // open pvtu header file + outHeader.open(headerFilename.c_str(), std::ios_base::out); + + outHeader << "" << std::endl; + + // print data and time + outHeader << "\n"; + + if (isBigEndian()) + outHeader << "" << std::endl; + else + outHeader << "" << std::endl; + + outHeader << " \n"; + + outHeader << " \n"; + outHeader << " \n"; + outHeader << " \n"; + + outHeader << " \n"; + outHeader << " \n"; + outHeader << " \n"; + outHeader << " \n"; + outHeader << " \n"; + + outHeader << " " << std::endl; + for (int iVar = 0; iVar < nbvar; iVar++) + { + outHeader << " " << std::endl; + } + outHeader << " " << std::endl; + + // one piece per MPI process + for (int iPiece = 0; iPiece < nProcs; ++iPiece) + { + std::ostringstream pieceFormat; + pieceFormat.width(5); + pieceFormat.fill('0'); + pieceFormat << iPiece; + std::string pieceFilename = + outputPrefix + "_time" + timeFormat.str() + "_mpi" + pieceFormat.str() + ".vtu"; + outHeader << " " << std::endl; + } + outHeader << "" << std::endl; + outHeader << "" << std::endl; + + // close header file + outHeader.close(); + + // end writing pvtu header + +} // write_pvtu_header + +#endif // KALYPSSO_CORE_USE_MPI + +} // namespace io + +} // namespace kalypsso diff --git a/src/kalypsso/utils/io/IO_VTK_shared.h b/src/kalypsso/utils/io/IO_VTK_shared.h new file mode 100644 index 0000000..6db59d8 --- /dev/null +++ b/src/kalypsso/utils/io/IO_VTK_shared.h @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef KALYPSSO_UTILS_IO_IOVTKSHARED_H_ +#define KALYPSSO_UTILS_IO_IOVTKSHARED_H_ + +#include + +#include +#include +#include + +namespace kalypsso +{ +namespace io +{ + +/** + * Write VTK unstructured grid header. + */ +void +write_vtu_header(std::ostream & outFile, ConfigMap & config_map); + +/** + * Write VTK unstructured grid metadata (date and time). + */ +void +write_vtk_metadata(std::ostream & outFile, int iStep, real_t time); + +/** + * Write closing VTK unstructured grid statement. + */ +void +close_vtu_grid(std::ostream & outFile); + +/** + * Write closing VTK file statement. + */ +void +write_vtu_footer(std::ostream & outFile); + +#ifdef KALYPSSO_CORE_USE_MPI +/** + * write pvtu header in a separate file. + */ +void +write_pvtu_header(std::string headerFilename, + std::string outputPrefix, + const int nProcs, + ConfigMap & config_map, + const std::map & varNames, + const int iStep); +#endif // KALYPSSO_CORE_USE_MPI + +} // namespace io + +} // namespace kalypsso + +#endif // KALYPSSO_UTILS_IO_IOVTKSHARED_H_ diff --git a/src/kalypsso/utils/io/VTKWriter.cpp b/src/kalypsso/utils/io/VTKWriter.cpp new file mode 100644 index 0000000..dc13bc6 --- /dev/null +++ b/src/kalypsso/utils/io/VTKWriter.cpp @@ -0,0 +1,417 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#ifdef KALYPSSO_CORE_USE_MPI +# include +#endif // KALYPSSO_CORE_USE_MPI + +#include // for type base64 encoding +#include // for std::max +#include // for std::ios::good +#include // for std::cerr + +#include + +#include + +namespace kalypsso +{ +namespace io +{ + +// ======================================================= +// ======================================================= +VTKWriter::VTKWriter(ConfigMap & config_map, int64_t nbCells) + : m_fileHandler() + , m_config_map(config_map) + , m_nbCells(nbCells) + , m_mpi_rank(0) + , m_nb_ranks(1) + , m_vtk_write_type(VTK_WRITE_ENUM::ASCII) + , m_write_type_str("undefined") + , m_parallelEnabled(false) + , m_out_file() +{ + // output directory + std::string outputDir = m_config_map.getString("output", "outputDir", "./"); + m_fileHandler.setDirectory(outputDir); + + // file name base + std::string outputPrefix = m_config_map.getString("output", "outputPrefix", "output"); + m_fileHandler.setName(outputPrefix); + + // file suffix (vtk unstructured grid) + m_fileHandler.setSuffix("vtu"); + + // set write type + bool outputVtkAscii = m_config_map.getBool("output", "outputVtkAscii", false); + bool outputVtkAppended = m_config_map.getBool("output", "outputVtkAppended", false); + bool outputVtkBinary = m_config_map.getBool("output", "outputVtkBinary", true); + + if (outputVtkAscii) + { + m_vtk_write_type = VTK_WRITE_ENUM::ASCII; + } + else if (outputVtkAppended) + { + m_vtk_write_type = VTK_WRITE_ENUM::APPENDED; + } + else if (outputVtkBinary) + { + m_vtk_write_type = VTK_WRITE_ENUM::BINARY; + } + + // set write type string + if (outputVtkAscii) + m_write_type_str = "ascii"; + else if (outputVtkAppended) + m_write_type_str = "appended"; + else if (outputVtkBinary) + m_write_type_str = "binary"; + +#ifdef KALYPSSO_CORE_USE_MPI + m_fileHandler.setIsParallel(true); + + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + m_fileHandler.setRank(rank); +#endif // KALYPSSO_CORE_USE_MPI + +} // VTKWriter::VTKWriter + +// ======================================================= +// ======================================================= +VTKWriter::~VTKWriter() +{ + + // make sure to close output file + close_file(); + +} // VTKWriter::~VTKWriter + +// ======================================================= +// ======================================================= +void +VTKWriter::open_file() +{ + + // open file only if not already open + if (!m_out_file.is_open()) + m_out_file.open(m_fileHandler.getFullPath(), std::ios_base::out); + +} // VTKWriter::open_file + +// ======================================================= +// ======================================================= +void +VTKWriter::close_file() +{ + + // close file only if currently open + if (m_out_file.is_open()) + m_out_file.close(); + +} // VTKWriter::close_file + +// ======================================================= +// ======================================================= +void +VTKWriter::write_header() +{ + + if (m_out_file.is_open()) + write_vtu_header(m_out_file, m_config_map); + else + std::cerr << "file " << m_fileHandler.getFullPath() << " is not open...\n"; + +} // VTKWriter::write_header + +// ======================================================= +// ======================================================= +void +VTKWriter::write_footer() +{ + + if (m_out_file.is_open()) + write_vtu_footer(m_out_file); + else + std::cerr << "file " << m_fileHandler.getFullPath() << " is not open...\n"; + +} // VTKWriter::write_footer + +// ======================================================= +// ======================================================= +void +VTKWriter::write_metadata(int iStep, real_t time) +{ + + if (m_out_file.is_open()) + write_vtk_metadata(m_out_file, iStep, time); + else + std::cerr << "file " << m_fileHandler.getFullPath() << " is not open...\n"; + +} // VTKWriter::write_metadata + +// ======================================================= +// ======================================================= +void +VTKWriter::write_piece_header(int64_t nbNodes) +{ + + if (m_out_file.is_open()) + { + + m_out_file << " \n"; + } + +} // VTKWriter::write_piece_header + +// ======================================================= +// ======================================================= +void +VTKWriter::write_piece_footer() +{ + + if (m_out_file.is_open()) + { + m_out_file << " \n"; + } + +} // VTKWriter::write_piece_footer + +// ======================================================= +// ======================================================= +void +VTKWriter::write_cell_data(const std::string & dataname, const std::vector & cell_data) +{ + + const char * dataType = "Float64"; + + /* + * write cell data. + */ + m_out_file << " \n"; + + m_offsetBytes += sizeof(uint64_t) + sizeof(real_t) * static_cast(m_nbCells); + + if (vtk_ascii_enabled()) + { + + for (uint64_t i = 0; i < static_cast(m_nbCells); ++i) + { + m_out_file << cell_data[i] << " "; + } + m_out_file << "\n"; + + } // end vtk_ascii_enabled + + if (vtk_binary_enabled()) + { + + write_base64_binary_data(reinterpret_cast(&(cell_data[0])), + sizeof(real_t) * static_cast(m_nbCells)); + + m_out_file << "\n"; + + } // end vtk_binary_enabled + + m_out_file << " \n"; + +} // VTKWriter::write_cell_data + +// ======================================================= +// ======================================================= +void +VTKWriter::write_cell_data(const std::string & dataname, + const Kokkos::View cell_data_d) +{ + + Kokkos::View::HostMirror cell_data = + Kokkos::create_mirror(cell_data_d); + + Kokkos::deep_copy(cell_data, cell_data_d); + + const char * dataType = "Float64"; + + /* + * write cell data. + */ + m_out_file << " \n"; + + m_offsetBytes += sizeof(uint64_t) + sizeof(real_t) * static_cast(m_nbCells); + + if (vtk_ascii_enabled()) + { + + for (int64_t i = 0; i < m_nbCells; ++i) + { + m_out_file << cell_data(i) << " "; + } + m_out_file << "\n"; + + } // end vtk_ascii_enabled + + if (vtk_binary_enabled()) + { + + write_base64_binary_data(reinterpret_cast(cell_data.data()), + sizeof(real_t) * static_cast(m_nbCells)); + + m_out_file << "\n"; + + } // end vtk_binary_enabled + + m_out_file << " \n"; + +} // VTKWriter::write_cell_data + +// ======================================================= +// ======================================================= +void +VTKWriter::open_data() +{ + m_out_file << " \n"; +} // VTKWriter::open_data + +// ======================================================= +// ======================================================= +void +VTKWriter::close_data() +{ + m_out_file << " \n"; +} // VTKWriter::close_data + +// ======================================================= +// ======================================================= +void +VTKWriter::close_grid() +{ + + close_vtu_grid(m_out_file); + +} // VTKWriter::close_grid + +// ======================================================= +// ======================================================= +void +VTKWriter::open_data_appended() +{ + m_out_file << " " << "\n"; + + // leading underscore + m_out_file << "_"; + +} // VTKWriter::open_data_appended + +// ======================================================= +// ======================================================= +void +VTKWriter::close_data_appended() +{ + + m_out_file << " " << "\n"; + +} // VTKwriter::close_data_appended + +// ================================================================ +// ================================================================ +void +VTKWriter::write_appended_binary_cell_data([[maybe_unused]] const std::string & dataname, + const std::vector & cell_data) +{ + + /* + * Write cells data. + */ + { + + auto size = sizeof(real_t) * static_cast(m_nbCells); + m_out_file.write(reinterpret_cast(&size), sizeof(uint64_t)); + m_out_file.write(reinterpret_cast(&(cell_data[0])), static_cast(size)); + + } // end write cells data + +} // VTKWriter::write_appended_binary_cell_data + +// ================================================================ +// ================================================================ +int +VTKWriter::write_base64_binary_data(const char * numeric_data, size_t byte_length) +{ + + size_t chunks, chunksize, remaining, writenow; + size_t code_length, base_length; + uint32_t int_header; + char * base_data; + base64_encodestate encode_state; + + /* VTK format used 32bit header info */ + // assert (byte_length <= (size_t) UINT32_MAX); + + /* This value may be changed although this is not tested with VTK */ + chunksize = static_cast(1) << 15; /* 32768 */ + int_header = static_cast(byte_length); + + /* Allocate sufficient memory for base64 encoder */ + code_length = 2 * std::max(chunksize, sizeof(int_header)); + code_length = std::max(code_length, static_cast(4)) + 1; + base_data = new char[code_length]; + + base64_init_encodestate(&encode_state); + base_length = base64_encode_block( + reinterpret_cast(&int_header), sizeof(int_header), base_data, &encode_state); + // assert (base_length < code_length); + base_data[base_length] = '\0'; + m_out_file.write(base_data, static_cast(base_length)); + + chunks = 0; + remaining = byte_length; + while (remaining > 0) + { + writenow = std::min(remaining, chunksize); + base_length = + base64_encode_block(numeric_data + chunks * chunksize, writenow, base_data, &encode_state); + // assert (base_length < code_length); + base_data[base_length] = '\0'; + m_out_file.write(base_data, static_cast(base_length)); + remaining -= writenow; + ++chunks; + } + + base_length = base64_encode_blockend(base_data, &encode_state); + // assert (base_length < code_length); + base_data[base_length] = '\0'; + m_out_file.write(base_data, static_cast(base_length)); + + delete[] base_data; + if (!m_out_file.good()) + { + // something wrong happened ! + return -1; + } + return 0; + +} // VTKWriter::write_base64_binary_data + +} // namespace io + +} // namespace kalypsso diff --git a/src/kalypsso/utils/io/VTKWriter.h b/src/kalypsso/utils/io/VTKWriter.h new file mode 100644 index 0000000..29fe3af --- /dev/null +++ b/src/kalypsso/utils/io/VTKWriter.h @@ -0,0 +1,593 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef KALYPSSO_UTILS_IO_VTKWRITER_H +#define KALYPSSO_UTILS_IO_VTKWRITER_H + +#include +#include + +#include +#include +#include +#include + +#include "kalypsso/core/Point.h" + +namespace kalypsso +{ +namespace io +{ + +enum class VTK_WRITE_ENUM +{ + + ASCII = 0, + APPENDED = 1, + BINARY = 2 + +}; + +// ================================================================== +// ================================================================== +/** + * \class VTKWriter + * + * Supporting VTK unstructured grid format. + * + * If run in parallel (MPI enabled), each MPI process writes a vtu file, + * and process 0 (master) writes an additional pvtu (partitioned vtu) + * which enables Paraview or Visit to re-compose all the pieces together. + * + */ +class VTKWriter +{ + +protected: + FileHandlerVtk m_fileHandler; /**< file handler (directory, name, ...) */ + ConfigMap & m_config_map; /**< application config map */ + + int64_t m_nbCells; + + uint64_t m_offsetBytes = 0; /**< offset in bytes, only meaningful when + binary output is enabled */ + + int m_mpi_rank; /**< MPI rank of current process */ + int m_nb_ranks; /**< total number of MPI processes */ + + VTK_WRITE_ENUM + m_vtk_write_type; /**< ascii, appended binary or base64 binary */ + std::string m_write_type_str; /**< a string identifying the type of vtk file + format (ascii, appended or binary) */ + + bool m_parallelEnabled; /**< write one piece per MPI process */ + + std::fstream m_out_file; /**< iostream handle */ + +public: + VTKWriter(ConfigMap & config_map, int64_t nbCells); + virtual ~VTKWriter(); + + void + parallel_enable() + { + m_parallelEnabled = true; + }; + void + parallel_disable() + { + m_parallelEnabled = false; + }; + + void + set_vtk_write_type(VTK_WRITE_ENUM wt) + { + m_vtk_write_type = wt; + }; + bool + vtk_ascii_enabled() + { + return m_vtk_write_type == VTK_WRITE_ENUM::ASCII; + }; + bool + vtk_appended_enabled() + { + return m_vtk_write_type == VTK_WRITE_ENUM::APPENDED; + }; + bool + vtk_binary_enabled() + { + return m_vtk_write_type == VTK_WRITE_ENUM::BINARY; + }; + + void + set_mpi_rank(int rank) + { + m_mpi_rank = rank; + }; + int + get_mpi_rank() + { + return m_mpi_rank; + }; + + void + set_nb_ranks(int nb_ranks) + { + m_nb_ranks = nb_ranks; + }; + int + get_nb_ranks() + { + return m_nb_ranks; + }; + + void + open_file(); + void + close_file(); + + void + write_header(); + void + write_footer(); + + /** Write scalar information cycle (=iStep) and time. */ + void + write_metadata(int iStep, real_t time); + + /** Write number of nodes and cells for current piece (MPI sub-domain). */ + void + write_piece_header(int64_t nbNodes); + + /** Just close the piece XML section. */ + void + write_piece_footer(); + + /** Write some scalar cell-centered data */ + void + write_cell_data(const std::string & dataname, + const std::vector & cell_data = std::vector()); + + /** Write some scalar cell-centered data from a Kokkos device array */ + void + write_cell_data(const std::string & dataname, + const Kokkos::View cell_data_d); + + /** open data section */ + void + open_data(); + + /** close data section */ + void + close_data(); + + /** close UnstructuredGrid section (that was open in write_header) */ + void + close_grid(); + + /** open section AppendedData - only useful for binary output */ + void + open_data_appended(); + + /** close section AppendedData - only useful for binary output */ + void + close_data_appended(); + + /** + * Write geometry (nodes coordinates and cells connectivity). + * + * Cells are assumed quadrangle in 2D and hexaedral in 3D. + * + * When writing with binary appended format, nodes_locations is + * not required yet. + */ + template + void + write_geometry(const std::vector> & nodes_location = std::vector>()); + + /** + * Write connectivity (list of nodes id, i.e. vertex associated to a cell. + * + * Cells are assumed quadrangle in 2D and hexahedral in 3D. + * + * VERY important: we assume an implicit connectivity; more precisely, all + * vertex are redundant (we will do better later) + * + */ + template + void + write_connectivity(); + + /** + * write nodes location, connectivity and offsets in binary format + * at the end of the file, after the ascii header. + */ + template + void + write_appended_binary_geometry(std::vector> & nodes_location); + + /** + * Write binary data in appended format; this toutine can be called + * several times but in the same order as write_cell_data. + */ + void + write_appended_binary_cell_data(const std::string & dataname, + const std::vector & cell_data); + + /** + * write binary data using base64 code. this code has been adapted from + * libsc (LGPL, copyright C. Burstedde, https://github.com/cburstedde/libsc). + */ + int + write_base64_binary_data(const char * bytes, size_t data_size); + +}; // class VTKWriter + +// ======================================================= +// ======================================================= +template +void +VTKWriter::write_geometry(const std::vector> & nodes_location) +{ + + uint64_t nbNodes = nodes_location.size(); + + // 4 nodes for a quadrangle in 2d + // 8 nodes for a hexahedron in 3d + int nbNodesPerCell = (dim == 2) ? 4 : 8; + + m_out_file << " \n"; + m_out_file << " " << "\n"; + + /* + * for binary format, see write_appended_binary_geometry + */ + if (vtk_ascii_enabled()) + { + + for (uint64_t i = 0; i < nbNodes; ++i) + { + + m_out_file << nodes_location[i][IX] << " " << nodes_location[i][IY] << " " + << nodes_location[i][IZ] << "\n"; + + } // end for + } + else if (vtk_binary_enabled()) + { + + // this is only necessary for binary output + float * vertices = new float[m_nbCells * nbNodesPerCell * 3]; + + for (int64_t i = 0; i < m_nbCells * nbNodesPerCell; ++i) + { + + vertices[3 * i] = static_cast(nodes_location[i][IX]); + vertices[3 * i + 1] = static_cast(nodes_location[i][IY]); + vertices[3 * i + 2] = static_cast(nodes_location[i][IZ]); + + } // end for i + + m_out_file << " "; + + write_base64_binary_data(reinterpret_cast(&(vertices[0])), + sizeof(float) * m_nbCells * nbNodesPerCell * 3); + + delete[] vertices; + + m_out_file << "\n"; + + } // vtk_ascii_enabled or vtk_binary_enabled + + m_out_file << " \n"; + m_out_file << " \n"; + + m_offsetBytes += sizeof(uint64_t) + sizeof(float) * m_nbCells * nbNodesPerCell * 3; + +} // VTKWriter::write_geometry + +// ======================================================= +// ======================================================= +template +void +VTKWriter::write_connectivity() +{ + + // 4 nodes for a quadrangle in 2d + // 8 nodes for a hexahedron in 3d + int nbNodesPerCell = (dim == 2) ? 4 : 8; + + // 9 means "Quad" - 12 means "Hexahedron" + int cellType = (dim == 2) ? 9 : 12; + + m_out_file << " \n"; + + /* + * CONNECTIVITY + */ + m_out_file << " \n"; + + m_offsetBytes += sizeof(uint64_t) + sizeof(uint64_t) * m_nbCells * nbNodesPerCell; + + if (vtk_ascii_enabled()) + { + + for (int64_t i = 0; i < m_nbCells; ++i) + { + + // offset to the first nodes in this cell + uint64_t offset = i * nbNodesPerCell; + for (uint8_t index = 0; index < nbNodesPerCell; ++index) + { + m_out_file << offset + index << " "; + } + + m_out_file << "\n"; + } + + } // end vtk_ascii_enabled + + if (vtk_binary_enabled()) + { + + int64_t * offsets_array = new int64_t[m_nbCells * nbNodesPerCell]; + + uint64_t tmp = 0; + for (int64_t i = 0; i < m_nbCells; ++i) + { + + // offset to the first nodes in this cell + uint64_t offset = i * nbNodesPerCell; + for (uint8_t index = 0; index < nbNodesPerCell; ++index) + { + offsets_array[tmp] = offset + index; + ++tmp; + } + } + + m_out_file << " "; + + write_base64_binary_data(reinterpret_cast(&(offsets_array[0])), + sizeof(int64_t) * m_nbCells * nbNodesPerCell); + + delete[] offsets_array; + + m_out_file << "\n"; + + } // end vtk_binary_enabled + + m_out_file << " \n"; + + /* + * OFFSETS + */ + m_out_file << " \n"; + + m_offsetBytes += sizeof(uint64_t) + sizeof(uint64_t) * m_nbCells; + + if (vtk_ascii_enabled()) + { + // number of nodes per cell is 4 in 2D, 8 in 3D + for (int64_t i = 1; i <= m_nbCells; ++i) + { + int64_t cell_offset = nbNodesPerCell * i; + m_out_file << cell_offset << " "; + } + m_out_file << "\n"; + } + + if (vtk_binary_enabled()) + { + + int64_t * tmpArray = new int64_t[m_nbCells]; + + for (int64_t i = 0; i < m_nbCells; ++i) + { + + tmpArray[i] = nbNodesPerCell * (i + 1); + } + + m_out_file << " "; + + write_base64_binary_data(reinterpret_cast(&(tmpArray[0])), + sizeof(int64_t) * m_nbCells); + + delete[] tmpArray; + + m_out_file << "\n"; + + } // end vtk_binary_enabled + + m_out_file << " \n"; + + /* + * CELL TYPES + */ + m_out_file << " \n"; + + m_offsetBytes += sizeof(uint64_t) + sizeof(unsigned char) * m_nbCells; + + if (vtk_ascii_enabled()) + { + for (int64_t i = 0; i < m_nbCells; ++i) + { + m_out_file << cellType << " "; + } + + m_out_file << "\n"; + } + + if (vtk_binary_enabled()) + { + + uint8_t * tmpArray = new uint8_t[m_nbCells]; + + for (int64_t i = 0; i < m_nbCells; ++i) + { + tmpArray[i] = static_cast(cellType); + } + + m_out_file << " "; + + write_base64_binary_data(reinterpret_cast(&(tmpArray[0])), + sizeof(uint8_t) * m_nbCells); + + delete[] tmpArray; + + m_out_file << "\n"; + + } // end vtk_binary_enabled + + m_out_file << " \n"; + + /* + * Close Cells section. + */ + m_out_file << " \n"; + +} // VTKWriter::write_connectivity + +// ================================================================ +// ================================================================ +template +void +VTKWriter::write_appended_binary_geometry(std::vector> & nodes_location) +{ + + // 4 nodes for a quadrangle in 2d + // 8 nodes for a hexahedron in 3d + int nbNodesPerCell = (dim == 2) ? 4 : 8; + + // total number of nodes (should be equal to nodes_location size) + int64_t nbNodesTotal = m_nbCells * nbNodesPerCell; + + /* + * Write nodes location. + */ + { + // this is only necessary for binary output + std::vector vertices; + + for (int64_t i = 0; i < nbNodesTotal; ++i) + { + + vertices.push_back(nodes_location[i][IX]); + vertices.push_back(nodes_location[i][IY]); + vertices.push_back(nodes_location[i][IZ]); + + } // end for i + + uint64_t size = sizeof(float) * nbNodesTotal * 3; + m_out_file.write(reinterpret_cast(&size), sizeof(uint64_t)); + m_out_file.write(reinterpret_cast(&(vertices[0])), size); + + vertices.clear(); + + } // end write nodes location + + /* + * Write connectivity. + */ + { + // this is only necessary for binary output + std::vector connectivity; + + for (int64_t i = 0; i < m_nbCells; ++i) + { + + // offset to the first nodes in this cell + uint64_t offset = i * nbNodesPerCell; + for (uint8_t index = 0; index < nbNodesPerCell; ++index) + connectivity.push_back(offset + index); + + } // for i + + uint64_t size = sizeof(uint64_t) * m_nbCells * nbNodesPerCell; + m_out_file.write(reinterpret_cast(&size), sizeof(uint64_t)); + m_out_file.write(reinterpret_cast(&(connectivity[0])), size); + + connectivity.clear(); + + } // end write connectivity + + /* + * Write offsets. + */ + { + std::vector offsets; + + // number of nodes per cell is 4 in 2D + for (int64_t i = 1; i <= m_nbCells; ++i) + { + offsets.push_back(4 * i); + } + + uint64_t size = sizeof(uint64_t) * m_nbCells; + m_out_file.write(reinterpret_cast(&size), sizeof(uint64_t)); + m_out_file.write(reinterpret_cast(&(offsets[0])), size); + offsets.clear(); + + } // end write offsets + + /* + * Write cell types. + */ + { + std::vector celltypes; + + // 9 means "Quad" - 12 means "Hexahedron" + int cellType = (dim == 2) ? 9 : 12; + + for (int64_t i = 0; i < m_nbCells; ++i) + { + celltypes.push_back(cellType); + } + + uint64_t size = sizeof(unsigned char) * m_nbCells; + m_out_file.write(reinterpret_cast(&size), sizeof(uint64_t)); + m_out_file.write(reinterpret_cast(&(celltypes[0])), size); + celltypes.clear(); + } + +} // write_appended_binary_geometry + +} // namespace io + +} // namespace kalypsso + +#endif // KALYPSSO_UTILS_IO_VTKWRITER_H diff --git a/src/kalypsso/utils/io/libb64/AUTHORS b/src/kalypsso/utils/io/libb64/AUTHORS new file mode 100644 index 0000000..af68737 --- /dev/null +++ b/src/kalypsso/utils/io/libb64/AUTHORS @@ -0,0 +1,7 @@ +libb64: Base64 Encoding/Decoding Routines +====================================== + +Authors: +------- + +Chris Venter chris.venter@gmail.com http://rocketpod.blogspot.com diff --git a/src/kalypsso/utils/io/libb64/CHANGELOG b/src/kalypsso/utils/io/libb64/CHANGELOG new file mode 100644 index 0000000..27be0a5 --- /dev/null +++ b/src/kalypsso/utils/io/libb64/CHANGELOG @@ -0,0 +1,16 @@ +libb64: Base64 Encoding/Decoding Routines +====================================== + +## Changelog ## + +Version 1.1 Release +------------------- +Modified encode.h to (correctly) read from the iostream argument, +instead of std::cin. +Thanks to Peter K. Lee for the heads-up. + +No API changes. + +Version 1.0 Release +------------------- +The current content is the changeset. diff --git a/src/kalypsso/utils/io/libb64/LICENSE b/src/kalypsso/utils/io/libb64/LICENSE new file mode 100644 index 0000000..ae8a7b9 --- /dev/null +++ b/src/kalypsso/utils/io/libb64/LICENSE @@ -0,0 +1,29 @@ +Copyright-Only Dedication (based on United States law) +or Public Domain Certification + +The person or persons who have associated work with this document (the +"Dedicator" or "Certifier") hereby either (a) certifies that, to the best of +his knowledge, the work of authorship identified is in the public domain of the +country from which the work is published, or (b) hereby dedicates whatever +copyright the dedicators holds in the work of authorship identified below (the +"Work") to the public domain. A certifier, moreover, dedicates any copyright +interest he may have in the associated work, and for these purposes, is +described as a "dedicator" below. + +A certifier has taken reasonable steps to verify the copyright status of this +work. Certifier recognizes that his good faith efforts may not shield him from +liability if in fact the work certified is not in the public domain. + +Dedicator makes this dedication for the benefit of the public at large and to +the detriment of the Dedicator's heirs and successors. Dedicator intends this +dedication to be an overt act of relinquishment in perpetuity of all present +and future rights under copyright law, whether vested or contingent, in the +Work. Dedicator understands that such relinquishment of all rights includes +the relinquishment of all rights to enforce (by lawsuit or otherwise) those +copyrights in the Work. + +Dedicator recognizes that, once placed in the public domain, the Work may be +freely reproduced, distributed, transmitted, used, modified, built upon, or +otherwise exploited by anyone for any purpose, commercial or non-commercial, +and in any way, including by methods that have not yet been invented or +conceived. diff --git a/src/kalypsso/utils/io/libb64/README b/src/kalypsso/utils/io/libb64/README new file mode 100644 index 0000000..5051392 --- /dev/null +++ b/src/kalypsso/utils/io/libb64/README @@ -0,0 +1,142 @@ +b64: Base64 Encoding/Decoding Routines +====================================== + +Overview: +-------- +libb64 is a library of ANSI C routines for fast encoding/decoding data into and +from a base64-encoded format. C++ wrappers are included, as well as the source +code for standalone encoding and decoding executables. + +base64 consists of ASCII text, and is therefore a useful encoding for storing +binary data in a text file, such as xml, or sending binary data over text-only +email. + +References: +---------- +* Wikipedia article: + http://en.wikipedia.org/wiki/Base64 +* base64, another implementation of a commandline en/decoder: + http://www.fourmilab.ch/webtools/base64/ + +Why? +--- +I did this because I need an implementation of base64 encoding and decoding, +without any licensing problems. Most OS implementations are released under +either the GNU/GPL, or a BSD-variant, which is not what I require. + +Also, the chance to actually use the co-routine implementation in code is rare, +and its use here is fitting. I couldn't pass up the chance. +For more information on this technique, see "Coroutines in C", by Simon Tatham, +which can be found online here: +http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html + +So then, under which license do I release this code? On to the next section... + +License: +------- +This work is released under into the Public Domain. +It basically boils down to this: I put this work in the public domain, and you +can take it and do whatever you want with it. + +An example of this "license" is the Creative Commons Public Domain License, a +copy of which can be found in the LICENSE file, and also online at +http://creativecommons.org/licenses/publicdomain/ + +Commandline Use: +--------------- +Two pairs of executables are available: +*) b64enc and b64dec, and +*) encoder and decoder + +Both pairs function in the same way: the encoding half accepts data from the +standard input, and spits out the base64-encoded data to standard output. +The decoding half does the reverse: it accepts base64-encoded data on standard +input, and spits out the plain data on standard output. + +This allows for direct use, as well as easy integration into a piped command. + +For example, to encode file, run +$ cat file | ./encode > file.txt +and to decode the text into the original file +$ cat file.txt | ./decode > file2 + +file and file2 is 100% identical, file.txt is a 100% pure ASCII file. + +Programming: +----------- +Some C++ wrappers are provided as well, so you don't have to get your hands +dirty. Encoding from standard input to standard output is as simple as + + #include + #include + int main() + { + base64::encoder E; + E.encode(std::cin, std::cout); + return 0; + } + +Both standalone executables and a static library is provided in the package, + +Implementation: +-------------- +It is DAMN fast, if I may say so myself. The C code uses a little trick which +has been used to implement coroutines, of which one can say that this +implementation is an example. + +The trick involves the fact that a switch-statement may legally cross into +sub-blocks. A very thorough and enlightening essay on co-routines in C, using +this method, can be found in the above mentioned "Coroutines in C", by Simon +Tatham: http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html + +For example, an RLE decompressing routine, adapted from the article: +1 static int STATE = 0; +2 static int len, c; +3 switch (STATE) +4 { +5 while (1) +6 { +7 c = getchar(); +8 if (c == EOF) return EOF; +9 if (c == 0xFF) { +10 len = getchar(); +11 c = getchar(); +12 while (len--) +13 { +14 STATE = 0; +15 return c; +16 case 0: +17 } +18 } else +19 STATE = 1; +20 return c; +21 case 1: +22 } +23 } +24 } + +As can be seen from this example, a coroutine depends on a state variable, +which it sets directly before exiting (lines 14 and 119). The next time the +routine is entered, the switch moves control to the specific point directly +after the previous exit (lines 16 and 21).hands + +(As an aside, in the mentioned article the combination of the top-level switch, +the various setting of the state, the return of a value, and the labelling of +the exit point is wrapped in #define macros, making the structure of the +routine even clearer.) + +The obvious problem with any such routine is the static keyword. +Any static variables in a function spell doom for multithreaded applications. +Also, in situations where this coroutine is used by more than one other +coroutines, the consistency is disturbed. + +What is needed is a structure for storing these variabled, which is passed to +the routine separately. This obviously breaks the modularity of the function, +since now the caller has to worry about and care for the internal state of the +routine (the callee). This allows for a fast, multithreading-enabled +implementation, which may (obviously) be wrapped in a C++ object for ease of +use. + +The base64 encoding and decoding functionality in this package is implemented +in exactly this way, providing both a high-speed high-maintenance C interface, +and a wrapped C++ which is low-maintenance and only slightly less performant. diff --git a/src/kalypsso/utils/io/libb64/README2 b/src/kalypsso/utils/io/libb64/README2 new file mode 100644 index 0000000..b9e7a93 --- /dev/null +++ b/src/kalypsso/utils/io/libb64/README2 @@ -0,0 +1 @@ +adapted from libsc (C. Burstedde, https://github.com/cburstedde/libsc under LGPL) diff --git a/src/kalypsso/utils/io/libb64/b64dec.c b/src/kalypsso/utils/io/libb64/b64dec.c new file mode 100644 index 0000000..43c228a --- /dev/null +++ b/src/kalypsso/utils/io/libb64/b64dec.c @@ -0,0 +1,42 @@ +/* + * adapted from libb64 by CB + */ + +/* +b64dec.c - c source to a base64 decoder + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#include +#include +#include + +int +main(void) +{ + const size_t readsize = 4096; + char * code = 0; + char * plaintext = 0; + size_t codelength; + size_t plainlength; + base64_decodestate state; + + code = (char *)malloc(sizeof(char) * readsize); + plaintext = (char *)malloc(sizeof(char) * readsize); + + base64_init_decodestate(&state); + + do + { + codelength = fread((void *)code, sizeof(char), readsize, stdin); + plainlength = base64_decode_block(code, codelength, plaintext, &state); + (void)fwrite((void *)plaintext, sizeof(char), plainlength, stdout); + } while (!feof(stdin) && codelength > 0); + + free(code); + free(plaintext); + + return 0; +} diff --git a/src/kalypsso/utils/io/libb64/b64enc.c b/src/kalypsso/utils/io/libb64/b64enc.c new file mode 100644 index 0000000..62a7a03 --- /dev/null +++ b/src/kalypsso/utils/io/libb64/b64enc.c @@ -0,0 +1,45 @@ +/* + * adapted from libb64 by CB + */ + +/* +b64enc.c - c source to a base64 encoder + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#include +#include +#include + +int +main(void) +{ + const size_t readsize = 4096; + char * plaintext = 0; + char * code = 0; + size_t plainlength; + size_t codelength; + base64_encodestate state; + + code = (char *)malloc(sizeof(char) * readsize * 2); + plaintext = (char *)malloc(sizeof(char) * readsize); + + base64_init_encodestate(&state); + + do + { + plainlength = fread((void *)plaintext, sizeof(char), readsize, stdin); + codelength = base64_encode_block(plaintext, plainlength, code, &state); + (void)fwrite((void *)code, sizeof(char), codelength, stdout); + } while (!feof(stdin) && plainlength > 0); + + codelength = base64_encode_blockend(code, &state); + (void)fwrite((void *)code, sizeof(char), codelength, stdout); + + free(code); + free(plaintext); + + return 0; +} diff --git a/src/kalypsso/utils/io/libb64/cdecode.c b/src/kalypsso/utils/io/libb64/cdecode.c new file mode 100644 index 0000000..1813170 --- /dev/null +++ b/src/kalypsso/utils/io/libb64/cdecode.c @@ -0,0 +1,98 @@ +/* + * adapted from libb64 by CB + */ + +/* +cdecoder.c - c source to a base64 decoding algorithm implementation + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#include "libb64.h" + +static inline char +base64_decode_value(char value_in) +{ + static const char decoding[] = { 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, + -1, -1, -2, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 }; + static const char decoding_size = (char)sizeof(decoding); + + value_in -= 43; + return (value_in < 0 || value_in >= decoding_size) ? -1 : decoding[(int)value_in]; +} + +void +base64_init_decodestate (base64_decodestate * state_in) +{ + state_in->step = step_a; + state_in->plainchar = 0; +} + +size_t +base64_decode_block (const char *code_in, size_t length_in, + char *plaintext_out, base64_decodestate * state_in) +{ + /*@unused@ */ + const char *codechar = code_in; + char *plainchar = plaintext_out; + /*@unused@ */ + char fragment; + + *plainchar = state_in->plainchar; + + switch (state_in->step) { + while (1) { + case step_a: + do { + if (codechar == code_in + length_in) { + state_in->step = step_a; + state_in->plainchar = *plainchar; + return (size_t) (plainchar - plaintext_out); + } + fragment = base64_decode_value (*codechar++); + } while (fragment < 0); + *plainchar = (char) ((fragment & 0x03f) << 2); + case step_b: + do { + if (codechar == code_in + length_in) { + state_in->step = step_b; + state_in->plainchar = *plainchar; + return (size_t) (plainchar - plaintext_out); + } + fragment = base64_decode_value (*codechar++); + } while (fragment < 0); + *plainchar = (char) (*plainchar | ((fragment & 0x030) >> 4)); + ++plainchar; + *plainchar = (char) ((fragment & 0x00f) << 4); + case step_c: + do { + if (codechar == code_in + length_in) { + state_in->step = step_c; + state_in->plainchar = *plainchar; + return (size_t) (plainchar - plaintext_out); + } + fragment = base64_decode_value (*codechar++); + } while (fragment < 0); + *plainchar = (char) (*plainchar | ((fragment & 0x03c) >> 2)); + ++plainchar; + *plainchar = (char) ((fragment & 0x003) << 6); + case step_d: + do { + if (codechar == code_in + length_in) { + state_in->step = step_d; + state_in->plainchar = *plainchar; + return (size_t) (plainchar - plaintext_out); + } + fragment = base64_decode_value (*codechar++); + } while (fragment < 0); + *plainchar = (char) (*plainchar | (fragment & 0x03f)); + ++plainchar; + } + } + /* control should not reach here */ + return (size_t) (plainchar - plaintext_out); +} diff --git a/src/kalypsso/utils/io/libb64/cencode.c b/src/kalypsso/utils/io/libb64/cencode.c new file mode 100644 index 0000000..a612368 --- /dev/null +++ b/src/kalypsso/utils/io/libb64/cencode.c @@ -0,0 +1,119 @@ +/* + * adapted from libb64 by CB + */ + +/* +cencoder.c - c source to a base64 encoding algorithm implementation + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#include "libb64.h" + +const int CHARS_PER_LINE = 72; + +static inline char +base64_encode_value(char value_in) +{ + static const char * encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + return value_in > 63 ? '=' : encoding[(int)value_in]; +} + +void +base64_init_encodestate (base64_encodestate * state_in) +{ + state_in->step = step_A; + state_in->result = 0; + state_in->stepcount = 0; +} + +size_t +base64_encode_block (const char *plaintext_in, size_t length_in, + char *code_out, base64_encodestate * state_in) +{ + /*@unused@ */ + const char *plainchar = plaintext_in; + /*@unused@ */ + const char *const plaintextend = plaintext_in + length_in; + char *codechar = code_out; + char result; + /*@unused@ */ + char fragment; + + result = state_in->result; + + switch (state_in->step) { + while (1) { + case step_A: + if (plainchar == plaintextend) { + state_in->result = result; + state_in->step = step_A; + return (size_t) (codechar - code_out); + } + fragment = *plainchar++; + result = (char) ((fragment & 0x0fc) >> 2); + *codechar++ = base64_encode_value (result); + result = (char) ((fragment & 0x003) << 4); + case step_B: + if (plainchar == plaintextend) { + state_in->result = result; + state_in->step = step_B; + return (size_t) (codechar - code_out); + } + fragment = *plainchar++; + result = (char) (result | ((fragment & 0x0f0) >> 4)); + *codechar++ = base64_encode_value (result); + result = (char) ((fragment & 0x00f) << 2); + case step_C: + if (plainchar == plaintextend) { + state_in->result = result; + state_in->step = step_C; + return (size_t) (codechar - code_out); + } + fragment = *plainchar++; + result = (char) (result | ((fragment & 0x0c0) >> 6)); + *codechar++ = base64_encode_value (result); + result = (char) ((fragment & 0x03f) >> 0); + *codechar++ = base64_encode_value (result); + + ++(state_in->stepcount); + /* CB: disable wrapping by default */ +#ifdef SC_BASE64_WRAP + if (state_in->stepcount == CHARS_PER_LINE / 4) { + *codechar++ = '\n'; + state_in->stepcount = 0; + } +#endif + } + } + /* control should not reach here */ + return (size_t) (codechar - code_out); +} + +size_t +base64_encode_blockend (char *code_out, base64_encodestate * state_in) +{ + char *codechar = code_out; + + switch (state_in->step) { + case step_B: + *codechar++ = base64_encode_value (state_in->result); + *codechar++ = '='; + *codechar++ = '='; + break; + case step_C: + *codechar++ = base64_encode_value (state_in->result); + *codechar++ = '='; + break; + case step_A: + break; + } + /* CB: remove final newline by default */ +#ifdef SC_BASE64_WRAP + *codechar++ = '\n'; +#endif + + return (size_t) (codechar - code_out); +} diff --git a/src/kalypsso/utils/io/libb64/libb64.h b/src/kalypsso/utils/io/libb64/libb64.h new file mode 100644 index 0000000..6925168 --- /dev/null +++ b/src/kalypsso/utils/io/libb64/libb64.h @@ -0,0 +1,144 @@ +/* + * adapted from libb64 by CB + */ + +/* #define SC_BASE64_WRAP */ + +#include + +/* +cdecode.h - c header for a base64 decoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CDECODE_H +# define BASE64_CDECODE_H + +# ifdef __cplusplus +extern "C" +{ +# if 0 +} +# endif +# endif + + typedef enum + { + step_a, + step_b, + step_c, + step_d + } base64_decodestep; + + typedef struct + { + base64_decodestep step; + char plainchar; + } base64_decodestate; + + /** This function needs to be called to initialize the internal decoder state. + * Does not allocate any memory so no cleanup function is necessary after use. + * \param [out] state_in Internal state of decoder. + */ + void + base64_init_decodestate(base64_decodestate * state_in); + + /** Decode a chunk of data. + * This function can be called multiple times for the same state_in. + * \param [in] code_in Data in base64 encoding. + * \param [in] length_in Length of code_in in bytes. + * \param [out] plaintext_out Memory of at least length_in bytes that will + * contain the plaintext on output. + * \param [in,out] state_in Internal state of decoder. + * \return Byte length of decoded data in plaintext_out. + */ + size_t + base64_decode_block(const char * code_in, + size_t length_in, + char * plaintext_out, + base64_decodestate * state_in); + +# ifdef __cplusplus +# if 0 +{ +# endif +} +# endif + +#endif /* BASE64_CDECODE_H */ + +/* +cencode.h - c header for a base64 encoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CENCODE_H +# define BASE64_CENCODE_H + +# ifdef __cplusplus +extern "C" +{ +# if 0 +} +# endif +# endif + + typedef enum + { + step_A, + step_B, + step_C + } base64_encodestep; + + typedef struct + { + base64_encodestep step; + char result; + int stepcount; + } base64_encodestate; + + /** This function needs to be called to initialize the internal encoder state. + * Does not allocate any memory so no cleanup function is necessary after use. + * \param [out] state_in Internal state of encoder. + */ + void + base64_init_encodestate(base64_encodestate * state_in); + + /** Encode a chunk of data. + * This function can be called multiple times for the same state_in. + * \param [in] plaintext_in Data to be base64 encoded. + * \param [in] length_in Length of plaintext_in in bytes. + * \param [out] code_out Memory of at least 2 * length_in that will + * contain the base64 encoded data on output. + * \param [in,out] state_in Internal state of encoder. + * \return Byte length of encoded data in code_out. + */ + size_t + base64_encode_block(const char * plaintext_in, + size_t length_in, + char * code_out, + base64_encodestate * state_in); + + /** Flush remaining code bytes after all input data have been encoded. + * Must be called when the encoding is done to create valid base64 data. + * \param [out] code_out Memory of at least 4 bytes that will contain + * the final encoded bits. + * \param [in,out] state_in Internal state of encoder. + * Needs base64_init_encodestate to be used again. + * \return Number of final bytes written to code_out. + */ + size_t + base64_encode_blockend(char * code_out, base64_encodestate * state_in); + +# ifdef __cplusplus +# if 0 +{ +# endif +} +# endif + +#endif /* BASE64_CENCODE_H */ diff --git a/src/kalypsso/utils/log/CMakeLists.txt b/src/kalypsso/utils/log/CMakeLists.txt new file mode 100644 index 0000000..7773dd9 --- /dev/null +++ b/src/kalypsso/utils/log/CMakeLists.txt @@ -0,0 +1,48 @@ +add_library(kalypsso_log kalypsso_log.h kalypsso_log.cpp) + +target_include_directories( + kalypsso_log + PUBLIC $ $ + $) + +if(KALYPSSO_CORE_USE_MPI) + target_link_libraries(kalypsso_log PUBLIC MPI::MPI_CXX) +endif(KALYPSSO_CORE_USE_MPI) + +if(KALYPSSO_CORE_USE_SPDLOG) + target_compile_definitions(kalypsso_log PUBLIC KALYPSSO_CORE_USE_SPDLOG) + + target_compile_definitions(kalypsso_log PUBLIC -DSPDLOG_ACTIVE_LEVEL=${KALYPSSO_CORE_LOG_LEVEL}) + + target_link_libraries(kalypsso_log PRIVATE kalypsso_warnings kalypsso_core_config) + + target_link_libraries(kalypsso_log PUBLIC spdlog::spdlog) +endif(KALYPSSO_CORE_USE_SPDLOG) + +# Add an alias so that library can be used inside the build tree +add_library(kalypsso::log ALIAS kalypsso_log) + +# +# Get prepared for install +# +include(${PROJECT_SOURCE_DIR}/cmake/setup_install.cmake) + +# +# install libraries and headers +# +install( + TARGETS kalypsso_log + EXPORT kalypsso-targets + LIBRARY DESTINATION ${INSTALL_LIBDIR} + ARCHIVE DESTINATION ${INSTALL_LIBDIR} + RUNTIME DESTINATION ${INSTALL_BINDIR}) + +# +# install headers (exclude cpp files) +# +install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DESTINATION ${INSTALL_INCLUDEDIR}/kalypsso/utils + FILES_MATCHING + PATTERN "*.h" + PATTERN "*.cpp" EXCLUDE) diff --git a/src/kalypsso/utils/log/kalypsso_log.cpp b/src/kalypsso/utils/log/kalypsso_log.cpp new file mode 100644 index 0000000..309bc10 --- /dev/null +++ b/src/kalypsso/utils/log/kalypsso_log.cpp @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file kalypsso_log.cpp + */ +#include +#include + +#include +#include +#include +#include +#include // for shared_ptr + +#ifdef KALYPSSO_CORE_USE_MPI +# include +#endif // KALYPSSO_CORE_USE_MPI + +namespace kalypsso +{ + +//! return the number of digits to represent an integer +int +numDigits(int64_t n) +{ + if (n / 10 == 0) + return 1; + return 1 + numDigits(n / 10); +} + +// should be properly initialized in kalypsso_spdlog_config +int spdlog_mpi_rank = -1; + +void +kalypsso_spdlog_config(int & argc, char **& argv, int mpi_rank, int mpi_size) +{ + kalypsso::spdlog_mpi_rank = mpi_rank; + +#ifdef KALYPSSO_CORE_USE_SPDLOG + // create kalypsso_master logger - only MPI rank 0 involved + auto kalypsso_logger_master = spdlog::stdout_color_mt("kalypsso_mpi_master"); + +# ifdef KALYPSSO_CORE_ENABLE_DEBUG + kalypsso_logger_master->set_pattern("[kalypsso master] [%^%l%$] [%s:%#] %v"); +# else + kalypsso_logger_master->set_pattern("[kalypsso master] [%^%l%$] %v"); +# endif + + // create kalypsso_all logger - all MPI proc involved + // logger name is built using MPI rank + auto kalypsso_logger_all = spdlog::stdout_color_mt("kalypsso_mpi_all"); + std::string kalypsso_logger_all_pattern = [=]() { + std::stringstream ss; +# ifdef KALYPSSO_CORE_ENABLE_DEBUG + ss << "[kalypsso " << std::setw(numDigits(mpi_size)) << mpi_rank << "] [%^%l%$] [%s:%#] %v"; +# else + ss << "[kalypsso " << std::setw(numDigits(mpi_size)) << mpi_rank << "] [%^%l%$] %v"; +# endif + return ss.str(); + }(); + kalypsso_logger_all->set_pattern(kalypsso_logger_all_pattern); + + // default log_level, default is info (everything above is printed) + spdlog::cfg::load_env_levels(); + + // load log levels from command line arguments: + spdlog::cfg::load_argv_levels(argc, argv); + + if (kalypsso::spdlog_mpi_rank == 0) + { + printf("================================================ \n"); + printf("START SPDLOG CONFIG\n"); + printf("================================================ \n"); + spdlog::apply_all([&](const std::shared_ptr & logger) { + if (logger->name().empty()) + { + return; // ignore default logger + } + std::cout << logger->name() << ": " << logger->level() << "\n"; + }); + printf("compile-time value of SPDLOG_ACTIVE_LEVEL is %d\n", SPDLOG_ACTIVE_LEVEL); + printf("================================================ \n"); + printf("END SPDLOG CONFIG\n"); + printf("================================================ \n"); + } +#endif +} // kalypsso_spdlog_config + +} // namespace kalypsso diff --git a/src/kalypsso/utils/log/kalypsso_log.h b/src/kalypsso/utils/log/kalypsso_log.h new file mode 100644 index 0000000..1b7f64d --- /dev/null +++ b/src/kalypsso/utils/log/kalypsso_log.h @@ -0,0 +1,206 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file kalypsso_log.h + * + * the macro defined here only require that the main of application defines + * two logger named "kalypsso_logger_master" and "kalypsso_logger_all" + * + * Minimal log level can be changed: + * - at compile-time using cmake variable KALYPSSO_CORE_LOG_LEVEL (default is + * SPDLOG_LEVEL_TRACE) + * - at run-time using env variable SPDLOG_LEVEL: + * export SPDLOG_LEVEL=trace + */ +#ifndef KALYPSSO_UTILS_LOG_KALYPSSO_LOG_H_ +#define KALYPSSO_UTILS_LOG_KALYPSSO_LOG_H_ + +#ifdef KALYPSSO_CORE_USE_SPDLOG +# include +# include // for loading levels from the environment variable +# include // for loading levels from the command line +# include +# include +#endif + +namespace kalypsso +{ + +extern int spdlog_mpi_rank; + +/** + * create and config default logger : + * - one for logging only in MPI master process + * - one for logging in all MPI processes + */ +void +kalypsso_spdlog_config(int & argc, char **& argv, int mpi_rank, int mpi_size); + +} // namespace kalypsso + + +// slightly modified log macro to log only in master MPI rank (rank == 0) +#ifndef KALYPSSO_TRACE +# if defined(KALYPSSO_CORE_USE_SPDLOG) && (SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_TRACE) +# define KALYPSSO_LOGGER_TRACE(logger, ...) \ + if (spdlog_mpi_rank == 0) \ + { \ + SPDLOG_LOGGER_CALL(logger, spdlog::level::trace, __VA_ARGS__); \ + } +# define KALYPSSO_TRACE(...) \ + KALYPSSO_LOGGER_TRACE(spdlog::get("kalypsso_mpi_master"), __VA_ARGS__) +# else +# define KALYPSSO_LOGGER_TRACE(logger, ...) (void)0 +# define KALYPSSO_TRACE(...) (void)0 +# endif +#endif + +#ifndef KALYPSSO_DEBUG +# if defined(KALYPSSO_CORE_USE_SPDLOG) && (SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_DEBUG) +# define KALYPSSO_LOGGER_DEBUG(logger, ...) \ + if (spdlog_mpi_rank == 0) \ + { \ + SPDLOG_LOGGER_CALL(logger, spdlog::level::debug, __VA_ARGS__); \ + } +# define KALYPSSO_DEBUG(...) \ + KALYPSSO_LOGGER_DEBUG(spdlog::get("kalypsso_mpi_master"), __VA_ARGS__) +# else +# define KALYPSSO_LOGGER_DEBUG(logger, ...) (void)0 +# define KALYPSSO_DEBUG(...) (void)0 +# endif +#endif + +#ifndef KALYPSSO_INFO +# if defined(KALYPSSO_CORE_USE_SPDLOG) && (SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_INFO) +# define KALYPSSO_LOGGER_INFO(logger, ...) \ + if (spdlog_mpi_rank == 0) \ + { \ + SPDLOG_LOGGER_CALL(logger, spdlog::level::info, __VA_ARGS__); \ + } +# define KALYPSSO_INFO(...) KALYPSSO_LOGGER_INFO(spdlog::get("kalypsso_mpi_master"), __VA_ARGS__) +# else +# define KALYPSSO_LOGGER_INFO(logger, ...) (void)0 +# define KALYPSSO_INFO(...) (void)0 +# endif +#endif + +#ifndef KALYPSSO_WARN +# if defined(KALYPSSO_CORE_USE_SPDLOG) && (SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_WARN) +# define KALYPSSO_LOGGER_WARN(logger, ...) \ + if (spdlog_mpi_rank == 0) \ + { \ + SPDLOG_LOGGER_CALL(logger, spdlog::level::warn, __VA_ARGS__); \ + } +# define KALYPSSO_WARN(...) KALYPSSO_LOGGER_WARN(spdlog::get("kalypsso_mpi_master"), __VA_ARGS__) +# else +# define KALYPSSO_LOGGER_WARN(logger, ...) (void)0 +# define KALYPSSO_WARN(...) (void)0 +# endif +#endif + +#ifndef KALYPSSO_ERROR +# if defined(KALYPSSO_CORE_USE_SPDLOG) && (SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_ERROR) +# define KALYPSSO_LOGGER_ERROR(logger, ...) \ + if (spdlog_mpi_rank == 0) \ + { \ + SPDLOG_LOGGER_CALL(logger, spdlog::level::err, __VA_ARGS__); \ + } +# define KALYPSSO_ERROR(...) \ + KALYPSSO_LOGGER_ERROR(spdlog::get("kalypsso_mpi_master"), __VA_ARGS__) +# else +# define KALYPSSO_LOGGER_ERROR(logger, ...) (void)0 +# define KALYPSSO_ERROR(...) (void)0 +# endif +#endif + +#ifndef KALYPSSO_CRITICAL +# if defined(KALYPSSO_CORE_USE_SPDLOG) && (SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_CRITICAL) +# define KALYPSSO_LOGGER_CRITICAL(logger, ...) \ + if (spdlog_mpi_rank == 0) \ + { \ + SPDLOG_LOGGER_CALL(logger, spdlog::level::critical, __VA_ARGS__); \ + } +# define KALYPSSO_CRITICAL(...) \ + KALYPSSO_LOGGER_CRITICAL(spdlog::get("kalypsso_mpi_master"), __VA_ARGS__) +# else +# define KALYPSSO_LOGGER_CRITICAL(logger, ...) (void)0 +# define KALYPSSO_CRITICAL(...) (void)0 +# endif +#endif + +// slightly modified log macro : all MPI proc prints +#ifndef KALYPSSO_TRACE_ALL +# if defined(KALYPSSO_CORE_USE_SPDLOG) && (SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_TRACE) +# define KALYPSSO_LOGGER_TRACE_ALL(logger, ...) \ + SPDLOG_LOGGER_CALL(logger, spdlog::level::trace, __VA_ARGS__) +# define KALYPSSO_TRACE_ALL(...) \ + KALYPSSO_LOGGER_TRACE_ALL(spdlog::get("kalypsso_mpi_all"), __VA_ARGS__) +# else +# define KALYPSSO_LOGGER_TRACE_ALL(logger, ...) (void)0 +# define KALYPSSO_TRACE_ALL(...) (void)0 +# endif +#endif + +#ifndef KALYPSSO_DEBUG_ALL +# if defined(KALYPSSO_CORE_USE_SPDLOG) && (SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_DEBUG) +# define KALYPSSO_LOGGER_DEBUG_ALL(logger, ...) \ + SPDLOG_LOGGER_CALL(logger, spdlog::level::debug, __VA_ARGS__) +# define KALYPSSO_DEBUG_ALL(...) \ + KALYPSSO_LOGGER_DEBUG_ALL(spdlog::get("kalypsso_mpi_all"), __VA_ARGS__) +# else +# define KALYPSSO_LOGGER_DEBUG_ALL(logger, ...) (void)0 +# define KALYPSSO_DEBUG_ALL(...) (void)0 +# endif +#endif + +#ifndef KALYPSSO_INFO_ALL +# if defined(KALYPSSO_CORE_USE_SPDLOG) && (SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_INFO) +# define KALYPSSO_LOGGER_INFO_ALL(logger, ...) \ + SPDLOG_LOGGER_CALL(logger, spdlog::level::info, __VA_ARGS__) +# define KALYPSSO_INFO_ALL(...) \ + KALYPSSO_LOGGER_INFO_ALL(spdlog::get("kalypsso_mpi_all"), __VA_ARGS__) +# else +# define KALYPSSO_LOGGER_INFO_ALL(logger, ...) (void)0 +# define KALYPSSO_INFO_ALL(...) (void)0 +# endif +#endif + +#ifndef KALYPSSO_WARN_ALL +# if defined(KALYPSSO_CORE_USE_SPDLOG) && (SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_WARN) +# define KALYPSSO_LOGGER_WARN_ALL(logger, ...) \ + SPDLOG_LOGGER_CALL(logger, spdlog::level::warn, __VA_ARGS__) +# define KALYPSSO_WARN_ALL(...) \ + KALYPSSO_LOGGER_WARN_ALL(spdlog::get("kalypsso_mpi_all"), __VA_ARGS__) +# else +# define KALYPSSO_LOGGER_WARN_ALL(logger, ...) (void)0 +# define KALYPSSO_WARN_ALL(...) (void)0 +# endif +#endif + +#ifndef KALYPSSO_ERROR_ALL +# if defined(KALYPSSO_CORE_USE_SPDLOG) && (SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_ERROR) +# define KALYPSSO_LOGGER_ERROR_ALL(logger, ...) \ + SPDLOG_LOGGER_CALL(logger, spdlog::level::err, __VA_ARGS__) +# define KALYPSSO_ERROR_ALL(...) \ + KALYPSSO_LOGGER_ERROR_ALL(spdlog::get("kalypsso_mpi_all"), __VA_ARGS__) +# else +# define KALYPSSO_LOGGER_ERROR_ALL(logger, ...) (void)0 +# define KALYPSSO_ERROR_ALL(...) (void)0 +# endif +#endif + +#ifndef KALYPSSO_CRITICAL_ALL +# if defined(KALYPSSO_CORE_USE_SPDLOG) && (SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_CRITICAL) +# define KALYPSSO_LOGGER_CRITICAL_ALL(logger, ...) \ + SPDLOG_LOGGER_CALL(logger, spdlog::level::critical, __VA_ARGS__) +# define KALYPSSO_CRITICAL_ALL(...) \ + KALYPSSO_LOGGER_CRITICAL_ALL(spdlog::get("kalypsso_mpi_all"), __VA_ARGS__) +# else +# define KALYPSSO_LOGGER_CRITICAL_ALL(logger, ...) (void)0 +# define KALYPSSO_CRITICAL_ALL(...) (void)0 +# endif +#endif + +#endif // KALYPSSO_UTILS_LOG_KALYPSSO_LOG_H_ diff --git a/src/kalypsso/utils/monitoring/CMakeLists.txt b/src/kalypsso/utils/monitoring/CMakeLists.txt new file mode 100644 index 0000000..8d765b4 --- /dev/null +++ b/src/kalypsso/utils/monitoring/CMakeLists.txt @@ -0,0 +1,45 @@ +add_library(kalypsso_monitoring HostTimer.h CudaBlockingTimer.h CudaAsyncTimer.h memory_utils.h + ProfilingManager.h ProfilingManager.cpp) + +target_include_directories( + kalypsso_monitoring + PUBLIC $ + $ + $) + +target_link_libraries(kalypsso_monitoring PRIVATE kalypsso_warnings kalypsso_core_config) + +target_link_libraries(kalypsso_monitoring PUBLIC kalypsso::config kalypsso::log kalypsso::mpiUtils + kalypsso::p4est_wrap Kokkos::kokkos) + +if(KALYPSSO_CORE_NVTX_ANNOTATION_ENABLE AND KALYPSSO_CORE_USE_NVTX_FROM_GITHUB) + target_link_libraries(kalypsso_monitoring PUBLIC nvtx3-c) +endif(KALYPSSO_CORE_NVTX_ANNOTATION_ENABLE AND KALYPSSO_CORE_USE_NVTX_FROM_GITHUB) + +# Add an alias so that library can be used inside the build tree +add_library(kalypsso::monitoring ALIAS kalypsso_monitoring) + +# +# Get prepared for install +# +include(${PROJECT_SOURCE_DIR}/cmake/setup_install.cmake) + +# +# install libraries and headers +# +install( + TARGETS kalypsso_monitoring + EXPORT kalypsso-targets + LIBRARY DESTINATION ${INSTALL_LIBDIR} + ARCHIVE DESTINATION ${INSTALL_LIBDIR} + RUNTIME DESTINATION ${INSTALL_BINDIR}) + +# +# install headers (exclude cpp files) +# +install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DESTINATION ${INSTALL_INCLUDEDIR}/kalypsso/utils + FILES_MATCHING + PATTERN "*.h" + PATTERN "*.cpp" EXCLUDE) diff --git a/src/kalypsso/utils/monitoring/CudaAsyncTimer.h b/src/kalypsso/utils/monitoring/CudaAsyncTimer.h new file mode 100644 index 0000000..7885a07 --- /dev/null +++ b/src/kalypsso/utils/monitoring/CudaAsyncTimer.h @@ -0,0 +1,218 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file CudaAsyncTimer.h + * \brief A simple timer class for CUDA based on events. + * + */ +#ifndef KALYPSSO_UTILS_MONITORING_CUDAASYNCTIMER_H_ +#define KALYPSSO_UTILS_MONITORING_CUDAASYNCTIMER_H_ + +#include + +#include + +#ifndef assertm +# define assertm(exp, msg) assert(((void)msg, exp)) +#endif + +namespace kalypsso +{ + +/** + * \brief a simple timer for CUDA kernel. + * + * cudaEventSynchronize is called with some delay (asynchronously) to avoid too much synchronization + * between CPU and GPU, and allow the CPU to perform useful stuff while the GPU is computing. + * + * The way to achieve this is to use an array of cuda events (start/stop), and use two indexes one + * to write/record events, and the other to read/synchronize then and compute accumulated time. + * These read/write indexes are always increasing; as they are stored on 64 bits integer the upper + * limit is sufficiently high to be never reached in practice. We use the rest of division by the + * size of the event array, to address the array. + * + */ +class CudaAsyncTimer +{ +protected: + class CUDAEvent + { + public: + CUDAEvent() { cudaEventCreate(&m_event); } + + CUDAEvent(const CUDAEvent &) = delete; + + ~CUDAEvent() { cudaEventDestroy(m_event); } + + void + record() + { + auto res = cudaEventRecord(m_event, 0); + assertm(res == cudaSuccess, "Recording cuda event failed"); + } + + bool + query() const + { + return cudaEventQuery(m_event) == cudaSuccess; + } + + void + synchronize() const + { + cudaEventSynchronize(m_event); + } + cudaEvent_t + event() const + { + return m_event; + } + + private: + cudaEvent_t m_event; + }; + + //! total number of pair of start/stop events + static constexpr int NUM_EVENTS = 32; + using idx_t = uint64_t; + + CUDAEvent startEv[NUM_EVENTS], stopEv[NUM_EVENTS]; + idx_t readIdx, writeIdx; + double total_time; + +public: + // ======================================================= + // ======================================================= + CudaAsyncTimer() + { + readIdx = 0; + writeIdx = 0; + total_time = 0.0; + } + + // ======================================================= + // ======================================================= + ~CudaAsyncTimer() {} + + // ======================================================= + // ======================================================= + /** + * record start event and process past events that are completed. + */ + void + start() + { + [[maybe_unused]] const auto processed_events = process_events(false); + + startEv[writeIdx % NUM_EVENTS].record(); + } + + // ======================================================= + // ======================================================= + /** + * record stop event. + */ + void + stop() + { + stopEv[writeIdx % NUM_EVENTS].record(); + assertm(writeIdx < std::numeric_limits::max(), "writeIdx is definitely too large"); + + writeIdx++; + } + + // ======================================================= + // ======================================================= + /** + * reset internal state + */ + void + reset() + { + readIdx = 0; + writeIdx = 0; + total_time = 0.0; + } + + // ======================================================= + // ======================================================= + /** + * return elapsed time in seconds (as record in total_time) + */ + double + elapsed() + { + // flush events to process + [[maybe_unused]] const auto processed_events = process_events(true); + assertm(readIdx == writeIdx, "Wrong read/write index after processing all events."); + return total_time; + } + +private: + // ======================================================= + // ======================================================= + /** + * Process a pair of start/stop event and accumulate time. + * + * \param[in] enforce_synchronization is true, CPU is blocked waiting for the stop event to happen + * + * When stop even is satisfied, total_time is updated, and the read index incremented. + */ + void + process_event(bool enforce_synchronize = true) + { + + float gpuTime; + + if (enforce_synchronize) + stopEv[readIdx % NUM_EVENTS].synchronize(); + cudaEventElapsedTime( + &gpuTime, startEv[readIdx % NUM_EVENTS].event(), stopEv[readIdx % NUM_EVENTS].event()); + readIdx++; + total_time += 1e-3 * static_cast(gpuTime); + + } // process_event + + // ======================================================= + // ======================================================= + /** + * Process pairs of start/stop events to accumulate time. + * + * \param[in] if flush is true, always call cudaEventSynchronize to make sure the stop event is + * completed. + * + * \return the number of events processed, i.e. events for which cudaEventQuery was successful. + */ + int + process_events(bool flush = false) + { + + int processed = 0; + constexpr bool NO_SYNC = false; + + // if the event stack is almost full, enforce synchronization to process events + // if flush is requested, enforce synchronization + while ((writeIdx - readIdx > NUM_EVENTS - 4) or (flush and readIdx < writeIdx)) + { + process_event(); + processed++; + } + + // just query for stop events that are satisfied, and if so process them + while (readIdx < writeIdx and stopEv[readIdx % NUM_EVENTS].query() == cudaSuccess) + { + process_event(NO_SYNC); + processed++; + } + + return processed; + + } // process_events + +}; // class CudaAsyncTimer + +} // namespace kalypsso + +#endif // KALYPSSO_UTILS_MONITORING_CUDAASYNCTIMER_H_ diff --git a/src/kalypsso/utils/monitoring/CudaBlockingTimer.h b/src/kalypsso/utils/monitoring/CudaBlockingTimer.h new file mode 100644 index 0000000..e6fe946 --- /dev/null +++ b/src/kalypsso/utils/monitoring/CudaBlockingTimer.h @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file CudaBlockingTimer.h + * \brief A simple timer class for CUDA based on events. + * + */ +#ifndef KALYPSSO_UTILS_MONITORING_CUDABLOCKINGTIMER_H_ +#define KALYPSSO_UTILS_MONITORING_CUDABLOCKINGTIMER_H_ + +#include + +namespace kalypsso +{ + +/** + * \brief a simple timer for CUDA kernel using CUDA events. + * \sa https://docs.nvidia.com/cuda/cuda-runtime-api/group__CUDART__EVENT.html + * CUDA kernels run asynchronously from CPU, + * so don't use CPU timing routines. + * + * Beware that stopping timer imply a call cudaEventSynchronize, so the CPU will wait (block) + * for the stop event event to happen; you won't be able to overlap computing on GPU with computing + * on CPU. This timer is ok for benchmarking the execution of a given kernel. + */ +class CudaBlockingTimer +{ +protected: + //! CUDA start and stop events + cudaEvent_t startEv, stopEv; + + //! total accumulated duration + double total_time; + +public: + CudaBlockingTimer() + { + cudaEventCreate(&startEv); + cudaEventCreate(&stopEv); + total_time = 0.0; + } + + ~CudaBlockingTimer() + { + cudaEventDestroy(startEv); + cudaEventDestroy(stopEv); + } + + //! start timer, push a start even in a cuda stream + void + start() + { + cudaEventRecord(startEv, 0); + } + + //! reset accumulated duration + void + reset() + { + total_time = 0.0; + } + + //! stop timer and accumulate time in seconds + void + stop() + { + float gpuTime; + cudaEventRecord(stopEv, 0); + cudaEventSynchronize(stopEv); + + // get elapsed time in milliseconds + cudaEventElapsedTime(&gpuTime, startEv, stopEv); + + // accumulate duration in seconds + total_time += (double)1e-3 * gpuTime; + } + + //! return elapsed time in seconds (as record in total_time) + double + elapsed() const + { + return total_time; + } + +}; // class CudaBlockingTimer + +} // namespace kalypsso + +#endif // KALYPSSO_UTILS_MONITORING_CUDABLOCKINGTIMER_H_ diff --git a/src/kalypsso/utils/monitoring/HostTimer.h b/src/kalypsso/utils/monitoring/HostTimer.h new file mode 100644 index 0000000..dec8aab --- /dev/null +++ b/src/kalypsso/utils/monitoring/HostTimer.h @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file HostTimer.h + * \brief A simple timer class for CPU time measurement. + * + */ +#ifndef KALYPSSO_UTILS_MONITORING_HOSTTIMER_H_ +#define KALYPSSO_UTILS_MONITORING_HOSTTIMER_H_ + +#include + +namespace kalypsso +{ + +using namespace std::literals::chrono_literals; // for string literals 1s = 1 second + +/** + * \brief A simple Timer class to perform time measurement on CPU. + * + * This class supports multiple calls to start/stop. + * When calling stop duration between the last call of start and stop is accumulated in a internal + * variable. + * Accumulated time can be retrieve using elapsed method. + * Timer can be reset (accumulated sets to zero) + * + */ +class HostTimer +{ +public: + using timer_t = std::chrono::high_resolution_clock; + using time_point_t = timer_t::time_point; + using duration_ns_t = std::chrono::nanoseconds; + + //! default constructor, timing starts rightaway + HostTimer() + : m_start(timer_t::now()) + , m_total_time(0s) + {} + + //! destructor + ~HostTimer() = default; + + //! start time measure + void + start() + { + m_start = timer_t::now(); + } + + //! stop time measure and add result to total_time + void + stop() + { + m_total_time += std::chrono::duration_cast(timer_t::now() - m_start); + } + + //! return elapsed time in seconds (converted from total_time in nanoseconds) + double + elapsed() const + { + return static_cast(m_total_time.count()) * 1e-9; + } + + void + reset() + { + m_total_time = 0s; + } + +protected: + //! store start time point + time_point_t m_start; + + //! store total accumulated durations in nanoseconds + duration_ns_t m_total_time; + +}; // class HostTimer + +} // namespace kalypsso + +#endif // KALYPSSO_UTILS_MONITORING_HOSTTIMER_H_ diff --git a/src/kalypsso/utils/monitoring/NvtxProfiling.h b/src/kalypsso/utils/monitoring/NvtxProfiling.h new file mode 100644 index 0000000..1901f46 --- /dev/null +++ b/src/kalypsso/utils/monitoring/NvtxProfiling.h @@ -0,0 +1,410 @@ +#ifndef KALYPSSO_UTILS_MONITORING_NVTXPROFILING_H_ +#define KALYPSSO_UTILS_MONITORING_NVTXPROFILING_H_ + +#include + +namespace kalypsso +{ + +// Copyright (c) 2018, Eyal Rozenberg +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// clang-format off +struct Color_t +{ + using underlying_type = ::std::uint32_t; + using channel_value = ::std::uint8_t; + channel_value alpha, red, green, blue; + + static constexpr Color_t from_hex(underlying_type raw_argb) noexcept { + return { + static_cast ((raw_argb >> 24) & 0xFF), + static_cast ((raw_argb >> 16) & 0xFF), + static_cast ((raw_argb >> 8) & 0xFF), + static_cast ((raw_argb >> 0) & 0xFF), + }; + } + + operator underlying_type() const noexcept { return as_hex(); } + underlying_type as_hex() const noexcept + { + return + static_cast(alpha) << 24 | + static_cast(red) << 16 | + static_cast(green) << 8 | + static_cast(blue) << 0; + } + + // Use color definition: Google Material Design colors + // See https://material.google.com/style/color.html + // see also https://github.com/yse/easy_profiler + + static constexpr Color_t Red50() noexcept { return from_hex(0xffffebee); } + static constexpr Color_t Red100() noexcept { return from_hex(0xffffcdd2); } + static constexpr Color_t Red200() noexcept { return from_hex(0xffef9a9a); } + static constexpr Color_t Red300() noexcept { return from_hex(0xffe57373); } + static constexpr Color_t Red400() noexcept { return from_hex(0xffef5350); } + static constexpr Color_t Red500() noexcept { return from_hex(0xfff44336); } + static constexpr Color_t Red600() noexcept { return from_hex(0xffe53935); } + static constexpr Color_t Red700() noexcept { return from_hex(0xffd32f2f); } + static constexpr Color_t Red800() noexcept { return from_hex(0xffc62828); } + static constexpr Color_t Red900() noexcept { return from_hex(0xffb71c1c); } + static constexpr Color_t RedA100() noexcept { return from_hex(0xffff8a80); } + static constexpr Color_t RedA200() noexcept { return from_hex(0xffff5252); } + static constexpr Color_t RedA400() noexcept { return from_hex(0xffff1744); } + static constexpr Color_t RedA700() noexcept { return from_hex(0xffd50000); } + + static constexpr Color_t Pink50() noexcept { return from_hex(0xfffce4ec); } + static constexpr Color_t Pink100() noexcept { return from_hex(0xfff8bbd0); } + static constexpr Color_t Pink200() noexcept { return from_hex(0xfff48fb1); } + static constexpr Color_t Pink300() noexcept { return from_hex(0xfff06292); } + static constexpr Color_t Pink400() noexcept { return from_hex(0xffec407a); } + static constexpr Color_t Pink500() noexcept { return from_hex(0xffe91e63); } + static constexpr Color_t Pink600() noexcept { return from_hex(0xffd81b60); } + static constexpr Color_t Pink700() noexcept { return from_hex(0xffc2185b); } + static constexpr Color_t Pink800() noexcept { return from_hex(0xffad1457); } + static constexpr Color_t Pink900() noexcept { return from_hex(0xff880e4f); } + static constexpr Color_t PinkA100() noexcept { return from_hex(0xffff80ab); } + static constexpr Color_t PinkA200() noexcept { return from_hex(0xffff4081); } + static constexpr Color_t PinkA400() noexcept { return from_hex(0xfff50057); } + static constexpr Color_t PinkA700() noexcept { return from_hex(0xffc51162); } + + static constexpr Color_t Purple50 () noexcept { return from_hex(0xfff3e5f5); } + static constexpr Color_t Purple100() noexcept { return from_hex(0xffe1bee7); } + static constexpr Color_t Purple200() noexcept { return from_hex(0xffce93d8); } + static constexpr Color_t Purple300() noexcept { return from_hex(0xffba68c8); } + static constexpr Color_t Purple400() noexcept { return from_hex(0xffab47bc); } + static constexpr Color_t Purple500() noexcept { return from_hex(0xff9c27b0); } + static constexpr Color_t Purple600() noexcept { return from_hex(0xff8e24aa); } + static constexpr Color_t Purple700() noexcept { return from_hex(0xff7b1fa2); } + static constexpr Color_t Purple800() noexcept { return from_hex(0xff6a1b9a); } + static constexpr Color_t Purple900() noexcept { return from_hex(0xff4a148c); } + static constexpr Color_t PurpleA100() noexcept { return from_hex(0xffea80fc); } + static constexpr Color_t PurpleA200() noexcept { return from_hex(0xffe040fb); } + static constexpr Color_t PurpleA400() noexcept { return from_hex(0xffd500f9); } + static constexpr Color_t PurpleA700() noexcept { return from_hex(0xffaa00ff); } + + static constexpr Color_t DeepPurple50() noexcept { return from_hex(0xffede7f6); } + static constexpr Color_t DeepPurple100() noexcept { return from_hex(0xffd1c4e9); } + static constexpr Color_t DeepPurple200() noexcept { return from_hex(0xffb39ddb); } + static constexpr Color_t DeepPurple300() noexcept { return from_hex(0xff9575cd); } + static constexpr Color_t DeepPurple400() noexcept { return from_hex(0xff7e57c2); } + static constexpr Color_t DeepPurple500() noexcept { return from_hex(0xff673ab7); } + static constexpr Color_t DeepPurple600() noexcept { return from_hex(0xff5e35b1); } + static constexpr Color_t DeepPurple700() noexcept { return from_hex(0xff512da8); } + static constexpr Color_t DeepPurple800() noexcept { return from_hex(0xff4527a0); } + static constexpr Color_t DeepPurple900() noexcept { return from_hex(0xff311b92); } + static constexpr Color_t DeepPurpleA100() noexcept { return from_hex(0xffb388ff); } + static constexpr Color_t DeepPurpleA200() noexcept { return from_hex(0xff7c4dff); } + static constexpr Color_t DeepPurpleA400() noexcept { return from_hex(0xff651fff); } + static constexpr Color_t DeepPurpleA700() noexcept { return from_hex(0xff6200ea); } + + static constexpr Color_t Indigo50() noexcept { return from_hex(0xffe8eaf6); } + static constexpr Color_t Indigo100() noexcept { return from_hex(0xffc5cae9); } + static constexpr Color_t Indigo200() noexcept { return from_hex(0xff9fa8da); } + static constexpr Color_t Indigo300() noexcept { return from_hex(0xff7986cb); } + static constexpr Color_t Indigo400() noexcept { return from_hex(0xff5c6bc0); } + static constexpr Color_t Indigo500() noexcept { return from_hex(0xff3f51b5); } + static constexpr Color_t Indigo600() noexcept { return from_hex(0xff3949ab); } + static constexpr Color_t Indigo700() noexcept { return from_hex(0xff303f9f); } + static constexpr Color_t Indigo800() noexcept { return from_hex(0xff283593); } + static constexpr Color_t Indigo900() noexcept { return from_hex(0xff1a237e); } + static constexpr Color_t IndigoA100() noexcept { return from_hex(0xff8c9eff); } + static constexpr Color_t IndigoA200() noexcept { return from_hex(0xff536dfe); } + static constexpr Color_t IndigoA400() noexcept { return from_hex(0xff3d5afe); } + static constexpr Color_t IndigoA700() noexcept { return from_hex(0xff304ffe); } + + static constexpr Color_t Blue50() noexcept { return from_hex(0xffe3f2fd); } + static constexpr Color_t Blue100() noexcept { return from_hex(0xffbbdefb); } + static constexpr Color_t Blue200() noexcept { return from_hex(0xff90caf9); } + static constexpr Color_t Blue300() noexcept { return from_hex(0xff64b5f6); } + static constexpr Color_t Blue400() noexcept { return from_hex(0xff42a5f5); } + static constexpr Color_t Blue500() noexcept { return from_hex(0xff2196f3); } + static constexpr Color_t Blue600() noexcept { return from_hex(0xff1e88e5); } + static constexpr Color_t Blue700() noexcept { return from_hex(0xff1976d2); } + static constexpr Color_t Blue800() noexcept { return from_hex(0xff1565c0); } + static constexpr Color_t Blue900() noexcept { return from_hex(0xff0d47a1); } + static constexpr Color_t BlueA100() noexcept { return from_hex(0xff82b1ff); } + static constexpr Color_t BlueA200() noexcept { return from_hex(0xff448aff); } + static constexpr Color_t BlueA400() noexcept { return from_hex(0xff2979ff); } + static constexpr Color_t BlueA700() noexcept { return from_hex(0xff2962ff); } + + static constexpr Color_t LightBlue50() noexcept { return from_hex(0xffe1f5fe); } + static constexpr Color_t LightBlue100() noexcept { return from_hex(0xffb3e5fc); } + static constexpr Color_t LightBlue200() noexcept { return from_hex(0xff81d4fa); } + static constexpr Color_t LightBlue300() noexcept { return from_hex(0xff4fc3f7); } + static constexpr Color_t LightBlue400() noexcept { return from_hex(0xff29b6f6); } + static constexpr Color_t LightBlue500() noexcept { return from_hex(0xff03a9f4); } + static constexpr Color_t LightBlue600() noexcept { return from_hex(0xff039be5); } + static constexpr Color_t LightBlue700() noexcept { return from_hex(0xff0288d1); } + static constexpr Color_t LightBlue800() noexcept { return from_hex(0xff0277bd); } + static constexpr Color_t LightBlue900() noexcept { return from_hex(0xff01579b); } + static constexpr Color_t LightBlueA100() noexcept { return from_hex(0xff80d8ff); } + static constexpr Color_t LightBlueA200() noexcept { return from_hex(0xff40c4ff); } + static constexpr Color_t LightBlueA400() noexcept { return from_hex(0xff00b0ff); } + static constexpr Color_t LightBlueA700() noexcept { return from_hex(0xff0091ea); } + + static constexpr Color_t Cyan50() noexcept { return from_hex(0xffe0f7fa); } + static constexpr Color_t Cyan100() noexcept { return from_hex(0xffb2ebf2); } + static constexpr Color_t Cyan200() noexcept { return from_hex(0xff80deea); } + static constexpr Color_t Cyan300() noexcept { return from_hex(0xff4dd0e1); } + static constexpr Color_t Cyan400() noexcept { return from_hex(0xff26c6da); } + static constexpr Color_t Cyan500() noexcept { return from_hex(0xff00bcd4); } + static constexpr Color_t Cyan600() noexcept { return from_hex(0xff00acc1); } + static constexpr Color_t Cyan700() noexcept { return from_hex(0xff0097a7); } + static constexpr Color_t Cyan800() noexcept { return from_hex(0xff00838f); } + static constexpr Color_t Cyan900() noexcept { return from_hex(0xff006064); } + static constexpr Color_t CyanA100() noexcept { return from_hex(0xff84ffff); } + static constexpr Color_t CyanA200() noexcept { return from_hex(0xff18ffff); } + static constexpr Color_t CyanA400() noexcept { return from_hex(0xff00e5ff); } + static constexpr Color_t CyanA700() noexcept { return from_hex(0xff00b8d4); } + + static constexpr Color_t Teal50() noexcept { return from_hex(0xffe0f2f1); } + static constexpr Color_t Teal100() noexcept { return from_hex(0xffb2dfdb); } + static constexpr Color_t Teal200() noexcept { return from_hex(0xff80cbc4); } + static constexpr Color_t Teal300() noexcept { return from_hex(0xff4db6ac); } + static constexpr Color_t Teal400() noexcept { return from_hex(0xff26a69a); } + static constexpr Color_t Teal500() noexcept { return from_hex(0xff009688); } + static constexpr Color_t Teal600() noexcept { return from_hex(0xff00897b); } + static constexpr Color_t Teal700() noexcept { return from_hex(0xff00796b); } + static constexpr Color_t Teal800() noexcept { return from_hex(0xff00695c); } + static constexpr Color_t Teal900() noexcept { return from_hex(0xff004d40); } + static constexpr Color_t TealA100() noexcept { return from_hex(0xffa7ffeb); } + static constexpr Color_t TealA200() noexcept { return from_hex(0xff64ffda); } + static constexpr Color_t TealA400() noexcept { return from_hex(0xff1de9b6); } + static constexpr Color_t TealA700() noexcept { return from_hex(0xff00bfa5); } + + static constexpr Color_t Green50() noexcept { return from_hex(0xffe8f5e9); } + static constexpr Color_t Green100() noexcept { return from_hex(0xffc8e6c9); } + static constexpr Color_t Green200() noexcept { return from_hex(0xffa5d6a7); } + static constexpr Color_t Green300() noexcept { return from_hex(0xff81c784); } + static constexpr Color_t Green400() noexcept { return from_hex(0xff66bb6a); } + static constexpr Color_t Green500() noexcept { return from_hex(0xff4caf50); } + static constexpr Color_t Green600() noexcept { return from_hex(0xff43a047); } + static constexpr Color_t Green700() noexcept { return from_hex(0xff388e3c); } + static constexpr Color_t Green800() noexcept { return from_hex(0xff2e7d32); } + static constexpr Color_t Green900() noexcept { return from_hex(0xff1b5e20); } + static constexpr Color_t GreenA100() noexcept { return from_hex(0xffb9f6ca); } + static constexpr Color_t GreenA200() noexcept { return from_hex(0xff69f0ae); } + static constexpr Color_t GreenA400() noexcept { return from_hex(0xff00e676); } + static constexpr Color_t GreenA700() noexcept { return from_hex(0xff00c853); } + + static constexpr Color_t LightGreen50() noexcept { return from_hex(0xfff1f8e9); } + static constexpr Color_t LightGreen100() noexcept { return from_hex(0xffdcedc8); } + static constexpr Color_t LightGreen200() noexcept { return from_hex(0xffc5e1a5); } + static constexpr Color_t LightGreen300() noexcept { return from_hex(0xffaed581); } + static constexpr Color_t LightGreen400() noexcept { return from_hex(0xff9ccc65); } + static constexpr Color_t LightGreen500() noexcept { return from_hex(0xff8bc34a); } + static constexpr Color_t LightGreen600() noexcept { return from_hex(0xff7cb342); } + static constexpr Color_t LightGreen700() noexcept { return from_hex(0xff689f38); } + static constexpr Color_t LightGreen800() noexcept { return from_hex(0xff558b2f); } + static constexpr Color_t LightGreen900() noexcept { return from_hex(0xff33691e); } + static constexpr Color_t LightGreenA100() noexcept { return from_hex(0xffccff90); } + static constexpr Color_t LightGreenA200() noexcept { return from_hex(0xffb2ff59); } + static constexpr Color_t LightGreenA400() noexcept { return from_hex(0xff76ff03); } + static constexpr Color_t LightGreenA700() noexcept { return from_hex(0xff64dd17); } + + static constexpr Color_t Lime50() noexcept { return from_hex(0xfff9ebe7); } + static constexpr Color_t Lime100() noexcept { return from_hex(0xfff0f4c3); } + static constexpr Color_t Lime200() noexcept { return from_hex(0xffe6ee9c); } + static constexpr Color_t Lime300() noexcept { return from_hex(0xffdce775); } + static constexpr Color_t Lime400() noexcept { return from_hex(0xffd4e157); } + static constexpr Color_t Lime500() noexcept { return from_hex(0xffcddc39); } + static constexpr Color_t Lime600() noexcept { return from_hex(0xffc0ca33); } + static constexpr Color_t Lime700() noexcept { return from_hex(0xffafb42b); } + static constexpr Color_t Lime800() noexcept { return from_hex(0xff9e9d24); } + static constexpr Color_t Lime900() noexcept { return from_hex(0xff827717); } + static constexpr Color_t LimeA100() noexcept { return from_hex(0xfff4ff81); } + static constexpr Color_t LimeA200() noexcept { return from_hex(0xffeeff41); } + static constexpr Color_t LimeA400() noexcept { return from_hex(0xffc6ff00); } + static constexpr Color_t LimeA700() noexcept { return from_hex(0xffaeea00); } + + static constexpr Color_t Yellow50() noexcept { return from_hex(0xfffffde7); } + static constexpr Color_t Yellow100() noexcept { return from_hex(0xfffff9c4); } + static constexpr Color_t Yellow200() noexcept { return from_hex(0xfffff59d); } + static constexpr Color_t Yellow300() noexcept { return from_hex(0xfffff176); } + static constexpr Color_t Yellow400() noexcept { return from_hex(0xffffee58); } + static constexpr Color_t Yellow500() noexcept { return from_hex(0xffffeb3b); } + static constexpr Color_t Yellow600() noexcept { return from_hex(0xfffdd835); } + static constexpr Color_t Yellow700() noexcept { return from_hex(0xfffbc02d); } + static constexpr Color_t Yellow800() noexcept { return from_hex(0xfff9a825); } + static constexpr Color_t Yellow900() noexcept { return from_hex(0xfff57f17); } + static constexpr Color_t YellowA100() noexcept { return from_hex(0xffffff8d); } + static constexpr Color_t YellowA200() noexcept { return from_hex(0xffffff00); } + static constexpr Color_t YellowA400() noexcept { return from_hex(0xffffea00); } + static constexpr Color_t YellowA700() noexcept { return from_hex(0xffffd600); } + + static constexpr Color_t Amber50() noexcept { return from_hex(0xfffff8e1); } + static constexpr Color_t Amber100() noexcept { return from_hex(0xffffecb3); } + static constexpr Color_t Amber200() noexcept { return from_hex(0xffffe082); } + static constexpr Color_t Amber300() noexcept { return from_hex(0xffffd54f); } + static constexpr Color_t Amber400() noexcept { return from_hex(0xffffca28); } + static constexpr Color_t Amber500() noexcept { return from_hex(0xffffc107); } + static constexpr Color_t Amber600() noexcept { return from_hex(0xffffb300); } + static constexpr Color_t Amber700() noexcept { return from_hex(0xffffa000); } + static constexpr Color_t Amber800() noexcept { return from_hex(0xffff8f00); } + static constexpr Color_t Amber900() noexcept { return from_hex(0xffff6f00); } + static constexpr Color_t AmberA100() noexcept { return from_hex(0xffffe57f); } + static constexpr Color_t AmberA200() noexcept { return from_hex(0xffffd740); } + static constexpr Color_t AmberA400() noexcept { return from_hex(0xffffc400); } + static constexpr Color_t AmberA700() noexcept { return from_hex(0xffffab00); } + + static constexpr Color_t Orange50() noexcept { return from_hex(0xfffff3e0); } + static constexpr Color_t Orange100() noexcept { return from_hex(0xffffe0b2); } + static constexpr Color_t Orange200() noexcept { return from_hex(0xffffcc80); } + static constexpr Color_t Orange300() noexcept { return from_hex(0xffffb74d); } + static constexpr Color_t Orange400() noexcept { return from_hex(0xffffa726); } + static constexpr Color_t Orange500() noexcept { return from_hex(0xffff9800); } + static constexpr Color_t Orange600() noexcept { return from_hex(0xfffb8c00); } + static constexpr Color_t Orange700() noexcept { return from_hex(0xfff57c00); } + static constexpr Color_t Orange800() noexcept { return from_hex(0xffef6c00); } + static constexpr Color_t Orange900() noexcept { return from_hex(0xffe65100); } + static constexpr Color_t OrangeA100() noexcept { return from_hex(0xffffd180); } + static constexpr Color_t OrangeA200() noexcept { return from_hex(0xffffab40); } + static constexpr Color_t OrangeA400() noexcept { return from_hex(0xffff9100); } + static constexpr Color_t OrangeA700() noexcept { return from_hex(0xffff6d00); } + + static constexpr Color_t DeepOrange50() noexcept { return from_hex(0xfffbe9e7); } + static constexpr Color_t DeepOrange100() noexcept { return from_hex(0xffffccbc); } + static constexpr Color_t DeepOrange200() noexcept { return from_hex(0xffffab91); } + static constexpr Color_t DeepOrange300() noexcept { return from_hex(0xffff8a65); } + static constexpr Color_t DeepOrange400() noexcept { return from_hex(0xffff7043); } + static constexpr Color_t DeepOrange500() noexcept { return from_hex(0xffff5722); } + static constexpr Color_t DeepOrange600() noexcept { return from_hex(0xfff4511e); } + static constexpr Color_t DeepOrange700() noexcept { return from_hex(0xffe64a19); } + static constexpr Color_t DeepOrange800() noexcept { return from_hex(0xffd84315); } + static constexpr Color_t DeepOrange900() noexcept { return from_hex(0xffbf360c); } + static constexpr Color_t DeepOrangeA100() noexcept { return from_hex(0xffff9e80); } + static constexpr Color_t DeepOrangeA200() noexcept { return from_hex(0xffff6e40); } + static constexpr Color_t DeepOrangeA400() noexcept { return from_hex(0xffff3d00); } + static constexpr Color_t DeepOrangeA700() noexcept { return from_hex(0xffdd2c00); } + + static constexpr Color_t Brown50() noexcept { return from_hex(0xffefebe9); } + static constexpr Color_t Brown100() noexcept { return from_hex(0xffd7ccc8); } + static constexpr Color_t Brown200() noexcept { return from_hex(0xffbcaaa4); } + static constexpr Color_t Brown300() noexcept { return from_hex(0xffa1887f); } + static constexpr Color_t Brown400() noexcept { return from_hex(0xff8d6e63); } + static constexpr Color_t Brown500() noexcept { return from_hex(0xff795548); } + static constexpr Color_t Brown600() noexcept { return from_hex(0xff6d4c41); } + static constexpr Color_t Brown700() noexcept { return from_hex(0xff5d4037); } + static constexpr Color_t Brown800() noexcept { return from_hex(0xff4e342e); } + static constexpr Color_t Brown900() noexcept { return from_hex(0xff3e2723); } + + static constexpr Color_t Grey50() noexcept { return from_hex(0xfffafafa); } + static constexpr Color_t Grey100() noexcept { return from_hex(0xfff5f5f5); } + static constexpr Color_t Grey200() noexcept { return from_hex(0xffeeeeee); } + static constexpr Color_t Grey300() noexcept { return from_hex(0xffe0e0e0); } + static constexpr Color_t Grey400() noexcept { return from_hex(0xffbdbdbd); } + static constexpr Color_t Grey500() noexcept { return from_hex(0xff9e9e9e); } + static constexpr Color_t Grey600() noexcept { return from_hex(0xff757575); } + static constexpr Color_t Grey700() noexcept { return from_hex(0xff616161); } + static constexpr Color_t Grey800() noexcept { return from_hex(0xff424242); } + static constexpr Color_t Grey900() noexcept { return from_hex(0xff212121); } + + static constexpr Color_t BlueGrey50() noexcept { return from_hex(0xffeceff1); } + static constexpr Color_t BlueGrey100() noexcept { return from_hex(0xffcfd8dc); } + static constexpr Color_t BlueGrey200() noexcept { return from_hex(0xffb0bec5); } + static constexpr Color_t BlueGrey300() noexcept { return from_hex(0xff90a4ae); } + static constexpr Color_t BlueGrey400() noexcept { return from_hex(0xff78909c); } + static constexpr Color_t BlueGrey500() noexcept { return from_hex(0xff607d8b); } + static constexpr Color_t BlueGrey600() noexcept { return from_hex(0xff546e7a); } + static constexpr Color_t BlueGrey700() noexcept { return from_hex(0xff455a64); } + static constexpr Color_t BlueGrey800() noexcept { return from_hex(0xff37474f); } + static constexpr Color_t BlueGrey900() noexcept { return from_hex(0xff263238); } + + static constexpr Color_t Black() noexcept { return from_hex(0xff000000); } + static constexpr Color_t White() noexcept { return from_hex(0xffffffff); } + static constexpr Color_t Null() noexcept { return from_hex(0x00000000); } + + static constexpr Color_t Red() noexcept { return Red500(); } + static constexpr Color_t DarkRed() noexcept { return Red900(); } + static constexpr Color_t Coral() noexcept { return Red200(); } + static constexpr Color_t RichRed() noexcept { return from_hex(0xffff0000); } + static constexpr Color_t Pink() noexcept { return Pink500(); } + static constexpr Color_t Rose() noexcept { return PinkA100(); } + static constexpr Color_t Purple() noexcept { return Purple500(); } + static constexpr Color_t Magenta() noexcept { return PurpleA200(); } + static constexpr Color_t DarkMagenta() noexcept { return PurpleA700(); } + static constexpr Color_t DeepPurple() noexcept { return DeepPurple500(); } + static constexpr Color_t Indigo() noexcept { return Indigo500(); } + static constexpr Color_t Blue() noexcept { return Blue500(); } + static constexpr Color_t DarkBlue() noexcept { return Blue900(); } + static constexpr Color_t RichBlue() noexcept { return from_hex(0xff0000ff); } + static constexpr Color_t LightBlue() noexcept { return LightBlue500(); } + static constexpr Color_t SkyBlue() noexcept { return LightBlueA100(); } + static constexpr Color_t Navy() noexcept { return LightBlue800(); } + static constexpr Color_t Cyan() noexcept { return Cyan500(); } + static constexpr Color_t DarkCyan() noexcept { return Cyan900(); } + static constexpr Color_t Teal() noexcept { return Teal500(); } + static constexpr Color_t DarkTeal() noexcept { return Teal900(); } + static constexpr Color_t Green() noexcept { return Green500(); } + static constexpr Color_t DarkGreen() noexcept { return Green900(); } + static constexpr Color_t RichGreen() noexcept { return from_hex(0xff00ff00); } + static constexpr Color_t LightGreen() noexcept { return LightGreen500(); } + static constexpr Color_t Mint() noexcept { return LightGreen900(); } + static constexpr Color_t Lime() noexcept { return Lime500(); } + static constexpr Color_t Olive() noexcept { return Lime900(); } + static constexpr Color_t Yellow() noexcept { return Yellow500(); } + static constexpr Color_t RichYellow() noexcept { return YellowA200(); } + static constexpr Color_t Amber() noexcept { return Amber500(); } + static constexpr Color_t Gold() noexcept { return Amber300(); } + static constexpr Color_t PaleGold() noexcept { return AmberA100(); } + static constexpr Color_t Orange() noexcept { return Orange500(); } + static constexpr Color_t Skin() noexcept { return Orange100(); } + static constexpr Color_t DeepOrange() noexcept { return DeepOrange500(); } + static constexpr Color_t Brick() noexcept { return DeepOrange900(); } + static constexpr Color_t Brown() noexcept { return Brown500(); } + static constexpr Color_t DarkBrown() noexcept { return Brown900(); } + static constexpr Color_t CreamWhite() noexcept { return Orange50(); } + static constexpr Color_t Wheat() noexcept { return Amber100(); } + static constexpr Color_t Grey() noexcept { return Grey500(); } + static constexpr Color_t Dark() noexcept { return Grey900(); } + static constexpr Color_t Silver() noexcept { return Grey300(); } + static constexpr Color_t BlueGrey() noexcept { return BlueGrey500(); } + + // old colors + static constexpr Color_t FullRed() noexcept { return from_hex(0x00FF0000); } + static constexpr Color_t FullGreen() noexcept { return from_hex(0x0000FF00); } + static constexpr Color_t FullBlue() noexcept { return from_hex(0x000000FF); } + static constexpr Color_t FullYellow() noexcept { return from_hex(0x00FFFF00); } + static constexpr Color_t FullOrange() noexcept { return from_hex(0x00FF8000); } + static constexpr Color_t LightRed() noexcept { return from_hex(0x00FFDDDD); } + static constexpr Color_t LightYellow() noexcept { return from_hex(0x00FFFFDD); } + static constexpr Color_t LightOrange() noexcept { return from_hex(0x00FFAA00); } + static constexpr Color_t DarkYellow() noexcept { return from_hex(0x00888800); } + static constexpr Color_t DarkOrange() noexcept { return from_hex(0x00FF8C00); } + static constexpr Color_t LightPurple() noexcept { return from_hex(0x009370DB); } + static constexpr Color_t Lavender() noexcept { return from_hex(0x00E6E6FA); } + static constexpr Color_t Violet() noexcept { return from_hex(0x00EE82EE); } + static constexpr Color_t Salmon() noexcept { return from_hex(0x00FA8072); } + static constexpr Color_t HotPink() noexcept { return from_hex(0x00FF69B4); } + static constexpr Color_t DeepPink() noexcept { return from_hex(0x00FF1493); } + static constexpr Color_t NavajoWhite() noexcept { return from_hex(0x00FFDEAD); } + static constexpr Color_t Turquoise() noexcept { return from_hex(0x0040E0D0); } + static constexpr Color_t Khaki() noexcept { return from_hex(0x00F0E68C); } + static constexpr Color_t DarkKhaki() noexcept { return from_hex(0x00BDB76B); } + static constexpr Color_t DeepSkyBlue() noexcept { return from_hex(0x0000BFFF); } + static constexpr Color_t DodgerBlue() noexcept { return from_hex(0x001E90FF); } + static constexpr Color_t RoyalBlue() noexcept { return from_hex(0x004169E1); } + static constexpr Color_t SteelBlue() noexcept { return from_hex(0x00B0C4DE); } + static constexpr Color_t BdxRed() noexcept { return from_hex(0x00800000); } + static constexpr Color_t Chocolate() noexcept { return from_hex(0x00D2691E); } + static constexpr Color_t SaddleBrown() noexcept { return from_hex(0x008B4513); } + static constexpr Color_t SandyBrown() noexcept { return from_hex(0x00F4A460); } + static constexpr Color_t RosyBrown() noexcept { return from_hex(0x00BC8F8F); } + static constexpr Color_t Burlywood() noexcept { return from_hex(0x00DEB887); } + + static constexpr Color_t DarkGray() noexcept { return from_hex(0x00A9A9A9); } + static constexpr Color_t Gray() noexcept { return from_hex(0x00808080); } + static constexpr Color_t SlateGray() noexcept { return from_hex(0x00708090); } + + static constexpr Color_t Default() noexcept { return Wheat(); } + +}; // class Color_t +// clang-format on + +} // namespace kalypsso + +#endif // KALYPSSO_UTILS_MONITORING_NVTXPROFILING_H_ diff --git a/src/kalypsso/utils/monitoring/PapiInfo.cpp b/src/kalypsso/utils/monitoring/PapiInfo.cpp new file mode 100644 index 0000000..d227d76 --- /dev/null +++ b/src/kalypsso/utils/monitoring/PapiInfo.cpp @@ -0,0 +1,121 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file PapiInfo.cpp + * \brief A simple PAPI interface class. + * + * Parts of this class is inspired by file sc_flops.c found in library + * libsc (https://github.com/cburstedde/libsc). + * + */ +#include "PapiInfo.h" + +#include +#include // for gettimeofday and struct timeval + +#include +#include + +namespace kalypsso +{ + +///////////////////////////////////////////////////////////////////////// +// PapiInfo class methods body +///////////////////////////////////////////////////////////////////////// + +// ======================================================= +// ======================================================= +PapiInfo::PapiInfo() + : papiTimer() +{ + + crtime = 0.0f; + cptime = 0.0f; + cflpops = 0; + irtime = 0.0f; + iptime = 0.0f; + iflpops = 0; + mflops = 0.0; + float tmp; + + // initialize PAPI counters + PAPI_flops(&irtime, &iptime, &iflpops, &tmp); + +} // PapiInfo::PapiInfo + +// ======================================================= +// ======================================================= +PapiInfo::~PapiInfo() {} // PapiInfo::~PapiInfo + +// ======================================================= +// ======================================================= +void +PapiInfo::start() +{ + + float tmp; + int retval; + + papiTimer.start(); + if ((retval = PAPI_flops(&irtime, &iptime, &iflpops, &tmp)) < PAPI_OK) + printf("PAPI not ok in PapiInfoStart with returned value %d\n", retval); + +} // PapiInfo::start + +// ======================================================= +// ======================================================= +void +PapiInfo::stop() +{ + + float rtime, ptime; + long long int flpops; + float tmp; + int retval; + + if ((retval = PAPI_flops(&rtime, &ptime, &flpops, &tmp)) < PAPI_OK) + printf("PAPI not ok in PapiInfoStop with returned value %d\n", retval); + papiTimer.stop(); + + // add increment from previous call to start values to accumulator counters + crtime = rtime - irtime; + cptime = ptime - iptime; + cflpops += flpops - iflpops; + + mflops = 1.0 * cflpops / papiTimer.elapsed(); + +} // PapiInfo::stop + +// ======================================================= +// ======================================================= +double +PapiInfo::getFlops() +{ + + return mflops; + +} // PapiInfo::getFlops + +// ======================================================= +// ======================================================= +long long int +PapiInfo::getFlop() +{ + + return cflpops; + +} // PapiInfo::getFlop + +// ======================================================= +// ======================================================= +double +PapiInfo::elapsed() +{ + + return papiTimer.elapsed(); + +} // PapiInfo::elapsed + +} // namespace kalypsso diff --git a/src/kalypsso/utils/monitoring/PapiInfo.h b/src/kalypsso/utils/monitoring/PapiInfo.h new file mode 100644 index 0000000..a6db061 --- /dev/null +++ b/src/kalypsso/utils/monitoring/PapiInfo.h @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file PapiInfo.h + * \brief A simple PAPI interface class. + * + * Parts of this class is adapted from file sc_flops.c found in library + * libsc (https://github.com/cburstedde/libsc) + * + */ +#ifndef KALYPSSO_UTILS_MONITORING_PAPIINFO_H_ +#define KALYPSSO_UTILS_MONITORING_PAPIINFO_H_ + +#include "OpenMPTimer.h" + +namespace kalypsso +{ + +class PapiInfo +{ +public: + /** + * constructor + */ + PapiInfo(); + + /** + * destructor + */ + ~PapiInfo(); + + void + start(); + + void + stop(); + + double + getFlops(); + + long long int + getFlop(); + + double + elapsed(); + +protected: + /* Wall clock time */ + OpenMPTimer papiTimer; + + /* cumulative counters */ + float crtime; //!< cumulative real time + float cptime; //!< cumulative process time + long long int cflpops; //!< cumulative floating point operations + + /* values used in start routine */ + float irtime; //!< interval real time + float iptime; //!< interval process time + long long int iflpops; //!< interval floating point operations + + double mflops; //!< MFlop/s rate + +}; // class PapiInfo + +} // namespace kalypsso + +#endif // KALYPSSO_UTILS_MONITORING_PAPIINFO_H_ diff --git a/src/kalypsso/utils/monitoring/ProfilingManager.cpp b/src/kalypsso/utils/monitoring/ProfilingManager.cpp new file mode 100644 index 0000000..4b5d4c7 --- /dev/null +++ b/src/kalypsso/utils/monitoring/ProfilingManager.cpp @@ -0,0 +1,273 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "ProfilingManager.h" + +#include + +#include // for std::setw +#include // for std::cerr + +namespace kalypsso +{ + +const std::string ProfilingData::whole_kalypsso_region_name = "Kalypsso::ALL"; +const Color_t ProfilingData::whole_kalypsso_region_color = Color_t::Silver(); + +bool ProfilingData::timings_valid = false; + +// ============================================================================================== +// ============================================================================================== +// ProfilingRegion +// ============================================================================================== +// ============================================================================================== +// +// ============================================================================================== +// ============================================================================================== +ProfilingRegion::ProfilingRegion(ParallelEnv const & par_env, + std::string const & name, + timer_location_t timer_location, + Color_t color) + : m_par_env(par_env) + , m_name(name) + , m_timer_location(timer_location) + , m_color(color) + , m_state(IDLE) +#ifdef KALYPSSO_CORE_TIMING_ENABLED + , m_host_timer() + , m_device_timer() +#endif +{ +#ifdef KALYPSSO_CORE_NVTX_ANNOTATION_ENABLED + m_eventAttrib.version = NVTX_VERSION; + m_eventAttrib.size = NVTX_EVENT_ATTRIB_STRUCT_SIZE; + m_eventAttrib.category = 0; + m_eventAttrib.colorType = NVTX_COLOR_ARGB; + m_eventAttrib.color = m_color; + m_eventAttrib.payloadType = 0; + m_eventAttrib.reserved0 = 0; + m_eventAttrib.messageType = NVTX_MESSAGE_TYPE_ASCII; + m_eventAttrib.message.ascii = m_name.c_str(); +#endif // KALYPSSO_CORE_NVTX_ANNOTATION_ENABLED + +} // ProfilingRegion::ProfilingRegion + +// ============================================================================================== +// ============================================================================================== +void +ProfilingRegion::start() +{ + // add profiling hook for kokkos tools + Kokkos::Profiling::pushRegion(m_name); + +#ifdef KALYPSSO_CORE_NVTX_ANNOTATION_ENABLED + nvtxRangePushEx(&m_eventAttrib); +#endif // KALYPSSO_CORE_NVTX_ANNOTATION_ENABLED + +#ifdef KALYPSSO_CORE_TIMING_ENABLED + if (m_state == IDLE) + { + m_state = RUNNING; + m_host_timer.start(); + m_device_timer.start(); + } + else + { + if (m_par_env.rank() == 0) + printf("Can't start timer, timer is already RUNNING"); + } +#endif // KALYPSSO_CORE_TIMING_ENABLED + +} // ProfilingRegion::start + +// ============================================================================================== +// ============================================================================================== +void +ProfilingRegion::stop() +{ +#ifdef KALYPSSO_CORE_TIMING_ENABLED + if (m_state == RUNNING) + { + m_state = IDLE; + m_host_timer.stop(); + m_device_timer.stop(); + if (m_name == ProfilingData::whole_kalypsso_region_name) + ProfilingData::timings_valid = true; + } + else + { + if (m_par_env.rank() == 0) + printf("Can't stop timer, timer is not RUNNING"); + } + +#endif // KALYPSSO_CORE_TIMING_ENABLED + +#ifdef KALYPSSO_CORE_NVTX_ANNOTATION_ENABLED + nvtxRangePop(); +#endif // KALYPSSO_CORE_NVTX_ANNOTATION_ENABLED + + // add profiling hook for kokkos tools + Kokkos::Profiling::popRegion(); + +} // ProfilingRegion::stop + +// ============================================================================================== +// ============================================================================================== +void +ProfilingRegion::reset() +{ + +#ifdef KALYPSSO_CORE_TIMING_ENABLED + m_host_timer.reset(); + m_device_timer.reset(); +#endif // KALYPSSO_CORE_TIMING_ENABLED + +} // ProfilingRegion::reset + +// ============================================================================================== +// ============================================================================================== +// ProfilingManager +// ============================================================================================== +// ============================================================================================== + +// ============================================================================================== +// ============================================================================================== +ProfilingManager::ProfilingManager(ParallelEnv const & par_env) + : m_par_env(par_env) +{ + // create a region that contains all computing regions + // all other region is supposed to be inside this region + // meaning that + // - the very first call to ProfileRegion::start + // - the very last call to ProfileRegion::stop + // should be using whole_kalypsso_region_name + // + // Note that start/stop for the whole application region should only be called once + // or else the timing report will erroneous values + m_profiling_region_map.emplace(std::piecewise_construct, + std::forward_as_tuple(whole_kalypsso_region_name), + std::forward_as_tuple(par_env, + whole_kalypsso_region_name, + ProfilingRegion::TIMER_HOST, + whole_kalypsso_region_color)); + +} // ProfilingManager::ProfilingManager + +// ============================================================================================== +// ============================================================================================== +ProfilingRegion & +ProfilingManager::get_region(std::string const & name, + ProfilingRegion::timer_location_t timer_location, + Color_t color) +{ + auto it = m_profiling_region_map.find(name); + + // if region does'nt exist, create it + if (it == m_profiling_region_map.end()) + it = m_profiling_region_map + .emplace(std::piecewise_construct, + std::forward_as_tuple(name), + std::forward_as_tuple(m_par_env, name, timer_location, color)) + .first; + return it->second; + +} // ProfilingManager::get_region + +// ============================================================================================== +// ============================================================================================== +ProfilingRegion & +ProfilingManager::get_region(std::string const & name, + ProfilingRegion::timer_location_t timer_location, + ColorMap_t const & colormap) +{ + // find color or use default color + auto color = [](std::string const & name_, ColorMap_t const & colormap_) { + auto itc = colormap_.find(name_); + if (itc == colormap_.end()) + { + KALYPSSO_WARN( + "colormap doesn't contain name {}; using color default color instead (Black). You should " + "probably update the colormap used in your solver.", + name_); + return Color_t::Black(); + } + return itc->second; + }(name, colormap); + + return get_region(name, timer_location, color); + +} // ProfilingManager::get_region + +// ============================================================================================== +// ============================================================================================== +void +ProfilingManager::print_timings() +{ +#ifdef KALYPSSO_CORE_TIMING_ENABLED + if (!ProfilingData::timings_valid) + { + if (m_par_env.rank() == 0) + std::cerr << "You can't print timings before the whole application region is still open.\n"; + return; + } + + if (m_par_env.rank() == 0) + { + printf("# ====================================\n"); + printf("# Kalypsso timings\n"); + printf("# ====================================\n"); + + // get whole application time using the host timer + auto & whole_app_region = m_profiling_region_map.at(whole_kalypsso_region_name); + auto total_time_sec = whole_app_region.host_timer().elapsed(); + assertm(total_time_sec > 0, "Invalid value: total_time_sec <= 0 !"); + std::cout << std::left << std::setw(40) << whole_kalypsso_region_name; + printf(" time : %5.3f \ts (%5.2f%%)", total_time_sec, 100.0); + std::cout << std::endl; + + // for other timer display the fraction of time in each region with respect to + // the whole application time + // timer elapsed method is not const (when using CudaAsyncTimer) + for (auto & prof : m_profiling_region_map) + { + const std::string & name = prof.first; + if (name != whole_kalypsso_region_name) + { + double time_sec = prof.second.elapsed(); + double percent = 100 * time_sec / total_time_sec; + std::cout << std::left << std::setw(40) << name; + printf(" time : %5.3f \ts (%5.2f%%)", time_sec, percent); + std::cout << std::endl; + } + } + } +#else + KALYPSSO_INFO(("Timings are disabled. Please re-build with KALYPSSO_CORE_TIMING_ENABLED=ON."); +#endif // KALYPSSO_CORE_TIMING_ENABLED +} // ProfilingManager::print_timings + +// ============================================================================================== +// ============================================================================================== +std::optional +ProfilingManager::total_time() +{ +#ifdef KALYPSSO_CORE_TIMING_ENABLED + if (!ProfilingData::timings_valid) + { + if (m_par_env.rank() == 0) + std::cerr << "You can't get timings before the whole application region is still open.\n"; + return std::nullopt; + } + + // get whole application time + auto & whole_app_region = m_profiling_region_map.at(whole_kalypsso_region_name); + return whole_app_region.host_timer().elapsed(); +#else + // return invalid value + return std::nullopt; +#endif // KALYPSSO_CORE_TIMING_ENABLED + +} // ProfilingManager::total_time + +} // namespace kalypsso diff --git a/src/kalypsso/utils/monitoring/ProfilingManager.h b/src/kalypsso/utils/monitoring/ProfilingManager.h new file mode 100644 index 0000000..19264ea --- /dev/null +++ b/src/kalypsso/utils/monitoring/ProfilingManager.h @@ -0,0 +1,226 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ProfilingManager.h + * \brief Define a simple class to handle monitoring profiling region. + * + * By profiling we mean: + * - use (hardware aware) timer + * - use nvtx annotations (requires to have cuda drive installed, but don't require to have Kokkos + * built with Kokkos::Cuda backend enabled.) + * + * All monitoring instrumentation can be turned off at compile time. + * For nvtx annotation, we just need to define symbol NVTX_DISABLE in cmake. + */ +#ifndef KALYPSSO_UTILS_MONITORING_PROFILINGMANAGER_H_ +#define KALYPSSO_UTILS_MONITORING_PROFILINGMANAGER_H_ + +#include +#include +#include +#include + +#include +#include +#include + +#include "HostTimer.h" + +#ifdef KOKKOS_ENABLE_CUDA +# include "CudaAsyncTimer.h" +#endif + +#ifdef KOKKOS_ENABLE_HIP +// # include "HIPAsyncTimer.h" +#endif + +#ifdef KALYPSSO_CORE_NVTX_ANNOTATION_ENABLED +# include +// # include +#endif // KALYPSSO_CORE_NVTX_ANNOTATION_ENABLED + +#include "NvtxProfiling.h" // for class Color + +// #include // for assertm +#ifndef assertm +# define assertm(exp, msg) assert(((void)msg, exp)) +#endif + +namespace kalypsso +{ + +//! a type alias to map profile region names to a color +using ColorMap_t = std::map; + + +// ============================================================================================== +// ============================================================================================== +// ============================================================================================== +struct ProfilingData +{ + static const std::string whole_kalypsso_region_name; + static const Color_t whole_kalypsso_region_color; + + //! flag indicating when all timings can be reported + //! default is false + //! become true when stopping the whole application timer + static bool timings_valid; + +}; // struct ProfilingData + +// ============================================================================================== +// ============================================================================================== +// ============================================================================================== +class ProfilingRegion +{ + +private: + //! a timer should be idle when calling start, and running when calling stop. + enum State_t : uint32_t + { + IDLE, + RUNNING + }; + +#if defined(KOKKOS_ENABLE_CUDA) + using DeviceTimer = CudaAsyncTimer; +#elif defined(KOKKOS_ENABLE_HIP) + // using DeviceTimer = HIPAsyncTimer; +#else + using DeviceTimer = HostTimer; +#endif + +public: + //! preferred timer location + enum timer_location_t + { + TIMER_HOST, + TIMER_DEVICE + }; + + ProfilingRegion(ParallelEnv const & par_env, + std::string const & name, + timer_location_t timer_location, + Color_t color); + + ~ProfilingRegion() = default; + + //! start timing + void + start(); + + //! stop timing + void + stop(); + + //! reset timer + void + reset(); + +#ifdef KALYPSSO_CORE_TIMING_ENABLED + // ===================================================== + // ===================================================== + HostTimer & + host_timer() + { + return m_host_timer; + } + + // ===================================================== + // ===================================================== + DeviceTimer & + device_timer() + { + return m_device_timer; + } + + //! elapsed time in seconds + double + elapsed() + { + return m_timer_location == TIMER_HOST ? m_host_timer.elapsed() : m_device_timer.elapsed(); + } + +#endif + +private: + ParallelEnv const & m_par_env; + const std::string m_name; + timer_location_t m_timer_location; // + total_time(); + +private: + ParallelEnv const & m_par_env; + std::map m_profiling_region_map; + +}; // class ProfilingManager + +} // namespace kalypsso + +#endif // KALYPSSO_UTILS_MONITORING_PROFILINGMANAGER_H_ diff --git a/src/kalypsso/utils/monitoring/memory_utils.h b/src/kalypsso/utils/monitoring/memory_utils.h new file mode 100644 index 0000000..685c3da --- /dev/null +++ b/src/kalypsso/utils/monitoring/memory_utils.h @@ -0,0 +1,133 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file memory_utils.h + * \brief Define some helper routines for monitoring free/occupied memory on device. + */ +#ifndef KALYPSSO_UTILS_MONITORING_MEMORYUTILS_H_ +#define KALYPSSO_UTILS_MONITORING_MEMORYUTILS_H_ + +#include +#include // for KOKKOS_ENABLE_XXX + +#include +#include + +#if (defined(__APPLE__)) +# define SYS_MEMORYINFO_MAC +# include +# include +# include +# include +# include +#elif (defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) || defined(__MINGW32__) || \ + defined(_WIN64)) +# define SYS_MEMORYINFO_WINDOWS +# include +# include +# undef min +# undef max +#else +# define SYS_MEMORYINFO_LINUX +# include +# include +# include +#endif + +namespace kalypsso +{ + +/** + * \struct MemInfo + */ +struct MemInfo +{ + //! amount of free physical memory on device in GBytes + double free; + + //! amount of used physical memory on device in GBytes + double used; + + //! total amount of physical memory on device in GBytes + double total; +}; + +// =============================================================== +// =============================================================== +template +MemInfo +get_device_mem_info() +{ + // using ExecSpace = typename device_t::execution_space; + using MemorySpace = typename device_t::memory_space; + + MemInfo mem_info; + mem_info.free = 0.0; + mem_info.used = 0.0; + mem_info.total = 0.0; + + if constexpr (std::is_same::value) + { +#ifdef SYS_MEMORYINFO_LINUX + struct sysinfo memInfo; + sysinfo(&memInfo); + mem_info.free = static_cast(memInfo.freeram) * memInfo.mem_unit * 1e-9; + mem_info.used = + static_cast(memInfo.totalram - memInfo.freeram) * memInfo.mem_unit * 1e-9; + mem_info.total = static_cast(memInfo.totalram) * memInfo.mem_unit * 1e-9; +#elif defined(SYS_MEMORYINFO_WINDOWS) + // TODO +#elif defined(SYS_MEMORYINFO_MAC) + // TODO +#endif + } +#if defined(KOKKOS_ENABLE_CUDA) + else if constexpr (std::is_same_v) + { + size_t free_t, total_t; + + cudaMemGetInfo(&free_t, &total_t); + + mem_info.free = static_cast(free_t) * 1e-9; + mem_info.used = static_cast(total_t - free_t) * 1e-9; + mem_info.total = static_cast(total_t) * 1e-9; + } +#endif +#if defined(KOKKOS_ENABLE_HIP) + else if constexpr (std::is_same_v) + { + size_t free_t, total_t; + + hipMemGetInfo(&free_t, &total_t); + + mem_info.free = static_cast(free_t) * 1e-9; + mem_info.used = static_cast(total_t - free_t) * 1e-9; + mem_info.total = static_cast(total_t) * 1e-9; + } +#endif + + return mem_info; + +} // get_device_mem_info + +// =============================================================== +// =============================================================== +/** + * Print memory information on master process. + */ +template +void +print_device_mem_info() +{ + const auto mem_info = get_device_mem_info(); + + KALYPSSO_INFO("Total memory: {:.3f} GBytes", mem_info.total); + KALYPSSO_INFO("Used memory: {:.3f} GBytes", mem_info.used); + KALYPSSO_INFO("Free memory: {:.3f} GBytes", mem_info.free); +} + +} // namespace kalypsso + +#endif // KALYPSSO_UTILS_MONITORING_MEMORYUTILS_H_ diff --git a/src/kalypsso/utils/mpi/CMakeLists.txt b/src/kalypsso/utils/mpi/CMakeLists.txt new file mode 100644 index 0000000..f88fcad --- /dev/null +++ b/src/kalypsso/utils/mpi/CMakeLists.txt @@ -0,0 +1,57 @@ +set(MPI_UTILS_SRCS ParallelEnv.h ParallelEnv.cpp) + +if(KALYPSSO_CORE_USE_MPI) + list( + APPEND + MPI_UTILS_SRCS + GlobalMpiSession.cpp + GlobalMpiSession.h + MpiComm.h + MpiComm.cpp + MpiCommCart.h + MpiCommCart.cpp) +endif(KALYPSSO_CORE_USE_MPI) + +add_library(kalypsso_mpiUtils ${MPI_UTILS_SRCS}) + +# Set target properties +target_include_directories( + kalypsso_mpiUtils + PUBLIC $ $ + $) + +target_link_libraries(kalypsso_mpiUtils PRIVATE kalypsso_warnings kalypsso_core_config) + +target_link_libraries(kalypsso_mpiUtils PUBLIC Kokkos::kokkos kalypsso::p4est_wrap) + +if(KALYPSSO_CORE_USE_MPI) + target_link_libraries(kalypsso_mpiUtils PUBLIC MPI::MPI_CXX) +endif(KALYPSSO_CORE_USE_MPI) + +# Add an alias so that library can be used inside the build tree +add_library(kalypsso::mpiUtils ALIAS kalypsso_mpiUtils) + +# +# Get prepared for install +# +include(${PROJECT_SOURCE_DIR}/cmake/setup_install.cmake) + +# +# install libraries and headers +# +install( + TARGETS kalypsso_mpiUtils + EXPORT kalypsso-targets + LIBRARY DESTINATION ${INSTALL_LIBDIR} + ARCHIVE DESTINATION ${INSTALL_LIBDIR} + RUNTIME DESTINATION ${INSTALL_BINDIR}) + +# +# install headers (exclude cpp files) +# +install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DESTINATION ${INSTALL_INCLUDEDIR}/kalypsso/utils + FILES_MATCHING + PATTERN "*.h" + PATTERN "*.cpp" EXCLUDE) diff --git a/src/kalypsso/utils/mpi/GlobalMpiSession.cpp b/src/kalypsso/utils/mpi/GlobalMpiSession.cpp new file mode 100644 index 0000000..1059923 --- /dev/null +++ b/src/kalypsso/utils/mpi/GlobalMpiSession.cpp @@ -0,0 +1,115 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file GlobalMpiSession.cpp + * \brief Implements class GlobalMpiSession. + * + */ + +#include + +#include + +namespace kalypsso +{ + +// ======================================================= +// ======================================================= +GlobalMpiSession::GlobalMpiSession(int & argc, char **& argv) + : i_initialized(false) +{ + if (!initialized()) + { + CHECK_MPI_ERR(MPI_Init(&argc, &argv)); + i_initialized = true; + } + +} // GlobalMpiSession::GlobalMpiSession + +// ======================================================= +// ======================================================= +GlobalMpiSession::~GlobalMpiSession() +{ + + // calling MPI_Finalize only if MPI_Init was in the constructor + if (i_initialized) + { + if (!finalized()) + { + CHECK_MPI_ERR(MPI_Finalize()); + } + } + +} // GlobalMpiSession::~GlobalMpiSession + +// ================================================================ +// ================================================================ +void +GlobalMpiSession::abort(int errcode) +{ + CHECK_MPI_ERR(MPI_Abort(MPI_COMM_WORLD, errcode)); +} // GlobalMpiSession::abort + +// ================================================================ +// ================================================================ +bool +GlobalMpiSession::initialized() +{ + int flag; + CHECK_MPI_ERR(MPI_Initialized(&flag)); + return flag != 0; +} // GlobalMpiSession::initialized + +// ================================================================ +// ================================================================ +bool +GlobalMpiSession::finalized() +{ + int flag; + CHECK_MPI_ERR(MPI_Finalized(&flag)); + return flag != 0; +} // GlobalMpiSession::finalized + +// ================================================================ +// ================================================================ +std::string +GlobalMpiSession::processor_name() +{ + char name[MPI_MAX_PROCESSOR_NAME]; + int len; + + CHECK_MPI_ERR(MPI_Get_processor_name(name, &len)); + return std::string(name, static_cast(len)); + +} // GlobalMpiSession::processor_name + +// ================================================================ +// ================================================================ +std::string +GlobalMpiSession::mpi_library_version() +{ +#if (3 <= MPI_VERSION) + char lib_version[MPI_MAX_LIBRARY_VERSION_STRING]; + int len = 0; + CHECK_MPI_ERR(MPI_Get_library_version(lib_version, &len)); + return std::string(lib_version, static_cast(len)); +#else + return ""; +#endif + +} // GlobalMpiSession::mpi_library_version + +// ================================================================ +// ================================================================ +std::pair +GlobalMpiSession::mpi_standard_version() +{ + int version, subversion; + CHECK_MPI_ERR(MPI_Get_version(&version, &subversion)); + return { version, subversion }; + +} // GlobalMpiSession::mpi_standard_version + +} // namespace kalypsso diff --git a/src/kalypsso/utils/mpi/GlobalMpiSession.h b/src/kalypsso/utils/mpi/GlobalMpiSession.h new file mode 100644 index 0000000..c0b4f83 --- /dev/null +++ b/src/kalypsso/utils/mpi/GlobalMpiSession.h @@ -0,0 +1,144 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file GlobalMpiSession.h + * \brief A MPI utilities class, providing methods for initializing, + * finalizing, and querying the global MPI session + * + */ +#ifndef KALYPSSO_UTILS_MPI_GLOBAL_MPI_SESSION_H_ +#define KALYPSSO_UTILS_MPI_GLOBAL_MPI_SESSION_H_ + +#include // for macro CHECK_MPI_ERR + +#include // for std::make_pair +#include + +namespace kalypsso +{ + +/** + * A base class that makes derived class non copyable. + * see https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Non-copyable_Mixin + */ +class NonCopyable +{ +public: + NonCopyable(const NonCopyable &) = delete; + NonCopyable & + operator=(const NonCopyable &) = delete; + +protected: + NonCopyable() = default; + ~NonCopyable() = default; /// Protected non-virtual destructor +}; + +/** + * \brief This class provides methods for initializing, finalizing, and + * querying the global MPI session. + * + * Main behavior: + * - if MPI is not already initialized, we use MPI_COMM_WORLD as MPI communicator + * - if MPI is already initialized, e.g. kalypsso used as library in a thrird party application, + * we expect the caller to give us a communicator from which either we attached to or we duplicate + * from. + * + * This class is not copyable because the base class forbids it. + * see https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Non-copyable_Mixin + */ +class GlobalMpiSession : public NonCopyable +{ +public: + //! @name Public constructor and destructor + //@{ + + /** + * \brief Calls MPI_Init() if MPI is enabled and not already initialized. + * + * \param argc [in] Argument passed into main(argc,argv) + * \param argv [in] Argument passed into main(argc,argv) + */ + GlobalMpiSession(int & argc, char **& argv); + + /** Shuts down the MPI environment. + * + * If this @c environment object was used to initialize the MPI + * environment, and the MPI environment has not already been shut + * down (finalized), this destructor will shut down the MPI + * environment. Under normal circumstances, this only involves + * invoking @c MPI_Finalize. However, if destruction is the result + * of an uncaught exception and the @c abort_on_exception parameter + * of the constructor had the value @c true, this destructor will + * invoke @c MPI_Abort with @c MPI_COMM_WORLD to abort the entire + * MPI program with a result code of -1. + */ + ~GlobalMpiSession(); + + //@} + + // //! Get MPI_COMM_WORLD communicator + // static MpiComm & + // get_comm_world() + // { + // return MpiComm::world(); + // } + + /** Abort all MPI processes. + * + * Aborts all MPI processes and returns to the environment. The + * precise behavior will be defined by the underlying MPI + * implementation. This is equivalent to a call to @c MPI_Abort + * with @c MPI_COMM_WORLD. + * + * @param errcode The error code to return to the environment. + * @returns Will not return. + */ + static void + abort(int errcode); + + /** Determine if the MPI environment has already been initialized. + * + * This routine is equivalent to a call to @c MPI_Initialized. + * + * @returns @c true if the MPI environment has been initialized. + */ + static bool + initialized(); + + /** Determine if the MPI environment has already been finalized. + * + * The routine is equivalent to a call to @c MPI_Finalized. + * + * @returns @c true if the MPI environment has been finalized. + */ + static bool + finalized(); + + //! Get processor name + static std::string + processor_name(); + + //! MPI library implementation version string. + //! MPI_Get_library_version is a MPI-3 API + static std::string + mpi_library_version(); + + //! return MPI standard version number as a pair (version, subversion). + static std::pair + mpi_standard_version(); + +private: + //! Whether this environment object called MPI_Init. + //! + //! if GlobalMpiSession called MPI_Init, it means we are responsible for calling MPI_Finalize. + //! When kalypsso is used as library, MPI_Init/MPI_Finalize is called elsewhere, so MPI is not + //! managed here, this variable must be false. + bool i_initialized; + +}; // class GlobalMpiSession + +} // namespace kalypsso + +#endif // KALYPSSO_UTILS_MPI_GLOBAL_MPI_SESSION_H_ diff --git a/src/kalypsso/utils/mpi/MpiComm.cpp b/src/kalypsso/utils/mpi/MpiComm.cpp new file mode 100644 index 0000000..5713544 --- /dev/null +++ b/src/kalypsso/utils/mpi/MpiComm.cpp @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include // for std::abort + +namespace kalypsso +{ + +// ============================================================ +// ============================================================ +MpiComm::MpiComm() +{ + comm_ptr.reset(new MPI_Comm(MPI_COMM_WORLD)); +} + +// ============================================================ +// ============================================================ +MpiComm::MpiComm(const MPI_Comm & comm, comm_create_kind kind) +{ + if (comm == MPI_COMM_NULL) + /* MPI_COMM_NULL indicates that the communicator is not usable. */ + return; + + switch (kind) + { + case COMM_DUPLICATE: { + MPI_Comm newcomm; + CHECK_MPI_ERR(MPI_Comm_dup(comm, &newcomm)); + comm_ptr.reset(new MPI_Comm(newcomm), comm_free()); + MPI_Comm_set_errhandler(newcomm, MPI_ERRORS_RETURN); + break; + } + + case COMM_TAKE_OWNERSHIP: + comm_ptr.reset(new MPI_Comm(comm), comm_free()); + break; + + case COMM_ATTACH: + comm_ptr.reset(new MPI_Comm(comm)); + break; + } + +} // MpiComm::MpiComm + +// ============================================================ +// ============================================================ +MpiComm::operator MPI_Comm() const +{ + if (comm_ptr) + return *comm_ptr; + else + return MPI_COMM_NULL; +} // MpiComm::operator MPI_Comm + +// ============================================================ +// ============================================================ +void +MpiComm::MPI_Barrier() const +{ + CHECK_MPI_ERR(::MPI_Barrier(MPI_Comm(*this))); +} + +// ============================================================ +// ============================================================ +void +MpiComm::abort(int errcode) const +{ + CHECK_MPI_ERR(::MPI_Abort(MPI_Comm(*this), errcode)); + std::abort(); +} + + +} // namespace kalypsso diff --git a/src/kalypsso/utils/mpi/MpiComm.h b/src/kalypsso/utils/mpi/MpiComm.h new file mode 100644 index 0000000..010feae --- /dev/null +++ b/src/kalypsso/utils/mpi/MpiComm.h @@ -0,0 +1,535 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file MpiComm.h + * \brief Object representation of a MPI communicator + * + * the following class is loosely adapted from boost::mpi package + * see class communicator + * https://www.boost.org/doc/libs/1_80_0/doc/html/mpi.html + * + * # Distributed under the Boost Software License, Version 1.0. + * # https://www.boost.org/LICENSE_1_0.txt + * + */ +#ifndef KALYPSSO_UTILS_MPI_MPICOMM_H +#define KALYPSSO_UTILS_MPI_MPICOMM_H + +#include +#include + +#include // for std::shared_ptr +#include + +#include +#ifndef assertm +# define assertm(exp, msg) assert(((void)msg, exp)) +#endif + +namespace kalypsso +{ + +namespace MpiComm_impl +{ + +template +inline MPI_Datatype +mpi_type() +{ + static_assert(!std::is_same::value, "Unknown MPI type"); + return MPI_DATATYPE_NULL; +} + +template <> +inline MPI_Datatype +mpi_type() +{ + return MPI_DOUBLE; +} + +template <> +inline MPI_Datatype +mpi_type() +{ + return MPI_FLOAT; +} + +template <> +inline MPI_Datatype +mpi_type() +{ + return MPI_UINT64_T; +} + +template <> +inline MPI_Datatype +mpi_type() +{ + return MPI_UINT32_T; +} + +template <> +inline MPI_Datatype +mpi_type() +{ + return MPI_UINT16_T; +} + +template <> +inline MPI_Datatype +mpi_type() +{ + return MPI_UINT8_T; +} + +template <> +inline MPI_Datatype +mpi_type() +{ + return MPI_INT64_T; +} + +template <> +inline MPI_Datatype +mpi_type() +{ + return MPI_INT32_T; +} + +template <> +inline MPI_Datatype +mpi_type() +{ + return MPI_INT16_T; +} + +template <> +inline MPI_Datatype +mpi_type() +{ + return MPI_INT8_T; +} + +template <> +inline MPI_Datatype +mpi_type() +{ + return MPI_CHAR; +} + +template <> +inline MPI_Datatype +mpi_type() +{ + return MPI_CXX_BOOL; +} + +} // namespace MpiComm_impl + +/** + * \brief Object representation of an MPI communicator. + * + * Inspired by boost::mpi and trilinos teuchos packages. + */ +class MpiComm +{ + +public: + //! enumerate possible operation used in MPI reduction / scan + enum struct MPI_OP : int + { + MIN, + MAX, + SUM, + PROD, + LOR, + BOR, + LAND, + BAND, + NUM_OPS // nor a valid operation, just the total number of operations + }; + + static constexpr auto MIN = MPI_OP::MIN; + static constexpr auto MAX = MPI_OP::MAX; + static constexpr auto SUM = MPI_OP::SUM; + static constexpr auto PROD = MPI_OP::PROD; + static constexpr auto LOR = MPI_OP::LOR; + static constexpr auto BOR = MPI_OP::BOR; + static constexpr auto LAND = MPI_OP::LAND; + static constexpr auto BAND = MPI_OP::BAND; + + template + static auto + mpi_type() + { + return MpiComm_impl::mpi_type(); + } + + //! mapping an MPI_Op_enum to an MPI_Op + template + static MPI_Op + MapMpiOp() + { + if constexpr (mpi_op == MPI_OP::MIN) + return MPI_MIN; + else if constexpr (mpi_op == MPI_OP::MAX) + return MPI_MAX; + else if constexpr (mpi_op == MPI_OP::SUM) + return MPI_SUM; + else if constexpr (mpi_op == MPI_OP::PROD) + return MPI_PROD; + else if constexpr (mpi_op == MPI_OP::LOR) + return MPI_LOR; + else if constexpr (mpi_op == MPI_OP::BOR) + return MPI_BOR; + else if constexpr (mpi_op == MPI_OP::LAND) + return MPI_LAND; + else if constexpr (mpi_op == MPI_OP::BAND) + return MPI_BAND; + // no default value, we xant the compilation to fail if operation is not found + } + + + /** + * @brief Enumeration used to describe how to adopt a C @c MPI_Comm into + * a MpiComm communicator. + * + * \note this enum is adapted from class communicator found in Boost::mpi + * + * The values for this enumeration determine how a MpiComm + * communicator will behave when constructed with an MPI + * communicator. The options are: + * + * - @c COMM_DUPLICATE: Duplicate the MPI_Comm communicator to + * create a new communicator (e.g., with MPI_Comm_dup). This new + * MPI_Comm communicator will be automatically freed when the + * MpiComm communicator (and all copies of it) is destroyed. + * + * - @c COMM_TAKE_OWNERSHIP: Take ownership of the communicator. It + * will be freed automatically when all of the MpiComm + * communicators go out of scope. This option must not be used with + * MPI_COMM_WORLD. + * + * - @c COMM_ATTACH: The MpiComm communicator will reference the + * existing MPI communicator but will not free it when the MpiComm + * communicator goes out of scope. This option should only be used + * when the communicator is managed by the final user or MPI library + * (e.g., MPI_COMM_WORLD). + */ + enum comm_create_kind + { + COMM_DUPLICATE, + COMM_TAKE_OWNERSHIP, + COMM_ATTACH + }; + + /* + * Build a new MpiComm communicator for @c MPI_COMM_WORLD. + * + * Constructs a MpiComm communicator that attaches to @c + * MPI_COMM_WORLD. This is the equivalent of constructing with + * @c (MPI_COMM_WORLD, comm_attach). + */ + MpiComm(); + + /** + * Build a new MpiComm communicator based on the MPI communicator + * @p comm. + * + * @p comm may be any valid MPI communicator. If @p comm is + * MPI_COMM_NULL, an empty communicator (that cannot be used for + * communication) is created and the @p kind parameter is + * ignored. Otherwise, the @p kind parameters determines how the + * MpiComm communicator will be related to @p comm: + * + * - If @p kind is @c comm_duplicate, duplicate @c comm to create + * a new communicator. This new communicator will be freed when + * the MpiComm communicator (and all copies of it) is destroyed. + * This option is only permitted if @p comm is a valid MPI + * intracommunicator or if the underlying MPI implementation + * supports MPI 2.0 (which supports duplication of + * intercommunicators). + * + * - If @p kind is @c comm_take_ownership, take ownership of @c + * comm. It will be freed automatically when all of the MpiComm + * communicators go out of scope. This option must not be used + * when @c comm is MPI_COMM_WORLD. + * + * - If @p kind is @c comm_attach, this MpiComm communicator + * will reference the existing MPI communicator @p comm but will + * not free @p comm when the MpiComm communicator goes out of + * scope. This option should only be used when the communicator is + * managed by the user or MPI library (e.g., MPI_COMM_WORLD). + */ + MpiComm(const MPI_Comm & comm, comm_create_kind kind); + + virtual ~MpiComm() = default; + + //! conversion operator to MPI_Comm + operator MPI_Comm() const; + + //! return raw MPI_Comm identifier + inline MPI_Comm + get_MPI_Comm() const + { + return MPI_Comm(*this); + } + + //! conversion operator to boolean + operator bool() const + { + if (comm_ptr) + return true; + return false; + } + + /** + * @brief Determine the rank of the executing process in a + * communicator. + * + * This routine is equivalent to @c MPI_Comm_rank. + * + * @returns The rank of the process in the communicator, which + * will be a value in [0, size()) + */ + inline int + rank() const + { + int rank_; + CHECK_MPI_ERR(::MPI_Comm_rank(MPI_Comm(*this), &rank_)); + return rank_; + } + + /** + * @brief Determine the number of processes in a communicator. + * + * This routine is equivalent to @c MPI_Comm_size. + * + * @returns The number of processes in the communicator. + */ + inline int + size() const + { + int size_; + CHECK_MPI_ERR(::MPI_Comm_size(MPI_Comm(*this), &size_)); + return size_; + } + +#ifdef barrier + // Linux defines a function-like macro named "barrier". So, we need + // to avoid expanding the macro when we define our barrier() + // function. However, some C++ parsers (Doxygen, for instance) can't + // handle this syntax, so we only use it when necessary. + void(barrier)() const; +#else + /** + * @brief Wait for all processes within a communicator to reach the + * barrier. + * + * This routine is a collective operation that blocks each process + * until all processes have entered it, then releases all of the + * processes "simultaneously". It is equivalent to @c MPI_Barrier. + */ + void + MPI_Barrier() const; +#endif + + /** Abort all tasks in the group of this communicator. + * + * Makes a "best attempt" to abort all of the tasks in the group of + * this communicator. Depending on the underlying MPI + * implementation, this may either abort the entire program (and + * possibly return @p errcode to the environment) or only abort + * some processes, allowing the others to continue. Consult the + * documentation for your MPI implementation. This is equivalent to + * a call to @c MPI_Abort + * + * @param errcode The error code to return from aborted processes. + * @returns Will not return. + */ + void + abort(int errcode) const; + +protected: + /** + * INTERNAL ONLY + * + * Function object that frees an MPI communicator and deletes the + * memory associated with it. Intended to be used as a deleter with + * shared_ptr. + */ + struct comm_free + { + void + operator()(MPI_Comm * comm) const + { + assertm(comm != 0, "MPI communicator pointer can't be null"); + assertm(*comm != MPI_COMM_NULL, "MPI communicator can't be MPI_COMM_NULL"); + int finalized; + CHECK_MPI_ERR(MPI_Finalized(&finalized)); + if (!finalized) + CHECK_MPI_ERR(MPI_Comm_free(comm)); + delete comm; + } + }; + +public: + template + void + MPI_Reduce(const T * sendbuf, T * recvbuf, int count, int root) const + { + CHECK_MPI_ERR( + ::MPI_Reduce(sendbuf, recvbuf, count, mpi_type(), MapMpiOp(), root, MPI_Comm(*this))); + } + + template + void + MPI_Allreduce(const T * sendbuf, T * recvbuf, int count) const + { + using namespace MpiComm_impl; + CHECK_MPI_ERR( + ::MPI_Allreduce(sendbuf, recvbuf, count, mpi_type(), MapMpiOp(), MPI_Comm(*this))); + } + + template + void + MPI_Scan(const T * sendbuf, T * recvbuf, int count) const + { + using namespace MpiComm_impl; + CHECK_MPI_ERR( + ::MPI_Scan(sendbuf, recvbuf, count, mpi_type(), MapMpiOp(), MPI_Comm(*this))); + } + + template + void + MPI_Exscan(const T * sendbuf, T * recvbuf, int count) const + { + using namespace MpiComm_impl; + CHECK_MPI_ERR( + ::MPI_Exscan(sendbuf, recvbuf, count, mpi_type(), MapMpiOp(), MPI_Comm(*this))); + } + + template + void + MPI_Allgather(const T * sendbuf, T * recvbuf, int count) const + { + using namespace MpiComm_impl; + CHECK_MPI_ERR(::MPI_Allgather( + sendbuf, count, mpi_type(), recvbuf, count, mpi_type(), MPI_Comm(*this))); + } + + template + void + MPI_Allgatherv_inplace(T * sendrecvbuf, int count) const + { + int comm_size = this->size(); + + std::vector counts(comm_size); + this->MPI_Allgather(&count, counts.data(), 1); + std::vector displs(comm_size); + for (int i = 1; i < comm_size; i++) + displs[i] = displs[i - 1] + counts[i - 1]; + + using namespace MpiComm_impl; + CHECK_MPI_ERR(::MPI_Allgatherv(MPI_IN_PLACE, + 0, + 0, + sendrecvbuf, + counts.data(), + displs.data(), + mpi_type(), + MPI_Comm(*this))); + } + + template + void + MPI_Bcast(T * buffer, int count, int root) const + { + using namespace MpiComm_impl; + CHECK_MPI_ERR(::MPI_Bcast(buffer, count, mpi_type(), root, MPI_Comm(*this))); + } + + template + void + MPI_Alltoall(const T * sendbuf, int sendcount, T * recvbuf, int recvcount) const + { + using namespace MpiComm_impl; + CHECK_MPI_ERR(::MPI_Alltoall( + sendbuf, sendcount, mpi_type(), recvbuf, recvcount, mpi_type(), MPI_Comm(*this))); + } + + // NOTE : does not support sending to self + template + MPI_Request + MPI_Isend(const Kokkos_View_t & view, int dest, int tag) const + { + using namespace MpiComm_impl; + MPI_Datatype type = mpi_type(); + MPI_Request r = MPI_REQUEST_NULL; + CHECK_MPI_ERR(::MPI_Isend( + view.data(), static_cast(view.size()), type, dest, tag, MPI_Comm(*this), &r)); + return r; + } + + template + MPI_Request + MPI_Irecv(const Kokkos_View_t & view, int dest, int tag) const + { + using namespace MpiComm_impl; + MPI_Datatype type = mpi_type(); + MPI_Request r = MPI_REQUEST_NULL; + CHECK_MPI_ERR(::MPI_Irecv( + view.data(), static_cast(view.size()), type, dest, tag, MPI_Comm(*this), &r)); + return r; + } + + inline void + MPI_Waitall(int count, MPI_Request * requests) const + { + CHECK_MPI_ERR(::MPI_Waitall(count, requests, MPI_STATUSES_IGNORE)); + } + + inline void + MPI_Waitall(uint32_t count, MPI_Request * requests) const + { + CHECK_MPI_ERR(::MPI_Waitall(static_cast(count), requests, MPI_STATUSES_IGNORE)); + } + + template + void + MPI_Sendrecv(const Kokkos_View_send_t & view_send, + int dest, + int tag_send, + const Kokkos_View_recv_t & view_recv, + int source, + int tag_recv) const + { + using namespace MpiComm_impl; + MPI_Datatype type_send = mpi_type(); + MPI_Datatype type_recv = mpi_type(); + + CHECK_MPI_ERR(::MPI_Sendrecv(view_send.data(), + view_send.size(), + type_send, + dest, + tag_send, + view_recv.data(), + view_recv.size(), + type_recv, + source, + tag_recv, + MPI_Comm(*this), + MPI_STATUSES_IGNORE)); + } // MPI_Sendrecv + +protected: + //! Raw MPI communicator + std::shared_ptr comm_ptr; + +}; // class MpiComm + +} // namespace kalypsso + +#endif // KALYPSSO_UTILS_MPI_MPICOMM_H diff --git a/src/kalypsso/utils/mpi/MpiCommCart.cpp b/src/kalypsso/utils/mpi/MpiCommCart.cpp new file mode 100644 index 0000000..72a037a --- /dev/null +++ b/src/kalypsso/utils/mpi/MpiCommCart.cpp @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file MpiCommCart.cpp + * \brief Implements class MpiCommCart + * + */ + +#include "MpiCommCart.h" + +namespace kalypsso +{ + +// ======================================================= +// ======================================================= +MpiCommCart::MpiCommCart(int mx, int my, int isPeriodic, int allowReorder) + : MpiComm() + , mx_(mx) + , my_(my) + , mz_(0) + , myCoords_(new int[NDIM_2D]) + , is2D(true) +{ + + int dims[NDIM_2D] = { mx, my }; + int periods[NDIM_2D] = { isPeriodic, isPeriodic }; + + // create virtual topology cartesian 2D + MPI_Comm new_comm; + CHECK_MPI_ERR(::MPI_Cart_create(MPI_COMM_WORLD, NDIM_2D, dims, periods, allowReorder, &new_comm)); + + // take ownership + this->comm_ptr.reset(new MPI_Comm(new_comm), comm_free()); + + // get cartesian coordinates (myCoords_) of current process + const auto my_rank = this->rank(); + getCoords(my_rank, NDIM_2D, myCoords_); +} + +// ======================================================= +// ======================================================= +MpiCommCart::MpiCommCart(int mx, int my, int mz, int isPeriodic, int allowReorder) + : MpiComm() + , mx_(mx) + , my_(my) + , mz_(mz) + , myCoords_(new int[NDIM_3D]) + , is2D(false) +{ + int dims[NDIM_3D] = { mx, my, mz }; + int periods[NDIM_3D] = { isPeriodic, isPeriodic, isPeriodic }; + + // create virtual topology cartesian 3D + MPI_Comm new_comm; + CHECK_MPI_ERR(::MPI_Cart_create(MPI_COMM_WORLD, NDIM_3D, dims, periods, allowReorder, &new_comm)); + + // take ownership + this->comm_ptr.reset(new MPI_Comm(new_comm), comm_free()); + + // get cartesian coordinates (myCoords_) of current process + const auto my_rank = this->rank(); + getCoords(my_rank, NDIM_3D, myCoords_); +} + +// ======================================================= +// ======================================================= +MpiCommCart::~MpiCommCart() +{ + delete[] myCoords_; +} + +} // namespace kalypsso diff --git a/src/kalypsso/utils/mpi/MpiCommCart.h b/src/kalypsso/utils/mpi/MpiCommCart.h new file mode 100644 index 0000000..6071b3e --- /dev/null +++ b/src/kalypsso/utils/mpi/MpiCommCart.h @@ -0,0 +1,189 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file MpiCommCart.h + * \brief A simple derive class of MpiComm to handle cartesian + * topology. + * + */ +#ifndef KALYPSSO_UTILS_MPI_MPICOMMCART_H_ +#define KALYPSSO_UTILS_MPI_MPICOMMCART_H_ + +#include "MpiComm.h" +#include "mpiEnums.h" + +namespace kalypsso +{ + +/** + * \brief Object representation of an MPI communicator with + * cartesian virtual topology. + */ +class MpiCommCart : public MpiComm +{ +public: + //! Construct a MpiCommCart using a 2D cartesian virtual topology + MpiCommCart(int mx, int my, int isPeriodic, int allowReorder); + + //! Construct a MpiCommCart using a 3D cartesian virtual topology + MpiCommCart(int mx, int my, int mz, int isPeriodic, int allowReorder); + + //! Destructor + virtual ~MpiCommCart(); + +private: + int mx_, my_, mz_; + int * myCoords_; + bool is2D; + +public: + //! get topology dimension (2 or 3) from MPI_Cartdim_get just for cross-checking + virtual int + getDim() const; + + //! use MPI_Cart_rank to get rank of the shifted process + virtual int + getCartRank(const int coords[]) const; + + //! return cartesian coordinate of a process of a given rank + virtual void + getCoords(int rank, int maxdims, int coords[]) const; + + //! return my coordinates + virtual void + getMyCoords(int coords[]); + + //! use MPI_Cart_shift to get rank of the shifted processes + //! regarding the given direction + virtual void + shift(int direction, int disp, int & rank_source, int & rank_dest) const; + + //! return rank of the neighbor process identified by the template parameter + template + int + getNeighborRank() const; +}; + +// ======================================================= +// ======================================================= +inline int +MpiCommCart::getDim() const +{ + int ndims; + CHECK_MPI_ERR(::MPI_Cartdim_get(this->get_MPI_Comm(), &ndims)); + return ndims; +} + +// ======================================================= +// ======================================================= +inline int +MpiCommCart::getCartRank(const int coords[]) const +{ + int my_rank; + CHECK_MPI_ERR(::MPI_Cart_rank(this->get_MPI_Comm(), const_cast(coords), &my_rank)); + return my_rank; +} + +// ======================================================= +// ======================================================= +inline void +MpiCommCart::getCoords(int rank, int maxdims, int coords[]) const +{ + CHECK_MPI_ERR(::MPI_Cart_coords(this->get_MPI_Comm(), rank, maxdims, coords)); +} + +// ======================================================= +// ======================================================= +inline void +MpiCommCart::getMyCoords(int coords[]) +{ + int maxDim = (is2D ? 2 : 3); + for (int i = 0; i < maxDim; ++i) + { + coords[i] = myCoords_[i]; + } +} + +// ======================================================= +// ======================================================= +inline void +MpiCommCart::shift(int direction, int disp, int & rank_source, int & rank_dest) const +{ + CHECK_MPI_ERR(::MPI_Cart_shift(this->get_MPI_Comm(), direction, disp, &rank_source, &rank_dest)); +} + +// ======================================================= +// ======================================================= +template +int +MpiCommCart::getNeighborRank() const +{ + + int rank; + + if (is2D) + { // 2D + int coords[NDIM_2D]; + coords[0] = myCoords_[0]; + coords[1] = myCoords_[1]; + + if (nl == X_MIN) + { + coords[0] -= 1; + } + else if (nl == X_MAX) + { + coords[0] += 1; + } + else if (nl == Y_MIN) + { + coords[1] -= 1; + } + else if (nl == Y_MAX) + { + coords[1] += 1; + } + rank = getCartRank(coords); + } + else + { // 3D + int coords[NDIM_3D]; + coords[0] = myCoords_[0]; + coords[1] = myCoords_[1]; + coords[2] = myCoords_[2]; + + if (nl == X_MIN) + { + coords[0] -= 1; + } + else if (nl == X_MAX) + { + coords[0] += 1; + } + else if (nl == Y_MIN) + { + coords[1] -= 1; + } + else if (nl == Y_MAX) + { + coords[1] += 1; + } + else if (nl == Z_MIN) + { + coords[2] -= 1; + } + else if (nl == Z_MAX) + { + coords[2] += 1; + } + rank = getCartRank(coords); + } + + return rank; +} + +} // namespace kalypsso + +#endif // KALYPSSO_UTILS_MPI_MPICOMMCART_H_ diff --git a/src/kalypsso/utils/mpi/ParallelEnv.cpp b/src/kalypsso/utils/mpi/ParallelEnv.cpp new file mode 100644 index 0000000..00b75ef --- /dev/null +++ b/src/kalypsso/utils/mpi/ParallelEnv.cpp @@ -0,0 +1,231 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ParallelEnv.cpp + */ +#include "ParallelEnv.h" + +#include + +#include +#include + +namespace kalypsso +{ + +// =================================================================================== +// =================================================================================== +void +set_p4est_log_priority_from_env() +{ + + if ([[maybe_unused]] const char * env_value = std::getenv("P4EST_LOG_PRIORITY")) + { + + printf("P4EST_LOG_PRIORITY set from environment to value %s\n", env_value); + + if (!std::strcmp(env_value, "SC_LP_ALWAYS")) + { + sc_package_set_verbosity(sc_package_id, SC_LP_ALWAYS); + sc_package_set_verbosity(p4est_package_id, SC_LP_ALWAYS); + } + else if (!std::strcmp(env_value, "SC_LP_TRACE")) + { + sc_package_set_verbosity(sc_package_id, SC_LP_TRACE); + sc_package_set_verbosity(p4est_package_id, SC_LP_TRACE); + } + else if (!std::strcmp(env_value, "SC_LP_INFO")) + { + sc_package_set_verbosity(sc_package_id, SC_LP_INFO); + sc_package_set_verbosity(p4est_package_id, SC_LP_INFO); + } + else if (!std::strcmp(env_value, "SC_LP_STATISTICS")) + { + sc_package_set_verbosity(sc_package_id, SC_LP_STATISTICS); + sc_package_set_verbosity(p4est_package_id, SC_LP_STATISTICS); + } + else if (!std::strcmp(env_value, "SC_LP_PRODUCTION")) + { + sc_package_set_verbosity(sc_package_id, SC_LP_PRODUCTION); + sc_package_set_verbosity(p4est_package_id, SC_LP_PRODUCTION); + } + else if (!std::strcmp(env_value, "SC_LP_ESSENTIAL")) + { + sc_package_set_verbosity(sc_package_id, SC_LP_ESSENTIAL); + sc_package_set_verbosity(p4est_package_id, SC_LP_ESSENTIAL); + } + else if (!std::strcmp(env_value, "SC_LP_ERROR")) + { + sc_package_set_verbosity(sc_package_id, SC_LP_ERROR); + sc_package_set_verbosity(p4est_package_id, SC_LP_ERROR); + } + else if (!std::strcmp(env_value, "SC_LP_SILENT")) + { + sc_package_set_verbosity(sc_package_id, SC_LP_SILENT); + sc_package_set_verbosity(p4est_package_id, SC_LP_SILENT); + } + } + +} // set_p4est_log_priority_from_env + +// =================================================================================== +// =================================================================================== +ParallelEnv::ParallelEnv(int & argc, char **& argv) +{ + bool m_initialize_kokkos_before_mpi = false; + +#ifdef KOKKOS_ENABLE_CUDA + // when running on platform with Intel OmniPath interconnect and Nvidia GPUs, we may + // need to initialize Kokkos (and Cuda context first) + if ([[maybe_unused]] const char * env_value = std::getenv("PSM2_CUDA")) + { + std::cout << "PSM2_CUDA detected : Initializing Kokkos before MPI" << std::endl; + m_initialize_kokkos_before_mpi = true; + } +#endif + + if ([[maybe_unused]] const char * env_value = std::getenv("KALYPSSO_INIT_KOKKOS_BEFORE_MPI")) + { + std::cout << "KALYPSSO_INIT_KOKKOS_BEFORE_MPI detected : Initializing Kokkos before MPI" + << std::endl; + m_initialize_kokkos_before_mpi = true; + } + + if (m_initialize_kokkos_before_mpi) + Kokkos::initialize(argc, argv); + +#ifdef KALYPSSO_CORE_USE_MPI + // Create MPI session if MPI enabled + m_mpiSession = std::make_unique(argc, argv); + + // create a communicator for MPI_COMM_WORLD + m_comm_ptr = std::make_unique(); +#endif // KALYPSSO_CORE_USE_MPI + + if (!m_initialize_kokkos_before_mpi) + Kokkos::initialize(argc, argv); + + print_kokkos_config(); + + // initialize sc +#ifdef KALYPSSO_CORE_USE_MPI + sc_init(m_comm_ptr->get_MPI_Comm(), 1, 1, nullptr, SC_LP_DEFAULT); +#else + sc_init(sc_MPI_COMM_WORLD, 1, 1, nullptr, SC_LP_DEFAULT); +#endif // KALYPSSO_CORE_USE_MPI + + // initialize p4est + p4est_init(nullptr, SC_LP_DEFAULT); + + set_p4est_log_priority_from_env(); + +} // ParallelEnv::ParallelEnv + +#ifdef KALYPSSO_CORE_USE_MPI +// =================================================================================== +// =================================================================================== +ParallelEnv::ParallelEnv(int argc, char * argv[], const MPI_Comm & comm) +{ + // Create MPI session (check if MPI is already initialized, it should be) + m_mpiSession = std::make_unique(argc, argv); + + // create a communicator wrapping comm + m_comm_ptr = std::make_unique(comm, MpiComm::COMM_DUPLICATE); + + // initialize kokkos + Kokkos::initialize(argc, argv); + print_kokkos_config(); + + // initialize p4est + sc_init(m_comm_ptr->get_MPI_Comm(), 1, 1, nullptr, SC_LP_DEFAULT); + p4est_init(nullptr, SC_LP_DEFAULT); + + set_p4est_log_priority_from_env(); + +} // ParallelEnv::ParallelEnv +#endif // KALYPSSO_CORE_USE_MPI + +// =================================================================================== +// =================================================================================== +ParallelEnv::~ParallelEnv() +{ + // clean up p4est and sc then exit + sc_finalize(); + + // cleanup kokkos + Kokkos::finalize(); + +} // ParallelEnv::~ParallelEnv + +// =================================================================================== +// =================================================================================== +bool +ParallelEnv::MPI_enabled() +{ +#ifdef KALYPSSO_CORE_USE_MPI + return true; +#else // KALYPSSO_CORE_USE_MPI + return false; +#endif // KALYPSSO_CORE_USE_MPI +} // ParallelEnv::MPI_enabled + +// =================================================================================== +// =================================================================================== +void +ParallelEnv::print_kokkos_config() +{ + // only master MPI task print Kokkos config information + if (rank() == 0) + { + std::cout << "##########################\n"; + std::cout << "KOKKOS CONFIG \n"; + std::cout << "##########################\n"; + std::ostringstream msg; + std::cout << "Kokkos configuration" << std::endl; + if (Kokkos::hwloc::available()) + { + msg << "hwloc( NUMA[" << Kokkos::hwloc::get_available_numa_count() << "] x CORE[" + << Kokkos::hwloc::get_available_cores_per_numa() << "] x HT[" + << Kokkos::hwloc::get_available_threads_per_core() << "] )" << std::endl; + } + Kokkos::print_configuration(msg); + std::cout << msg.str(); + std::cout << "##########################\n"; + std::cout << "END KOKKOS CONFIG \n"; + std::cout << "##########################\n"; + } + +#ifdef KOKKOS_ENABLE_CUDA + if ([[maybe_unused]] const char * env_value = std::getenv("CUDA_VISIBLE_DEVICES")) + { + std::cout << "I'm MPI task #" << this->rank() << " CUDA_VISIBLE_DEVICES was set to " + << std::string(env_value) << "\n"; + } + else + { + std::cout << "I'm MPI task #" << this->rank() << " CUDA_VISIBLE_DEVICES was not set" << "\n"; + } + + if (MPI_enabled()) + { + + // To enable kokkos accessing multiple GPUs don't forget to + // add option "--ndevices=X" where X is the number of GPUs + // you want to use per node. + + // on a large cluster, the scheduler should assign resources + // in a way that each MPI task is mapped to a different GPU + // let's cross-checked that: + + int cudaDeviceId; + cudaGetDevice(&cudaDeviceId); + std::cout << "I'm MPI task #" << this->rank() << " (out of " << this->nRanks() << ")" + << " pinned to GPU #" << cudaDeviceId << "\n"; + } +#endif // KOKKOS_ENABLE_CUDA + +} // ParallelEnv::print_kokkos_config + +} // namespace kalypsso diff --git a/src/kalypsso/utils/mpi/ParallelEnv.h b/src/kalypsso/utils/mpi/ParallelEnv.h new file mode 100644 index 0000000..923a46c --- /dev/null +++ b/src/kalypsso/utils/mpi/ParallelEnv.h @@ -0,0 +1,141 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file ParallelEnv.h + * \brief Provide a class for initializing, finalizing environment for parallel + * computation (MPI, Kokkos, p4est, ...) + * + * MPI is optional + * Kokkos and p4est are mandatory + * + */ +#ifndef KALYPSSO_UTILS_MPI_PARALLEL_ENV_H_ +#define KALYPSSO_UTILS_MPI_PARALLEL_ENV_H_ + +#include + +#ifdef KALYPSSO_CORE_USE_MPI +# include +# include +#endif // KALYPSSO_CORE_USE_MPI + +#include +#include // for KOKKOS_ENABLE_XXX + +#include // for std::getenv +#include // for std::make_unique +#include // for p4est_init / sc_init + +namespace kalypsso +{ + +/** + * If env variable P4EST_LOG_PRIORITY is defined, use it to modify p4est log priority level. + * + * \note if this env varriable is not defined, SC_LP_DEFAULT is used. + */ +void +set_p4est_log_priority_from_env(); + +/** + * \class ParallelEnv + * Should initialize MPI, Kokkos and p4est. + */ +class ParallelEnv +{ +private: +#ifdef KALYPSSO_CORE_USE_MPI + std::unique_ptr m_mpiSession; + std::unique_ptr m_comm_ptr; +#endif // KALYPSSO_CORE_USE_MPI + +public: + /** + * Default constructor. + * + * To be used in a standalone app. + * MPI communicator will default to MPI_COMM_WORLD + */ + ParallelEnv(int & argc, char **& argv); + +#ifdef KALYPSSO_CORE_USE_MPI + /** + * Additional constructor to be used when kalypsso is used as library. + * + * The calling code should provide a MPI communicator. + * Here we either attach to the provided MPI communicator or duplicate it. + * + * In this case, we take responsibility to initialize kokkos and p4est unconditionally. + */ + ParallelEnv(int argc, char * argv[], const MPI_Comm & comm); +#endif // KALYPSSO_CORE_USE_MPI + + // Destructor. + ~ParallelEnv(); + + //! \return MPI rank + inline int + rank() const + { +#ifdef KALYPSSO_CORE_USE_MPI + return m_comm_ptr->rank(); +#else + return 0; +#endif // KALYPSSO_CORE_USE_MPI + } + + //! \return MPI size + inline int + nRanks() const + { +#ifdef KALYPSSO_CORE_USE_MPI + return m_comm_ptr->size(); +#else + return 1; +#endif // KALYPSSO_CORE_USE_MPI + } + + //! \return MPI size + inline int + size() const + { +#ifdef KALYPSSO_CORE_USE_MPI + return m_comm_ptr->size(); +#else + return 1; +#endif // KALYPSSO_CORE_USE_MPI + } + +#ifdef KALYPSSO_CORE_USE_MPI + + //! \return MPI communicator (see MPIComm) + const MpiComm & + comm() const + { + return *m_comm_ptr; + } + + //! return raw MPI communicator + MPI_Comm + mpi_comm() const + { + return m_comm_ptr->get_MPI_Comm(); + } +#endif // KALYPSSO_CORE_USE_MPI + + //! \return boolean to indicated if MPI is enabled + static bool + MPI_enabled(); + +private: + //! print Kokkos configuration (backend enabled, etc...) + void + print_kokkos_config(); + +}; // class ParallelEnv + +} // namespace kalypsso + +#endif // KALYPSSO_UTILS_MPI_PARALLEL_ENV_H_ diff --git a/src/kalypsso/utils/mpi/mpiEnums.h b/src/kalypsso/utils/mpi/mpiEnums.h new file mode 100644 index 0000000..5590789 --- /dev/null +++ b/src/kalypsso/utils/mpi/mpiEnums.h @@ -0,0 +1,78 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef KALYPSSO_UTILS_MPI_MPIENUMS_H_ +#define KALYPSSO_UTILS_MPI_MPIENUMS_H_ + +namespace kalypsso +{ + +//! defgroup mpi_cartesian +//!@{ +//! number of dimensions of the cartesian virtual topology +enum +{ + NDIM_2D = 2, + NDIM_3D = 3 +}; + +//! do we allow processor reordering by the MPI cartesian communicator ? +enum +{ + MPI_REORDER_FALSE = 0, + MPI_REORDER_TRUE = 1 +}; + +//! should the cartesian virtual topology be considered periodic ? +enum +{ + MPI_CART_PERIODIC_FALSE = 0, + MPI_CART_PERIODIC_TRUE = 1 +}; + +//! MPI topology directions +enum +{ + MPI_TOPO_DIR_X = 0, + MPI_TOPO_DIR_Y = 1, + MPI_TOPO_DIR_Z = 2 +}; + +//! MPI topology shift direction +enum +{ + MPI_SHIFT_NONE = 0, + MPI_SHIFT_FORWARD = 1 +}; + +//! identifying neighbors +enum NeighborLocation +{ + X_MIN = 0, + X_MAX = 1, + Y_MIN = 2, + Y_MAX = 3, + Z_MIN = 4, + Z_MAX = 5 +}; + +//! number of neighbors +enum +{ + N_NEIGHBORS_2D = 4, + N_NEIGHBORS_3D = 6 +}; + +// //! direction +// enum Dir { +// DIR_X=0, +// DIR_Y=1, +// DIR_Z=2 +// }; + +//!@} + +} // namespace kalypsso + +#endif // KALYPSSO_UTILS_MPI_MPIENUMS_H_ diff --git a/src/kalypsso/utils/mpi/mpi_utils.h b/src/kalypsso/utils/mpi/mpi_utils.h new file mode 100644 index 0000000..59909c2 --- /dev/null +++ b/src/kalypsso/utils/mpi/mpi_utils.h @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef KALYPSSO_UTILS_MPI_MPIUTILS_H_ +#define KALYPSSO_UTILS_MPI_MPIUTILS_H_ + +#include + +#include + +namespace kalypsso +{ + +//! inline function checking the result of a MPI API call +int inline mpi_check_error(int err, + char const * const func, + const char * const file, + int const line) +{ + + if (err != MPI_SUCCESS) + { + int errorStringLen; + char errorString[MPI_MAX_ERROR_STRING]; + MPI_Error_string(err, errorString, &errorStringLen); + std::fprintf(stderr, "Error at %s:%d: calling %s ==> %s\n", file, line, func, errorString); + } + + return err; +} // check_mpi_error + +//! preprocessor macro ensuring that the error message can print filen name and line number +#define CHECK_MPI_ERR(value) mpi_check_error((value), #value, __FILE__, __LINE__) + +} // namespace kalypsso + +#endif // KALYPSSO_UTILS_MPI_MPIUTILS_H_ diff --git a/src/kalypsso/utils/p4est/CMakeLists.txt b/src/kalypsso/utils/p4est/CMakeLists.txt new file mode 100644 index 0000000..91e3b03 --- /dev/null +++ b/src/kalypsso/utils/p4est/CMakeLists.txt @@ -0,0 +1,44 @@ +add_library( + kalypsso_p4est_wrap + ${CMAKE_CURRENT_SOURCE_DIR}/connectivity.h ${CMAKE_CURRENT_SOURCE_DIR}/connectivity.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/geometry.h ${CMAKE_CURRENT_SOURCE_DIR}/geometry.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/p4est_wrapper.h ${CMAKE_CURRENT_SOURCE_DIR}/p4est_wrapper.cpp) + +# add kalypsso/utils to include dirs for kalypsso_core_config.h +target_include_directories(kalypsso_p4est_wrap PUBLIC $ + $) + +target_link_libraries(kalypsso_p4est_wrap PRIVATE kalypsso_warnings kalypsso_core_config) +target_link_libraries(kalypsso_p4est_wrap PUBLIC kalypsso::config P4EST::P4EST SC::SC) + +if(KALYPSSO_CORE_USE_MPI) + target_link_libraries(kalypsso_p4est_wrap PUBLIC MPI::MPI_CXX) +endif(KALYPSSO_CORE_USE_MPI) + +# Add an alias so that library can be used inside the build tree +add_library(kalypsso::p4est_wrap ALIAS kalypsso_p4est_wrap) + +# +# Get prepared for install +# +include(${PROJECT_SOURCE_DIR}/cmake/setup_install.cmake) + +# +# install libraries and headers +# +install( + TARGETS kalypsso_p4est_wrap + EXPORT kalypsso-targets + LIBRARY DESTINATION ${INSTALL_LIBDIR} + ARCHIVE DESTINATION ${INSTALL_LIBDIR} + RUNTIME DESTINATION ${INSTALL_BINDIR}) + +# +# install headers (exclude cpp files) +# +install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DESTINATION ${INSTALL_INCLUDEDIR}/kalypsso/utils + FILES_MATCHING + PATTERN "*.h" + PATTERN "*.cpp" EXCLUDE) diff --git a/src/kalypsso/utils/p4est/connectivity.cpp b/src/kalypsso/utils/p4est/connectivity.cpp new file mode 100644 index 0000000..22e4908 --- /dev/null +++ b/src/kalypsso/utils/p4est/connectivity.cpp @@ -0,0 +1,1430 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * @file connectivity.cpp + * @date 17 octobre 2020 + * @author pkestene + * @note + * + * This file is part of the Kalypsso software project. + * + */ +#include "connectivity.h" + +#include + +p4est_connectivity_t * +p4est_connectivity_new_two(connectivity_periodic_t is_periodic_x, + connectivity_periodic_t is_periodic_y) +{ + /* + * p4est_connectivity_new_brick constructs a set of m x n x p trees that + * are sitting side by side and can be periodic in either x, y or z. + */ + return p4est_connectivity_new_brick(2, 1, is_periodic_x, is_periodic_y); +} + +p8est_connectivity_t * +p8est_connectivity_new_two(connectivity_periodic_t is_periodic_x, + connectivity_periodic_t is_periodic_y, + connectivity_periodic_t is_periodic_z) +{ + /* + * p4est_connectivity_new_brick constructs a set of m x n x p trees that + * are sitting side by side and can be periodic in either x, y or z. + */ + return p8est_connectivity_new_brick(2, 1, 1, is_periodic_x, is_periodic_y, is_periodic_z); +} + +p4est_connectivity_t * +p4est_connectivity_new_two_simple(void) +{ + return p4est_connectivity_new_brick(2, 1, 0, 0); +} + +p8est_connectivity_t * +p8est_connectivity_new_two_simple(void) +{ + return p8est_connectivity_new_brick(2, 1, 1, 0, 0, 0); +} + +p4est_connectivity_t * +p4est_connectivity_new_shock_tube(void) +{ + return p4est_connectivity_new_brick(10, 1, 0, 1); +} + +p8est_connectivity_t * +p8est_connectivity_new_shock_tube(void) +{ + return p8est_connectivity_new_brick(10, 1, 1, 0, 1, 0); +} + +p4est_connectivity_t * +p4est_connectivity_new_tetris(void) +{ + + // clang-format off +/* *INDENT-OFF* */ + const p4est_topidx_t num_vertices = 12; + const p4est_topidx_t num_trees = 5; + const p4est_topidx_t num_corners = 2; + const double vertices[12 * 3] = { + 0, 0, 0, /* vertex 0 */ + 1, 0, 0, /* vertex 1 */ + 2, 0, 0, /* vertex 2 */ + 3, 0, 0, /* vertex 3 */ + 4, 0, 0, /* vertex 4 */ + 0, -1, 0, /* vertex 5 */ + 1, -1, 0, /* vertex 6 */ + 2, -1, 0, /* vertex 7 */ + 3, -1, 0, /* vertex 8 */ + 4, -1, 0, /* vertex 9 */ + 1, -2, 0, /* vertex 10 */ + 2, -2, 0, /* vertex 11 */ + }; + const p4est_topidx_t tree_to_vertex[5 * 4] = { + 5, 6, 0, 1, /* tree 0 */ + 6, 7, 1, 2, /* tree 1 */ + 7, 8, 2, 3, /* tree 2 */ + 8, 9, 3, 4, /* tree 3 */ + 10,11, 6, 7, /* tree 4 */ + }; + const p4est_topidx_t tree_to_tree[5 * 4] = { + 0, 1, 0, 0, /* tree 0 */ + 0, 2, 4, 1, /* tree 1 */ + 1, 3, 2, 2, /* tree 2 */ + 2, 3, 3, 3, /* tree 3 */ + 4, 4, 4, 1, /* tree 4 */ + }; + const int8_t tree_to_face[5 * 4] = { + 0, 0, 2, 3, /* tree 0 */ + 1, 0, 3, 3, /* tree 1 */ + 1, 0, 2, 3, /* tree 2 */ + 1, 1, 2, 3, /* tree 3 */ + 0, 1, 2, 2, /* tree 4 */ + }; + const p4est_topidx_t tree_to_corner[5 * 4] = { + -1, 0, -1, -1, /* tree 0 */ + 0 , 1, -1, -1, /* tree 1 */ + 1 , -1, -1, -1, /* tree 2 */ + -1, -1, -1, -1, /* tree 3 */ + -1, -1, 0, 1, /* tree 4 */ + }; + const p4est_topidx_t ctt_offset[2+1] = { + 0,3,6, + }; + const p4est_topidx_t corner_to_tree[6] = { + 0,1,4, /* corner 0 (i.e vertex 6) */ + 1,2,4, /* corner 1 (i.e vertex 7) */ + }; + + /* a given corner belong to multiple trees; + for each tree, we report the index identifying the vertex location + in the tree_to_vertex. + e.g. here : + - corner 0 is vertex 6 + - corner 0 is vertex 7 + For each entry in corner_to_tree, we report the location of the vertex in + tree_to_vertex + */ + const int8_t corner_to_corner[6] = { + 1, 0, 2, /* corner 0 (i.e vertex 6) */ + 1, 0, 3, /* corner 1 (i.e vertex 7) */ + }; + +/* *INDENT-ON* */ + // clang-format on + + p4est_connectivity_t * conn = p4est_connectivity_new_copy(num_vertices, + num_trees, + num_corners, + vertices, + tree_to_vertex, + tree_to_tree, + tree_to_face, + tree_to_corner, + ctt_offset, + corner_to_tree, + corner_to_corner); + + P4EST_ASSERT(p4est_connectivity_is_valid(conn)); + + return conn; + +} /* p4est_connectivity_new_tetris */ + +p4est_connectivity_t * +p4est_connectivity_new_ring(int num_trees_radial, + int num_trees_orthoradial, + double rMin, + double rMax) +{ + p4est_topidx_t num_vertices = (num_trees_radial + 1) * (num_trees_orthoradial); + p4est_topidx_t num_trees = num_trees_radial * num_trees_orthoradial; + p4est_topidx_t num_ctt = 0; + double * vertices = new double[num_vertices * 3]; + + p4est_topidx_t * tree_to_vertex = new p4est_topidx_t[num_trees * 4]; + p4est_topidx_t * tree_to_tree = new p4est_topidx_t[num_trees * 4]; + int8_t * tree_to_face = new int8_t[num_trees * 4]; + + int iVertex = 0; + int iRadial; + int iOrthoradial; + int iTree; + + // fill vertices array + for (iRadial = 0; iRadial < num_trees_radial + 1; iRadial++) + { + + double radius = rMin + iRadial * (rMax - rMin) / num_trees_radial; + + for (iOrthoradial = 0; iOrthoradial < num_trees_orthoradial; iOrthoradial++) + { + vertices[3 * iVertex + 0] = radius * cos(2 * M_PI / num_trees_orthoradial * iOrthoradial); + vertices[3 * iVertex + 1] = radius * sin(2 * M_PI / num_trees_orthoradial * iOrthoradial); + vertices[3 * iVertex + 2] = 0; + + iVertex++; + } + } + + // fill tree to vertex + iTree = 0; + for (iRadial = 0; iRadial < num_trees_radial; iRadial++) + { + for (iOrthoradial = 0; iOrthoradial < num_trees_orthoradial; iOrthoradial++) + { + + p4est_topidx_t left_corner_vertex = iOrthoradial + num_trees_orthoradial * iRadial; + + tree_to_vertex[4 * iTree + 0] = left_corner_vertex; + tree_to_vertex[4 * iTree + 1] = left_corner_vertex + num_trees_orthoradial; + tree_to_vertex[4 * iTree + 2] = left_corner_vertex + 1; + tree_to_vertex[4 * iTree + 3] = left_corner_vertex + 1 + num_trees_orthoradial; + + // need to modify this when we cross iTree % num_trees_orthoradial == 0 + if (iTree % num_trees_orthoradial == num_trees_orthoradial - 1) + { + tree_to_vertex[4 * iTree + 2] -= num_trees_orthoradial; + tree_to_vertex[4 * iTree + 3] -= num_trees_orthoradial; + } + + iTree++; + } + } + + // fill tree to tree + iTree = 0; + for (iRadial = 0; iRadial < num_trees_radial; iRadial++) + { + for (iOrthoradial = 0; iOrthoradial < num_trees_orthoradial; iOrthoradial++) + { + + tree_to_tree[4 * iTree + 0] = + iTree - num_trees_orthoradial < 0 ? iTree : iTree - num_trees_orthoradial; + tree_to_tree[4 * iTree + 1] = + iTree + num_trees_orthoradial >= num_trees ? iTree : iTree + num_trees_orthoradial; + tree_to_tree[4 * iTree + 2] = iTree - 1; + tree_to_tree[4 * iTree + 3] = iTree + 1; + + // need to modify this when we cross iTree % num_trees_orthoradial == 0 + if (iTree % num_trees_orthoradial == 0) + { + tree_to_tree[4 * iTree + 2] += num_trees_orthoradial; + } + if (iTree % num_trees_orthoradial == num_trees_orthoradial - 1) + { + tree_to_tree[4 * iTree + 3] -= num_trees_orthoradial; + } + + iTree++; + } + } + + // tree_to_face + iTree = 0; + for (iRadial = 0; iRadial < num_trees_radial; iRadial++) + { + for (iOrthoradial = 0; iOrthoradial < num_trees_orthoradial; iOrthoradial++) + { + tree_to_face[4 * iTree + 0] = 1; + tree_to_face[4 * iTree + 1] = 0; + tree_to_face[4 * iTree + 2] = 3; + tree_to_face[4 * iTree + 3] = 2; + + // need to modify this when tree is on edge (inner or outer) + if (iTree < num_trees_orthoradial) + { // inner border + tree_to_face[4 * iTree + 0] = 0; + } + if (iTree + num_trees_orthoradial >= num_trees) + { // outer border + tree_to_face[4 * iTree + 1] = 1; + } + + iTree++; + } + } + + p4est_connectivity_t * conn = p4est_connectivity_new_copy(num_vertices, + num_trees, + 0, + vertices, + tree_to_vertex, + tree_to_tree, + tree_to_face, + NULL, + &num_ctt, + NULL, + NULL); + + P4EST_ASSERT(p4est_connectivity_is_valid(conn)); + + delete[] vertices; + delete[] tree_to_vertex; + delete[] tree_to_tree; + delete[] tree_to_face; + + return conn; + +} /* p4est_connectivity_new_ring */ + +p4est_connectivity_t * +p4est_connectivity_new_forward_facing_step_small(void) +{ + + const p4est_topidx_t num_vertices = 21; /* 6*3 + 3 */ + const p4est_topidx_t num_trees = 12; /* 5*2 + 2 */ + const p4est_topidx_t num_corners = 6; /* 4 + 2 */ + double vertices[21 * 3]; + p4est_topidx_t tree_to_vertex[12 * 4]; + p4est_topidx_t tree_to_tree[12 * 4]; + int8_t tree_to_face[12 * 4]; + p4est_topidx_t tree_to_corner[12 * 4]; + p4est_topidx_t ctt_offset[6 + 1]; + p4est_topidx_t corner_to_tree[23]; /* 5 corners*4trees + 1 corner*3trees */ + int8_t corner_to_corner[23]; /* 5 corners*4trees + 1 corner*3trees */ + + int i, j, iVertex = 0; + + double dx = 0.2; + double dy = 0.2; + + /* fill vertices the 18 first vertices */ + for (j = 0; j <= 2; j++) + { + for (i = 0; i <= 5; i++) + { + vertices[3 * iVertex + 0] = i * dx; /* */ + vertices[3 * iVertex + 1] = 0.6 - j * dy; /* */ + vertices[3 * iVertex + 2] = 0.0; /* */ + iVertex++; + } + } + + /* and the last 3 */ + vertices[3 * 18 + 0] = 0.0; + vertices[3 * 18 + 1] = 0.0; + vertices[3 * 18 + 2] = 0.0; + + vertices[3 * 19 + 0] = 0.0 + dx; + vertices[3 * 19 + 1] = 0.0; + vertices[3 * 19 + 2] = 0.0; + + vertices[3 * 20 + 0] = 0.0 + 2 * dx; + vertices[3 * 20 + 1] = 0.0; + vertices[3 * 20 + 2] = 0.0; + + /* + * tree to vertex + */ + { + int iTree = 0; + int v0 = 6; + int v1 = 7; + int v2 = 0; + int v3 = 1; + + for (j = 0; j < 2; j++) + { + for (i = 0; i < 5; i++) + { + iTree = j * 5 + i; + + tree_to_vertex[4 * iTree + 0] = v0 + i + j * 6; + tree_to_vertex[4 * iTree + 1] = v1 + i + j * 6; + tree_to_vertex[4 * iTree + 2] = v2 + i + j * 6; + tree_to_vertex[4 * iTree + 3] = v3 + i + j * 6; + + /* printf("ttv %d | %d %d %d %d\n",iTree, */ + /* tree_to_vertex[4*iTree + 0], */ + /* tree_to_vertex[4*iTree + 1], */ + /* tree_to_vertex[4*iTree + 2], */ + /* tree_to_vertex[4*iTree + 3]); */ + } + } + + tree_to_vertex[4 * 10 + 0] = 18; + tree_to_vertex[4 * 10 + 1] = 19; + tree_to_vertex[4 * 10 + 2] = 12; + tree_to_vertex[4 * 10 + 3] = 13; + + tree_to_vertex[4 * 11 + 0] = 19; + tree_to_vertex[4 * 11 + 1] = 20; + tree_to_vertex[4 * 11 + 2] = 13; + tree_to_vertex[4 * 11 + 3] = 14; + } + + /* + * tree to tree + */ + { + int iTree = 0; + + for (j = 0; j < 2; j++) + { + for (i = 0; i < 5; i++) + { + iTree = j * 5 + i; + + tree_to_tree[4 * iTree + 0] = iTree - 1; /* left */ + tree_to_tree[4 * iTree + 1] = iTree + 1; /* right */ + tree_to_tree[4 * iTree + 2] = iTree + 5; /* below */ + tree_to_tree[4 * iTree + 3] = iTree - 5; /* above */ + + if (i == 0) + tree_to_tree[4 * iTree + 0] = iTree; + if (i == 4) + tree_to_tree[4 * iTree + 1] = iTree; + if (j == 0) + tree_to_tree[4 * iTree + 3] = iTree; + if (j == 1 && i >= 2) + tree_to_tree[4 * iTree + 2] = iTree; + + /* printf("ttt %d | %d %d %d %d\n",iTree, */ + /* tree_to_tree[4*iTree + 0], */ + /* tree_to_tree[4*iTree + 1], */ + /* tree_to_tree[4*iTree + 2], */ + /* tree_to_tree[4*iTree + 3]); */ + } + } + + tree_to_tree[4 * 10 + 0] = 10; + tree_to_tree[4 * 10 + 1] = 11; + tree_to_tree[4 * 10 + 2] = 10; + tree_to_tree[4 * 10 + 3] = 10 - 5; + + tree_to_tree[4 * 11 + 0] = 10; + tree_to_tree[4 * 11 + 1] = 11; + tree_to_tree[4 * 11 + 2] = 11; + tree_to_tree[4 * 11 + 3] = 11 - 5; + } + + /* + * tree to face + */ + { + int iTree = 0; + + for (j = 0; j < 2; j++) + { + for (i = 0; i < 5; i++) + { + iTree = j * 5 + i; + + tree_to_face[4 * iTree + 0] = 1; /* left */ + tree_to_face[4 * iTree + 1] = 0; /* right */ + tree_to_face[4 * iTree + 2] = 3; /* bottow */ + tree_to_face[4 * iTree + 3] = 2; /* up */ + + if (i == 0) + tree_to_face[4 * iTree + 0] = 0; + if (i == 4) + tree_to_face[4 * iTree + 1] = 1; + if (j == 0) + tree_to_face[4 * iTree + 3] = 3; + if (j == 1 && i >= 2) + tree_to_face[4 * iTree + 2] = 2; + + /* printf("ttf %d | %d %d %d %d\n",iTree, */ + /* tree_to_face[4*iTree + 0], */ + /* tree_to_face[4*iTree + 1], */ + /* tree_to_face[4*iTree + 2], */ + /* tree_to_face[4*iTree + 3]); */ + } + } + + tree_to_face[4 * 10 + 0] = 0; /* left */ + tree_to_face[4 * 10 + 1] = 0; /* right */ + tree_to_face[4 * 10 + 2] = 2; /* bottow */ + tree_to_face[4 * 10 + 3] = 2; /* up */ + + tree_to_face[4 * 11 + 0] = 1; /* left */ + tree_to_face[4 * 11 + 1] = 1; /* right */ + tree_to_face[4 * 11 + 2] = 2; /* bottow */ + tree_to_face[4 * 11 + 3] = 2; /* up */ + } + + /* + * tree to corner + */ + { + int iTree = 0; + for (j = 0; j < 2; j++) + { + for (i = 0; i < 5; i++) + { + iTree = j * 5 + i; + tree_to_corner[4 * iTree + 0] = i - 1 + 4 * j; + tree_to_corner[4 * iTree + 1] = i + 4 * j; + tree_to_corner[4 * iTree + 2] = i - 1 + 4 * (j - 1); + tree_to_corner[4 * iTree + 3] = i + 4 * (j - 1); + + if (i == 0) + { + tree_to_corner[4 * iTree + 0] = -1; + tree_to_corner[4 * iTree + 2] = -1; + } + if (i == 4) + { + tree_to_corner[4 * iTree + 1] = -1; + tree_to_corner[4 * iTree + 3] = -1; + } + if (j == 0) + { + tree_to_corner[4 * iTree + 2] = -1; + tree_to_corner[4 * iTree + 3] = -1; + } + if (j == 1 && i == 2) + { + tree_to_corner[4 * iTree + 0] = 5; + tree_to_corner[4 * iTree + 1] = -1; + } + if (j == 1 && i > 2) + { + tree_to_corner[4 * iTree + 0] = -1; + tree_to_corner[4 * iTree + 1] = -1; + } + + /* printf("ttc %d | %d %d %d %d\n",iTree, */ + /* tree_to_corner[4*iTree + 0], */ + /* tree_to_corner[4*iTree + 1], */ + /* tree_to_corner[4*iTree + 2], */ + /* tree_to_corner[4*iTree + 3]); */ + } + } + + tree_to_corner[4 * 10 + 0] = -1; + tree_to_corner[4 * 10 + 1] = -1; + tree_to_corner[4 * 10 + 2] = -1; + tree_to_corner[4 * 10 + 3] = 4; + + tree_to_corner[4 * 11 + 0] = -1; + tree_to_corner[4 * 11 + 1] = -1; + tree_to_corner[4 * 11 + 2] = 4; + tree_to_corner[4 * 11 + 3] = 5; + } + + /* + * corner to tree + */ + { + int iCorner, iTree; + + for (iCorner = 0; iCorner < 6; iCorner++) + ctt_offset[iCorner] = iCorner * 4; + ctt_offset[6] = 5 * 4 + 3; + + for (j = 0; j < 1; j++) + { + for (i = 0; i < 4; i++) + { + iCorner = j * 4 + i; + iTree = j * 5 + i; + + corner_to_tree[4 * iCorner + 0] = iTree; + corner_to_tree[4 * iCorner + 1] = iTree + 1; + corner_to_tree[4 * iCorner + 2] = iTree + 5; + corner_to_tree[4 * iCorner + 3] = iTree + 6; + + /* printf("ctt %d | %d %d %d %d\n",iCorner, */ + /* corner_to_tree[4*iCorner + 0], */ + /* corner_to_tree[4*iCorner + 1], */ + /* corner_to_tree[4*iCorner + 2], */ + /* corner_to_tree[4*iCorner + 3]); */ + } + } + + corner_to_tree[4 * 4 + 0] = 5; + corner_to_tree[4 * 4 + 1] = 6; + corner_to_tree[4 * 4 + 2] = 10; + corner_to_tree[4 * 4 + 3] = 11; + + corner_to_tree[4 * 5 + 0] = 6; + corner_to_tree[4 * 5 + 1] = 7; + corner_to_tree[4 * 5 + 2] = 11; + } + + /* + * corner to corner + */ + { + int iCorner; + + for (j = 0; j < 1; j++) + { + for (i = 0; i < 4; i++) + { + iCorner = j * 4 + i; + + corner_to_corner[4 * iCorner + 0] = 1; + corner_to_corner[4 * iCorner + 1] = 0; + corner_to_corner[4 * iCorner + 2] = 3; + corner_to_corner[4 * iCorner + 3] = 2; + } + } + + corner_to_corner[4 * 4 + 0] = 1; + corner_to_corner[4 * 4 + 1] = 0; + corner_to_corner[4 * 4 + 2] = 3; + corner_to_corner[4 * 4 + 3] = 2; + + corner_to_corner[4 * 5 + 0] = 1; + corner_to_corner[4 * 5 + 1] = 0; + corner_to_corner[4 * 5 + 2] = 3; + } + + p4est_connectivity_t * conn = p4est_connectivity_new_copy(num_vertices, + num_trees, + num_corners, + vertices, + tree_to_vertex, + tree_to_tree, + tree_to_face, + tree_to_corner, + ctt_offset, + corner_to_tree, + corner_to_corner); + + + connectivity_print(conn); + + P4EST_GLOBAL_INFOF("Is connectivity ok : %d\n", p4est_connectivity_is_valid(conn)); + + P4EST_ASSERT(p4est_connectivity_is_valid(conn)); + + return conn; + +} /* p4est_connectivity_new_forward_facing_step_small */ + +p4est_connectivity_t * +p4est_connectivity_new_forward_facing_step(void) +{ + + const p4est_topidx_t num_vertices = 84; /* 16*5 + 4 */ + const p4est_topidx_t num_trees = 63; /* 15*4 + 3 */ + const p4est_topidx_t num_corners = 45; /* 14*3 + 3 */ + double vertices[84 * 3]; + p4est_topidx_t tree_to_vertex[63 * 4]; + p4est_topidx_t tree_to_tree[63 * 4]; + int8_t tree_to_face[63 * 4]; + p4est_topidx_t tree_to_corner[63 * 4]; + p4est_topidx_t ctt_offset[45 + 1]; + p4est_topidx_t corner_to_tree[179]; /* 44 corners*4trees + 1 corner*3tree */ + int8_t corner_to_corner[179]; /* 44 corners*4trees + 1 corner*3tree */ + + int i, j, iVertex = 0; + + double dx = 0.2; + double dy = 0.2; + + /* fill vertices the 80 first vertices */ + for (j = 0; j <= 4; j++) + { + for (i = 0; i <= 15; i++) + { + vertices[3 * iVertex + 0] = i * dx; /* */ + vertices[3 * iVertex + 1] = 1.0 - j * dy; /* */ + vertices[3 * iVertex + 2] = 0.0; /* */ + iVertex++; + } + } + + /* and the last 4 */ + vertices[3 * 80 + 0] = 0.0; + vertices[3 * 80 + 1] = 0.0; + vertices[3 * 80 + 2] = 0.0; + + vertices[3 * 81 + 0] = 0.2; + vertices[3 * 81 + 1] = 0.0; + vertices[3 * 81 + 2] = 0.0; + + vertices[3 * 82 + 0] = 0.4; + vertices[3 * 82 + 1] = 0.0; + vertices[3 * 82 + 2] = 0.0; + + vertices[3 * 83 + 0] = 0.6; + vertices[3 * 83 + 1] = 0.0; + vertices[3 * 83 + 2] = 0.0; + + /* + * tree to vertex + */ + { + int iTree = 0; + int v0 = 16; + int v1 = 17; + int v2 = 0; + int v3 = 1; + + for (j = 0; j < 4; j++) + { + for (i = 0; i < 15; i++) + { + iTree = j * 15 + i; + + tree_to_vertex[4 * iTree + 0] = v0 + i + j * 16; + tree_to_vertex[4 * iTree + 1] = v1 + i + j * 16; + tree_to_vertex[4 * iTree + 2] = v2 + i + j * 16; + tree_to_vertex[4 * iTree + 3] = v3 + i + j * 16; + + /* printf("ttv %d | %d %d %d %d\n",iTree, */ + /* tree_to_vertex[4*iTree + 0], */ + /* tree_to_vertex[4*iTree + 1], */ + /* tree_to_vertex[4*iTree + 2], */ + /* tree_to_vertex[4*iTree + 3]); */ + } + } + + tree_to_vertex[4 * 60 + 0] = 80; + tree_to_vertex[4 * 60 + 1] = 81; + tree_to_vertex[4 * 60 + 2] = 64; + tree_to_vertex[4 * 60 + 3] = 65; + + tree_to_vertex[4 * 61 + 0] = 81; + tree_to_vertex[4 * 61 + 1] = 82; + tree_to_vertex[4 * 61 + 2] = 65; + tree_to_vertex[4 * 61 + 3] = 66; + + tree_to_vertex[4 * 62 + 0] = 82; + tree_to_vertex[4 * 62 + 1] = 83; + tree_to_vertex[4 * 62 + 2] = 66; + tree_to_vertex[4 * 62 + 3] = 67; + } + + /* + * tree to tree + */ + { + int iTree = 0; + + for (j = 0; j < 4; j++) + { + for (i = 0; i < 15; i++) + { + iTree = j * 15 + i; + + tree_to_tree[4 * iTree + 0] = iTree - 1; /* left */ + tree_to_tree[4 * iTree + 1] = iTree + 1; /* right */ + tree_to_tree[4 * iTree + 2] = iTree + 15; /* below */ + tree_to_tree[4 * iTree + 3] = iTree - 15; /* above */ + + if (i == 0) + tree_to_tree[4 * iTree + 0] = iTree; + if (i == 14) + tree_to_tree[4 * iTree + 1] = iTree; + if (j == 0) + tree_to_tree[4 * iTree + 3] = iTree; + if (j == 3 && i >= 3) + tree_to_tree[4 * iTree + 2] = iTree; + + /* printf("ttt %d | %d %d %d %d\n",iTree, */ + /* tree_to_tree[4*iTree + 0], */ + /* tree_to_tree[4*iTree + 1], */ + /* tree_to_tree[4*iTree + 2], */ + /* tree_to_tree[4*iTree + 3]); */ + } + } + + tree_to_tree[4 * 60 + 0] = 60; + tree_to_tree[4 * 60 + 1] = 61; + tree_to_tree[4 * 60 + 2] = 60; + tree_to_tree[4 * 60 + 3] = 60 - 15; + + tree_to_tree[4 * 61 + 0] = 60; + tree_to_tree[4 * 61 + 1] = 62; + tree_to_tree[4 * 61 + 2] = 61; + tree_to_tree[4 * 61 + 3] = 61 - 15; + + tree_to_tree[4 * 62 + 0] = 61; + tree_to_tree[4 * 62 + 1] = 62; + tree_to_tree[4 * 62 + 2] = 62; + tree_to_tree[4 * 62 + 3] = 62 - 15; + } + + /* + * tree to face + */ + { + int iTree = 0; + + for (j = 0; j < 4; j++) + { + for (i = 0; i < 15; i++) + { + iTree = j * 15 + i; + + tree_to_face[4 * iTree + 0] = 1; /* left */ + tree_to_face[4 * iTree + 1] = 0; /* right */ + tree_to_face[4 * iTree + 2] = 3; /* bottow */ + tree_to_face[4 * iTree + 3] = 2; /* up */ + + if (i == 0) + tree_to_face[4 * iTree + 0] = 0; + if (i == 14) + tree_to_face[4 * iTree + 1] = 1; + if (j == 0) + tree_to_face[4 * iTree + 3] = 3; + if (j == 3 && i >= 3) + tree_to_face[4 * iTree + 2] = 2; + + /* printf("ttf %d | %d %d %d %d\n",iTree, */ + /* tree_to_face[4*iTree + 0], */ + /* tree_to_face[4*iTree + 1], */ + /* tree_to_face[4*iTree + 2], */ + /* tree_to_face[4*iTree + 3]); */ + } + } + + tree_to_face[4 * 60 + 0] = 0; /* left */ + tree_to_face[4 * 60 + 1] = 0; /* right */ + tree_to_face[4 * 60 + 2] = 2; /* bottow */ + tree_to_face[4 * 60 + 3] = 2; /* up */ + + tree_to_face[4 * 61 + 0] = 1; /* left */ + tree_to_face[4 * 61 + 1] = 0; /* right */ + tree_to_face[4 * 61 + 2] = 2; /* bottow */ + tree_to_face[4 * 61 + 3] = 2; /* up */ + tree_to_face[4 * 62 + 0] = 1; /* left */ + tree_to_face[4 * 62 + 1] = 1; /* right */ + tree_to_face[4 * 62 + 2] = 2; /* bottow */ + tree_to_face[4 * 62 + 3] = 2; /* up */ + } + + /* + * tree to corner + */ + { + int iTree = 0; + + for (j = 0; j < 4; j++) + { + for (i = 0; i < 15; i++) + { + iTree = j * 15 + i; + + tree_to_corner[4 * iTree + 0] = i - 1 + 14 * j; + tree_to_corner[4 * iTree + 1] = i + 14 * j; + tree_to_corner[4 * iTree + 2] = i - 1 + 14 * (j - 1); + tree_to_corner[4 * iTree + 3] = i + 14 * (j - 1); + + if (i == 0) + { + tree_to_corner[4 * iTree + 0] = -1; + tree_to_corner[4 * iTree + 2] = -1; + } + if (i == 14) + { + tree_to_corner[4 * iTree + 1] = -1; + tree_to_corner[4 * iTree + 3] = -1; + } + if (j == 0) + { + tree_to_corner[4 * iTree + 2] = -1; + tree_to_corner[4 * iTree + 3] = -1; + } + if (j == 3 && i == 3) + { + tree_to_corner[4 * iTree + 0] = 44; + tree_to_corner[4 * iTree + 1] = -1; + } + if (j == 3 && i > 3) + { + tree_to_corner[4 * iTree + 0] = -1; + tree_to_corner[4 * iTree + 1] = -1; + } + + /* printf("ttc %d | %d %d %d %d\n",iTree, */ + /* tree_to_corner[4*iTree + 0], */ + /* tree_to_corner[4*iTree + 1], */ + /* tree_to_corner[4*iTree + 2], */ + /* tree_to_corner[4*iTree + 3]); */ + } + } + + tree_to_corner[4 * 60 + 0] = -1; + tree_to_corner[4 * 60 + 1] = -1; + tree_to_corner[4 * 60 + 2] = -1; + tree_to_corner[4 * 60 + 3] = 42; + + tree_to_corner[4 * 61 + 0] = -1; + tree_to_corner[4 * 61 + 1] = -1; + tree_to_corner[4 * 61 + 2] = 42; + tree_to_corner[4 * 61 + 3] = 43; + + tree_to_corner[4 * 62 + 0] = -1; + tree_to_corner[4 * 62 + 1] = -1; + tree_to_corner[4 * 62 + 2] = 43; + tree_to_corner[4 * 62 + 3] = 44; + } + + /* + * corner to tree + */ + { + int iCorner, iTree; + + for (iCorner = 0; iCorner < 45; iCorner++) + ctt_offset[iCorner] = iCorner * 4; + ctt_offset[45] = 44 * 4 + 3; + + for (j = 0; j < 3; j++) + { + for (i = 0; i < 14; i++) + { + iCorner = j * 14 + i; + iTree = j * 15 + i; + + corner_to_tree[4 * iCorner + 0] = iTree; + corner_to_tree[4 * iCorner + 1] = iTree + 1; + corner_to_tree[4 * iCorner + 2] = iTree + 15; + corner_to_tree[4 * iCorner + 3] = iTree + 16; + + /* printf("ctt %d | %d %d %d %d\n",iCorner, */ + /* corner_to_tree[4*iCorner + 0], */ + /* corner_to_tree[4*iCorner + 1], */ + /* corner_to_tree[4*iCorner + 2], */ + /* corner_to_tree[4*iCorner + 3]); */ + } + } + + corner_to_tree[4 * 42 + 0] = 45; + corner_to_tree[4 * 42 + 1] = 46; + corner_to_tree[4 * 42 + 2] = 60; + corner_to_tree[4 * 42 + 3] = 61; + + corner_to_tree[4 * 43 + 0] = 46; + corner_to_tree[4 * 43 + 1] = 47; + corner_to_tree[4 * 43 + 2] = 61; + corner_to_tree[4 * 43 + 3] = 62; + + corner_to_tree[4 * 44 + 0] = 47; + corner_to_tree[4 * 44 + 1] = 48; + corner_to_tree[4 * 44 + 2] = 62; + } + + /* + * corner to corner + */ + { + int iCorner; + + for (j = 0; j < 3; j++) + { + for (i = 0; i < 14; i++) + { + iCorner = j * 14 + i; + + corner_to_corner[4 * iCorner + 0] = 1; + corner_to_corner[4 * iCorner + 1] = 0; + corner_to_corner[4 * iCorner + 2] = 3; + corner_to_corner[4 * iCorner + 3] = 2; + } + } + + corner_to_corner[4 * 42 + 0] = 1; + corner_to_corner[4 * 42 + 1] = 0; + corner_to_corner[4 * 42 + 2] = 3; + corner_to_corner[4 * 42 + 3] = 2; + + corner_to_corner[4 * 43 + 0] = 1; + corner_to_corner[4 * 43 + 1] = 0; + corner_to_corner[4 * 43 + 2] = 3; + corner_to_corner[4 * 43 + 3] = 2; + + corner_to_corner[4 * 44 + 0] = 1; + corner_to_corner[4 * 44 + 1] = 0; + corner_to_corner[4 * 44 + 2] = 3; + } + + p4est_connectivity_t * conn = p4est_connectivity_new_copy(num_vertices, + num_trees, + num_corners, + vertices, + tree_to_vertex, + tree_to_tree, + tree_to_face, + tree_to_corner, + ctt_offset, + corner_to_tree, + corner_to_corner); + + + connectivity_print(conn); + + P4EST_GLOBAL_INFOF("Is connectivity ok : %d\n", p4est_connectivity_is_valid(conn)); + + P4EST_ASSERT(p4est_connectivity_is_valid(conn)); + + return conn; + +} /* p4est_connectivity_new_forward_facing_step */ + +p4est_connectivity_t * +p4est_connectivity_new_backward_facing_step(void) +{ + + const p4est_topidx_t num_vertices = 8; + const p4est_topidx_t num_trees = 3; + const p4est_topidx_t num_corners = 1; + + double dx = 1.3; + double dy = 1.3; + + /* vertices */ + const double vertices[8 * 3] = { + /* num_vertices*3 */ + 0 * dx, 2 * dy, 0, /* vertex 0 */ + 1 * dx, 2 * dy, 0, /* vertex 1 */ + 2 * dx, 2 * dy, 0, /* vertex 2 */ + 0 * dx, 1 * dy, 0, /* vertex 3 */ + 1 * dx, 1 * dy, 0, /* vertex 4 */ + 2 * dx, 1 * dy, 0, /* vertex 5 */ + 1 * dx, 0 * dy, 0, /* vertex 6 */ + 2 * dx, 0 * dy, 0, /* vertex 7 */ + }; + + const p4est_topidx_t tree_to_vertex[3 * 4] = { + 3, 4, 0, 1, /* tree 0 */ + 4, 5, 1, 2, /* tree 1 */ + 6, 7, 4, 5, /* tree 2 */ + }; + + const p4est_topidx_t tree_to_tree[3 * 4] = { + 0, 1, 0, 0, /* tree 0 */ + 0, 1, 2, 1, /* tree 1 */ + 2, 2, 2, 1, /* tree 2 */ + }; + + const int8_t tree_to_face[3 * 4] = { + 0, 0, 2, 3, /* tree 0 */ + 1, 1, 3, 3, /* tree 1 */ + 0, 1, 2, 2, /* tree 2 */ + }; + + const p4est_topidx_t tree_to_corner[3 * 4] = { + -1, 0, -1, -1, /* tree 0 */ + 0, -1, -1, -1, /* tree 1 */ + -1, -1, 0, -1, /* tree 2 */ + }; + + const p4est_topidx_t ctt_offset[1 + 1] = { + /* num_corners=1 */ + 0, + 3, + }; + const p4est_topidx_t corner_to_tree[1 * 3] = { + /* num_corners=1 */ + 0, + 1, + 2, /* corner 0 (i.e. vertex 4) */ + }; + + const int8_t corner_to_corner[3] = { + 1, 0, 2 /* corner 0 (i.e. vertex 4) */ + }; + + p4est_connectivity_t * conn = p4est_connectivity_new_copy(num_vertices, + num_trees, + num_corners, + vertices, + tree_to_vertex, + tree_to_tree, + tree_to_face, + tree_to_corner, + ctt_offset, + corner_to_tree, + corner_to_corner); + + connectivity_print(conn); + + P4EST_GLOBAL_INFOF("Is connectivity ok : %d\n", p4est_connectivity_is_valid(conn)); + + P4EST_ASSERT(p4est_connectivity_is_valid(conn)); + + return conn; + +} /* p4est_connectivity_new_backward_facing_step */ + + +/* TODO: add tree_to_edge or however its called */ +void +connectivity_print(p4est_connectivity_t * c) +{ + SC_GLOBAL_INFOF("num_vertices = %d\n", c->num_vertices); + SC_GLOBAL_INFOF("num_trees = %d\n", c->num_trees); + SC_GLOBAL_INFOF("num_corners = %d\n", c->num_corners); + + SC_GLOBAL_INFO("vertices:\n"); + for (int i = 0; i < c->num_vertices && c->vertices; ++i) + { + SC_GLOBAL_INFOF("v=%d | %g %g %g\n", + i, + c->vertices[3 * i + 0], + c->vertices[3 * i + 1], + c->vertices[3 * i + 2]); + } + + SC_GLOBAL_INFO("tree_to_vertex:\n"); + for (int i = 0; i < c->num_trees && c->tree_to_vertex; ++i) + { + SC_GLOBAL_INFOF("tree=%d | %d %d %d %d\n", + i, + c->tree_to_vertex[4 * i + 0], + c->tree_to_vertex[4 * i + 1], + c->tree_to_vertex[4 * i + 2], + c->tree_to_vertex[4 * i + 3]); + } + + SC_GLOBAL_INFO("tree_to_tree:\n"); + for (int i = 0; i < c->num_trees && c->tree_to_tree; ++i) + { + SC_GLOBAL_INFOF("tree=%d | %d %d %d %d\n", + i, + c->tree_to_tree[4 * i + 0], + c->tree_to_tree[4 * i + 1], + c->tree_to_tree[4 * i + 2], + c->tree_to_tree[4 * i + 3]); + } + + SC_GLOBAL_INFO("tree_to_face:\n"); + for (int i = 0; i < c->num_trees && c->tree_to_face; ++i) + { + SC_GLOBAL_INFOF("tree=%d | %d %d %d %d\n", + i, + c->tree_to_face[4 * i + 0], + c->tree_to_face[4 * i + 1], + c->tree_to_face[4 * i + 2], + c->tree_to_face[4 * i + 3]); + } + + SC_GLOBAL_INFO("tree_to_corner:\n"); + for (int i = 0; i < c->num_trees && c->tree_to_corner; ++i) + { + SC_GLOBAL_INFOF("tree=%d | %d %d %d %d\n", + i, + c->tree_to_corner[4 * i + 0], + c->tree_to_corner[4 * i + 1], + c->tree_to_corner[4 * i + 2], + c->tree_to_corner[4 * i + 3]); + } + + SC_GLOBAL_INFO("ctt_offset:\n[libsc] "); + for (int i = 0; (i < (c->num_corners + 1)) && c->ctt_offset; ++i) + { + printf("%d ", c->ctt_offset[i]); + } + printf("\n"); + + SC_GLOBAL_INFO("corner_to_tree:\n"); + for (int i = 0; i < c->num_corners && c->corner_to_tree; ++i) + { + printf("[libsc] "); + + printf("corner=%d | ", i); + for (int j = c->ctt_offset[i]; j < c->ctt_offset[i + 1]; ++j) + { + printf("%d ", c->corner_to_tree[j]); + } + printf("\n"); + } + + SC_GLOBAL_INFO("corner_to_corner:\n"); + for (int i = 0; i < c->num_corners && c->corner_to_corner; ++i) + { + printf("[libsc] "); + + printf("corner=%d | ", i); + for (int j = c->ctt_offset[i]; j < c->ctt_offset[i + 1]; ++j) + { + printf("%d ", c->corner_to_corner[j]); + } + printf("\n"); + } +} + +void +connectivity_print(p8est_connectivity_t * c) +{ + SC_GLOBAL_INFOF("num_vertices = %d\n", c->num_vertices); + SC_GLOBAL_INFOF("num_trees = %d\n", c->num_trees); + SC_GLOBAL_INFOF("num_corners = %d\n", c->num_corners); + + /* TODO */ +} + +/* + * DONE: + * modify interface to pass cfg (so that connectivity like ring can + * initialize the number of trees along radial and orthoradial direction + * by using values read from the input configuration file. + */ +p4est_connectivity_t * +connectivity_2d_new_byname(const char * name, const kalypsso::ConfigMap & cfg) +{ + + // first look at hard coded connectivity in p4est + p4est_connectivity_t * c = p4est_connectivity_new_byname(name); + + if (c != nullptr) + { + return c; + } + + // else, look into for our own connectivities + if (strcmp(name, "shock_tube") == 0) + { + + return p4est_connectivity_new_shock_tube(); + } + else if (strcmp(name, "two") == 0) + { + + int per_x = cfg.getInteger("p4est_connectivity", "periodic_x", CONNECTIVITY_PERIODIC_FALSE); + connectivity_periodic_t periodic_x = (per_x == CONNECTIVITY_PERIODIC_TRUE) + ? CONNECTIVITY_PERIODIC_TRUE + : CONNECTIVITY_PERIODIC_FALSE; + + int per_y = cfg.getInteger("p4est_connectivity", "periodic_y", CONNECTIVITY_PERIODIC_TRUE); + connectivity_periodic_t periodic_y = (per_y == CONNECTIVITY_PERIODIC_TRUE) + ? CONNECTIVITY_PERIODIC_TRUE + : CONNECTIVITY_PERIODIC_FALSE; + + // int per_z = cfg.getInteger("p4est_connectivity", "periodic_z", CONNECTIVITY_PERIODIC_FALSE); + // [[maybe_unused]] connectivity_periodic_t periodic_z = (per_z == CONNECTIVITY_PERIODIC_TRUE) + // ? CONNECTIVITY_PERIODIC_TRUE + // : CONNECTIVITY_PERIODIC_FALSE; + + return p4est_connectivity_new_two(periodic_x, periodic_y); + } + else if (strcmp(name, "two_simple") == 0) + { + + return p4est_connectivity_new_two_simple(); + } + else if (strcmp(name, "brick") == 0) + { + + int nbrick_x = cfg.getInteger("p4est_connectivity", "nbrick_x", 2); + int nbrick_y = cfg.getInteger("p4est_connectivity", "nbrick_y", 2); + //[[maybe_unused]] int nbrick_z = cfg.getInteger("p4est_connectivity", "nbrick_z", 2); + + int per_x = cfg.getInteger("p4est_connectivity", "periodic_x", CONNECTIVITY_PERIODIC_FALSE); + connectivity_periodic_t periodic_x = (per_x == CONNECTIVITY_PERIODIC_TRUE) + ? CONNECTIVITY_PERIODIC_TRUE + : CONNECTIVITY_PERIODIC_FALSE; + + int per_y = cfg.getInteger("p4est_connectivity", "periodic_y", CONNECTIVITY_PERIODIC_TRUE); + connectivity_periodic_t periodic_y = (per_y == CONNECTIVITY_PERIODIC_TRUE) + ? CONNECTIVITY_PERIODIC_TRUE + : CONNECTIVITY_PERIODIC_FALSE; + + // int per_z = cfg.getInteger("p4est_connectivity", "periodic_z", CONNECTIVITY_PERIODIC_FALSE); + // [[maybe_unused]] connectivity_periodic_t periodic_z = (per_z == CONNECTIVITY_PERIODIC_TRUE) + // ? CONNECTIVITY_PERIODIC_TRUE + // : CONNECTIVITY_PERIODIC_FALSE; + + return p4est_connectivity_new_brick(nbrick_x, nbrick_y, periodic_x, periodic_y); + } + else if (strcmp(name, "tetris") == 0) + { + return p4est_connectivity_new_tetris(); + } + else if (strcmp(name, "forward_facing_step_small") == 0) + { + return p4est_connectivity_new_forward_facing_step(); + } + else if (strcmp(name, "forward_facing_step") == 0) + { + return p4est_connectivity_new_forward_facing_step(); + } + else if (strcmp(name, "backward_facing_step") == 0) + { + return p4est_connectivity_new_backward_facing_step(); + } + + /* ring and shell2d only available in 2D ! */ + else if (strcmp(name, "ring") == 0) + { + + int num_trees_radial, num_trees_orthoradial; + double rMin, rMax; + num_trees_radial = cfg.getInteger("p4est_connectivity", "ring.num_trees_radial", 3); + num_trees_orthoradial = cfg.getInteger("p4est_connectivity", "ring.num_trees_orthoradial", 10); + rMin = cfg.getDouble("p4est_connectivity", "ring.rMin", 0.5); + rMax = cfg.getDouble("p4est_connectivity", "ring.rMax", 4.0); + return p4est_connectivity_new_ring(num_trees_radial, num_trees_orthoradial, rMin, rMax); + } + else if (strcmp(name, "disk") == 0) + { +#if defined(KALYPSSO_CORE_USE_OLD_P4EST_API) + return p4est_connectivity_new_disk(); +#else + int per_x = cfg.getInteger("p4est_connectivity", "periodic_x", CONNECTIVITY_PERIODIC_FALSE); + connectivity_periodic_t periodic_x = (per_x == CONNECTIVITY_PERIODIC_TRUE) + ? CONNECTIVITY_PERIODIC_TRUE + : CONNECTIVITY_PERIODIC_FALSE; + + int per_y = cfg.getInteger("p4est_connectivity", "periodic_y", CONNECTIVITY_PERIODIC_TRUE); + connectivity_periodic_t periodic_y = (per_y == CONNECTIVITY_PERIODIC_TRUE) + ? CONNECTIVITY_PERIODIC_TRUE + : CONNECTIVITY_PERIODIC_FALSE; + return p4est_connectivity_new_disk(periodic_x, periodic_y); +#endif + } + else if (strcmp(name, "disk2d") == 0) + { + return p4est_connectivity_new_disk2d(); + } + else if (strcmp(name, "shell2d") == 0) + { + return p4est_connectivity_new_shell2d(); + } + else if (strcmp(name, "icosahedron") == 0) + { + return p4est_connectivity_new_icosahedron(); + } + + /* + * cylindrical connectivity has only been tested with ramses solver. + */ + + /* cylindrical ramses */ + else if (strcmp(name, "cylindrical") == 0) + { + + int nbrick_r = 1; + int nbrick_theta = cfg.getInteger("geometry.cylindrical", "nbTrees_theta", 8); + //[[maybe_unused]] int nbrick_z = cfg.getInteger("geometry.cylindrical", "nbTrees_z", 1); + int periodic_r = 0; + int periodic_theta = 1; /* periodic in theta */ + //[[maybe_unused]] int periodic_z = cfg.getInteger("geometry.cylindrical", "zPeriodic", 0); + + return p4est_connectivity_new_brick(nbrick_r, nbrick_theta, periodic_r, periodic_theta); + } + + // If still not, look for a mesh file name.inp and let p4est do the + // connectivity based on this. p4est_connectivity_read_inp will return + // NULL if the given name is not a filename either. + return p4est_connectivity_read_inp(name); + +} // connectivity_2d_new_byname + +p8est_connectivity_t * +connectivity_3d_new_byname(const char * name, const kalypsso::ConfigMap & cfg) +{ + + // first look at hard coded connectivity in p4est + p8est_connectivity_t * c = p8est_connectivity_new_byname(name); + + if (c != nullptr) + { + return c; + } + + // else, look into for our own connectivities + if (strcmp(name, "shock_tube") == 0) + { + + return p8est_connectivity_new_shock_tube(); + } + else if (strcmp(name, "two") == 0) + { + + int per_x = cfg.getInteger("p4est_connectivity", "periodic_x", CONNECTIVITY_PERIODIC_FALSE); + connectivity_periodic_t periodic_x = (per_x == CONNECTIVITY_PERIODIC_TRUE) + ? CONNECTIVITY_PERIODIC_TRUE + : CONNECTIVITY_PERIODIC_FALSE; + + int per_y = cfg.getInteger("p4est_connectivity", "periodic_y", CONNECTIVITY_PERIODIC_TRUE); + connectivity_periodic_t periodic_y = (per_y == CONNECTIVITY_PERIODIC_TRUE) + ? CONNECTIVITY_PERIODIC_TRUE + : CONNECTIVITY_PERIODIC_FALSE; + + int per_z = cfg.getInteger("p4est_connectivity", "periodic_z", CONNECTIVITY_PERIODIC_FALSE); + connectivity_periodic_t periodic_z = (per_z == CONNECTIVITY_PERIODIC_TRUE) + ? CONNECTIVITY_PERIODIC_TRUE + : CONNECTIVITY_PERIODIC_FALSE; + + return p8est_connectivity_new_two(periodic_x, periodic_y, periodic_z); + } + else if (strcmp(name, "two_simple") == 0) + { + + return p8est_connectivity_new_two_simple(); + } + else if (strcmp(name, "brick") == 0) + { + + int nbrick_x = cfg.getInteger("p4est_connectivity", "nbrick_x", 2); + int nbrick_y = cfg.getInteger("p4est_connectivity", "nbrick_y", 2); + int nbrick_z = cfg.getInteger("p4est_connectivity", "nbrick_z", 2); + + int per_x = cfg.getInteger("p4est_connectivity", "periodic_x", CONNECTIVITY_PERIODIC_FALSE); + connectivity_periodic_t periodic_x = (per_x == CONNECTIVITY_PERIODIC_TRUE) + ? CONNECTIVITY_PERIODIC_TRUE + : CONNECTIVITY_PERIODIC_FALSE; + + int per_y = cfg.getInteger("p4est_connectivity", "periodic_y", CONNECTIVITY_PERIODIC_TRUE); + connectivity_periodic_t periodic_y = (per_y == CONNECTIVITY_PERIODIC_TRUE) + ? CONNECTIVITY_PERIODIC_TRUE + : CONNECTIVITY_PERIODIC_FALSE; + + int per_z = cfg.getInteger("p4est_connectivity", "periodic_z", CONNECTIVITY_PERIODIC_FALSE); + connectivity_periodic_t periodic_z = (per_z == CONNECTIVITY_PERIODIC_TRUE) + ? CONNECTIVITY_PERIODIC_TRUE + : CONNECTIVITY_PERIODIC_FALSE; + + return p8est_connectivity_new_brick( + nbrick_x, nbrick_y, nbrick_z, periodic_x, periodic_y, periodic_z); + } + + /* + * cylindrical connectivity has only been tested with ramses solver. + */ + + /* cylindrical ramses */ + else if (strcmp(name, "cylindrical") == 0) + { + + int nbrick_r = 1; + int nbrick_theta = cfg.getInteger("geometry.cylindrical", "nbTrees_theta", 8); + int nbrick_z = cfg.getInteger("geometry.cylindrical", "nbTrees_z", 1); + int periodic_r = 0; + int periodic_theta = 1; /* periodic in theta */ + int periodic_z = cfg.getInteger("geometry.cylindrical", "zPeriodic", 0); + + return p8est_connectivity_new_brick( + nbrick_r, nbrick_theta, nbrick_z, periodic_r, periodic_theta, periodic_z); + } + + // If still not, look for a mesh file name.inp and let p4est do the + // connectivity based on this. p4est_connectivity_read_inp will return + // NULL if the given name is not a filename either. + return p8est_connectivity_read_inp(name); + +} // connectivity_3d_new_byname diff --git a/src/kalypsso/utils/p4est/connectivity.h b/src/kalypsso/utils/p4est/connectivity.h new file mode 100644 index 0000000..41bd0b3 --- /dev/null +++ b/src/kalypsso/utils/p4est/connectivity.h @@ -0,0 +1,208 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * @file connectivity.h + * @date 17 octobre 2020 + * @author pkestene + * @note + * + * This file is part of the Kalypsso software project. + * + */ +#ifndef KALYPSSO_UTILS_P4EST_CONNECTIVITY_H_ +#define KALYPSSO_UTILS_P4EST_CONNECTIVITY_H_ + +// #include "p4est_wrapper.h" + +#include +#include + +// class ConfigMap; +#include + +typedef enum +{ + CONNECTIVITY_PERIODIC_FALSE = 0, + CONNECTIVITY_PERIODIC_TRUE = 1 +} connectivity_periodic_t; + + +/** + * \brief Connectivity with two side-by-side trees along direction x. + * + * The trees can be periodic in any direction. + */ +p4est_connectivity_t * +p4est_connectivity_new_two(connectivity_periodic_t is_periodic_x, + connectivity_periodic_t is_periodic_y); + + +/** + * \brief Connectivity with two side-by-side trees along direction x. + * + * The trees can be periodic in any direction. + */ +p8est_connectivity_t * +p8est_connectivity_new_two(connectivity_periodic_t is_periodic_x, + connectivity_periodic_t is_periodic_y, + connectivity_periodic_t is_periodic_z); + + +/** + * \brief 2D connectivity with two side-by-side trees. + */ +p4est_connectivity_t * +p4est_connectivity_new_two_simple(void); + +/** + * \brief 3D connectivity with two side-by-side trees. + */ +p8est_connectivity_t * +p8est_connectivity_new_two_simple(void); + +/** + * \brief 2D connectivity with 10 side-by-side trees. + * + * The trees are periodic in the y direction. + */ +p4est_connectivity_t * +p4est_connectivity_new_shock_tube(void); + +/** + * \brief 3D connectivity with 10 side-by-side trees. + * + * The trees are periodic in the y direction. + */ +p8est_connectivity_t * +p8est_connectivity_new_shock_tube(void); + +/** + * \brief 2D connectivity with five trees in a tetris-like shape. + * + * _ _ _ _ + * |_|_|_|_| + * |_| + * + * Tree numbering: + * 0 1 2 3 + * 4 + * + * Vertex numbering: + * 0 1 2 3 4 + * 5 6 7 8 9 + * 10 11 + * + * Note: the 3D equivalent is possible, just use the abaqus input file "tetris3d.inp" + * + */ +p4est_connectivity_t * +p4est_connectivity_new_tetris(void); + +/** + * \brief 2D connectivity with ring shape (cylindrical geometry). + * + * No geometry required. + * + * \param[in] num_trees_radial number of trees in the radial direction + * \param[in] num_trees_orthoradial number of trees in the orthoradial direction + * \param[in] rMin radius of the inner border + * \param[in] rMax radius of the outer border + */ +p4est_connectivity_t * +p4est_connectivity_new_ring(int num_trees_radial, + int num_trees_orthoradial, + double rMin, + double rMax); + +/** + * \brief 2D connectivity for forward facing step test (small). + * + * + * 12 trees. + * 21 vertices. + * 6 corners. + * _ _ _ _ _ + *|_|_|_|_|_| + *|_|_|_|_|_| + *|_|_| + * + */ +p4est_connectivity_t * +p4est_connectivity_new_forward_facing_step_small(void); + +/** + * \brief 2D connectivity for forward facing step test. + * + * + * 15 trees along x direction and 4 rows + 3 extra trees (below left). + * That is 63 trees in total. + * _ _ _ _ _ _ _ _ _ _ + *|_|_|_|_|_|_|_|_|_|_| + *|_|_|_|_|_|_|_|_|_|_| + *|_|_|_|_|_|_|_|_|_|_| + *|_|_|_|_|_|_|_|_|_|_| + *|_|_|_| + * + */ +p4est_connectivity_t * +p4est_connectivity_new_forward_facing_step(void); + +/** + * \brief 2D connectivity for backward facing step test. + * + * + * 3 trees + * _ _ + *|_|_| + * |_| + * + */ +p4est_connectivity_t * +p4est_connectivity_new_backward_facing_step(void); + +/** + * \brief Print all the information in the 2d connectivity struct. + */ +void +connectivity_print(p4est_connectivity_t * c); + +/** + * \brief Print all the information in the 3d connectivity struct. + */ +void +connectivity_print(p8est_connectivity_t * c); + +/** + * \brief Get a connectivity by name. + * + * Allowed p4est values for 2D: + * brick, corner, cubed, disk, disk2d, icosahedron, moebius, periodic, pillow, rotwrap, shell2d, + * star, unit + * + * Allowed p4est values for 3D: + * brick, periodic, rotcubes, rotwrap, shell, sphere, twocubes, twowrap, unit + * + * Besides the p4est names found in p4est_connectivity.h, we have: + * two connectivity_new_two + * two_simple connectivity_new_two_simple + * shock_tube connectivity_new_shock_tube + * tetris, backward_facing_step, ring + * + * This function first looks for p4est provided connectivities, if the name + * is not found there, it falls back to our own and as a last resort, tries + * to read the connectivity with the filename "name" using + * p4est_connectivity_read_inp. + * + * \param[in] name A connectivity name. + * \param[in] cfg A config reader. + * \return A fully allocated connectivity. + */ +p4est_connectivity_t * +connectivity_2d_new_byname(const char * name, const kalypsso::ConfigMap & cfg); + +p8est_connectivity_t * +connectivity_3d_new_byname(const char * name, const kalypsso::ConfigMap & cfg); + +#endif // KALYPSSO_UTILS_P4EST_CONNECTIVITY_H_ diff --git a/src/kalypsso/utils/p4est/geometry.cpp b/src/kalypsso/utils/p4est/geometry.cpp new file mode 100644 index 0000000..50162bf --- /dev/null +++ b/src/kalypsso/utils/p4est/geometry.cpp @@ -0,0 +1,544 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * @file geometry.cpp + * @date 17 octobre 2020 + * @author pkestene + * @note + * + * This file is part of the Kalypsso software project. + * + */ +#include "geometry.h" + +#include + +/* + * basic types - 2d and 3d. + */ +enum p4est_geometry_builtin_type_t +{ + P4EST_GEOMETRY_BUILTIN_MAGIC = + 0x65F2F8DE, /* should be different from P8EST_GEOMETRY_BUILTIN_MAGIC ? */ + P4EST_GEOMETRY_BUILTIN_CYLINDRICAL_COMPUTE, + P4EST_GEOMETRY_BUILTIN_CYLINDRICAL_IO, +}; + + +/* CYLINDRICAL_COMPUTE 2D and 3D */ +struct p4est_geometry_builtin_cylindrical_compute_t +{ + p4est_geometry_builtin_type_t type; + double R0, R1; + int nbTrees_theta; + int nbTrees_z; +}; + + +/* CYLINDRICAL_IO 2D and 3D */ +struct p4est_geometry_builtin_cylindrical_io_t +{ + p4est_geometry_builtin_type_t type; + double R0, R1; + int nbTrees_theta; + int nbTrees_z; +}; + + +/* + * This is one the uglyest thing that can be done in C, + * for now, just keep it as it is done in p4est... + */ +struct p4est_geometry_builtin_t +{ + /** The geom member needs to come first; we cast to p4est_geometry_t * */ + p4est_geometry_t geom; + union + { + p4est_geometry_builtin_type_t type; + p4est_geometry_builtin_cylindrical_compute_t cylindrical_compute; + p4est_geometry_builtin_cylindrical_io_t cylindrical_io; + } p; +}; + +struct p8est_geometry_builtin_t +{ + /** The geom member needs to come first; we cast to p8est_geometry_t * */ + p8est_geometry_t geom; + union + { + p4est_geometry_builtin_type_t type; + p4est_geometry_builtin_cylindrical_compute_t cylindrical_compute; + p4est_geometry_builtin_cylindrical_io_t cylindrical_io; + } p; +}; + +/**************************************************** + * CYLINDRICAL_COMPUTE + ****************************************************/ +/** + * geometric coordinate transformation for cylindrical_compute geometry. + * + * Define the geometric transformation from logical space (where AMR + * is performed) to the physical space. + * + * \param[in] p4est the forest + * \param[in] which_tree tree id inside forest + * \param[in] rst coordinates in AMR space : [0,1]^3 + * \param[out] xyz cartesian coordinates in physical space after geometry + * + * Note abc[3] contains cartesian coordinates in logical + * vertex space (before geometry). + */ +static void +p4est_geometry_cylindrical_compute_X(p4est_geometry_t * geom, + p4est_topidx_t which_tree, + const double rst[3], + double xyz[3]) +{ + const p4est_geometry_builtin_cylindrical_compute_t * cylindrical_compute = + &(reinterpret_cast(geom))->p.cylindrical_compute; + // double x, y, z; + double R0 = cylindrical_compute->R0; + double R1 = cylindrical_compute->R1; + + int nbTrees_theta = cylindrical_compute->nbTrees_theta; + [[maybe_unused]] int nbTrees_z = cylindrical_compute->nbTrees_z; + double abc[3]; + + /* transform from the reference cube [0,1]^3 into logical vertex space + using bi/trilinear transformation */ + p4est_geometry_connectivity_X(geom, which_tree, rst, abc); + + /* + * assert that input points are in the expected range + * Note: maybe we should remove these assert, this would allow + * ghost quadrant at external boundary to call this routine ? + */ + P4EST_ASSERT(cylindrical_compute->type == P4EST_GEOMETRY_BUILTIN_CYLINDRICAL_COMPUTE); + P4EST_ASSERT(0 <= which_tree && which_tree < nbTrees_theta * nbTrees_z); + P4EST_ASSERT(abc[0] < 1.0 + SC_1000_EPS && abc[0] > 0.0 - SC_1000_EPS); + P4EST_ASSERT(abc[1] < nbTrees_theta + SC_1000_EPS && abc[1] > 0.0 - SC_1000_EPS); + + // abc[0] is in range [0..1] + // radius is in range [R0..R1] + xyz[0] = R0 + abc[0] * (R1 - R0); + + // abc[1] is in range [0..nbTrees_theta] + // theta is in range [0, 2 M_PI] + xyz[1] = abc[1] / nbTrees_theta * 2 * M_PI; + + // abc[2] is in range [0..nbTrees_z] + // just make it zero-centered + xyz[2] = 0.0; + +} // p4est_geometry_cylindrical_compute_X + +static void +p8est_geometry_cylindrical_compute_X(p8est_geometry_t * geom, + p4est_topidx_t which_tree, + const double rst[3], + double xyz[3]) +{ + const p4est_geometry_builtin_cylindrical_compute_t * cylindrical_compute = + &(reinterpret_cast(geom))->p.cylindrical_compute; + // double x, y, z; + double R0 = cylindrical_compute->R0; + double R1 = cylindrical_compute->R1; + + int nbTrees_theta = cylindrical_compute->nbTrees_theta; + [[maybe_unused]] int nbTrees_z = cylindrical_compute->nbTrees_z; + double abc[3]; + + /* transform from the reference cube [0,1]^3 into logical vertex space + using bi/trilinear transformation */ + p8est_geometry_connectivity_X(geom, which_tree, rst, abc); + + /* + * assert that input points are in the expected range + * Note: maybe we should remove these assert, this would allow + * ghost quadrant at external boundary to call this routine ? + */ + P4EST_ASSERT(cylindrical_compute->type == P4EST_GEOMETRY_BUILTIN_CYLINDRICAL_COMPUTE); + P4EST_ASSERT(0 <= which_tree && which_tree < nbTrees_theta * nbTrees_z); + P4EST_ASSERT(abc[0] < 1.0 + SC_1000_EPS && abc[0] > 0.0 - SC_1000_EPS); + P4EST_ASSERT(abc[1] < nbTrees_theta + SC_1000_EPS && abc[1] > 0.0 - SC_1000_EPS); + + P4EST_ASSERT(abc[2] < nbTrees_z + SC_1000_EPS && abc[2] > 0.0 - SC_1000_EPS); + + + // abc[0] is in range [0..1] + // radius is in range [R0..R1] + xyz[0] = R0 + abc[0] * (R1 - R0); + + // abc[1] is in range [0..nbTrees_theta] + // theta is in range [0, 2 M_PI] + xyz[1] = abc[1] / nbTrees_theta * 2 * M_PI; + + // abc[2] is in range [0..nbTrees_z] + // just make it zero-centered + + xyz[2] = abc[2] - 0.5 * nbTrees_z; + +} // p8est_geometry_cylindrical_compute_X + +p4est_geometry_t * +p4est_geometry_new_cylindrical_compute(p4est_connectivity_t * conn, + double R0, + double R1, + int nbTrees_theta, + int nbTrees_z) +{ + + p4est_geometry_builtin_t * builtin; + p4est_geometry_builtin_cylindrical_compute_t * cylindrical_compute; + + builtin = P4EST_ALLOC_ZERO(p4est_geometry_builtin_t, 1); + + cylindrical_compute = &builtin->p.cylindrical_compute; + cylindrical_compute->type = P4EST_GEOMETRY_BUILTIN_CYLINDRICAL_COMPUTE; + cylindrical_compute->R0 = R0; + cylindrical_compute->R1 = R1; + cylindrical_compute->nbTrees_theta = nbTrees_theta; + cylindrical_compute->nbTrees_z = nbTrees_z; + + builtin->geom.name = "p4est_cylindrical_compute"; + builtin->geom.user = conn; + builtin->geom.X = p4est_geometry_cylindrical_compute_X; + + return reinterpret_cast(builtin); + +} /* p4est_geometry_new_cylindrical_compute */ + +p8est_geometry_t * +p8est_geometry_new_cylindrical_compute(p8est_connectivity_t * conn, + double R0, + double R1, + int nbTrees_theta, + int nbTrees_z) +{ + + p8est_geometry_builtin_t * builtin; + p4est_geometry_builtin_cylindrical_compute_t * cylindrical_compute; + + builtin = P4EST_ALLOC_ZERO(p8est_geometry_builtin_t, 1); + + cylindrical_compute = &builtin->p.cylindrical_compute; + cylindrical_compute->type = P4EST_GEOMETRY_BUILTIN_CYLINDRICAL_COMPUTE; + cylindrical_compute->R0 = R0; + cylindrical_compute->R1 = R1; + cylindrical_compute->nbTrees_theta = nbTrees_theta; + cylindrical_compute->nbTrees_z = nbTrees_z; + + builtin->geom.name = "p4est_cylindrical_compute"; + builtin->geom.user = conn; + builtin->geom.X = p8est_geometry_cylindrical_compute_X; + + return reinterpret_cast(builtin); + +} /* p8est_geometry_new_cylindrical_compute */ + +/**************************************************** + * CYLINDRICAL_IO + ****************************************************/ +/** + * geometric coordinate transformation for cylindrical_io geometry. + * + * Define the geometric transformation from logical space (where AMR + * is performed) to the physical space. + * + * \param[in] p4est the forest + * \param[in] which_tree tree id inside forest + * \param[in] rst coordinates in AMR space : [0,1]^3 + * \param[out] xyz cartesian coordinates in physical space after geometry + * + * Note abc[3] contains cartesian coordinates in logical + * vertex space (before geometry). + */ +static void +p4est_geometry_cylindrical_io_X(p4est_geometry_t * geom, + p4est_topidx_t which_tree, + const double rst[3], + double xyz[3]) +{ + const p4est_geometry_builtin_cylindrical_io_t * cylindrical_io = + &(reinterpret_cast(geom))->p.cylindrical_io; + // double x, y, z; + double R0 = cylindrical_io->R0; + double R1 = cylindrical_io->R1; + + int nbTrees_theta = cylindrical_io->nbTrees_theta; + [[maybe_unused]] int nbTrees_z = cylindrical_io->nbTrees_z; + double abc[3]; + + double r, theta; + + /* transform from the reference cube [0,1]^3 into logical vertex space + using bi/trilinear transformation */ + p4est_geometry_connectivity_X(geom, which_tree, rst, abc); + + /* + * assert that input points are in the expected range + * Note: maybe we should remove these assert, this would allow + * ghost quadrant at external boundary to call this routine ? + */ + P4EST_ASSERT(cylindrical_io->type == P4EST_GEOMETRY_BUILTIN_CYLINDRICAL_IO); + P4EST_ASSERT(0 <= which_tree && which_tree < nbTrees_theta * nbTrees_z); + P4EST_ASSERT(abc[0] < 1.0 + SC_1000_EPS && abc[0] > 0.0 - SC_1000_EPS); + P4EST_ASSERT(abc[1] < nbTrees_theta + SC_1000_EPS && abc[1] > 0.0 - SC_1000_EPS); + + // abc[0] is in range [0..1] + // r is in range [R0..R1] + r = R0 + abc[0] * (R1 - R0); + + // abc[1] is in range [0..nbTrees_theta] + // theta is in range [0, 2 M_PI] + theta = abc[1] / nbTrees_theta * 2 * M_PI; + + // return cartesian coordinates for plotting + xyz[0] = r * cos(theta); + xyz[1] = r * sin(theta); + + // abc[2] is in range [0..nbTrees_z] + // just make it zero-centered + xyz[2] = 0.0; + +} /* p4est_geometry_cylindrical_io_X */ + +static void +p8est_geometry_cylindrical_io_X(p8est_geometry_t * geom, + p4est_topidx_t which_tree, + const double rst[3], + double xyz[3]) +{ + const p4est_geometry_builtin_cylindrical_io_t * cylindrical_io = + &(reinterpret_cast(geom))->p.cylindrical_io; + // double x, y, z; + double R0 = cylindrical_io->R0; + double R1 = cylindrical_io->R1; + + int nbTrees_theta = cylindrical_io->nbTrees_theta; + [[maybe_unused]] int nbTrees_z = cylindrical_io->nbTrees_z; + double abc[3]; + + double r, theta; + + /* transform from the reference cube [0,1]^3 into logical vertex space + using bi/trilinear transformation */ + p8est_geometry_connectivity_X(geom, which_tree, rst, abc); + + /* + * assert that input points are in the expected range + * Note: maybe we should remove these assert, this would allow + * ghost quadrant at external boundary to call this routine ? + */ + P4EST_ASSERT(cylindrical_io->type == P4EST_GEOMETRY_BUILTIN_CYLINDRICAL_IO); + P4EST_ASSERT(0 <= which_tree && which_tree < nbTrees_theta * nbTrees_z); + P4EST_ASSERT(abc[0] < 1.0 + SC_1000_EPS && abc[0] > 0.0 - SC_1000_EPS); + P4EST_ASSERT(abc[1] < nbTrees_theta + SC_1000_EPS && abc[1] > 0.0 - SC_1000_EPS); + + P4EST_ASSERT(abc[2] < nbTrees_z + SC_1000_EPS && abc[2] > 0.0 - SC_1000_EPS); + + // abc[0] is in range [0..1] + // r is in range [R0..R1] + r = R0 + abc[0] * (R1 - R0); + + // abc[1] is in range [0..nbTrees_theta] + // theta is in range [0, 2 M_PI] + theta = abc[1] / nbTrees_theta * 2 * M_PI; + + // return cartesian coordinates for plotting + xyz[0] = r * cos(theta); + xyz[1] = r * sin(theta); + + // abc[2] is in range [0..nbTrees_z] + // just make it zero-centered + xyz[2] = abc[2] - 0.5 * nbTrees_z; + +} /* p8est_geometry_cylindrical_io_X */ + +p4est_geometry_t * +p4est_geometry_new_cylindrical_io(p4est_connectivity_t * conn, + double R0, + double R1, + int nbTrees_theta, + int nbTrees_z) +{ + + p4est_geometry_builtin_t * builtin; + p4est_geometry_builtin_cylindrical_io_t * cylindrical_io; + + builtin = P4EST_ALLOC_ZERO(p4est_geometry_builtin_t, 1); + + cylindrical_io = &builtin->p.cylindrical_io; + cylindrical_io->type = P4EST_GEOMETRY_BUILTIN_CYLINDRICAL_IO; + cylindrical_io->R0 = R0; + cylindrical_io->R1 = R1; + cylindrical_io->nbTrees_theta = nbTrees_theta; + cylindrical_io->nbTrees_z = nbTrees_z; + + builtin->geom.name = "p4est_cylindrical_io"; + builtin->geom.user = conn; + builtin->geom.X = p4est_geometry_cylindrical_io_X; + + return reinterpret_cast(builtin); + +} /* p4est_geometry_new_cylindrical_io */ + +p8est_geometry_t * +p8est_geometry_new_cylindrical_io(p8est_connectivity_t * conn, + double R0, + double R1, + int nbTrees_theta, + int nbTrees_z) +{ + + p8est_geometry_builtin_t * builtin; + p4est_geometry_builtin_cylindrical_io_t * cylindrical_io; + + builtin = P4EST_ALLOC_ZERO(p8est_geometry_builtin_t, 1); + + cylindrical_io = &builtin->p.cylindrical_io; + cylindrical_io->type = P4EST_GEOMETRY_BUILTIN_CYLINDRICAL_IO; + cylindrical_io->R0 = R0; + cylindrical_io->R1 = R1; + cylindrical_io->nbTrees_theta = nbTrees_theta; + cylindrical_io->nbTrees_z = nbTrees_z; + + builtin->geom.name = "p4est_cylindrical_io"; + builtin->geom.user = conn; + builtin->geom.X = p8est_geometry_cylindrical_io_X; + + return reinterpret_cast(builtin); + +} /* p8est_geometry_new_cylindrical_io */ + + +p4est_geometry_t * +geometry_2d_new_byname(const char * name, + p4est_connectivity_t * conn, + const kalypsso::ConfigMap & cfg) +{ + p4est_geometry_t * geom = nullptr; + + if (strcmp(name, "cartesian") == 0 or strcmp(name, "no_geometry") == 0) + { + geom = nullptr; + } + + else if (strcmp(name, "cylindrical_compute") == 0 || strcmp(name, "cylindrical_io") == 0) + { + + double R0, R1; + R0 = cfg.getDouble("p4est_geometry", "cylindrical.rMin", 0.1); + R1 = cfg.getDouble("p4est_geometry", "cylindrical.rMax", 1.0); + int nbTrees_theta, nbTrees_z; + nbTrees_theta = cfg.getInteger("p4est_geometry", "cylindrical.nbTrees_theta", 8); + nbTrees_z = cfg.getInteger("p4est_geometry", "cylindrical.nbTrees_z", 1); + + if (strcmp(name, "cylindrical_compute") == 0) + geom = p4est_geometry_new_cylindrical_compute(conn, R0, R1, nbTrees_theta, nbTrees_z); + else + geom = p4est_geometry_new_cylindrical_io(conn, R0, R1, nbTrees_theta, nbTrees_z); + } + + else if (strcmp(name, "disk2d") == 0) + { + double R0, R1; + R0 = cfg.getDouble("p4est_geometry", "disk2d.R0", 0.5); + R1 = cfg.getDouble("p4est_geometry", "disk2d.R1", 1.0); + + geom = p4est_geometry_new_disk2d(conn, R0, R1); + } + + else if (strcmp(name, "shell2d") == 0) + { + double R1, R2; + R1 = cfg.getDouble("p4est_geometry", "shell2d.R1", 0.5); + R2 = cfg.getDouble("p4est_geometry", "shell2d.R2", 4.0); + + geom = p4est_geometry_new_shell2d(conn, R2, R1); + } + + else if (strcmp(name, "icosahedron") == 0) + { + double a; + a = cfg.getDouble("p4est_geometry", "icosahedron.a", 1.0); + + geom = p4est_geometry_new_icosahedron(conn, a); + } + + else + { + + P4EST_GLOBAL_LERRORF("###### Unrecognized geometry \"%s\".\n", name); + P4EST_GLOBAL_LERROR("###### Defaulting to nullptr.\n"); + geom = nullptr; + } + + return geom; + +} // geometry_2d_new_byname + +p8est_geometry_t * +geometry_3d_new_byname(const char * name, + p8est_connectivity_t * conn, + const kalypsso::ConfigMap & cfg) +{ + p8est_geometry_t * geom = nullptr; + + if (strcmp(name, "cartesian") == 0 or strcmp(name, "no_geometry") == 0) + { + geom = nullptr; + } + + else if (strcmp(name, "sphere") == 0) + { + + double R0, R1, R2; + R2 = cfg.getDouble("p4est_geometry", "sphere.R2", 1.0); + R1 = cfg.getDouble("p4est_geometry", "sphere.R1", 0.7); + R0 = cfg.getDouble("p4est_geometry", "sphere.R0", 0.5); + + geom = p8est_geometry_new_sphere(conn, R2, R1, R0); + } + + else if (strcmp(name, "shell") == 0) + { + + double R1, R2; + R2 = cfg.getDouble("p4est_geometry", "shell.R2", 1.0); + R1 = cfg.getDouble("p4est_geometry", "shell.R1", 0.44); + + geom = p8est_geometry_new_shell(conn, R2, R1); + } + + else if (strcmp(name, "cylindrical_compute") == 0 || strcmp(name, "cylindrical_io") == 0) + { + + double R0, R1; + R0 = cfg.getDouble("p4est_geometry", "cylindrical.rMin", 0.1); + R1 = cfg.getDouble("p4est_geometry", "cylindrical.rMax", 1.0); + int nbTrees_theta, nbTrees_z; + nbTrees_theta = cfg.getInteger("p4est_geometry", "cylindrical.nbTrees_theta", 8); + nbTrees_z = cfg.getInteger("p4est_geometry", "cylindrical.nbTrees_z", 1); + + if (strcmp(name, "cylindrical_compute") == 0) + geom = p8est_geometry_new_cylindrical_compute(conn, R0, R1, nbTrees_theta, nbTrees_z); + else + geom = p8est_geometry_new_cylindrical_io(conn, R0, R1, nbTrees_theta, nbTrees_z); + } + + else + { + + P4EST_GLOBAL_LERRORF("###### Unrecognized geometry \"%s\".\n", name); + P4EST_GLOBAL_LERROR("###### Defaulting to nullptr.\n"); + geom = nullptr; + } + + return geom; + +} // geometry_3d_new_byname diff --git a/src/kalypsso/utils/p4est/geometry.h b/src/kalypsso/utils/p4est/geometry.h new file mode 100644 index 0000000..0967366 --- /dev/null +++ b/src/kalypsso/utils/p4est/geometry.h @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * @file geometry.h + * @date 17 octobre 2020 + * @author pkestene + * @note + * + * This file is part of the Kalypsso software project. + * + */ +#ifndef KALYPSSO_UTILS_P4EST_GEOMETRY_H_ +#define KALYPSSO_UTILS_P4EST_GEOMETRY_H_ + +#include +#include + +#include +#include + +namespace kalypsso +{ +class ConfigMap; +} // namespace kalypsso + +/** + * cylindrical geometry for computation associated to cylindrical connectivity. + * + * \param[in] R0 radius of the inner border + * \param[in] R1 radius of the outer border + * \param[in] nbTrees_theta number of tree along theta (orthoradial) + * \param[in] nbTrees_z number of tree along z direction + * + */ +p4est_geometry_t * +p4est_geometry_new_cylindrical_compute(p4est_connectivity_t * conn, + double R0, + double R1, + int nbTrees_theta, + int nbTrees_z); + +/** + * cylindrical geometry used only for IO associated to cylindrical connectivity. + * + * \param[in] R0 radius of the inner border + * \param[in] R1 radius of the outer border + * \param[in] nbTrees_theta number of tree along theta (orthoradial) + * \param[in] nbTrees_z number of tree along z direction + * + */ +p4est_geometry_t * +p4est_geometry_new_cylindrical_io(p4est_connectivity_t * conn, + double R0, + double R1, + int nbTrees_theta, + int nbTrees_z); + +/** + * \brief Create a geometry by name. + * + * This is optional. If you don't do anything, i.e. geometry parameter is not set, the + * default geometry is cartesian. + * + * Allowed values for 2D: + * shell2d + * disk2d + * icosahedron (map the sphere) + * + * Allowed values for 3D: + * shell + * sphere + * + * \param[in] name A geometry name. + * \param[in] conn A connectivity already created. + * \param[in] cfg A config reader. + * \return A fully allocated geometry. + */ +p4est_geometry_t * +geometry_2d_new_byname(const char * name, + p4est_connectivity_t * conn, + const kalypsso::ConfigMap & cfg); + +p8est_geometry_t * +geometry_3d_new_byname(const char * name, + p8est_connectivity_t * conn, + const kalypsso::ConfigMap & cfg); + +#endif // KALYPSSO_UTILS_P4EST_GEOMETRY_H_ diff --git a/src/kalypsso/utils/p4est/p4est_wrapper.cpp b/src/kalypsso/utils/p4est/p4est_wrapper.cpp new file mode 100644 index 0000000..195fdf6 --- /dev/null +++ b/src/kalypsso/utils/p4est/p4est_wrapper.cpp @@ -0,0 +1,989 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * @file p4est_wrapper.cpp + * @date 17 octobre 2020 + * @author pkestene + * @note + * + * Add a wrapper unifying 2D / 3D interface to p4est. + * + * This file is part of the Kalypsso software project. + * + */ +#include +#include +#include + +#include + +namespace kalypsso +{ + +namespace p4est +{ + +/***************************************************************** + * Static reference function member initialization for Wrapper<2>. + *****************************************************************/ +template <> +const int Wrapper<2>::face_corners[][NB_NODES_PER_FACE] = { { 0, 2 }, + { 1, 3 }, + { 0, 1 }, + { 2, 3 } }; + +template <> +const int Wrapper<2>::face_dual[] = { 1, 0, 3, 2 }; + +template <> +void +Wrapper<2>::qcoord_to_vertex(connectivity_t * connectivity, + topidx_t treeid, + qcoord_t xyz[3], + double vxyz[3]) +{ + p4est_qcoord_to_vertex(connectivity, treeid, xyz[0], xyz[1], vxyz); +} + +template <> +int (&Wrapper<2>::quadrant_is_equal)(const quadrant_t * q1, + const quadrant_t * q2) = p4est_quadrant_is_equal; + +template <> +int (&Wrapper<2>::quadrant_is_ancestor)(const quadrant_t * q, + const quadrant_t * r) = p4est_quadrant_is_ancestor; + +template <> +void (&Wrapper<2>::quadrant_child)(const quadrant_t * q, + quadrant_t * r, + int child_id) = p4est_quadrant_child; + +template <> +int (&Wrapper<2>::quadrant_child_id)(const quadrant_t * q) = p4est_quadrant_child_id; + +// connectivity / geometry routines +template <> +Wrapper<2>::connectivity_t * (&Wrapper<2>::connectivity_new_byname)(const char * name) = + p4est_connectivity_new_byname; + +template <> +Wrapper<2>::connectivity_t * (&Wrapper<2>::my_connectivity_new_byname)( + const char * name, + const ConfigMap & config_map) = connectivity_2d_new_byname; + +template <> +Wrapper<2>::geometry_t * (&Wrapper<2>::my_geometry_new_byname)(const char * name, + connectivity_t * conn, + const ConfigMap & config_map) = + geometry_2d_new_byname; + +template <> +void (&Wrapper<2>::connectivity_destroy)(connectivity_t * connectivity) = + p4est_connectivity_destroy; + +template <> +int (&Wrapper<2>::connectivity_save)(const char * filename, + connectivity_t * connectivity) = p4est_connectivity_save; + +template <> +int (&Wrapper<2>::connectivity_is_valid)(connectivity_t * connectivity) = + p4est_connectivity_is_valid; + +template <> +Wrapper<2>::connectivity_t * ( + &Wrapper<2>::connectivity_load)(const char * filename, size_t * length) = p4est_connectivity_load; + +template <> +Wrapper<2>::connectivity_t * (&Wrapper<2>::connectivity_read_inp)(const char * filename) = + p4est_connectivity_read_inp; + +template <> +void (&Wrapper<2>::geometry_connectivity_X)(geometry_t * geometry, + topidx_t which_tree, + const double abc[3], + double xyz[3]) = p4est_geometry_connectivity_X; +template <> +void (&Wrapper<2>::geometry_destroy)(geometry_t * geometry) = p4est_geometry_destroy; + +// forest routines +template <> +Wrapper<2>::forest_t * (&Wrapper<2>::new_forest)(sc_MPI_Comm mpicomm, + connectivity_t * connectivity, + locidx_t min_quadrants, + int min_level, + int fill_uniform, + size_t data_size, + init_cb_t init_fn, + void * user_pointer) = p4est_new_ext; + +template <> +void (&Wrapper<2>::destroy)(forest_t * forest) = p4est_destroy; + +template <> +Wrapper<2>::forest_t * (&Wrapper<2>::load)(const char * filename, + sc_MPI_Comm mpicomm, + size_t data_size, + int load_data, + void * user_pointer, + connectivity_t ** connectivity) = p4est_load; + +template <> +Wrapper<2>::forest_t * (&Wrapper<2>::load_ext)(const char * filename, + sc_MPI_Comm mpicomm, + size_t data_size, + int load_data, + int autopartition, + int broadcasthead, + void * user_pointer, + connectivity_t ** connectivity) = p4est_load_ext; + +template <> +void (&Wrapper<2>::refine)(forest_t * forest, + int refine_recursive, + refine_cb_t refine_fn, + init_cb_t init_fn) = p4est_refine; + +template <> +void (&Wrapper<2>::refine_ext)(forest_t * forest, + int refine_recursive, + int maxlevel, + refine_cb_t refine_fn, + init_cb_t init_fn, + replace_cb_t replace_fn) = p4est_refine_ext; + +template <> +void (&Wrapper<2>::coarsen)(forest_t * forest, + int coarsen_recursive, + coarsen_cb_t coarsen_fn, + init_cb_t init_fn) = p4est_coarsen; + +template <> +void (&Wrapper<2>::coarsen_ext)(forest_t * p4est, + int coarsen_recursive, + int callback_orphans, + coarsen_cb_t coarsen_fn, + init_cb_t init_fn, + replace_cb_t replace_fn) = p4est_coarsen_ext; + +template <> +void (&Wrapper<2>::balance)(forest_t * forest, + connect_type_t btype, + init_cb_t init_fn) = p4est_balance; + +template <> +void (&Wrapper<2>::balance_ext)(forest_t * forest, + connect_type_t btype, + init_cb_t init_fn, + replace_cb_t replace_fn) = p4est_balance_ext; + +template <> +const Wrapper<2>::connect_type_t Wrapper<2>::CONNECT_SELF = P4EST_CONNECT_SELF; +template <> +const Wrapper<2>::connect_type_t Wrapper<2>::CONNECT_FACE = P4EST_CONNECT_FACE; +template <> +const Wrapper<2>::connect_type_t Wrapper<2>::CONNECT_EDGE = P4EST_CONNECT_FACE; // WOULDN'T BE USED +template <> +const Wrapper<2>::connect_type_t Wrapper<2>::CONNECT_CORNER = P4EST_CONNECT_CORNER; +template <> +const Wrapper<2>::connect_type_t Wrapper<2>::CONNECT_FULL = P4EST_CONNECT_FULL; + +template <> +void (&Wrapper<2>::partition)(forest_t * forest, + int allow_for_coarsening, + weight_cb_t weight_fn) = p4est_partition; + +template <> +gloidx_t (&Wrapper<2>::partition_ext)(forest_t * forest, + int partition_for_coarsening, + weight_cb_t weight_fn) = p4est_partition_ext; + +template <> +void +Wrapper<2>::iterate_volume(forest_t * forest, + ghost_t * ghost, + void * user_data, + volume_cb_t volume_cb) +{ + p4est_iterate(forest, + ghost, // ghost layer + user_data, + volume_cb, + nullptr, // iter_face, + nullptr // iter_corner + ); +} + +template <> +void (&Wrapper<2>::save)(const char * filename, forest_t * forest, int save_data) = p4est_save; + +template <> +void (&Wrapper<2>::save_ext)(const char * filename, + forest_t * forest, + int save_data, + int save_partition) = p4est_save_ext; + +template <> +unsigned int (&Wrapper<2>::checksum)(forest_t * forest) = p4est_checksum; + +/* + * VTK related routines + */ +template <> +void (&Wrapper<2>::vtk_write_file)(forest_t * forest, + geometry_t * geometry, + const char * baseName) = p4est_vtk_write_file; + +template <> +Wrapper<2>::vtk_context_t * ( + &Wrapper<2>::vtk_context_new)(forest_t * forest, const char * filename) = p4est_vtk_context_new; + +template <> +void (&Wrapper<2>::vtk_context_set_geom)(vtk_context_t * cont, + geometry_t * geom) = p4est_vtk_context_set_geom; + +template <> +void (&Wrapper<2>::vtk_context_set_scale)(vtk_context_t * cont, + double scale) = p4est_vtk_context_set_scale; + +template <> +void (&Wrapper<2>::vtk_context_set_continuous)(vtk_context_t * cont, + int continuous) = p4est_vtk_context_set_continuous; + +template <> +void (&Wrapper<2>::vtk_context_destroy)(vtk_context_t * context) = p4est_vtk_context_destroy; + +template <> +Wrapper<2>::vtk_context_t * (&Wrapper<2>::vtk_write_header)(vtk_context_t * cont) = + p4est_vtk_write_header; + +template <> +Wrapper<2>::vtk_context_t * (&Wrapper<2>::vtk_write_cell_dataf)(vtk_context_t * cont, + int write_tree, + int write_level, + int write_rank, + int wrap_rank, + int num_cell_scalars, + int num_cell_vectors, + ...) = p4est_vtk_write_cell_dataf; + +// template<> +// Wrapper<2>::vtk_context_t* (&Wrapper<2>::vtk_write_cell_data) (vtk_context_t * cont, +// int write_tree, +// int write_level, +// int write_rank, +// int wrap_rank, +// int num_cell_scalars, +// int num_cell_vectors, +// const char *filenames[], +// sc_array_t * values[]) = +// p4est_vtk_write_cell_data; + +template <> +Wrapper<2>::vtk_context_t * (&Wrapper<2>::vtk_write_point_dataf)(vtk_context_t * cont, + int num_point_scalars, + int num_point_vectors, + ...) = p4est_vtk_write_point_dataf; + +template <> +int (&Wrapper<2>::vtk_write_footer)(vtk_context_t * cont) = p4est_vtk_write_footer; + +/* + * ghost related routine. + */ +template <> +int (&Wrapper<2>::ghost_is_valid)(forest_t * forest, ghost_t * ghost) = p4est_ghost_is_valid; + +template <> +size_t (&Wrapper<2>::ghost_memory_used)(ghost_t * ghost) = p4est_ghost_memory_used; +template <> +Wrapper<2>::ghost_t * (&Wrapper<2>::ghost_new)(forest_t * forest, + connect_type_t btype) = p4est_ghost_new; + +template <> +void (&Wrapper<2>::ghost_destroy)(ghost_t * ghost) = p4est_ghost_destroy; + +template <> +void (&Wrapper<2>::ghost_exchange_data)(forest_t * forest, + ghost_t * ghost, + void * ghost_data) = p4est_ghost_exchange_data; + +template <> +int (&Wrapper<2>::is_balanced)(forest_t * forest, connect_type_t btype) = p4est_is_balanced; + +template <> +unsigned (&Wrapper<2>::ghost_checksum)(forest_t * forest, ghost_t * ghost) = p4est_ghost_checksum; + +/* + * mesh related routines. + */ +template <> +Wrapper<2>::mesh_t * (&Wrapper<2>::mesh_new)(forest_t * p4est, + ghost_t * ghost, + connect_type_t btype) = p4est_mesh_new; + +template <> +Wrapper<2>::mesh_t * (&Wrapper<2>::mesh_new_ext)(forest_t * p4est, + ghost_t * ghost, + int compute_tree_index, + int compute_level_lists, + connect_type_t btype) = p4est_mesh_new_ext; + +template <> +void (&Wrapper<2>::mesh_destroy)(mesh_t * mesh) = p4est_mesh_destroy; + +template <> +Wrapper<2>::quadrant_t * (&Wrapper<2>::mesh_quadrant_cumulative)(forest_t * forest, + mesh_t * mesh, + locidx_t cumulative_id, + topidx_t * which_tree, + locidx_t * quadrant_id) = + p4est_mesh_quadrant_cumulative; + +template <> +void (&Wrapper<2>::mesh_face_neighbor_init2)(mesh_face_neighbor_t * mfn, + forest_t * forest, + ghost_t * ghost, + mesh_t * mesh, + topidx_t which_tree, + locidx_t quadrant_id) = p4est_mesh_face_neighbor_init2; + +template <> +Wrapper<2>::quadrant_t * (&Wrapper<2>::mesh_face_neighbor_next)(mesh_face_neighbor_t * mfn, + topidx_t * ntree, + locidx_t * nquad, + int * nface, + int * nrank) = + p4est_mesh_face_neighbor_next; + + +/* + * nodes / lnodes related routines. + */ +template <> +Wrapper<2>::nodes_t * (&Wrapper<2>::nodes_new)(forest_t * forest, + ghost_t * ghost) = p4est_nodes_new; + +template <> +void (&Wrapper<2>::nodes_destroy)(nodes_t * nodes) = p4est_nodes_destroy; + +template <> +int (&Wrapper<2>::nodes_is_valid)(forest_t * forest, nodes_t * nodes) = p4est_nodes_is_valid; + +template <> +Wrapper<2>::lnodes_t * (&Wrapper<2>::lnodes_new)(forest_t * forest, + ghost_t * ghost, + int degree) = p4est_lnodes_new; + +template <> +void (&Wrapper<2>::lnodes_destroy)(lnodes_t * lnodes) = p4est_lnodes_destroy; + +template <> +Wrapper<2>::lnodes_buffer_t * (&Wrapper<2>::lnodes_share_all)(sc_array_t * node_data, + lnodes_t * lnodes) = + p4est_lnodes_share_all; + +template <> +void (&Wrapper<2>::lnodes_buffer_destroy)(lnodes_buffer_t * buffer) = p4est_lnodes_buffer_destroy; + +/* + * Communication / user data transfer related routines. + */ +template <> +void (&Wrapper<2>::comm_count_quadrants)(forest_t * forest) = p4est_comm_count_quadrants; + +template <> +void (&Wrapper<2>::comm_count_pertree)(forest_t * forest, + gloidx_t * pertree) = p4est_comm_count_pertree; + +template <> +void (&Wrapper<2>::transfer_fixed)(const gloidx_t * dest_gfq, + const gloidx_t * src_gfq, + sc_MPI_Comm mpicomm, + int tag, + void * dest_data, + const void * src_data, + size_t data_size) = p4est_transfer_fixed; + +template <> +int (&Wrapper<2>::bsearch_partition)(gloidx_t target, + const gloidx_t * gfq, + int nmemb) = p4est_bsearch_partition; + +template <> +Wrapper<2>::transfer_context_t * (&Wrapper<2>::transfer_fixed_begin)(const gloidx_t * dest_gfq, + const gloidx_t * src_gfq, + sc_MPI_Comm mpicomm, + int tag, + void * dest_data, + const void * src_data, + size_t data_size) = + p4est_transfer_fixed_begin; + +template <> +void (&Wrapper<2>::transfer_fixed_end)(transfer_context_t * tc) = p4est_transfer_fixed_end; + +template <> +void (&Wrapper<2>::transfer_custom)(const gloidx_t * dest_gfq, + const gloidx_t * src_gfq, + sc_MPI_Comm mpicomm, + int tag, + void * dest_data, + const int * dest_sizes, + const void * src_data, + const int * src_sizes) = p4est_transfer_custom; + +template <> +Wrapper<2>::transfer_context_t * (&Wrapper<2>::transfer_custom_begin)(const gloidx_t * dest_gfq, + const gloidx_t * src_gfq, + sc_MPI_Comm mpicomm, + int tag, + void * dest_data, + const int * dest_sizes, + const void * src_data, + const int * src_sizes) = + p4est_transfer_custom_begin; + +template <> +void (&Wrapper<2>::transfer_custom_end)(transfer_context_t * tc) = p4est_transfer_custom_end; + +template <> +void (&Wrapper<2>::transfer_items)(const gloidx_t * dest_gfq, + const gloidx_t * src_gfq, + sc_MPI_Comm mpicomm, + int tag, + void * dest_data, + const int * dest_counts, + const void * src_data, + const int * src_counts, + size_t item_size) = p4est_transfer_items; + +template <> +Wrapper<2>::transfer_context_t * (&Wrapper<2>::transfer_items_begin)(const gloidx_t * dest_gfq, + const gloidx_t * src_gfq, + sc_MPI_Comm mpicomm, + int tag, + void * dest_data, + const int * dest_counts, + const void * src_data, + const int * src_counts, + size_t item_size) = + p4est_transfer_items_begin; + +template <> +void (&Wrapper<2>::transfer_items_end)(transfer_context_t * tc) = p4est_transfer_items_end; + +template <> +void (&Wrapper<2>::transfer_end)(transfer_context_t * tc) = p4est_transfer_end; + + +/* + * array related routines. + */ +template <> +Wrapper<2>::tree_t * (&Wrapper<2>::tree_array_index)(sc_array_t * array, + topidx_t it) = p4est_tree_array_index; + +template <> +Wrapper<2>::quadrant_t * (&Wrapper<2>::quadrant_array_index)(sc_array_t * array, size_t it) = + p4est_quadrant_array_index; + +template <> +void (&Wrapper<2>::reset_data)(forest_t * forest, + size_t data_size, + init_cb_t init_fn, + void * user_pointer) = p4est_reset_data; + +template <> +size_t (&Wrapper<2>::forest_memory_used)(forest_t * forest) = p4est_memory_used; + +template <> +size_t (&Wrapper<2>::connectivity_memory_used)(connectivity_t * conn) = + p4est_connectivity_memory_used; + + +/********************************************************************* + * Static reference function member initialization for Wrapper<3>. + *********************************************************************/ +template <> +const int Wrapper<3>::face_corners[][NB_NODES_PER_FACE] = { { 0, 2, 4, 6 }, { 1, 3, 5, 7 }, + { 0, 1, 4, 5 }, { 2, 3, 6, 7 }, + { 0, 1, 2, 3 }, { 4, 5, 6, 7 } }; + +template <> +const int Wrapper<3>::face_dual[] = { 1, 0, 3, 2, 5, 4 }; + +template <> +void +Wrapper<3>::qcoord_to_vertex(connectivity_t * connectivity, + topidx_t treeid, + qcoord_t xyz[3], + double vxyz[3]) +{ + p8est_qcoord_to_vertex(connectivity, treeid, xyz[0], xyz[1], xyz[2], vxyz); +} + +template <> +int (&Wrapper<3>::quadrant_is_equal)(const quadrant_t * q1, + const quadrant_t * q2) = p8est_quadrant_is_equal; + +template <> +int (&Wrapper<3>::quadrant_is_ancestor)(const quadrant_t * q, + const quadrant_t * r) = p8est_quadrant_is_ancestor; + +template <> +void (&Wrapper<3>::quadrant_child)(const quadrant_t * q, + quadrant_t * r, + int child_id) = p8est_quadrant_child; + +template <> +int (&Wrapper<3>::quadrant_child_id)(const quadrant_t * q) = p8est_quadrant_child_id; + +// connectivity / geometry routines +template <> +Wrapper<3>::connectivity_t * (&Wrapper<3>::connectivity_new_byname)(const char * name) = + p8est_connectivity_new_byname; + +template <> +Wrapper<3>::connectivity_t * (&Wrapper<3>::my_connectivity_new_byname)( + const char * name, + const ConfigMap & config_map) = connectivity_3d_new_byname; + +template <> +Wrapper<3>::geometry_t * (&Wrapper<3>::my_geometry_new_byname)(const char * name, + connectivity_t * conn, + const ConfigMap & config_map) = + geometry_3d_new_byname; + +template <> +void (&Wrapper<3>::connectivity_destroy)(connectivity_t * connectivity) = + p8est_connectivity_destroy; + +template <> +int (&Wrapper<3>::connectivity_save)(const char * filename, + connectivity_t * connectivity) = p8est_connectivity_save; + +template <> +int (&Wrapper<3>::connectivity_is_valid)(connectivity_t * connectivity) = + p8est_connectivity_is_valid; + +template <> +Wrapper<3>::connectivity_t * ( + &Wrapper<3>::connectivity_load)(const char * filename, size_t * length) = p8est_connectivity_load; + +template <> +Wrapper<3>::connectivity_t * (&Wrapper<3>::connectivity_read_inp)(const char * filename) = + p8est_connectivity_read_inp; + +template <> +void (&Wrapper<3>::geometry_connectivity_X)(geometry_t * geometry, + topidx_t which_tree, + const double abc[3], + double xyz[3]) = p8est_geometry_connectivity_X; + +template <> +void (&Wrapper<3>::geometry_destroy)(geometry_t * geometry) = p8est_geometry_destroy; + +// forest routines +template <> +Wrapper<3>::forest_t * (&Wrapper<3>::new_forest)(sc_MPI_Comm mpicomm, + connectivity_t * connectivity, + locidx_t min_quadrants, + int min_level, + int fill_uniform, + size_t data_size, + init_cb_t init_fn, + void * user_pointer) = p8est_new_ext; + +template <> +void (&Wrapper<3>::destroy)(forest_t * forest) = p8est_destroy; + +template <> +Wrapper<3>::forest_t * (&Wrapper<3>::load)(const char * filename, + sc_MPI_Comm mpicomm, + size_t data_size, + int load_data, + void * user_pointer, + connectivity_t ** connectivity) = p8est_load; + +template <> +Wrapper<3>::forest_t * (&Wrapper<3>::load_ext)(const char * filename, + sc_MPI_Comm mpicomm, + size_t data_size, + int load_data, + int autopartition, + int broadcasthead, + void * user_pointer, + connectivity_t ** connectivity) = p8est_load_ext; + +template <> +void (&Wrapper<3>::refine)(forest_t * forest, + int refine_recursive, + refine_cb_t refine_fn, + init_cb_t init_fn) = p8est_refine; + +template <> +void (&Wrapper<3>::refine_ext)(forest_t * forest, + int refine_recursive, + int maxlevel, + refine_cb_t refine_fn, + init_cb_t init_fn, + replace_cb_t replace_fn) = p8est_refine_ext; + + +template <> +void (&Wrapper<3>::coarsen)(forest_t * forest, + int coarsen_recursive, + coarsen_cb_t coarsen_fn, + init_cb_t init_fn) = p8est_coarsen; + +template <> +void (&Wrapper<3>::coarsen_ext)(forest_t * p4est, + int coarsen_recursive, + int callback_orphans, + coarsen_cb_t coarsen_fn, + init_cb_t init_fn, + replace_cb_t replace_fn) = p8est_coarsen_ext; + +template <> +void (&Wrapper<3>::balance)(forest_t * forest, + connect_type_t btype, + init_cb_t init_fn) = p8est_balance; + +template <> +void (&Wrapper<3>::balance_ext)(forest_t * forest, + connect_type_t btype, + init_cb_t init_fn, + replace_cb_t replace_fn) = p8est_balance_ext; + +template <> +const Wrapper<3>::connect_type_t Wrapper<3>::CONNECT_SELF = P8EST_CONNECT_SELF; +template <> +const Wrapper<3>::connect_type_t Wrapper<3>::CONNECT_FACE = P8EST_CONNECT_FACE; +template <> +const Wrapper<3>::connect_type_t Wrapper<3>::CONNECT_EDGE = P8EST_CONNECT_EDGE; +template <> +const Wrapper<3>::connect_type_t Wrapper<3>::CONNECT_CORNER = P8EST_CONNECT_CORNER; +template <> +const Wrapper<3>::connect_type_t Wrapper<3>::CONNECT_FULL = P8EST_CONNECT_FULL; + +template <> +void (&Wrapper<3>::partition)(forest_t * forest, + int allow_for_coarsening, + weight_cb_t weight_fn) = p8est_partition; + +template <> +gloidx_t (&Wrapper<3>::partition_ext)(forest_t * forest, + int partition_for_coarsening, + weight_cb_t weight_fn) = p8est_partition_ext; + +template <> +void +Wrapper<3>::iterate_volume(forest_t * forest, + ghost_t * ghost, + void * user_data, + volume_cb_t volume_cb) +{ + p8est_iterate(forest, + ghost, // ghost layer + user_data, + volume_cb, + nullptr, // iter_face + nullptr, // iter_edge + nullptr // iter_corner + ); +} + +template <> +void (&Wrapper<3>::save)(const char * filename, forest_t * forest, int save_data) = p8est_save; + +template <> +void (&Wrapper<3>::save_ext)(const char * filename, + forest_t * forest, + int save_data, + int save_partition) = p8est_save_ext; + + +template <> +unsigned int (&Wrapper<3>::checksum)(forest_t * forest) = p8est_checksum; + +/* + * VTK related routines + */ +template <> +void (&Wrapper<3>::vtk_write_file)(forest_t * forest, + geometry_t * geometry, + const char * baseName) = p8est_vtk_write_file; + +template <> +Wrapper<3>::vtk_context_t * ( + &Wrapper<3>::vtk_context_new)(forest_t * forest, const char * filename) = p8est_vtk_context_new; + +template <> +void (&Wrapper<3>::vtk_context_set_geom)(vtk_context_t * cont, + geometry_t * geom) = p8est_vtk_context_set_geom; + +template <> +void (&Wrapper<3>::vtk_context_set_scale)(vtk_context_t * cont, + double scale) = p8est_vtk_context_set_scale; + +template <> +void (&Wrapper<3>::vtk_context_set_continuous)(vtk_context_t * cont, + int continuous) = p8est_vtk_context_set_continuous; + +template <> +void (&Wrapper<3>::vtk_context_destroy)(vtk_context_t * context) = p8est_vtk_context_destroy; + +template <> +Wrapper<3>::vtk_context_t * (&Wrapper<3>::vtk_write_header)(vtk_context_t * cont) = + p8est_vtk_write_header; + +template <> +Wrapper<3>::vtk_context_t * (&Wrapper<3>::vtk_write_cell_dataf)(vtk_context_t * cont, + int write_tree, + int write_level, + int write_rank, + int wrap_rank, + int num_cell_scalars, + int num_cell_vectors, + ...) = p8est_vtk_write_cell_dataf; + +// template<> +// Wrapper<3>::vtk_context_t* (&Wrapper<3>::vtk_write_cell_data) (vtk_context_t * cont, +// int write_tree, +// int write_level, +// int write_rank, +// int wrap_rank, +// int num_cell_scalars, +// int num_cell_vectors, +// const char *filenames[], +// sc_array_t * values[]) = +// p8est_vtk_write_cell_data; + +template <> +Wrapper<3>::vtk_context_t * (&Wrapper<3>::vtk_write_point_dataf)(vtk_context_t * cont, + int num_point_scalars, + int num_point_vectors, + ...) = p8est_vtk_write_point_dataf; + +template <> +int (&Wrapper<3>::vtk_write_footer)(vtk_context_t * cont) = p8est_vtk_write_footer; + +/* + * ghost related routines. + */ +template <> +int (&Wrapper<3>::ghost_is_valid)(forest_t * forest, ghost_t * ghost) = p8est_ghost_is_valid; + +template <> +size_t (&Wrapper<3>::ghost_memory_used)(ghost_t * ghost) = p8est_ghost_memory_used; + +template <> +Wrapper<3>::ghost_t * (&Wrapper<3>::ghost_new)(forest_t * forest, + connect_type_t btype) = p8est_ghost_new; + +template <> +void (&Wrapper<3>::ghost_destroy)(ghost_t * ghost) = p8est_ghost_destroy; + +template <> +void (&Wrapper<3>::ghost_exchange_data)(forest_t * forest, + ghost_t * ghost, + void * ghost_data) = p8est_ghost_exchange_data; + +template <> +int (&Wrapper<3>::is_balanced)(forest_t * forest, connect_type_t btype) = p8est_is_balanced; + +template <> +unsigned (&Wrapper<3>::ghost_checksum)(forest_t * forest, ghost_t * ghost) = p8est_ghost_checksum; + + +/* + * mesh related routines. + */ +template <> +Wrapper<3>::mesh_t * (&Wrapper<3>::mesh_new)(forest_t * p4est, + ghost_t * ghost, + connect_type_t btype) = p8est_mesh_new; + +template <> +Wrapper<3>::mesh_t * (&Wrapper<3>::mesh_new_ext)(forest_t * p4est, + ghost_t * ghost, + int compute_tree_index, + int compute_level_lists, + connect_type_t btype) = p8est_mesh_new_ext; + +template <> +void (&Wrapper<3>::mesh_destroy)(mesh_t * mesh) = p8est_mesh_destroy; + +template <> +Wrapper<3>::quadrant_t * (&Wrapper<3>::mesh_quadrant_cumulative)(forest_t * forest, + mesh_t * mesh, + locidx_t cumulative_id, + topidx_t * which_tree, + locidx_t * quadrant_id) = + p8est_mesh_quadrant_cumulative; + +template <> +void (&Wrapper<3>::mesh_face_neighbor_init2)(mesh_face_neighbor_t * mfn, + forest_t * forest, + ghost_t * ghost, + mesh_t * mesh, + topidx_t which_tree, + locidx_t quadrant_id) = p8est_mesh_face_neighbor_init2; + +template <> +Wrapper<3>::quadrant_t * (&Wrapper<3>::mesh_face_neighbor_next)(mesh_face_neighbor_t * mfn, + topidx_t * ntree, + locidx_t * nquad, + int * nface, + int * nrank) = + p8est_mesh_face_neighbor_next; + +/* + * nodes / lnodes related routines. + */ +template <> +Wrapper<3>::nodes_t * (&Wrapper<3>::nodes_new)(forest_t * forest, + ghost_t * ghost) = p8est_nodes_new; + +template <> +void (&Wrapper<3>::nodes_destroy)(nodes_t * nodes) = p8est_nodes_destroy; + +template <> +int (&Wrapper<3>::nodes_is_valid)(forest_t * forest, nodes_t * nodes) = p8est_nodes_is_valid; + +template <> +Wrapper<3>::lnodes_t * (&Wrapper<3>::lnodes_new)(forest_t * forest, + ghost_t * ghost, + int degree) = p8est_lnodes_new; + +template <> +void (&Wrapper<3>::lnodes_destroy)(lnodes_t * lnodes) = p8est_lnodes_destroy; + +template <> +Wrapper<3>::lnodes_buffer_t * (&Wrapper<3>::lnodes_share_all)(sc_array_t * node_data, + lnodes_t * lnodes) = + p8est_lnodes_share_all; + +template <> +void (&Wrapper<3>::lnodes_buffer_destroy)(lnodes_buffer_t * buffer) = p8est_lnodes_buffer_destroy; + +/* + * Communication / user data transfer related routines. + */ +template <> +void (&Wrapper<3>::comm_count_quadrants)(forest_t * forest) = p8est_comm_count_quadrants; + +template <> +void (&Wrapper<3>::comm_count_pertree)(forest_t * forest, + gloidx_t * pertree) = p8est_comm_count_pertree; + +template <> +void (&Wrapper<3>::transfer_fixed)(const gloidx_t * dest_gfq, + const gloidx_t * src_gfq, + sc_MPI_Comm mpicomm, + int tag, + void * dest_data, + const void * src_data, + size_t data_size) = p8est_transfer_fixed; + +template <> +int (&Wrapper<3>::bsearch_partition)(gloidx_t target, + const gloidx_t * gfq, + int nmemb) = p8est_bsearch_partition; + +template <> +Wrapper<3>::transfer_context_t * (&Wrapper<3>::transfer_fixed_begin)(const gloidx_t * dest_gfq, + const gloidx_t * src_gfq, + sc_MPI_Comm mpicomm, + int tag, + void * dest_data, + const void * src_data, + size_t data_size) = + p8est_transfer_fixed_begin; + +template <> +void (&Wrapper<3>::transfer_fixed_end)(transfer_context_t * tc) = p8est_transfer_fixed_end; + +template <> +void (&Wrapper<3>::transfer_custom)(const gloidx_t * dest_gfq, + const gloidx_t * src_gfq, + sc_MPI_Comm mpicomm, + int tag, + void * dest_data, + const int * dest_sizes, + const void * src_data, + const int * src_sizes) = p8est_transfer_custom; + +template <> +Wrapper<3>::transfer_context_t * (&Wrapper<3>::transfer_custom_begin)(const gloidx_t * dest_gfq, + const gloidx_t * src_gfq, + sc_MPI_Comm mpicomm, + int tag, + void * dest_data, + const int * dest_sizes, + const void * src_data, + const int * src_sizes) = + p8est_transfer_custom_begin; + +template <> +void (&Wrapper<3>::transfer_custom_end)(transfer_context_t * tc) = p8est_transfer_custom_end; + +template <> +void (&Wrapper<3>::transfer_items)(const gloidx_t * dest_gfq, + const gloidx_t * src_gfq, + sc_MPI_Comm mpicomm, + int tag, + void * dest_data, + const int * dest_counts, + const void * src_data, + const int * src_counts, + size_t item_size) = p8est_transfer_items; + +template <> +Wrapper<3>::transfer_context_t * (&Wrapper<3>::transfer_items_begin)(const gloidx_t * dest_gfq, + const gloidx_t * src_gfq, + sc_MPI_Comm mpicomm, + int tag, + void * dest_data, + const int * dest_counts, + const void * src_data, + const int * src_counts, + size_t item_size) = + p8est_transfer_items_begin; + +template <> +void (&Wrapper<3>::transfer_items_end)(transfer_context_t * tc) = p8est_transfer_items_end; + +template <> +void (&Wrapper<3>::transfer_end)(transfer_context_t * tc) = p8est_transfer_end; + + +/* + * array related routines. + */ +template <> +Wrapper<3>::tree_t * (&Wrapper<3>::tree_array_index)(sc_array_t * array, + topidx_t it) = p8est_tree_array_index; + +template <> +Wrapper<3>::quadrant_t * (&Wrapper<3>::quadrant_array_index)(sc_array_t * array, size_t it) = + p8est_quadrant_array_index; + +template <> +void (&Wrapper<3>::reset_data)(forest_t * forest, + size_t data_size, + init_cb_t init_fn, + void * user_pointer) = p8est_reset_data; + +template <> +size_t (&Wrapper<3>::forest_memory_used)(forest_t * forest) = p8est_memory_used; + +template <> +size_t (&Wrapper<3>::connectivity_memory_used)(connectivity_t * conn) = + p8est_connectivity_memory_used; + +} // namespace p4est + +} // namespace kalypsso diff --git a/src/kalypsso/utils/p4est/p4est_wrapper.h b/src/kalypsso/utils/p4est/p4est_wrapper.h new file mode 100644 index 0000000..5619c08 --- /dev/null +++ b/src/kalypsso/utils/p4est/p4est_wrapper.h @@ -0,0 +1,716 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * @file p4est_wrapper.h + * @date 17 octobre 2020 + * @author pkestene + * @note + * + * Add a wrapper unifying 2D / 3D interface to p4est. + * + * This file is part of the Kalypsso software project. + * + */ +#ifndef KALYPSSO_UTILS_P4EST_P4ESTWRAPPER_H_ +#define KALYPSSO_UTILS_P4EST_P4ESTWRAPPER_H_ + +#include +#include +#include + +#include + +#include // for 2D +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // for 3D +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // for standard integral types +#include // for memset + +#if defined(KALYPSSO_CORE_USE_MPI) +# include +#else +// typedef int MPI_Comm; +#endif + + +namespace kalypsso +{ + +namespace p4est +{ + +using qcoord_t = p4est_qcoord_t; +using topidx_t = p4est_topidx_t; +using locidx_t = p4est_locidx_t; +using gloidx_t = p4est_gloidx_t; + +/** flags used in refine / coarsen callback functions */ +static constexpr int P4EST_WRAP_NONE = 0; +static constexpr int P4EST_WRAP_REFINE = 0x01; +static constexpr int P4EST_WRAP_COARSEN = 0x02; + +// quadrant routines +static inline qcoord_t +get_x(p4est_quadrant_t * quad) +{ + return quad->x; +} +static inline qcoord_t +get_y(p4est_quadrant_t * quad) +{ + return quad->y; +} +static inline qcoord_t +get_z(p4est_quadrant_t * quad) +{ + (void)(quad); + return 0; +} + +static inline qcoord_t +get_x(p8est_quadrant_t * quad) +{ + return quad->x; +} +static inline qcoord_t +get_y(p8est_quadrant_t * quad) +{ + return quad->y; +} +static inline qcoord_t +get_z(p8est_quadrant_t * quad) +{ + return quad->z; +} + +// same for independent nodes +static inline qcoord_t +get_x(p4est_indep_t * quad) +{ + return quad->x; +} +static inline qcoord_t +get_y(p4est_indep_t * quad) +{ + return quad->y; +} +static inline qcoord_t +get_z(p4est_indep_t * quad) +{ + (void)(quad); + return 0; +} + +static inline qcoord_t +get_x(p8est_indep_t * quad) +{ + return quad->x; +} +static inline qcoord_t +get_y(p8est_indep_t * quad) +{ + return quad->y; +} +static inline qcoord_t +get_z(p8est_indep_t * quad) +{ + return quad->z; +} + +/** + * A structure aimed at being specialized for 2D / 3D, and routing + * to the right p4est/p8est data/routines. + * + * Loosely adapted from Deal.II, file p4est_wrappers. + * http://www.dealii.org/ + * + * Note for later: c++14 allows the following helper templated alias: + * + * template< bool B, class T, class F > + * using conditional_t = typename conditional::type; + * + * For now, just stick to plain c++11. + * + */ +template +struct Wrapper +{ + + /** Maximum level for representing nodes, 30 in 2D and 3D since mid-2020 */ + // static constexpr int MAXLEVEL = dim == 2 ? P4EST_MAXLEVEL : P8EST_MAXLEVEL; + static constexpr int MAXLEVEL = P4EST_MAXLEVEL; + + /** Maximum level for representing quadrants, 29 in 2D and 3D since mid-2020 + */ + // static constexpr int QMAXLEVEL = dim == 2 ? P4EST_QMAXLEVEL : P8EST_QMAXLEVEL; + static constexpr int QMAXLEVEL = P4EST_QMAXLEVEL; + + /** The number of children of a quadrant also the number of corners */ + static constexpr int NB_CHILDREN = dim == 2 ? P4EST_CHILDREN : P8EST_CHILDREN; + + /** The number of children/corners touching one face */ + static constexpr int HALF = dim == 2 ? P4EST_HALF : P8EST_HALF; + + /** The number of faces per cell is 2*dim */ + static constexpr uint32_t NB_FACES = dim == 2 ? P4EST_FACES : P8EST_FACES; + + static constexpr uint32_t NB_NODES_PER_FACE = HALF; + + /** Store the corner numbers 0..3 or 0..7 for each tree face. */ + static const int face_corners[NB_FACES][NB_NODES_PER_FACE]; + + /** Store the face numbers in the face neighbor's system. */ + static const int face_dual[2 * dim]; + + // type alias + using connectivity_t = + typename std::conditional::type; + using geometry_t = typename std::conditional::type; + using forest_t = typename std::conditional::type; + using tree_t = typename std::conditional::type; + using quadrant_t = typename std::conditional::type; + using ghost_t = typename std::conditional::type; + using connect_type_t = + typename std::conditional::type; + using balance_type_t = connect_type_t; + using vtk_context_t = + typename std::conditional::type; + using forest_wrap_t = typename std::conditional::type; + + using transfer_context_t = + typename std::conditional::type; + + static const connect_type_t CONNECT_SELF; + static const connect_type_t CONNECT_FACE; + static const connect_type_t CONNECT_EDGE; + static const connect_type_t CONNECT_CORNER; + static const connect_type_t CONNECT_FULL; + + // mesh types + using mesh_t = typename std::conditional::type; + using mesh_face_neighbor_t = typename std:: + conditional::type; + + // nodes / lnodes types + using nodes_t = typename std::conditional::type; + using indep_t = typename std::conditional::type; + using lnodes_t = typename std::conditional::type; + using lnodes_code_t = + typename std::conditional::type; + using lnodes_rank_t = + typename std::conditional::type; + using lnodes_buffer_t = + typename std::conditional::type; + + // iteration types + using volume_info_t = + typename std::conditional::type; + using face_info_t = + typename std::conditional::type; + using face_corner_t = + typename std::conditional::type; + + using iter_face_side_t = + typename std::conditional::type; + + // iteration callback types + using volume_cb_t = + typename std::conditional::type; + using face_cb_t = typename std::conditional::type; + using corner_cb_t = + typename std::conditional::type; + + // callback type alias + using init_cb_t = typename std::conditional::type; + using refine_cb_t = typename std::conditional::type; + using coarsen_cb_t = typename std::conditional::type; + using weight_cb_t = typename std::conditional::type; + using replace_cb_t = typename std::conditional::type; + + static void + qcoord_to_vertex(connectivity_t * connectivity, topidx_t treeid, qcoord_t xyz[3], double vxyz[3]); + + static int (&quadrant_is_equal)(const quadrant_t * q1, const quadrant_t * q2); + + static int (&quadrant_is_ancestor)(const quadrant_t * q, const quadrant_t * r); + + static void (&quadrant_child)(const quadrant_t * q, quadrant_t * r, int child_id); + + static int (&quadrant_child_id)(const quadrant_t * q); + + // connectivity / geometry routines + static connectivity_t * (&connectivity_new_byname)(const char * name); + + // kalypsso's own connectivity by name constructor + static connectivity_t * (&my_connectivity_new_byname)(const char * name, + const ConfigMap & config_map); + + // kalypsso's own geometry by name constructor + static geometry_t * (&my_geometry_new_byname)(const char * name, + connectivity_t * conn, + const ConfigMap & config_map); + + static void (&connectivity_destroy)(connectivity_t * connectivity); + + static int (&connectivity_save)(const char * filename, connectivity_t * connectivity); + + static int (&connectivity_is_valid)(connectivity_t * connectivity); + + static connectivity_t * (&connectivity_load)(const char * filename, size_t * length); + + static connectivity_t * (&connectivity_read_inp)(const char * filename); + + static void (&geometry_connectivity_X)(geometry_t * geom, + topidx_t which_tree, + const double abc[3], + double xyz[3]); + + static void (&geometry_destroy)(geometry_t * geometry); + + // forest routines + static forest_t * (&new_forest)(sc_MPI_Comm mpicomm, + connectivity_t * connectivity, + locidx_t min_quadrants, + int min_level, + int fill_uniform, + size_t data_size, + init_cb_t init_fn, + void * user_pointer); + + static void (&destroy)(forest_t * forest); + + static forest_t * (&load)(const char * filename, + sc_MPI_Comm mpicomm, + size_t data_size, + int load_data, + void * user_pointer, + connectivity_t ** connectivity); + + static forest_t * (&load_ext)(const char * filename, + sc_MPI_Comm mpicomm, + size_t data_size, + int load_data, + int autopartition, + int broadcasthead, + void * user_pointer, + connectivity_t ** connectivity); + + static void (&refine)(forest_t * forest, + int refine_recursive, + refine_cb_t refine_fn, + init_cb_t init_fn); + + static void (&refine_ext)(forest_t * forest, + int refine_recursive, + int maxlevel, + refine_cb_t refine_fn, + init_cb_t init_fn, + replace_cb_t replace_fn); + + static void (&coarsen)(forest_t * forest, + int coarsen_recursive, + coarsen_cb_t coarsen_fn, + init_cb_t init_fn); + + static void (&coarsen_ext)(forest_t * p4est, + int coarsen_recursive, + int callback_orphans, + coarsen_cb_t coarsen_fn, + init_cb_t init_fn, + replace_cb_t replace_fn); + + static void (&balance)(forest_t * forest, connect_type_t btype, init_cb_t init_fn); + + static void (&balance_ext)(forest_t * forest, + connect_type_t btype, + init_cb_t init_fn, + replace_cb_t replace_fn); + + static void (&partition)(forest_t * forest, int allow_for_coarsening, weight_cb_t weight_fn); + + static gloidx_t (&partition_ext)(forest_t * forest, + int partition_for_coarsening, + weight_cb_t weight_fn); + + static void + iterate_volume(forest_t * forest, ghost_t * ghost, void * user_data, volume_cb_t volume_cb); + + static void (&save)(const char * filename, forest_t * forest, int save_data); + + static void (&save_ext)(const char * filename, + forest_t * forest, + int save_data, + int save_partition); + + static unsigned int (&checksum)(forest_t * forest); + + /* + * VTK related routines. + */ + + static void (&vtk_write_file)(forest_t * forest, geometry_t * geom, const char * baseName); + + static vtk_context_t * (&vtk_context_new)(forest_t * forest, const char * filename); + + static void (&vtk_context_set_geom)(vtk_context_t * cont, geometry_t * geom); + + static void (&vtk_context_set_scale)(vtk_context_t * cont, double scale); + + static void (&vtk_context_set_continuous)(vtk_context_t * cont, int continuous); + + static void (&vtk_context_destroy)(vtk_context_t * context); + + static vtk_context_t * (&vtk_write_header)(vtk_context_t * cont); + + static vtk_context_t * (&vtk_write_cell_dataf)(vtk_context_t * cont, + int write_tree, + int write_level, + int write_rank, + int wrap_rank, + int num_cell_scalars, + int num_cell_vectors, + ...); + + // static + // vtk_context_t* (&vtk_write_cell_data) (vtk_context_t * cont, + // int write_tree, + // int write_level, + // int write_rank, + // int wrap_rank, + // int num_cell_scalars, + // int num_cell_vectors, + // const char *filenames[], + // sc_array_t * values[]); + + static vtk_context_t * (&vtk_write_point_dataf)(vtk_context_t * cont, + int num_point_scalars, + int num_point_vectors, + ...); + + static int (&vtk_write_footer)(vtk_context_t * cont); + + /* + * ghost related routines. + */ + static int (&ghost_is_valid)(forest_t * forest, ghost_t * ghost); + + static size_t (&ghost_memory_used)(ghost_t * ghost); + + static ghost_t * (&ghost_new)(forest_t * forest, connect_type_t btype); + + static void (&ghost_destroy)(ghost_t * ghost); + + static void (&ghost_exchange_data)(forest_t * forest, ghost_t * ghost, void * ghost_data); + + static int (&is_balanced)(forest_t * forest, connect_type_t btype); + + static unsigned (&ghost_checksum)(forest_t * forest, ghost_t * ghost); + + /* + * mesh related routines. + */ + static mesh_t * (&mesh_new)(forest_t * p4est, ghost_t * ghost, connect_type_t btype); + + static mesh_t * (&mesh_new_ext)(forest_t * p4est, + ghost_t * ghost, + int compute_tree_index, + int compute_level_lists, + connect_type_t btype); + + static void (&mesh_destroy)(mesh_t * mesh); + + static quadrant_t * (&mesh_quadrant_cumulative)(forest_t * forest, + mesh_t * mesh, + locidx_t cumulative_id, + topidx_t * which_tree, + locidx_t * quadrant_id); + + static void (&mesh_face_neighbor_init2)(mesh_face_neighbor_t * mfn, + forest_t * forest, + ghost_t * ghost, + mesh_t * mesh, + topidx_t which_tree, + locidx_t quadrant_id); + + static quadrant_t * (&mesh_face_neighbor_next)(mesh_face_neighbor_t * mfn, + topidx_t * ntree, + locidx_t * nquad, + int * nface, + int * nrank); + + /* + * nodes / lnodes related routines + */ + static nodes_t * (&nodes_new)(forest_t * forest, ghost_t * ghost); + + /** + * \brief Create a new list of nodes without any ghost data. + * + * When there is no ghost data, p4est_nodes_new doesn't compute all the + * global information about owned nodes and offsets, so we do it here + * instead. + * + * \note this is useful only in HDF5 IO routines. + */ + static nodes_t * + nodes_new2(forest_t * forest); + + static void (&nodes_destroy)(nodes_t * nodes); + + static int (&nodes_is_valid)(forest_t * forest, nodes_t * nodes); + + static lnodes_t * (&lnodes_new)(forest_t * forest, ghost_t * ghost_layer, int degree); + + static void (&lnodes_destroy)(lnodes_t * lnodes); + + static lnodes_buffer_t * (&lnodes_share_all)(sc_array_t * node_data, lnodes_t * lnodes); + + static void (&lnodes_buffer_destroy)(lnodes_buffer_t * buffer); + + /* + * Communication / user data transfer related routines. + * Wrapper from p4est_communication.h and p8est_communication.h + */ + static void (&comm_count_quadrants)(forest_t * forest); + + static void (&comm_count_pertree)(forest_t * forest, gloidx_t * pertree); + + static void (&transfer_fixed)(const gloidx_t * dest_gfq, + const gloidx_t * src_gfq, + sc_MPI_Comm mpicomm, + int tag, + void * dest_data, + const void * src_data, + size_t data_size); + + static int (&bsearch_partition)(gloidx_t target, const gloidx_t * gfq, int nmemb); + + static transfer_context_t * (&transfer_fixed_begin)(const gloidx_t * dest_gfq, + const gloidx_t * src_gfq, + sc_MPI_Comm mpicomm, + int tag, + void * dest_data, + const void * src_data, + size_t data_size); + + static void (&transfer_fixed_end)(transfer_context_t * tc); + + static void (&transfer_custom)(const gloidx_t * dest_gfq, + const gloidx_t * src_gfq, + sc_MPI_Comm mpicomm, + int tag, + void * dest_data, + const int * dest_sizes, + const void * src_data, + const int * src_sizes); + + static transfer_context_t * (&transfer_custom_begin)(const gloidx_t * dest_gfq, + const gloidx_t * src_gfq, + sc_MPI_Comm mpicomm, + int tag, + void * dest_data, + const int * dest_sizes, + const void * src_data, + const int * src_sizes); + + static void (&transfer_custom_end)(transfer_context_t * tc); + + static void (&transfer_items)(const gloidx_t * dest_gfq, + const gloidx_t * src_gfq, + sc_MPI_Comm mpicomm, + int tag, + void * dest_data, + const int * dest_counts, + const void * src_data, + const int * src_counts, + size_t item_size); + + static transfer_context_t * (&transfer_items_begin)(const gloidx_t * dest_gfq, + const gloidx_t * src_gfq, + sc_MPI_Comm mpicomm, + int tag, + void * dest_data, + const int * dest_counts, + const void * src_data, + const int * src_counts, + size_t item_size); + + static void (&transfer_items_end)(transfer_context_t * tc); + + static void (&transfer_end)(transfer_context_t * tc); + + /* + * array related routines. TODO: check about inlining the following two. + */ + static tree_t * (&tree_array_index)(sc_array_t * array, topidx_t it); + + static quadrant_t * (&quadrant_array_index)(sc_array_t * array, size_t it); + + static void (&reset_data)(forest_t * forest, + size_t data_size, + init_cb_t init_fn, + void * user_pointer); + + static size_t (&forest_memory_used)(forest_t * forest); + + static size_t (&connectivity_memory_used)(connectivity_t * connectivity); + + static void + quadrant_reset(quadrant_t * quad) + { + if (dim == 2) + { + ((void)std::memset((quad), -1, sizeof(quadrant_t))); + } + else + { + ((void)std::memset((quad), -1, sizeof(quadrant_t))); + } + } + + /** The length of a side of the root quadrant */ + static constexpr uint32_t ROOT_LEN = (1 << MAXLEVEL); + + /** The length of a quadrant of level l */ + static inline uint32_t + QUADRANT_LEN(int level) + { + return 1 << (MAXLEVEL - (level)); + } + + /** The offset of the highest (farthest from the origin) quadrant at level l*/ + static inline int32_t + LAST_OFFSET(int level) + { + return ROOT_LEN - QUADRANT_LEN(level); + } + + /** + * \brief Compute the length of a quadrant from its level. + * + * NOTE: Copied from p4est quadrant_ext.c + */ + static double + quadrant_length_level(int level) + { + return static_cast(QUADRANT_LEN(level)) / static_cast(ROOT_LEN); + } + + /** Transform a quadrant coordinate into the space spanned by tree vertices and + * taking geometry into account. + * \param [in] connectivity Connectivity must provide the vertices. + * \param [in] geom Geometry. + * \param [in] treeid Identify the tree that contains x, y. + * \param [in] x, y, z Quadrant coordinates relative to treeid. + * \param [out] vxyz Transformed coordinates in vertex space. + * + * Note that z is not used in 2D, however vxyz may be fully populated (see e.g. + * icosahedron geometry). + */ + static void + qcoord_to_vertex_with_geom(geometry_t * geom, + topidx_t treeid, + qcoord_t x, + qcoord_t y, + qcoord_t z, + double vxyz[3]) + { + + const double intsize = 1.0 / ROOT_LEN; + double xyz_logic[3] = { intsize * x, intsize * y, intsize * z }; + + if (dim == 2) + xyz_logic[2] = 0; + + // from logical coordinates to physical coordinates + geom->X(geom, treeid, xyz_logic, vxyz); + + } // qcoord_to_vertex_with_geom + +}; // struct Wrapper + +// some template implementation +template +typename Wrapper::nodes_t * +Wrapper::nodes_new2(forest_t * forest) +{ + nodes_t * nodes = nodes_new(forest, nullptr); + int rank = forest->mpirank; + sc_MPI_Comm mpicomm = forest->mpicomm; + + KALYPSSO_DISABLE_NVCC_WARNINGS_PUSH() + nodes->global_owned_indeps = P4EST_ALLOC(locidx_t, forest->mpisize); + KALYPSSO_DISABLE_NVCC_WARNINGS_POP() + + nodes->num_owned_indeps = nodes->global_owned_indeps[rank] = + nodes->indep_nodes.elem_count + nodes->face_hangings.elem_count; + + if (dim == 3) + nodes->num_owned_indeps += nodes->edge_hangings.elem_count + + + MPI_Allgather(&(nodes->num_owned_indeps), + 1, + P4EST_MPI_LOCIDX, + nodes->global_owned_indeps, + 1, + P4EST_MPI_LOCIDX, + mpicomm); + + nodes->offset_owned_indeps = 0; + for (int i = 0; i < rank; ++i) + { + nodes->offset_owned_indeps += nodes->global_owned_indeps[i]; + } + + return nodes; + +} // Wrapper::nodes_new2 + + +} // namespace p4est + +template +using forest_t = typename p4est::Wrapper::forest_t; + +template +using tree_t = typename p4est::Wrapper::tree_t; + +template +using quadrant_t = typename p4est::Wrapper::quadrant_t; + +template +using ghost_t = typename p4est::Wrapper::ghost_t; + +template +using geometry_t = typename p4est::Wrapper::geometry_t; + +} // namespace kalypsso + +#endif // KALYPSSO_UTILS_P4EST_P4ESTWRAPPER_H_ diff --git a/src/kalypsso/utils/p4est/p4est_wrapper_old.cpp b/src/kalypsso/utils/p4est/p4est_wrapper_old.cpp new file mode 100644 index 0000000..7cb443b --- /dev/null +++ b/src/kalypsso/utils/p4est/p4est_wrapper_old.cpp @@ -0,0 +1,321 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "p4est_wrapper_old.h" + +#include + +namespace kalypsso +{ + +namespace p4est +{ + +/* + * Static reference function member initialization for Wrapper<2>. + */ +int (&Wrapper<2>::quadrant_is_equal)(const quadrant_t * q1, + const quadrant_t * q2) = p4est_quadrant_is_equal; + +int (&Wrapper<2>::quadrant_is_ancestor)(const quadrant_t * q, + const quadrant_t * r) = p4est_quadrant_is_ancestor; + +void (&Wrapper<2>::quadrant_child)(const quadrant_t * q, + quadrant_t * r, + int child_id) = p4est_quadrant_child; + +int (&Wrapper<2>::quadrant_child_id)(const quadrant_t * q) = p4est_quadrant_child_id; + +void (&Wrapper<2>::connectivity_destroy)(connectivity_t * connectivity) = + p4est_connectivity_destroy; + +int (&Wrapper<2>::connectivity_save)(const char * filename, + Wrapper<2>::connectivity_t * connectivity) = + p4est_connectivity_save; + +int (&Wrapper<2>::connectivity_is_valid)(Wrapper<2>::connectivity_t * connectivity) = + p4est_connectivity_is_valid; + +Wrapper<2>::connectivity_t * ( + &Wrapper<2>::connectivity_load)(const char * filename, size_t * length) = p4est_connectivity_load; + +void (&Wrapper<2>::geometry_destroy)(geometry_t * geometry) = p4est_geometry_destroy; + +Wrapper<2>::forest_t * (&Wrapper<2>::new_forest)(MPI_Comm mpicomm, + connectivity_t * connectivity, + locidx_t min_quadrants, + int min_level, + int fill_uniform, + size_t data_size, + init_cb_t init_fn, + void * user_pointer) = p4est_new_ext; + +void (&Wrapper<2>::destroy)(Wrapper<2>::forest_t * forest) = p4est_destroy; + +void (&Wrapper<2>::refine)(Wrapper<2>::forest_t * forest, + int refine_recursive, + refine_cb_t refine_fn, + init_cb_t init_fn) = p4est_refine; + +void (&Wrapper<2>::coarsen)(Wrapper<2>::forest_t * forest, + int coarsen_recursive, + coarsen_cb_t coarsen_fn, + init_cb_t init_fn) = p4est_coarsen; + +void (&Wrapper<2>::balance)(Wrapper<2>::forest_t * forest, + Wrapper<2>::balance_type_t btype, + init_cb_t init_fn) = p4est_balance; + + +Wrapper<2>::gloidx_t (&Wrapper<2>::partition)(Wrapper<2>::forest_t * forest, + int partition_for_coarsening, + weight_cb_t weight_fn) = p4est_partition_ext; + +void +Wrapper<2>::iterate_volume(forest_t * forest, void * user_data, volume_cb_t volume_cb) +{ + + p4est_iterate(forest, + nullptr, // ghost layer + user_data, + volume_cb, + nullptr, // iter_face, + nullptr // iter_corner + ); + +} // Wrapper<2>::iterate_volume + +void (&Wrapper<2>::save)(const char * filename, + Wrapper<2>::forest_t * forest, + int save_data) = p4est_save; + +Wrapper<2>::forest_t * (&Wrapper<2>::load_ext)(const char * filename, + MPI_Comm mpicomm, + std::size_t data_size, + int load_data, + int autopartition, + int broadcasthead, + void * user_pointer, + Wrapper<2>::connectivity_t ** connectivity) = + p4est_load_ext; + +unsigned int (&Wrapper<2>::checksum)(Wrapper<2>::forest_t * forest) = p4est_checksum; + +void (&Wrapper<2>::vtk_write_file)(Wrapper<2>::forest_t * forest, + Wrapper<2>::geometry_t * geometry, + const char * baseName) = p4est_vtk_write_file; + +Wrapper<2>::vtk_context_t * ( + &Wrapper<2>::vtk_context_new)(forest_t * forest, const char * filename) = p4est_vtk_context_new; + +void (&Wrapper<2>::vtk_context_set_geom)(vtk_context_t * cont, + geometry_t * geom) = p4est_vtk_context_set_geom; + +void (&Wrapper<2>::vtk_context_set_scale)(vtk_context_t * cont, + double scale) = p4est_vtk_context_set_scale; + +void (&Wrapper<2>::vtk_context_set_continuous)(vtk_context_t * cont, + int continuous) = p4est_vtk_context_set_continuous; + +void (&Wrapper<2>::vtk_context_destroy)(vtk_context_t * context) = p4est_vtk_context_destroy; + +Wrapper<2>::vtk_context_t * (&Wrapper<2>::vtk_write_header)(vtk_context_t * cont) = + p4est_vtk_write_header; + +Wrapper<2>::vtk_context_t * (&Wrapper<2>::vtk_write_cell_dataf)(vtk_context_t * cont, + int write_tree, + int write_level, + int write_rank, + int wrap_rank, + int num_cell_scalars, + int num_cell_vectors, + ...) = p4est_vtk_write_cell_dataf; + +Wrapper<2>::vtk_context_t * (&Wrapper<2>::vtk_write_point_dataf)(vtk_context_t * cont, + int num_point_scalars, + int num_point_vectors, + ...) = p4est_vtk_write_point_dataf; + +int (&Wrapper<2>::vtk_write_footer)(vtk_context_t * cont) = p4est_vtk_write_footer; + +Wrapper<2>::ghost_t * (&Wrapper<2>::ghost_new)(Wrapper<2>::forest_t * forest, + Wrapper<2>::balance_type_t btype) = p4est_ghost_new; + +void (&Wrapper<2>::ghost_destroy)(Wrapper<2>::ghost_t * ghost) = p4est_ghost_destroy; + +void (&Wrapper<2>::reset_data)(Wrapper<2>::forest_t * forest, + size_t data_size, + init_cb_t init_fn, + void * user_pointer) = p4est_reset_data; + +size_t (&Wrapper<2>::forest_memory_used)(Wrapper<2>::forest_t * forest) = p4est_memory_used; + +size_t (&Wrapper<2>::connectivity_memory_used)(Wrapper<2>::connectivity_t * conn) = + p4est_connectivity_memory_used; + + +/* + * Static reference function member initialization for Wrapper<3>. + */ +int (&Wrapper<3>::quadrant_is_equal)(const quadrant_t * q1, + const quadrant_t * q2) = p8est_quadrant_is_equal; + +int (&Wrapper<3>::quadrant_is_ancestor)(const quadrant_t * q, + const quadrant_t * r) = p8est_quadrant_is_ancestor; + +void (&Wrapper<3>::quadrant_child)(const quadrant_t * q, + quadrant_t * r, + int child_id) = p8est_quadrant_child; + +int (&Wrapper<3>::quadrant_child_id)(const quadrant_t * q) = p8est_quadrant_child_id; + +void (&Wrapper<3>::connectivity_destroy)(connectivity_t * connectivity) = + p8est_connectivity_destroy; + +int (&Wrapper<3>::connectivity_save)(const char * filename, + Wrapper<3>::connectivity_t * connectivity) = + p8est_connectivity_save; + +int (&Wrapper<3>::connectivity_is_valid)(Wrapper<3>::connectivity_t * connectivity) = + p8est_connectivity_is_valid; + +Wrapper<3>::connectivity_t * ( + &Wrapper<3>::connectivity_load)(const char * filename, size_t * length) = p8est_connectivity_load; + +void (&Wrapper<3>::geometry_destroy)(geometry_t * geometry) = p8est_geometry_destroy; + +Wrapper<3>::forest_t * (&Wrapper<3>::new_forest)(MPI_Comm mpicomm, + connectivity_t * connectivity, + locidx_t min_quadrants, + int min_level, + int fill_uniform, + size_t data_size, + init_cb_t init_fn, + void * user_pointer) = p8est_new_ext; + +void (&Wrapper<3>::destroy)(Wrapper<3>::forest_t * forest) = p8est_destroy; + +void (&Wrapper<3>::refine)(Wrapper<3>::forest_t * forest, + int refine_recursive, + refine_cb_t refine_fn, + init_cb_t init_fn) = p8est_refine; + +void (&Wrapper<3>::coarsen)(Wrapper<3>::forest_t * forest, + int coarsen_recursive, + coarsen_cb_t coarsen_fn, + init_cb_t init_fn) = p8est_coarsen; + +void (&Wrapper<3>::balance)(Wrapper<3>::forest_t * forest, + Wrapper<3>::balance_type_t btype, + init_cb_t init_fn) = p8est_balance; + + +Wrapper<3>::gloidx_t (&Wrapper<3>::partition)(Wrapper<3>::forest_t * forest, + int partition_for_coarsening, + weight_cb_t weight_fn) = p8est_partition_ext; + +void +Wrapper<3>::iterate_volume(forest_t * forest, void * user_data, volume_cb_t volume_cb) +{ + + p8est_iterate(forest, + nullptr, // ghost layer + user_data, + volume_cb, + nullptr, // iter_face + nullptr, // iter_edge + nullptr // iter_corner + ); + +} // Wrapper<3>::iterate_volume + +void (&Wrapper<3>::save)(const char * filename, + Wrapper<3>::forest_t * forest, + int save_data) = p8est_save; + +Wrapper<3>::forest_t * (&Wrapper<3>::load_ext)(const char * filename, + MPI_Comm mpicomm, + std::size_t data_size, + int load_data, + int autopartition, + int broadcasthead, + void * user_pointer, + Wrapper<3>::connectivity_t ** connectivity) = + p8est_load_ext; + +unsigned int (&Wrapper<3>::checksum)(Wrapper<3>::forest_t * forest) = p8est_checksum; + +void (&Wrapper<3>::vtk_write_file)(Wrapper<3>::forest_t * forest, + Wrapper<3>::geometry_t * geometry, + const char * baseName) = p8est_vtk_write_file; + +Wrapper<3>::vtk_context_t * ( + &Wrapper<3>::vtk_context_new)(forest_t * forest, const char * filename) = p8est_vtk_context_new; + +void (&Wrapper<3>::vtk_context_set_geom)(vtk_context_t * cont, + geometry_t * geom) = p8est_vtk_context_set_geom; + +void (&Wrapper<3>::vtk_context_set_scale)(vtk_context_t * cont, + double scale) = p8est_vtk_context_set_scale; + +void (&Wrapper<3>::vtk_context_set_continuous)(vtk_context_t * cont, + int continuous) = p8est_vtk_context_set_continuous; + +void (&Wrapper<3>::vtk_context_destroy)(vtk_context_t * context) = p8est_vtk_context_destroy; + +Wrapper<3>::vtk_context_t * (&Wrapper<3>::vtk_write_header)(vtk_context_t * cont) = + p8est_vtk_write_header; + +Wrapper<3>::vtk_context_t * (&Wrapper<3>::vtk_write_cell_dataf)(vtk_context_t * cont, + int write_tree, + int write_level, + int write_rank, + int wrap_rank, + int num_cell_scalars, + int num_cell_vectors, + ...) = p8est_vtk_write_cell_dataf; + +Wrapper<3>::vtk_context_t * (&Wrapper<3>::vtk_write_point_dataf)(vtk_context_t * cont, + int num_point_scalars, + int num_point_vectors, + ...) = p8est_vtk_write_point_dataf; + +int (&Wrapper<3>::vtk_write_footer)(vtk_context_t * cont) = p8est_vtk_write_footer; + +Wrapper<3>::ghost_t * (&Wrapper<3>::ghost_new)(Wrapper<3>::forest_t * forest, + Wrapper<3>::balance_type_t btype) = p8est_ghost_new; + +void (&Wrapper<3>::ghost_destroy)(Wrapper<3>::ghost_t * ghost) = p8est_ghost_destroy; + +void (&Wrapper<3>::reset_data)(Wrapper<3>::forest_t * forest, + size_t data_size, + init_cb_t init_fn, + void * user_pointer) = p8est_reset_data; + +size_t (&Wrapper<3>::forest_memory_used)(Wrapper<3>::forest_t * forest) = p8est_memory_used; + +size_t (&Wrapper<3>::connectivity_memory_used)(Wrapper<3>::connectivity_t * conn) = + p8est_connectivity_memory_used; + +/* ============================================== */ +/* ============================================== */ +void +Wrapper<2>::quadrant_reset(quadrant_t * quad) +{ + + ((void)std::memset((quad), -1, sizeof(quadrant_t))); + +} // Wrapper<2>::quadrant_reset(quadrant_t *quad) + +void +Wrapper<3>::quadrant_reset(quadrant_t * quad) +{ + + ((void)std::memset((quad), -1, sizeof(quadrant_t))); + +} // Wrapper<3>::quadrant_reset(quadrant_t *quad) + +} // namespace p4est + +} // namespace kalypsso diff --git a/src/kalypsso/utils/p4est/p4est_wrapper_old.h b/src/kalypsso/utils/p4est/p4est_wrapper_old.h new file mode 100644 index 0000000..b4d11ef --- /dev/null +++ b/src/kalypsso/utils/p4est/p4est_wrapper_old.h @@ -0,0 +1,413 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef P4EST_WRAPPERS_OLD_H_ +#define P4EST_WRAPPERS_OLD_H_ + +#include // for 2D +#include +#include +#include +#include + +#include // for 3D +#include +#include +#include +#include + +// mapping between logical and physical space +#include +#include + +#include + +#if defined(USE_MPI) +# include +#else +typedef int MPI_Comm; +#endif + + +namespace kalypsso +{ + +namespace p4est +{ + +/** + * A structure aimed at being specialized for 2D / 3D, and routing + * to the right p4est/p8est data/routines. + * + * Loosely adapted from Deal.II, file p4est_wrappers. + * http://www.dealii.org/ + */ +template +struct Wrapper; + +/* 2D specialization */ +template <> +struct Wrapper<2> +{ + + static constexpr int MAXLEVEL = 30; + static constexpr int QMAXLEVEL = 29; + + // type alias + using connectivity_t = p4est_connectivity_t; + using geometry_t = p4est_geometry_t; + using forest_t = p4est_t; + using tree_t = p4est_tree_t; + using quadrant_t = p4est_quadrant_t; + using qcoord_t = p4est_qcoord_t; + using topidx_t = p4est_topidx_t; + using locidx_t = p4est_locidx_t; + using gloidx_t = p4est_gloidx_t; + using ghost_t = p4est_ghost_t; + using balance_type_t = p4est_connect_type_t; + using vtk_context_t = p4est_vtk_context_t; + + // iteration types + using volume_info_t = p4est_iter_volume_info_t; + using face_info_t = p4est_iter_face_info_t; + using face_corner_t = p4est_iter_corner_info_t; + + // iteration callback types + using volume_cb_t = p4est_iter_volume_t; + using face_cb_t = p4est_iter_face_t; + using corner_cb_t = p4est_iter_corner_t; + + // callback type alias + using init_cb_t = p4est_init_t; + using refine_cb_t = p4est_refine_t; + using coarsen_cb_t = p4est_coarsen_t; + using weight_cb_t = p4est_weight_t; + + // quadrant routines + static inline qcoord_t + get_x(quadrant_t * quad) + { + return quad->x; + }; + static inline qcoord_t + get_y(quadrant_t * quad) + { + return quad->y; + }; + static inline qcoord_t + get_z(quadrant_t * quad) + { + return 0.0; + }; + + static inline void + qcoord_to_vertex(connectivity_t * connectivity, topidx_t treeid, qcoord_t xyz[3], double vxyz[3]) + { + p4est_qcoord_to_vertex(connectivity, treeid, xyz[0], xyz[1], vxyz); + }; + + static int (&quadrant_is_equal)(const quadrant_t * q1, const quadrant_t * q2); + + static int (&quadrant_is_ancestor)(const quadrant_t * q, const quadrant_t * r); + + static void (&quadrant_child)(const quadrant_t * q, quadrant_t * r, int child_id); + + static int (&quadrant_child_id)(const quadrant_t * q); + + // connectivity / geometry routines + static void (&connectivity_destroy)(connectivity_t * connectivity); + + static int (&connectivity_save)(const char * filename, connectivity_t * connectivity); + static int (&connectivity_is_valid)(connectivity_t * connectivity); + + static connectivity_t * (&connectivity_load)(const char * filename, size_t * length); + + static void (&geometry_destroy)(geometry_t * geometry); + + // forest routines + static forest_t * (&new_forest)(MPI_Comm mpicomm, + connectivity_t * connectivity, + locidx_t min_quadrants, + int min_level, + int fill_uniform, + size_t data_size, + init_cb_t init_fn, + void * user_pointer); + + static void (&destroy)(forest_t * forest); + + static void (&refine)(forest_t * forest, + int refine_recursive, + refine_cb_t refine_fn, + init_cb_t init_fn); + + static void (&coarsen)(forest_t * forest, + int coarsen_recursive, + coarsen_cb_t coarsen_fn, + init_cb_t init_fn); + + static void (&balance)(forest_t * forest, balance_type_t btype, init_cb_t init_fn); + + static gloidx_t (&partition)(forest_t * forest, + int partition_for_coarsening, + weight_cb_t weight_fn); + + static void + iterate_volume(forest_t * forest, void * user_data, volume_cb_t volume_cb); + + + static void (&save)(const char * filename, forest_t * forest, int save_data); + + static forest_t * (&load_ext)(const char * filename, + MPI_Comm mpicomm, + size_t data_size, + int load_data, + int autopartition, + int broadcasthead, + void * user_pointer, + connectivity_t ** connectivity); + + + static unsigned int (&checksum)(forest_t * forest); + + static void (&vtk_write_file)(forest_t * forest, geometry_t * geom, const char * baseName); + + static vtk_context_t * (&vtk_context_new)(forest_t * forest, const char * filename); + + static void (&vtk_context_set_geom)(vtk_context_t * cont, geometry_t * geom); + + static void (&vtk_context_set_scale)(vtk_context_t * cont, double scale); + + static void (&vtk_context_set_continuous)(vtk_context_t * cont, int continuous); + + static void (&vtk_context_destroy)(vtk_context_t * context); + + static vtk_context_t * (&vtk_write_header)(vtk_context_t * cont); + + static vtk_context_t * (&vtk_write_cell_dataf)(vtk_context_t * cont, + int write_tree, + int write_level, + int write_rank, + int wrap_rank, + int num_cell_scalars, + int num_cell_vectors, + ...); + + static vtk_context_t * (&vtk_write_point_dataf)(vtk_context_t * cont, + int num_point_scalars, + int num_point_vectors, + ...); + + static int (&vtk_write_footer)(vtk_context_t * cont); + + static ghost_t * (&ghost_new)(forest_t * forest, balance_type_t btype); + + static void (&ghost_destroy)(ghost_t * ghost); + + static void (&reset_data)(forest_t * forest, + size_t data_size, + init_cb_t init_fn, + void * user_pointer); + + static size_t (&forest_memory_used)(forest_t * forest); + + static size_t (&connectivity_memory_used)(connectivity_t * connectivity); + + static void + quadrant_reset(quadrant_t * quad); + + static constexpr uint32_t ROOT_LEN = ((qcoord_t)1 << MAXLEVEL); + + static inline uint32_t + QUADRANT_LEN(int level) + { + return (qcoord_t)1 << (MAXLEVEL - (level)); + } + +}; // struct Wrapper<2> + +/* 3D specialization */ +template <> +struct Wrapper<3> +{ + + static constexpr int MAXLEVEL = 19; + static constexpr int QMAXLEVEL = 18; + + // type alias + using connectivity_t = p8est_connectivity_t; + using geometry_t = p8est_geometry_t; + using forest_t = p8est_t; + using tree_t = p8est_tree_t; + using quadrant_t = p8est_quadrant_t; + using qcoord_t = p4est_qcoord_t; /* */ + using topidx_t = p4est_topidx_t; /* */ + using locidx_t = p4est_locidx_t; /* */ + using gloidx_t = p4est_gloidx_t; /* */ + using ghost_t = p8est_ghost_t; + using balance_type_t = p8est_connect_type_t; + using vtk_context_t = p8est_vtk_context_t; + + // iteration types + using volume_info_t = p8est_iter_volume_info_t; + using face_info_t = p8est_iter_face_info_t; + using face_corner_t = p8est_iter_corner_info_t; + + // iteration callback types + using volume_cb_t = p8est_iter_volume_t; + using face_cb_t = p8est_iter_face_t; + using corner_cb_t = p8est_iter_corner_t; + + // callback type alias + using init_cb_t = p8est_init_t; + using refine_cb_t = p8est_refine_t; + using coarsen_cb_t = p8est_coarsen_t; + using weight_cb_t = p8est_weight_t; + + // quadrant routines + static inline qcoord_t + get_x(quadrant_t * quad) + { + return quad->x; + }; + static inline qcoord_t + get_y(quadrant_t * quad) + { + return quad->y; + }; + static inline qcoord_t + get_z(quadrant_t * quad) + { + return quad->z; + }; + + static inline void + qcoord_to_vertex(connectivity_t * connectivity, topidx_t treeid, qcoord_t xyz[3], double vxyz[3]) + { + p8est_qcoord_to_vertex(connectivity, treeid, xyz[0], xyz[1], xyz[2], vxyz); + } + + static int (&quadrant_is_equal)(const quadrant_t * q1, const quadrant_t * q2); + + static int (&quadrant_is_ancestor)(const quadrant_t * q, const quadrant_t * r); + + static void (&quadrant_child)(const quadrant_t * q, quadrant_t * r, int child_id); + + static int (&quadrant_child_id)(const quadrant_t * q); + + // connectivity / geometry routines + static void (&connectivity_destroy)(connectivity_t * connectivity); + + static int (&connectivity_save)(const char * filename, connectivity_t * connectivity); + static int (&connectivity_is_valid)(connectivity_t * connectivity); + + static connectivity_t * (&connectivity_load)(const char * filename, size_t * length); + + static void (&geometry_destroy)(geometry_t * geometry); + + // forest routines + static forest_t * (&new_forest)(MPI_Comm mpicomm, + connectivity_t * connectivity, + locidx_t min_quadrants, + int min_level, + int fill_uniform, + size_t data_size, + init_cb_t init_fn, + void * user_pointer); + + static void (&destroy)(forest_t * forest); + + static void (&refine)(forest_t * forest, + int refine_recursive, + refine_cb_t refine_fn, + init_cb_t init_fn); + + static void (&coarsen)(forest_t * forest, + int coarsen_recursive, + coarsen_cb_t coarsen_fn, + init_cb_t init_fn); + + static void (&balance)(forest_t * forest, balance_type_t btype, init_cb_t init_fn); + + static gloidx_t (&partition)(forest_t * forest, + int partition_for_coarsening, + weight_cb_t weight_fn); + + static void + iterate_volume(forest_t * forest, void * user_data, volume_cb_t volume_cb); + + static void (&save)(const char * filename, forest_t * forest, int save_data); + + static forest_t * (&load_ext)(const char * filename, + MPI_Comm mpicomm, + size_t data_size, + int load_data, + int autopartition, + int broadcasthead, + void * user_pointer, + connectivity_t ** connectivity); + + static unsigned int (&checksum)(forest_t * forest); + + static void (&vtk_write_file)(forest_t * forest, geometry_t * geom, const char * baseName); + + static vtk_context_t * (&vtk_context_new)(forest_t * forest, const char * filename); + + static void (&vtk_context_set_geom)(vtk_context_t * cont, geometry_t * geom); + + static void (&vtk_context_set_scale)(vtk_context_t * cont, double scale); + + static void (&vtk_context_set_continuous)(vtk_context_t * cont, int continuous); + + static void (&vtk_context_destroy)(vtk_context_t * context); + + static vtk_context_t * (&vtk_write_header)(vtk_context_t * cont); + + static vtk_context_t * (&vtk_write_cell_dataf)(vtk_context_t * cont, + int write_tree, + int write_level, + int write_rank, + int wrap_rank, + int num_cell_scalars, + int num_cell_vectors, + ...); + + static vtk_context_t * (&vtk_write_point_dataf)(vtk_context_t * cont, + int num_point_scalars, + int num_point_vectors, + ...); + + static int (&vtk_write_footer)(vtk_context_t * cont); + + static ghost_t * (&ghost_new)(forest_t * forest, balance_type_t btype); + + static void (&ghost_destroy)(ghost_t * ghost); + + static void (&reset_data)(forest_t * forest, + size_t data_size, + init_cb_t init_fn, + void * user_pointer); + + static size_t (&forest_memory_used)(forest_t * forest); + + static size_t (&connectivity_memory_used)(connectivity_t * connectivity); + + static void + quadrant_reset(quadrant_t * quad); + + static constexpr uint32_t ROOT_LEN = ((qcoord_t)1 << MAXLEVEL); + + static inline uint32_t + QUADRANT_LEN(int level) + { + return (qcoord_t)1 << (MAXLEVEL - (level)); + } + +}; // struct Wrapper<3> + +} // namespace p4est + +} // namespace kalypsso + +#endif // P4EST_WRAPPERS_OLD_H_ diff --git a/src/kalypsso_core_version.h.in b/src/kalypsso_core_version.h.in new file mode 100644 index 0000000..84671d5 --- /dev/null +++ b/src/kalypsso_core_version.h.in @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * @file Kalypsso_version.h + * + * @brief CMake generated version information + * + * This file is automatically generated by the CMake build process. + * It should not be edited directly. + */ +#ifndef KALYPSSO_CORE_VERSION_H_ +#define KALYPSSO_CORE_VERSION_H_ + +#cmakedefine KALYPSSO_CORE_HAS_GIT_INFO + +#define KALYPSSO_CORE_VERSION "@KALYPSSO_CORE_VERSION@" + +// clang-format off +#define KALYPSSO_CORE_GIT_TAG "@KALYPSSO_CORE_GIT_TAG@" +#define KALYPSSO_CORE_GIT_HASH "@KALYPSSO_CORE_GIT_HASH@" +#define KALYPSSO_CORE_GIT_HEAD "@KALYPSSO_CORE_GIT_HEAD@" +#define KALYPSSO_CORE_GIT_HAS_LOCAL_CHANGES "@KALYPSSO_CORE_GIT_HAS_LOCAL_CHANGES@" +#define KALYPSSO_CORE_GIT_IS_CLEAN @KALYPSSO_CORE_GIT_IS_CLEAN@ +#define KALYPSSO_CORE_GIT_REMOTE_URL "@KALYPSSO_CORE_GIT_REMOTE_URL@" +#define KALYPSSO_CORE_GIT_BRANCH "@KALYPSSO_CORE_GIT_BRANCH@" + +#define KALYPSSO_CORE_COMPILE_DATE "@KALYPSSO_CORE_COMPILE_DATE@" +#define KALYPSSO_CORE_COMPILE_TIME "@KALYPSSO_CORE_COMPILE_TIME@" +// clang-format on + +#endif // KALYPSSO_CORE_VERSION_H_ diff --git a/test/AMRkey/CMakeLists.txt b/test/AMRkey/CMakeLists.txt new file mode 100644 index 0000000..bca6a68 --- /dev/null +++ b/test/AMRkey/CMakeLists.txt @@ -0,0 +1,27 @@ +add_executable(test_orchard test_orchard.cpp) + +target_link_libraries(test_orchard PUBLIC kalypsso::core kalypsso::p4est_wrap kalypsso::config) + +add_test(NAME orchard COMMAND test_orchard) +set_property(TEST orchard PROPERTY LABELS AMRkey) + +if(Kokkos_ENABLE_OPENMP) + set_property(TEST orchard PROPERTY ENVIRONMENT OMP_NUM_THREADS=${KALYPSSO_TEST_OMP_NUM_THREADS} + OMP_PROC_BIND=spread OMP_PLACES=threads) +endif() + +# +# this is not a test, just a utility to learn how orchard keys work +# +find_package(Boost COMPONENTS program_options) +if(Boost_FOUND) + + add_executable(decode_orchard decode_orchard.cpp) + + target_link_libraries(decode_orchard PUBLIC kalypsso::core Boost::program_options) + + add_executable(encode_orchard encode_orchard.cpp) + + target_link_libraries(encode_orchard PUBLIC kalypsso::core Boost::program_options) + +endif() diff --git a/test/AMRkey/decode_orchard.cpp b/test/AMRkey/decode_orchard.cpp new file mode 100644 index 0000000..2b31097 --- /dev/null +++ b/test/AMRkey/decode_orchard.cpp @@ -0,0 +1,132 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file decode_orchard.cpp + * + */ +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace kalypsso +{ + +// ================================================================================ +// ================================================================================ +template +void +decode_orchard_key(uint64_t key) +{ + + std::cout << "Decode a " << dim << "D orchard key:\n"; + + std::cout << "key : " << key << "\n"; + + std::cout << "level : " << (uint)orchard_key_t::level(key) << "\n"; + + std::cout << "tree id : " << orchard_key_t::morton_tree(key) << "\n"; + + // clang-format off + if constexpr (dim == 2) + std::cout << "tree coord : " + << orchard_key_t::template get_tree_coord(key) << " " + << orchard_key_t::template get_tree_coord(key) << "\n"; + else if constexpr (dim == 3) + std::cout << "tree coord : " + << orchard_key_t::template get_tree_coord(key) << " " + << orchard_key_t::template get_tree_coord(key) << " " + << orchard_key_t::template get_tree_coord(key) << "\n"; + // clang-format on + + std::cout << "oct id : " << orchard_key_t::morton_octant(key) << "\n"; + + // real space lower left corner of leaf block identified by key + auto vertex_coord = orchard_key_to_vertex_coord(key, false); + + // clang-format off + if constexpr (dim == 2) + { + std::cout << "oct coords (Logical space) : " + << orchard_key_t::template get_octant_coord(key) << " " + << orchard_key_t::template get_octant_coord(key) << "\n" + ; + std::cout << "oct coords (Vertex space) : " + << vertex_coord[IX] << " " + << vertex_coord[IY] << "\n"; + } + else if constexpr (dim == 3) + { + std::cout << "oct coords (Logical space) : " + << orchard_key_t::template get_octant_coord(key) << " " + << orchard_key_t::template get_octant_coord(key) << " " + << orchard_key_t::template get_octant_coord(key) << "\n" + ; + std::cout << "oct coords (Vertex space) : " + << vertex_coord[IX] << " " + << vertex_coord[IY] << " " + << vertex_coord[IZ] << "\n"; + } + // clang-format on + +} // decode_orchard_key + +} // namespace kalypsso + +// ================================================================================ +// ================================================================================ +// ================================================================================ +int +main(int argc, char * argv[]) +{ + namespace po = boost::program_options; + + bool dim3 = false; + + po::options_description desc("Allowed options"); + + // clang-format off + desc.add_options() + ("help", "produce help message") + ("3d", po::bool_switch(&dim3)->default_value(false), "decode a 3d key") + ("key", po::value()->default_value(0), "key to decode") + ; + // clang-format-on + + // Positional arguments don't need a parameter flag + po::positional_options_description pos_desc; + pos_desc.add("key", 1); + + po::variables_map vm; + + try{ + po::store( + po::command_line_parser(argc, argv).options(desc).positional(pos_desc).run(), vm); + po::notify(vm); + } catch(po::error & e) { + std::cout << "ERROR: " << e.what() << "\n"; + return EXIT_FAILURE; + } + + if (vm.count("help")) { + std::cout << desc << std::endl; + return EXIT_SUCCESS; + } + + auto key { vm["key"].as() }; + + if (dim3) + kalypsso::decode_orchard_key<3>(key); + else + kalypsso::decode_orchard_key<2>(key); + + return EXIT_SUCCESS; + +} // main diff --git a/test/AMRkey/encode_orchard.cpp b/test/AMRkey/encode_orchard.cpp new file mode 100644 index 0000000..5259387 --- /dev/null +++ b/test/AMRkey/encode_orchard.cpp @@ -0,0 +1,174 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file decode_orchard.cpp + * + */ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace kalypsso +{ + +using tree_coord_v_t = std::vector; +using tree_coord_t = std::array; +using oct_coord_v_t = std::vector; +using oct_coord_t = std::array; + +// ================================================================================ +// ================================================================================ +template +void +encode_orchard_key(tree_coord_t tree_coord, + oct_coord_t oct_coord, + uint8_t level, + Kokkos ::Array outside_status) +{ + + std::cout << "Encode a " << dim << "D orchard key:\n"; + + Kokkos::Array tree_coord_kokkos; + tree_coord_kokkos[0] = tree_coord[0]; + tree_coord_kokkos[1] = tree_coord[1]; + if constexpr (dim == 3) + tree_coord_kokkos[2] = tree_coord[2]; + + Kokkos::Array oct_coord_kokkos; + oct_coord_kokkos[0] = oct_coord[0]; + oct_coord_kokkos[1] = oct_coord[1]; + if constexpr (dim == 3) + oct_coord_kokkos[2] = oct_coord[2]; + + std::cout << "root length : " << orchard_key_t::ROOT_LENGTH << " : 0x" << std::hex + << orchard_key_t::ROOT_LENGTH << std::dec << "\n"; + + auto key = orchard_key_t::encode_orchard(tree_coord_kokkos, oct_coord_kokkos, level); + + orchard_key_t::set_is_touching_face_X(key, outside_status[IX]); + orchard_key_t::set_is_touching_face_Y(key, outside_status[IY]); + if constexpr (dim == 3) + orchard_key_t::set_is_touching_face_Z(key, outside_status[IZ]); + + if constexpr (dim == 2) + { + // clang-format off + std::cout << "tree coord : " + << tree_coord_kokkos[0] << " " + << tree_coord_kokkos[1] << "\n"; + std::cout << "oct coord (input) : " + << oct_coord_kokkos[0] << " " + << oct_coord_kokkos[1] << "\n"; + std::cout << "oct coords (encoded) : " + << orchard_key_t::template get_octant_coord(key) << " " + << orchard_key_t::template get_octant_coord(key) << "\n"; + // clang-format on + } + else + { + // clang-format off + std::cout << "tree coord : " + << tree_coord_kokkos[0] << " " + << tree_coord_kokkos[1] << " " + << tree_coord_kokkos[2] << "\n"; + std::cout << "oct coord (input) : " + << oct_coord_kokkos[0] << " " + << oct_coord_kokkos[1] << " " + << oct_coord_kokkos[2] << "\n"; + std::cout << "oct coords (Logical space) : " + << orchard_key_t::template get_octant_coord(key) << " " + << orchard_key_t::template get_octant_coord(key) << " " + << orchard_key_t::template get_octant_coord(key) << "\n"; + // clang-format on + } + std::cout << "level : " << (int)level << "\n"; + + + std::cout << "key : " << key << "\n"; + std::cout << "key (hex) : 0x" << std::hex << key << std::dec << "\n"; + +} // encode_orchard_key + +} // namespace kalypsso + +// ================================================================================ +// ================================================================================ +// ================================================================================ +int +main(int argc, char * argv[]) +{ + using tree_coord_v_t = kalypsso::tree_coord_v_t; + using tree_coord_t = kalypsso::tree_coord_t; + using oct_coord_v_t = kalypsso::oct_coord_v_t; + using oct_coord_t = kalypsso::oct_coord_t; + + namespace po = boost::program_options; + + bool dim3 = false; + tree_coord_v_t tree_coord{ 0, 0, 0 }; + oct_coord_v_t oct_coord{ 0, 0, 0 }; + uint16_t level_u16{ 0 }; + bool is_outside_x = false; + bool is_outside_y = false; + bool is_outside_z = false; + + po::options_description desc("Allowed options"); + + // clang-format off + desc.add_options() + ("help", "produce help message") + ("3d", po::bool_switch(&dim3)->default_value(false), "decode a 3d key") + ("tree_coord", po::value(&tree_coord)->multitoken(), "set tree coords") + ("oct_coord", po::value(&oct_coord)->multitoken(), "set octant coords") + ("level", po::value(&level_u16), "set leaf quadrant level") + ("is_outside_x", po::bool_switch(&is_outside_x)->default_value(false), "leaf quadrant is touching a X face") + ("is_outside_y", po::bool_switch(&is_outside_y)->default_value(false), "leaf quadrant is touching a Y face") + ("is_outside_z", po::bool_switch(&is_outside_z)->default_value(false), "leaf quadrant is touching a Z face") + ; + // clang-format-on + + po::variables_map vm; + + try{ + po::store( + po::command_line_parser(argc, argv).options(desc).run(), vm); + po::notify(vm); + } catch(po::error & e) { + std::cout << "ERROR: " << e.what() << "\n"; + return EXIT_FAILURE; + } + + if (vm.count("help")) { + std::cout << desc << std::endl; + std::cout << "Example: ./encode_orchard --level 5 --oct_coord $((2**4)) $((2**4)) \n"; + return EXIT_SUCCESS; + } + + tree_coord_t tree_coord_array; + std::copy_n(tree_coord.begin(), 3, tree_coord_array.begin()); + oct_coord_t oct_coord_array; + std::copy_n(oct_coord.begin(), 3, oct_coord_array.begin()); + + uint8_t level = static_cast(level_u16); + + if (dim3) { + Kokkos::Array is_outside{is_outside_x, is_outside_y, is_outside_z}; + kalypsso::encode_orchard_key<3>(tree_coord_array, oct_coord_array, level, is_outside); + } else { + Kokkos::Array is_outside{is_outside_x, is_outside_y}; + kalypsso::encode_orchard_key<2>(tree_coord_array, oct_coord_array, level, is_outside); + } + + return EXIT_SUCCESS; + +} // main diff --git a/test/AMRkey/test_orchard.cpp b/test/AMRkey/test_orchard.cpp new file mode 100644 index 0000000..daa7b02 --- /dev/null +++ b/test/AMRkey/test_orchard.cpp @@ -0,0 +1,365 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file test_orchard.cpp + * + * binary print borrowed from stackoverflow : + * https://stackoverflow.com/questions/111928/is-there-a-printf-converter-to-print-in-binary-format + */ +#include +#include + +#include + +// clang-format off +#define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c" +#define BYTE_TO_BINARY(byte) \ + ((byte) & 0x80 ? '1' : '0'), \ + ((byte) & 0x40 ? '1' : '0'), \ + ((byte) & 0x20 ? '1' : '0'), \ + ((byte) & 0x10 ? '1' : '0'), \ + ((byte) & 0x08 ? '1' : '0'), \ + ((byte) & 0x04 ? '1' : '0'), \ + ((byte) & 0x02 ? '1' : '0'), \ + ((byte) & 0x01 ? '1' : '0') +// clang-format on + +namespace kalypsso +{ + +// ================================================================================ +// ================================================================================ +template +void +print_morton_tree(Kokkos::Array treeCoord) +{ + uint16_t morton_tree = kalypsso::orchard_key_t::encode_morton_tree(treeCoord); + + if constexpr (dim == 2) + { + printf("treeCoord=(%8d,%8d)=(0x%04x,0x%04x) | morton key = 0x%016x = 0b" BYTE_TO_BINARY_PATTERN + "|" BYTE_TO_BINARY_PATTERN "\n", + treeCoord[IX], + treeCoord[IY], + treeCoord[IX], + treeCoord[IY], + morton_tree, + BYTE_TO_BINARY(morton_tree >> 8), + BYTE_TO_BINARY(morton_tree)); + } + + if constexpr (dim == 3) + { + printf("treeCoord=(%5d,%5d,%5d)=(0x%04x,0x%04x,0x%04x) | morton key = 0x%016x = " + "0b" BYTE_TO_BINARY_PATTERN "|" BYTE_TO_BINARY_PATTERN "\n", + treeCoord[IX], + treeCoord[IY], + treeCoord[IZ], + treeCoord[IX], + treeCoord[IY], + treeCoord[IZ], + morton_tree, + BYTE_TO_BINARY(morton_tree >> 8), + BYTE_TO_BINARY(morton_tree)); + } + + // build full orchard key + uint64_t full_orchard_key = morton_tree; + + // for cross-check, decode morton + if constexpr (dim == 2) + { + uint16_t iTreeX = kalypsso::orchard_key_t::template get_tree_coord(full_orchard_key); + uint16_t iTreeY = kalypsso::orchard_key_t::template get_tree_coord(full_orchard_key); + printf("decode orchard key : tree=(%d,%d)\n", iTreeX, iTreeY); + } + if constexpr (dim == 3) + { + uint16_t iTreeX = kalypsso::orchard_key_t::template get_tree_coord(full_orchard_key); + uint16_t iTreeY = kalypsso::orchard_key_t::template get_tree_coord(full_orchard_key); + uint16_t iTreeZ = kalypsso::orchard_key_t::template get_tree_coord(full_orchard_key); + printf("decode orchard key : tree=(%d,%d,%d)\n", iTreeX, iTreeY, iTreeZ); + } + +} // print_morton_tree + +// ================================================================================ +// ================================================================================ +template +void +print_morton_octant(Kokkos::Array octCoord) +{ + uint64_t key = 0; + + kalypsso::orchard_key_t::encode_morton_octant(key, octCoord); + uint64_t morton_oct = kalypsso::orchard_key_t::morton_octant(key); + + if constexpr (dim == 2) + { + printf("octCoord=(%4d,%4d)=(0x%04x,0x%04x) | morton key = 0x%016lx = 0b" BYTE_TO_BINARY_PATTERN + "|" BYTE_TO_BINARY_PATTERN "||" BYTE_TO_BINARY_PATTERN "|" BYTE_TO_BINARY_PATTERN + "||" BYTE_TO_BINARY_PATTERN "|" BYTE_TO_BINARY_PATTERN "\n", + octCoord[IX], + octCoord[IY], + octCoord[IX], + octCoord[IY], + morton_oct, + BYTE_TO_BINARY(morton_oct >> 40), + BYTE_TO_BINARY(morton_oct >> 32), + BYTE_TO_BINARY(morton_oct >> 24), + BYTE_TO_BINARY(morton_oct >> 16), + BYTE_TO_BINARY(morton_oct >> 8), + BYTE_TO_BINARY(morton_oct)); + } + else if constexpr (dim == 3) + { + printf("octCoord=(%5d,%5d,%5d)=(0x%04x,0x%04x,0x%04x) | morton key = 0x%016lx = " + "0b" BYTE_TO_BINARY_PATTERN "|" BYTE_TO_BINARY_PATTERN "||" BYTE_TO_BINARY_PATTERN + "|" BYTE_TO_BINARY_PATTERN "||" BYTE_TO_BINARY_PATTERN "|" BYTE_TO_BINARY_PATTERN "\n", + octCoord[IX], + octCoord[IY], + octCoord[IZ], + octCoord[IX], + octCoord[IY], + octCoord[IZ], + morton_oct, + BYTE_TO_BINARY(morton_oct >> 40), + BYTE_TO_BINARY(morton_oct >> 32), + BYTE_TO_BINARY(morton_oct >> 24), + BYTE_TO_BINARY(morton_oct >> 16), + BYTE_TO_BINARY(morton_oct >> 8), + BYTE_TO_BINARY(morton_oct)); + } + + // build full orchard key + uint64_t full_orchard_key = morton_oct << kalypsso::orchard_key_t::MORTON_OCTANT_OFFSET; + + // for cross-check, decode morton octant + if constexpr (dim == 2) + { + uint32_t iOctX = kalypsso::orchard_key_t::template get_octant_coord(full_orchard_key); + uint32_t iOctY = kalypsso::orchard_key_t::template get_octant_coord(full_orchard_key); + + printf("decode orchard key : octant=(%d,%d)\n", iOctX, iOctY); + } + else if constexpr (dim == 3) + { + uint32_t iOctX = kalypsso::orchard_key_t::template get_octant_coord(full_orchard_key); + uint32_t iOctY = kalypsso::orchard_key_t::template get_octant_coord(full_orchard_key); + uint32_t iOctZ = kalypsso::orchard_key_t::template get_octant_coord(full_orchard_key); + + printf("decode orchard key : octant=(%d,%d,%d)\n", iOctX, iOctY, iOctZ); + } +} // print_morton_octant + +// ================================================================================ +// ================================================================================ +template +void +run_test_tree() +{ + + if constexpr (dim == 2) + { + printf("=================================================\n"); + printf("Cross-checking morton encoding for tree - 2d case\n"); + printf("=================================================\n"); + // input is the tree coordinate in the brick connectivity + // output is the morton tree key, here printed in binary for eye check + + // use brick connectivity - max number of trees per dim is 2^7 = 128 + // uint16_t nTreeX = 128; + // uint16_t nTreeY = 128; + + Kokkos::Array iTree{ 0, 0 }; + + iTree[IX] = 0x0000; + iTree[IY] = 0x007F; + print_morton_tree(iTree); + + iTree[IX] = 0x007F; + iTree[IY] = 0x0000; + print_morton_tree(iTree); + + iTree[IX] = 0x0005; + iTree[IY] = 0x0002; + print_morton_tree(iTree); + + iTree[IX] = 0x0002; + iTree[IY] = 0x0005; + print_morton_tree(iTree); + + iTree[IX] = 32; + iTree[IY] = 17; + print_morton_tree(iTree); + + iTree[IX] = 77; + iTree[IY] = 111; + print_morton_tree(iTree); + } + + if constexpr (dim == 3) + { + printf("=================================================\n"); + printf("Cross-checking morton encoding for tree - 3d case\n"); + printf("=================================================\n"); + // input is the tree coordinate in the brick connectivity + // output is the morton tree key, here printed in binary for eye check + + // use brick connectivity - max number of trees per dim is 2^5 = 32 + + Kokkos::Array iTree{ 0, 0, 0 }; + + iTree[IX] = 0x001F; + iTree[IY] = 0x0000; + iTree[IZ] = 0x0000; + print_morton_tree(iTree); + + iTree[IX] = 0x0000; + iTree[IY] = 0x001F; + iTree[IZ] = 0x0000; + print_morton_tree(iTree); + + iTree[IX] = 0x0000; + iTree[IY] = 0x0000; + iTree[IZ] = 0x001F; + print_morton_tree(iTree); + + iTree[IX] = 18; + iTree[IY] = 4; + iTree[IZ] = 1; + print_morton_tree(iTree); + + iTree[IX] = 8; + iTree[IY] = 17; + iTree[IZ] = 29; + print_morton_tree(iTree); + + // this one should fail + iTree[IX] = 32; + iTree[IY] = 1; + iTree[IZ] = 2; + print_morton_tree(iTree); + } +} // run_test_tree + +// ================================================================================ +// ================================================================================ +template +void +run_test_octant() +{ + + if constexpr (dim == 2) + { + printf("====================================================\n"); + printf("Cross-checking morton encoding for octant - 2d case \n"); + printf("====================================================\n"); + // input is the octant coordinates in the brick connectivity + // output is the morton octant key, here printed in binary for eye check + + // octant coordinates are unsigned integer on 22 bits + + Kokkos::Array iOct{ 0, 0 }; + + iOct[IX] = 0x0000; + iOct[IY] = 0x007F; + print_morton_octant(iOct); + + iOct[IX] = 0x007F; + iOct[IY] = 0x0000; + print_morton_octant(iOct); + + iOct[IX] = 0x0005; + iOct[IY] = 0x0002; + print_morton_octant(iOct); + + iOct[IX] = 0x0002; + iOct[IY] = 0x0005; + print_morton_octant(iOct); + + iOct[IX] = 320; + iOct[IY] = 1777; + print_morton_octant(iOct); + + iOct[IX] = 77; + iOct[IY] = 111; + print_morton_octant(iOct); + + iOct[IX] = 2048; + iOct[IY] = 1024; + print_morton_octant(iOct); + + iOct[IX] = 1024; + iOct[IY] = 2048; + print_morton_octant(iOct); + + iOct[IX] = 1 << 16; + iOct[IY] = 1 << 18; + print_morton_octant(iOct); + + // this one should fail, since 1^22 is 1 too large + iOct[IX] = 1 << 22; + iOct[IY] = 1 << 18; + print_morton_octant(iOct); + + // this one should fail, since 6544213 is >= 4194304 (2^22) + iOct[IX] = 1000000; + iOct[IY] = 6544213; + print_morton_octant(iOct); + } + else if constexpr (dim == 3) + { + printf("====================================================\n"); + printf("Cross-checking morton encoding for octant - 3d case \n"); + printf("====================================================\n"); + // input is the octant coordinates in the brick connectivity + // output is the morton octant key, here printed in binary for eye check + + // octant coordinates are unsigned integer on 15 bits + + Kokkos::Array iOct{ 0, 0, 0 }; + + iOct[IX] = 0x0000; + iOct[IY] = 0x001F; + iOct[IZ] = 0x0000; + print_morton_octant(iOct); + + iOct[IX] = 0x0100; + iOct[IY] = 0x001F; + iOct[IZ] = 0x4000; + print_morton_octant(iOct); + + iOct[IX] = 0x1000; + iOct[IY] = 0x001F; + iOct[IZ] = 0x7FFF; + print_morton_octant(iOct); + + // this one should fail + iOct[IX] = 0x1000; + iOct[IY] = 0x001F; + iOct[IZ] = 0x8000; // should fail here + print_morton_octant(iOct); + } + +} // run_test_octant + +} // namespace kalypsso + +// ================================================================================ +// ================================================================================ +// ================================================================================ +int +main() +{ + + kalypsso::run_test_tree<2>(); + kalypsso::run_test_octant<2>(); + + kalypsso::run_test_tree<3>(); + kalypsso::run_test_octant<3>(); + + return EXIT_SUCCESS; +} diff --git a/test/AMRmesh/CMakeLists.txt b/test/AMRmesh/CMakeLists.txt new file mode 100644 index 0000000..9abceaa --- /dev/null +++ b/test/AMRmesh/CMakeLists.txt @@ -0,0 +1,308 @@ +add_executable(test_AMRmesh "") +target_sources(test_AMRmesh PRIVATE test_AMRmesh.cpp) +target_link_libraries(test_AMRmesh PUBLIC kalypsso::core kalypsso::core_config kalypsso::p4est_wrap + kalypsso::config kalypsso::mpiUtils) + +add_test(NAME AMRmesh COMMAND ${TEST_MPI_PREFIX_CMD} test_AMRmesh) + +if(Kokkos_ENABLE_OPENMP) + set_property(TEST AMRmesh PROPERTY ENVIRONMENT OMP_NUM_THREADS=${KALYPSSO_TEST_OMP_NUM_THREADS} + OMP_PROC_BIND=spread OMP_PLACES=threads) +endif() + +configure_file(test_AMRmesh_2d.ini test_AMRmesh_2d.ini COPYONLY) +configure_file(test_AMRmesh_3d.ini test_AMRmesh_3d.ini COPYONLY) +configure_file(test_AMRmesh_simple_2d.ini test_AMRmesh_simple_2d.ini COPYONLY) +configure_file(test_AMRmesh_simple_3d.ini test_AMRmesh_simple_3d.ini COPYONLY) +configure_file(test_AMRmesh_brick_2d.ini test_AMRmesh_brick_2d.ini COPYONLY) +configure_file(test_AMRmesh_brick_3d.ini test_AMRmesh_brick_3d.ini COPYONLY) +configure_file(test_AMRmesh_brick_2d_ghost_exchange_mpi.ini + test_AMRmesh_brick_2d_ghost_exchange_mpi.ini COPYONLY) +configure_file(test_AMRmesh_brick_3d_ghost_exchange_mpi.ini + test_AMRmesh_brick_3d_ghost_exchange_mpi.ini COPYONLY) +configure_file(test_AMRmesh_fill_ghost_brick_2d.ini test_AMRmesh_fill_ghost_brick_2d.ini COPYONLY) +configure_file(test_AMRmesh_fill_ghost_brick_3d.ini test_AMRmesh_fill_ghost_brick_3d.ini COPYONLY) +configure_file(test_AMRmesh_brick_2d.ini test_AMRmesh_stencil_2d.ini COPYONLY) +configure_file(test_AMRmesh_brick_3d.ini test_AMRmesh_stencil_3d.ini COPYONLY) +configure_file(test_AMRmesh_userdata_2d.ini test_AMRmesh_userdata_2d.ini COPYONLY) +configure_file(test_AMRmesh_userdata_3d.ini test_AMRmesh_userdata_3d.ini COPYONLY) + +add_executable(test_AMRmesh_userdata "") +target_sources(test_AMRmesh_userdata PRIVATE test_AMRmesh_userdata.cpp) +target_include_directories( + test_AMRmesh_userdata PUBLIC $ + $) +target_link_libraries(test_AMRmesh_userdata PUBLIC kalypsso::core kalypsso::p4est_wrap + kalypsso::config kalypsso::AMRtest) + +add_test(NAME AMRmesh_userdata_2d COMMAND ${TEST_MPI_PREFIX_CMD} test_AMRmesh_userdata --ini + test_AMRmesh_userdata_2d.ini) +# add_test(NAME AMRmesh_userdata_3d COMMAND ${TEST_MPI_PREFIX_CMD} test_AMRmesh_userdata --ini +# test_AMRmesh_userdata_3d.ini --3d) + +if(Kokkos_ENABLE_OPENMP) + set_property( + TEST AMRmesh_userdata_2d PROPERTY ENVIRONMENT OMP_NUM_THREADS=${KALYPSSO_TEST_OMP_NUM_THREADS} + OMP_PROC_BIND=spread OMP_PLACES=threads) + # set_property(TEST AMRmesh_userdata_3d PROPERTY ENVIRONMENT + # OMP_NUM_THREADS=${KALYPSSO_TEST_OMP_NUM_THREADS} OMP_PROC_BIND=spread OMP_PLACES=threads) +endif() + +# ################################################################################################## +# ################################################################################################## +# ################################################################################################## +add_executable(test_AMRContext "") +target_sources(test_AMRContext PRIVATE test_AMRContext.cpp) +target_include_directories(test_AMRContext PUBLIC $ + $) +target_link_libraries( + test_AMRContext PUBLIC kalypsso::core kalypsso::core_config kalypsso::p4est_wrap kalypsso::config + kalypsso::mpiUtils kalypsso::AMRtest) +target_link_libraries(test_AMRContext PRIVATE kalypsso_warnings) + +if(KALYPSSO_CORE_USE_SPDLOG) + target_compile_definitions(test_AMRContext + PRIVATE -DSPDLOG_ACTIVE_LEVEL=${KALYPSSO_CORE_LOG_LEVEL}) + target_link_libraries(test_AMRContext PUBLIC spdlog::spdlog) +endif() + +if(KALYPSSO_CORE_USE_CNPY) + target_link_libraries(test_AMRContext PUBLIC cnpy::cnpy) +endif() + +add_test(NAME AMRContext_2d COMMAND ${TEST_MPI_PREFIX_CMD} test_AMRContext --ini + test_AMRmesh_brick_2d.ini --refine_level 6) +add_test(NAME AMRContext_3d COMMAND ${TEST_MPI_PREFIX_CMD} test_AMRContext --ini + test_AMRmesh_brick_2d.ini --refine_level 6 --3d) + +if(Kokkos_ENABLE_OPENMP) + set_property( + TEST AMRContext_2d PROPERTY ENVIRONMENT OMP_NUM_THREADS=${KALYPSSO_TEST_OMP_NUM_THREADS} + OMP_PROC_BIND=spread OMP_PLACES=threads) + set_property( + TEST AMRContext_3d PROPERTY ENVIRONMENT OMP_NUM_THREADS=${KALYPSSO_TEST_OMP_NUM_THREADS} + OMP_PROC_BIND=spread OMP_PLACES=threads) +endif() + +# ################################################################################################## +# ################################################################################################## +# ################################################################################################## +if(KALYPSSO_CORE_USE_MPI) + + add_executable(test_AMR_ghost_exchange_mpi "") + + target_sources(test_AMR_ghost_exchange_mpi PRIVATE test_AMR_ghost_exchange_mpi.cpp) + + target_include_directories( + test_AMR_ghost_exchange_mpi PUBLIC $ + $) + + target_link_libraries( + test_AMR_ghost_exchange_mpi PUBLIC kalypsso::core kalypsso::p4est_wrap kalypsso::config + kalypsso::mpiUtils kalypsso::AMRtest) + target_link_libraries(test_AMR_ghost_exchange_mpi PRIVATE kalypsso_warnings) + + if(KALYPSSO_CORE_USE_SPDLOG) + target_compile_definitions(test_AMR_ghost_exchange_mpi + PRIVATE -DSPDLOG_ACTIVE_LEVEL=${KALYPSSO_CORE_LOG_LEVEL}) + target_link_libraries(test_AMR_ghost_exchange_mpi PUBLIC spdlog::spdlog) + endif(KALYPSSO_CORE_USE_SPDLOG) + +endif(KALYPSSO_CORE_USE_MPI) + +# ################################################################################################## +# ################################################################################################## +# ################################################################################################## +add_executable(test_AMRmesh_fill_block_ghost_cells_v2 "") +target_sources(test_AMRmesh_fill_block_ghost_cells_v2 + PRIVATE test_AMRmesh_fill_block_ghost_cells_v2.cpp) + +target_include_directories( + test_AMRmesh_fill_block_ghost_cells_v2 PUBLIC $ + $) + +target_link_libraries( + test_AMRmesh_fill_block_ghost_cells_v2 + PUBLIC kalypsso::core kalypsso::core_config kalypsso::p4est_wrap kalypsso::config + kalypsso::mpiUtils kalypsso::AMRtest) + +target_link_libraries(test_AMRmesh_fill_block_ghost_cells_v2 PRIVATE kalypsso_warnings) + +if(KALYPSSO_CORE_USE_SPDLOG) + target_compile_definitions(test_AMRmesh_fill_block_ghost_cells_v2 + PRIVATE -DSPDLOG_ACTIVE_LEVEL=${KALYPSSO_CORE_LOG_LEVEL}) + target_link_libraries(test_AMRmesh_fill_block_ghost_cells_v2 PUBLIC spdlog::spdlog) +endif() + +# ################################################################################################## +# ################################################################################################## +# ################################################################################################## +add_executable(test_AMRmesh_fill_block_ghost_cells "") +target_sources(test_AMRmesh_fill_block_ghost_cells PRIVATE test_AMRmesh_fill_block_ghost_cells.cpp) + +target_include_directories( + test_AMRmesh_fill_block_ghost_cells PUBLIC $ + $) + +target_link_libraries( + test_AMRmesh_fill_block_ghost_cells PUBLIC kalypsso::core kalypsso::p4est_wrap kalypsso::config + kalypsso::mpiUtils kalypsso::AMRtest) + +target_link_libraries(test_AMRmesh_fill_block_ghost_cells PRIVATE kalypsso_warnings) + +if(KALYPSSO_CORE_USE_SPDLOG) + target_compile_definitions(test_AMRmesh_fill_block_ghost_cells + PRIVATE -DSPDLOG_ACTIVE_LEVEL=${KALYPSSO_CORE_LOG_LEVEL}) + target_link_libraries(test_AMRmesh_fill_block_ghost_cells PUBLIC spdlog::spdlog) +endif() + +# ################################################################################################## +# ################################################################################################## +# ################################################################################################## +add_executable(test_AMRmesh_fill_block_ghost_cells_in_place "") +target_sources(test_AMRmesh_fill_block_ghost_cells_in_place + PRIVATE test_AMRmesh_fill_block_ghost_cells_in_place.cpp) +target_include_directories( + test_AMRmesh_fill_block_ghost_cells_in_place + PUBLIC $ + $) +target_link_libraries( + test_AMRmesh_fill_block_ghost_cells_in_place + PUBLIC kalypsso::core kalypsso::core_config kalypsso::p4est_wrap kalypsso::config + kalypsso::mpiUtils kalypsso::AMRtest) + +target_link_libraries(test_AMRmesh_fill_block_ghost_cells_in_place PRIVATE kalypsso_warnings) + +if(KALYPSSO_CORE_USE_SPDLOG) + target_compile_definitions(test_AMRmesh_fill_block_ghost_cells_in_place + PRIVATE -DSPDLOG_ACTIVE_LEVEL=${KALYPSSO_CORE_LOG_LEVEL}) + target_link_libraries(test_AMRmesh_fill_block_ghost_cells_in_place PUBLIC spdlog::spdlog) +endif() + +# +# ################################################################################################## +# +# ################################################################################################## +# +# ################################################################################################## +add_executable(test_AMRmesh_fill_block_ghost_faces "") +target_sources(test_AMRmesh_fill_block_ghost_faces PRIVATE test_AMRmesh_fill_block_ghost_faces.cpp) + +target_include_directories( + test_AMRmesh_fill_block_ghost_faces PUBLIC $ + $) + +target_link_libraries( + test_AMRmesh_fill_block_ghost_faces PUBLIC kalypsso::core kalypsso::p4est_wrap kalypsso::config + kalypsso::mpiUtils kalypsso::AMRtest) + +if(KALYPSSO_CORE_USE_SPDLOG) + target_compile_definitions(test_AMRmesh_fill_block_ghost_faces + PRIVATE -DSPDLOG_ACTIVE_LEVEL=${KALYPSSO_CORE_LOG_LEVEL}) + target_link_libraries(test_AMRmesh_fill_block_ghost_faces PUBLIC spdlog::spdlog) +endif() + +# ################################################################################################## +# ################################################################################################## +# ################################################################################################## +add_executable(test_AMRmesh_refine_flags "") +target_sources(test_AMRmesh_refine_flags PRIVATE test_AMRmesh_refine_flags.cpp) +target_include_directories( + test_AMRmesh_refine_flags PUBLIC $ + $) +target_link_libraries( + test_AMRmesh_refine_flags PUBLIC kalypsso::core kalypsso::core_config kalypsso::p4est_wrap + kalypsso::config kalypsso::mpiUtils kalypsso::AMRtest) +target_link_libraries(test_AMRmesh_refine_flags PRIVATE kalypsso_warnings) + +if(KALYPSSO_CORE_USE_SPDLOG) + target_compile_definitions(test_AMRmesh_refine_flags + PRIVATE -DSPDLOG_ACTIVE_LEVEL=${KALYPSSO_CORE_LOG_LEVEL}) + target_link_libraries(test_AMRmesh_refine_flags PUBLIC spdlog::spdlog) +endif() + +if(KALYPSSO_CORE_USE_CNPY) + target_link_libraries(test_AMRmesh_refine_flags PUBLIC cnpy::cnpy) +endif() + +# ################################################################################################## +# ################################################################################################## +# ################################################################################################## +add_executable(test_AMRmesh_stencil "") +target_sources(test_AMRmesh_stencil PRIVATE test_AMRmesh_stencil.cpp) +target_include_directories( + test_AMRmesh_stencil PUBLIC $ + $) +target_link_libraries( + test_AMRmesh_stencil PUBLIC kalypsso::core kalypsso::p4est_wrap kalypsso::config + kalypsso::mpiUtils kalypsso::AMRtest) +target_link_libraries(test_AMRmesh_stencil PRIVATE kalypsso_warnings) + +if(KALYPSSO_CORE_USE_SPDLOG) + target_compile_definitions(test_AMRmesh_stencil + PRIVATE -DSPDLOG_ACTIVE_LEVEL=${KALYPSSO_CORE_LOG_LEVEL}) + target_link_libraries(test_AMRmesh_stencil PUBLIC spdlog::spdlog) +endif() + +# ################################################################################################## +# ################################################################################################## +# ################################################################################################## +add_executable(test_first_derivative "") +target_sources(test_first_derivative PRIVATE test_first_derivative.cpp) +target_include_directories( + test_first_derivative PUBLIC $ + $) +target_link_libraries( + test_first_derivative PUBLIC kalypsso::core kalypsso::core_config kalypsso::p4est_wrap + kalypsso::config kalypsso::mpiUtils kalypsso::AMRtest) +target_link_libraries(test_first_derivative PRIVATE kalypsso_warnings) + +if(KALYPSSO_CORE_USE_SPDLOG) + target_compile_definitions(test_first_derivative + PRIVATE -DSPDLOG_ACTIVE_LEVEL=${KALYPSSO_CORE_LOG_LEVEL}) + target_link_libraries(test_first_derivative PUBLIC spdlog::spdlog) +endif() + +# ################################################################################################## +# ################################################################################################## +# ################################################################################################## +add_executable(test_AMRmesh_stencil_helper "") +target_sources(test_AMRmesh_stencil_helper PRIVATE test_AMRmesh_stencil_helper.cpp) +target_include_directories( + test_AMRmesh_stencil_helper PUBLIC $ + $) +target_link_libraries( + test_AMRmesh_stencil_helper PUBLIC kalypsso::core kalypsso::p4est_wrap kalypsso::config + kalypsso::mpiUtils kalypsso::AMRtest) +target_link_libraries(test_AMRmesh_stencil_helper PRIVATE kalypsso_warnings) + +if(KALYPSSO_CORE_USE_SPDLOG) + target_compile_definitions(test_AMRmesh_stencil_helper + PRIVATE -DSPDLOG_ACTIVE_LEVEL=${KALYPSSO_CORE_LOG_LEVEL}) + target_link_libraries(test_AMRmesh_stencil_helper PUBLIC spdlog::spdlog) +endif() + +# ################################################################################################## +# ################################################################################################## +# ################################################################################################## +add_executable(test_brick_connectivity "") +target_sources(test_brick_connectivity PRIVATE test_brick_connectivity.cpp) +target_link_libraries(test_brick_connectivity PUBLIC kalypsso::core kalypsso::core_config) +# target_link_libraries(test_brick_connectivity PRIVATE kalypsso_warnings) + +if(KALYPSSO_CORE_USE_CNPY) + target_link_libraries(test_brick_connectivity PUBLIC cnpy::cnpy) +endif() + +# +# copy python script to display orchard keys versus index (across all MPI process) +# +configure_file(plot_orchard_keys.py plot_orchard_keys.py) +configure_file(plot_brick_connectivity_2d.py plot_brick_connectivity_2d.py) +configure_file(plot_brick_connectivity_3d.py plot_brick_connectivity_3d.py) + +# +# cleanup any artefact hdf5 / xmf / npy files produced by running the targets +# +add_custom_target(clean_AMRmesh_data COMMAND ${CMAKE_COMMAND} -P + ${CMAKE_CURRENT_SOURCE_DIR}/clean_data.cmake) diff --git a/test/AMRmesh/clean_data.cmake b/test/AMRmesh/clean_data.cmake new file mode 100644 index 0000000..5513080 --- /dev/null +++ b/test/AMRmesh/clean_data.cmake @@ -0,0 +1,3 @@ +file(GLOB files_to_remove "${CMAKE_CURRENT_BINARY_DIR}/*[.h5,.xmf,.npy]") +message("removing ${files_to_remove}") +file(REMOVE ${files_to_remove}) diff --git a/test/AMRmesh/plot_brick_connectivity_2d.py b/test/AMRmesh/plot_brick_connectivity_2d.py new file mode 100644 index 0000000..3183ab8 --- /dev/null +++ b/test/AMRmesh/plot_brick_connectivity_2d.py @@ -0,0 +1,40 @@ +import numpy as np +import matplotlib.pyplot as plt + +x = np.load("x_2d.npy") +y = np.load("y_2d.npy") + +treeids = np.arange(x.size) + +ax = plt.figure().add_subplot() + +ax.plot(x+0.5, y+0.5) + +x_min = np.min(x) +x_max = np.max(x) +y_min = np.min(y) +y_max = np.max(y) + +#ax.axis("equal") +#ax.set_aspect("equal", "box") +ax.set_aspect("equal") + +# Major ticks every 1 +x_major_ticks = np.arange(x_min, x_max+1.5, 1) +y_major_ticks = np.arange(y_min, y_max+1.5, 1) + +ax.set_xticks(x_major_ticks) +ax.set_yticks(y_major_ticks) + + +ax.grid() + +for treeid in treeids: + ax.annotate(treeid, xy = (x[treeid]+0.3, y[treeid]+0.3)) + +ax.set_xlabel('x') +ax.set_ylabel('y') + +plt.title("p4est tree numbering in 2d brick connectivity.") + +plt.show() diff --git a/test/AMRmesh/plot_brick_connectivity_3d.py b/test/AMRmesh/plot_brick_connectivity_3d.py new file mode 100644 index 0000000..88426d4 --- /dev/null +++ b/test/AMRmesh/plot_brick_connectivity_3d.py @@ -0,0 +1,40 @@ +import numpy as np +import matplotlib.pyplot as plt + +x = np.load("x_3d.npy") +y = np.load("y_3d.npy") +z = np.load("z_3d.npy") + +treeids = np.arange(x.size) + +x_min = np.min(x) +x_max = np.max(x) +y_min = np.min(y) +y_max = np.max(y) +z_min = np.min(z) +z_max = np.max(z) + +ax = plt.figure().add_subplot(projection='3d') +ax.plot(x+0.5, y+0.5, z+0.5) + +# Major ticks every 1 +x_major_ticks = np.arange(x_min, x_max+1.5, 1) +y_major_ticks = np.arange(y_min, y_max+1.5, 1) +z_major_ticks = np.arange(z_min, z_max+1.5, 1) + +ax.set_xticks(x_major_ticks) +ax.set_yticks(y_major_ticks) +ax.set_zticks(z_major_ticks) +ax.grid() + +for treeid in treeids: + ax.text(x[treeid]+0.4, y[treeid]+0.4, z[treeid]+0.4, '%s' % (str(treeid)),size=16,color='r') + +#plt.axis("equal") + +plt.title("p4est tree numbering in 3d brick connectivity.") +ax.set_xlabel('x') +ax.set_ylabel('y') +ax.set_zlabel('z') + +plt.show() diff --git a/test/AMRmesh/plot_orchard_keys.py b/test/AMRmesh/plot_orchard_keys.py new file mode 100644 index 0000000..9ec0079 --- /dev/null +++ b/test/AMRmesh/plot_orchard_keys.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- + +""" +Just plotting Morton orchard keys before / after AMR cycle for illustrative purpose. +""" + +import numpy as np +import matplotlib.pyplot as plt +from matplotlib import rc +#rc('text', usetex=True) +import os +import fnmatch + +# count the number of file which name matches "orchard_keys_before_amr_mpi_0000*.npy" +mpi_size = len(fnmatch.filter(os.listdir('./'), 'orchard_keys_before*.npy')) + +# create empty list of array (1 per MPI rank) +list_keys_before = [] +list_index_before = [] +list_keys_after = [] +list_index_after = [] + +for i in range(mpi_size): + print(i) + + mpi_rank = "{:05d}".format(i) + + keys_before = np.load("./orchard_keys_before_amr_mpi_"+mpi_rank+".npy") + index_before = np.arange(keys_before.size) + + keys_after = np.load("./orchard_keys_after_amr_mpi_"+mpi_rank+".npy") + index_after = np.arange(keys_after.size) + + list_keys_before.append(keys_before) + list_index_before.append(index_before) + list_keys_after.append(keys_after) + list_index_after.append(index_after) + + if i>0: + for j in range(i): + list_index_before[i] += list_index_before[j].size + list_index_after[i] += list_index_after[j].size + + plt.plot(list_index_before[i], list_keys_before[i], label='orchard_keys_before_amr_mpi', color='red') + plt.plot(list_index_after[i], list_keys_after[i], label='orchard_keys_after_amr_mpi', color='blue') + +plt.grid(True) +plt.title('Orchard keys along the Morton curve before / after AMR cycle') +plt.xlabel('key index') +plt.ylabel('Morton - orchard key') + +plt.show() diff --git a/test/AMRmesh/test_AMRContext.cpp b/test/AMRmesh/test_AMRContext.cpp new file mode 100644 index 0000000..c43811b --- /dev/null +++ b/test/AMRmesh/test_AMRContext.cpp @@ -0,0 +1,542 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file test_AMRContext.cpp + * + * Purpose: illustrate use of class AMRContext (helper class provide all that is necessary to + * perform AMR cycle on device, GPU compatible). + * + * What we do here: + * - prepare an AMR mesh with some initial refinement (done using regular p4est API, cpu only) + * - create some user data (as Kokkos device view) attached to the AMR mesh + * - perform an AMR cycle (refine, coarsen, balance) to modify mesh + * - remap user data from the old mesh to the new mesh + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef KALYPSSO_CORE_USE_MPI +# include +# include +#endif // KALYPSSO_CORE_USE_MPI + +#include // for block_size_t + +#include +#include +#include + +#ifdef KALYPSSO_CORE_USE_CNPY +# include +#endif // KALYPSSO_CORE_USE_CNPY + +#include + +#include + +#include + +#include + +namespace kalypsso +{ + +// ============================================================================= +// ============================================================================= +template +void +run_test(const ParallelEnv & par_env, const ConfigMap & config_map) +{ + + using DataArrayLeaf_t = DataArrayLeaf; + + using DataArrayBlock_t = DataArrayBlock; + + using FaceDataArrayBlock_t = FaceDataArrayBlock; + + using ExecutionSpace = typename device_t::execution_space; + + using Hydro_t = core::models::Hydro; + + InitialAMRSetup initial_amr_setup(par_env, config_map, InitFunc3{}); + + // setup initial p4est mesh with some test pseudo random refined/coarsen cells pattern + initial_amr_setup.setup_initial_mesh(); + + auto & amr_mesh = initial_amr_setup.mesh(); + auto forest = amr_mesh.forest(); + auto mesh_map = initial_amr_setup.mesh_map(); + + // initialize ghost + amr_mesh.reset_ghost(); + + // retrieve amr keys + mesh_map->update_orchard_keys(amr_mesh.forest(), amr_mesh.ghost()); + auto orchard_keys_host = mesh_map->orchard_keys_host(); + auto orchard_keys_device = mesh_map->orchard_keys(); + +#ifdef KALYPSSO_CORE_USE_CNPY + // save orchard key corresponding to locally owned quadrants (exclude ghost quadrant) + save_cnpy( + Kokkos::subview(orchard_keys_host, + std::make_pair(0, initial_amr_setup.mesh().forest()->local_num_quadrants)), + "orchard_keys_before_amr", + par_env); +#endif + + + // populate user data + auto userdataLeaf = initial_amr_setup.setup_initial_data_leaf(orchard_keys_device); + auto userdataBlock = initial_amr_setup.setup_initial_data_block_new(orchard_keys_device); + auto userdataBlock_face = + initial_amr_setup.setup_initial_data_block_face(orchard_keys_device, false); + + ExecutionSpace exec_space; + + // + // save data before AMR cycle + // + { + std::string filename = + dim == 2 ? "test_userdata_2d_before_amr_cycle" : "test_userdata_3d_before_amr_cycle"; + + // provide mapping between variables Id and variables names + const Hydro_t model(dim); + + DataWriter::save( + filename, userdataLeaf, userdataBlock, config_map, amr_mesh, model); + + DataWriter::save( + filename + "_face", userdataBlock_face, config_map, amr_mesh, "facedata"); + + // compute divergence of the face array + const auto divBdata = + FaceDataArrayBlock_t::compute_divergence(userdataBlock_face, mesh_map->orchard_keys()); + + DataWriter::save_scalar( + filename + std::string("_face_div"), divBdata, 0, config_map, amr_mesh); + } + + // + // here comes the interesting part : apply the AMR cycle + // + + + // + // 1. store the UnorderedMap before AMR cycle + // + const bool on_device = true; + // update hashmap (orchard keys array are already up to date) + mesh_map->update_hashmap(on_device); + auto amr_hashmap_device_old = mesh_map->hashmap_clone(); + + + AMRContext amr_context(forest->local_num_quadrants); + + auto flags = amr_context.m_amrflags_d; + auto level_min = config_map.getInteger("amr", "level_min", 4); + auto level_max = config_map.getInteger("amr", "level_max", 6); + + // + // 2. initalize/compute an amrflags array on device (as we would do in a real application) + // + + // get geometrical scaling factor + const auto scaling_factor = get_scaling_factor(config_map); + + // get domain lower left corner + const auto xyz_min = get_xyz_min(config_map); + + Kokkos::parallel_for( + "Fill_test_data", + Kokkos::RangePolicy(exec_space, 0, forest->local_num_quadrants), + KOKKOS_LAMBDA(const int i) { + auto key = orchard_keys_device(i); + + auto level = orchard_key_t::level(key); + auto tree_x = orchard_key_t::template get_tree_coord(key); + auto tree_y = orchard_key_t::template get_tree_coord(key); + + constexpr bool use_center = true; + const auto xyz_vertex = orchard_key_to_vertex_coord(key, use_center); + const auto xyz = vertex_coord_to_real_space(xyz_vertex, scaling_factor, xyz_min); + + // create some dummy refinement criterion (geometry based) + + // reminder: + // - we can only refine if level < level_max + // - we can only derefine if level > level_min + if ((level < level_max) and (tree_x == 1) and (tree_y == 1) and + (::kalypsso::fabs(xyz[IX] - KALYPSSO_NUM(1.5)) < KALYPSSO_NUM(0.1)) and + (::kalypsso::fabs(xyz[IY] - KALYPSSO_NUM(1.5)) < KALYPSSO_NUM(0.1))) + { + flags(i) = AMRContextBase::KALYPSSO_DO_REFINE; + } + else if ((level > level_min) and (tree_x == 1) and (tree_y == 0) and + (::kalypsso::fabs(xyz[IX] - KALYPSSO_NUM(1.2)) < KALYPSSO_NUM(0.2)) and + (::kalypsso::fabs(xyz[IY] - KALYPSSO_NUM(0.5)) < KALYPSSO_NUM(0.1))) + { + flags(i) = AMRContextBase::KALYPSSO_DO_COARSEN; + } + else + { + flags(i) = AMRContextBase::KALYPSSO_DO_NOTHING; + } + }); + + Kokkos::deep_copy(exec_space, amr_context.m_amrflags_h, amr_context.m_amrflags_d); + + // + // 3. apply AMR cycle on device : refine + coarsen + 2:1 balance + // + { + Kokkos::Profiling::ScopedRegion prof("AMR_refinement_device"); + [[maybe_unused]] auto changed = amr_context.adapt_mesh(forest); + KALYPSSO_INFO_ALL("Mesh changed ? {}", changed); + } + + // + // re-initialize ghost + // + amr_mesh.reset_ghost(); + auto ghost_new = amr_mesh.ghost(); + + // + // prepare for remapping user data + // + auto orchard_keys_device_old = mesh_map->orchard_keys_clone(); + mesh_map->update_orchard_keys(amr_mesh.forest(), ghost_new); + auto orchard_keys_device_new = mesh_map->orchard_keys_clone(); + +#ifdef KALYPSSO_CORE_USE_CNPY + { + auto orchard_keys_host_new = mesh_map->orchard_keys_host_clone(); + save_cnpy( + Kokkos::subview(orchard_keys_host_new, std::make_pair(0, forest->local_num_quadrants)), + "orchard_keys_after_amr", + par_env); + } +#endif + + HydroParams params = HydroParams(config_map); + const int nbvar = nbvar_hydro(); + + // get block sizes + const auto bx = config_map.getInteger("amr", "bx", 1); + const auto by = config_map.getInteger("amr", "by", 1); + const auto bz = config_map.getInteger("amr", "bz", 1); + + const auto bSizes = [=]() { + if constexpr (dim == 2) + return block_size_t<2>{ bx, by }; + if constexpr (dim == 3) + return block_size_t<3>{ bx, by, bz }; + }(); + + // memory allocation for userdata on leaf after remapping + auto userdataLeaf_new = DataArrayLeaf_t("test_data_after_amr_cycle", + static_cast(forest->local_num_quadrants), + static_cast(nbvar)); + auto userdataLeaf_new_host = Kokkos::create_mirror_view(userdataLeaf_new); + + // memory allocation for userdata on block after remapping + auto userdataBlock_new = + DataArrayBlock_t("test_data_block_after_amr_cycle", bSizes, nbvar, forest->local_num_quadrants); + auto userdataBlock_face_new = FaceDataArrayBlock_t( + "test_facedata_block_after_amr_cycle", bSizes, forest->local_num_quadrants); + + // apply data remapping - alternate version (TODO): + // 1. find remapping index + // for each key in keys_new (after AMR cycle), find index "j" such that + // keys_old(j) less or equal than keys_new(i) less than keys_old(j+1) + // here doing a simple naive binary search + // 2. apply remapping + // WIP + // + // when ready, to be moved into class UserDataRemapper + if constexpr (0) + { + auto local_num_octants = forest->local_num_quadrants; + Kokkos::parallel_for( + "DataRemap", + Kokkos::RangePolicy(0, local_num_octants), + KOKKOS_LAMBDA(const int i) { + const auto key_new = orchard_keys_device_new(i); + + // initialize step 1 (find remapping index, doing a simple binary search) + int j = i; + int jp1 = i; + auto key_old = orchard_keys_host(j); + + if (key_new > orchard_keys_device(j)) + jp1 = local_num_octants - 1; + else + jp1 = 0; + + // TO BE CONTINUED + }); + } + + // retrieve the new local number of octants (after AMR cycle) + auto local_num_octants = forest->local_num_quadrants; + + // const bool on_device = true; + // rebuild/update the new hashmap (after AMR cycle) + // orchard keys arrays are already up to date (see line 186) + mesh_map->update_hashmap(on_device); + auto amr_hashmap_device_new = mesh_map->hashmap_clone(); + // auto amr_hashmap_device_new = mesh_map->template create_map_device(forest, ghost_new, + // on_device); + + KALYPSSO_INFO_ALL("after create map on device"); + + UserDataRemapper userdataRemapper(amr_hashmap_device_old, + orchard_keys_device_new, + orchard_keys_device_old, + local_num_octants, + bSizes, + config_map); + + // + // 4.1 apply data remapping (on device) using the hash map approach - leaf data + // + { + KALYPSSO_INFO_ALL("after AMR local_num_quadrants = {} - remapping leaf data", + forest->local_num_quadrants); + + userdataRemapper.remap_leaf_data(exec_space, userdataLeaf, userdataLeaf_new); + } // end apply remapping + + // + // 4.2 apply data remapping (on device) using the hash map approach - cell-centered block data + // + { + KALYPSSO_INFO_ALL("after AMR local_num_quadrants = {} - remapping cell-centered data", + forest->local_num_quadrants); + userdataRemapper.remap_block_data(exec_space, userdataBlock, userdataBlock_new); + } // end apply remapping + + // + // 4.3 apply data remapping (on device) using the hash map approach - face-centered block data + // + { + KALYPSSO_INFO_ALL("after AMR local_num_quadrants = {} - remapping face-centered data", + forest->local_num_quadrants); + userdataRemapper.remap_block_data(exec_space, userdataBlock_face, userdataBlock_face_new); + } // end apply remapping + + KALYPSSO_INFO_ALL("after remapping"); + + // + // save data after AMR cycle + // + { + std::string filename = + dim == 2 ? "test_userdata_2d_after_amr_cycle" : "test_userdata_3d_after_amr_cycle"; + + // provide mapping between variables Id and variables names + const Hydro_t model(dim); + + DataWriter::save( + filename, userdataLeaf_new, userdataBlock_new, config_map, amr_mesh, model); + + DataWriter::save( + filename + "_face", userdataBlock_face_new, config_map, amr_mesh, "facedata"); + + // compute divergence of the face array + const auto divBdata = + FaceDataArrayBlock_t::compute_divergence(userdataBlock_face_new, mesh_map->orchard_keys()); + + DataWriter::save_scalar( + filename + std::string("_face_div_new"), divBdata, 0, config_map, amr_mesh); + } + +#ifdef KALYPSSO_CORE_USE_MPI + // + // perform load balance, aka mesh re-partitioning + // + { + MeshPartitioner mesh_partitioner(config_map, par_env); + + // after that, forest will change + mesh_partitioner.partition_mesh(forest); + + // repartition user data + + auto userdataBlock_new2 = DataArrayBlock_t( + "test_data_block_after_partition", bSizes, nbvar, forest->local_num_quadrants); + + mesh_partitioner.repartition_userdata(userdataBlock_new, userdataBlock_new2); + + auto userdataLeaf_new2 = DataArrayLeaf_t("test_data_leaf_after_partition", + static_cast(forest->local_num_quadrants), + static_cast(nbvar)); + + mesh_partitioner.repartition_userdata(userdataLeaf_new, userdataLeaf_new2); + + // + // save data after re-partitioning + // + { + std::string filename = + dim == 2 ? "test_userdata_2d_after_partition" : "test_userdata_3d_after_partition"; + + // provide mapping between variables Id and variables names + const Hydro_t model(dim); + + DataWriter::save( + filename, + par_env.nRanks() > 1 ? userdataLeaf_new2 : userdataLeaf_new, + par_env.nRanks() > 1 ? userdataBlock_new2 : userdataBlock_new, + config_map, + amr_mesh, + model); + } + + + // testing MPI ghosts exchange + if constexpr (0 == 1) + { + // update p4est ghost after partition + amr_mesh.reset_ghost(); + + // create array of orchard keys for mirror quadrants + // TODO: see if the following lines should be better used inside MeshGhostsExchanger + auto mirror_keys_pair = std::move(mesh_map->template create_mirror_orchard_keys_views( + amr_mesh.forest(), amr_mesh.ghost(), "orchard_keys_mirror_quads")); + auto orchard_keys_mirror_host_new = mirror_keys_pair.first; + auto orchard_keys_mirror_device_new = mirror_keys_pair.second; + + // create the main object doing MPI comm to exchange ghost data + MeshGhostsExchanger mesh_ghosts_exchanger( + config_map, par_env, amr_mesh); + mesh_ghosts_exchanger.allocate_send_recv_buffers(userdataBlock_new2.extent(0) * + userdataBlock_new2.extent(1)); + } + + } // end load balancing +#endif // KALYPSSO_CORE_USE_MPI + + const auto refine_type = config_map.getString("amr", "refine_type", "normal"); + if (refine_type == "simple") + { + printf("=======================================================\n"); + // display AMR keys on screen + for (uint32_t iOct = 0; iOct < orchard_keys_host.extent(0); ++iOct) + { + printf("iOct=%u key=%lu\n", iOct, orchard_keys_host(iOct)); + } + } + + +} // run_test + +} // namespace kalypsso + +bool +hasEnding(std::string const & fullString, std::string const & ending) +{ + if (fullString.length() >= ending.length()) + { + return (0 == + fullString.compare(fullString.length() - ending.length(), ending.length(), ending)); + } + else + { + return false; + } +} // hasEnding + +// ============================================================================= +// ============================================================================= +// ============================================================================= +int +main(int argc, char ** argv) +{ + + { + // initialize mpi, kokkos and p4est, use default MPI communicator : MPI_COMM_WORLD + kalypsso::ParallelEnv par_env(argc, argv); + +#ifdef KALYPSSO_CORE_USE_SPDLOG + kalypsso::kalypsso_spdlog_config(argc, argv, par_env.rank(), par_env.size()); +#endif + + if (kalypsso::cmdline_arg_exists(argv, argv + argc, "--help")) + { + if (par_env.rank() == 0) + { + // clang-format off + std::cout << "Example cmdline: \"mpirun -np 1 ./test_AMRContext --ini test_AMRmesh_brick_2d.ini --refine_level 6\"\n"; + // clang-format on + } + return 0; + } + + // parse command line : 2d or 3d ? + bool threed = kalypsso::cmdline_arg_exists(argv, argv + argc, "--3d"); + + // check if user passed a custom ini filename + std::string config_filename = kalypsso::cmdline_get_string(argv, argv + argc, "--ini"); + + // provide a default config filename (that exists) + if (config_filename.size() == 0) + config_filename = threed ? "./test_AMRmesh_brick_3d.ini" : "./test_AMRmesh_brick_2d.ini"; + + kalypsso::ConfigMap config_map = kalypsso::broadcast_parameters(config_filename); + + // check if input file is valid, i.e. parameter run/dimension is 2 or 3 + const auto dim = kalypsso::get_dim(config_map); + assertm(dim == 2 or dim == 3, "[test_AMRContext] Wrong dimension"); + + // check command line for optional parameter (refine_level) + kalypsso::InitialAMRSetupBase::refine_level = + kalypsso::cmdline_arg_exists(argv, argv + argc, "--refine_level") + ? kalypsso::cmdline_get_integer(argv, argv + argc, "--refine_level") + : 4; + + if (par_env.rank() == 0) + { + printf("================================================\n"); + printf("START RUN_TEST\n"); + printf("================================================\n"); + } + + if (dim == 2) + { + kalypsso::run_test<2, kalypsso::DefaultDevice>(par_env, config_map); + } + else if (dim == 3) + { + kalypsso::run_test<3, kalypsso::DefaultDevice>(par_env, config_map); + } + else + { + if (par_env.rank() == 0) + { + std::cout << "Input file is not valid ! check parameter run/dimension.\n"; + } + } + + if (par_env.rank() == 0) + { + printf("================================================\n"); + printf("END RUN_TEST\n"); + printf("================================================\n"); + } + } + + return 0; + +} // main diff --git a/test/AMRmesh/test_AMR_ghost_exchange_mpi.cpp b/test/AMRmesh/test_AMR_ghost_exchange_mpi.cpp new file mode 100644 index 0000000..677d647 --- /dev/null +++ b/test/AMRmesh/test_AMR_ghost_exchange_mpi.cpp @@ -0,0 +1,583 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file test_AMR_ghost_exchange_mpi.cpp + * + * Purpose: testing MPI communications used to fill ghost blocks. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#include + +#include +#include + +namespace kalypsso +{ + +// ============================================================================= +// ============================================================================= +template +void +run_test(const ParallelEnv & par_env, const ConfigMap & config_map) +{ + + using Hydro_t = core::models::Hydro; + using DataArrayBlock_t = DataArrayBlock; + + InitialAMRSetup initial_amr_setup(par_env, config_map, InitFunc2{}); + const auto f = InitFunc2{}; + + // setup initial p4est mesh with some test pseudo random refined/coarsen cells pattern + initial_amr_setup.setup_initial_mesh(); + + auto & amr_mesh = initial_amr_setup.mesh(); + auto forest = amr_mesh.forest(); + auto mesh_map = initial_amr_setup.mesh_map(); + + // initialize ghost + amr_mesh.reset_ghost(); + + // retrieve amr keys + mesh_map->update_orchard_keys(amr_mesh.forest(), amr_mesh.ghost()); + auto orchard_keys_host = mesh_map->orchard_keys_host(); + auto orchard_keys_device = mesh_map->orchard_keys(); + + // mirror keys array must be up to date for MeshGhostExchange to be functional + mesh_map->update_mirror_orchard_keys(amr_mesh.ghost()); + + // auto [userdata_leaf, userdata_block] = + // initial_amr_setup.setup_initial_data(orchard_keys_device); + auto userdata_leaf = initial_amr_setup.setup_initial_data_leaf(orchard_keys_device); + auto userdata_block = initial_amr_setup.setup_initial_data_block_new(orchard_keys_device); + auto userdata_block_face = + initial_amr_setup.setup_initial_data_block_face(orchard_keys_device, false); + + // + // save data before MPI ghost exchange + // + { + std::string filename = + dim == 2 ? "test_AMR_ghost_exchange_mpi_before_2d" : "test_AMR_ghost_exchange_mpi_before_3d"; + + // provide mapping between variables Id and variables names + const Hydro_t model(dim); + + DataWriter::save( + filename, userdata_leaf, userdata_block, config_map, amr_mesh, model); + } + + // + // here comes the interesting part : perform MPI ghosts exchange + // + + // + // 1. store the UnorderedMap before MPI ghost exchange + // + + // make sure p4est ghost is up to date (should be ok, since initial AMR setup already initialized + // ghost) + // amr_mesh.reset_ghost(); + auto ghost = amr_mesh.ghost(); + + auto userdata_leaf_host = Kokkos::create_mirror_view(userdata_leaf); + + auto userdata_block_host = DataArrayBlock_t::create_host_mirror_view(userdata_block); + + // compute amr keys map + const bool on_device = true; + + // retrieve hash map of orchard keys + mesh_map->update_hashmap(on_device); + // auto amrkeys_hashmap = mesh_map->hashmap(); + + // + // testing MPI ghosts exchange + // + { + + // create array of orchard keys for mirror quadrants + // TODO: see if the following lines should be better used inside MeshGhostsExchanger + // auto mirror_keys_pair = mesh_map->template create_mirror_orchard_keys_views( + // amr_mesh.forest(), amr_mesh.ghost(), "orchard_keys_mirror_quads"); + // auto orchard_keys_mirror_device = mirror_keys_pair.second; + + // create the main object doing MPI comm to exchange ghost data + MeshGhostsExchanger mesh_ghosts_exchanger( + config_map, par_env, amr_mesh, *mesh_map); + MPI_Barrier(par_env.mpi_comm()); + + mesh_ghosts_exchanger.exchange(userdata_leaf); + Kokkos::deep_copy(userdata_leaf_host, userdata_leaf); + + mesh_ghosts_exchanger.exchange(userdata_block); + Kokkos::deep_copy(userdata_block_host.logical_view(), userdata_block.logical_view()); + + mesh_ghosts_exchanger.exchange(userdata_block_face); + auto userdata_block_face_flat_host = + Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, userdata_block_face.logical_view()); + + auto orchard_keys_mirror_host = mesh_map->mirror_orchard_keys_host(); + + // get geometrical scaling factor + const auto scaling_factor = get_scaling_factor(config_map); + + // get domain lower left corner + const auto xyz_min = get_xyz_min(config_map); + + // + // HERE is the MAIN test + // + + // in the following test, the pivot rank prints its mirror quadrants data (to be sent), and all + // other MPI processes print their ghost data (received from the pivot rank) + // + // a more exhaustive test should use all possible values for the pivot rank, i.e. from 0 to + // MPI_Comm_size - 1. + int pivot_rank = 1; + + // ==================================================================== + // + // LEAF data + // + // ==================================================================== + + // cross-checking that mirrors quadrants in rank "pivot_rank" are also ghosts in + // other mpi ranks + for (int irank = 0; irank < par_env.size(); ++irank) + { + if (irank == par_env.rank()) + { + if (par_env.rank() == pivot_rank) + { + constexpr bool use_center = false; + for (int iproc = 0; iproc < par_env.size(); ++iproc) + { + auto first_mirror = ghost->mirror_proc_offsets[iproc]; + auto num_mirrors = + ghost->mirror_proc_offsets[iproc + 1] - ghost->mirror_proc_offsets[iproc]; + for (int32_t iOct = first_mirror; iOct < first_mirror + num_mirrors; ++iOct) + { + const auto key = orchard_keys_mirror_host(iOct); + const auto xyz_vertex = orchard_key_to_vertex_coord(key, use_center); + const auto xyz = vertex_coord_to_real_space(xyz_vertex, scaling_factor, xyz_min); + + if constexpr (dim == 2) + { + KALYPSSO_INFO("[rank={}] mirror to proc={} imirror={}, key={} x={} y=%{}\n", + par_env.rank(), + iproc, + iOct, + key, + xyz[IX], + xyz[IY]); + } + else if constexpr (dim == 3) + { + KALYPSSO_INFO("[rank={}] mirror to proc={} imirror={}, key={} x={} y={} z={}\n", + par_env.rank(), + iproc, + iOct, + key, + xyz[IX], + xyz[IY], + xyz[IZ]); + } + } + } + } + else + { + constexpr bool use_center = false; + auto first_ghost = forest->local_num_quadrants + ghost->proc_offsets[pivot_rank]; + auto num_ghosts = ghost->proc_offsets[pivot_rank + 1] - ghost->proc_offsets[pivot_rank]; + + for (int32_t iOct = first_ghost; iOct < first_ghost + num_ghosts; ++iOct) + { + const auto key = orchard_keys_host(iOct); + const auto xyz_vertex = orchard_key_to_vertex_coord(key, use_center); + const auto xyz = vertex_coord_to_real_space(xyz_vertex, scaling_factor, xyz_min); + + if constexpr (dim == 2) + { + KALYPSSO_INFO("[rank={}] ghost i={}, key={} x={} y={} data_leaf={}\n", + par_env.rank(), + iOct, + key, + xyz[IX], + xyz[IY], + userdata_leaf_host(iOct, 1)); + } + else if constexpr (dim == 3) + { + KALYPSSO_INFO("[rank={}] ghost i={}, key={} x={} y={} z={} data_leaf={}\n", + par_env.rank(), + iOct, + key, + xyz[IX], + xyz[IY], + xyz[IZ], + userdata_leaf_host(iOct, 1)); + } + } + } + } // end if(irank == par_env.rank()) + MPI_Barrier(par_env.mpi_comm()); + } // end for irank + + // ==================================================================== + // + // cell-center block data + // + // ==================================================================== + + MPI_Barrier(par_env.mpi_comm()); + KALYPSSO_INFO("\n\n"); + + for (int irank = 0; irank < par_env.size(); ++irank) + { + if (irank == par_env.rank()) + { + if (par_env.rank() == pivot_rank) + { + constexpr bool use_center = false; + for (int iproc = 0; iproc < par_env.size(); ++iproc) + { + auto first_mirror = ghost->mirror_proc_offsets[iproc]; + auto num_mirrors = + ghost->mirror_proc_offsets[iproc + 1] - ghost->mirror_proc_offsets[iproc]; + for (int32_t iOct = first_mirror; iOct < first_mirror + num_mirrors; ++iOct) + { + const auto key = orchard_keys_mirror_host(iOct); + const auto xyz_vertex = orchard_key_to_vertex_coord(key, use_center); + const auto xyz = vertex_coord_to_real_space(xyz_vertex, scaling_factor, xyz_min); + + if constexpr (dim == 2) + { + KALYPSSO_INFO("[rank={}] mirror to proc={} imirror={}, key={} x={} y={} - block\n", + par_env.rank(), + iproc, + iOct, + key, + xyz[IX], + xyz[IY]); + } + else if constexpr (dim == 3) + { + KALYPSSO_INFO( + "[rank={}] mirror to proc={} imirror={}, key={} x={} y={} z={} - block\n", + par_env.rank(), + iproc, + iOct, + key, + xyz[IX], + xyz[IY], + xyz[IZ]); + } + } + } + } + else + { + constexpr bool use_center = false; + auto first_ghost = forest->local_num_quadrants + ghost->proc_offsets[pivot_rank]; + auto num_ghosts = ghost->proc_offsets[pivot_rank + 1] - ghost->proc_offsets[pivot_rank]; + const auto block_sizes = initial_amr_setup.block_sizes(); + + for (int32_t iOct = first_ghost; iOct < first_ghost + num_ghosts; ++iOct) + { + const auto key = orchard_keys_host(iOct); + const auto xyz_vertex = orchard_key_to_vertex_coord(key, use_center); + const auto xyz = vertex_coord_to_real_space(xyz_vertex, scaling_factor, xyz_min); + const auto level = orchard_key_t::level(key); + + const int32_t ivar = 1; + const int32_t icell = 6; + const auto iCoord = icell_to_icoord(icell, block_sizes[IX]); + const auto xyz_cell_vertex = + compute_cell_coordinates(level, xyz_vertex, iCoord, block_sizes); + const auto xyz_cell = + vertex_coord_to_real_space(xyz_cell_vertex, scaling_factor, xyz_min); + + // make sure data recv in ghost block are the same as the analytical expected value + // (provided by functor f) + if constexpr (dim == 2) + { + KALYPSSO_INFO( + "[rank={}] ghost i={}, key={} x={} y={} data_block_recv={} data_block_true={} " + "- diff={}\n", + par_env.rank(), + iOct, + key, + xyz[IX], + xyz[IY], + userdata_block_host(icell, ivar, iOct), + f(xyz_cell[IX], xyz_cell[IY], ivar), + userdata_block_host(icell, ivar, iOct) - f(xyz_cell[IX], xyz_cell[IY], ivar)); + } + else if constexpr (dim == 3) + { + KALYPSSO_INFO("[rank={}] ghost i={}, key={} x={} y={} z={} data_block_recv={} " + "data_block_true={} " + "- diff={}\n", + par_env.rank(), + iOct, + key, + xyz[IX], + xyz[IY], + xyz[IZ], + userdata_block_host(icell, ivar, iOct), + f(xyz_cell[IX], xyz_cell[IY], xyz_cell[IZ], ivar), + userdata_block_host(icell, ivar, iOct) - + f(xyz_cell[IX], xyz_cell[IY], xyz_cell[IZ], ivar)); + } + } + } + } // end if(irank == par_env.rank()) + MPI_Barrier(par_env.mpi_comm()); + } // end for irank + + // ==================================================================== + // + // face-center block data + // + // ==================================================================== + + MPI_Barrier(par_env.mpi_comm()); + KALYPSSO_INFO("\n\n"); + + for (int irank = 0; irank < par_env.size(); ++irank) + { + if (irank == par_env.rank()) + { + if (par_env.rank() == pivot_rank) + { + constexpr bool use_center = false; + for (int iproc = 0; iproc < par_env.size(); ++iproc) + { + auto first_mirror = ghost->mirror_proc_offsets[iproc]; + auto num_mirrors = + ghost->mirror_proc_offsets[iproc + 1] - ghost->mirror_proc_offsets[iproc]; + for (int32_t iOct = first_mirror; iOct < first_mirror + num_mirrors; ++iOct) + { + const auto key = orchard_keys_mirror_host(iOct); + const auto xyz_vertex = orchard_key_to_vertex_coord(key, use_center); + const auto xyz = vertex_coord_to_real_space(xyz_vertex, scaling_factor, xyz_min); + + if constexpr (dim == 2) + { + KALYPSSO_INFO( + "[rank={}] mirror to proc={} imirror={}, key={} x={} y={} - block face\n", + par_env.rank(), + iproc, + iOct, + key, + xyz[IX], + xyz[IY]); + } + else if constexpr (dim == 3) + { + KALYPSSO_INFO( + "[rank={}] mirror to proc={} imirror={}, key={} x={} y={} z={} - block face\n", + par_env.rank(), + iproc, + iOct, + key, + xyz[IX], + xyz[IY], + xyz[IZ]); + } + } + } + } + else + { + // constexpr bool use_center = false; + auto first_ghost = forest->local_num_quadrants + ghost->proc_offsets[pivot_rank]; + auto num_ghosts = ghost->proc_offsets[pivot_rank + 1] - ghost->proc_offsets[pivot_rank]; + const auto block_sizes = initial_amr_setup.block_sizes(); + const auto num_elts_per_octant = userdata_block_face.num_elements_per_octant(); + + for (int32_t iOct = first_ghost; iOct < first_ghost + num_ghosts; ++iOct) + { + const auto key = orchard_keys_host(iOct); + // const auto xyz_vertex = orchard_key_to_vertex_coord(key, use_center); + // const auto xyz = vertex_coord_to_real_space(xyz_vertex, scaling_factor, + // xyz_min); + + // face flat index is in range [0, userdata_block_face.num_elements_per_octants-1] + const int32_t face_flat_index = 6; + + const auto face_indexes = face_flat_index_unravel(face_flat_index, + block_sizes, + userdata_block_face.offsets(), + userdata_block_face.shift()); + + const auto xyz = orchard_key_to_facecenter_real_space( + key, face_indexes, block_sizes[IX], scaling_factor, xyz_min); + + const auto & ivar = face_indexes[dim]; + + // make sure data recv in ghost block are the same as the analytical expected value + // (provided by functor f) + if constexpr (dim == 2) + { + KALYPSSO_INFO( + "[rank={}] ghost i={}, key={} x={} y={} data_block_face_recv={} " + "data_block_face_true={} " + "- diff={}\n", + par_env.rank(), + iOct, + key, + xyz[IX], + xyz[IY], + userdata_block_face_flat_host(face_flat_index + num_elts_per_octant * iOct), + f(xyz[IX], xyz[IY], ivar), + userdata_block_face_flat_host(face_flat_index + num_elts_per_octant * iOct) - + f(xyz[IX], xyz[IY], ivar)); + } + else if constexpr (dim == 3) + { + KALYPSSO_INFO( + "[rank={}] ghost i={}, key={} x={} y={} z={} data_block_face_recv={} " + "data_block_face_true={} " + "- diff={}\n", + par_env.rank(), + iOct, + key, + xyz[IX], + xyz[IY], + xyz[IZ], + userdata_block_face_flat_host(face_flat_index + num_elts_per_octant * iOct), + f(xyz[IX], xyz[IY], xyz[IZ], ivar), + userdata_block_face_flat_host(face_flat_index + num_elts_per_octant * iOct) - + f(xyz[IX], xyz[IY], xyz[IZ], ivar)); + } + } + } + } // end if(irank == par_env.rank()) + MPI_Barrier(par_env.mpi_comm()); + } // end for irank + + } // end testing MPI ghosts exchange + + MPI_Barrier(par_env.mpi_comm()); + +} // run_test + +} // namespace kalypsso + +bool +hasEnding(std::string const & fullString, std::string const & ending) +{ + if (fullString.length() >= ending.length()) + { + return (0 == + fullString.compare(fullString.length() - ending.length(), ending.length(), ending)); + } + else + { + return false; + } +} // hasEnding + +// ============================================================================= +// ============================================================================= +// ============================================================================= +int +main(int argc, char ** argv) +{ + + { + // initialize mpi, kokkos and p4est, use default MPI communicator : MPI_COMM_WORLD + kalypsso::ParallelEnv par_env(argc, argv); + +#ifdef KALYPSSO_CORE_USE_SPDLOG + kalypsso::kalypsso_spdlog_config(argc, argv, par_env.rank(), par_env.size()); +#endif + + if (kalypsso::cmdline_arg_exists(argv, argv + argc, "--help")) + { + if (par_env.rank() == 0) + { + // clang-format off + std::cout << "Example cmdline: \"mpirun -np 4 ./test_AMR_ghost_exchange_mpi --ini test_AMRmesh_brick_2d_ghost_exchange_mpi.ini --refine_level 3\"\n"; + // clang-format on + } + return 0; + } + + // parse command line : 2d or 3d ? + bool threed = kalypsso::cmdline_arg_exists(argv, argv + argc, "--3d"); + + // check if user passed a custom ini filename + std::string config_filename = kalypsso::cmdline_get_string(argv, argv + argc, "--ini"); + + // provide a default config filename (that exists) + if (config_filename.size() == 0) + config_filename = threed ? "./test_AMRmesh_brick_3d_ghost_exchange_mpi.ini" + : "./test_AMRmesh_brick_2d_ghost_exchange_mpi.ini"; + + kalypsso::ConfigMap config_map = kalypsso::broadcast_parameters(config_filename); + + // check if input file is valid, i.e. parameter run/dimension is 2 or 3 + const auto dim = kalypsso::get_dim(config_map); + assertm(dim == 2 or dim == 3, "[test_AMR_ghost_exchange_mpi] Wrong dimension"); + + // check command line for optional parameter (refine_level) + kalypsso::InitialAMRSetupBase::refine_level = + kalypsso::cmdline_arg_exists(argv, argv + argc, "--refine_level") + ? kalypsso::cmdline_get_integer(argv, argv + argc, "--refine_level") + : 4; + + if (par_env.rank() == 0) + { + printf("================================================\n"); + printf("START RUN_TEST\n"); + printf("================================================\n"); + } + + if (dim == 2) + { + kalypsso::run_test<2, kalypsso::DefaultDevice>(par_env, config_map); + } + else if (dim == 3) + { + kalypsso::run_test<3, kalypsso::DefaultDevice>(par_env, config_map); + } + else + { + if (par_env.rank() == 0) + { + std::cout << "Input file is not valid ! check parameter run/dimension.\n"; + } + } + + if (par_env.rank() == 0) + { + printf("================================================\n"); + printf("END RUN_TEST\n"); + printf("================================================\n"); + } + } + + return 0; + +} // main diff --git a/test/AMRmesh/test_AMRmesh.cpp b/test/AMRmesh/test_AMRmesh.cpp new file mode 100644 index 0000000..4195ed5 --- /dev/null +++ b/test/AMRmesh/test_AMRmesh.cpp @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file test_AMRmesh.cpp + */ +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#include + +namespace kalypsso +{ + +/* ============================================================ */ +/* ============================================================ */ +/* ============================================================ */ +template +void +run_test(const ParallelEnv & par_env, [[maybe_unused]] int argc, [[maybe_unused]] char * argv[]) +{ + using namespace p4est; + + ConfigMap config_map = + broadcast_parameters(dim == 2 ? "./test_AMRmesh_2d.ini" : "./test_AMRmesh_3d.ini"); + + if (par_env.rank() == 0) + printf("================================================\n"); + + AMRmesh amr_mesh(par_env, config_map); + + auto forest = amr_mesh.forest(); + // auto conn = amr_mesh.connectivity(); + auto geom = amr_mesh.geometry(); + + auto conn_name = config_map.getString("amr", "connectivity", "invalid_connectivity"); + auto geom_name = geom->name; + + if (par_env.rank() == 0) + { + printf("Running a %dD test with connectivity %s and geometry %s\n", + dim, + conn_name.c_str(), + geom_name); + } + + printf("Forest has %ld global octants, %d local octants on MPI proc %d.\n", + forest->global_num_quadrants, + forest->local_num_quadrants, + par_env.rank()); + +} // run_test + +} // namespace kalypsso + +// ====================================================== +// ====================================================== +// ====================================================== +int +main(int argc, char ** argv) +{ + + + kalypsso::ParallelEnv par_env(argc, argv); + + if (kalypsso::cmdline_arg_exists(argv, argv + argc, "--help")) + { + if (par_env.rank() == 0) + { + // clang-format off + std::cout << "Doing nothing but creating an instance of AMRmesh class.\n"; + std::cout << "Example cmdline: \"./test_AMRmesh\"\n"; + // clang-format on + } + return 0; + } + // run a 2d test + kalypsso::run_test<2>(par_env, argc, argv); + + // run a 3d test + kalypsso::run_test<3>(par_env, argc, argv); + + return 0; +} diff --git a/test/AMRmesh/test_AMRmesh_2d.ini b/test/AMRmesh/test_AMRmesh_2d.ini new file mode 100644 index 0000000..05e9ffe --- /dev/null +++ b/test/AMRmesh/test_AMRmesh_2d.ini @@ -0,0 +1,66 @@ +[run] +dimension=2 +tEnd=0.5 +nStepmax=10 + +# noutput equals -1 means we dump data at every time steps +nOutput=-1 + +[amr] +level_min=4 +level_max=6 + +use_block_data=yes +bx=4 +by=4 + +# init step may contain calls to globalRefine +# if the following option is true, we interleave +# calls to load balancing in between globalRefine +# if the following option is false, a single load +# balancing operation is done at the end of +# globalRefine steps +enable_load_balance_during_init=false + +epsilon_refine=0.01 +epsilon_coarsen=0.03 + +connectivity = disk2d +geometry = disk2d + +[mesh] +xmin=0.0 +ymin=0.0 + +scaling_factor=1.0 + +boundary_type_xmin=WALL +boundary_type_xmax=WALL + +boundary_type_ymin=WALL +boundary_type_ymax=WALL + +[hydro] +gamma0=1.666 +cfl=0.8 +niter_riemann=10 +slope_type=2 +problem=blast +riemann=HLLC + +[blast] +density_in=1.0 +density_out=1.2 +radius=0.25 + +[output] +outputDir=./ +outputPrefix=test_hdf5 +outputVtkAscii=false +write_variables=rho,rho_vx +write_mesh_info=yes +vtk_enabled=no +hdf5_enabled=yes + +[other] +implementationVersion=0 diff --git a/test/AMRmesh/test_AMRmesh_3d.ini b/test/AMRmesh/test_AMRmesh_3d.ini new file mode 100644 index 0000000..c27fe3b --- /dev/null +++ b/test/AMRmesh/test_AMRmesh_3d.ini @@ -0,0 +1,71 @@ +[run] +dimension=3 +tEnd=0.5 +nStepmax=10 + +# noutput equals -1 means we dump data at every time steps +nOutput=-1 + +[amr] +level_min=4 +level_max=6 + +use_block_data=yes +bx=4 +by=4 +bz=4 + +# init step may contain calls to globalRefine +# if the following option is true, we interleave +# calls to load balancing in between globalRefine +# if the following option is false, a single load +# balancing operation is done at the end of +# globalRefine steps +enable_load_balance_during_init=false + +epsilon_refine=0.01 +epsilon_coarsen=0.03 + +connectivity = sphere +geometry = sphere + +[mesh] +xmin=0.0 +ymin=0.0 +zmin=0.0 + +scaling_factor=1.0 + +boundary_type_xmin=WALL +boundary_type_xmax=WALL + +boundary_type_ymin=WALL +boundary_type_ymax=WALL + +boundary_type_zmin=WALL +boundary_type_zmax=WALL + +[hydro] +gamma0=1.666 +cfl=0.8 +niter_riemann=10 +slope_type=2 +problem=blast +riemann=HLLC + +[blast] +density_in=1.0 +density_out=1.2 +radius=0.25 + +[output] +outputDir=./ +outputPrefix=test_hdf5 +outputVtkAscii=false +write_variables=rho,rho_vx +write_mesh_info=yes +vtk_enabled=no +hdf5_enabled=yes + +[other] +implementationVersion=0 diff --git a/test/AMRmesh/test_AMRmesh_brick_2d.ini b/test/AMRmesh/test_AMRmesh_brick_2d.ini new file mode 100644 index 0000000..b62279d --- /dev/null +++ b/test/AMRmesh/test_AMRmesh_brick_2d.ini @@ -0,0 +1,72 @@ +[run] +dimension=2 +tEnd=0.5 +nStepmax=10 + +# noutput equals -1 means we dump data at every time steps +nOutput=-1 + +[amr] +level_min=4 +level_max=6 + +use_block_data=yes +bx=4 +by=4 + +# init step may contain calls to globalRefine +# if the following option is true, we interleave +# calls to load balancing in between globalRefine +# if the following option is false, a single load +# balancing operation is done at the end of +# globalRefine steps +enable_load_balance_during_init=false + +epsilon_refine=0.01 +epsilon_coarsen=0.0025 + +connectivity = brick + +[p4est_connectivity] +nbrick_x=3 +nbrick_y=2 + +periodic_x=1 +periodic_y=1 + +[mesh] +xmin=0.0 +ymin=0.0 +scaling_factor=1.0 + +boundary_type_xmin=WALL +boundary_type_xmax=WALL + +boundary_type_ymin=WALL +boundary_type_ymax=WALL + +[hydro] +gamma0=1.666 +cfl=0.8 +niter_riemann=10 +slope_type=2 +problem=blast +riemann=HLLC + +[blast] +density_in=1.0 +density_out=1.2 +radius=0.25 + +[output] +outputDir=./ +outputPrefix=test_hdf5 +outputVtkAscii=false +write_variables=rho,rho_vx +write_mesh_info=yes +vtk_enabled=no +hdf5_enabled=yes +write_iOct=yes + +[other] +implementationVersion=0 diff --git a/test/AMRmesh/test_AMRmesh_brick_2d_ghost_exchange_mpi.ini b/test/AMRmesh/test_AMRmesh_brick_2d_ghost_exchange_mpi.ini new file mode 100644 index 0000000..2c24cc2 --- /dev/null +++ b/test/AMRmesh/test_AMRmesh_brick_2d_ghost_exchange_mpi.ini @@ -0,0 +1,71 @@ +[run] +dimension=2 +tEnd=0.5 +nStepmax=10 + +# noutput equals -1 means we dump data at every time steps +nOutput=-1 + +[amr] +level_min=2 +level_max=4 + +use_block_data=yes +bx=4 +by=4 + +# init step may contain calls to globalRefine +# if the following option is true, we interleave +# calls to load balancing in between globalRefine +# if the following option is false, a single load +# balancing operation is done at the end of +# globalRefine steps +enable_load_balance_during_init=false + +epsilon_refine=0.01 +epsilon_coarsen=0.03 + +connectivity = brick + +[p4est_connectivity] +nbrick_x=3 +nbrick_y=2 + +periodic_x=1 +periodic_y=1 + +[mesh] +xmin=0.0 +ymin=0.0 +scaling_factor=1.0 + +boundary_type_xmin=WALL +boundary_type_xmax=WALL + +boundary_type_ymin=WALL +boundary_type_ymax=WALL + +[hydro] +gamma0=1.666 +cfl=0.8 +niter_riemann=10 +slope_type=2 +problem=blast +riemann=HLLC + +[blast] +density_in=1.0 +density_out=1.2 +radius=0.25 + +[output] +outputDir=./ +outputPrefix=test_hdf5 +outputVtkAscii=false +write_variables=rho,rho_vx +write_mesh_info=yes +vtk_enabled=no +hdf5_enabled=yes + +[other] +implementationVersion=0 diff --git a/test/AMRmesh/test_AMRmesh_brick_3d.ini b/test/AMRmesh/test_AMRmesh_brick_3d.ini new file mode 100644 index 0000000..9b996c6 --- /dev/null +++ b/test/AMRmesh/test_AMRmesh_brick_3d.ini @@ -0,0 +1,79 @@ +[run] +dimension=3 +tEnd=0.5 +nStepmax=10 + +# noutput equals -1 means we dump data at every time steps +nOutput=-1 + +[amr] +level_min=4 +level_max=6 + +use_block_data=yes +bx=4 +by=4 +bz=4 + +# init step may contain calls to globalRefine +# if the following option is true, we interleave +# calls to load balancing in between globalRefine +# if the following option is false, a single load +# balancing operation is done at the end of +# globalRefine steps +enable_load_balance_during_init=false + +epsilon_refine=0.01 +epsilon_coarsen=0.0025 + +connectivity = brick + +[p4est_connectivity] +nbrick_x=3 +nbrick_y=2 +nbrick_z=4 + +periodic_x=1 +periodic_y=1 +periodic_z=1 + +[mesh] +xmin=0.0 +ymin=0.0 +zmin=0.0 +scaling_factor=1.0 + +boundary_type_xmin=WALL +boundary_type_xmax=WALL + +boundary_type_ymin=WALL +boundary_type_ymax=WALL + +boundary_type_zmin=WALL +boundary_type_zmax=WALL + +[hydro] +gamma0=1.666 +cfl=0.8 +niter_riemann=10 +slope_type=2 +problem=blast +riemann=HLLC + +[blast] +density_in=1.0 +density_out=1.2 +radius=0.25 + +[output] +outputDir=./ +outputPrefix=test_hdf5 +outputVtkAscii=false +write_variables=rho,rho_vx +write_mesh_info=yes +vtk_enabled=no +hdf5_enabled=yes +write_iOct=yes + +[other] +implementationVersion=0 diff --git a/test/AMRmesh/test_AMRmesh_brick_3d_ghost_exchange_mpi.ini b/test/AMRmesh/test_AMRmesh_brick_3d_ghost_exchange_mpi.ini new file mode 100644 index 0000000..a1f5f7d --- /dev/null +++ b/test/AMRmesh/test_AMRmesh_brick_3d_ghost_exchange_mpi.ini @@ -0,0 +1,78 @@ +[run] +dimension=3 +tEnd=0.5 +nStepmax=10 + +# noutput equals -1 means we dump data at every time steps +nOutput=-1 + +[amr] +level_min=4 +level_max=6 + +use_block_data=yes +bx=4 +by=4 +bz=4 + +# init step may contain calls to globalRefine +# if the following option is true, we interleave +# calls to load balancing in between globalRefine +# if the following option is false, a single load +# balancing operation is done at the end of +# globalRefine steps +enable_load_balance_during_init=false + +epsilon_refine=0.01 +epsilon_coarsen=0.03 + +connectivity = brick + +[p4est_connectivity] +nbrick_x=3 +nbrick_y=2 +nbrick_y=4 + +periodic_x=1 +periodic_y=1 +periodic_z=1 + +[mesh] +xmin=0.0 +ymin=0.0 +zmin=0.0 +scaling_factor=1.0 + +boundary_type_xmin=WALL +boundary_type_xmax=WALL + +boundary_type_ymin=WALL +boundary_type_ymax=WALL + +boundary_type_zmin=WALL +boundary_type_zmax=WALL + +[hydro] +gamma0=1.666 +cfl=0.8 +niter_riemann=10 +slope_type=2 +problem=blast +riemann=HLLC + +[blast] +density_in=1.0 +density_out=1.2 +radius=0.25 + +[output] +outputDir=./ +outputPrefix=test_hdf5 +outputVtkAscii=false +write_variables=rho,rho_vx +write_mesh_info=yes +vtk_enabled=no +hdf5_enabled=yes + +[other] +implementationVersion=0 diff --git a/test/AMRmesh/test_AMRmesh_fill_block_ghost_cells.cpp b/test/AMRmesh/test_AMRmesh_fill_block_ghost_cells.cpp new file mode 100644 index 0000000..cbe3589 --- /dev/null +++ b/test/AMRmesh/test_AMRmesh_fill_block_ghost_cells.cpp @@ -0,0 +1,419 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file test_AMRmesh_fill_block_ghost_cells.cpp + * + * Purpose: test and validate code used to fill ghost cells around blocks (leaf of octree). + * + * Input array is non-ghosted + * Output array is ghosted + * + */ + +#include +#include +#include +#include +#include +#include +#ifdef KALYPSSO_CORE_USE_MPI +# include +#endif // KALYPSSO_CORE_USE_MPI + +#include + +#include + +#include +#include +#include + +#include +#include +#include + +#include + +namespace kalypsso +{ + +// ============================================================================= +// ============================================================================= +template +void +run_test(const ParallelEnv & par_env, const ConfigMap & config_map) +{ + using exec_space = typename device_t::execution_space; + + using Hydro_t = core::models::Hydro; + + using DataArrayBlock_t = DataArrayBlock; + + InitialAMRSetup initial_amr_setup(par_env, config_map, InitFunc{}); + + auto level_min = config_map.getInteger("amr", "level_min", 2); + auto level_max = config_map.getInteger("amr", "level_max", 2); + auto no_refine = (level_min == level_max); + + // setup initial p4est mesh with some test pseudo random refined/coarsen cells pattern + initial_amr_setup.setup_initial_mesh(no_refine); + + auto & amr_mesh = initial_amr_setup.mesh(); + auto mesh_map = initial_amr_setup.mesh_map(); + auto brick_sizes = initial_amr_setup.brick_sizes(); + auto is_brick_periodic = initial_amr_setup.is_brick_periodic(); + auto amr_mesh_info = initial_amr_setup.amr_mesh_info(); + + // initialize ghost + amr_mesh.reset_ghost(); + + // retrieve amr keys + mesh_map->update_orchard_keys(amr_mesh.forest(), amr_mesh.ghost()); + auto orchard_keys_host = mesh_map->orchard_keys_host(); + auto orchard_keys_device = mesh_map->orchard_keys(); + +#ifdef KALYPSSO_CORE_USE_MPI + // mirror keys array must be up to date for MeshGhostExchanger to be functional + mesh_map->update_mirror_orchard_keys(amr_mesh.ghost()); +#endif // KALYPSSO_CORE_USE_MPI + + // rebuild the hashmap (must be done after AMR cycle) + constexpr bool on_device = true; + mesh_map->update_hashmap(on_device); + auto amr_hashmap_device = mesh_map->hashmap(); + + // auto [userdata_leaf, userdata_block] = + // initial_amr_setup.setup_initial_data(orchard_keys_device); + auto userdata_leaf = initial_amr_setup.setup_initial_data_leaf(orchard_keys_device); + auto userdata_block = initial_amr_setup.setup_initial_data_block_new(orchard_keys_device); + + userdata_block.resize(amr_mesh_info.local_num_quadrants_total()); + + // auto userdata_block_host = Kokkos::create_mirror_view(userdata_block); + auto userdata_block_host = DataArrayBlock_t::create_host_mirror_view(userdata_block); + + auto userdata_ghosted_block = + initial_amr_setup.setup_initial_data_ghosted_block(orchard_keys_device, true); + + userdata_ghosted_block.resize(amr_mesh_info.local_num_quadrants_total()); + + // reset (for test) + Kokkos::deep_copy(userdata_ghosted_block.flat_view(), 0.0); + + // + // save data before filling block ghosts + // + { + std::string filename = dim == 2 ? "test_AMR_fill_block_noghosts_2d_before" + : "test_AMR_fill_block_noghosts_3d_before"; + + // provide mapping between variables Id and variables names + const Hydro_t model(dim); + + DataWriter::save(filename, userdata_block, config_map, amr_mesh, model); + } + +#ifdef KALYPSSO_CORE_USE_MPI + // + // make sure MPI ghosts are OK + // + + // create the main object doing MPI comm to exchange ghost data + MeshGhostsExchanger mesh_ghosts_exchanger( + config_map, par_env, amr_mesh, *mesh_map); + MPI_Barrier(par_env.mpi_comm()); + + // auto ghosted_data = userdata_ghosted_block.view(); + + mesh_ghosts_exchanger.exchange(userdata_block); + + // auto ghosted_data_host = Kokkos::create_mirror_view(ghosted_data); + // Kokkos::deep_copy(ghosted_data_host, ghosted_data); +#endif // KALYPSSO_CORE_USE_MPI + + // + // Make sure outside cells are ok (this non-op if user set the mesh as periodic) + // + { + using bc_array_t = BorderConditionsConfig::bc_array_t; + + bc_array_t bc_types = [=]() { + if constexpr (dim == 2) + { + // clang-format off + return bc_array_t{ + test::BC_HYDRO::ANALYTICAL, + test::BC_HYDRO::ANALYTICAL, + test::BC_HYDRO::ANALYTICAL, + test::BC_HYDRO::ANALYTICAL + }; + // clang-format on + } + else if constexpr (dim == 3) + { + return bc_array_t{ test::BC_HYDRO::ANALYTICAL, test::BC_HYDRO::ANALYTICAL, + test::BC_HYDRO::ANALYTICAL, test::BC_HYDRO::ANALYTICAL, + test::BC_HYDRO::ANALYTICAL, test::BC_HYDRO::ANALYTICAL }; + } + }(); + + // fill outside userdata + test::FillOutsideCellFunctor::apply(config_map, + amr_hashmap_device, + orchard_keys_device, + amr_mesh_info, + userdata_block, + initial_amr_setup.block_sizes(), + brick_sizes, + mesh_map->is_brick_periodic(), + bc_types, + InitFunc{}); + } + + // + // filling ghost blocks (piecewise) + // + { + const auto num_cells = userdata_ghosted_block.num_cells(); + const auto num_vars = userdata_ghosted_block.num_vars(); + + const auto nbOcts = amr_mesh_info.local_num_quadrants(); + + // process octant in a picewise manner + const int32_t num_octants_per_group = 32; + + const int32_t nbGroups = (nbOcts + num_octants_per_group - 1) / num_octants_per_group; + + auto userdata_ghosted_block_range = + DataArrayGhostedBlock(userdata_ghosted_block.block_size(), + userdata_ghosted_block.ghosted_block_size(), + userdata_ghosted_block.shift(), + "userdata_ghosted_block_range", + userdata_ghosted_block.num_vars(), + num_octants_per_group); + + for (int32_t iGroup = 0; iGroup < nbGroups; ++iGroup) + { + const auto iOct_begin = iGroup * num_octants_per_group; + const auto num_octants_to_process = + (iGroup == (nbGroups - 1)) ? nbOcts - iOct_begin : num_octants_per_group; + + FillBlockGhostCellsFunctor::apply(config_map, + amr_hashmap_device, + orchard_keys_device, + nbOcts, + iOct_begin, + num_octants_to_process, + userdata_block, + userdata_ghosted_block_range, + brick_sizes, + is_brick_periodic); + + // copy back results + { + // source view + auto data_range_in = + std::pair(0, num_cells * num_vars * num_octants_to_process); + auto userdata_ghosted_block_range_view = + Kokkos::subview(userdata_ghosted_block_range.flat_view(), data_range_in); + + // destination view + auto data_range_out = std::pair( + num_cells * num_vars * iOct_begin, + num_cells * num_vars * (iOct_begin + num_octants_to_process)); + auto userdata_ghosted_block_subview = + Kokkos::subview(userdata_ghosted_block.flat_view(), data_range_out); + + // copy + Kokkos::deep_copy(userdata_ghosted_block_subview, userdata_ghosted_block_range_view); + } + } + } + + { + std::string filename = + dim == 2 ? "test_AMR_fill_block_ghosts_2d_after" : "test_AMR_fill_block_ghosts_3d_after"; + + // provide mapping between variables Id and variables names + const Hydro_t model(dim); + + DataWriter::save( + filename, userdata_ghosted_block, config_map, amr_mesh, true, model); + } + + // create data array with ghost block filled with analytical values + auto userdata_ghosted_block_true = + initial_amr_setup.setup_initial_data_ghosted_block(orchard_keys_device, true); + + // perform comparison + auto diff = initial_amr_setup.compute_diff_ghosted_block( + orchard_keys_device, userdata_ghosted_block, userdata_ghosted_block_true); + + { + std::string filename = + dim == 2 ? "test_AMR_fill_block_ghosts_2d_diff" : "test_AMR_fill_block_ghosts_3d_diff"; + + // provide mapping between variables Id and variables names + const Hydro_t model(dim); + + DataWriter::save(filename, diff, config_map, amr_mesh, true, model); + } + + // + // perform a reduce to check everything is ok (this is a unit test) + // + real_t l2_square_diff = 0; + Kokkos::Sum reducer_diff(l2_square_diff); + auto diff_v = diff.data(); + auto userdata_ghosted_block_v = userdata_ghosted_block.data(); + auto userdata_ghosted_block_true_v = userdata_ghosted_block_true.data(); + + Kokkos::parallel_reduce( + "Compute_l2_square_diff", + Kokkos::MDRangePolicy>( + { 0, 0, 0 }, + { diff_v.num_cells(), + diff_v.num_vars(), + //(int32_t)diff_v.num_quadrants() + amr_mesh_info.local_num_quadrants() + 0 * amr_mesh_info.local_num_ghosts() }), + KOKKOS_LAMBDA(const int32_t i, const int32_t j, const int32_t k, real_t & local_sum) { + local_sum += diff_v(i, j, k) * diff_v(i, j, k); + // if (diff_v(i, j, k) * diff_v(i, j, k) > 0) + // { + // KALYPSSO_INFO("error at ({},{},{}) {} | {} {}\n", + // i, + // j, + // k, + // diff_v(i, j, k), + // userdata_ghosted_block_v(i, j, k), + // userdata_ghosted_block_true_v(i, j, k)); + // } + }, + reducer_diff); + + real_t l2_square_true = 0; + Kokkos::Sum reducer_true(l2_square_true); + // auto userdata_ghosted_block_v = userdata_ghosted_block.view(); + + Kokkos::parallel_reduce( + "Compute_l2_square_true", + Kokkos::MDRangePolicy>( + { 0, 0, 0 }, + { userdata_ghosted_block_v.num_cells(), + userdata_ghosted_block_v.num_vars(), + /*(int32_t)userdata_ghosted_block_v.num_quadrants()*/ + amr_mesh_info.local_num_quadrants() }), + KOKKOS_LAMBDA(const int32_t i, const int32_t j, const int32_t k, real_t & local_sum) { + local_sum += userdata_ghosted_block_v(i, j, k) * userdata_ghosted_block_v(i, j, k); + }, + reducer_true); + + KALYPSSO_INFO("[MPI rank={}] Relative L2 norm is : {}\n", + par_env.rank(), + sqrt(l2_square_diff / l2_square_true)); + +} // run_test + +} // namespace kalypsso + +bool +hasEnding(std::string const & fullString, std::string const & ending) +{ + if (fullString.length() >= ending.length()) + { + return (0 == + fullString.compare(fullString.length() - ending.length(), ending.length(), ending)); + } + else + { + return false; + } +} // hasEnding + +// ============================================================================= +// ============================================================================= +// ============================================================================= +int +main(int argc, char ** argv) +{ + + { + // initialize mpi, kokkos and p4est, use default MPI communicator : MPI_COMM_WORLD + kalypsso::ParallelEnv par_env(argc, argv); + +#ifdef KALYPSSO_CORE_USE_SPDLOG + kalypsso::kalypsso_spdlog_config(argc, argv, par_env.rank(), par_env.size()); +#endif + + if (kalypsso::cmdline_arg_exists(argv, argv + argc, "--help")) + { + if (par_env.rank() == 0) + { + // clang-format off + std::cout << "Example cmdline: \"mpirun -np 4 ./test_AMRmesh_fill_block_ghost_cells --ini test_AMRmesh_fill_ghost_brick_2d.ini --refine_level 6\"\n"; + // clang-format on + } + return 0; + } + + // parse command line : 2d or 3d ? + bool use_3d = kalypsso::cmdline_arg_exists(argv, argv + argc, "--3d"); + + // check if user passed a custom ini filename + std::string config_filename = kalypsso::cmdline_get_string(argv, argv + argc, "--ini"); + + // provide a default config filename (that exists) + if (config_filename.size() == 0) + config_filename = use_3d ? "./test_AMRmesh_fill_ghost_brick_3d.ini" + : "./test_AMRmesh_fill_ghost_brick_2d.ini"; + + kalypsso::ConfigMap config_map = kalypsso::broadcast_parameters(config_filename); + + // check if input file is valid, i.e. parameter run/dimension is 2 or 3 + const auto dim = kalypsso::get_dim(config_map); + assertm(dim == 2 or dim == 3, "[test_AMRmesh_fill_block_ghost_cells] Wrong dimension"); + + // check command line for optional parameter (refine_level) + kalypsso::InitialAMRSetupBase::refine_level = + kalypsso::cmdline_arg_exists(argv, argv + argc, "--refine_level") + ? kalypsso::cmdline_get_integer(argv, argv + argc, "--refine_level") + : 4; + + if (par_env.rank() == 0) + { + printf("================================================\n"); + printf("START RUN_TEST\n"); + printf("================================================\n"); + } + + using InitFunc = kalypsso::InitFunc2; + + if (dim == 2) + { + kalypsso::run_test<2, kalypsso::DefaultDevice, InitFunc>(par_env, config_map); + } + else if (dim == 3) + { + kalypsso::run_test<3, kalypsso::DefaultDevice, InitFunc>(par_env, config_map); + } + else + { + if (par_env.rank() == 0) + { + std::cout << "Input file is not valid ! check parameter run/dimension.\n"; + } + } + + if (par_env.rank() == 0) + { + printf("================================================\n"); + printf("END RUN_TEST\n"); + printf("================================================\n"); + } + } + + return 0; + +} // main diff --git a/test/AMRmesh/test_AMRmesh_fill_block_ghost_cells_in_place.cpp b/test/AMRmesh/test_AMRmesh_fill_block_ghost_cells_in_place.cpp new file mode 100644 index 0000000..78a538f --- /dev/null +++ b/test/AMRmesh/test_AMRmesh_fill_block_ghost_cells_in_place.cpp @@ -0,0 +1,366 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file test_AMRmesh_fill_block_ghost_cells_in_place.cpp + * + * Purpose: test and validate code used to fill ghost cells around blocks when input/output data is + * a DataArrayGhostedBlock object. + * + * + */ + +#include +#include +#include +#include +#include +#include +#ifdef KALYPSSO_CORE_USE_MPI +# include +#endif // KALYPSSO_CORE_USE_MPI + +#include + +#include + +#include +#include +#include + +#include +#include +#include + +#include + +namespace kalypsso +{ + +// ============================================================================= +// ============================================================================= +template +void +run_test(const ParallelEnv & par_env, const ConfigMap & config_map) +{ + using exec_space = typename device_t::execution_space; + + using Hydro_t = core::models::Hydro; + + // using DataArrayBlock_t = DataArrayBlock; + + InitialAMRSetup initial_amr_setup(par_env, config_map, InitFunc{}); + + auto level_min = config_map.getInteger("amr", "level_min", 2); + auto level_max = config_map.getInteger("amr", "level_max", 2); + auto no_refine = (level_min == level_max); + + // setup initial p4est mesh with some test pseudo random refined/coarsen cells pattern + initial_amr_setup.setup_initial_mesh(no_refine); + + auto & amr_mesh = initial_amr_setup.mesh(); + auto mesh_map = initial_amr_setup.mesh_map(); + auto brick_sizes = initial_amr_setup.brick_sizes(); + auto is_brick_periodic = initial_amr_setup.is_brick_periodic(); + auto amr_mesh_info = initial_amr_setup.amr_mesh_info(); + + // initialize ghost + amr_mesh.reset_ghost(); + + // retrieve amr keys + mesh_map->update_orchard_keys(amr_mesh.forest(), amr_mesh.ghost()); + auto orchard_keys_host = mesh_map->orchard_keys_host(); + auto orchard_keys_device = mesh_map->orchard_keys(); + +#ifdef KALYPSSO_CORE_USE_MPI + // mirror keys array must be up to date for MeshGhostExchanger to be functional + mesh_map->update_mirror_orchard_keys(amr_mesh.ghost()); +#endif // KALYPSSO_CORE_USE_MPI + + // rebuild the hashmap (must be done after AMR cycle) + constexpr bool on_device = true; + mesh_map->update_hashmap(on_device); + auto amr_hashmap_device = mesh_map->hashmap(); + + auto userdata_ghosted_block = + initial_amr_setup.setup_initial_data_ghosted_block(orchard_keys_device, false); + + userdata_ghosted_block.resize(amr_mesh_info.local_num_quadrants_total()); + + // + // save data before filling block ghosts + // + { + std::string filename = dim == 2 ? "test_AMR_fill_block_noghosts_in_place_2d_before" + : "test_AMR_fill_block_noghosts_in_place_3d_before"; + + // provide mapping between variables Id and variables names + const Hydro_t model(dim); + + DataWriter::save( + filename, userdata_ghosted_block, config_map, amr_mesh, true, model); + } + +#ifdef KALYPSSO_CORE_USE_MPI + // + // make sure MPI ghosts are OK + // + + // create the main object doing MPI comm to exchange ghost data + MeshGhostsExchanger mesh_ghosts_exchanger( + config_map, par_env, amr_mesh, *mesh_map); + MPI_Barrier(par_env.mpi_comm()); + + mesh_ghosts_exchanger.exchange(userdata_ghosted_block.data()); + +#endif // KALYPSSO_CORE_USE_MPI + + // + // Make sure outside cells are ok (this non-op if user set the mesh as periodic) + // + { + using bc_array_t = BorderConditionsConfig::bc_array_t; + + bc_array_t bc_types = [=]() { + if constexpr (dim == 2) + { + // clang-format off + return bc_array_t{ + test::BC_HYDRO::ANALYTICAL, + test::BC_HYDRO::ANALYTICAL, + test::BC_HYDRO::ANALYTICAL, + test::BC_HYDRO::ANALYTICAL + }; + // clang-format on + } + else if constexpr (dim == 3) + { + return bc_array_t{ test::BC_HYDRO::ANALYTICAL, test::BC_HYDRO::ANALYTICAL, + test::BC_HYDRO::ANALYTICAL, test::BC_HYDRO::ANALYTICAL, + test::BC_HYDRO::ANALYTICAL, test::BC_HYDRO::ANALYTICAL }; + } + }(); + + // fill outside userdata + test::FillOutside2CellFunctor::apply(config_map, + amr_hashmap_device, + orchard_keys_device, + amr_mesh_info, + userdata_ghosted_block, + initial_amr_setup.block_sizes(), + brick_sizes, + is_brick_periodic, + bc_types, + InitFunc{}); + } + + // + // filling ghost blocks (piecewise) + // + FillBlockGhostCellsInPlaceFunctor::apply(config_map, + amr_hashmap_device, + orchard_keys_device, + userdata_ghosted_block, + brick_sizes, + is_brick_periodic); + +#ifdef KALYPSSO_CORE_USE_MPI + // + // make sure MPI ghosts are OK + // + + // create the main object doing MPI comm to exchange ghost data + // MeshGhostsExchanger mesh_ghosts_exchanger( + // config_map, par_env, amr_mesh, *mesh_map); + MPI_Barrier(par_env.mpi_comm()); + + mesh_ghosts_exchanger.exchange(userdata_ghosted_block.data()); + +#endif // KALYPSSO_CORE_USE_MPI + + { + std::string filename = dim == 2 ? "test_AMR_fill_block_ghosts_in_place_2d_after" + : "test_AMR_fill_block_ghosts_in_place_3d_after"; + + // provide mapping between variables Id and variables names + const Hydro_t model(dim); + + DataWriter::save( + filename, userdata_ghosted_block, config_map, amr_mesh, true, model); + } + + // create data array with ghost block filled with analytical values + auto userdata_ghosted_block_true = + initial_amr_setup.setup_initial_data_ghosted_block(orchard_keys_device, true); + + // perform comparison + auto diff = initial_amr_setup.compute_diff_ghosted_block( + orchard_keys_device, userdata_ghosted_block, userdata_ghosted_block_true); + + { + std::string filename = dim == 2 ? "test_AMR_fill_block_ghosts_in_place_2d_diff" + : "test_AMR_fill_block_ghosts_in_place_3d_diff"; + + // provide mapping between variables Id and variables names + const Hydro_t model(dim); + + DataWriter::save(filename, diff, config_map, amr_mesh, true, model); + } + + // + // perform a reduce to check everything is ok (this is a unit test) + // + real_t l2_square_diff = 0; + Kokkos::Sum reducer_diff(l2_square_diff); + auto diff_v = diff.data(); + auto userdata_ghosted_block_v = userdata_ghosted_block.data(); + auto userdata_ghosted_block_true_v = userdata_ghosted_block_true.data(); + + Kokkos::parallel_reduce( + "Compute_l2_square_diff", + Kokkos::MDRangePolicy>( + { 0, 0, 0 }, + { diff_v.num_cells(), + diff_v.num_vars(), + //(int32_t)diff_v.num_quadrants() + amr_mesh_info.local_num_quadrants() + 0 * amr_mesh_info.local_num_ghosts() }), + KOKKOS_LAMBDA(const int32_t i, const int32_t j, const int32_t k, real_t & local_sum) { + local_sum += diff_v(i, j, k) * diff_v(i, j, k); + // if (diff_v(i, j, k) * diff_v(i, j, k) > 0) + // { + // printf("error at (%d,%d,%d) %f | %f %f\n", + // i, + // j, + // k, + // diff_v(i, j, k), + // userdata_ghosted_block_v(i, j, k), + // userdata_ghosted_block_true_v(i, j, k)); + // } + }, + reducer_diff); + + real_t l2_square_true = 0; + Kokkos::Sum reducer_true(l2_square_true); + // auto userdata_ghosted_block_v = userdata_ghosted_block.view(); + + Kokkos::parallel_reduce( + "Compute_l2_square_true", + Kokkos::MDRangePolicy>( + { 0, 0, 0 }, + { userdata_ghosted_block_v.num_cells(), + userdata_ghosted_block_v.num_vars(), + /*(int32_t)userdata_ghosted_block_v.num_quadrants()*/ + amr_mesh_info.local_num_quadrants() }), + KOKKOS_LAMBDA(const int32_t i, const int32_t j, const int32_t k, real_t & local_sum) { + local_sum += userdata_ghosted_block_v(i, j, k) * userdata_ghosted_block_v(i, j, k); + }, + reducer_true); + + KALYPSSO_INFO("[MPI rank={}] Relative L2 norm is : {}\n", + par_env.rank(), + sqrt(l2_square_diff / l2_square_true)); + +} // run_test + +} // namespace kalypsso + +bool +hasEnding(std::string const & fullString, std::string const & ending) +{ + if (fullString.length() >= ending.length()) + { + return (0 == + fullString.compare(fullString.length() - ending.length(), ending.length(), ending)); + } + else + { + return false; + } +} // hasEnding + +// ============================================================================= +// ============================================================================= +// ============================================================================= +int +main(int argc, char ** argv) +{ + + { + // initialize mpi, kokkos and p4est, use default MPI communicator : MPI_COMM_WORLD + kalypsso::ParallelEnv par_env(argc, argv); + +#ifdef KALYPSSO_CORE_USE_SPDLOG + kalypsso::kalypsso_spdlog_config(argc, argv, par_env.rank(), par_env.size()); +#endif + + if (kalypsso::cmdline_arg_exists(argv, argv + argc, "--help")) + { + if (par_env.rank() == 0) + { + // clang-format off + std::cout << "Example cmdline: \"mpirun -np 4 ./test_AMRmesh_fill_block_ghost_cells_in_place --ini test_AMRmesh_fill_ghost_brick_2d.ini --refine_level 6\"\n"; + // clang-format on + } + return 0; + } + + // parse command line : 2d or 3d ? + bool use_3d = kalypsso::cmdline_arg_exists(argv, argv + argc, "--3d"); + + // check if user passed a custom ini filename + std::string config_filename = kalypsso::cmdline_get_string(argv, argv + argc, "--ini"); + + // provide a default config filename (that exists) + if (config_filename.size() == 0) + config_filename = use_3d ? "./test_AMRmesh_fill_ghost_brick_3d.ini" + : "./test_AMRmesh_fill_ghost_brick_2d.ini"; + + kalypsso::ConfigMap config_map = kalypsso::broadcast_parameters(config_filename); + + // check if input file is valid, i.e. parameter run/dimension is 2 or 3 + const auto dim = kalypsso::get_dim(config_map); + assertm(dim == 2 or dim == 3, "[test_AMRmesh_fill_block_ghost_cells_in_place] Wrong dimension"); + + // check command line for optional parameter (refine_level) + kalypsso::InitialAMRSetupBase::refine_level = + kalypsso::cmdline_arg_exists(argv, argv + argc, "--refine_level") + ? kalypsso::cmdline_get_integer(argv, argv + argc, "--refine_level") + : 4; + + if (par_env.rank() == 0) + { + printf("================================================\n"); + printf("START RUN_TEST\n"); + printf("================================================\n"); + } + + using InitFunc = kalypsso::InitFunc2; + + if (dim == 2) + { + kalypsso::run_test<2, kalypsso::DefaultDevice, InitFunc>(par_env, config_map); + } + else if (dim == 3) + { + kalypsso::run_test<3, kalypsso::DefaultDevice, InitFunc>(par_env, config_map); + } + else + { + if (par_env.rank() == 0) + { + std::cout << "Input file is not valid ! check parameter run/dimension.\n"; + } + } + + if (par_env.rank() == 0) + { + printf("================================================\n"); + printf("END RUN_TEST\n"); + printf("================================================\n"); + } + } + + return 0; + +} // main diff --git a/test/AMRmesh/test_AMRmesh_fill_block_ghost_cells_v2.cpp b/test/AMRmesh/test_AMRmesh_fill_block_ghost_cells_v2.cpp new file mode 100644 index 0000000..c9061d6 --- /dev/null +++ b/test/AMRmesh/test_AMRmesh_fill_block_ghost_cells_v2.cpp @@ -0,0 +1,416 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file test_AMRmesh_fill_block_ghost_cells_v2.cpp + * + * Purpose: test and validate code used to fill ghost cells around blocks (leaf of octree). + * + * Input array is non-ghosted + * Output array is ghosted + * + */ + +#include +#include +#include +#include +#include +#include +#ifdef KALYPSSO_CORE_USE_MPI +# include +#endif // KALYPSSO_CORE_USE_MPI + +#include + +#include +#include +#include + +#include +#include +#include + +#include + +namespace kalypsso +{ + +// ============================================================================= +// ============================================================================= +template +void +run_test(const ParallelEnv & par_env, const ConfigMap & config_map) +{ + using exec_space = typename device_t::execution_space; + + using Hydro_t = core::models::Hydro; + + using DataArrayBlock_t = DataArrayBlock; + + InitialAMRSetup initial_amr_setup(par_env, config_map, InitFunc{}); + + auto level_min = config_map.getInteger("amr", "level_min", 2); + auto level_max = config_map.getInteger("amr", "level_max", 2); + auto no_refine = (level_min == level_max); + + // setup initial p4est mesh with some test pseudo random refined/coarsen cells pattern + initial_amr_setup.setup_initial_mesh(no_refine); + + auto & amr_mesh = initial_amr_setup.mesh(); + auto mesh_map = initial_amr_setup.mesh_map(); + auto brick_sizes = initial_amr_setup.brick_sizes(); + auto is_brick_periodic = initial_amr_setup.is_brick_periodic(); + auto amr_mesh_info = initial_amr_setup.amr_mesh_info(); + + // initialize ghost + amr_mesh.reset_ghost(); + + // retrieve amr keys + mesh_map->update_orchard_keys(amr_mesh.forest(), amr_mesh.ghost()); + auto orchard_keys_host = mesh_map->orchard_keys_host(); + auto orchard_keys_device = mesh_map->orchard_keys(); + +#ifdef KALYPSSO_CORE_USE_MPI + // mirror keys array must be up to date for MeshGhostExchanger to be functional + mesh_map->update_mirror_orchard_keys(amr_mesh.ghost()); +#endif // KALYPSSO_CORE_USE_MPI + + // rebuild the hashmap (must be done after AMR cycle) + constexpr bool on_device = true; + mesh_map->update_hashmap(on_device); + auto amr_hashmap_device = mesh_map->hashmap(); + + // auto [userdata_leaf, userdata_block] = + // initial_amr_setup.setup_initial_data(orchard_keys_device); + auto userdata_leaf = initial_amr_setup.setup_initial_data_leaf(orchard_keys_device); + auto userdata_block = initial_amr_setup.setup_initial_data_block_new(orchard_keys_device); + + userdata_block.resize(amr_mesh_info.local_num_quadrants_total()); + + // auto userdata_block_host = Kokkos::create_mirror_view(userdata_block); + auto userdata_block_host = DataArrayBlock_t::create_host_mirror_view(userdata_block); + + auto userdata_ghosted_block = + initial_amr_setup.setup_initial_data_ghosted_block(orchard_keys_device, true); + + userdata_ghosted_block.resize(amr_mesh_info.local_num_quadrants_total()); + + // reset (for test) + Kokkos::deep_copy(userdata_ghosted_block.flat_view(), 0.0); + + // + // save data before filling block ghosts + // + { + std::string filename = dim == 2 ? "test_AMR_fill_block_noghosts_2d_before" + : "test_AMR_fill_block_noghosts_3d_before"; + + // provide mapping between variables Id and variables names + const Hydro_t model(dim); + + DataWriter::save(filename, userdata_block, config_map, amr_mesh, model); + } + +#ifdef KALYPSSO_CORE_USE_MPI + // + // make sure MPI ghosts are OK + // + + // create the main object doing MPI comm to exchange ghost data + MeshGhostsExchanger mesh_ghosts_exchanger( + config_map, par_env, amr_mesh, *mesh_map); + MPI_Barrier(par_env.mpi_comm()); + + // auto ghosted_data = userdata_ghosted_block.view(); + + mesh_ghosts_exchanger.exchange(userdata_block); + + // auto ghosted_data_host = Kokkos::create_mirror_view(ghosted_data); + // Kokkos::deep_copy(ghosted_data_host, ghosted_data); +#endif // KALYPSSO_CORE_USE_MPI + + // + // Make sure outside cells are ok (this non-op if user set the mesh as periodic) + // + { + using bc_array_t = BorderConditionsConfig::bc_array_t; + + bc_array_t bc_types = [=]() { + if constexpr (dim == 2) + { + // clang-format off + return bc_array_t{ + test::BC_HYDRO::ANALYTICAL, + test::BC_HYDRO::ANALYTICAL, + test::BC_HYDRO::ANALYTICAL, + test::BC_HYDRO::ANALYTICAL + }; + // clang-format on + } + else if constexpr (dim == 3) + { + return bc_array_t{ test::BC_HYDRO::ANALYTICAL, test::BC_HYDRO::ANALYTICAL, + test::BC_HYDRO::ANALYTICAL, test::BC_HYDRO::ANALYTICAL, + test::BC_HYDRO::ANALYTICAL, test::BC_HYDRO::ANALYTICAL }; + } + }(); + + // fill outside userdata + test::FillOutsideCellFunctor::apply(config_map, + amr_hashmap_device, + orchard_keys_device, + amr_mesh_info, + userdata_block, + initial_amr_setup.block_sizes(), + brick_sizes, + mesh_map->is_brick_periodic(), + bc_types, + InitFunc{}); + } + + // + // filling ghost blocks (piecewise) + // + { + const auto num_cells = userdata_ghosted_block.num_cells(); + const auto num_vars = userdata_ghosted_block.num_vars(); + + const auto nbOcts = amr_mesh_info.local_num_quadrants(); + + // process octant in a picewise manner + const int32_t num_octants_per_group = 32; + + const int32_t nbGroups = (nbOcts + num_octants_per_group - 1) / num_octants_per_group; + + auto userdata_ghosted_block_range = + DataArrayGhostedBlock(userdata_ghosted_block.block_size(), + userdata_ghosted_block.ghosted_block_size(), + userdata_ghosted_block.shift(), + "userdata_ghosted_block_range", + userdata_ghosted_block.num_vars(), + num_octants_per_group); + + for (int32_t iGroup = 0; iGroup < nbGroups; ++iGroup) + { + const auto iOct_begin = iGroup * num_octants_per_group; + const auto num_octants_to_process = + (iGroup == (nbGroups - 1)) ? nbOcts - iOct_begin : num_octants_per_group; + + FillBlockGhostCellsFunctorV2::apply(amr_hashmap_device, + orchard_keys_device, + nbOcts, + iOct_begin, + num_octants_to_process, + userdata_block, + userdata_ghosted_block_range, + brick_sizes, + is_brick_periodic); + + // copy back results + { + // source view + auto data_range_in = + std::pair(0, num_cells * num_vars * num_octants_to_process); + auto userdata_ghosted_block_range_view = + Kokkos::subview(userdata_ghosted_block_range.flat_view(), data_range_in); + + // destination view + auto data_range_out = std::pair( + num_cells * num_vars * iOct_begin, + num_cells * num_vars * (iOct_begin + num_octants_to_process)); + auto userdata_ghosted_block_subview = + Kokkos::subview(userdata_ghosted_block.flat_view(), data_range_out); + + // copy + Kokkos::deep_copy(userdata_ghosted_block_subview, userdata_ghosted_block_range_view); + } + } + } + + { + std::string filename = + dim == 2 ? "test_AMR_fill_block_ghosts_2d_after" : "test_AMR_fill_block_ghosts_3d_after"; + + // provide mapping between variables Id and variables names + const Hydro_t model(dim); + + DataWriter::save( + filename, userdata_ghosted_block, config_map, amr_mesh, true, model); + } + + // create data array with ghost block filled with analytical values + auto userdata_ghosted_block_true = + initial_amr_setup.setup_initial_data_ghosted_block(orchard_keys_device, true); + + // perform comparison + auto diff = initial_amr_setup.compute_diff_ghosted_block( + orchard_keys_device, userdata_ghosted_block, userdata_ghosted_block_true); + + { + std::string filename = + dim == 2 ? "test_AMR_fill_block_ghosts_2d_diff" : "test_AMR_fill_block_ghosts_3d_diff"; + + // provide mapping between variables Id and variables names + const Hydro_t model(dim); + + DataWriter::save(filename, diff, config_map, amr_mesh, true, model); + } + + // + // perform a reduce to check everything is ok (this is a unit test) + // + real_t l2_square_diff = 0; + Kokkos::Sum reducer_diff(l2_square_diff); + auto diff_v = diff.data(); + auto userdata_ghosted_block_v = userdata_ghosted_block.data(); + auto userdata_ghosted_block_true_v = userdata_ghosted_block_true.data(); + + Kokkos::parallel_reduce( + "Compute_l2_square_diff", + Kokkos::MDRangePolicy>( + { 0, 0, 0 }, + { diff_v.num_cells(), + diff_v.num_vars(), + // diff_v.num_quadrants() + amr_mesh_info.local_num_quadrants() + 0 * amr_mesh_info.local_num_ghosts() }), + KOKKOS_LAMBDA(const int32_t i, const int32_t j, const int32_t k, real_t & local_sum) { + local_sum += diff_v(i, j, k) * diff_v(i, j, k); + // if (diff_v(i, j, k) * diff_v(i, j, k) > 0) + // { + // KALYPSSO_INFO("error at ({},{},{}) {} | {} {}\n", + // i, + // j, + // k, + // diff_v(i, j, k), + // userdata_ghosted_block_v(i, j, k), + // userdata_ghosted_block_true_v(i, j, k)); + // } + }, + reducer_diff); + + real_t l2_square_true = 0; + Kokkos::Sum reducer_true(l2_square_true); + // auto userdata_ghosted_block_v = userdata_ghosted_block.view(); + + Kokkos::parallel_reduce( + "Compute_l2_square_true", + Kokkos::MDRangePolicy>( + { 0, 0, 0 }, + { userdata_ghosted_block_v.num_cells(), + userdata_ghosted_block_v.num_vars(), + /*userdata_ghosted_block_v.num_quadrants()*/ + amr_mesh_info.local_num_quadrants() }), + KOKKOS_LAMBDA(const int32_t i, const int32_t j, const int32_t k, real_t & local_sum) { + local_sum += userdata_ghosted_block_v(i, j, k) * userdata_ghosted_block_v(i, j, k); + }, + reducer_true); + + KALYPSSO_INFO("[MPI rank={}] Relative L2 norm is : {}\n", + par_env.rank(), + sqrt(l2_square_diff / l2_square_true)); + +} // run_test + +} // namespace kalypsso + +bool +hasEnding(std::string const & fullString, std::string const & ending) +{ + if (fullString.length() >= ending.length()) + { + return (0 == + fullString.compare(fullString.length() - ending.length(), ending.length(), ending)); + } + else + { + return false; + } +} // hasEnding + +// ============================================================================= +// ============================================================================= +// ============================================================================= +int +main(int argc, char ** argv) +{ + + { + // initialize mpi, kokkos and p4est, use default MPI communicator : MPI_COMM_WORLD + kalypsso::ParallelEnv par_env(argc, argv); + +#ifdef KALYPSSO_CORE_USE_SPDLOG + kalypsso::kalypsso_spdlog_config(argc, argv, par_env.rank(), par_env.size()); +#endif + + if (kalypsso::cmdline_arg_exists(argv, argv + argc, "--help")) + { + if (par_env.rank() == 0) + { + // clang-format off + std::cout << "Example cmdline: \"mpirun -np 4 ./test_AMRmesh_fill_block_ghost_cells_v2 --ini test_AMRmesh_fill_ghost_brick_2d.ini --refine_level 6\"\n"; + // clang-format on + } + return 0; + } + + // parse command line : 2d or 3d ? + bool use_3d = kalypsso::cmdline_arg_exists(argv, argv + argc, "--3d"); + + // check if user passed a custom ini filename + std::string config_filename = kalypsso::cmdline_get_string(argv, argv + argc, "--ini"); + + // provide a default config filename (that exists) + if (config_filename.size() == 0) + config_filename = use_3d ? "./test_AMRmesh_fill_ghost_brick_3d.ini" + : "./test_AMRmesh_fill_ghost_brick_2d.ini"; + + kalypsso::ConfigMap config_map = kalypsso::broadcast_parameters(config_filename); + + // check if input file is valid, i.e. parameter run/dimension is 2 or 3 + const auto dim = kalypsso::get_dim(config_map); + assertm(dim == 2 or dim == 3, "[test_AMRmesh_fill_block_ghost_cells_v2] Wrong dimension"); + + // check command line for optional parameter (refine_level) + kalypsso::InitialAMRSetupBase::refine_level = + kalypsso::cmdline_arg_exists(argv, argv + argc, "--refine_level") + ? kalypsso::cmdline_get_integer(argv, argv + argc, "--refine_level") + : 4; + + if (par_env.rank() == 0) + { + printf("================================================\n"); + printf("START RUN_TEST\n"); + printf("================================================\n"); + } + + using InitFunc = kalypsso::InitFunc2; + + if (dim == 2) + { + kalypsso::run_test<2, kalypsso::DefaultDevice, InitFunc>(par_env, config_map); + } + else if (dim == 3) + { + kalypsso::run_test<3, kalypsso::DefaultDevice, InitFunc>(par_env, config_map); + } + else + { + if (par_env.rank() == 0) + { + std::cout << "Input file is not valid ! check parameter run/dimension.\n"; + } + } + + if (par_env.rank() == 0) + { + printf("================================================\n"); + printf("END RUN_TEST\n"); + printf("================================================\n"); + } + } + + return 0; + +} // main diff --git a/test/AMRmesh/test_AMRmesh_fill_block_ghost_faces.cpp b/test/AMRmesh/test_AMRmesh_fill_block_ghost_faces.cpp new file mode 100644 index 0000000..0513506 --- /dev/null +++ b/test/AMRmesh/test_AMRmesh_fill_block_ghost_faces.cpp @@ -0,0 +1,431 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file test_AMRmesh_fill_block_ghost_faces.cpp + * + * Purpose: test and validate code used to fill ghost faces around a block. + * + * Input array is non-ghosted + * Output array is ghosted + * + */ + +#include +#include +#include +#include +#include +#include +#ifdef KALYPSSO_CORE_USE_MPI +# include +#endif // KALYPSSO_CORE_USE_MPI + +#include + +#include +// #include + +#include +#include + +#include +#include +#include + +#include + +namespace kalypsso +{ + +// ============================================================================= +// ============================================================================= +template +void +run_test(const ParallelEnv & par_env, const ConfigMap & config_map) +{ + // using exec_space = typename device_t::execution_space; + + using Hydro_t = core::models::Hydro; + + // using DataArrayBlock_t = DataArrayBlock; + using FaceDataArrayBlock_t = FaceDataArrayBlock; + + InitialAMRSetup initial_amr_setup(par_env, config_map, InitFunc{}); + + auto level_min = config_map.getInteger("amr", "level_min", 2); + auto level_max = config_map.getInteger("amr", "level_max", 2); + auto no_refine = (level_min == level_max); + + // setup initial p4est mesh with some test pseudo random refined/coarsen cells pattern + initial_amr_setup.setup_initial_mesh(no_refine); + + auto & amr_mesh = initial_amr_setup.mesh(); + auto mesh_map = initial_amr_setup.mesh_map(); + auto brick_sizes = initial_amr_setup.brick_sizes(); + auto is_brick_periodic = initial_amr_setup.is_brick_periodic(); + auto amr_mesh_info = initial_amr_setup.amr_mesh_info(); + + // initialize ghost + amr_mesh.reset_ghost(); + + // retrieve amr keys + mesh_map->update_orchard_keys(amr_mesh.forest(), amr_mesh.ghost()); + auto orchard_keys_host = mesh_map->orchard_keys_host(); + auto orchard_keys_device = mesh_map->orchard_keys(); + +#ifdef KALYPSSO_CORE_USE_MPI + // mirror keys array must be up to date for MeshGhostExchanger to be functional + mesh_map->update_mirror_orchard_keys(amr_mesh.ghost()); +#endif // KALYPSSO_CORE_USE_MPI + + // rebuild the hashmap (must be done after AMR cycle) + constexpr bool on_device = true; + mesh_map->update_hashmap(on_device); + auto amr_hashmap_device = mesh_map->hashmap(); + + // auto [userdata_leaf, userdata_block] = + // initial_amr_setup.setup_initial_data(orchard_keys_device); + auto facedata_block = initial_amr_setup.setup_initial_data_block_face(orchard_keys_device, true); + + // ghostwidth of 2 + const uint32_t ghostwidth = 2; + const auto bSize = facedata_block.cell_block_size_inner(); + const auto num_octs = facedata_block.num_quadrants(); + auto facedata_block_ghosted = + FaceDataArrayBlock_t("facedata_block_ghosted", bSize, ghostwidth, num_octs); + + // + // save face data before filling block ghosts + // + { + std::string filename = dim == 2 ? "test_AMR_fill_block_faces_noghosts_2d_before" + : "test_AMR_fill_block_faces_noghosts_3d_before"; + + // provide mapping between variables Id and variables names + const Hydro_t model(dim); + + DataWriter::save( + filename, facedata_block, config_map, amr_mesh, "facedata_before_remapping"); + + // compute divergence + const auto divBdata = + FaceDataArrayBlock_t::compute_divergence(facedata_block, mesh_map->orchard_keys()); + + DataWriter::save_scalar( + filename + std::string("_div"), divBdata, 0, config_map, amr_mesh); + } + +#ifdef KALYPSSO_CORE_USE_MPI + // + // make sure MPI ghosts are OK + // + + // create the main object doing MPI comm to exchange ghost data + MeshGhostsExchanger mesh_ghosts_exchanger( + config_map, par_env, amr_mesh, *mesh_map); + MPI_Barrier(par_env.mpi_comm()); + + // auto ghosted_data = userdata_ghosted_block.view(); + + mesh_ghosts_exchanger.exchange(facedata_block); + + // auto ghosted_data_host = Kokkos::create_mirror_view(ghosted_data); + // Kokkos::deep_copy(ghosted_data_host, ghosted_data); +#endif // KALYPSSO_CORE_USE_MPI + + // + // Make sure outside cells are ok (this non-op if user set the mesh as periodic) + // + // { + // using bc_array_t = BorderConditionsConfig::bc_array_t; + + // bc_array_t bc_types = [=]() { + // if constexpr (dim == 2) + // { + // // clang-format off + // return bc_array_t{ + // godunov_hydro::BC_HYDRO::ANALYTICAL, + // godunov_hydro::BC_HYDRO::ANALYTICAL, + // godunov_hydro::BC_HYDRO::ANALYTICAL, + // godunov_hydro::BC_HYDRO::ANALYTICAL + // }; + // // clang-format on + // } + // else if constexpr (dim == 3) + // { + // return bc_array_t{ godunov_hydro::BC_HYDRO::ANALYTICAL, + // godunov_hydro::BC_HYDRO::ANALYTICAL, + // godunov_hydro::BC_HYDRO::ANALYTICAL, + // godunov_hydro::BC_HYDRO::ANALYTICAL, + // godunov_hydro::BC_HYDRO::ANALYTICAL, + // godunov_hydro::BC_HYDRO::ANALYTICAL + // }; + // } + // }(); + + // // fill outside userdata + // muscl_mhd::FillOutsideCellFunctor::apply( + // config_map, + // amr_hashmap_device, + // orchard_keys_device, + // amr_mesh_info, + // facedata_block, + // initial_amr_setup.block_sizes(), + // brick_sizes, + // mesh_map->is_brick_periodic(), + // bc_types, + // InitFunc{}); + // } + + // + // filling blocks ghost faces (piecewise) + // + { + const auto num_elts = facedata_block_ghosted.num_elements_per_octant(); + + const auto nbOcts = amr_mesh_info.local_num_quadrants(); + + // process octants in a picewise manner + const uint32_t num_octants_per_group = 32000; + + const uint32_t nbGroups = (nbOcts + num_octants_per_group - 1) / num_octants_per_group; + + auto facedata_block_ghosted_range = + FaceDataArrayBlock("facedata_block_ghosted_range", + facedata_block_ghosted.cell_block_size_inner(), + ghostwidth, + num_octants_per_group); + + for (uint32_t iGroup = 0; iGroup < nbGroups; ++iGroup) + { + const auto iOct_begin = iGroup * num_octants_per_group; + const auto num_octants_to_process = + (iGroup == (nbGroups - 1)) ? nbOcts - iOct_begin : num_octants_per_group; + + FillBlockGhostFacesFunctor::apply(config_map, + amr_hashmap_device, + orchard_keys_device, + nbOcts, + iOct_begin, + num_octants_to_process, + facedata_block, + facedata_block_ghosted_range, + brick_sizes, + is_brick_periodic, + amr_mesh_info); + + // copy back results into the full array + { + // source view + auto data_range_in = + std::pair(0, num_elts * num_octants_to_process); + auto facedata_block_ghosted_range_view = + Kokkos::subview(facedata_block_ghosted_range.logical_view(), data_range_in); + + // destination view + auto data_range_out = std::pair( + num_elts * iOct_begin, num_elts * (iOct_begin + num_octants_to_process)); + auto facedata_block_ghosted_subview = + Kokkos::subview(facedata_block_ghosted.logical_view(), data_range_out); + + // copy + Kokkos::deep_copy(facedata_block_ghosted_subview, facedata_block_ghosted_range_view); + } + } + } + + { + std::string filename = dim == 2 ? "test_AMR_fill_block_faces_ghost_2d_after" + : "test_AMR_fill_block_faces_ghost_3d_after"; + + // provide mapping between variables Id and variables names + const Hydro_t model(dim); + + DataWriter::save( + filename, facedata_block_ghosted, config_map, amr_mesh, "facedata_after_remapping"); + + // compute divergence + const auto divBdata = + FaceDataArrayBlock_t::compute_divergence(facedata_block_ghosted, mesh_map->orchard_keys()); + + DataWriter::save_scalar( + filename + std::string("_div"), divBdata, 0, config_map, amr_mesh); + } + + // // create data array with ghost blocks filled with analytical values + // auto userdata_ghosted_block_true = + // initial_amr_setup.setup_initial_data_ghosted_block(orchard_keys_device, true); + + // // perform comparison + // auto diff = initial_amr_setup.compute_diff_ghosted_block( + // orchard_keys_device, userdata_ghosted_block, userdata_ghosted_block_true); + + // { + // std::string filename = + // dim == 2 ? "test_AMR_fill_block_ghosts_2d_diff" : "test_AMR_fill_block_ghosts_3d_diff"; + + // // provide mapping between variables Id and variables names + // const Hydro_t model(dim); + + // DataWriter::template save( + // filename, diff, config_map, amr_mesh, true, model); + // } + + // // + // // perform a reduce to check everything is ok (this is a unit test) + // // + // double l2_square_diff = 0; + // Kokkos::Sum reducer_diff(l2_square_diff); + // auto diff_v = diff.data(); + // auto userdata_ghosted_block_v = userdata_ghosted_block.data(); + // auto userdata_ghosted_block_true_v = userdata_ghosted_block_true.data(); + + // Kokkos::parallel_reduce( + // "Compute_l2_square_diff", + // Kokkos::MDRangePolicy>( + // { 0, 0, 0 }, + // { (int32_t)diff_v.num_cells(), + // (int32_t)diff_v.num_vars(), + // //(int32_t)diff_v.num_quadrants() + // amr_mesh_info.local_num_quadrants() + 0 * amr_mesh_info.local_num_ghosts() }), + // KOKKOS_LAMBDA(const uint32_t i, const uint32_t j, const uint32_t k, double & local_sum) { + // local_sum += diff_v(i, j, k) * diff_v(i, j, k); + // // if (diff_v(i, j, k) * diff_v(i, j, k) > 0) + // // { + // // printf("error at (%d,%d,%d) %f | %f %f\n", + // // i, + // // j, + // // k, + // // diff_v(i, j, k), + // // userdata_ghosted_block_v(i, j, k), + // // userdata_ghosted_block_true_v(i, j, k)); + // // } + // }, + // reducer_diff); + + // double l2_square_true = 0; + // Kokkos::Sum reducer_true(l2_square_true); + // // auto userdata_ghosted_block_v = userdata_ghosted_block.view(); + + // Kokkos::parallel_reduce( + // "Compute_l2_square_true", + // Kokkos::MDRangePolicy>( + // { 0, 0, 0 }, + // { (int32_t)userdata_ghosted_block_v.num_cells(), + // (int32_t)userdata_ghosted_block_v.num_vars(), + // /*(int32_t)userdata_ghosted_block_v.num_quadrants()*/ + // amr_mesh_info.local_num_quadrants() }), + // KOKKOS_LAMBDA(const uint32_t i, const uint32_t j, const uint32_t k, double & local_sum) { + // local_sum += userdata_ghosted_block_v(i, j, k) * userdata_ghosted_block_v(i, j, k); + // }, + // reducer_true); + + // printf("[MPI rank=%d] Relative L2 norm is : %f\n", + // par_env.rank(), + // sqrt(l2_square_diff / l2_square_true)); + +} // run_test + +} // namespace kalypsso + +bool +hasEnding(std::string const & fullString, std::string const & ending) +{ + if (fullString.length() >= ending.length()) + { + return (0 == + fullString.compare(fullString.length() - ending.length(), ending.length(), ending)); + } + else + { + return false; + } +} // hasEnding + +// ============================================================================= +// ============================================================================= +// ============================================================================= +int +main(int argc, char ** argv) +{ + + { + // initialize mpi, kokkos and p4est, use default MPI communicator : MPI_COMM_WORLD + kalypsso::ParallelEnv par_env(argc, argv); + +#ifdef KALYPSSO_CORE_USE_SPDLOG + kalypsso::kalypsso_spdlog_config(argc, argv, par_env.rank(), par_env.size()); +#endif + + if (kalypsso::cmdline_arg_exists(argv, argv + argc, "--help")) + { + if (par_env.rank() == 0) + { + // clang-format off + std::cout << "Example cmdline: \"mpirun -np 4 ./test_AMRmesh_fill_block_ghost_faces --ini test_AMRmesh_fill_ghost_brick_2d.ini --refine_level 6\"\n"; + // clang-format on + } + return 0; + } + + // parse command line : 2d or 3d ? + bool use_3d = kalypsso::cmdline_arg_exists(argv, argv + argc, "--3d"); + + // check if user passed a custom ini filename + std::string config_filename = kalypsso::cmdline_get_string(argv, argv + argc, "--ini"); + + // provide a default config filename (that exists) + if (config_filename.size() == 0) + config_filename = use_3d ? "./test_AMRmesh_fill_ghost_brick_3d.ini" + : "./test_AMRmesh_fill_ghost_brick_2d.ini"; + + kalypsso::ConfigMap config_map = kalypsso::broadcast_parameters(config_filename); + + // check if input file is valid, i.e. parameter run/dimension is 2 or 3 + const auto dim = kalypsso::get_dim(config_map); + assertm(dim == 2 or dim == 3, "[test_AMRmesh_fill_block_ghost_faces] Wrong dimension"); + + // check command line for optional parameter (refine_level) + kalypsso::InitialAMRSetupBase::refine_level = + kalypsso::cmdline_arg_exists(argv, argv + argc, "--refine_level") + ? kalypsso::cmdline_get_integer(argv, argv + argc, "--refine_level") + : 4; + + if (par_env.rank() == 0) + { + printf("================================================\n"); + printf("START RUN_TEST\n"); + printf("================================================\n"); + } + + using InitFunc = kalypsso::InitFunc3; + + if (dim == 2) + { + kalypsso::run_test<2, kalypsso::DefaultDevice, InitFunc>(par_env, config_map); + } + else if (dim == 3) + { + kalypsso::run_test<3, kalypsso::DefaultDevice, InitFunc>(par_env, config_map); + } + else + { + if (par_env.rank() == 0) + { + std::cout << "Input file is not valid ! check parameter run/dimension.\n"; + } + } + + if (par_env.rank() == 0) + { + printf("================================================\n"); + printf("END RUN_TEST\n"); + printf("================================================\n"); + } + } + + return 0; + +} // main diff --git a/test/AMRmesh/test_AMRmesh_fill_ghost_brick_2d.ini b/test/AMRmesh/test_AMRmesh_fill_ghost_brick_2d.ini new file mode 100644 index 0000000..170baca --- /dev/null +++ b/test/AMRmesh/test_AMRmesh_fill_ghost_brick_2d.ini @@ -0,0 +1,81 @@ +[run] +dimension=2 +tEnd=0.5 +nStepmax=10 + +# noutput equals -1 means we dump data at every time steps +nOutput=-1 + +[amr] +level_min=2 +level_max=4 + +use_block_data=yes +bx=8 +by=8 + +gx=4 +gy=4 + +# init step may contain calls to globalRefine +# if the following option is true, we interleave +# calls to load balancing in between globalRefine +# if the following option is false, a single load +# balancing operation is done at the end of +# globalRefine steps +enable_load_balance_during_init=false + +epsilon_refine=0.01 +epsilon_coarsen=0.03 + +connectivity = brick + +cell_centered_prolongation_type = SIMPLE_COPY +face_centered_prolongation_internal_type = TOTH_AND_ROE +face_centered_prolongation_external_type = SIMPLE_COPY +#face_centered_prolongation_external_type = EXTRAPOLATE_LINEAR_MINMOD + +[p4est_connectivity] +nbrick_x=3 +nbrick_y=2 + +periodic_x=1 +periodic_y=1 + +[mesh] +xmin=0.0 +ymin=0.0 + +scaling_factor=1.0 + +boundary_type_xmin=WALL +boundary_type_xmax=WALL + +boundary_type_ymin=WALL +boundary_type_ymax=WALL + +[hydro] +gamma0=1.666 +cfl=0.8 +niter_riemann=10 +slope_type=2 +problem=blast +riemann=HLLC + +[blast] +density_in=1.0 +density_out=1.2 +radius=0.25 + +[output] +outputDir=./ +outputPrefix=test_hdf5 +outputVtkAscii=false +write_variables=rho,rho_vx +write_mesh_info=yes +write_iOct=yes +vtk_enabled=no +hdf5_enabled=yes + +[other] +implementationVersion=0 diff --git a/test/AMRmesh/test_AMRmesh_fill_ghost_brick_3d.ini b/test/AMRmesh/test_AMRmesh_fill_ghost_brick_3d.ini new file mode 100644 index 0000000..5d6458e --- /dev/null +++ b/test/AMRmesh/test_AMRmesh_fill_ghost_brick_3d.ini @@ -0,0 +1,89 @@ +[run] +dimension=3 +tEnd=0.5 +nStepmax=10 + +# noutput equals -1 means we dump data at every time steps +nOutput=-1 + +[amr] +level_min=2 +level_max=4 + +use_block_data=yes +bx=4 +by=4 +bz=4 + +gx=2 +gy=2 +gz=2 + +# init step may contain calls to globalRefine +# if the following option is true, we interleave +# calls to load balancing in between globalRefine +# if the following option is false, a single load +# balancing operation is done at the end of +# globalRefine steps +enable_load_balance_during_init=false + +epsilon_refine=0.01 +epsilon_coarsen=0.03 + +connectivity = brick + +cell_centered_prolongation_type = SIMPLE_COPY + +[p4est_connectivity] +#nbrick_x=3 +#nbrick_y=2 +#nbrick_z=4 +nbrick_x=2 +nbrick_y=1 +nbrick_z=1 + +periodic_x=1 +periodic_y=1 +periodic_z=1 + +[mesh] +xmin=0.0 +ymin=0.0 +zmin=0.0 + +scaling_factor=1.0 + +boundary_type_xmin=WALL +boundary_type_xmax=WALL + +boundary_type_ymin=WALL +boundary_type_ymax=WALL + +boundary_type_zmin=WALL +boundary_type_zmax=WALL + +[hydro] +gamma0=1.666 +cfl=0.8 +niter_riemann=10 +slope_type=2 +problem=blast +riemann=HLLC + +[blast] +density_in=1.0 +density_out=1.2 +radius=0.25 + +[output] +outputDir=./ +outputPrefix=test_hdf5 +outputVtkAscii=false +write_variables=rho,rho_vx +write_mesh_info=yes +write_iOct=yes +vtk_enabled=no +hdf5_enabled=yes + +[other] +implementationVersion=0 diff --git a/test/AMRmesh/test_AMRmesh_refine_flags.cpp b/test/AMRmesh/test_AMRmesh_refine_flags.cpp new file mode 100644 index 0000000..5add748 --- /dev/null +++ b/test/AMRmesh/test_AMRmesh_refine_flags.cpp @@ -0,0 +1,426 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file test_AMRmesh_refine_flags.cpp + * + * Purpose: illustrate use of class ComputeRefineFlags. + * + * What we do here: + * - prepare an AMR mesh with some initial refinement (done using regular p4est API, cpu only) + * - create some user data (as Kokkos device view) attached to the AMR mesh + * - perform an AMR cycle (refine, coarsen, balance) to modify mesh + * - remap user data from the old mesh to the new mesh + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef KALYPSSO_CORE_USE_MPI +# include +# include +#endif // KALYPSSO_CORE_USE_MPI + +#include // for block_size_t +#include + +#include +#include + +#include +#include +#include + +#include + +namespace kalypsso +{ + +// ============================================================================= +// ============================================================================= +template +void +run_test(const ParallelEnv & par_env, const ConfigMap & config_map) +{ + + using DataArrayLeaf_t = DataArrayLeaf; + using DataArrayBlock_t = DataArrayBlock; + + using ExecutionSpace [[maybe_unused]] = typename device_t::execution_space; + + using Hydro_t = core::models::Hydro; + + // auto sigma = config_map.getReal("other", "sigma", KALYPSSO_NUM(0.2)); + // InitialAMRSetup initial_amr_setup( + // par_env, config_map, InitGaussian{ sigma }); + + auto radius = config_map.getReal("other", "radius", KALYPSSO_NUM(0.2)); + InitialAMRSetup initial_amr_setup(par_env, config_map, InitHat{ radius }); + + const auto level_min = config_map.getInteger("amr", "level_min", 2); + const auto level_max = config_map.getInteger("amr", "level_max", 4); + const auto no_refine = true; + + // setup initial p4est mesh with some test pseudo random refined/coarsen cells pattern + initial_amr_setup.setup_initial_mesh(no_refine); + + auto & amr_mesh = initial_amr_setup.mesh(); + auto forest = amr_mesh.forest(); + auto mesh_map = initial_amr_setup.mesh_map(); + auto amr_mesh_info = initial_amr_setup.amr_mesh_info(); + + // initialize ghost + // amr_mesh.reset_ghost(); + + // retrieve amr keys + mesh_map->update_orchard_keys(amr_mesh.forest(), amr_mesh.ghost()); + auto orchard_keys_host = mesh_map->orchard_keys_host_clone(); + auto orchard_keys_device = mesh_map->orchard_keys_clone(); + + // mirror keys array must be up to date for MeshGhostExchange to be functional + mesh_map->update_mirror_orchard_keys(amr_mesh.ghost()); + + // populate user data + auto userdata_leaf = initial_amr_setup.setup_initial_data_leaf(orchard_keys_device); + auto userdata_block = initial_amr_setup.setup_initial_data_block_new(orchard_keys_device); + + // + // save data before AMR cycle + // + { + std::string filename = + dim == 2 ? "test_userdata_2d_before_amr_cycle" : "test_userdata_3d_before_amr_cycle"; + + // provide mapping between variables Id and variables names + const Hydro_t model(dim); + + DataWriter::save( + filename, userdata_leaf, userdata_block, config_map, amr_mesh, model); + } + + // + // here comes the interesting part : apply the AMR cycle + // + + + // + // 1. store the UnorderedMap before AMR cycle + // + // auto ghost = amr_mesh.ghost(); + const bool on_device = true; + // mesh_map->template fill_map_serial(forest, ghost); + // mesh_map->template fill_map(forest, ghost, on_device); + // auto amr_hashmap_device_old = mesh_map->get_map_device(); + mesh_map->update_hashmap(on_device); + auto amr_hashmap_device_old = mesh_map->hashmap_clone(); + + // + // 2.1 Update ghost quadrant userdata + // +#ifdef KALYPSSO_CORE_USE_MPI + { + // create the main object doing MPI comm to exchange ghost data + MeshGhostsExchanger mesh_ghosts_exchanger( + config_map, par_env, amr_mesh, *mesh_map); + MPI_Barrier(par_env.mpi_comm()); + + mesh_ghosts_exchanger.exchange(userdata_block); + } +#endif // KALYPSSO_CORE_USE_MPI + + // + // 2.2 initalize/compute an amrflags array on device (as we would do in a real application) + // + + AMRContext amr_context(amr_mesh_info.local_num_quadrants()); + + const auto refine_threshold = config_map.getReal("amr", "epsilon_refine", KALYPSSO_NUM(0.01)); + const auto coarsen_threshold = config_map.getReal("amr", "epsilon_coarser", KALYPSSO_NUM(0.03)); + const int ivar_to_refine = 0; + + RefineIndicatorData refine_params{ + level_min, level_max, Indicator::LOHNER_UNSPLIT, refine_threshold, + coarsen_threshold, ivar_to_refine, KALYPSSO_NUM(0.02) + }; + ComputeRefineFlags::run(amr_hashmap_device_old, + orchard_keys_device, + amr_mesh_info.local_num_quadrants(), + initial_amr_setup.brick_sizes(), + initial_amr_setup.is_brick_periodic(), + userdata_block, + amr_context.m_amrflags_d, + refine_params); + + Kokkos::deep_copy(amr_context.m_amrflags_h, amr_context.m_amrflags_d); + + const auto nbCellsPerBlock = userdata_block.num_cells(); + Kokkos::parallel_for( + "copy_flags", + Kokkos::RangePolicy(0, + nbCellsPerBlock * amr_mesh.forest()->local_num_quadrants), + KOKKOS_LAMBDA(const int32_t i) { + auto iOct = i / nbCellsPerBlock; + auto iCell = i - iOct * nbCellsPerBlock; + userdata_block(iCell, 1, iOct) = static_cast(amr_context.m_amrflags_d(iOct)); + }); + + // + // 3. apply AMR cycle on device : refine + coarsen + 2:1 balance + // + { + Kokkos::Profiling::ScopedRegion prof("AMR_refinement_device"); + [[maybe_unused]] auto changed = amr_context.adapt_mesh(forest); + KALYPSSO_INFO_ALL("Mesh changed ? {}", changed); + } + + // + // re-initialize ghost + // + amr_mesh.reset_ghost(); + auto ghost_new = amr_mesh.ghost(); + + // + // prepare for remapping user data + // + auto orchard_keys_device_old = mesh_map->orchard_keys_clone(); + mesh_map->update_orchard_keys(amr_mesh.forest(), ghost_new); + auto orchard_keys_host_new = mesh_map->orchard_keys_host_clone(); + auto orchard_keys_device_new = mesh_map->orchard_keys_clone(); + + + HydroParams params = HydroParams(config_map); + const int nbvar = nbvar_hydro(); + + // get block sizes + const auto bx = config_map.getInteger("amr", "bx", 1); + const auto by = config_map.getInteger("amr", "by", 1); + const auto bz = config_map.getInteger("amr", "bz", 1); + + const auto bSizes = [=]() { + if constexpr (dim == 2) + return block_size_t<2>{ bx, by }; + if constexpr (dim == 3) + return block_size_t<3>{ bx, by, bz }; + }(); + + // memory allocation for userdata on leaf after remapping + auto userdata_leaf_new = DataArrayLeaf_t("test_data_after_amr_cycle", + static_cast(forest->local_num_quadrants), + static_cast(nbvar)); + + // memory allocation for userdata on block after remapping + auto userdata_block_new = + DataArrayBlock_t("test_data_block_after_amr_cycle", bSizes, nbvar, forest->local_num_quadrants); + + // retrieve the new local number of octants (after AMR cycle) + auto local_num_octants = forest->local_num_quadrants; + + // const bool on_device = true; + // rebuild the new hashmap (after AMR cycle) + mesh_map->update_hashmap(on_device); + auto amr_hashmap_device_new = mesh_map->hashmap_clone(); + + KALYPSSO_INFO_ALL("after create map on device"); + + ExecutionSpace exec_space; + + UserDataRemapper userdataRemapper(amr_hashmap_device_old, + orchard_keys_device_new, + orchard_keys_device_old, + local_num_octants, + bSizes, + config_map); + + // + // 4.1 apply data remapping (on device) using the hash map approach - leaf data + // + { + KALYPSSO_INFO_ALL("after AMR local_num_quadrants = {} - remapping leaf data", + forest->local_num_quadrants); + + userdataRemapper.remap_leaf_data(exec_space, userdata_leaf, userdata_leaf_new); + } // end apply remapping + + // + // 4.2 apply data remapping (on device) using the hash map approach - cell data + // + { + KALYPSSO_INFO_ALL("after AMR local_num_quadrants = {} - remapping cell data", + forest->local_num_quadrants); + userdataRemapper.remap_block_data(exec_space, userdata_block, userdata_block_new); + } // end apply remapping + + KALYPSSO_INFO_ALL("after remapping"); + + // + // save data after AMR cycle + // + { + std::string filename = + dim == 2 ? "test_userdata_2d_after_amr_cycle" : "test_userdata_3d_after_amr_cycle"; + + // provide mapping between variables Id and variables names + const Hydro_t model(dim); + + DataWriter::save( + filename, userdata_leaf_new, userdata_block_new, config_map, amr_mesh, model); + } + +#ifdef KALYPSSO_CORE_USE_MPI + // + // perform load balance, aka mesh re-partitioning + // + { + MeshPartitioner mesh_partitioner(config_map, par_env); + + // after that, forest will change + mesh_partitioner.partition_mesh(forest); + + // repartition user data + + auto userdata_block_new2 = DataArrayBlock_t( + "test_data_block_after_partition", bSizes, nbvar, forest->local_num_quadrants); + + mesh_partitioner.repartition_userdata(userdata_block_new, userdata_block_new2); + + auto userdata_leaf_new2 = DataArrayLeaf_t("test_data_leaf_after_partition", + static_cast(forest->local_num_quadrants), + static_cast(nbvar)); + + mesh_partitioner.repartition_userdata(userdata_leaf_new, userdata_leaf_new2); + + + // + // save data after re-partitioning + // + { + std::string filename = + dim == 2 ? "test_userdata_2d_after_partition" : "test_userdata_3d_after_partition"; + + // provide mapping between variables Id and variables names + const Hydro_t model(dim); + + // when using actually only one MPI rank, MeshPartioner didn't do anything, + // so we need to chose which array to save + DataWriter::save( + filename, + par_env.nRanks() > 1 ? userdata_leaf_new2 : userdata_leaf_new, + par_env.nRanks() > 1 ? userdata_block_new2 : userdata_block_new, + config_map, + amr_mesh, + model); + } + + } // end load balancing +#endif // KALYPSSO_CORE_USE_MPI + +} // run_test + +} // namespace kalypsso + +bool +hasEnding(std::string const & fullString, std::string const & ending) +{ + if (fullString.length() >= ending.length()) + { + return (0 == + fullString.compare(fullString.length() - ending.length(), ending.length(), ending)); + } + else + { + return false; + } +} // hasEnding + +// ============================================================================= +// ============================================================================= +// ============================================================================= +int +main(int argc, char ** argv) +{ + + { + // initialize mpi, kokkos and p4est, use default MPI communicator : MPI_COMM_WORLD + kalypsso::ParallelEnv par_env(argc, argv); + +#ifdef KALYPSSO_CORE_USE_SPDLOG + kalypsso::kalypsso_spdlog_config(argc, argv, par_env.rank(), par_env.size()); +#endif + + if (kalypsso::cmdline_arg_exists(argv, argv + argc, "--help")) + { + if (par_env.rank() == 0) + { + // clang-format off + std::cout << "Example cmdline: \"mpirun -np 1 ./test_AMRmesh_refine_flags --ini test_AMRmesh_brick_2d.ini --refine_level 6\"\n"; + // clang-format on + } + return 0; + } + + // parse command line : 2d or 3d ? + bool threed = kalypsso::cmdline_arg_exists(argv, argv + argc, "--3d"); + + // check if user passed a custom ini filename + std::string config_filename = kalypsso::cmdline_get_string(argv, argv + argc, "--ini"); + + // provide a default config filename (that exists) + if (config_filename.size() == 0) + config_filename = threed ? "./test_AMRmesh_brick_3d.ini" : "./test_AMRmesh_brick_2d.ini"; + + kalypsso::ConfigMap config_map = kalypsso::broadcast_parameters(config_filename); + + // check if input file is valid, i.e. parameter run/dimension is 2 or 3 + const auto dim = kalypsso::get_dim(config_map); + assertm(dim == 2 or dim == 3, "[test_AMRmesh_refine_flags] Wrong dimension"); + + // check command line for optional parameter (refine_level) + kalypsso::InitialAMRSetupBase::refine_level = + kalypsso::cmdline_arg_exists(argv, argv + argc, "--refine_level") + ? kalypsso::cmdline_get_integer(argv, argv + argc, "--refine_level") + : 4; + + if (par_env.rank() == 0) + { + printf("================================================\n"); + printf("START RUN_TEST\n"); + printf("================================================\n"); + } + + if (dim == 2) + { + kalypsso::run_test<2, kalypsso::DefaultDevice>(par_env, config_map); + } + else if (dim == 3) + { + kalypsso::run_test<3, kalypsso::DefaultDevice>(par_env, config_map); + } + else + { + if (par_env.rank() == 0) + { + std::cout << "Input file is not valid ! check parameter run/dimension.\n"; + } + } + + if (par_env.rank() == 0) + { + printf("================================================\n"); + printf("END RUN_TEST\n"); + printf("================================================\n"); + } + } + + return 0; + +} // main diff --git a/test/AMRmesh/test_AMRmesh_simple_2d.ini b/test/AMRmesh/test_AMRmesh_simple_2d.ini new file mode 100644 index 0000000..96560f7 --- /dev/null +++ b/test/AMRmesh/test_AMRmesh_simple_2d.ini @@ -0,0 +1,72 @@ +[run] +dimension=2 +tEnd=0.5 +nStepmax=10 + +# noutput equals -1 means we dump data at every time steps +nOutput=-1 + +[amr] +level_min=0 +level_max=2 + +use_block_data=yes +bx=4 +by=4 + +# init step may contain calls to globalRefine +# if the following option is true, we interleave +# calls to load balancing in between globalRefine +# if the following option is false, a single load +# balancing operation is done at the end of +# globalRefine steps +enable_load_balance_during_init=false + +epsilon_refine=0.01 +epsilon_coarsen=0.03 + +connectivity=brick +refine_type=simple + +[p4est_connectivity] +nbrick_x=1 +nbrick_y=1 + +periodic_x=1 +periodic_y=1 + +[mesh] +xmin=0.0 +ymin=0.0 +scaling_factor=1.0 + +boundary_type_xmin=WALL +boundary_type_xmax=WALL + +boundary_type_ymin=WALL +boundary_type_ymax=WALL + +[hydro] +gamma0=1.666 +cfl=0.8 +niter_riemann=10 +slope_type=2 +problem=blast +riemann=HLLC + +[blast] +density_in=1.0 +density_out=1.2 +radius=0.25 + +[output] +outputDir=./ +outputPrefix=test_hdf5 +outputVtkAscii=false +write_variables=rho,rho_vx +write_mesh_info=yes +vtk_enabled=no +hdf5_enabled=yes + +[other] +implementationVersion=0 diff --git a/test/AMRmesh/test_AMRmesh_simple_3d.ini b/test/AMRmesh/test_AMRmesh_simple_3d.ini new file mode 100644 index 0000000..43af2b2 --- /dev/null +++ b/test/AMRmesh/test_AMRmesh_simple_3d.ini @@ -0,0 +1,79 @@ +[run] +dimension=3 +tEnd=0.5 +nStepmax=10 + +# noutput equals -1 means we dump data at every time steps +nOutput=-1 + +[amr] +level_min=0 +level_max=2 + +use_block_data=yes +bx=4 +by=4 +bz=4 + +# init step may contain calls to globalRefine +# if the following option is true, we interleave +# calls to load balancing in between globalRefine +# if the following option is false, a single load +# balancing operation is done at the end of +# globalRefine steps +enable_load_balance_during_init=false + +epsilon_refine=0.01 +epsilon_coarsen=0.03 + +connectivity = brick +refine_type=simple + +[p4est_connectivity] +nbrick_x=1 +nbrick_y=1 +nbrick_z=1 + +periodic_x=1 +periodic_y=1 +periodic_z=1 + +[mesh] +xmin=0.0 +ymin=0.0 +zmin=0.0 +scaling_factor=1.0 + +boundary_type_xmin=WALL +boundary_type_xmax=WALL + +boundary_type_ymin=WALL +boundary_type_ymax=WALL + +boundary_type_zmin=WALL +boundary_type_zmax=WALL + +[hydro] +gamma0=1.666 +cfl=0.8 +niter_riemann=10 +slope_type=2 +problem=blast +riemann=HLLC + +[blast] +density_in=1.0 +density_out=1.2 +radius=0.25 + +[output] +outputDir=./ +outputPrefix=test_hdf5 +outputVtkAscii=false +write_variables=rho,rho_vx +write_mesh_info=yes +vtk_enabled=no +hdf5_enabled=yes + +[other] +implementationVersion=0 diff --git a/test/AMRmesh/test_AMRmesh_stencil.cpp b/test/AMRmesh/test_AMRmesh_stencil.cpp new file mode 100644 index 0000000..95bd389 --- /dev/null +++ b/test/AMRmesh/test_AMRmesh_stencil.cpp @@ -0,0 +1,831 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file test_AMRmesh_stencil.cpp + * + * Purpose: + * test and validate code used to perform stencil computation (on the leaves of octree). + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef KALYPSSO_CORE_USE_MPI +# include +#endif // KALYPSSO_CORE_USE_MPI + +#include + +#include +#include + +#include +#include +#include + +#include + +namespace kalypsso +{ + +// ============================================================================= +// ============================================================================= +/** + * Example stencil computation. + * + * Evaluate laplacian: + * \f$ \frac{\partial^2 f}{\partial x^2} + \frac{\partial^2 f}{\partial y^2} \f$ + * using a finite difference approximation. + * + * Here is the general 3-points finite difference approximation of + * \f$ \frac{\partial^2 f}{\partial x^2} \f$ : + * + * alpha*dx beta*dx + * x1 <-----------> x0 <-----------> x2 + * + * with \f$ x_1 = x_0 + \alpha \dif x \f$ with \f$ \alpha<0 \f$ and \f$ x_2 = x_0 + \beta \dif x \f$ + * with \f$ \beta<0 \f$. + * + * \f$ f''(x_0) \simeq \frac{2}{(\beta-\alpha)\dif x} \left[ \frac{f(x_0+\beta \dif x)}{\beta + * \dif x} - \frac{f(x_0+\alpha \dif x)}{\alpha \dif x} + * -(\frac{1}{\beta}-\frac{1}{\alpha})\frac{f(x_0)}{\dif x}\right] \f$ + * + * On AMR mesh, when current cell "touches" block border, the neighbor cell can be either at coaser, + * same or fine level, so we have to adapt the finite difference in that case. + * + * E.g. left neighbor is in coarser block, while current cell and right neighbor belong to the same + * block. In that case we have \f$ \alpha =-\frac{3}{2}\f$ and \f$\beta = 1\f$ + * ______ + * | | + * | |_______ + * | | | | + * |______|___|___| + * + + * \f$ f''(x_0) \simeq \frac{4}{5 (\dif x)^2} \left[ f(x_2) + \frac{2}{3} f(x_1) - + * \frac{5}{3}f(x_0)\right] \f$ + * + * \tparam dim is dimension (2 or 3) + * \tparam device_t is kokkos execution space + * \tparam Function is a functor used to initial data outside domain (border condition) + */ +template +class StencilComputation +{ + +public: + using exec_space = typename device_t::execution_space; + using index_t = int32_t; + using amr_hashmap_t = typename hashmap_base_t::map_t; + using orchard_key_view_t = typename orchard_key_base_t::view_t; + using CellLocation_t = CellLocation; + + using DataArrayBlock_t = DataArrayBlock; + + // ==================================================================== + // ==================================================================== + //! constructor. + StencilComputation(ConfigMap const & config_map, + amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + int32_t local_num_octants, + block_size_t block_sizes, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + int32_t iOct_begin, + int32_t num_octants, + DataArrayBlock_t userdata_in, + DataArrayBlock_t userdata_out, + const Function & f) + : m_helper(amr_hashmap, orchard_keys, block_sizes, brick_sizes, is_brick_periodic) + , m_amr_hashmap_device(amr_hashmap) + , m_orchard_keys_device(orchard_keys) + , m_local_num_octants(local_num_octants) + , m_iOct_begin(iOct_begin) + , m_num_octants(num_octants) + , m_nbCellsPerLeaf(Kokkos::dim_prod(block_sizes)) + , m_block_sizes(block_sizes) + , m_brick_sizes(brick_sizes) + , m_is_brick_periodic(is_brick_periodic) + , m_userdata_in(userdata_in) + , m_userdata_out(userdata_out) + , m_f(f) + , m_scaling_factor(get_scaling_factor(config_map)) + , m_xyz_min(get_xyz_min(config_map)) + {} + + // ==================================================================== + // ==================================================================== + //! destructor. + virtual ~StencilComputation() = default; + + // ==================================================================== + // ==================================================================== + static void + apply(ConfigMap const & config_map, + amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + int32_t local_num_octants, + block_size_t block_sizes, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + int32_t iOct_begin, + int32_t num_octants, + DataArrayBlock_t userdata_in, + DataArrayBlock_t userdata_out) + { + Function f; + StencilComputation functor(config_map, + amr_hashmap, + orchard_keys, + local_num_octants, + block_sizes, + brick_sizes, + is_brick_periodic, + iOct_begin, + num_octants, + userdata_in, + userdata_out, + f); + + const auto nbCellsPerLeaf = Kokkos::dim_prod(block_sizes); + + const auto nbCellsTotal = local_num_octants * nbCellsPerLeaf; + + Kokkos::parallel_for( + "StencilComputation", Kokkos::RangePolicy(0, nbCellsTotal), functor); + }; // apply + + // ============================================================== + // ============================================================== + //! compute face-averaged value in a 2^dim cell mesh. + //! + //! Example situation where dir is IX, face is on the right: + //! _______________ ______________ + //! | | | | | + //! | | x | | | + //! | | | | neighbor | + //! |_______|_______| | cell at | + //! | | | | coarser | + //! | | x | | level | + //! | | | | | + //! |_______|_______| |______________| + //! + //! + //! Current cell is one of the cells flagged with "x"; what we do here is that we average the + //! value of all the siblings cells marked with "x". + //! What we do is find a generic way to identify face-siblings in a 2^dim neighborhood. + //! + //! Typical application : compute face average value at a non-conform interface; the face average + //! value can be used to compute a ghost cell at coarser level. + //! + //! \param[in] coord is a dim-vector of integer block-coordinates of current cell + //! \param[in] ivar identify which variable we want to average + //! \param[in] iOct_global is the block-octant id (which current cell belong to) + //! + //! \tparam dir can be IX, IY or IZ, it identifies a direction orthogonal to a face we want to + //! average. + //! + //! \return average value + template + KOKKOS_INLINE_FUNCTION real_t + compute_face_average(coord_t const & coord, int ivar, iOct_t iOct_global) const + { + auto const & b = m_block_sizes; + + // round down coordinates to a multiple of two + coord_t coord0; + coord0[IX] = (coord[IX] / 2) * 2; + coord0[IY] = (coord[IY] / 2) * 2; + if constexpr (dim == 3) + coord0[IZ] = (coord[IZ] / 2) * 2; + + auto cell_index0 = coord_to_cellindex(coord0, b); + + real_t data = 0; + const int16_t nbCells = (1 << (dim - 1)); + for (int16_t i = 0; i < nbCells; ++i) + { + // index ii will span all siblings index matching a face orthogonal to direction "dir" + // in other words, in the following we swap bit "dir" the last dimension (IY in 2D, IZ in 3D) + auto ii = swapBits(i, dim - 1, dir); + + // if coord[dir] is odd we are in a right face + // if coord[dir] is even we are in a left face + if (coord[dir] % 2 == 1) + ii += (1 << dir); + + if constexpr (dim == 2) + { + data += + m_userdata_in(cell_index0 + (ii & 0x1) + ((ii & 0x2) >> 1) * b[IX], ivar, iOct_global); + } + else if constexpr (dim == 3) + { + data += m_userdata_in(cell_index0 + (ii & 0x1) + ((ii & 0x2) >> 1) * b[IX] + + ((ii & 0x4) >> 2) * b[IX] * b[IY], + ivar, + iOct_global); + } + } + + return data / nbCells; + + } // compute_face_average + + // ============================================================== + // ============================================================== + /** + * Compute second derivative along direction dir + * + * \tparam dir direction along which second partial derivative is computed + */ + template + KOKKOS_INLINE_FUNCTION real_t + compute_second_derivative(int32_t const & cell_index, int ivar, int32_t const & iOct_local) const + { + + const auto & b = m_block_sizes; + + const auto iOct_global = m_iOct_begin + iOct_local; + const auto coord = cellindex_to_coord(cell_index, m_block_sizes); + const auto key_cur = m_orchard_keys_device(iOct_global); + CellLocation_t cell_loc{ coord, key_cur, iOct_global, false }; + + constexpr shift_t shift_left = []() { + if constexpr (dim == 2) + { + if constexpr (dir == IX) + return shift_t{ -1, 0 }; + else if constexpr (dir == IY) + return shift_t{ 0, -1 }; + } + else + { + if constexpr (dir == IX) + { + return shift_t{ -1, 0, 0 }; + } + else if constexpr (dir == IY) + { + return shift_t{ 0, -1, 0 }; + } + else if constexpr (dir == IZ) + { + return shift_t{ 0, 0, -1 }; + } + } + }(); + + constexpr shift_t shift_right = []() { + if constexpr (dim == 2) + { + if constexpr (dir == IX) + return shift_t{ 1, 0 }; + else if constexpr (dir == IY) + return shift_t{ 0, 1 }; + } + else + { + if constexpr (dir == IX) + { + return shift_t{ 1, 0, 0 }; + } + else if constexpr (dir == IY) + { + return shift_t{ 0, 1, 0 }; + } + else if constexpr (dir == IZ) + { + return shift_t{ 0, 0, 1 }; + } + } + }(); + + const auto cell_loc_left = m_helper.getNeighLoc(cell_loc, shift_left); + const auto cell_loc_right = m_helper.getNeighLoc(cell_loc, shift_right); + const auto cell_index_left = coord_to_cellindex(cell_loc_left.ijk, m_block_sizes); + const auto cell_index_right = coord_to_cellindex(cell_loc_right.ijk, m_block_sizes); + + real_t data = 0; + + real_t dx = static_cast(orchard_key_t::octantLength(key_cur)) / + static_cast(orchard_key_t::ROOT_LENGTH) / + static_cast(m_block_sizes[dir]); + + // compute stencil coef + const auto coef = [&cell_loc, &cell_loc_left, &cell_loc_right, &dx]() { + if (cell_loc_left.level() == cell_loc.level() and cell_loc_right.level() == cell_loc.level()) + { + Kokkos::Array stencil_coef{ KALYPSSO_NUM(1.0), + KALYPSSO_NUM(-2.0), + KALYPSSO_NUM(1.0) }; + return stencil_coef / (dx * dx); + } + else if (cell_loc_left.level() == cell_loc.level() - 1) + { + Kokkos::Array stencil_coef{ KALYPSSO_NUM(8.0) / 15, + KALYPSSO_NUM(-20.0) / 15, + KALYPSSO_NUM(12.0) / 15 }; + return stencil_coef / (dx * dx); + } + else if (cell_loc_right.level() == cell_loc.level() - 1) + { + Kokkos::Array stencil_coef{ KALYPSSO_NUM(12.0) / 15, + KALYPSSO_NUM(-20.0) / 15, + KALYPSSO_NUM(8.0) / 15 }; + return stencil_coef / (dx * dx); + } + else if (cell_loc_left.level() == cell_loc.level() + 1) + { + Kokkos::Array stencil_coef{ KALYPSSO_NUM(32.0) / 21, + KALYPSSO_NUM(-56.0) / 21, + KALYPSSO_NUM(24.0) / 21 }; + return stencil_coef / (dx * dx); + } + else if (cell_loc_right.level() == cell_loc.level() + 1) + { + Kokkos::Array stencil_coef{ KALYPSSO_NUM(24.0) / 21, + KALYPSSO_NUM(-56.0) / 21, + KALYPSSO_NUM(32.0) / 21 }; + return stencil_coef / (dx * dx); + } + else + { + return Kokkos::Array{ KALYPSSO_NUM(0.0), KALYPSSO_NUM(0.0), KALYPSSO_NUM(0.0) }; + } + }(); + + // functor to get data in neighbor cell (whether it is a regular cell, or cell outside + // domain) + auto getNeighDataSameLevel = [&](CellLocation cell_loc_neigh, + int32_t cell_index_neigh, + shift_t shift) { + if (cell_loc_neigh.is_outside_domain) + { + // use analytical value to init data on the left (direct neighbor, same level) + + // get real space coordinates of lower left corner of the block + constexpr bool use_center = false; + const auto xyz_corner_vertex = orchard_key_to_vertex_coord(key_cur, use_center); + + const auto xyz_cell_vertex = + compute_cell_coordinates(cell_loc.level(), xyz_corner_vertex, coord, m_block_sizes); + const auto xyz_cell = + vertex_coord_to_real_space(xyz_cell_vertex, m_scaling_factor, m_xyz_min); + + // clang-format off + if constexpr (dim == 2) + { + return static_cast(m_f(xyz_cell[IX] + static_cast(shift[IX]) * dx, + xyz_cell[IY] + static_cast(shift[IY]) * dx, + ivar)); + } + else if constexpr (dim == 3) + { + return static_cast(m_f(xyz_cell[IX] + static_cast(shift[IX]) * dx, + xyz_cell[IY] + static_cast(shift[IY]) * dx, + xyz_cell[IZ] + static_cast(shift[IZ]) * dx, + ivar)); + } + // clang-format on + } + else + { + return m_userdata_in(cell_index_neigh, ivar, cell_loc_neigh.iOct); + } + }; + + // Note: among left and right cell neighbors, only one of them can be at different level, + // this can only append when cell is at block border + + // left and right neighbor are at same level, one of them can be outside domain + if (cell_loc_left.level() == cell_loc.level() and cell_loc_right.level() == cell_loc.level()) + { + + const auto data_L = getNeighDataSameLevel(cell_loc_left, cell_index_left, shift_left); + const auto data_R = getNeighDataSameLevel(cell_loc_right, cell_index_right, shift_right); + const auto data_C = m_userdata_in(cell_index, ivar, iOct_global); + data = (coef[0] * data_L + coef[1] * data_C + coef[2] * data_R); + } + + // left neighbor is coarser and right neighbor is same level (actually same block) + else if (cell_loc_left.level() == cell_loc.level() - 1) + { + auto coord_R = coord; + coord_R[dir] += 1; + + const auto data_L = m_userdata_in(cell_index_left, ivar, cell_loc_left.iOct); + const auto data_C = compute_face_average(coord, ivar, iOct_global); + const auto data_R = compute_face_average(coord_R, ivar, iOct_global); + data = (coef[0] * data_L + coef[1] * data_C + coef[2] * data_R); + } + + // right neighbor is coarser and left neighbor is same level (actually same block) + else if (cell_loc_right.level() == cell_loc.level() - 1) + { + auto coord_L = coord; + coord_L[dir] -= 1; + + const auto data_L = compute_face_average(coord_L, ivar, iOct_global); + const auto data_C = compute_face_average(coord, ivar, iOct_global); + const auto data_R = m_userdata_in(cell_index_right, ivar, cell_loc_right.iOct); + data = (coef[0] * data_L + coef[1] * data_C + coef[2] * data_R); + } + + // left neighbor is finer + // average the direct finer neighbors + else if (cell_loc_left.level() == cell_loc.level() + 1) + { + auto coord_L = cellindex_to_coord(cell_index_left, b); + // add extra shift because CellLocation contains coordinate of the lower left corner fine cell + coord_L[dir] += 1; + + auto data_L = compute_face_average(coord_L, ivar, cell_loc_left.iOct); + auto data_C = m_userdata_in(cell_index, ivar, iOct_global); + auto data_R = + m_userdata_in(shifted_cellindex(cell_index, shift_right, b), ivar, iOct_global); + + data = (coef[0] * data_L + coef[1] * data_C + coef[2] * data_R); + } + + // right neighbor is finer + // average the direct finer neighbors + else if (cell_loc_right.level() == cell_loc.level() + 1) + { + auto coord_R = cellindex_to_coord(cell_index_right, b); + + auto data_L = + m_userdata_in(shifted_cellindex(cell_index, shift_left, b), ivar, iOct_global); + auto data_C = m_userdata_in(cell_index, ivar, iOct_global); + auto data_R = compute_face_average(coord_R, ivar, cell_loc_right.iOct); + + data = (coef[0] * data_L + coef[1] * data_C + coef[2] * data_R); + } + + return data; + + } // compute_second_derivative + + // ============================================================== + // ============================================================== + /** + * range policy functor + */ + KOKKOS_INLINE_FUNCTION void + operator()(const index_t & global_index) const + { + const auto iOct_local = global_index / m_nbCellsPerLeaf; + const auto cell_index = global_index - iOct_local * m_nbCellsPerLeaf; + + const auto iOct_global = m_iOct_begin + iOct_local; + + real_t laplacian = 0.0; + laplacian += compute_second_derivative(cell_index, 0, iOct_local); + laplacian += compute_second_derivative(cell_index, 0, iOct_local); + if constexpr (dim == 3) + laplacian += compute_second_derivative(cell_index, 0, iOct_local); + + m_userdata_out(cell_index, 0, iOct_global) = laplacian; + + // just for debug and for facilitating comparison, save additional fields + for (int32_t var = 1; var < m_userdata_out.num_vars(); ++var) + { + if (var == m_userdata_out.num_vars() - 1) + { + // difference between computed value and exact value + m_userdata_out(cell_index, var, iOct_global) = + laplacian - m_userdata_in(cell_index, var, iOct_global); + } + else + { + m_userdata_out(cell_index, var, iOct_global) = m_userdata_in(cell_index, var, iOct_global); + } + } + } // operator() + +private: + //! help to compute cell location + StencilHelper m_helper; + + //! AMR unordered map which maps orchard keys to quadrant number for all key in the mesh + //! (owned quadrants and ghost quadrants) + amr_hashmap_t m_amr_hashmap_device; + + //! list of orchard key of the mesh + orchard_key_view_t m_orchard_keys_device; + + //! total number of octants in the current MPI process (ghost block excluded) + const int32_t m_local_num_octants; + + //! starting octant id + const int32_t m_iOct_begin; + + //! number of octant to process, starting at m_iOct_begin + const int32_t m_num_octants; + + //! number of cells per leaf block + const int32_t m_nbCellsPerLeaf; + + //! block sizes + const block_size_t m_block_sizes; + + //! p4est brick connectivity sizes + const brick_size_t m_brick_sizes; + + //! is p4est connectivity periodic ? + const Kokkos::Array m_is_brick_periodic; + + //! a block data array (no ghosts, sizes= bx,by,bz) + DataArrayBlock_t m_userdata_in; + + //! a ghosted data array (which block ghost cells need to be filled) + DataArrayBlock_t m_userdata_out; + + //! pointwise init functor + const Function & m_f; + + // get geometrical scaling factor + const real_t m_scaling_factor; + + // get domain lower left corner + const Kokkos::Array m_xyz_min; + +}; // class StencilComputation + +// ============================================================================= +// ============================================================================= +template +void +run_test(const ParallelEnv & par_env, const ConfigMap & config_map) +{ + // using exec_space = typename device_t::execution_space; + + // using DataArrayLeaf_t = DataArrayLeaf; + // using DataArrayHost_t = DataArrayHost; + + using DataArrayBlock_t = DataArrayBlock; + // using DataArrayBlockHost_t = DataArrayBlockHost; + + // using exec_space = typename device_t::execution_space; + + using Hydro_t = core::models::Hydro; + + InitialAMRSetup initial_amr_setup( + par_env, config_map, InitFuncParabola{}); + // InitialAMRSetup initial_amr_setup( + // par_env, config_map, InitFuncSineWave{}); + + const auto level_min = config_map.getInteger("amr", "level_min", 2); + const auto level_max = config_map.getInteger("amr", "level_max", 2); + const auto no_refine = (level_min == level_max); + + // setup initial p4est mesh with some test pseudo random refined/coarsen cells pattern + initial_amr_setup.setup_initial_mesh(no_refine); + + auto & amr_mesh = initial_amr_setup.mesh(); + auto mesh_map = initial_amr_setup.mesh_map(); + auto block_sizes = initial_amr_setup.block_sizes(); + auto brick_sizes = initial_amr_setup.brick_sizes(); + auto is_brick_periodic = initial_amr_setup.is_brick_periodic(); + auto amr_mesh_info = initial_amr_setup.amr_mesh_info(); + + // retrieve amr keys + mesh_map->update_orchard_keys(amr_mesh.forest(), amr_mesh.ghost()); + auto orchard_keys_host = mesh_map->orchard_keys_host_clone(); + auto orchard_keys_device = mesh_map->orchard_keys_clone(); + +#ifdef KALYPSSO_CORE_USE_MPI + // mirror keys array must be up to date for MeshGhostExchanger to be functional + mesh_map->update_mirror_orchard_keys(amr_mesh.ghost()); +#endif // KALYPSSO_CORE_USE_MPI + + // rebuild the hashmap (must be done after AMR cycle) + constexpr bool on_device = true; + mesh_map->update_hashmap(on_device); + auto amr_hashmap_device = mesh_map->hashmap(); + + + // auto [userdata_leaf, userdata_block] = + // initial_amr_setup.setup_initial_data(orchard_keys_device); + auto userdata_leaf = initial_amr_setup.setup_initial_data_leaf(orchard_keys_device); + auto userdata_block_in = initial_amr_setup.setup_initial_data_block_new(orchard_keys_device); + userdata_block_in.resize(amr_mesh_info.local_num_quadrants_total()); + + auto userdata_block_in_host = DataArrayBlock_t::create_host_mirror_view(userdata_block_in); + + auto userdata_block_out = DataArrayBlock_t("userdata_block_out", + userdata_block_in.block_size(), + userdata_block_in.num_vars(), + userdata_block_in.num_quadrants()); + Kokkos::deep_copy(userdata_block_out.logical_view(), -1.0); + + // + // save data before stencil computation + // + // { + // std::string filename = dim == 2 ? "test_AMR_stencil_2d_before" : + // "test_AMR_stencil_3d_before"; + + // DataWriter::save( + // filename, userdata_block_in, config_map, amr_mesh); + // } + +#ifdef KALYPSSO_CORE_USE_MPI + // + // make sure MPI ghosts are OK + // + + // create the main object doing MPI comm to exchange ghost data + MeshGhostsExchanger mesh_ghosts_exchanger( + config_map, par_env, amr_mesh, *mesh_map); + MPI_Barrier(par_env.mpi_comm()); + + mesh_ghosts_exchanger.exchange(userdata_block_in); +#endif // KALYPSSO_CORE_USE_MPI + + // + // filling ghost blocks (piecewise) + // + { + const auto nbOcts = amr_mesh_info.local_num_quadrants(); + + + StencilComputation::apply(config_map, + amr_hashmap_device, + orchard_keys_device, + nbOcts, + block_sizes, + brick_sizes, + is_brick_periodic, + 0, + nbOcts, + userdata_block_in, + userdata_block_out); + // StencilComputation::apply(config_map, + // amr_hashmap_device, + // orchard_keys_device, + // nbOcts, + // block_sizes, + // brick_sizes, + // is_brick_periodic, + // 0, + // nbOcts, + // userdata_block_in, + // userdata_block_out); + + + { + std::string filename = dim == 2 ? "test_AMR_stencil_2d_after" : "test_AMR_stencil_3d_after"; + + // provide mapping between variables Id and variables names + const Hydro_t model(dim); + + DataWriter::save( + filename, userdata_block_out, config_map, amr_mesh, model); + } + } + // create data array with ghost block filled with analytical values + auto userdata_ghosted_block_true = + initial_amr_setup.setup_initial_data_ghosted_block(orchard_keys_device, true); + + // perform comparison + // auto diff = initial_amr_setup.compute_diff_ghosted_block( + // orchard_keys_device, userdata_ghosted_block, userdata_ghosted_block_true); + + // { + // std::string filename = + // dim == 2 ? "test_AMR_fill_block_ghosts_2d_diff" : "test_AMR_fill_block_ghosts_3d_diff"; + + // DataWriter::save(filename, diff, config_map, amr_mesh, true); + // } + + // + // perform a reduce to check everything is ok (this is a unit test) + // + // double errors_square = 0; + // Kokkos::Sum reducer(errors_square); + + // auto diff_v = diff.view(); + + // Kokkos::parallel_reduce( + // "Compute_sum_of_differences", + // Kokkos::MDRangePolicy>( + // { 0, 0, 0 }, + // { (int32_t)diff_v.num_cells(), (int32_t)diff_v.num_vars(), (int32_t)diff_v.num_quadrants() + // }), + // KOKKOS_LAMBDA(const int32_t i, const int32_t j, const int32_t k, double & local_sum) { + // local_sum += diff_v(i, j, k) * diff_v(i, j, k); + // }, + // reducer); + + // printf("Square of errors is : %f\n", errors_square); + +} // run_test + +} // namespace kalypsso + +bool +hasEnding(std::string const & fullString, std::string const & ending) +{ + if (fullString.length() >= ending.length()) + { + return (0 == + fullString.compare(fullString.length() - ending.length(), ending.length(), ending)); + } + else + { + return false; + } +} // hasEnding + +// ============================================================================= +// ============================================================================= +// ============================================================================= +int +main(int argc, char ** argv) +{ + + { + // initialize mpi, kokkos and p4est, use default MPI communicator : MPI_COMM_WORLD + kalypsso::ParallelEnv par_env(argc, argv); + +#ifdef KALYPSSO_CORE_USE_SPDLOG + kalypsso::kalypsso_spdlog_config(argc, argv, par_env.rank(), par_env.size()); +#endif + + if (kalypsso::cmdline_arg_exists(argv, argv + argc, "--help")) + { + if (par_env.rank() == 0) + { + // clang-format off + std::cout << "Example cmdline: \"mpirun -np 4 ./test_AMRmesh_stencil --ini test_AMRmesh_stencil_2d.ini --refine_level 6\"\n"; + // clang-format on + } + return 0; + } + + // parse command line : 2d or 3d ? + bool use_3d = kalypsso::cmdline_arg_exists(argv, argv + argc, "--3d"); + + // check if user passed a custom ini filename + std::string config_filename = kalypsso::cmdline_get_string(argv, argv + argc, "--ini"); + + // provide a default config filename (that exists) + if (config_filename.size() == 0) + config_filename = use_3d ? "./test_AMRmesh_stencil_3d.ini" : "./test_AMRmesh_stencil_2d.ini"; + + kalypsso::ConfigMap config_map = kalypsso::broadcast_parameters(config_filename); + + // check if input file is valid, i.e. parameter run/dimension is 2 or 3 + const auto dim = kalypsso::get_dim(config_map); + assertm(dim == 2 or dim == 3, "[test_AMRmesh_stencil] Wrong dimension"); + + // check command line for optional parameter (refine_level) + kalypsso::InitialAMRSetupBase::refine_level = + kalypsso::cmdline_arg_exists(argv, argv + argc, "--refine_level") + ? kalypsso::cmdline_get_integer(argv, argv + argc, "--refine_level") + : 4; + + if (par_env.rank() == 0) + { + printf("================================================\n"); + printf("START RUN_TEST\n"); + printf("================================================\n"); + } + + if (dim == 2) + { + kalypsso::run_test<2, kalypsso::DefaultDevice>(par_env, config_map); + } + else if (dim == 3) + { + kalypsso::run_test<3, kalypsso::DefaultDevice>(par_env, config_map); + } + else + { + if (par_env.rank() == 0) + { + std::cout << "Input file is not valid ! check parameter run/dimension.\n"; + } + } + + if (par_env.rank() == 0) + { + printf("================================================\n"); + printf("END RUN_TEST\n"); + printf("================================================\n"); + } + } + + return 0; + +} // main diff --git a/test/AMRmesh/test_AMRmesh_stencil_2d.ini b/test/AMRmesh/test_AMRmesh_stencil_2d.ini new file mode 100644 index 0000000..6e51ff4 --- /dev/null +++ b/test/AMRmesh/test_AMRmesh_stencil_2d.ini @@ -0,0 +1,75 @@ +[run] +dimension=2 +tEnd=0.5 +nStepmax=10 + +# noutput equals -1 means we dump data at every time steps +nOutput=-1 + +[amr] +level_min=4 +level_max=6 + +use_block_data=yes +bx=8 +by=8 + +gx=4 +gy=4 + +# init step may contain calls to globalRefine +# if the following option is true, we interleave +# calls to load balancing in between globalRefine +# if the following option is false, a single load +# balancing operation is done at the end of +# globalRefine steps +enable_load_balance_during_init=false + +epsilon_refine=0.01 +epsilon_coarsen=0.03 + +connectivity = brick + +[p4est_connectivity] +nbrick_x=3 +nbrick_y=2 + +periodic_x=0 +periodic_y=0 + +[mesh] +xmin=0.0 +ymin=0.0 +scaling_factor=1.0 + +boundary_type_xmin=WALL +boundary_type_xmax=WALL + +boundary_type_ymin=WALL +boundary_type_ymax=WALL + +[hydro] +gamma0=1.666 +cfl=0.8 +niter_riemann=10 +slope_type=2 +problem=blast +riemann=HLLC + +[blast] +density_in=1.0 +density_out=1.2 +radius=0.25 + +[output] +outputDir=./ +outputPrefix=test_hdf5 +outputVtkAscii=false +write_variables=rho,rho_vx,rho_vy,e_tot +write_mesh_info=yes +write_iOct=yes +vtk_enabled=no +hdf5_enabled=yes + +[other] +implementationVersion=0 diff --git a/test/AMRmesh/test_AMRmesh_stencil_3d.ini b/test/AMRmesh/test_AMRmesh_stencil_3d.ini new file mode 100644 index 0000000..f45d3a3 --- /dev/null +++ b/test/AMRmesh/test_AMRmesh_stencil_3d.ini @@ -0,0 +1,86 @@ +[run] +dimension=3 +tEnd=0.5 +nStepmax=10 + +# noutput equals -1 means we dump data at every time steps +nOutput=-1 + +[amr] +level_min=2 +level_max=4 + +use_block_data=yes +bx=4 +by=4 +bz=4 + +gx=2 +gy=2 +gz=2 + +# init step may contain calls to globalRefine +# if the following option is true, we interleave +# calls to load balancing in between globalRefine +# if the following option is false, a single load +# balancing operation is done at the end of +# globalRefine steps +enable_load_balance_during_init=false + +epsilon_refine=0.01 +epsilon_coarsen=0.03 + +connectivity = brick + +[p4est_connectivity] +#nbrick_x=3 +#nbrick_y=2 +#nbrick_z=4 +nbrick_x=2 +nbrick_y=1 +nbrick_z=1 + +periodic_x=0 +periodic_y=0 +periodic_z=0 + +[mesh] +xmin=0.0 +ymin=0.0 +zmin=0.0 +scaling_factor=1.0 + +boundary_type_xmin=WALL +boundary_type_xmax=WALL + +boundary_type_ymin=WALL +boundary_type_ymax=WALL + +boundary_type_zmin=WALL +boundary_type_zmax=WALL + +[hydro] +gamma0=1.666 +cfl=0.8 +niter_riemann=10 +slope_type=2 +problem=blast +riemann=HLLC + +[blast] +density_in=1.0 +density_out=1.2 +radius=0.25 + +[output] +outputDir=./ +outputPrefix=test_hdf5 +outputVtkAscii=false +write_variables=rho,rho_vx,rho_vy,rho_vz,e_tot +write_mesh_info=yes +write_iOct=yes +vtk_enabled=no +hdf5_enabled=yes + +[other] +implementationVersion=0 diff --git a/test/AMRmesh/test_AMRmesh_stencil_helper.cpp b/test/AMRmesh/test_AMRmesh_stencil_helper.cpp new file mode 100644 index 0000000..ea83685 --- /dev/null +++ b/test/AMRmesh/test_AMRmesh_stencil_helper.cpp @@ -0,0 +1,284 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file test_AMRmesh_stencil_help.cpp + * + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +#include +#include + +namespace kalypsso +{ +// ============================================================================= +// ============================================================================= +template +void +run_test(const ParallelEnv & par_env, const ConfigMap & config_map) +{ + using StencilHelper_t = StencilHelper; + using CellLocation_t = CellLocation; + + using Hydro_t = core::models::Hydro; + + InitialAMRSetup initial_amr_setup( + par_env, config_map, InitFuncSineWave{}); + + const auto level_min = config_map.getInteger("amr", "level_min", 2); + const auto level_max = config_map.getInteger("amr", "level_max", 2); + const auto no_refine = (level_min == level_max); + + // setup initial p4est mesh with some test pseudo random refined/coarsen cells pattern + initial_amr_setup.setup_initial_mesh(no_refine); + + auto & amr_mesh = initial_amr_setup.mesh(); + auto mesh_map = initial_amr_setup.mesh_map(); + auto block_sizes = initial_amr_setup.block_sizes(); + auto brick_sizes = initial_amr_setup.brick_sizes(); + auto is_brick_periodic = initial_amr_setup.is_brick_periodic(); + + // retrieve amr keys + mesh_map->update_orchard_keys(amr_mesh.forest(), amr_mesh.ghost()); + auto orchard_keys_host = mesh_map->orchard_keys_host_clone(); + auto orchard_keys_device = mesh_map->orchard_keys_clone(); + + // mirror keys array must be up to date for MeshGhostExchange to be functional + mesh_map->update_mirror_orchard_keys(amr_mesh.ghost()); + + // rebuild the hashmap (must be done after AMR cycle) + constexpr bool on_device = true; + mesh_map->update_hashmap(on_device); + auto amr_hashmap_device = mesh_map->hashmap(); + mesh_map->update_conformal_status(); + auto conformal_status = mesh_map->conformal_status(); + + { + auto amr_mesh_info = initial_amr_setup.amr_mesh_info(); + auto userdata_block_in = initial_amr_setup.setup_initial_data_block_new(orchard_keys_device); + userdata_block_in.resize(amr_mesh_info.local_num_quadrants_total()); + + std::string filename = dim == 2 ? "test_AMR_stencil_helper_2d" : "test_AMR_stencil_helper_3d"; + + // provide mapping between variables Id and variables names + const Hydro_t model(dim); + + DataWriter::save( + filename, userdata_block_in, config_map, amr_mesh, model); + } + + const auto block_sizes_emf = block_sizes + 1; + + auto stencil_helper = StencilHelper_t( + amr_hashmap_device, orchard_keys_device, block_sizes, brick_sizes, is_brick_periodic); + + + // const auto iOct = 1235; + // const auto iOct = 1246; + // const auto iOct = 1248; + // const auto iOct = 1264; + std::list octs = { 1235, 1246, 1248, 1264 }; + + for (const auto & iOct : octs) + { + const auto face_xmin_neighbor_is_coarser = + conformal_face_status_t::face_xmin(conformal_status(iOct)) == + conformal_neighbor_status::NEIGHBOR_IS_COARSER; + + // const auto face_xmin_neighbor_is_finer = + // conformal_face_status_t::face_xmin(conformal_status(iOct)) == + // conformal_neighbor_status::NEIGHBOR_IS_FINER; + + for (int32_t cell_index = 0; cell_index < Kokkos::dim_prod(block_sizes_emf); ++cell_index) + { + auto const coords_face = cellindex_to_coord(cell_index, block_sizes_emf); + auto const & i = coords_face[IX]; + auto const & j = coords_face[IY]; + + auto coords_cell = coords_face; + + if (i == 0 and face_xmin_neighbor_is_coarser) + { + if (j == block_sizes[IY]) + coords_cell[IY] -= 1; + constexpr shift_t shift{ -1, 0 }; + const auto key_cur = orchard_keys_device(iOct); + const CellLocation_t cell_loc{ coords_cell, key_cur, iOct, false }; + const auto cell_loc_neigh = stencil_helper.getNeighLocCoarser(cell_loc, shift); + + const auto ijk_cell = cell_loc_neigh.ijk; + const auto iOct_neigh = cell_loc_neigh.iOct; + + auto ijk_face = ijk_cell; + if (j == block_sizes[IY]) + ijk_face[IY] += 1; + + if ((j & 0x1) == 0) + { + printf("i=%d j=%d iOct=%d | in=%d jn=%d iOctn=%ld\n", + i, + j, + iOct, + ijk_face[IX], + ijk_face[IY], + iOct_neigh); + } + } + else + { + printf("i=%d j=%d iOct=%d\n", i, j, iOct); + } + } + } + + std::list octs_fine = { 1247, 1264 }; + + for (const auto & iOct : octs_fine) + { + const auto face_xmin_neighbor_is_finer = + conformal_face_status_t::face_xmin(conformal_status(iOct)) == + conformal_neighbor_status::NEIGHBOR_IS_FINER; + + const auto face_xmax_neighbor_is_finer = + conformal_face_status_t::face_xmax(conformal_status(iOct)) == + conformal_neighbor_status::NEIGHBOR_IS_FINER; + + for (int32_t cell_index = 0; cell_index < Kokkos::dim_prod(block_sizes); ++cell_index) + { + auto const coords_cell = cellindex_to_coord(cell_index, block_sizes); + auto const & i = coords_cell[IX]; + auto const & j = coords_cell[IY]; + + if (i == block_sizes[IX] - 1 and face_xmax_neighbor_is_finer) + { + constexpr shift_t shift{ 1, 0 }; + const auto key_cur = orchard_keys_device(iOct); + const CellLocation_t cell_loc{ coords_cell, key_cur, iOct, false }; + const auto cell_loc_neigh = stencil_helper.getNeighLocFiner(cell_loc, shift); + + const auto ijk_cell = cell_loc_neigh.ijk; + const auto iOct_neigh = cell_loc_neigh.iOct; + + if ((j & 0x1) == 0) + { + printf("i=%d j=%d iOct=%d | in=%d jn=%d iOctn=%ld\n", + i, + j, + iOct, + ijk_cell[IX], + ijk_cell[IY], + iOct_neigh); + } + } + else if (i == 0 and face_xmin_neighbor_is_finer) + { + constexpr shift_t shift{ -1, 0 }; + const auto key_cur = orchard_keys_device(iOct); + const CellLocation_t cell_loc{ coords_cell, key_cur, iOct, false }; + const auto cell_loc_neigh = stencil_helper.getNeighLocFiner(cell_loc, shift); + + const auto ijk_cell = cell_loc_neigh.ijk; + const auto iOct_neigh = cell_loc_neigh.iOct; + + // if ((j & 0x1) == 0) + { + printf("i=%d j=%d iOct=%d | in=%d jn=%d iOctn=%ld\n", + i, + j, + iOct, + ijk_cell[IX], + ijk_cell[IY], + iOct_neigh); + } + } + else + { + printf("i=%d j=%d iOct=%d\n", i, j, iOct); + } + } + } + +} // run_test + +} // namespace kalypsso + +// ============================================================================= +// ============================================================================= +// ============================================================================= +int +main(int argc, char ** argv) +{ + + { + // initialize mpi, kokkos and p4est, use default MPI communicator : MPI_COMM_WORLD + kalypsso::ParallelEnv par_env(argc, argv); + +#ifdef KALYPSSO_CORE_USE_SPDLOG + kalypsso::kalypsso_spdlog_config(argc, argv, par_env.rank(), par_env.size()); +#endif + + if (kalypsso::cmdline_arg_exists(argv, argv + argc, "--help")) + { + if (par_env.rank() == 0) + { + // clang-format off + std::cout << "Example cmdline: \"./test_AMRmesh_stencil_helper --ini test_AMRmesh_stencil_2d.ini \"\n"; + // clang-format on + } + return 0; + } + + // parse command line : 2d or 3d ? + bool use_3d = kalypsso::cmdline_arg_exists(argv, argv + argc, "--3d"); + + // check if user passed a custom ini filename + std::string config_filename = kalypsso::cmdline_get_string(argv, argv + argc, "--ini"); + + // provide a default config filename (that exists) + if (config_filename.size() == 0) + config_filename = use_3d ? "./test_AMRmesh_stencil_3d.ini" : "./test_AMRmesh_stencil_2d.ini"; + + kalypsso::ConfigMap config_map = kalypsso::broadcast_parameters(config_filename); + + if (use_3d) + { + // run a 3d test + kalypsso::run_test<3, kalypsso::DefaultDevice>(par_env, config_map); + } + else + { + // run a 2d test + kalypsso::run_test<2, kalypsso::DefaultDevice>(par_env, config_map); + } + + + if (par_env.rank() == 0) + { + printf("================================================\n"); + printf("END RUN_TEST\n"); + printf("================================================\n"); + } + } + + return EXIT_SUCCESS; + +} // main diff --git a/test/AMRmesh/test_AMRmesh_userdata.cpp b/test/AMRmesh/test_AMRmesh_userdata.cpp new file mode 100644 index 0000000..8ab32e4 --- /dev/null +++ b/test/AMRmesh/test_AMRmesh_userdata.cpp @@ -0,0 +1,285 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file test_AMRmesh_userdata.cpp + */ + +#include // for KALYPSSO_CORE_USE_HDF5, ... +#include +#include // for orchard_key_view_t +#include +#include +#include +#include +#include +#include +#ifdef KALYPSSO_CORE_USE_HDF5 +# include +#endif +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +namespace kalypsso +{ + +// ============================================================ +// ============================================================ +// ============================================================ +template +void +run_test(const ParallelEnv & par_env, const ConfigMap & config_map) +{ + //! our kokkos execution space + using exec_space = typename device_t::execution_space; + + using namespace p4est; + + using Hydro_t = core::models::Hydro; + + using DataArrayBlock_t = DataArrayBlock; + + if (par_env.rank() == 0) + printf("================================================\n"); + + auto amr_mesh = std::make_shared>(par_env, config_map); + + amr_mesh->reset_ghost(); + + auto forest = amr_mesh->forest(); + // auto conn = amr_mesh->connectivity(); + auto geom = amr_mesh->geometry(); + + // geometry not supported here (for now) + assert(geom == nullptr); + + auto conn_name = config_map.getString("amr", "connectivity", "invalid_connectivity"); + auto geom_name = geom == nullptr ? "no_geometry" : geom->name; + + if (par_env.rank() == 0) + { + printf("Running a %dD test with connectivity %s and geometry %s\n", + dim, + conn_name.c_str(), + geom_name); + } + + printf("Forest has %ld global octants, %d local octants on MPI proc %d.\n", + forest->global_num_quadrants, + forest->local_num_quadrants, + par_env.rank()); + + // now generate some user data (block based AMR) + HydroParams params = HydroParams(config_map); + // BlastParams blastParams = BlastParams(config_map); + + uint32_t bx = (uint32_t)config_map.getInteger("amr", "bx", 0); + uint32_t by = (uint32_t)config_map.getInteger("amr", "by", 0); + uint32_t bz = (uint32_t)config_map.getInteger("amr", "bz", 0); + + block_size_t block_sizes; + block_sizes[IX] = bx; + block_sizes[IY] = by; + if constexpr (dim == 3) + block_sizes[IZ] = bz; + + Kokkos::Array brick_sizes; + brick_sizes[0] = static_cast(config_map.getInteger("p4est_connectivity", "nbrick_x", 2)); + brick_sizes[1] = static_cast(config_map.getInteger("p4est_connectivity", "nbrick_y", 3)); + if constexpr (dim == 3) + brick_sizes[2] = + static_cast(config_map.getInteger("p4est_connectivity", "nbrick_z", 4)); + + const int nbvar = nbvar_hydro(); + + // retrieve available / allowed names: fieldManager, and field map (fm) + // necessary to access user data + Hydro_t model = Hydro_t(params.dimType); + // FieldMap fm; + // fm.setup(params, config_map); + // auto fm = fieldMgr.get_id2index(); + const auto fm = model.get_fieldmap(); + + // variable map + const auto id2names = model.get_id2names_map(); + + auto userdataBlock = + DataArrayBlock_t("fake_data_block", block_sizes, nbvar, amr_mesh->local_num_quadrants()); + + // create orchard keys as a view + MeshMap mesh_map(config_map, par_env); + + mesh_map.update_orchard_keys(amr_mesh->forest(), amr_mesh->ghost()); + auto orchard_keys_host = mesh_map.orchard_keys_host(); + auto orchard_keys_device = mesh_map.orchard_keys(); + + const auto nbCellsPerLeaf = userdataBlock.num_cells(); + const auto nbCellsTotal = amr_mesh->local_num_quadrants() * nbCellsPerLeaf; + + Kokkos::parallel_for( + "init_data", + Kokkos::RangePolicy(0, nbCellsTotal), + KOKKOS_LAMBDA(uint32_t global_index) { + constexpr auto ID = core::models::Hydro::ID; + // constexpr auto IP = core::models::Hydro::IP; + constexpr auto IE = core::models::Hydro::IE; + constexpr auto IU = core::models::Hydro::IU; + constexpr auto IV = core::models::Hydro::IV; + constexpr auto IW = core::models::Hydro::IW; + + // convert global index into + // - octant id + // - cell_index inside block (from 0 to nbCellsPerLeaf-1) + const auto iOct = global_index / nbCellsPerLeaf; + const auto cell_index = global_index - iOct * nbCellsPerLeaf; + + // compute ix,iy,iz of local cell inside + // block from index + auto iCoord = cellindex_to_coord(cell_index, block_sizes); + + // get block orchard key + const auto key = orchard_keys_device(iOct); + + // get block level + // const auto level = orchard_key_t::level(key); + + // compute physical x,y,z for that cell (cell center) + const auto xyz = orchard_key_to_cell_coord(key, iCoord, block_sizes[IX]); + + auto e = xyz[IX] * xyz[IX] + xyz[IY] * xyz[IY]; + if constexpr (dim == 3) + e += xyz[IZ] * xyz[IZ]; + + userdataBlock(cell_index, fm[ID], iOct) = xyz[0] + xyz[1]; + userdataBlock(cell_index, fm[IE], iOct) = e; + userdataBlock(cell_index, fm[IU], iOct) = 0; + userdataBlock(cell_index, fm[IV], iOct) = 1; + if constexpr (dim == 3) + userdataBlock(cell_index, fm[IW], iOct) = 42; + }); + + + { + std::string outputDir = config_map.getString("output", "outputDir", "./"); + std::string filename = dim == 2 ? "test_userdata_2d" : "test_userdata_3d"; + + DataWriter::save(filename, userdataBlock, config_map, *amr_mesh, model); + } + +} // run_test + +} // namespace kalypsso + +// ====================================================== +// ====================================================== +bool +hasEnding(std::string const & fullString, std::string const & ending) +{ + if (fullString.length() >= ending.length()) + { + return (0 == + fullString.compare(fullString.length() - ending.length(), ending.length(), ending)); + } + else + { + return false; + } +} + +// ====================================================== +// ====================================================== +//! parse command line +bool +arg_exists(char ** begin, char ** end, const std::string & arg) +{ + return std::find(begin, end, arg) != end; +} + +// ====================================================== +// ====================================================== +std::string +get_ini_filename(char ** begin, char ** end, const std::string & arg) +{ + char ** itr = std::find(begin, end, arg); + if (itr != end && ++itr != end) + { + return std::string(*itr); + } + return std::string(""); +} + +// ====================================================== +// ====================================================== +// ====================================================== +int +main(int argc, char ** argv) +{ + + { + // initialize mpi, kokkos and p4est + kalypsso::ParallelEnv par_env(argc, argv); + +#ifdef KALYPSSO_CORE_USE_SPDLOG + kalypsso::kalypsso_spdlog_config(argc, argv, par_env.rank(), par_env.size()); +#endif + + if (kalypsso::cmdline_arg_exists(argv, argv + argc, "--help")) + { + if (par_env.rank() == 0) + { + // clang-format off + std::cout << "Example cmdline: \"mpirun -np 4 ./test_AMRmesh_userdata --ini test_AMRmesh_userdata_2d.ini --refine_level 6\"\n"; + // clang-format on + } + return 0; + } + + // parse command line : 2d or 3d ? + bool threed = arg_exists(argv, argv + argc, "--3d"); + + // check if user passed a custom ini filename + std::string config_filename = get_ini_filename(argv, argv + argc, "--ini"); + + // provide a default config filename (that exists) + if (config_filename.size() == 0) + { + config_filename = + threed ? "./test_AMRmesh_userdata_3d.ini" : "./test_AMRmesh_userdata_2d.ini"; + } + + kalypsso::ConfigMap config_map = kalypsso::broadcast_parameters(config_filename); + + // check if input file is valid, i.e. parameter run/dimension is 2 or 3 + const auto dim = kalypsso::get_dim(config_map); + assertm(dim == 2 or dim == 3, "[test_AMRmesh_userdata] Wrong dimension"); + + if (dim == 2) + { + kalypsso::run_test<2, kalypsso::DefaultDevice>(par_env, config_map); + } + else if (dim == 3) + { + kalypsso::run_test<3, kalypsso::DefaultDevice>(par_env, config_map); + } + else + { + if (par_env.rank() == 0) + { + std::cout << "Input file is not valid ! check parameter run/dimension.\n"; + } + } + } + + return 0; +} diff --git a/test/AMRmesh/test_AMRmesh_userdata_2d.ini b/test/AMRmesh/test_AMRmesh_userdata_2d.ini new file mode 100644 index 0000000..0c23206 --- /dev/null +++ b/test/AMRmesh/test_AMRmesh_userdata_2d.ini @@ -0,0 +1,71 @@ +[run] +dimension=2 +tEnd=0.5 +nStepmax=10 + +# noutput equals -1 means we dump data at every time steps +nOutput=-1 + +[amr] +level_min=4 +level_max=6 + +use_block_data=yes +bx=4 +by=4 + +# init step may contain calls to globalRefine +# if the following option is true, we interleave +# calls to load balancing in between globalRefine +# if the following option is false, a single load +# balancing operation is done at the end of +# globalRefine steps +enable_load_balance_during_init=false + +epsilon_refine=0.01 +epsilon_coarsen=0.0025 + +connectivity = brick + +[p4est_connectivity] +nbrick_x=3 +nbrick_y=2 + +periodic_x=1 +periodic_y=1 + +[mesh] +xmin=0.0 +ymin=0.0 +scaling_factor=1.0 + +boundary_type_xmin=WALL +boundary_type_xmax=WALL + +boundary_type_ymin=WALL +boundary_type_ymax=WALL + +[hydro] +gamma0=1.666 +cfl=0.8 +niter_riemann=10 +slope_type=2 +problem=blast +riemann=HLLC + +[blast] +density_in=1.0 +density_out=1.2 +radius=0.25 + +[output] +outputDir=./ +outputPrefix=test_AMRmesh_userdata_2d +outputVtkAscii=false +write_variables=rho,rho_vx +write_mesh_info=yes +vtk_enabled=no +hdf5_enabled=yes + +[other] +implementationVersion=0 diff --git a/test/AMRmesh/test_AMRmesh_userdata_3d.ini b/test/AMRmesh/test_AMRmesh_userdata_3d.ini new file mode 100644 index 0000000..6469576 --- /dev/null +++ b/test/AMRmesh/test_AMRmesh_userdata_3d.ini @@ -0,0 +1,78 @@ +[run] +dimension=3 +tEnd=0.5 +nStepmax=10 + +# noutput equals -1 means we dump data at every time steps +nOutput=-1 + +[amr] +level_min=4 +level_max=6 + +use_block_data=yes +bx=4 +by=4 +bz=4 + +# init step may contain calls to globalRefine +# if the following option is true, we interleave +# calls to load balancing in between globalRefine +# if the following option is false, a single load +# balancing operation is done at the end of +# globalRefine steps +enable_load_balance_during_init=false + +epsilon_refine=0.01 +epsilon_coarsen=0.0025 + +connectivity = brick + +[p4est_connectivity] +nbrick_x=3 +nbrick_y=2 +nbrick_z=4 + +periodic_x=1 +periodic_y=1 +periodic_z=1 + +[mesh] +xmin=0.0 +ymin=0.0 +zmin=0.0 +scaling_factor=1.0 + +boundary_type_xmin=WALL +boundary_type_xmax=WALL + +boundary_type_ymin=WALL +boundary_type_ymax=WALL + +boundary_type_zmin=WALL +boundary_type_zmax=WALL + +[hydro] +gamma0=1.666 +cfl=0.8 +niter_riemann=10 +slope_type=2 +problem=blast +riemann=HLLC + +[blast] +density_in=1.0 +density_out=1.2 +radius=0.25 + +[output] +outputDir=./ +outputPrefix=test_AMRmesh_userdata_3d +outputVtkAscii=false +write_variables=rho,rho_vx +write_mesh_info=yes +vtk_enabled=no +hdf5_enabled=yes + +[other] +implementationVersion=0 diff --git a/test/AMRmesh/test_brick_connectivity.cpp b/test/AMRmesh/test_brick_connectivity.cpp new file mode 100644 index 0000000..994aac2 --- /dev/null +++ b/test/AMRmesh/test_brick_connectivity.cpp @@ -0,0 +1,170 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file test_brick_connectivity.cpp + */ + +#include +#include +#include + +#include + +#ifdef KALYPSSO_CORE_USE_CNPY +# include +#endif // KALYPSSO_CORE_USE_CNPY + +#include + +namespace kalypsso +{ + +/* ============================================================ */ +/* ============================================================ */ +/* ============================================================ */ +template +void +run_test(brick_size_t brick_sizes) +{ + + BrickConnectivityData brick_conn_data(brick_sizes); + + auto num_trees = brick_conn_data.m_num_trees; + using coord_view_t = + Kokkos::View>; + + coord_view_t coord_view(brick_conn_data.m_brick_coords.data(), + dim * static_cast(num_trees)); + + if constexpr (dim == 2) + { +#ifdef KALYPSSO_CORE_USE_CNPY + save_cnpy(Kokkos::subview(coord_view, std::make_pair(0 * num_trees, 1 * num_trees)), "x_2d"); + save_cnpy(Kokkos::subview(coord_view, std::make_pair(1 * num_trees, 2 * num_trees)), "y_2d"); +#endif + } + else if constexpr (dim == 3) + { +#ifdef KALYPSSO_CORE_USE_CNPY + save_cnpy(Kokkos::subview(coord_view, std::make_pair(0 * num_trees, 1 * num_trees)), "x_3d"); + save_cnpy(Kokkos::subview(coord_view, std::make_pair(1 * num_trees, 2 * num_trees)), "y_3d"); + save_cnpy(Kokkos::subview(coord_view, std::make_pair(2 * num_trees, 3 * num_trees)), "z_3d"); +#endif + } + + auto treeids = brick_conn_data.m_treeIds; + auto coords = brick_conn_data.m_brick_coords; + + if constexpr (dim == 2) + { + for (int i = 0; i < num_trees; ++i) + { + printf("i=%5d treeId=%5d x=%5d y=%5d\n", i, treeids(i), coords(i, 0), coords(i, 1)); + } + } + if constexpr (dim == 3) + { + for (int i = 0; i < num_trees; ++i) + { + printf("i=%5d treeId=%5d x=%5d y=%5d z=%5d\n", + i, + treeids(i), + coords(i, 0), + coords(i, 1), + coords(i, 2)); + } + } + +} // run_test + +} // namespace kalypsso + +// parse command line +bool +arg_exists(char ** begin, char ** end, const std::string & arg) +{ + return std::find(begin, end, arg) != end; +} + +template +decltype(auto) +get_brick_sizes(char ** begin, char ** end) +{ + KALYPSSO_DISABLE_CONVERSION_WARNINGS_PUSH() + if constexpr (dim == 2) + { + kalypsso::brick_size_t<2> res{ -1, -1 }; + char ** itr = std::find(begin, end, std::string("--2d")); + if (itr != end && (itr + 1) != end && (itr + 2) != end) + { + res[0] = std::atoi(*(itr + 1)); + res[1] = std::atoi(*(itr + 2)); + } + return res; + } + else if constexpr (dim == 3) + { + kalypsso::brick_size_t<3> res{ -1, -1, -1 }; + char ** itr = std::find(begin, end, std::string("--3d")); + if ((itr != end) and (itr + 1) != end and (itr + 2) != end and (itr + 3) != end) + { + res[0] = std::atoi(*(itr + 1)); + res[1] = std::atoi(*(itr + 2)); + res[2] = std::atoi(*(itr + 3)); + } + return res; + } + KALYPSSO_DISABLE_CONVERSION_WARNINGS_POP() +} + +// ====================================================== +// ====================================================== +// ====================================================== +int +main(int argc, char ** argv) +{ + // initialize mpi, kokkos and p4est, use default MPI communicator : MPI_COMM_WORLD + kalypsso::ParallelEnv par_env(argc, argv); + + // parse command line : 2d / 3d + bool use_2d = arg_exists(argv, argv + argc, "--2d"); + bool use_3d = arg_exists(argv, argv + argc, "--3d"); + + // make sure at least 2d is enable + if (!use_3d and !use_2d) + use_2d = true; + + { + + if (use_3d) + { + auto brick_sizes = get_brick_sizes<3>(argv, argv + argc); + + if (brick_sizes[0] <= 0 or brick_sizes[1] <= 0 or brick_sizes[2] <= 0) + { + printf("Wrong brick sizes\nExample cmdline: \n ./test_brick_connectivity --2d 3 2\n\n"); + return -1; + } + + // run a 3d test + kalypsso::run_test<3>(brick_sizes); + } + else if (use_2d) + { + auto brick_sizes = get_brick_sizes<2>(argv, argv + argc); + + if (brick_sizes[0] <= 0 or brick_sizes[1] <= 0) + { + printf("Wrong brick sizes\nExample cmdline: \n ./test_brick_connectivity --2d 3 2\n\n"); + return -1; + } + + // run a 2d test + kalypsso::run_test<2>(brick_sizes); + } + } + + return 0; +} diff --git a/test/AMRmesh/test_first_derivative.cpp b/test/AMRmesh/test_first_derivative.cpp new file mode 100644 index 0000000..21db087 --- /dev/null +++ b/test/AMRmesh/test_first_derivative.cpp @@ -0,0 +1,260 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file test_first_derivative.cpp + * + * Purpose: + * test and validate code used to compute 1st derivative on 3 or 5 points stencil. + * + */ + +#include + +#include +#include +#include +#include +#include +#ifdef KALYPSSO_CORE_USE_MPI +# include +#endif // KALYPSSO_CORE_USE_MPI + +#include +#include + +#include +#include +#include + +#include + +namespace kalypsso +{ + +// ============================================================================= +// ============================================================================= +template +void +run_test(const ParallelEnv & par_env, const ConfigMap & config_map) +{ + // using exec_space = typename device_t::execution_space; + + // using DataArrayBlock_t = DataArrayBlock; + // using DataArrayBlockHost_t = DataArrayBlockHost; + + using DataArrayGhostedBlock_t = DataArrayGhostedBlock; + + using Hydro_t = core::models::Hydro; + + InitialAMRSetup initial_amr_setup( + par_env, config_map, InitFuncParabola{}); + // InitialAMRSetup initial_amr_setup( + // par_env, config_map, InitFuncSineWave{}); + + const auto level_min = config_map.getInteger("amr", "level_min", 2); + const auto level_max = config_map.getInteger("amr", "level_max", 2); + const auto no_refine = (level_min == level_max); + + // setup initial p4est mesh with some test pseudo random refined/coarsen cells pattern + initial_amr_setup.setup_initial_mesh(no_refine); + + auto & amr_mesh = initial_amr_setup.mesh(); + auto mesh_map = initial_amr_setup.mesh_map(); + auto amr_mesh_info = initial_amr_setup.amr_mesh_info(); + + // retrieve amr keys + mesh_map->update_orchard_keys(amr_mesh.forest(), amr_mesh.ghost()); + auto orchard_keys_host = mesh_map->orchard_keys_host_clone(); + auto orchard_keys_device = mesh_map->orchard_keys_clone(); + +#ifdef KALYPSSO_CORE_USE_MPI + // mirror keys array must be up to date for MeshGhostExchanger to be functional + mesh_map->update_mirror_orchard_keys(amr_mesh.ghost()); +#endif // KALYPSSO_CORE_USE_MPI + + // rebuild the hashmap (must be done after AMR cycle) + constexpr bool on_device = true; + mesh_map->update_hashmap(on_device); + + auto userdata_in = initial_amr_setup.setup_initial_data_ghosted_block(orchard_keys_device, false); + userdata_in.resize(amr_mesh_info.local_num_quadrants_total()); + + // auto userdata_in_host = DataArrayGhostedBlock_t::create_host_mirror_view(userdata_in); + + auto userdata_out = DataArrayGhostedBlock_t(userdata_in.block_size(), + userdata_in.block_size() + 2 * 1, + get_shift(-1), + "userdata_out", + userdata_in.num_vars(), + userdata_in.num_quadrants()); + // Kokkos::deep_copy(userdata_out.data().storage(), -1.0); + + // + // save data before stencil computation + // + // { + // std::string filename = dim == 2 ? "test_AMR_stencil_2d_before" : + // "test_AMR_stencil_3d_before"; + + // DataWriter::save( + // filename, userdata_in, config_map, amr_mesh); + // } + +#ifdef KALYPSSO_CORE_USE_MPI + // + // make sure MPI ghosts are OK + // + + // create the main object doing MPI comm to exchange ghost data + // MeshGhostsExchanger mesh_ghosts_exchanger( + // config_map, par_env, amr_mesh, *mesh_map); + // MPI_Barrier(par_env.mpi_comm()); + + // mesh_ghosts_exchanger.exchange(userdata_in); +#endif // KALYPSSO_CORE_USE_MPI + + // + // compute first derivative + // + { + const auto stencil_length = core::FIRST_DERIVATIVE_STENCIL::THREE_POINTS; + // const auto stencil_length = core::FIRST_DERIVATIVE_STENCIL::FIVE_POINTS; + // const auto stencil_length = core::FIRST_DERIVATIVE_STENCIL::SEVEN_POINTS; + + const auto nbOcts = amr_mesh_info.local_num_quadrants(); + + core::FirstOrderDerivativeFiniteDifference::first_derivative(config_map, + orchard_keys_device, + 0, + nbOcts, + userdata_in, + 0, + userdata_out, + 0, + IX, + stencil_length); + + core::FirstOrderDerivativeFiniteDifference::first_derivative(config_map, + orchard_keys_device, + 0, + nbOcts, + userdata_in, + 0, + userdata_out, + 1, + IY, + stencil_length); + + { + std::string filename = dim == 2 ? "test_AMR_stencil_2d_after" : "test_AMR_stencil_3d_after"; + + // provide mapping between variables Id and variables names + const Hydro_t model(dim); + + DataWriter::save( + filename, userdata_out, config_map, amr_mesh, true, model); + } + } + +} // run_test + +} // namespace kalypsso + +bool +hasEnding(std::string const & fullString, std::string const & ending) +{ + if (fullString.length() >= ending.length()) + { + return (0 == + fullString.compare(fullString.length() - ending.length(), ending.length(), ending)); + } + else + { + return false; + } +} // hasEnding + +// ============================================================================= +// ============================================================================= +// ============================================================================= +int +main(int argc, char ** argv) +{ + + { + // initialize mpi, kokkos and p4est, use default MPI communicator : MPI_COMM_WORLD + kalypsso::ParallelEnv par_env(argc, argv); + +#ifdef KALYPSSO_CORE_USE_SPDLOG + kalypsso::kalypsso_spdlog_config(argc, argv, par_env.rank(), par_env.size()); +#endif + + if (kalypsso::cmdline_arg_exists(argv, argv + argc, "--help")) + { + if (par_env.rank() == 0) + { + // clang-format off + std::cout << "Example cmdline: \"mpirun -np 4 ./test_first_derivative --ini test_AMRmesh_stencil_2d.ini --refine_level 6\"\n"; + // clang-format on + } + return 0; + } + + // parse command line : 2d or 3d ? + bool use_3d = kalypsso::cmdline_arg_exists(argv, argv + argc, "--3d"); + + // check if user passed a custom ini filename + std::string config_filename = kalypsso::cmdline_get_string(argv, argv + argc, "--ini"); + + // provide a default config filename (that exists) + if (config_filename.size() == 0) + config_filename = use_3d ? "./test_AMRmesh_stencil_3d.ini" : "./test_AMRmesh_stencil_2d.ini"; + + kalypsso::ConfigMap config_map = kalypsso::broadcast_parameters(config_filename); + + // check if input file is valid, i.e. parameter run/dimension is 2 or 3 + const auto dim = kalypsso::get_dim(config_map); + assertm(dim == 2 or dim == 3, "[test_first_derivative] Wrong dimension"); + + // check command line for optional parameter (refine_level) + kalypsso::InitialAMRSetupBase::refine_level = + kalypsso::cmdline_arg_exists(argv, argv + argc, "--refine_level") + ? kalypsso::cmdline_get_integer(argv, argv + argc, "--refine_level") + : 4; + + if (par_env.rank() == 0) + { + printf("================================================\n"); + printf("START RUN_TEST\n"); + printf("================================================\n"); + } + + if (dim == 2) + { + kalypsso::run_test<2, kalypsso::DefaultDevice>(par_env, config_map); + } + else if (dim == 3) + { + kalypsso::run_test<3, kalypsso::DefaultDevice>(par_env, config_map); + } + else + { + if (par_env.rank() == 0) + { + std::cout << "Input file is not valid ! check parameter run/dimension.\n"; + } + } + + if (par_env.rank() == 0) + { + printf("================================================\n"); + printf("END RUN_TEST\n"); + printf("================================================\n"); + } + } + + return 0; + +} // main diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..f02e78f --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,51 @@ +set(KALYPSSO_TEST_AMR_MAX_LEVEL + 3 + CACHE STRING "Number of maximum level of AMR for tests") + +if(KALYPSSO_CORE_USE_MPI) + set(KALYPSSO_TEST_MPIEXEC_NUMPROCS + 2 + CACHE STRING "Number of MPI process for tests") + set(TEST_MPI_PREFIX_CMD ${MPIEXEC_EXECUTABLE} ${MPIEXEC_NUMPROC_FLAG} + ${KALYPSSO_TEST_MPIEXEC_NUMPROCS}) +endif() + +if(Kokkos_ENABLE_OPENMP) + set(KALYPSSO_TEST_OMP_NUM_THREADS + 2 + CACHE STRING "Number of OpenMP threads for tests") +endif() + +add_subdirectory(test_common) + +add_subdirectory(kokkos) + +add_subdirectory(p4est_simple) + +add_subdirectory(p4est_wrapper) + +add_subdirectory(io) + +add_subdirectory(AMRmesh) + +add_subdirectory(AMRkey) + +add_subdirectory(MeshMap) + +if(KALYPSSO_CORE_USE_SPDLOG) + add_subdirectory(spdlog) +endif(KALYPSSO_CORE_USE_SPDLOG) + +add_subdirectory(monitoring) + +add_subdirectory(HighFive) + +add_subdirectory(container) + +add_subdirectory(core) + +if(KALYPSSO_CORE_USE_CPPTRACE) + add_subdirectory(cpptrace) +endif(KALYPSSO_CORE_USE_CPPTRACE) + +add_subdirectory(utils) diff --git a/test/HighFive/CMakeLists.txt b/test/HighFive/CMakeLists.txt new file mode 100644 index 0000000..d76c20b --- /dev/null +++ b/test/HighFive/CMakeLists.txt @@ -0,0 +1,15 @@ +if(KALYPSSO_CORE_USE_MPI) + # ======================================================================= + # ======================================================================= + add_executable(parallel_hdf5_collective_io_write parallel_hdf5_collective_io_write.cpp) + target_compile_options(parallel_hdf5_collective_io_write PRIVATE -DKALYPSSO_IO_HIGH_FIVE) + target_link_libraries(parallel_hdf5_collective_io_write + PUBLIC kalypsso::mpiUtils kalypsso::core_config HighFive::HighFive) + + # ======================================================================= + # ======================================================================= + add_executable(parallel_hdf5_collective_io_read parallel_hdf5_collective_io_read.cpp) + target_compile_options(parallel_hdf5_collective_io_read PRIVATE -DKALYPSSO_IO_HIGH_FIVE) + target_link_libraries(parallel_hdf5_collective_io_read + PUBLIC kalypsso::mpiUtils kalypsso::core_config HighFive::HighFive) +endif(KALYPSSO_CORE_USE_MPI) diff --git a/test/HighFive/parallel_hdf5_collective_io_read.cpp b/test/HighFive/parallel_hdf5_collective_io_read.cpp new file mode 100644 index 0000000..e080d3e --- /dev/null +++ b/test/HighFive/parallel_hdf5_collective_io_read.cpp @@ -0,0 +1,120 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include + +#include + +#include + +#include + +const std::string file_name("parallel_collective_example.h5"); + +// Currently, HighFive doesn't wrap retrieving information from property lists. +// Therefore, one needs to use HDF5 directly. For example, to see if collective +// MPI-IO operations were used, one may. Conveniently, this also provides identifiers +// of the cause for not using collective MPI calls. +void +check_collective_io(const HighFive::DataTransferProps & xfer_props) +{ + auto mnccp = HighFive::MpioNoCollectiveCause(xfer_props); + if (mnccp.getLocalCause() || mnccp.getGlobalCause()) + { + std::cout << "The operation was successful, but couldn't use collective MPI-IO. local cause: " + << mnccp.getLocalCause() << " global cause:" << mnccp.getGlobalCause() << std::endl; + } +} + +// This is an example of how to read HDF5 files when all +// operations are collective, i.e. all MPI ranks participate in +// all HDF5 related function calls. +// +int +main(int argc, char ** argv) +{ + // initialize mpi, kokkos and p4est, use default MPI communicator : MPI_COMM_WORLD + kalypsso::ParallelEnv par_env(argc, argv); + + try + { + // MPI-IO requires informing HDF5 that we want something other than + // the default behaviour. This is done through property lists. We + // need a file access property list. + auto fapl = HighFive::FileAccessProps(); + + // We tell HDF5 to use MPI-IO + fapl.add(HighFive::MPIOFileAccess{ par_env.mpi_comm(), MPI_INFO_NULL }); + + // We also specify that we want all meta-data related operations + // to use MPI collective operations. This implies that all MPI ranks + // in the communicator must participate in any HDF5 operation that + // reads or writes metadata. Essentially, this is safe if all MPI ranks + // participate in all HDF5 operations. + fapl.add(HighFive::MPIOCollectiveMetadata{}); + + // Now we can create the file as usual. + HighFive::File file(file_name, HighFive::File::ReadOnly, fapl); + + auto dataset = file.getDataSet("grp/dset"); + + auto dims = dataset.getDimensions(); + + if (par_env.rank() == 0) + { + std::cout << "dimensions of dataset [grp/dset]\n"; + for (size_t i = 0; i < dims.size(); ++i) + { + std::cout << "dataset dims[" << i << "] = " << dims[i] << "\n"; + } + } + + // each MPI rank will read a different piece, split dims[0] among all MPI ranks + // if division is not exact, the last rank receive a smaller amount of data + std::size_t pieceSize = (dims[0] + par_env.size() - 1) / par_env.size(); + std::vector offset{ std::size_t(pieceSize * par_env.rank()), 0ul }; + std::vector count{ pieceSize, 2ul }; + + // the last rank maybe read a smaller amount of data + if (par_env.rank() == par_env.size() - 1) + { + pieceSize = dims[0] - pieceSize * (par_env.size() - 1); + count[0] = pieceSize; + } + + // Each node want to read its own piece + std::vector data(dataset.getElementCount()); + + auto xfer_props = HighFive::DataTransferProps{}; + xfer_props.add(HighFive::UseCollectiveIO{}); + + dataset.select(offset, count).read_raw(data.data(), xfer_props); + check_collective_io(xfer_props); + + // std::cout << "[rank=" << par_env.rank() << "] " + // << "count[0] = " << count[0] << "\n"; + + for (size_t i = 0; i < count[0]; ++i) + { + for (size_t j = 0; j < count[1]; ++j) + { + printf("[rank=%d] data[%ld][%ld]=%f\n", + par_env.rank(), + i + offset[0], + j, + data[j + count[1] * i]); + } + } + } + catch (HighFive::Exception & err) + { + // catch and print any HDF5 error + std::cerr << err.what() << std::endl; + MPI_Abort(par_env.mpi_comm(), 1); + } + + return EXIT_SUCCESS; +} diff --git a/test/HighFive/parallel_hdf5_collective_io_write.cpp b/test/HighFive/parallel_hdf5_collective_io_write.cpp new file mode 100644 index 0000000..f6f7255 --- /dev/null +++ b/test/HighFive/parallel_hdf5_collective_io_write.cpp @@ -0,0 +1,116 @@ +// Copyright (c), 2017, Adrien Devresse +// Copyright (c), 2022, Blue Brain Project +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include + +#include + +#include + +#include + +const std::string file_name("parallel_collective_example.h5"); +const std::string dataset_name("dset"); + +// Currently, HighFive doesn't wrap retrieving information from property lists. +// Therefore, one needs to use HDF5 directly. For example, to see if collective +// MPI-IO operations were used, one may. Conveniently, this also provides identifiers +// of the cause for not using collective MPI calls. +void +check_collective_io(const HighFive::DataTransferProps & xfer_props) +{ + auto mnccp = HighFive::MpioNoCollectiveCause(xfer_props); + if (mnccp.getLocalCause() || mnccp.getGlobalCause()) + { + std::cout << "The operation was successful, but couldn't use collective MPI-IO. local cause: " + << mnccp.getLocalCause() << " global cause:" << mnccp.getGlobalCause() << std::endl; + } +} + +// This is an example of how to write HDF5 files when all +// operations are collective, i.e. all MPI ranks participate in +// all HDF5 related function calls. +// +// If this assumption is met then one can ask HDF5 to use +// collective MPI-IO operations. This enables MPI-IO to optimize +// reads and writes. +// +// In this example we will create groups, and let every MPI rank +// write part of a 2D array; and then have all MPI ranks read back +// a different part of the array. +int +main(int argc, char ** argv) +{ + // initialize mpi, kokkos and p4est, use default MPI communicator : MPI_COMM_WORLD + kalypsso::ParallelEnv par_env(argc, argv); + + try + { + // MPI-IO requires informing HDF5 that we want something other than + // the default behaviour. This is done through property lists. We + // need a file access property list. + HighFive::FileAccessProps fapl; + // We tell HDF5 to use MPI-IO + fapl.add(HighFive::MPIOFileAccess{ par_env.mpi_comm(), MPI_INFO_NULL }); + // We also specify that we want all meta-data related operations + // to use MPI collective operations. This implies that all MPI ranks + // in the communicator must participate in any HDF5 operation that + // reads or writes metadata. Essentially, this is safe if all MPI ranks + // participate in all HDF5 operations. + fapl.add(HighFive::MPIOCollectiveMetadata{}); + + // Now we can create the file as usual. + HighFive::File file(file_name, HighFive::File::Truncate, fapl); + + // We can create a group as usual, but all MPI ranks must participate. + auto group = file.createGroup("grp"); + + // We define the dataset have one row per MPI rank and two columns. + std::vector dims(2); + dims[0] = std::size_t(par_env.size()); + dims[1] = 2ul; + + // We follow the path for + HighFive::DataSet dataset = + group.createDataSet(dataset_name, HighFive::DataSpace(dims)); + + // Each node want to write its own rank two time in + // its associated row + auto data = std::array{ par_env.rank() * 1.0, par_env.rank() * 2.0 }; + + auto xfer_props = HighFive::DataTransferProps{}; + xfer_props.add(HighFive::UseCollectiveIO{}); + + // Each MPI rank writes a non-overlapping part of the array. + std::vector offset{ std::size_t(par_env.rank()), 0ul }; + std::vector count{ 1ul, 2ul }; + + dataset.select(offset, count).write(data, xfer_props); + check_collective_io(xfer_props); + + // Let's ensure that everything has been written do disk. + file.flush(); + + // We'd like to read back some data. For simplicity, we'll read the + // row from the MPI above us (wrapping) + offset[0] = (offset[0] + 1ul) % dims[0]; + + // MPI ranks don't have to read non-overlapping parts, but in this + // example they happen to. Again all rank participate in this call. + dataset.select(offset, count).read(data, xfer_props); + check_collective_io(xfer_props); + } + catch (HighFive::Exception & err) + { + // catch and print any HDF5 error + std::cerr << err.what() << std::endl; + MPI_Abort(par_env.mpi_comm(), 1); + } + + return EXIT_SUCCESS; +} diff --git a/test/MeshMap/CMakeLists.txt b/test/MeshMap/CMakeLists.txt new file mode 100644 index 0000000..cb5da9a --- /dev/null +++ b/test/MeshMap/CMakeLists.txt @@ -0,0 +1,31 @@ +# ========================================== +add_executable(test_MeshMap "") +target_sources(test_MeshMap PRIVATE test_MeshMap.cpp) +target_link_libraries(test_MeshMap PUBLIC kalypsso::core kalypsso::p4est_wrap kalypsso::config + kalypsso::mpiUtils) +target_link_libraries(test_MeshMap PRIVATE kalypsso_warnings) + +add_test(NAME MeshMap COMMAND test_MeshMap) +set_property(TEST MeshMap PROPERTY LABELS MeshMap) + +if(Kokkos_ENABLE_OPENMP) + set_property(TEST MeshMap PROPERTY ENVIRONMENT OMP_NUM_THREADS=${KALYPSSO_TEST_OMP_NUM_THREADS} + OMP_PROC_BIND=spread OMP_PLACES=threads) +endif() + +# ========================================== +add_executable(test_MeshMap_fillOutside "") +target_sources(test_MeshMap_fillOutside PRIVATE test_MeshMap_fillOutside.cpp) +target_include_directories( + test_MeshMap_fillOutside PUBLIC $ + $) +target_link_libraries( + test_MeshMap_fillOutside PUBLIC kalypsso::core kalypsso::p4est_wrap kalypsso::config + kalypsso::mpiUtils kalypsso::AMRtest) +target_link_libraries(test_MeshMap_fillOutside PRIVATE kalypsso_warnings) + +# add_test(NAME MeshMap COMMAND test_MeshMap_fillOutside) set_property(TEST MeshMap PROPERTY LABELS +# MeshMap_fillOutside) + +configure_file(test_MeshMap_brick_2d.ini test_MeshMap_brick_2d.ini COPYONLY) +configure_file(test_MeshMap_brick_3d.ini test_MeshMap_brick_3d.ini COPYONLY) diff --git a/test/MeshMap/test_MeshMap.cpp b/test/MeshMap/test_MeshMap.cpp new file mode 100644 index 0000000..5e80f98 --- /dev/null +++ b/test/MeshMap/test_MeshMap.cpp @@ -0,0 +1,660 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file test_MeshMap.cpp + * + * purpose: just illustrate usage of class MeshMap, especially method create_orchard_keys_view or + * fill_map + */ + +#include // for KALYPSSO_CORE_USE_HDF5, ... +#include + +#include +#include +#include +#include + +#ifdef KALYPSSO_CORE_USE_HDF5 +# include +#endif + +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace kalypsso +{ + +static int refine_level = 0; + +// ============================================================================= +// ============================================================================= +template +static int +refine_normal_fn(typename p4est::Wrapper::forest_t * forest, + typename p4est::topidx_t which_tree, + typename p4est::Wrapper::quadrant_t * quadrant) +{ + using Wrapper = typename p4est::Wrapper; + + using p4est_userdata_t = typename AMRmesh::p4est_userdata_t; + + p4est_userdata_t * p4est_userdata = static_cast(forest->user_pointer); + [[maybe_unused]] const auto level_min = p4est_userdata->level_min; + const auto level_max = p4est_userdata->level_max; + + // when reaching max level, do not refine anymore + if (quadrant->level == level_max) + return 0; + + if (which_tree == 0 and static_cast(quadrant->level) < 4) + { + double x = (static_cast(p4est::get_x(quadrant))) / Wrapper::ROOT_LEN; + double y = (static_cast(p4est::get_y(quadrant))) / Wrapper::ROOT_LEN; + if (x > 0.25 and x < 0.35 and y > 0.85 and y < 0.95) + return 1; + } + + if (which_tree == 1 and static_cast(quadrant->level) < 4) + { + double x = (static_cast(p4est::get_x(quadrant))) / Wrapper::ROOT_LEN; + double y = (static_cast(p4est::get_y(quadrant))) / Wrapper::ROOT_LEN; + if (x > 0.45 and x < 0.55 and y > 0.45 and y < 0.55) + return 1; + } + + if (static_cast(quadrant->level) >= (kalypsso::refine_level - (which_tree % 3))) + { + return 0; + } + if (quadrant->level == 1 && Wrapper::quadrant_child_id(quadrant) == 3) + { + return 1; + } + if (quadrant->x == P4EST_LAST_OFFSET(2) && quadrant->y == P4EST_LAST_OFFSET(2)) + { + return 1; + } + + if constexpr (dim == 2) + { + if (p4est::get_x(quadrant) >= static_cast(Wrapper::QUADRANT_LEN(2))) + { + return 0; + } + } + else + { + if (p4est::get_z(quadrant) >= static_cast(Wrapper::QUADRANT_LEN(2))) + { + return 0; + } + } + + return 1; + +} // refine_normal_fn + +// ============================================================================= +// ============================================================================= +template +void +quadrant_center_vertex(typename kalypsso::p4est::Wrapper::connectivity_t * connectivity, + typename kalypsso::p4est::Wrapper::geometry_t * geom, + typename kalypsso::p4est::topidx_t tree, + typename kalypsso::p4est::Wrapper::quadrant_t * quad, + double xyz[3]) +{ + + using namespace kalypsso::p4est; + + uint32_t root_len = Wrapper::ROOT_LEN; + uint32_t quad_len = Wrapper::QUADRANT_LEN(quad->level); + + qcoord_t half_length = static_cast(quad_len) / 2; + + double h2 = 0.5 * quad_len / root_len; + const double intsize = 1.0 / root_len; + + if (geom != nullptr) + { + + double xyz_logic[3] = { 0., 0., 0. }; + + /* + * get coordinates at cell center + */ + xyz_logic[0] = intsize * get_x(quad) + h2; + xyz_logic[1] = intsize * get_y(quad) + h2; + xyz_logic[2] = dim == 3 ? intsize * get_z(quad) + h2 : 0.0; + + // from logical coordinates to physical coordinates + geom->X(geom, tree, xyz_logic, xyz); + } + else + { // connectivity space (no deformation geometry) + + qcoord_t xyz_logic[3] = { get_x(quad) + half_length, + get_y(quad) + half_length, + get_z(quad) + half_length }; + + Wrapper::qcoord_to_vertex(connectivity, tree, xyz_logic, xyz); + + } // end cartesian geometry + +} // quadrant_center_vertex + +// ============================================================================= +// ============================================================================= +template +auto +setup_initial_data_leaf(typename MeshMap::orchard_key_view_t orchard_keys_device, + AMRMeshInfo amr_mesh_info, + ConfigMap const & config_map) -> DataArrayLeaf +{ + const auto brick_sizes = get_brick_sizes(config_map); + + using DataArrayLeaf_t = DataArrayLeaf; + + // + // create some test user data (on leaf, not cells for simplicity), and upload to device + // + DataArrayLeaf_t userdataLeaf(Kokkos::view_alloc(Kokkos::WithoutInitializing, "dummy_data"), + static_cast(amr_mesh_info.local_num_quadrants_total()), + 1); + + { + using exec_space = typename device_t::execution_space; + + // get geometrical scaling factor + const auto scaling_factor = get_scaling_factor(config_map); + + // get domain lower left corner + const auto xyz_min = get_xyz_min(config_map); + + Kokkos::parallel_for( + "Fill dummy data", + Kokkos::RangePolicy(0, amr_mesh_info.local_num_quadrants_total()), + KOKKOS_LAMBDA(const int32_t iOct) { + constexpr bool use_center = false; + + const auto key = orchard_keys_device(iOct); + + auto xyz_vertex = orchard_key_to_vertex_coord(key, use_center); + if (iOct >= amr_mesh_info.local_num_quadrants() + amr_mesh_info.local_num_ghosts() and + iOct < amr_mesh_info.local_num_quadrants() + amr_mesh_info.local_num_ghosts() + + amr_mesh_info.local_num_quadrants_outside()) + { + xyz_vertex = outside_key_to_vertex_coord(key, false, brick_sizes); + } + const auto xyz = vertex_coord_to_real_space(xyz_vertex, scaling_factor, xyz_min); + + // the only reason of the following dummy code to be here, is that cuda nvcc compile + // doesn't support capturing variables inside the inner lambda inside a constexpr if + // + // strangely, nvc++ is ok and don't need that + // TODO: remove theses lines when nvcc will have support for this. +#ifdef __NVCC__ + [[maybe_unused]] int dummy = 0; + if (userdataLeaf.extent(0) == 0) + dummy++; +#endif + if constexpr (dim == 2) + userdataLeaf(iOct, 0) = xyz[IX] + xyz[IY]; + else if constexpr (dim == 3) + userdataLeaf(iOct, 0) = xyz[IX] + xyz[IY] + xyz[IZ]; + }); + } + + return userdataLeaf; + +} // setup_initial_data_leaf + +// ============================================================================= +// ============================================================================= +template +auto +setup_initial_data_block(typename MeshMap::orchard_key_view_t orchard_keys_device, + AMRMeshInfo amr_mesh_info, + ConfigMap const & config_map) -> DataArrayBlock +{ + + using DataArrayBlock_t = DataArrayBlock; + using exec_space = typename device_t::execution_space; + + const auto brick_sizes = get_brick_sizes(config_map); + + // + // create some data + // + + // get block sizes + const auto bx = config_map.getInteger("amr", "bx", 1); + const auto by = config_map.getInteger("amr", "by", 1); + const auto bz = config_map.getInteger("amr", "bz", 1); + + const auto block_sizes = [=]() { + if constexpr (dim == 2) + return kalypsso::block_size_t<2>{ bx, by }; + if constexpr (dim == 3) + return kalypsso::block_size_t<3>{ bx, by, bz }; + }(); + + // + // create some test user data (on cells), and upload to device + // + auto userdataBlock = + DataArrayBlock_t("test_data_block", block_sizes, 1, amr_mesh_info.local_num_quadrants_total()); + + { + // number of quadrants in current MPI process + const int32_t nbOcts = amr_mesh_info.local_num_quadrants_total(); + + // Kokkos team policy type alias + using team_policy_t = Kokkos::TeamPolicy>; + + team_policy_t policy(nbOcts, + Kokkos::AUTO() /* number of threads per team is chosen by kokkos */); + using thread_t = typename team_policy_t::member_type; + + // get geometrical scaling factor + const auto scaling_factor = get_scaling_factor(config_map); + + // get domain lower left corner + const auto xyz_min = get_xyz_min(config_map); + + Kokkos::parallel_for( + "Fill_test_data_block", policy, KOKKOS_LAMBDA(const thread_t & member) { + // block sizes (bx,by,bz) are captured by the lambda + + // get lower left corner real space coordinates + constexpr bool use_center = false; + + // number of cells per octant + const int32_t nbCells = dim == 2 ? bx * by : bx * by * bz; + + // the first octant to process is indexed by the team id + int32_t iOct = member.league_rank(); + + // get orchard key of current block/octant + const auto key = orchard_keys_device(iOct); + + // get octant level + const auto level = orchard_key_t::level(key); + + // compute cell length in real space + // const double dx_cell = compute_cell_length(level, bx); + + // get real space coordinates of lower left corner of the block + auto xyz_corner_vertex = orchard_key_to_vertex_coord(key, use_center); + + if (iOct >= amr_mesh_info.local_num_quadrants() + amr_mesh_info.local_num_ghosts() and + iOct < amr_mesh_info.local_num_quadrants() + amr_mesh_info.local_num_ghosts() + + amr_mesh_info.local_num_quadrants_outside()) + { + xyz_corner_vertex = outside_key_to_vertex_coord(key, false, brick_sizes); + } + + // the only reason of the following dummy code to be here, is that cuda nvcc compile + // doesn't support capturing variables inside the inner lambda inside a constexpr if + // + // strangely, nvc++ is ok and don't need that + // TODO: remove theses lines when nvcc will have support for this. +#ifdef __NVCC__ + [[maybe_unused]] int dummy = 0; + if (userdataBlock.num_cells() == 0) + dummy++; +#endif + + // initialize cell id + Kokkos::parallel_for(Kokkos::TeamVectorRange(member, nbCells), [=](const int32_t icell) { + // compute ix,iy,iz of local cell inside + // block from index + auto iCoord = icell_to_icoord(icell, bx); + auto xyz_cell_vertex = + compute_cell_coordinates(level, xyz_corner_vertex, iCoord, block_sizes); + + const auto xyz_cell = + vertex_coord_to_real_space(xyz_cell_vertex, scaling_factor, xyz_min); + + if constexpr (dim == 2) + userdataBlock(icell, 0, iOct) = xyz_cell[IX] + xyz_cell[IY]; + if constexpr (dim == 3) + userdataBlock(icell, 0, iOct) = xyz_cell[IX] + xyz_cell[IY] + xyz_cell[IZ]; + }); // end TeamVectorRange + }); + } + + return userdataBlock; + +} // setup_initial_data_block + +// ============================================================================= +// ============================================================================= +template +static void +userdata_init_fn(typename kalypsso::p4est::Wrapper::forest_t * p4est, + typename kalypsso::p4est::topidx_t which_tree, + typename kalypsso::p4est::Wrapper::quadrant_t * quadrant) +{ + + using namespace kalypsso::p4est; + + using connectivity_t = typename Wrapper::connectivity_t; + using geometry_t = typename Wrapper::geometry_t; + + // using udata_t = typename AMRmesh::user_data_t; + // udata_t * udata = (udata_t *)quadrant->p.user_data; + + // compute physical space coordinates, and put "x" into user_data + + connectivity_t * conn = static_cast(p4est->connectivity); + + using p4est_userdata_t = typename AMRmesh::p4est_userdata_t; + p4est_userdata_t * p4est_userdata = static_cast(p4est->user_pointer); + + geometry_t * geom = p4est_userdata->geom; + + double XYZ[3]; + + quadrant_center_vertex(conn, geom, which_tree, quadrant, XYZ); + + // initialize user data for current quandrant + // udata->x = XYZ[0]; + +} // userdata_init_fn + +// ============================================================================= +// ============================================================================= +template +void +run_test(const ParallelEnv & par_env, int argc, char * argv[]) +{ + using namespace p4est; + // using DataArrayLeafHost_t = DataArrayLeafHost; + // using DataArrayBlockHost_t = DataArrayBlockHost; + using DataArrayBlock_t = DataArrayBlock; + + // check if user passed a custom ini filename + std::string config_filename = kalypsso::cmdline_get_string(argv, argv + argc, "--ini"); + + // provide a default config filename (that exists) + if (config_filename.size() == 0) + config_filename = dim == 2 ? "./test_MeshMap_brick_2d.ini" : "./test_MeshMap_brick_3d.ini"; + + ConfigMap config_map = broadcast_parameters(config_filename); + + if (par_env.rank() == 0) + printf("================================================\n"); + + // create a p4est object (using brick connectivity) + + typename Wrapper::refine_cb_t refine_fn = refine_normal_fn; + typename Wrapper::coarsen_cb_t coarsen_fn = nullptr; + // using balance_type_t = typename Wrapper::balance_type_t; + + AMRmesh amr_mesh(par_env, config_map); + + auto forest = amr_mesh.forest(); + // auto geom = amr_mesh.geometry(); + + auto conn_name = amr_mesh.connectivity_name(); + auto geom_name = amr_mesh.geometry_name(); + + // refinement and coarsening + Wrapper::refine(forest, 1, refine_fn, userdata_init_fn); + if (coarsen_fn != nullptr) + { + Wrapper::coarsen(forest, 1, coarsen_fn, userdata_init_fn); + } + + if (par_env.rank() == 0) + { + printf("Running a %ld test with connectivity %s and geometry %s\n", + dim, + conn_name.c_str(), + geom_name.c_str()); + } + + printf("Forest has %ld global octants, %d local octants on MPI proc %d.\n", + forest->global_num_quadrants, + forest->local_num_quadrants, + par_env.rank()); + + // balance + Wrapper::balance(forest, Wrapper::CONNECT_FULL, userdata_init_fn); + + // partition + Wrapper::partition(forest, 0, nullptr); + + // initialize ghost + amr_mesh.reset_ghost(); + + auto ghost = amr_mesh.ghost(); + + auto mesh_map = std::make_shared>(config_map, par_env); + + mesh_map->compute_outside_quad_info(forest, ghost); + mesh_map->update_hashmap_serial(forest, ghost); + + mesh_map->update_amr_mesh_info(forest, ghost); + auto amr_mesh_info = mesh_map->get_amr_mesh_info(); + if (par_env.rank() == 0) + { + amr_mesh_info.print(); + } + + // retrieve amr keys + mesh_map->update_orchard_keys(forest, ghost); + auto orchard_keys_host = mesh_map->orchard_keys_host(); + auto orchard_keys_device = mesh_map->orchard_keys_clone(); + + auto conformal_full_status = mesh_map->update_conformal_full_status(); + auto conformal_full_status_h = + Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, conformal_full_status); + + + if (par_env.rank() == 0) + { + for (int qid = 0; qid < forest->local_num_quadrants; ++qid) + { + const auto status = conformal_full_status_h(qid); + printf("[rank = %d] orchard_key[%d] = %zu conformal_full_status=%ld (face %d %d %d %d edge " + "%d %d %d %d)\n", + par_env.rank(), + qid, + orchard_keys_host(qid), + static_cast(status), + conformal_full_status_t::face_xmin(status), + conformal_full_status_t::face_xmax(status), + conformal_full_status_t::face_ymin(status), + conformal_full_status_t::face_ymax(status), + conformal_full_status_t::edge_xmin_ymin(status), + conformal_full_status_t::edge_xmax_ymin(status), + conformal_full_status_t::edge_xmin_ymax(status), + conformal_full_status_t::edge_xmax_ymax(status)); + } + } + + // testing scan_utils, example parameters + // level_min = 1 + // level_max = 4 + // mpirun -np 1 ./test_MeshMap --ini test_MeshMap_brick_2d.ini --refine_level 3 + { + uint8_t level_min = static_cast(config_map.getInteger("amr", "level_min", 4)); + uint8_t level_max = static_cast(config_map.getInteger("amr", "level_max", 4)); + auto level_indexes = + compute_index_by_level(orchard_keys_device, + level_min, + level_max, + mesh_map->get_amr_mesh_info().local_num_quadrants(), + par_env); + auto level_indexes_host = + Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, level_indexes); + + for (size_t ikey = 0; ikey < orchard_keys_device.size(); ++ikey) + { + printf("i=%lu key=%lu level=%d index=%lu\n", + ikey, + orchard_keys_host(ikey), + orchard_key_t::level(orchard_keys_host(ikey)), + level_indexes_host(ikey)); + } + } + +#ifdef KALYPSSO_CORE_USE_HDF5 + // testing IO + { + bool use_outside_quads = false; + if (kalypsso::cmdline_arg_exists(argv, argv + argc, "--outside")) + use_outside_quads = true; + + // first create dummy data + auto userdataLeaf_d = + setup_initial_data_leaf(orchard_keys_device, amr_mesh_info, config_map); + auto userdataLeaf_h = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, userdataLeaf_d); + + auto userdataBlock_d = + setup_initial_data_block(orchard_keys_device, amr_mesh_info, config_map); + auto userdataBlock_h = DataArrayBlock_t::create_host_mirror_view_and_copy(userdataBlock_d); + + // write hdf5/xmdf + HDF5_Xdmf_Writer writer(par_env, config_map, mesh_map); + + std::string outputDir = config_map.getString("output", "outputDir", "./"); + + std::string filename_cell = "test_MeshMap_cell"; + std::string filename_leaf = "test_MeshMap_leaf"; + + if (use_outside_quads) + { + filename_cell = filename_cell + "_outside"; + filename_leaf = filename_leaf + "_outside"; + } + + // block mode + writer.set_block_mode(); + writer.use_outside_quads(use_outside_quads); + writer.set_write_mesh_info(true); + writer.open(filename_cell, outputDir); + writer.write_header(0.0); + writer.write_amr_metadata(orchard_keys_host); + { + // write dummy test data + if (use_outside_quads) + { + writer.write_quadrant_attribute(userdataBlock_h, + 0, + "dummy", + amr_mesh_info.local_num_quadrants() + + amr_mesh_info.local_num_ghosts(), + amr_mesh_info.local_num_quadrants_outside()); + } + else + { + writer.write_quadrant_attribute( + userdataBlock_h, 0, "dummy", 0, amr_mesh_info.local_num_quadrants()); + } + } + writer.write_footer(); + writer.close(); + + // leaf mode + writer.set_leaf_mode(); + writer.use_outside_quads(use_outside_quads); + writer.set_write_mesh_info(true); + writer.open(filename_leaf, outputDir); + writer.write_header(0.0); + writer.write_amr_metadata(orchard_keys_host); + { + // write dummy test data + if (use_outside_quads) + { + // auto userdataLeaf_outside_h = Kokkos::subview( + // userdataLeaf_h, + // std::make_pair(amr_mesh_info.local_num_quadrants + amr_mesh_info.local_num_ghosts, + // amr_mesh_info.local_num_quadrants + amr_mesh_info.local_num_ghosts + + // amr_mesh_info.local_num_quadrants_outside), + // Kokkos::ALL); + + // extract slice as a contiguous memory buffer + using DataArrayLeaf_t = DataArrayLeaf; + using DataArrayLeafHost_t = typename DataArrayLeaf_t::host_mirror_type; + DataArrayLeafHost_t userdataLeaf_outside_h( + Kokkos::view_alloc(Kokkos::WithoutInitializing, "dummy_data_outside"), + static_cast(amr_mesh_info.local_num_quadrants_outside()), + 1); + + const auto offset = amr_mesh_info.local_num_quadrants() + amr_mesh_info.local_num_ghosts(); + Kokkos::parallel_for( + "extract outside data", + Kokkos::RangePolicy(0, amr_mesh_info.local_num_quadrants_outside()), + KOKKOS_LAMBDA(int32_t iOct) { + userdataLeaf_outside_h(iOct, 0) = userdataLeaf_h(iOct + offset, 0); + }); + + writer.write_quadrant_attribute(userdataLeaf_h, 0, "dummy"); + } + else + { + writer.write_quadrant_attribute(userdataLeaf_h, 0, "dummy"); + } + } + writer.write_footer(); + writer.close(); + } +#else + std::cout << "HDF5 output unavailable.\n"; +#endif + +} // run_test + +} // namespace kalypsso + +// ============================================================================= +// ============================================================================= +// ============================================================================= +int +main(int argc, char * argv[]) +{ + + { + kalypsso::ParallelEnv par_env(argc, argv); + + if (kalypsso::cmdline_arg_exists(argv, argv + argc, "--help")) + { + if (par_env.rank() == 0) + { + // clang-format off + std::cout << "Example cmdline: \"mpirun -np 4 ./test_MeshMap --ini test_MeshMap_brick_2d.ini --refine_level 6\"\n"; + // clang-format on + } + return 0; + } + + // assign static global variables based on configuration + if (kalypsso::cmdline_arg_exists(argv, argv + argc, "--refine_level")) + kalypsso::refine_level = kalypsso::cmdline_get_integer(argv, argv + argc, "--refine_level"); + + if (par_env.rank() == 0) + printf("Using refine_level=%d\n", kalypsso::refine_level); + + // parse command line : 2d or 3d ? + bool run_3d = kalypsso::cmdline_arg_exists(argv, argv + argc, "--3d"); + + run_3d ? kalypsso::run_test<3, kalypsso::DefaultDevice>(par_env, argc, argv) + : kalypsso::run_test<2, kalypsso::DefaultDevice>(par_env, argc, argv); + } + + return 0; +} // main diff --git a/test/MeshMap/test_MeshMap_brick_2d.ini b/test/MeshMap/test_MeshMap_brick_2d.ini new file mode 100644 index 0000000..5df43fe --- /dev/null +++ b/test/MeshMap/test_MeshMap_brick_2d.ini @@ -0,0 +1,72 @@ +[run] +dimension=2 +tEnd=0.5 +nStepmax=10 + +# noutput equals -1 means we dump data at every time steps +nOutput=-1 + +[amr] +level_min=4 +level_max=6 + +use_block_data=yes +bx=4 +by=4 + +# init step may contain calls to globalRefine +# if the following option is true, we interleave +# calls to load balancing in between globalRefine +# if the following option is false, a single load +# balancing operation is done at the end of +# globalRefine steps +enable_load_balance_during_init=false + +epsilon_refine=0.01 +epsilon_coarsen=0.03 + +connectivity = brick + +[p4est_connectivity] +nbrick_x=3 +nbrick_y=2 + +periodic_x=0 +periodic_y=0 + +[mesh] +xmin=0.0 +ymin=0.0 +scaling_factor=1.0 + +boundary_type_xmin=WALL +boundary_type_xmax=WALL + +boundary_type_ymin=WALL +boundary_type_ymax=WALL + +[hydro] +gamma0=1.666 +cfl=0.8 +niter_riemann=10 +slope_type=2 +problem=blast +riemann=HLLC + +[blast] +density_in=1.0 +density_out=1.2 +radius=0.25 + +[output] +outputDir=./ +outputPrefix=test_hdf5 +outputVtkAscii=false +write_variables=rho,rho_vx,rho_vy +write_mesh_info=yes +write_iOct=yes +vtk_enabled=no +hdf5_enabled=yes + +[other] +implementationVersion=0 diff --git a/test/MeshMap/test_MeshMap_brick_3d.ini b/test/MeshMap/test_MeshMap_brick_3d.ini new file mode 100644 index 0000000..956761a --- /dev/null +++ b/test/MeshMap/test_MeshMap_brick_3d.ini @@ -0,0 +1,79 @@ +[run] +dimension=3 +tEnd=0.5 +nStepmax=10 + +# noutput equals -1 means we dump data at every time steps +nOutput=-1 + +[amr] +level_min=4 +level_max=6 + +use_block_data=yes +bx=4 +by=4 +bz=4 + +# init step may contain calls to globalRefine +# if the following option is true, we interleave +# calls to load balancing in between globalRefine +# if the following option is false, a single load +# balancing operation is done at the end of +# globalRefine steps +enable_load_balance_during_init=false + +epsilon_refine=0.01 +epsilon_coarsen=0.03 + +connectivity = brick + +[p4est_connectivity] +nbrick_x=3 +nbrick_y=2 +nbrick_y=4 + +periodic_x=0 +periodic_y=0 +periodic_z=0 + +[mesh] +xmin=0.0 +ymin=0.0 +zmin=0.0 +scaling_factor=1.0 + +boundary_type_xmin=WALL +boundary_type_xmax=WALL + +boundary_type_ymin=WALL +boundary_type_ymax=WALL + +boundary_type_zmin=WALL +boundary_type_zmax=WALL + +[hydro] +gamma0=1.666 +cfl=0.8 +niter_riemann=10 +slope_type=2 +problem=blast +riemann=HLLC + +[blast] +density_in=1.0 +density_out=1.2 +radius=0.25 + +[output] +outputDir=./ +outputPrefix=test_hdf5 +outputVtkAscii=false +write_variables=rho,rho_vx +write_mesh_info=yes +write_iOct=yes +vtk_enabled=no +hdf5_enabled=yes + +[other] +implementationVersion=0 diff --git a/test/MeshMap/test_MeshMap_fillOutside.cpp b/test/MeshMap/test_MeshMap_fillOutside.cpp new file mode 100644 index 0000000..17a8609 --- /dev/null +++ b/test/MeshMap/test_MeshMap_fillOutside.cpp @@ -0,0 +1,219 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file test_MeshMap_fillOutside.cpp + * + * purpose: just illustrate usage of functor class FillOutsideCellFunctor + */ + +#include // for KALYPSSO_CORE_USE_HDF5, ... +#include + +#include +#include +#include +#include +#include + +#ifdef KALYPSSO_CORE_USE_HDF5 +# include +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace kalypsso +{ + +// ============================================================================= +// ============================================================================= +template +void +run_test(const ParallelEnv & par_env, int argc, char * argv[]) +{ + // check if user passed a custom ini filename + std::string config_filename = kalypsso::cmdline_get_string(argv, argv + argc, "--ini"); + + // provide a default config filename (that exists) + if (config_filename.size() == 0) + config_filename = dim == 2 ? "./test_MeshMap_brick_2d.ini" : "./test_MeshMap_brick_3d.ini"; + + ConfigMap config_map = broadcast_parameters(config_filename); + + if (par_env.rank() == 0) + printf("================================================\n"); + + + InitialAMRSetup initial_amr_setup(par_env, config_map, InitFunc1{}); + + auto level_min = config_map.getInteger("amr", "level_min", 2); + auto level_max = config_map.getInteger("amr", "level_max", 2); + auto no_refine = (level_min == level_max); + + // setup initial p4est mesh with some test pseudo random refined/coarsen cells pattern + initial_amr_setup.setup_initial_mesh(no_refine); + + auto & amr_mesh = initial_amr_setup.mesh(); + auto forest = amr_mesh.forest(); + auto mesh_map = initial_amr_setup.mesh_map(); + const auto block_sizes = initial_amr_setup.block_sizes(); + const auto brick_sizes = initial_amr_setup.brick_sizes(); + // const auto is_brick_periodic = initial_amr_setup.is_brick_periodic(); + const auto amr_mesh_info = initial_amr_setup.amr_mesh_info(); + if (par_env.rank() == 0) + { + amr_mesh_info.print(); + } + + + // initialize ghost + amr_mesh.reset_ghost(); + auto ghost = amr_mesh.ghost(); + + mesh_map->compute_outside_quad_info(forest, ghost); + mesh_map->update_orchard_keys(forest, ghost); + mesh_map->update_hashmap(forest, ghost); + + auto orchard_keys_host = mesh_map->orchard_keys_host_clone(); + auto orchard_keys_device = mesh_map->orchard_keys_clone(); + + // extract userdata + auto userdata_leaf_d = initial_amr_setup.setup_initial_data_leaf(orchard_keys_device); + auto userdata_block_d = initial_amr_setup.setup_initial_data_block_new(orchard_keys_device); + + auto amr_hashmap_device = mesh_map->hashmap(); + + using bc_array_t = BorderConditionsConfig::bc_array_t; + + bc_array_t bc_types = [=]() { + if constexpr (dim == 2) + { + // XMIN, XMAX, YMIN, YMAX + + // clang-format off + // return bc_array_t{ test::BC_HYDRO::ZERO_GRADIENT, + // test::BC_HYDRO::ZERO_GRADIENT, + // test::BC_HYDRO::ZERO_GRADIENT, + // test::BC_HYDRO::ZERO_GRADIENT }; + + return bc_array_t{ test::BC_HYDRO::WALL, + test::BC_HYDRO::WALL, + test::BC_HYDRO::WALL, + test::BC_HYDRO::WALL }; + // clang-format on + } + else if constexpr (dim == 3) + { + // XMIN, XMAX, YMIN, YMAX, ZMIN, ZMAX + return bc_array_t{ test::BC_HYDRO::ZERO_GRADIENT, test::BC_HYDRO::ZERO_GRADIENT, + test::BC_HYDRO::ZERO_GRADIENT, test::BC_HYDRO::ZERO_GRADIENT, + test::BC_HYDRO::ZERO_GRADIENT, test::BC_HYDRO::ZERO_GRADIENT }; + } + }(); + + // fill outside userdata + test::FillOutsideCellFunctor::apply(config_map, + amr_hashmap_device, + orchard_keys_device, + amr_mesh_info, + userdata_block_d, + block_sizes, + brick_sizes, + mesh_map->is_brick_periodic(), + bc_types); + + // save data inside / outside +#ifdef KALYPSSO_CORE_USE_HDF5 + { + // // create host mirrors + // auto userdata_leaf_h = + // Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, userdata_leaf_d); + // auto userdata_block_h = + // Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, userdata_block_d); + + std::string filename = dim == 2 ? "test_MeshMap_fillOutside_2d" : "test_MeshMap_fillOutside_3d"; + + // provide mapping between variables Id and variables names + using Hydro_t = core::models::Hydro; + const Hydro_t model(dim); + + DataWriter::save(filename, + userdata_leaf_d, + userdata_block_d, + config_map, + par_env, + mesh_map, + false, // inside + model); + + DataWriter::save(filename, + userdata_leaf_d, + userdata_block_d, + config_map, + par_env, + mesh_map, + true, // outside + model); + } +#else + std::cout << "HDF5 output unavailable.\n"; +#endif + +} // run_test + +} // namespace kalypsso + +// ============================================================================= +// ============================================================================= +// ============================================================================= +int +main(int argc, char * argv[]) +{ + + { + kalypsso::ParallelEnv par_env(argc, argv); + +#ifdef KALYPSSO_CORE_USE_SPDLOG + kalypsso::kalypsso_spdlog_config(argc, argv, par_env.rank(), par_env.size()); +#endif + + if (kalypsso::cmdline_arg_exists(argv, argv + argc, "--help")) + { + if (par_env.rank() == 0) + { + // clang-format off + std::cout << "Example cmdline: \"mpirun -np 4 ./test_MeshMap_fillOutside --ini test_MeshMap_brick_2d.ini --refine_level 6\"\n"; + // clang-format on + } + return 0; + } + + // assign static global variables based on configuration + kalypsso::InitialAMRSetupBase::refine_level = + kalypsso::cmdline_arg_exists(argv, argv + argc, "--refine_level") + ? kalypsso::cmdline_get_integer(argv, argv + argc, "--refine_level") + : 6; + + if (par_env.rank() == 0) + printf("Using refine_level=%d\n", kalypsso::InitialAMRSetupBase::refine_level); + + // parse command line : 2d or 3d ? + bool run_3d = kalypsso::cmdline_arg_exists(argv, argv + argc, "--3d"); + + run_3d ? kalypsso::run_test<3, kalypsso::DefaultDevice>(par_env, argc, argv) + : kalypsso::run_test<2, kalypsso::DefaultDevice>(par_env, argc, argv); + } + + return 0; +} // main diff --git a/test/container/CMakeLists.txt b/test/container/CMakeLists.txt new file mode 100644 index 0000000..337d08d --- /dev/null +++ b/test/container/CMakeLists.txt @@ -0,0 +1,32 @@ +# add_executable(test_FaceDataArrayBlock test_FaceDataArrayBlock.cpp) + +# target_link_libraries(test_FaceDataArrayBlock PUBLIC kalypsso::core kalypsso::config) + +# add_test(NAME FaceDataArrayBlock COMMAND test_FaceDataArrayBlock) set_property(TEST +# FaceDataArrayBlock PROPERTY LABELS AMRkey) + +# if(Kokkos_ENABLE_OPENMP) set_property(TEST FaceDataArrayBlock PROPERTY ENVIRONMENT +# OMP_NUM_THREADS=${KALYPSSO_TEST_OMP_NUM_THREADS} OMP_PROC_BIND=spread OMP_PLACES=threads) endif() + +# +# this is not a test, just a utility to play with DataArrayBlock +# +add_executable(create_DataArrayBlock create_DataArrayBlock.cpp) + +target_link_libraries(create_DataArrayBlock PUBLIC kalypsso::core kalypsso::mpiUtils kalypsso::log) + +# +# this is not a test, just a utility to play with FaceDataArrayBlock +# +add_executable(create_FaceDataArrayBlock create_FaceDataArrayBlock.cpp) + +target_link_libraries(create_FaceDataArrayBlock PUBLIC kalypsso::core kalypsso::mpiUtils + kalypsso::log) + +# +# this is not a test, just a utility to play with EdgeDataArrayBlock +# +add_executable(create_EdgeDataArrayBlock create_EdgeDataArrayBlock.cpp) + +target_link_libraries(create_EdgeDataArrayBlock PUBLIC kalypsso::core kalypsso::mpiUtils + kalypsso::log) diff --git a/test/container/create_DataArrayBlock.cpp b/test/container/create_DataArrayBlock.cpp new file mode 100644 index 0000000..eaa577d --- /dev/null +++ b/test/container/create_DataArrayBlock.cpp @@ -0,0 +1,177 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file create_DataArrayBlock.cpp + * + */ +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace kalypsso +{ + +template +void +test(const ParallelEnv & par_env) +{ + using DataArrayBlock_t = DataArrayBlock; + using ExecutionSpace = typename device_t::execution_space; + + const auto bsize = get_block_size(4); + const auto num_vars = 2; + const auto num_octants = 10; + const auto num_cells_per_leaf = Kokkos::dim_prod(bsize); + const auto num_cells = num_octants * num_cells_per_leaf; + + auto data = DataArrayBlock_t("somedata", bsize, num_vars, num_octants); + + Kokkos::parallel_for( + "init", + Kokkos::RangePolicy(0, num_cells), + KOKKOS_LAMBDA(const uint32_t & global_index) { + // convert global index into + // - octant id + // - cell_index inside block (from 0 to nbCellsPerLeaf-1) + const auto iOct = global_index / num_cells_per_leaf; + const auto cell_index = global_index - iOct * num_cells_per_leaf; + + // compute ix,iy,iz of local cell inside + // block from index + const auto iCoord = cellindex_to_coord(cell_index, bsize); + +#ifdef __NVCC__ + [[maybe_unused]] int dummy = 0; + if (data.num_quadrants() != data.num_quadrants()) + dummy++; +#endif + + if constexpr (dim == 2) + { + auto const & i = iCoord[IX]; + auto const & j = iCoord[IY]; + + data(i, j, 0, iOct) = 1.0 * i + 1.0 * j; + data(i, j, 1, iOct) = 1.0 * i - 1.0 * j; + } + else if constexpr (dim == 3) + { + auto const & i = iCoord[IX]; + auto const & j = iCoord[IY]; + auto const & k = iCoord[IZ]; + + data(i, j, k, 0, iOct) = 1.0 * i + j + k; + data(i, j, k, 1, iOct) = 1.0 * i - j + k; + } + }); + + const auto ivar = 1; + const auto data_h = DataArrayBlock_t::create_host_mirror_view_and_copy(data); + +#ifdef KALYPSSO_CORE_USE_SPDLOG + if constexpr (dim == 2) + { + for (auto iOct = 0; iOct < num_octants; ++iOct) + { + for (uint32_t icell = 0; icell < Kokkos::dim_prod(bsize); ++icell) + { + fmt::print("data[{},{},{}]={:4}\n", icell, ivar, iOct, data_h(icell, ivar, iOct)); + } + } + + for (auto iOct = 0; iOct < num_octants; ++iOct) + { + for (uint32_t j = 0; j < bsize[IY]; ++j) + { + for (uint32_t i = 0; i < bsize[IX]; ++i) + { + fmt::print("data[{},{},{},{}]={:4} ", i, j, ivar, iOct, data_h(i, j, ivar, iOct)); + } + fmt::print("\n"); + } + } + } + else if constexpr (dim == 3) + { + for (auto iOct = 0; iOct < 2; ++iOct) + { + for (uint32_t icell = 0; icell < Kokkos::dim_prod(bsize); ++icell) + { + fmt::print("data[{},{},{}]={:4}\n", icell, ivar, iOct, data_h(icell, ivar, iOct)); + } + } + + for (auto iOct = 0; iOct < 2; ++iOct) + { + for (uint32_t k = 0; k < bsize[IZ]; ++k) + { + for (uint32_t j = 0; j < bsize[IY]; ++j) + { + for (uint32_t i = 0; i < bsize[IX]; ++i) + { + fmt::print( + "data[{},{},{},{},{}]={:4} ", i, j, k, ivar, iOct, data_h(i, j, k, ivar, iOct)); + } + fmt::print("\n"); + } + fmt::print("\n\n"); + } + } + } +#endif // KALYPSSO_CORE_USE_SPDLOG +} // test + +} // namespace kalypsso + +// ================================================================================ +// ================================================================================ +// ================================================================================ +int +main([[maybe_unused]] int argc, [[maybe_unused]] char * argv[]) +{ + + kalypsso::ParallelEnv par_env(argc, argv); + +#ifdef KALYPSSO_CORE_USE_SPDLOG + kalypsso::kalypsso_spdlog_config(argc, argv, par_env.rank(), par_env.size()); +#endif + + if (kalypsso::cmdline_arg_exists(argv, argv + argc, "--help")) + { + if (par_env.rank() == 0) + { + std::cout << "Example cmdline: \"./create_DataArrayBlock\"\n"; + } + return EXIT_SUCCESS; + } + + // parse command line : 2d or 3d ? + bool do_3d = kalypsso::cmdline_arg_exists(argv, argv + argc, "--3d"); + + using DefaultDevice = + Kokkos::Device; + + if (do_3d) + { + kalypsso::test<3, DefaultDevice>(par_env); + } + else + { + kalypsso::test<2, DefaultDevice>(par_env); + } + + return EXIT_SUCCESS; + +} // main diff --git a/test/container/create_EdgeDataArrayBlock.cpp b/test/container/create_EdgeDataArrayBlock.cpp new file mode 100644 index 0000000..ae2e016 --- /dev/null +++ b/test/container/create_EdgeDataArrayBlock.cpp @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file create_EdgeDataArrayBlock.cpp + * + */ +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace kalypsso +{ + +template +void +test(const ParallelEnv & par_env) +{ + using EdgeDataArrayBlock_t = EdgeDataArrayBlock; + // using DataArrayBlock_t = typename EdgeDataArrayBlock_t::DataArrayBlock_t; + using ExecutionSpace = typename device_t::execution_space; + + const auto bsize = get_block_size(4); + const auto num_octants = 10; + + EdgeDataArrayBlock_t edata("edata", bsize, num_octants); + + const auto num_elements_per_octant = edata.num_elements_per_octant(); + + Kokkos::parallel_for( + "init", + Kokkos::RangePolicy(0, num_elements_per_octant * num_octants), + KOKKOS_LAMBDA(const uint32_t & global_index) { + // convert global index into + // - octant id + // - cell_index inside block (from 0 to nbCellsPerLeaf-1) + const auto iOct = global_index / num_elements_per_octant; + const auto edge_index = global_index - iOct * num_elements_per_octant; + + // compute ix,iy,iz of current edge + auto const edge_indexes = edge_flat_index_unravel(edge_index, bsize, edata.offsets()); + + if constexpr (dim == 2) + { + auto const & i = edge_indexes[IX]; + auto const & j = edge_indexes[IY]; + auto const & ivar = edge_indexes[dim]; + + edata(i, j, ivar, iOct) = 1.0 * (i + j) + 2 * (ivar + 1); + } + else if constexpr (dim == 3) + { + auto const & i = edge_indexes[IX]; + auto const & j = edge_indexes[IY]; + auto const & k = edge_indexes[IZ]; + auto const & ivar = edge_indexes[dim]; + + edata(i, j, k, ivar, iOct) = i + j + k + (3 * ivar + 1); + } + }); + + // auto curl_of_edata = EdgeDataArrayBlock_t::compute_curl(edata, orchard_key); + +} // test + +} // namespace kalypsso + +// ================================================================================ +// ================================================================================ +// ================================================================================ +int +main([[maybe_unused]] int argc, [[maybe_unused]] char * argv[]) +{ + + kalypsso::ParallelEnv par_env(argc, argv); + +#ifdef KALYPSSO_CORE_USE_SPDLOG + kalypsso::kalypsso_spdlog_config(argc, argv, par_env.rank(), par_env.size()); +#endif + + using DefaultDevice = + Kokkos::Device; + + kalypsso::test<2, DefaultDevice>(par_env); + + return EXIT_SUCCESS; +} diff --git a/test/container/create_FaceDataArrayBlock.cpp b/test/container/create_FaceDataArrayBlock.cpp new file mode 100644 index 0000000..ad66bb2 --- /dev/null +++ b/test/container/create_FaceDataArrayBlock.cpp @@ -0,0 +1,199 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file create_FaceDataArrayBlock.cpp + * + */ +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace kalypsso +{ + +template +void +test(const ParallelEnv & par_env) +{ + using FaceDataArrayBlock_t = FaceDataArrayBlock; + using DataArrayBlock_t = typename FaceDataArrayBlock_t::DataArrayBlock_t; + using ExecutionSpace = typename device_t::execution_space; + + const auto bsize = get_block_size(4); + const auto num_octants = 10; + const auto num_cells_per_leaf = Kokkos::dim_prod(bsize); + const auto num_cells = num_octants * num_cells_per_leaf; + + FaceDataArrayBlock_t fdata("fdata", bsize, num_octants); + + Kokkos::parallel_for( + "init", + Kokkos::RangePolicy(0, num_cells), + KOKKOS_LAMBDA(const uint32_t & global_index) { + // convert global index into + // - octant id + // - cell_index inside block (from 0 to nbCellsPerLeaf-1) + const auto iOct = global_index / num_cells_per_leaf; + const auto cell_index = global_index - iOct * num_cells_per_leaf; + + // compute ix,iy,iz of local cell inside + // block from index + const auto iCoord = cellindex_to_coord(cell_index, bsize); + +#ifdef __NVCC__ + [[maybe_unused]] int dummy = 0; + if (fdata.num_quadrants() != fdata.num_quadrants()) + dummy++; +#endif + + if constexpr (dim == 2) + { + auto const & i = iCoord[IX]; + auto const & j = iCoord[IY]; + + fdata(i, j, IX, iOct) = 1.0 * (i + j); + fdata(i, j, IY, iOct) = 1.0 * (i - j); + fdata(i, j, IZ, iOct) = 0.0; + + // init right faces of the last cell + if (i == bsize[IX] - 1) + { + fdata(i + 1, j, IX, iOct) = 1.0 * (i + 1 + j); + } + if (j == bsize[IY] - 1) + { + fdata(i, j + 1, IY, iOct) = 1.0 * (i - j - 1); + } + } + else if constexpr (dim == 3) + { + auto const & i = iCoord[IX]; + auto const & j = iCoord[IY]; + auto const & k = iCoord[IZ]; + + fdata(i, j, k, IX, iOct) = i + j + k; + fdata(i, j, k, IY, iOct) = i - j + k; + fdata(i, j, k, IZ, iOct) = i + j - k; + + // init right faces of the last cell + if (i == bsize[IX] - 1) + { + fdata(i + 1, j, k, IX, iOct) = 1.0 * (i + 1 + j + k); + } + if (j == bsize[IY] - 1) + { + fdata(i, j + 1, k, IY, iOct) = 1.0 * (i - j - 1 + k); + } + if (k == bsize[IZ] - 1) + { + fdata(i, j, k + 1, IZ, iOct) = 1.0 * (i + j - k - 1); + } + } + }); + + auto res = DataArrayBlock_t("fdata_centered", bsize, 1, num_octants); + + const int dir = IX; + + // compute unit vector associated to direction + const auto unit_vector = [](int direction) { + Kokkos::Array v; + for (int i = 0; i < dim; ++i) + { + v[i] = i == direction ? 1 : 0; + } + return v; + }(dir); + + Kokkos::parallel_for( + "convert", + Kokkos::RangePolicy(0, num_cells), + KOKKOS_LAMBDA(const uint32_t & global_index) { + // convert global index into + // - octant id + // - cell_index inside block (from 0 to nbCellsPerLeaf-1) + const auto iOct = global_index / num_cells_per_leaf; + const auto cell_index = global_index - iOct * num_cells_per_leaf; + + // compute ix,iy,iz of local cell inside + // block from index + const auto iCoord = cellindex_to_coord(cell_index, bsize); + +#ifdef __NVCC__ + [[maybe_unused]] int dummy = 0; + if ((fdata.num_quadrants() == 0) or (res.num_cells() == 0) or (dir == 0) or + (unit_vector[0] == 0)) + dummy++; +#endif + if constexpr (dim == 2) + { + auto const & i = iCoord[IX]; + auto const & j = iCoord[IY]; + + res(cell_index, 0, iOct) = + HALF_F * + (fdata(i, j, dir, iOct) + fdata(i + unit_vector[IX], j + unit_vector[IY], dir, iOct)); + } + else + { + auto const & i = iCoord[IX]; + auto const & j = iCoord[IY]; + auto const & k = iCoord[IZ]; + + res(cell_index, 0, iOct) = + HALF_F * + (fdata(i, j, k, dir, iOct) + + fdata(i + unit_vector[IX], j + unit_vector[IY], k + unit_vector[IZ], dir, iOct)); + } + }); + + auto fdata_centered = FaceDataArrayBlock_t::to_DataArrayBlockCentered(fdata, IX); + + const auto fdata_centered_h = DataArrayBlock_t::create_host_mirror_view_and_copy(fdata_centered); + + for (auto iOct = 0; iOct < num_octants; ++iOct) + for (uint32_t icell = 0; icell < Kokkos::dim_prod(bsize); ++icell) + { + KALYPSSO_INFO_ALL("fdata_centered[{},{},{}]={}", + icell, + static_cast(IX), + 0, + fdata_centered_h(icell, 0, iOct)); + } + +} // test + +} // namespace kalypsso + + +// ================================================================================ +// ================================================================================ +// ================================================================================ +int +main([[maybe_unused]] int argc, [[maybe_unused]] char * argv[]) +{ + + kalypsso::ParallelEnv par_env(argc, argv); + +#ifdef KALYPSSO_CORE_USE_SPDLOG + kalypsso::kalypsso_spdlog_config(argc, argv, par_env.rank(), par_env.size()); +#endif + + using DefaultDevice = + Kokkos::Device; + + + kalypsso::test<2, DefaultDevice>(par_env); + + return EXIT_SUCCESS; +} diff --git a/test/core/CMakeLists.txt b/test/core/CMakeLists.txt new file mode 100644 index 0000000..401ad8e --- /dev/null +++ b/test/core/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2025 kalypsso-core authors +# +# SPDX-License-Identifier: Unlicense + +add_executable(test_binomial_coef test_binomial_coef.cpp) + +target_link_libraries(test_binomial_coef PRIVATE Kokkos::kokkos kalypsso::core) +target_link_libraries(test_binomial_coef PRIVATE kalypsso_warnings) diff --git a/test/core/test_binomial_coef.cpp b/test/core/test_binomial_coef.cpp new file mode 100644 index 0000000..25dbd4b --- /dev/null +++ b/test/core/test_binomial_coef.cpp @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include + +#include +#include + +namespace kalypsso +{ + +template +void +test_binomial_coef() +{ + // print Pascal triangle + for (int n = 1; n <= N; ++n) + { + for (int k = 0; k <= n; ++k) + printf("%d ", binomial_coef(n, k)); + printf("\n"); + } + + auto coef = BinomialCoef(); + for (int n = 1; n <= N; ++n) + { + for (int k = 0; k <= n; ++k) + printf("%d ", coef(n, k)); + printf("\n"); + } +} + +} // namespace kalypsso + +// =========================================================================== +// =========================================================================== +// =========================================================================== +int +main(int argc, char * argv[]) +{ + // using DefaultDevice = + // Kokkos::Device; + // using device = DefaultDevice; + + /* + * Initialize kokkos (host + device) + * + * If CUDA is enabled, Kokkos will try to use the default GPU, + * i.e. GPU #0 if you have multiple GPUs. + */ + Kokkos::initialize(argc, argv); + + { + std::cout << "##########################\n"; + std::cout << "KOKKOS CONFIG \n"; + std::cout << "##########################\n"; + + std::ostringstream msg; + std::cout << "Kokkos configuration" << std::endl; + if (Kokkos::hwloc::available()) + { + msg << "hwloc( NUMA[" << Kokkos::hwloc::get_available_numa_count() << "] x CORE[" + << Kokkos::hwloc::get_available_cores_per_numa() << "] x HT[" + << Kokkos::hwloc::get_available_threads_per_core() << "] )" << std::endl; + } + Kokkos::print_configuration(msg); + std::cout << msg.str(); + std::cout << "##########################\n"; + } + + constexpr int N = 17; + printf("Computing binomial coefficients for N=%d\n", N); + kalypsso::test_binomial_coef(); + + Kokkos::finalize(); + + return EXIT_SUCCESS; + +} // end main diff --git a/test/cpptrace/CMakeLists.txt b/test/cpptrace/CMakeLists.txt new file mode 100644 index 0000000..178275a --- /dev/null +++ b/test/cpptrace/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(test_cpptrace "") +target_sources(test_cpptrace PRIVATE test_cpptrace.cpp) +target_link_libraries(test_cpptrace PUBLIC cpptrace::cpptrace kalypsso::core) diff --git a/test/cpptrace/test_cpptrace.cpp b/test/cpptrace/test_cpptrace.cpp new file mode 100644 index 0000000..cb6babe --- /dev/null +++ b/test/cpptrace/test_cpptrace.cpp @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * A minimalist example to use library backward-cpp to pretty print + * error / stack trace... + * + * cpptrace is available from https://github.com/jeremy-rifkin/cpptrace + */ + +#include + +#include + +void +badass_function() +{ + KALYPSSO_DISABLE_STRINGOP_OVERFLOW_WARNINGS_PUSH() + char * ptr = (char *)42; + *ptr = 42; + KALYPSSO_DISABLE_STRINGOP_OVERFLOW_WARNINGS_POP() +} + +int +main(int argc, char * argv[]) +{ + kalypsso::cpptrace_initialize(); + + std::cout << "Create a segfault on purpose...\n"; + badass_function(); + + return 0; +} diff --git a/test/io/CMakeLists.txt b/test/io/CMakeLists.txt new file mode 100644 index 0000000..c7b8912 --- /dev/null +++ b/test/io/CMakeLists.txt @@ -0,0 +1,91 @@ +# ======================================================================= +# ======================================================================= +add_executable(test_io_vtk "") +target_sources(test_io_vtk PRIVATE test_io.cpp) +target_compile_options(test_io_vtk PRIVATE -DKALYPSSO_IO_VTK) +target_link_libraries(test_io_vtk PUBLIC kalypsso::core kalypsso::p4est_wrap kalypsso::config + kalypsso::mpiUtils) + +add_test(NAME io_vtk_2d COMMAND ${TEST_MPI_PREFIX_CMD} test_io_vtk brick + ${KALYPSSO_TEST_AMR_MAX_LEVEL} 5 3) +set_property(TEST io_vtk_2d PROPERTY LABELS io 2D) +add_test(NAME io_vtk_3d COMMAND ${TEST_MPI_PREFIX_CMD} test_io_vtk brick3 + ${KALYPSSO_TEST_AMR_MAX_LEVEL} 4 3 2) +set_property(TEST io_vtk_2d PROPERTY LABELS io 3D) + +if(Kokkos_ENABLE_OPENMP) + set_property(TEST io_vtk_2d PROPERTY ENVIRONMENT OMP_NUM_THREADS=${KALYPSSO_TEST_OMP_NUM_THREADS} + OMP_PROC_BIND=spread OMP_PLACES=threads) + set_property(TEST io_vtk_3d PROPERTY ENVIRONMENT OMP_NUM_THREADS=${KALYPSSO_TEST_OMP_NUM_THREADS} + OMP_PROC_BIND=spread OMP_PLACES=threads) +endif() + +# ======================================================================= +# ======================================================================= +if(KALYPSSO_CORE_USE_HDF5) + add_executable(test_io_hdf5_block "") + target_sources(test_io_hdf5_block PRIVATE test_io.cpp) + target_compile_options(test_io_hdf5_block PRIVATE -DKALYPSSO_IO_HDF5_BLOCK) + target_link_libraries(test_io_hdf5_block PUBLIC kalypsso::core kalypsso::p4est_wrap + kalypsso::config kalypsso::mpiUtils) + + add_test(NAME io_hdf5_block_2d COMMAND ${TEST_MPI_PREFIX_CMD} test_io_hdf5_block brick + ${KALYPSSO_TEST_AMR_MAX_LEVEL} 5 3) + + set_property(TEST io_hdf5_block_2d PROPERTY LABELS io 2D) + + add_test(NAME io_hdf5_block_3d COMMAND ${TEST_MPI_PREFIX_CMD} test_io_hdf5_block brick3 + ${KALYPSSO_TEST_AMR_MAX_LEVEL} 4 3 2) + set_property(TEST io_hdf5_block_3d PROPERTY LABELS io 3D) + + if(Kokkos_ENABLE_OPENMP) + set_property( + TEST io_hdf5_block_2d PROPERTY ENVIRONMENT OMP_NUM_THREADS=${KALYPSSO_TEST_OMP_NUM_THREADS} + OMP_PROC_BIND=spread OMP_PLACES=threads) + set_property( + TEST io_hdf5_block_3d PROPERTY ENVIRONMENT OMP_NUM_THREADS=${KALYPSSO_TEST_OMP_NUM_THREADS} + OMP_PROC_BIND=spread OMP_PLACES=threads) + endif() +endif() + +# ======================================================================= +# ======================================================================= +if(KALYPSSO_CORE_USE_HDF5) + add_executable(test_io_hdf5_ghosted_block "") + target_sources(test_io_hdf5_ghosted_block PRIVATE test_io.cpp) + target_compile_options(test_io_hdf5_ghosted_block PRIVATE -DKALYPSSO_IO_HDF5_GHOSTED_BLOCK) + target_link_libraries(test_io_hdf5_ghosted_block PUBLIC kalypsso::core kalypsso::p4est_wrap + kalypsso::config kalypsso::mpiUtils) +endif() + +# ======================================================================= +# ======================================================================= +if(KALYPSSO_CORE_USE_HDF5) + add_executable(test_io_brick_block "") + target_sources(test_io_brick_block PRIVATE test_io_brick.cpp) + target_compile_options(test_io_brick_block PRIVATE -DKALYPSSO_IO_HDF5_BLOCK) + target_link_libraries(test_io_brick_block PUBLIC kalypsso::core kalypsso::p4est_wrap + kalypsso::config kalypsso::mpiUtils) +endif() + +# ======================================================================= +# ======================================================================= +if(KALYPSSO_CORE_USE_HDF5) + add_executable(test_io_brick_ghosted_block "") + target_sources(test_io_brick_ghosted_block PRIVATE test_io_brick.cpp) + target_compile_options(test_io_brick_ghosted_block PRIVATE -DKALYPSSO_IO_HDF5_GHOSTED_BLOCK) + target_link_libraries(test_io_brick_ghosted_block PUBLIC kalypsso::core kalypsso::p4est_wrap + kalypsso::config kalypsso::mpiUtils) +endif() + +# ======================================================================= +# ======================================================================= + +configure_file(test_io_vtk.ini test_io_vtk.ini COPYONLY) +configure_file(test_io_hdf5_block.ini test_io_hdf5_block.ini COPYONLY) + +# +# cleanup any artefact hdf5 / xmf / vtk files produced by running the targets +# +add_custom_target(clean_io_data COMMAND ${CMAKE_COMMAND} -P + ${CMAKE_CURRENT_SOURCE_DIR}/clean_data.cmake) diff --git a/test/io/clean_data.cmake b/test/io/clean_data.cmake new file mode 100644 index 0000000..7ac4980 --- /dev/null +++ b/test/io/clean_data.cmake @@ -0,0 +1,3 @@ +file(GLOB files_to_remove "${CMAKE_CURRENT_BINARY_DIR}/*[.h5,.xmf,.vtu]") +message("removing ${files_to_remove}") +file(REMOVE ${files_to_remove}) diff --git a/test/io/test_io.cpp b/test/io/test_io.cpp new file mode 100644 index 0000000..32b5372 --- /dev/null +++ b/test/io/test_io.cpp @@ -0,0 +1,1429 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file test_io.cpp + */ +/* + * Usage: test_simple_vtkio + * + * possible 2D configurations: + * o unit Refinement on the unit square. + * o three Refinement on a forest with three trees. + * o brick Refinement on a regular grid of trees (orchard) + * o evil Check second round of refinement with np=5 level=7 + * o evil3 Check second round of refinement on three trees + * o pillow Refinement on a 2-tree pillow-shaped domain. + * o moebius Refinement on a 5-tree Moebius band. + * o star Refinement on a 6-tree star shaped domain. + * o cubed Refinement on a 6-tree cubed sphere surface. + * o disk Refinement on a 5-tree spherical disk. + * o periodic Refinement on the unit square with all-periodic b.c. + * o rotwrap Refinement on the unit square with weird periodic b.c. + * o icosahedron Refinement on the sphere + * o shell2d Refinement on a 2d shell with geometry. + * o disk2d Refinement on a 2d disk with geometry. + * + * possible 3D configurations: + * o unit3 The unit cube. + * o brick3 Refinement on a regular grid of trees (orchard) + * o periodic3 The unit cube with all-periodic boundary conditions. + * o rotwrap3 The unit cube with various self-periodic b.c. + * o twocubes3 Two connected cubes. + * o twowrap3 Two cubes with periodically identified far ends. + * o rotcubes3 A collection of six connected rotated cubes. + * o shell3 A 24-tree discretization of a hollow sphere. + * o sphere3 A 13-tree discretization of a solid sphere. + * o torus A configurable discretization of a solid torus. + */ + +#if defined(KALYPSSO_IO_VTK) +# include +#elif defined(KALYPSSO_IO_HDF5_BLOCK) || defined(KALYPSSO_IO_HDF5_GHOSTED_BLOCK) +# include +#endif + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +enum simple_config_t +{ + // 2D + P4EST_CONFIG_NULL, + P4EST_CONFIG_UNIT, + P4EST_CONFIG_THREE, + P4EST_CONFIG_BRICK, + P4EST_CONFIG_EVIL, + P4EST_CONFIG_EVIL3, + P4EST_CONFIG_PILLOW, + P4EST_CONFIG_MOEBIUS, + P4EST_CONFIG_STAR, + P4EST_CONFIG_CUBED, + P4EST_CONFIG_DISK, + P4EST_CONFIG_PERIODIC, + P4EST_CONFIG_ROTWRAP, + P4EST_CONFIG_ICOSAHEDRON, + P4EST_CONFIG_DISK2D, + P4EST_CONFIG_SHELL2D, + // 3D + P8EST_CONFIG_NULL, + P8EST_CONFIG_UNIT, + P8EST_CONFIG_PERIODIC, + P8EST_CONFIG_BRICK, + P8EST_CONFIG_ROTWRAP, + P8EST_CONFIG_TWOCUBES, + P8EST_CONFIG_TWOWRAP, + P8EST_CONFIG_ROTCUBES, + P8EST_CONFIG_SHELL, + P8EST_CONFIG_SPHERE, + P8EST_CONFIG_TORUS +}; + +struct user_data_t +{ + double x; // physical space +}; + +static int refine_level = 0; + +// ======================================================================================= +// ======================================================================================= +template +void +quadrant_center_vertex(typename kalypsso::p4est::Wrapper::connectivity_t * connectivity, + typename kalypsso::p4est::Wrapper::geometry_t * geom, + typename kalypsso::p4est::topidx_t tree, + typename kalypsso::p4est::Wrapper::quadrant_t * quad, + double xyz[3]) +{ + + using namespace kalypsso::p4est; + + uint32_t root_len = Wrapper::ROOT_LEN; + uint32_t quad_len = Wrapper::QUADRANT_LEN(quad->level); + + qcoord_t half_length = quad_len / 2; + + double h2 = 0.5 * quad_len / root_len; + const double intsize = 1.0 / root_len; + + if (geom != nullptr) + { + + double xyz_logic[3] = { 0., 0., 0. }; + + /* + * get coordinates at cell center + */ + xyz_logic[0] = intsize * get_x(quad) + h2; + xyz_logic[1] = intsize * get_y(quad) + h2; + xyz_logic[2] = dim == 3 ? intsize * get_z(quad) + h2 : 0.0; + + // from logical coordinates to physical coordinates + geom->X(geom, tree, xyz_logic, xyz); + } + else + { // connectivity space (no deformation geometry) + + qcoord_t xyz_logic[3] = { get_x(quad) + half_length, + get_y(quad) + half_length, + get_z(quad) + half_length }; + + Wrapper::qcoord_to_vertex(connectivity, tree, xyz_logic, xyz); + + } // end cartesian geometry + +} // quadrant_center_vertex + +// ======================================================================================= +// ======================================================================================= +template +static void +init_fn(typename kalypsso::p4est::Wrapper::forest_t * p4est, + typename kalypsso::p4est::topidx_t which_tree, + typename kalypsso::p4est::Wrapper::quadrant_t * quadrant) +{ + + using namespace kalypsso::p4est; + + using connectivity_t = typename Wrapper::connectivity_t; + using geometry_t = typename Wrapper::geometry_t; + + user_data_t * data = (user_data_t *)quadrant->p.user_data; + + // compute physical space coordinates, and put "x" into user_data + + connectivity_t * conn = (connectivity_t *)p4est->connectivity; + geometry_t * geom = (geometry_t *)p4est->user_pointer; + + double XYZ[3]; + + quadrant_center_vertex(conn, geom, which_tree, quadrant, XYZ); + + data->x = XYZ[0]; +} + +// ======================================================================================= +// ======================================================================================= +template +static int +refine_normal_fn(typename kalypsso::p4est::Wrapper::forest_t * p4est, + typename kalypsso::p4est::topidx_t which_tree, + typename kalypsso::p4est::Wrapper::quadrant_t * quadrant) +{ + using namespace kalypsso::p4est; + + if ((int)quadrant->level >= (refine_level - (int)(which_tree % 3))) + { + return 0; + } + if (quadrant->level == 1 && Wrapper::quadrant_child_id(quadrant) == 3) + { + return 1; + } + if (quadrant->x == P4EST_LAST_OFFSET(2) && quadrant->y == P4EST_LAST_OFFSET(2)) + { + return 1; + } + + if (dim == 2) + { + if (get_x(quadrant) >= (int)Wrapper::QUADRANT_LEN(2)) + { + return 0; + } + } + else + { + if (get_z(quadrant) >= (int)Wrapper::QUADRANT_LEN(2)) + { + return 0; + } + } + + return 1; +} + +// ================================================================================================ +// ================================================================================================ +template +static int +refine_sparse_fn(typename kalypsso::p4est::Wrapper::forest_t * forest, + typename kalypsso::p4est::topidx_t which_tree, + typename kalypsso::p4est::Wrapper::quadrant_t * quadrant) +{ + using namespace kalypsso::p4est; + // uint32_t quad_len = Wrapper::QUADRANT_LEN (quadrant->level); + + if (which_tree != 0) + { + return 0; + } + if ((int)quadrant->level >= refine_level) + { + return 0; + } + if (quadrant->level == 0) + { + return 1; + } + if (dim == 2) + { + if (get_x(quadrant) < (int)Wrapper::QUADRANT_LEN(2) && get_y(quadrant) > 0) + { + return 1; + } + } + else + { + if (get_x(quadrant) < (int)Wrapper::QUADRANT_LEN(2) && get_y(quadrant) > 0 and + get_z(quadrant) < (int)Wrapper::QUADRANT_LEN(2)) + { + return 1; + } + } + + return 0; +} + +template +static int +refine_evil_fn(typename kalypsso::p4est::Wrapper::forest_t * forest, + typename kalypsso::p4est::topidx_t which_tree, + typename kalypsso::p4est::Wrapper::quadrant_t * quadrant) +{ + if ((int)quadrant->level >= refine_level) + { + return 0; + } + if (forest->mpirank <= 1) + { + return 1; + } + + return 0; +} + +template +static int +refine_evil3_fn(typename kalypsso::p4est::Wrapper::forest_t * p4est, + typename kalypsso::p4est::topidx_t which_tree, + typename kalypsso::p4est::Wrapper::quadrant_t * quadrant) +{ + + using namespace kalypsso::p4est; + + qcoord_t u2; + typename Wrapper::quadrant_t ref; + + P4EST_QUADRANT_INIT(&ref); + + u2 = Wrapper::QUADRANT_LEN(2); + + if (which_tree == 0) + { + ref.x = 3 * u2; + ref.y = 2 * u2; + } + else if (which_tree == 1) + { + ref.x = 2 * u2; + ref.y = 3 * u2; + } + ref.level = 2; + + if ((int)quadrant->level >= refine_level) + { + return 0; + } + if ((which_tree == 0 || which_tree == 1) && (Wrapper::quadrant_is_equal(&ref, quadrant) || + Wrapper::quadrant_is_ancestor(&ref, quadrant))) + { + return 1; + } + + return 0; +} + +template +static int +coarsen_evil_fn(typename kalypsso::p4est::Wrapper::forest_t * forest, + typename kalypsso::p4est::topidx_t which_tree, + typename kalypsso::p4est::Wrapper::quadrant_t * quadrant[]) +{ + if (forest->mpirank >= 2) + { + return 1; + } + + return 0; +} + +template +static int +refine_icosahedron_fn(typename kalypsso::p4est::Wrapper::forest_t * forest, + typename kalypsso::p4est::topidx_t which_tree, + typename kalypsso::p4est::Wrapper::quadrant_t * quadrant) +{ + using namespace kalypsso::p4est; + using geometry_t = typename Wrapper::geometry_t; + + geometry_t * geom = (geometry_t *)forest->user_pointer; + + /* logical coordinates */ + double xyz[3] = { 0, 0, 0 }; + + /* physical coordinates */ + double XYZ[3] = { 0, 0, 0 }; + + double h2 = 0.5 * Wrapper::QUADRANT_LEN(quadrant->level) / Wrapper::ROOT_LEN; + const double intsize = 1.0 / Wrapper::ROOT_LEN; + + /* + * get coordinates at cell center + */ + xyz[0] = intsize * quadrant->x + h2; + xyz[1] = intsize * quadrant->y + h2; + xyz[2] = 0; // dim == 3 ? intsize * quadrant->z + h2 : 0; + + /* from logical coordinates to physical coordinates (cartesian) */ + geom->X(geom, which_tree, xyz, XYZ); + + if (quadrant->level > 6) + return 0; + if (XYZ[2] > 0 and quadrant->level >= 3) + return 0; + + return 1; +} + + +template +static int +refine_radius_fn(typename kalypsso::p4est::Wrapper::forest_t * forest, + typename kalypsso::p4est::topidx_t which_tree, + typename kalypsso::p4est::Wrapper::quadrant_t * quadrant) +{ + + using namespace kalypsso::p4est; + using geometry_t = typename Wrapper::geometry_t; + + uint32_t root_len = Wrapper::ROOT_LEN; + uint32_t quad_len = Wrapper::QUADRANT_LEN(quadrant->level); + + // stop criterion + if ((int)quadrant->level >= refine_level) + { + return 0; + } + + geometry_t * geom = (geometry_t *)forest->user_pointer; + + // logical coordinates + double xyz[3] = { 0, 0, 0 }; + + // physical coordinates (after geometry mapping ) + double XYZ[3] = { 0, 0, 0 }; + + // half-size of the cell in logical coordinate + double h2 = 0.5 * quad_len / root_len; + + const double intsize = 1.0 / root_len; + + /* + * get coordinates at cell center + */ + xyz[0] = intsize * get_x(quadrant) + h2; + xyz[1] = intsize * get_y(quadrant) + h2; + if (dim == 3) + xyz[2] = intsize * get_z(quadrant) + h2; + + // apply mapping from logical coordinates to physical coordinates + geom->X(geom, which_tree, xyz, XYZ); + + double radius = 0; + if (dim == 3) + radius = sqrt(XYZ[0] * XYZ[0] + XYZ[1] * XYZ[1] + XYZ[2] * XYZ[2]); + else + radius = sqrt(XYZ[0] * XYZ[0] + XYZ[1] * XYZ[1]); + + if (dim == 2) + { + if (radius < 0.7 and quadrant->level > 4) + return 0; + if (radius < 0.8 and quadrant->level > 5) + return 0; + if (radius < 0.9 and quadrant->level > 6) + return 0; + } + else + { + if (radius < 0.7 and quadrant->level > 3) + return 0; + if (radius < 0.85 and quadrant->level > 4) + return 0; + if (radius < 0.95 and quadrant->level > 5) + return 0; + if (radius < 0.99 and quadrant->level > 6) + return 0; + } + + return 1; +} + +// ======================================================================================= +// ======================================================================================= +/** + * callback routine for p4est_iterate to generate fake data. + */ +template +void +set_userdata_cb(typename kalypsso::p4est::Wrapper::volume_info_t * info, void * user_data) +{ + + using namespace kalypsso::p4est; + + using forest_t = typename Wrapper::forest_t; + using tree_t = typename Wrapper::tree_t; + using quadrant_t = typename Wrapper::quadrant_t; + + /* we passed the array of values to fill as the user_data in the call + to p4est_iterate */ + sc_array_t * scalar_data = (sc_array_t *)user_data; + double * this_data; + forest_t * forest = info->p4est; + quadrant_t * q = info->quad; + topidx_t which_tree = info->treeid; + locidx_t local_id = + info->quadid; /* this is the index of q *within its tree's numbering*. We want to convert it + its index for all the quadrants on this process, which we do below */ + tree_t * tree; + user_data_t * udata = (user_data_t *)q->p.user_data; + locidx_t arrayoffset; + + tree = (tree_t *)(forest->trees->array + sizeof(tree_t) * (size_t)which_tree); + + /* compute id relative inside current MPI process */ + local_id += tree->quadrants_offset; + arrayoffset = local_id; + + this_data = (double *)sc_array_index(scalar_data, arrayoffset); + this_data[0] = udata->x; + +} // set_userdata_cb + +// ======================================================================================= +// ======================================================================================= +/** + * callback routine for p4est_iterate to generate fake data. + */ +template +void +set_orchard_key_cb(typename kalypsso::p4est::Wrapper::volume_info_t * info, void * user_data) +{ + + using namespace kalypsso::p4est; + + using forest_t = typename Wrapper::forest_t; + using tree_t = typename Wrapper::tree_t; + using quadrant_t = typename Wrapper::quadrant_t; + + /* we passed the array of values to fill as the user_data in the call + to p4est_iterate */ + sc_array_t * scalar_data = (sc_array_t *)user_data; + double * this_data; + forest_t * forest = info->p4est; + quadrant_t * q = info->quad; + topidx_t which_tree = info->treeid; + locidx_t local_id = + info->quadid; /* this is the index of q *within its tree's numbering*. We want to convert it + its index for all the quadrants on this process, which we do below */ + tree_t * tree; + user_data_t * udata = (user_data_t *)q->p.user_data; + locidx_t arrayoffset; + + tree = (tree_t *)(forest->trees->array + sizeof(tree_t) * (size_t)which_tree); + + /* compute id relative inside current MPI process */ + local_id += tree->quadrants_offset; + arrayoffset = local_id; + + this_data = (double *)sc_array_index(scalar_data, arrayoffset); + this_data[0] = udata->x; // TODO - WIP + +} // set_orchard_key_cb + +// ======================================================================================= +// ======================================================================================= +/* + * Output routine. + */ +template +void +my_p4est_write_file(kalypsso::ParallelEnv const & par_env, + typename kalypsso::p4est::Wrapper::forest_t * forest, + std::string connectivity_name, + int brick_dim[3], + typename kalypsso::p4est::Wrapper::geometry_t * geom, + std::string filename, + [[maybe_unused]] double scale) +{ + + using namespace kalypsso::p4est; + + // makes enum Hydro::VarId available + using Hydro = kalypsso::core::models::Hydro; + + // number of quadrant in local processor + auto numOcts = forest->local_num_quadrants; + + // create fake data as a scalar array + sc_array_t * scalar_data; + scalar_data = sc_array_new_size(sizeof(double), forest->local_num_quadrants); + + // Use the iterator to visit every cell and fill scalar_data vector + Wrapper::iterate_volume(forest, nullptr, (void *)scalar_data, set_userdata_cb); + + // config map +#if defined(KALYPSSO_IO_VTK) + kalypsso::ConfigMap config_map = kalypsso::broadcast_parameters("./test_io_vtk.ini"); +#elif defined(KALYPSSO_IO_HDF5_BLOCK) || defined(KALYPSSO_IO_HDF5_GHOSTED_BLOCK) + kalypsso::ConfigMap config_map = kalypsso::broadcast_parameters("./test_io_hdf5_block.ini"); +#endif + + // ensure amr connectivity is set + config_map.setString("amr", "connectivity", connectivity_name); + + // ensure brick sizes are set in config_map + config_map.setInteger("p4est_connectivity", "nbrick_x", brick_dim[0]); + config_map.setInteger("p4est_connectivity", "nbrick_y", brick_dim[1]); + config_map.setInteger("p4est_connectivity", "nbrick_z", brick_dim[2]); + + // create fake DataArray as Kokkos::View + // + // HDF5 can write both block data and leaf data + // VTK can (currently) only write leaf data + + [[maybe_unused]] const int nbvar = 5; + +#if defined(KALYPSSO_IO_HDF5_BLOCK) + using DataArrayBlock_t = kalypsso::DataArrayBlock; + + int32_t bx = config_map.getInteger("amr", "bx", 1); + int32_t by = config_map.getInteger("amr", "by", 1); + int32_t bz = config_map.getInteger("amr", "bz", 1); + uint32_t nbCells = dim == 2 ? bx * by : bx * by * bz; + + const auto block_sizes = [=]() { + if constexpr (dim == 2) + return kalypsso::block_size_t<2>{ bx, by }; + if constexpr (dim == 3) + return kalypsso::block_size_t<3>{ bx, by, bz }; + }(); + + auto fakeData_block = DataArrayBlock_t("fakeData_block", block_sizes, nbvar, numOcts); + auto fakeData_block_h = DataArrayBlock_t::create_host_mirror_view(fakeData_block); +#elif defined(KALYPSSO_IO_HDF5_GHOSTED_BLOCK) + using DataArrayGhostedBlock_t = kalypsso::DataArrayGhostedBlock; + using DataArrayGhostedBlockHost_t = + kalypsso::DataArrayGhostedBlock; + + // get block sizes + int32_t bx = config_map.getInteger("amr", "bx", 1); + int32_t by = config_map.getInteger("amr", "by", 1); + int32_t bz = config_map.getInteger("amr", "bz", 1); + + const auto block_sizes = [=]() { + if constexpr (dim == 2) + return kalypsso::block_size_t<2>{ bx, by }; + if constexpr (dim == 3) + return kalypsso::block_size_t<3>{ bx, by, bz }; + }(); + + int nbCells = dim == 2 ? bx * by : bx * by * bz; + + // get ghost sizes + const int32_t gx = config_map.getInteger("amr", "gx", 2); + const int32_t gy = config_map.getInteger("amr", "gy", 2); + const int32_t gz = config_map.getInteger("amr", "gz", 2); + + const auto ghosted_sizes = [=]() { + if constexpr (dim == 2) + return kalypsso::block_size_t<2>{ bx + 2 * gx, by + 2 * gy }; + if constexpr (dim == 3) + return kalypsso::block_size_t<3>{ bx + 2 * gx, by + 2 * gy, bz + 2 * gz }; + }(); + + const auto shift = [=]() { + if constexpr (dim == 2) + return kalypsso::block_size_t<2>{ -gx, -gy }; + if constexpr (dim == 3) + return kalypsso::block_size_t<3>{ -gx, -gy, -gz }; + }(); + + auto fakeData_ghosted_block = DataArrayGhostedBlock_t(block_sizes, + ghosted_sizes, + shift, + "test_data_ghosted_block", + nbvar, + forest->local_num_quadrants); + + auto fakeData_ghosted_block_h = DataArrayGhostedBlockHost_t(block_sizes, + ghosted_sizes, + shift, + "test_data_ghosted_block_host", + nbvar, + forest->local_num_quadrants); + + auto total_block_sizes = fakeData_ghosted_block.ghosted_block_size(); + auto tx = total_block_sizes[kalypsso::IX]; + auto ty = total_block_sizes[kalypsso::IY]; + auto tz = [=]() { + if constexpr (dim == 2) + return 1; + if constexpr (dim == 3) + return total_block_sizes[kalypsso::IZ]; + }(); + +#endif + + using DataArrayLeaf_t = kalypsso::DataArrayLeaf; + + auto fakeData_leaf = DataArrayLeaf_t("fakeData_leaf", numOcts, 5); + auto fakeData_leaf_h = Kokkos::create_mirror(fakeData_leaf); + + // model + Hydro::Settings settings{ dim }; + Hydro model(settings); + + // build list of variables to write by parsing config field "output/write_variables" + // list is actually a map of names to enum + kalypsso::names2index_t names2index; // this is initially empty + kalypsso::build_var_to_write_map(names2index, model.get_names2id_map(), config_map); + + auto fm = model.get_fieldmap(); + const auto id2names = model.get_id2names_map(); + + // fill fakeData on host +#if defined(KALYPSSO_IO_HDF5_BLOCK) + for (locidx_t iOct = 0; iOct < numOcts; ++iOct) + { + // global index + gloidx_t iOct_g = iOct + forest->global_first_quadrant[forest->mpirank]; + + double * ptr = (double *)sc_array_index(scalar_data, iOct); + for (uint32_t icell = 0; icell < nbCells; ++icell) + { + fakeData_block_h(icell, fm[Hydro::ID], iOct) = ptr[0]; + fakeData_block_h(icell, fm[Hydro::IE], iOct) = 42.0; + fakeData_block_h(icell, fm[Hydro::IU], iOct) = 1.0 * iOct_g + icell * 1.0 / nbCells; + fakeData_block_h(icell, fm[Hydro::IV], iOct) = 2.0; + } + } +#elif defined(KALYPSSO_IO_HDF5_GHOSTED_BLOCK) + for (locidx_t iOct = 0; iOct < numOcts; ++iOct) + { + // global index + gloidx_t iOct_g = iOct + forest->global_first_quadrant[forest->mpirank]; + + double * ptr = (double *)sc_array_index(scalar_data, iOct); + + const auto shift = fakeData_ghosted_block.shift(); + const auto ghosted_size = fakeData_ghosted_block.ghosted_block_size(); + + if constexpr (dim == 2) + { + for (int32_t j = shift[kalypsso::IY]; j < shift[kalypsso::IY] + ghosted_size[kalypsso::IY]; + ++j) + for (int32_t i = shift[kalypsso::IX]; i < shift[kalypsso::IX] + ghosted_size[kalypsso::IX]; + ++i) + { + if (i >= 0 and i < bx and j >= 0 and j < by) + { + auto icell = (i + gx) + bx * (j + gy); + fakeData_ghosted_block_h(i, j, fm[Hydro::ID], iOct) = ptr[0]; + fakeData_ghosted_block_h(i, j, fm[Hydro::IE], iOct) = 42.0; + fakeData_ghosted_block_h(i, j, fm[Hydro::IU], iOct) = + 1.0 * iOct_g + icell * 1.0 / nbCells; + fakeData_ghosted_block_h(i, j, fm[Hydro::IV], iOct) = 2.0; + } + else + { + fakeData_ghosted_block_h(i, j, fm[Hydro::ID], iOct) = -0.1; + fakeData_ghosted_block_h(i, j, fm[Hydro::IE], iOct) = 1.0; + fakeData_ghosted_block_h(i, j, fm[Hydro::IU], iOct) = 2.0; + fakeData_ghosted_block_h(i, j, fm[Hydro::IV], iOct) = 3.0; + } + } + } + if constexpr (dim == 3) + { + for (int32_t k = shift[kalypsso::IZ]; k < shift[kalypsso::IZ] + ghosted_size[kalypsso::IZ]; + ++k) + for (int32_t j = shift[kalypsso::IY]; j < shift[kalypsso::IY] + ghosted_size[kalypsso::IY]; + ++j) + for (int32_t i = shift[kalypsso::IX]; + i < shift[kalypsso::IX] + ghosted_size[kalypsso::IX]; + ++i) + { + if (i >= 0 and i < bx and j >= 0 and j < by and k >= 0 and k < bz) + { + auto icell = (i + gx) + bx * (j + gy) + bx * by * (k + gz); + fakeData_ghosted_block_h(i, j, k, fm[Hydro::ID], iOct) = ptr[0]; + fakeData_ghosted_block_h(i, j, k, fm[Hydro::IE], iOct) = 42.0; + fakeData_ghosted_block_h(i, j, k, fm[Hydro::IU], iOct) = + 1.0 * iOct_g + icell * 1.0 / nbCells; + fakeData_ghosted_block_h(i, j, k, fm[Hydro::IV], iOct) = 2.0; + fakeData_ghosted_block_h(i, j, k, fm[Hydro::IW], iOct) = 3.0; + } + else + { + fakeData_ghosted_block_h(i, j, k, fm[Hydro::ID], iOct) = 0.0; + fakeData_ghosted_block_h(i, j, k, fm[Hydro::IE], iOct) = 1.0; + fakeData_ghosted_block_h(i, j, k, fm[Hydro::IU], iOct) = 2.0; + fakeData_ghosted_block_h(i, j, k, fm[Hydro::IV], iOct) = 3.0; + fakeData_ghosted_block_h(i, j, k, fm[Hydro::IW], iOct) = 4.0; + } + } + } + } + Kokkos::deep_copy(fakeData_ghosted_block.flat_view(), fakeData_ghosted_block_h.flat_view()); +#endif + + for (locidx_t i = 0; i < numOcts; ++i) + { + double * ptr = (double *)sc_array_index(scalar_data, i); + fakeData_leaf_h(i, fm[Hydro::ID]) = ptr[0]; + fakeData_leaf_h(i, fm[Hydro::IE]) = 42.0; + fakeData_leaf_h(i, fm[Hydro::IU]) = 1.0 * i; + fakeData_leaf_h(i, fm[Hydro::IV]) = 2.0; + } + + // and copy to device + // usually, it is the other way around + Kokkos::deep_copy(fakeData_leaf, fakeData_leaf_h); + +#if defined(KALYPSSO_IO_HDF5_BLOCK) + Kokkos::deep_copy(fakeData_block.logical_view(), fakeData_block_h.logical_view()); +#endif + +#if defined(KALYPSSO_IO_VTK) + // finally write vtk file + kalypsso::writeVTK( + forest, geom, fakeData_leaf, model, filename); +#elif defined(KALYPSSO_IO_HDF5_BLOCK) || defined(KALYPSSO_IO_HDF5_GHOSTED_BLOCK) + + const auto start_index = [=]() { + if constexpr (dim == 2) + return kalypsso::coord_t<2>{ 0, 0 }; + if constexpr (dim == 3) + return kalypsso::coord_t<3>{ 0, 0, 0 }; + }(); + + // don't write mesh info at cell level (only at block level) + kalypsso::HDF5_Xdmf_Writer_legacy writer( + forest, geom, config_map, block_sizes, start_index); + + std::string outputDir = config_map.getString("output", "outputDir", "./"); + +# if defined(KALYPSSO_IO_HDF5_GHOSTED_BLOCK) + // start index is opposite of shift + const auto start_index2 = [=]() { + if constexpr (dim == 2) + return kalypsso::coord_t<2>{ gx, gy }; + if constexpr (dim == 3) + return kalypsso::coord_t<3>{ gx, gy, gz }; + }(); + + // 1. save only the inner part (should produce exactly the same hdf5 as KALYPSSO_IO_HDF5_BLOCK) + // this can be check using h5diff + writer.set_block_mode(block_sizes, start_index2); + + // 2. save all (inner part and ghost cells) + // writer.set_block_mode(ghosted_sizes, start_index); +# endif + + writer.set_write_mesh_info(true); + writer.update_mesh_info(); + writer.open(filename, outputDir); + writer.write_header(0.0); + + // write user the fake data (all scalar fields, here only one) +# if defined(KALYPSSO_IO_HDF5_BLOCK) + writer.write_quadrant_attribute(fakeData_block_h, fm[Hydro::ID], id2names.at(Hydro::ID)); + writer.write_quadrant_attribute(fakeData_block_h, fm[Hydro::IE], id2names.at(Hydro::IE)); + writer.write_quadrant_attribute(fakeData_block_h, fm[Hydro::IU], id2names.at(Hydro::IU)); + writer.write_quadrant_attribute(fakeData_block_h, fm[Hydro::IV], id2names.at(Hydro::IV)); +# elif defined(KALYPSSO_IO_HDF5_GHOSTED_BLOCK) + Kokkos::deep_copy(fakeData_ghosted_block_h.flat_view(), fakeData_ghosted_block.flat_view()); + writer.write_quadrant_attribute( + fakeData_ghosted_block_h.data(), fm[Hydro::ID], id2names.at(Hydro::ID)); + writer.write_quadrant_attribute( + fakeData_ghosted_block_h.data(), fm[Hydro::IE], id2names.at(Hydro::IE)); + writer.write_quadrant_attribute( + fakeData_ghosted_block_h.data(), fm[Hydro::IU], id2names.at(Hydro::IU)); + writer.write_quadrant_attribute( + fakeData_ghosted_block_h.data(), fm[Hydro::IV], id2names.at(Hydro::IV)); +# endif + + // check if we want to write velocity or rhoV vector fields + // std::string write_variables = config_map.getString("output", "write_variables", ""); + + // close the file + writer.write_footer(); + writer.close(); + + // + // now write leaf data + // + std::string filename2 = filename + "_quad"; + + writer.set_leaf_mode(); + writer.set_write_mesh_info(true); + + // writer2.update_mesh_info(); + writer.open(filename2, outputDir); + writer.write_header(0.0); + + writer.write_quadrant_attribute(fakeData_leaf_h, fm[Hydro::ID], id2names.at(Hydro::ID)); + + // close the file + writer.write_footer(); + writer.close(); + +#endif + + sc_array_destroy(scalar_data); + +} // my_p4est_write_file + +template +using connectivity_data_t = + std::tuple::connectivity_t *, std::string>; + +/* ================================================================= */ +/* ================================================================= */ +/* ================================================================= */ +template +connectivity_data_t +create_connectivity(simple_config_t config, int brick_dim[3]); + +template <> +connectivity_data_t<2> +create_connectivity<2>(simple_config_t config, int brick_dim[3]) +{ + + typename kalypsso::p4est::Wrapper<2>::connectivity_t * connectivity = nullptr; + std::string connectivity_name = "unknown"; + + if (config == P4EST_CONFIG_UNIT) + { + connectivity = p4est_connectivity_new_unitsquare(); + connectivity_name = "unit"; + } + else if (config == P4EST_CONFIG_THREE || config == P4EST_CONFIG_EVIL3) + { + connectivity = p4est_connectivity_new_corner(); + connectivity_name = "corner"; + } + else if (config == P4EST_CONFIG_BRICK) + { + connectivity = p4est_connectivity_new_brick(brick_dim[0], brick_dim[1], 1, 1); + connectivity_name = "brick"; + } + else if (config == P4EST_CONFIG_PILLOW) + { + connectivity = p4est_connectivity_new_pillow(); + connectivity_name = "pillow"; + } + else if (config == P4EST_CONFIG_MOEBIUS) + { + connectivity = p4est_connectivity_new_moebius(); + connectivity_name = "moebius"; + } + else if (config == P4EST_CONFIG_STAR) + { + connectivity = p4est_connectivity_new_star(); + connectivity_name = "star"; + } + else if (config == P4EST_CONFIG_CUBED) + { + connectivity = p4est_connectivity_new_cubed(); + connectivity_name = "cubed"; + } + else if (config == P4EST_CONFIG_DISK) + { +#if defined(KALYPSSO_CORE_USE_OLD_P4EST_API) + connectivity = p4est_connectivity_new_disk(); +#else + // non periodic disk + connectivity = p4est_connectivity_new_disk(0, 0); +#endif // USE_OLD_P4EST_API + connectivity_name = "disk"; + } + else if (config == P4EST_CONFIG_PERIODIC) + { + connectivity = p4est_connectivity_new_periodic(); + connectivity_name = "periodic"; + } + else if (config == P4EST_CONFIG_ROTWRAP) + { + connectivity = p4est_connectivity_new_rotwrap(); + connectivity_name = "rotwrap"; + } + else if (config == P4EST_CONFIG_ICOSAHEDRON) + { + connectivity = p4est_connectivity_new_icosahedron(); + connectivity_name = "icosahedron"; + } + else if (config == P4EST_CONFIG_DISK2D) + { + connectivity = p4est_connectivity_new_disk2d(); + connectivity_name = "disk2d"; + } + else if (config == P4EST_CONFIG_SHELL2D) + { + connectivity = p4est_connectivity_new_shell2d(); + connectivity_name = "shell2d"; + } + else + { + connectivity = p4est_connectivity_new_unitsquare(); + connectivity_name = "unit"; + } + + return { connectivity, connectivity_name }; + +} // create_connectivity<2> + +template <> +connectivity_data_t<3> +create_connectivity<3>(simple_config_t config, int brick_dim[3]) +{ + + kalypsso::p4est::Wrapper<3>::connectivity_t * connectivity = nullptr; + std::string connectivity_name = "unknown"; + + if (config == P8EST_CONFIG_UNIT) + { + connectivity = p8est_connectivity_new_unitcube(); + connectivity_name = "unit"; + } + else if (config == P8EST_CONFIG_PERIODIC) + { + connectivity = p8est_connectivity_new_periodic(); + connectivity_name = "periodic"; + } + else if (config == P8EST_CONFIG_BRICK) + { + connectivity = p8est_connectivity_new_brick(brick_dim[0], brick_dim[1], brick_dim[2], 1, 1, 1); + connectivity_name = "brick"; + } + else if (config == P8EST_CONFIG_ROTWRAP) + { + connectivity = p8est_connectivity_new_rotwrap(); + connectivity_name = "rotwrap"; + } + else if (config == P8EST_CONFIG_TWOCUBES) + { + connectivity = p8est_connectivity_new_twocubes(); + connectivity_name = "twocubes"; + } + else if (config == P8EST_CONFIG_TWOWRAP) + { + connectivity = p8est_connectivity_new_twowrap(); + connectivity_name = "twowrap"; + } + else if (config == P8EST_CONFIG_ROTCUBES) + { + connectivity = p8est_connectivity_new_rotcubes(); + connectivity_name = "rotcubes"; + } + else if (config == P8EST_CONFIG_SHELL) + { + connectivity = p8est_connectivity_new_shell(); + connectivity_name = "shell"; + } + else if (config == P8EST_CONFIG_SPHERE) + { + connectivity = p8est_connectivity_new_sphere(); + connectivity_name = "sphere"; + } + else if (config == P8EST_CONFIG_TORUS) + { + connectivity = p8est_connectivity_new_torus(8); + connectivity_name = "torus"; + } + + return { connectivity, connectivity_name }; + +} // create_connectivity<3> + +/* ================================================================= */ +/* ================================================================= */ +/* ================================================================= */ +template +typename kalypsso::p4est::Wrapper::geometry_t * +create_geometry(simple_config_t config, + typename kalypsso::p4est::Wrapper::connectivity_t * connectivity, + double R0, + double R1); + +template <> +kalypsso::p4est::Wrapper<2>::geometry_t * +create_geometry<2>(simple_config_t config, + kalypsso::p4est::Wrapper<2>::connectivity_t * connectivity, + double R0, + double R1) +{ + + kalypsso::p4est::Wrapper<2>::geometry_t * geom = nullptr; + + if (config == P4EST_CONFIG_ICOSAHEDRON) + { + geom = p4est_geometry_new_icosahedron(connectivity, R0); + } + else if (config == P4EST_CONFIG_DISK2D) + { + geom = p4est_geometry_new_disk2d(connectivity, R0, R1); + } + else if (config == P4EST_CONFIG_SHELL2D) + { + geom = p4est_geometry_new_shell2d(connectivity, R0, R1); + } + + return geom; + +} // create_geometry + +template <> +kalypsso::p4est::Wrapper<3>::geometry_t * +create_geometry<3>(simple_config_t config, + kalypsso::p4est::Wrapper<3>::connectivity_t * connectivity, + double R0, + double R1) +{ + + kalypsso::p4est::Wrapper<3>::geometry_t * geom = nullptr; + + if (config == P8EST_CONFIG_SHELL) + { + geom = p8est_geometry_new_shell(connectivity, 1., .44); + } + else if (config == P8EST_CONFIG_SPHERE) + { + geom = p8est_geometry_new_sphere(connectivity, 1., 0.7, 0.5); + } + else if (config == P8EST_CONFIG_TORUS) + { + geom = p8est_geometry_new_torus(connectivity, 0.44, 1.0, 3.0); + } + + return geom; +} // create_geometry + +/* ============================================================ */ +/* ============================================================ */ +/* ============================================================ */ +template +void +run_test(simple_config_t config, const kalypsso::ParallelEnv & par_env, int argc, char * argv[]) +{ + + if (dim == 2 and par_env.rank() == 0) + printf("Running a 2D test\n"); + if (dim == 3 and par_env.rank() == 0) + printf("Running a 3D test\n"); + + using namespace kalypsso::p4est; + + typename Wrapper::forest_t * forest; + typename Wrapper::geometry_t * geom = nullptr; + typename Wrapper::refine_cb_t refine_fn; + typename Wrapper::coarsen_cb_t coarsen_fn; + + // only meaningful when using brick connectivity + int brick_dim[3]; + brick_dim[0] = argc > 3 ? atoi(argv[3]) : 2; + brick_dim[1] = argc > 4 ? atoi(argv[4]) : 3; + brick_dim[2] = argc > 5 ? atoi(argv[5]) : 4; + + // create connectivity and forest structures + auto [connectivity, connectivity_name] = create_connectivity(config, brick_dim); + + // create geometry + double R0 = 0.44; + double R1 = 1.0; + if (argc >= 4) + R0 = atof(argv[3]); + if (argc >= 5) + R1 = atof(argv[4]); + geom = create_geometry(config, connectivity, R0, R1); + + // assign refine_fn, coarsen_fn + refine_fn = refine_normal_fn; + coarsen_fn = nullptr; + + if (config == P4EST_CONFIG_EVIL) + { + refine_fn = refine_evil_fn; + coarsen_fn = coarsen_evil_fn; + } + else if (config == P4EST_CONFIG_EVIL3) + { + refine_fn = refine_evil3_fn; + coarsen_fn = nullptr; + } + else if (config == P4EST_CONFIG_DISK2D) + { + refine_fn = refine_radius_fn; + coarsen_fn = nullptr; + } + else if (config == P4EST_CONFIG_ICOSAHEDRON) + { + refine_fn = refine_icosahedron_fn; + coarsen_fn = nullptr; + } + else if (config == P4EST_CONFIG_SHELL2D) + { + refine_fn = refine_radius_fn; + coarsen_fn = nullptr; + } + else if (config == P8EST_CONFIG_TWOCUBES) + { + refine_fn = refine_sparse_fn; + } + else if (config == P8EST_CONFIG_TWOWRAP) + { + refine_fn = refine_sparse_fn; + } + else + { + refine_fn = refine_normal_fn; + coarsen_fn = nullptr; + } + + /* + * set geometry as p4est user pointer so that we can + * retrieve it in the callbacks. + */ + forest = Wrapper::new_forest( +#ifdef KALYPSSO_CORE_USE_MPI + par_env.mpi_comm(), +#else + sc_MPI_COMM_WORLD, +#endif // KALYPSSO_CORE_USE_MPI + connectivity, + dim == 2 ? 4 : 4, + 0, + 0, + sizeof(user_data_t), + init_fn, + geom); + + double scale = 1.0; + +#if defined(KALYPSSO_IO_HDF5_BLOCK) + my_p4est_write_file( + par_env, forest, connectivity_name, brick_dim, geom, "test_new_block", scale); +#elif defined(KALYPSSO_IO_HDF5_GHOSTED_BLOCK) + my_p4est_write_file( + par_env, forest, connectivity_name, brick_dim, geom, "test_new_block_ghosted", scale); +#else + my_p4est_write_file( + par_env, forest, connectivity_name, brick_dim, geom, "test_new", scale); +#endif + + // refinement and coarsening + Wrapper::refine(forest, 1, refine_fn, init_fn); + if (coarsen_fn != nullptr) + { + Wrapper::coarsen(forest, 1, coarsen_fn, init_fn); + } +#if defined(KALYPSSO_IO_HDF5_BLOCK) + my_p4est_write_file( + par_env, forest, connectivity_name, brick_dim, geom, "test_refined_block", scale); +#elif defined(KALYPSSO_IO_HDF5_GHOSTED_BLOCK) + my_p4est_write_file( + par_env, forest, connectivity_name, brick_dim, geom, "test_refined_block_ghosted", scale); +#else + my_p4est_write_file( + par_env, forest, connectivity_name, brick_dim, geom, "test_refined", scale); +#endif + + // balance + Wrapper::balance(forest, Wrapper::CONNECT_FULL, init_fn); +#if defined(KALYPSSO_IO_HDF5_BLOCK) + my_p4est_write_file( + par_env, forest, connectivity_name, brick_dim, geom, "test_balanced_block", scale); +#elif defined(KALYPSSO_IO_HDF5_GHOSTED_BLOCK) + my_p4est_write_file( + par_env, forest, connectivity_name, brick_dim, geom, "test_balanced_block_ghosted", scale); +#else + my_p4est_write_file( + par_env, forest, connectivity_name, brick_dim, geom, "test_balanced", scale); +#endif + + [[maybe_unused]] auto crc = Wrapper::checksum(forest); + + // partition + Wrapper::partition(forest, 0, nullptr); +#if defined(KALYPSSO_IO_HDF5_BLOCK) + my_p4est_write_file( + par_env, forest, connectivity_name, brick_dim, geom, "test_partition_block", scale); +#elif defined(KALYPSSO_IO_HDF5_GHOSTED_BLOCK) + my_p4est_write_file( + par_env, forest, connectivity_name, brick_dim, geom, "test_partition_block_ghosted", scale); +#else + my_p4est_write_file( + par_env, forest, connectivity_name, brick_dim, geom, "test_partition", scale); +#endif + +#ifdef P4EST_ENABLE_DEBUG + // rebalance should not change checksum + Wrapper::balance(forest, Wrapper::CONNECT_FULL, init_fn); + P4EST_ASSERT(Wrapper::checksum(forest) == crc); +#endif + + // destroy the p4est and its connectivity structure + Wrapper::destroy(forest); + if (geom != nullptr) + { + Wrapper::geometry_destroy(geom); + } + Wrapper::connectivity_destroy(connectivity); + +} // run_test + +// ====================================================== +// ====================================================== +// ====================================================== +int +main(int argc, char ** argv) +{ + + simple_config_t config; + int wrongusage; + const char * usage; + + { + kalypsso::ParallelEnv par_env(argc, argv); + + // Process command line arguments + usage = + "Arguments: \n" + " 2D configuration can be any of\n" + " unit|three|brick|evil|evil3|pillow|moebius|\n" + " star|cubed|disk|periodic|rotwrap|icosahedron|disk2d|shell2d\n" + " 3D configuration can be any of\n" + " unit3|periodic3|brick3|rotwrap3|twocubes3|twowrap3|rotcubes3|shell3|sphere3|torus\n" + " Level controls the maximum depth of refinement\n" + "\n" + "Example run:\n" + " mpirun -np 3 ./test_io_hdf5_block unit 6\n" + " mpirun -np 6 ./test_io_hdf5_block brick 5 5 3\n" + " mpirun -np 3 ./test_io_hdf5_ghosted_block unit 6\n" + " mpirun -np 6 ./test_io_hdf5_ghosted_block brick 5 5 3\n"; + wrongusage = 0; + config = P4EST_CONFIG_NULL; + if (!wrongusage && argc < 3) + { + wrongusage = 1; + } + if (!wrongusage) + { + if (!strcmp(argv[1], "unit")) + { + config = P4EST_CONFIG_UNIT; + } + else if (!strcmp(argv[1], "three")) + { + config = P4EST_CONFIG_THREE; + } + else if (!strcmp(argv[1], "brick")) + { + config = P4EST_CONFIG_BRICK; + } + else if (!strcmp(argv[1], "evil")) + { + config = P4EST_CONFIG_EVIL; + } + else if (!strcmp(argv[1], "evil3")) + { + config = P4EST_CONFIG_EVIL3; + } + else if (!strcmp(argv[1], "pillow")) + { + config = P4EST_CONFIG_PILLOW; + } + else if (!strcmp(argv[1], "moebius")) + { + config = P4EST_CONFIG_MOEBIUS; + } + else if (!strcmp(argv[1], "star")) + { + config = P4EST_CONFIG_STAR; + } + else if (!strcmp(argv[1], "cubed")) + { + config = P4EST_CONFIG_CUBED; + } + else if (!strcmp(argv[1], "disk")) + { + config = P4EST_CONFIG_DISK; + } + else if (!strcmp(argv[1], "periodic")) + { + config = P4EST_CONFIG_PERIODIC; + } + else if (!strcmp(argv[1], "rotwrap")) + { + config = P4EST_CONFIG_ROTWRAP; + } + else if (!strcmp(argv[1], "icosahedron")) + { + config = P4EST_CONFIG_ICOSAHEDRON; + } + else if (!strcmp(argv[1], "disk2d")) + { + config = P4EST_CONFIG_DISK2D; + } + else if (!strcmp(argv[1], "shell2d")) + { + config = P4EST_CONFIG_SHELL2D; + } + // 3D config + else if (!strcmp(argv[1], "unit3")) + { + config = P8EST_CONFIG_UNIT; + } + else if (!strcmp(argv[1], "periodic3")) + { + config = P8EST_CONFIG_PERIODIC; + } + else if (!strcmp(argv[1], "brick3")) + { + config = P8EST_CONFIG_BRICK; + } + else if (!strcmp(argv[1], "rotwrap3")) + { + config = P8EST_CONFIG_ROTWRAP; + } + else if (!strcmp(argv[1], "twocubes3")) + { + config = P8EST_CONFIG_TWOCUBES; + } + else if (!strcmp(argv[1], "twowrap3")) + { + config = P8EST_CONFIG_TWOWRAP; + } + else if (!strcmp(argv[1], "rotcubes3")) + { + config = P8EST_CONFIG_ROTCUBES; + } + else if (!strcmp(argv[1], "shell3")) + { + config = P8EST_CONFIG_SHELL; + } + else if (!strcmp(argv[1], "sphere3")) + { + config = P8EST_CONFIG_SPHERE; + } + else if (!strcmp(argv[1], "torus")) + { + config = P8EST_CONFIG_TORUS; + } + else + { + wrongusage = 1; + } + } + if (wrongusage) + { + P4EST_GLOBAL_LERROR(usage); + sc_abort_collective("Usage error"); + } + + // assign variables based on configuration + refine_level = atoi(argv[2]); + + // run some tests + if (config <= P4EST_CONFIG_SHELL2D) + run_test<2, kalypsso::DefaultDevice>(config, par_env, argc, argv); + else + run_test<3, kalypsso::DefaultDevice>(config, par_env, argc, argv); + } + + return EXIT_SUCCESS; +} diff --git a/test/io/test_io_brick.cpp b/test/io/test_io_brick.cpp new file mode 100644 index 0000000..d711852 --- /dev/null +++ b/test/io/test_io_brick.cpp @@ -0,0 +1,749 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file test_io_brick.cpp + * + * same as test_io.cpp but simpler; only brick connectivity supported and hdf5 only + * easier to update for new test features, e.g. outside quadrants + */ + +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#include + +struct user_data_t +{ + double x; // physical space +}; + +static int refine_level = 0; + +// ======================================================================================= +// ======================================================================================= +template +void +quadrant_center_vertex(typename kalypsso::p4est::Wrapper::connectivity_t * connectivity, + typename kalypsso::p4est::Wrapper::geometry_t * geom, + typename kalypsso::p4est::topidx_t tree, + typename kalypsso::p4est::Wrapper::quadrant_t * quad, + double xyz[3]) +{ + + using namespace kalypsso::p4est; + + uint32_t root_len = Wrapper::ROOT_LEN; + uint32_t quad_len = Wrapper::QUADRANT_LEN(quad->level); + + qcoord_t half_length = quad_len / 2; + + double h2 = 0.5 * quad_len / root_len; + const double intsize = 1.0 / root_len; + + if (geom != nullptr) + { + + double xyz_logic[3] = { 0., 0., 0. }; + + /* + * get coordinates at cell center + */ + xyz_logic[0] = intsize * get_x(quad) + h2; + xyz_logic[1] = intsize * get_y(quad) + h2; + xyz_logic[2] = dim == 3 ? intsize * get_z(quad) + h2 : 0.0; + + // from logical coordinates to physical coordinates + geom->X(geom, tree, xyz_logic, xyz); + } + else + { // connectivity space (no deformation geometry) + + qcoord_t xyz_logic[3] = { get_x(quad) + half_length, + get_y(quad) + half_length, + get_z(quad) + half_length }; + + Wrapper::qcoord_to_vertex(connectivity, tree, xyz_logic, xyz); + + } // end cartesian geometry + +} // quadrant_center_vertex + +// ======================================================================================= +// ======================================================================================= +template +static void +init_fn(typename kalypsso::p4est::Wrapper::forest_t * p4est, + typename kalypsso::p4est::topidx_t which_tree, + typename kalypsso::p4est::Wrapper::quadrant_t * quadrant) +{ + + using namespace kalypsso::p4est; + + using connectivity_t = typename Wrapper::connectivity_t; + using geometry_t = typename Wrapper::geometry_t; + + user_data_t * data = (user_data_t *)quadrant->p.user_data; + + // compute physical space coordinates, and put "x" into user_data + + connectivity_t * conn = (connectivity_t *)p4est->connectivity; + geometry_t * geom = (geometry_t *)p4est->user_pointer; + + double XYZ[3]; + + quadrant_center_vertex(conn, geom, which_tree, quadrant, XYZ); + + data->x = XYZ[0]; +} + +// ======================================================================================= +// ======================================================================================= +template +static int +refine_normal_fn(typename kalypsso::p4est::Wrapper::forest_t * p4est, + typename kalypsso::p4est::topidx_t which_tree, + typename kalypsso::p4est::Wrapper::quadrant_t * quadrant) +{ + using namespace kalypsso::p4est; + + if ((int)quadrant->level >= (refine_level - (int)(which_tree % 3))) + { + return 0; + } + if (quadrant->level == 1 && Wrapper::quadrant_child_id(quadrant) == 3) + { + return 1; + } + if (quadrant->x == P4EST_LAST_OFFSET(2) && quadrant->y == P4EST_LAST_OFFSET(2)) + { + return 1; + } + + if (dim == 2) + { + if (get_x(quadrant) >= (int)Wrapper::QUADRANT_LEN(2)) + { + return 0; + } + } + else + { + if (get_z(quadrant) >= (int)Wrapper::QUADRANT_LEN(2)) + { + return 0; + } + } + + return 1; +} + +// ======================================================================================= +// ======================================================================================= +/** + * callback routine for p4est_iterate to generate fake data. + */ +template +void +set_userdata_cb(typename kalypsso::p4est::Wrapper::volume_info_t * info, void * user_data) +{ + + using namespace kalypsso::p4est; + + using forest_t = typename Wrapper::forest_t; + using tree_t = typename Wrapper::tree_t; + using quadrant_t = typename Wrapper::quadrant_t; + + /* we passed the array of values to fill as the user_data in the call + to p4est_iterate */ + sc_array_t * scalar_data = (sc_array_t *)user_data; + double * this_data; + forest_t * forest = info->p4est; + quadrant_t * q = info->quad; + topidx_t which_tree = info->treeid; + locidx_t local_id = + info->quadid; /* this is the index of q *within its tree's numbering*. We want to convert it + its index for all the quadrants on this process, which we do below */ + tree_t * tree; + user_data_t * udata = (user_data_t *)q->p.user_data; + locidx_t arrayoffset; + + tree = (tree_t *)(forest->trees->array + sizeof(tree_t) * (size_t)which_tree); + + /* compute id relative inside current MPI process */ + local_id += tree->quadrants_offset; + arrayoffset = local_id; + + this_data = (double *)sc_array_index(scalar_data, arrayoffset); + this_data[0] = udata->x; + +} // set_userdata_cb + +// ======================================================================================= +// ======================================================================================= +/* + * Output routine. + */ +template +void +my_p4est_write_file(kalypsso::ParallelEnv const & par_env, + typename kalypsso::p4est::Wrapper::forest_t * forest, + std::string connectivity_name, + int brick_dim[3], + typename kalypsso::p4est::Wrapper::geometry_t * geom, + std::string filename, + [[maybe_unused]] double scale) +{ + + using namespace kalypsso::p4est; + + // makes enum Hydro::VarId available + using Hydro = kalypsso::core::models::Hydro; + + // number of quadrant in local processor + auto numOcts = forest->local_num_quadrants; + + // create fake data as a scalar array + sc_array_t * scalar_data; + scalar_data = sc_array_new_size(sizeof(double), forest->local_num_quadrants); + + // Use the iterator to visit every cell and fill scalar_data vector + Wrapper::iterate_volume(forest, nullptr, (void *)scalar_data, set_userdata_cb); + + // config map + kalypsso::ConfigMap config_map = kalypsso::broadcast_parameters("./test_io_hdf5_block.ini"); + + // ensure amr connectivity is set + config_map.setString("amr", "connectivity", connectivity_name); + + // ensure brick sizes are set in config_map + config_map.setInteger("p4est_connectivity", "nbrick_x", brick_dim[0]); + config_map.setInteger("p4est_connectivity", "nbrick_y", brick_dim[1]); + config_map.setInteger("p4est_connectivity", "nbrick_z", brick_dim[2]); + + // create fake DataArray as Kokkos::View + // + // HDF5 can write both block data and leaf data + + [[maybe_unused]] const int nbvar = 5; + +#if defined(KALYPSSO_IO_HDF5_BLOCK) + using DataArrayBlock_t = kalypsso::DataArrayBlock; + + int32_t bx = config_map.getInteger("amr", "bx", 1); + int32_t by = config_map.getInteger("amr", "by", 1); + int32_t bz = config_map.getInteger("amr", "bz", 1); + uint32_t nbCells = dim == 2 ? bx * by : bx * by * bz; + + const auto block_sizes = [=]() { + if constexpr (dim == 2) + return kalypsso::block_size_t<2>{ bx, by }; + if constexpr (dim == 3) + return kalypsso::block_size_t<3>{ bx, by, bz }; + }(); + + auto fakeData_block = DataArrayBlock_t("fakeData_block", block_sizes, nbvar, numOcts); + auto fakeData_block_h = DataArrayBlock_t::create_host_mirror_view(fakeData_block); +#elif defined(KALYPSSO_IO_HDF5_GHOSTED_BLOCK) + using DataArrayGhostedBlock_t = kalypsso::DataArrayGhostedBlock; + using DataArrayGhostedBlockHost_t = + kalypsso::DataArrayGhostedBlock; + + // get block sizes + int32_t bx = config_map.getInteger("amr", "bx", 1); + int32_t by = config_map.getInteger("amr", "by", 1); + int32_t bz = config_map.getInteger("amr", "bz", 1); + + const auto block_sizes = [=]() { + if constexpr (dim == 2) + return kalypsso::block_size_t<2>{ bx, by }; + if constexpr (dim == 3) + return kalypsso::block_size_t<3>{ bx, by, bz }; + }(); + + int nbCells = dim == 2 ? bx * by : bx * by * bz; + + // get ghost sizes + const int32_t gx = config_map.getInteger("amr", "gx", 2); + const int32_t gy = config_map.getInteger("amr", "gy", 2); + const int32_t gz = config_map.getInteger("amr", "gz", 2); + + const auto ghosted_sizes = [=]() { + if constexpr (dim == 2) + return kalypsso::block_size_t<2>{ bx + 2 * gx, by + 2 * gy }; + if constexpr (dim == 3) + return kalypsso::block_size_t<3>{ bx + 2 * gx, by + 2 * gy, bz + 2 * gz }; + }(); + + const auto shift = [=]() { + if constexpr (dim == 2) + return kalypsso::block_size_t<2>{ -gx, -gy }; + if constexpr (dim == 3) + return kalypsso::block_size_t<3>{ -gx, -gy, -gz }; + }(); + + auto fakeData_ghosted_block = DataArrayGhostedBlock_t(block_sizes, + ghosted_sizes, + shift, + "test_data_ghosted_block", + nbvar, + forest->local_num_quadrants); + + auto fakeData_ghosted_block_h = DataArrayGhostedBlockHost_t(block_sizes, + ghosted_sizes, + shift, + "test_data_ghosted_block_host", + nbvar, + forest->local_num_quadrants); + + auto total_block_sizes = fakeData_ghosted_block.ghosted_block_size(); + auto tx = total_block_sizes[kalypsso::IX]; + auto ty = total_block_sizes[kalypsso::IY]; + auto tz = [=]() { + if constexpr (dim == 2) + return 1; + if constexpr (dim == 3) + return total_block_sizes[kalypsso::IZ]; + }(); + +#endif + + using DataArrayLeaf_t = kalypsso::DataArrayLeaf; + + auto fakeData_leaf = DataArrayLeaf_t("fakeData_leaf", numOcts, 5); + auto fakeData_leaf_h = Kokkos::create_mirror(fakeData_leaf); + + // model + Hydro::Settings settings{ dim }; + Hydro model(settings); + + // build list of variables to write by parsing config field "output/write_variables" + // list is actually a map of names to enum + kalypsso::names2index_t names2index; // this is initially empty + kalypsso::build_var_to_write_map(names2index, model.get_names2id_map(), config_map); + + auto fm = model.get_fieldmap(); + const auto id2names = model.get_id2names_map(); + + // fill fakeData on host +#if defined(KALYPSSO_IO_HDF5_BLOCK) + for (locidx_t iOct = 0; iOct < numOcts; ++iOct) + { + // global index + gloidx_t iOct_g = iOct + forest->global_first_quadrant[forest->mpirank]; + + double * ptr = (double *)sc_array_index(scalar_data, iOct); + for (uint32_t icell = 0; icell < nbCells; ++icell) + { + fakeData_block_h(icell, fm[Hydro::ID], iOct) = ptr[0]; + fakeData_block_h(icell, fm[Hydro::IE], iOct) = 42.0; + fakeData_block_h(icell, fm[Hydro::IU], iOct) = 1.0 * iOct_g + icell * 1.0 / nbCells; + fakeData_block_h(icell, fm[Hydro::IV], iOct) = 2.0; + } + } +#elif defined(KALYPSSO_IO_HDF5_GHOSTED_BLOCK) + for (locidx_t iOct = 0; iOct < numOcts; ++iOct) + { + // global index + gloidx_t iOct_g = iOct + forest->global_first_quadrant[forest->mpirank]; + + double * ptr = (double *)sc_array_index(scalar_data, iOct); + + const auto shift = fakeData_ghosted_block.shift(); + const auto ghosted_size = fakeData_ghosted_block.ghosted_block_size(); + + if constexpr (dim == 2) + { + for (int32_t j = shift[kalypsso::IY]; j < shift[kalypsso::IY] + ghosted_size[kalypsso::IY]; + ++j) + for (int32_t i = shift[kalypsso::IX]; i < shift[kalypsso::IX] + ghosted_size[kalypsso::IX]; + ++i) + { + if (i >= 0 and i < bx and j >= 0 and j < by) + { + auto icell = (i + gx) + bx * (j + gy); + fakeData_ghosted_block_h(i, j, fm[Hydro::ID], iOct) = ptr[0]; + fakeData_ghosted_block_h(i, j, fm[Hydro::IE], iOct) = 42.0; + fakeData_ghosted_block_h(i, j, fm[Hydro::IU], iOct) = + 1.0 * iOct_g + icell * 1.0 / nbCells; + fakeData_ghosted_block_h(i, j, fm[Hydro::IV], iOct) = 2.0; + } + else + { + fakeData_ghosted_block_h(i, j, fm[Hydro::ID], iOct) = 701.0; + fakeData_ghosted_block_h(i, j, fm[Hydro::IE], iOct) = 1.0; + fakeData_ghosted_block_h(i, j, fm[Hydro::IU], iOct) = 2.0; + fakeData_ghosted_block_h(i, j, fm[Hydro::IV], iOct) = 3.0; + } + } + } + if constexpr (dim == 3) + { + for (int32_t k = shift[kalypsso::IZ]; k < shift[kalypsso::IZ] + ghosted_size[kalypsso::IZ]; + ++k) + for (int32_t j = shift[kalypsso::IY]; j < shift[kalypsso::IY] + ghosted_size[kalypsso::IY]; + ++j) + for (int32_t i = shift[kalypsso::IX]; + i < shift[kalypsso::IX] + ghosted_size[kalypsso::IX]; + ++i) + { + if (i >= 0 and i < bx and j >= 0 and j < by and k >= 0 and k < bz) + { + auto icell = (i + gx) + bx * (j + gy) + bx * by * (k + gz); + fakeData_ghosted_block_h(i, j, k, fm[Hydro::ID], iOct) = ptr[0]; + fakeData_ghosted_block_h(i, j, k, fm[Hydro::IE], iOct) = 42.0; + fakeData_ghosted_block_h(i, j, k, fm[Hydro::IU], iOct) = + 1.0 * iOct_g + icell * 1.0 / nbCells; + fakeData_ghosted_block_h(i, j, k, fm[Hydro::IV], iOct) = 2.0; + fakeData_ghosted_block_h(i, j, k, fm[Hydro::IW], iOct) = 3.0; + } + else + { + fakeData_ghosted_block_h(i, j, k, fm[Hydro::ID], iOct) = 0.0; + fakeData_ghosted_block_h(i, j, k, fm[Hydro::IE], iOct) = 1.0; + fakeData_ghosted_block_h(i, j, k, fm[Hydro::IU], iOct) = 2.0; + fakeData_ghosted_block_h(i, j, k, fm[Hydro::IV], iOct) = 3.0; + fakeData_ghosted_block_h(i, j, k, fm[Hydro::IW], iOct) = 4.0; + } + } + } + } + Kokkos::deep_copy(fakeData_ghosted_block.flat_view(), fakeData_ghosted_block_h.flat_view()); +#endif + + for (locidx_t i = 0; i < numOcts; ++i) + { + double * ptr = (double *)sc_array_index(scalar_data, i); + fakeData_leaf_h(i, fm[Hydro::ID]) = ptr[0]; + fakeData_leaf_h(i, fm[Hydro::IE]) = 42.0; + fakeData_leaf_h(i, fm[Hydro::IU]) = 1.0 * i; + fakeData_leaf_h(i, fm[Hydro::IV]) = 2.0; + } + + // and copy to device + // usually, it is the other way around + Kokkos::deep_copy(fakeData_leaf, fakeData_leaf_h); + +#if defined(KALYPSSO_IO_HDF5_BLOCK) + Kokkos::deep_copy(fakeData_block.logical_view(), fakeData_block_h.logical_view()); +#endif + +#if defined(KALYPSSO_IO_HDF5_BLOCK) || defined(KALYPSSO_IO_HDF5_GHOSTED_BLOCK) + + // outside quad info is only valid for brick connectivity, and really useful + // only if brick connectivity is not periodic + kalypsso::OutsideQuadsInfo outside_quads_info; + if (connectivity_name == "brick") + { + auto brick_sizes = kalypsso::get_brick_sizes(config_map); + + Kokkos::Array is_brick_periodic; + is_brick_periodic[kalypsso::IX] = false; + is_brick_periodic[kalypsso::IY] = false; + if constexpr (dim == 3) + is_brick_periodic[kalypsso::IZ] = false; + + is_brick_periodic[kalypsso::IX] = static_cast( + config_map.getInteger("p4est_connectivity", "periodic_x", CONNECTIVITY_PERIODIC_FALSE)); + is_brick_periodic[kalypsso::IY] = static_cast( + config_map.getInteger("p4est_connectivity", "periodic_y", CONNECTIVITY_PERIODIC_FALSE)); + if (dim == 3) + is_brick_periodic[kalypsso::IZ] = static_cast( + config_map.getInteger("p4est_connectivity", "periodic_z", CONNECTIVITY_PERIODIC_FALSE)); + + outside_quads_info.update(forest, nullptr, par_env, is_brick_periodic, brick_sizes); + } + + const auto start_index = [=]() { + if constexpr (dim == 2) + return kalypsso::coord_t<2>{ 0, 0 }; + if constexpr (dim == 3) + return kalypsso::coord_t<3>{ 0, 0, 0 }; + }(); + + // don't write mesh info at cell level (only at block level) + kalypsso::HDF5_Xdmf_Writer_legacy writer( + forest, geom, config_map, block_sizes, start_index); + + std::string outputDir = config_map.getString("output", "outputDir", "./"); + +# if defined(KALYPSSO_IO_HDF5_GHOSTED_BLOCK) + // start index is opposite of shift + const auto start_index2 = [=]() { + if constexpr (dim == 2) + return kalypsso::coord_t<2>{ gx, gy }; + if constexpr (dim == 3) + return kalypsso::coord_t<3>{ gx, gy, gz }; + }(); + writer.set_block_mode(block_sizes, start_index2); +# endif + + writer.set_write_mesh_info(true); + writer.update_mesh_info(); + writer.open(filename, outputDir); + writer.write_header(0.0); + + // write user the fake data (all scalar fields, here only one) +# if defined(KALYPSSO_IO_HDF5_BLOCK) + writer.write_quadrant_attribute(fakeData_block_h, fm[Hydro::ID], id2names.at(Hydro::ID)); + writer.write_quadrant_attribute(fakeData_block_h, fm[Hydro::IE], id2names.at(Hydro::IE)); + writer.write_quadrant_attribute(fakeData_block_h, fm[Hydro::IU], id2names.at(Hydro::IU)); + writer.write_quadrant_attribute(fakeData_block_h, fm[Hydro::IV], id2names.at(Hydro::IV)); +# elif defined(KALYPSSO_IO_HDF5_GHOSTED_BLOCK) + Kokkos::deep_copy(fakeData_ghosted_block_h.flat_view(), fakeData_ghosted_block.flat_view()); + writer.write_quadrant_attribute( + fakeData_ghosted_block_h.data(), fm[Hydro::ID], id2names.at(Hydro::ID)); + writer.write_quadrant_attribute( + fakeData_ghosted_block_h.data(), fm[Hydro::IE], id2names.at(Hydro::IE)); + writer.write_quadrant_attribute( + fakeData_ghosted_block_h.data(), fm[Hydro::IU], id2names.at(Hydro::IU)); + writer.write_quadrant_attribute( + fakeData_ghosted_block_h.data(), fm[Hydro::IV], id2names.at(Hydro::IV)); +# endif + + // check if we want to write velocity or rhoV vector fields + // std::string write_variables = config_map.getString("output", "write_variables", ""); + + // close the file + writer.write_footer(); + writer.close(); + + // + // now write leaf data + // + std::string filename2 = filename + "_quad"; + + writer.set_leaf_mode(); + writer.set_write_mesh_info(true); + + // writer2.update_mesh_info(); + writer.open(filename2, outputDir); + writer.write_header(0.0); + + writer.write_quadrant_attribute(fakeData_leaf_h, fm[Hydro::ID], id2names.at(Hydro::ID)); + + // close the file + writer.write_footer(); + writer.close(); + +#endif + + sc_array_destroy(scalar_data); + +} // my_p4est_write_file + +template +using connectivity_data_t = + std::tuple::connectivity_t *, std::string>; + +/* ================================================================= */ +/* ================================================================= */ +/* ================================================================= */ +template +connectivity_data_t +create_connectivity(int brick_dim[3]); + +template <> +connectivity_data_t<2> +create_connectivity<2>(int brick_dim[3]) +{ + + return { p4est_connectivity_new_brick(brick_dim[0], brick_dim[1], 1, 1), "brick" }; + +} // create_connectivity<2> + +template <> +connectivity_data_t<3> +create_connectivity<3>(int brick_dim[3]) +{ + + return { p8est_connectivity_new_brick(brick_dim[0], brick_dim[1], brick_dim[2], 1, 1, 1), + "brick" }; + +} // create_connectivity<3> + +/* ============================================================ */ +/* ============================================================ */ +/* ============================================================ */ +template +void +run_test(const kalypsso::ParallelEnv & par_env, int argc, char * argv[]) +{ + + if (dim == 2 and par_env.rank() == 0) + printf("Running a 2D test\n"); + if (dim == 3 and par_env.rank() == 0) + printf("Running a 3D test\n"); + + using namespace kalypsso::p4est; + + typename Wrapper::forest_t * forest; + typename Wrapper::geometry_t * geom = nullptr; + typename Wrapper::refine_cb_t refine_fn; + typename Wrapper::coarsen_cb_t coarsen_fn; + + // only meaningful when using brick connectivity + int brick_dim[3]; + brick_dim[0] = argc > 2 ? atoi(argv[2]) : 2; + brick_dim[1] = argc > 3 ? atoi(argv[3]) : 3; + brick_dim[2] = argc > 4 ? atoi(argv[4]) : 4; + + // create connectivity and forest structures + auto [connectivity, connectivity_name] = create_connectivity(brick_dim); + + geom = nullptr; + + // assign refine_fn, coarsen_fn + refine_fn = refine_normal_fn; + coarsen_fn = nullptr; + + /* + * set geometry as p4est user pointer so that we can + * retrieve it in the callbacks. + */ + forest = Wrapper::new_forest( +#ifdef KALYPSSO_CORE_USE_MPI + par_env.mpi_comm(), +#else + sc_MPI_COMM_WORLD, +#endif // KALYPSSO_CORE_USE_MPI + connectivity, + dim == 2 ? 4 : 4, + 0, + 0, + sizeof(user_data_t), + init_fn, + geom); + + double scale = 1.0; + +#if defined(KALYPSSO_IO_HDF5_BLOCK) + my_p4est_write_file( + par_env, forest, connectivity_name, brick_dim, geom, "test_new_block", scale); +#elif defined(KALYPSSO_IO_HDF5_GHOSTED_BLOCK) + my_p4est_write_file( + par_env, forest, connectivity_name, brick_dim, geom, "test_new_block_ghosted", scale); +#else + my_p4est_write_file( + par_env, forest, connectivity_name, brick_dim, geom, "test_new", scale); +#endif + + // refinement and coarsening + Wrapper::refine(forest, 1, refine_fn, init_fn); + if (coarsen_fn != nullptr) + { + Wrapper::coarsen(forest, 1, coarsen_fn, init_fn); + } +#if defined(KALYPSSO_IO_HDF5_BLOCK) + my_p4est_write_file( + par_env, forest, connectivity_name, brick_dim, geom, "test_refined_block", scale); +#elif defined(KALYPSSO_IO_HDF5_GHOSTED_BLOCK) + my_p4est_write_file( + par_env, forest, connectivity_name, brick_dim, geom, "test_refined_block_ghosted", scale); +#else + my_p4est_write_file( + par_env, forest, connectivity_name, brick_dim, geom, "test_refined", scale); +#endif + + // balance + Wrapper::balance(forest, Wrapper::CONNECT_FULL, init_fn); +#if defined(KALYPSSO_IO_HDF5_BLOCK) + my_p4est_write_file( + par_env, forest, connectivity_name, brick_dim, geom, "test_balanced_block", scale); +#elif defined(KALYPSSO_IO_HDF5_GHOSTED_BLOCK) + my_p4est_write_file( + par_env, forest, connectivity_name, brick_dim, geom, "test_balanced_block_ghosted", scale); +#else + my_p4est_write_file( + par_env, forest, connectivity_name, brick_dim, geom, "test_balanced", scale); +#endif + + [[maybe_unused]] auto crc = Wrapper::checksum(forest); + + // partition + Wrapper::partition(forest, 0, nullptr); +#if defined(KALYPSSO_IO_HDF5_BLOCK) + my_p4est_write_file( + par_env, forest, connectivity_name, brick_dim, geom, "test_partition_block", scale); +#elif defined(KALYPSSO_IO_HDF5_GHOSTED_BLOCK) + my_p4est_write_file( + par_env, forest, connectivity_name, brick_dim, geom, "test_partition_block_ghosted", scale); +#else + my_p4est_write_file( + par_env, forest, connectivity_name, brick_dim, geom, "test_partition", scale); +#endif + +#ifdef P4EST_ENABLE_DEBUG + // rebalance should not change checksum + Wrapper::balance(forest, Wrapper::CONNECT_FULL, init_fn); + P4EST_ASSERT(Wrapper::checksum(forest) == crc); +#endif + + // destroy the p4est and its connectivity structure + Wrapper::destroy(forest); + if (geom != nullptr) + { + Wrapper::geometry_destroy(geom); + } + Wrapper::connectivity_destroy(connectivity); + +} // run_test + +// ====================================================== +// ====================================================== +// ====================================================== +int +main(int argc, char ** argv) +{ + + int wrongusage; + const char * usage; + + { + kalypsso::ParallelEnv par_env(argc, argv); + + // Process command line arguments + usage = "Arguments: \n" + " Level controls the maximum depth of refinement\n" + " Brick sizes (parameter of p4est brick connectivity)\n" + "\n" + "Example run:\n" + " mpirun -np 6 ./test_io_brick_block 5 5 3\n"; + wrongusage = 0; + if (!wrongusage && argc < 2) + { + P4EST_GLOBAL_LERROR(usage); + sc_abort_collective("Usage error"); + } + + // assign variables based on configuration + refine_level = atoi(argv[1]); + + // run some tests + if (argc == 4) + run_test<2, kalypsso::DefaultDevice>(par_env, argc, argv); + else if (argc == 5) + run_test<3, kalypsso::DefaultDevice>(par_env, argc, argv); + else + printf("Can't run test; wrong number of arguments\n"); + } + + return EXIT_SUCCESS; +} diff --git a/test/io/test_io_hdf5.ini b/test/io/test_io_hdf5.ini new file mode 100644 index 0000000..558ff2d --- /dev/null +++ b/test/io/test_io_hdf5.ini @@ -0,0 +1,58 @@ +[run] +dimension=2 +tEnd=0.5 +nStepmax=10 + +# noutput equals -1 means we dump data at every time steps +nOutput=-1 + +[amr] +level_min=4 +level_max=6 + +# init step may contain calls to globalRefine +# if the following option is true, we interleave +# calls to load balancing in between globalRefine +# if the following option is false, a single load +# balancing operation is done at the end of +# globalRefine steps +enable_load_balance_during_init=false + +epsilon_refine=0.01 +epsilon_coarsen=0.03 + +[mesh] +xmin=0.0 +ymin=0.0 +scaling_factor=1.0 + +boundary_type_xmin=WALL +boundary_type_xmax=WALL + +boundary_type_ymin=WALL +boundary_type_ymax=WALL + +[hydro] +gamma0=1.666 +cfl=0.8 +niter_riemann=10 +slope_type=2 +problem=blast +riemann=HLLC + +[blast] +density_in=1.0 +density_out=1.2 +radius=0.25 + +[output] +outputDir=./ +outputPrefix=test_hdf5 +outputVtkAscii=false +write_variables=rho,rho_vx +write_mesh_info=yes +vtk_enabled=no +hdf5_enabled=yes + +[other] +implementationVersion=0 diff --git a/test/io/test_io_hdf5_block.ini b/test/io/test_io_hdf5_block.ini new file mode 100644 index 0000000..4957ee7 --- /dev/null +++ b/test/io/test_io_hdf5_block.ini @@ -0,0 +1,63 @@ +[run] +dimension=2 +tEnd=0.5 +nStepmax=10 + +# noutput equals -1 means we dump data at every time steps +nOutput=-1 + +[amr] +level_min=4 +level_max=6 + +# init step may contain calls to globalRefine +# if the following option is true, we interleave +# calls to load balancing in between globalRefine +# if the following option is false, a single load +# balancing operation is done at the end of +# globalRefine steps +enable_load_balance_during_init=false + +epsilon_refine=0.01 +epsilon_coarsen=0.03 + +use_block_data=yes +bx=4 +by=4 +bz=4 + +[mesh] +xmin=0.0 +ymin=0.0 +scaling_factor=1.0 + +boundary_type_xmin=WALL +boundary_type_xmax=WALL + +boundary_type_ymin=WALL +boundary_type_ymax=WALL + +[hydro] +gamma0=1.666 +cfl=0.8 +niter_riemann=10 +slope_type=2 +problem=blast +riemann=HLLC + +[blast] +density_in=1.0 +density_out=1.2 +radius=0.25 + +[output] +outputDir=./ +outputPrefix=test_hdf5_block +outputVtkAscii=false +write_variables=rho,rho_vx +write_mesh_info=yes +vtk_enabled=no +hdf5_enabled=yes + +[other] +implementationVersion=0 diff --git a/test/io/test_io_vtk.ini b/test/io/test_io_vtk.ini new file mode 100644 index 0000000..455df36 --- /dev/null +++ b/test/io/test_io_vtk.ini @@ -0,0 +1,58 @@ +[run] +dimension=2 +tEnd=0.5 +nStepmax=10 + +# noutput equals -1 means we dump data at every time steps +nOutput=-1 + +[amr] +level_min=4 +level_max=6 + +# init step may contain calls to globalRefine +# if the following option is true, we interleave +# calls to load balancing in between globalRefine +# if the following option is false, a single load +# balancing operation is done at the end of +# globalRefine steps +enable_load_balance_during_init=false + +epsilon_refine=0.01 +epsilon_coarsen=0.03 + +[mesh] +xmin=0.0 +ymin=0.0 +scaling_factor=1.0 + +boundary_type_xmin=WALL +boundary_type_xmax=WALL + +boundary_type_ymin=WALL +boundary_type_ymax=WALL + +[hydro] +gamma0=1.666 +cfl=0.8 +niter_riemann=10 +slope_type=2 +problem=blast +riemann=HLLC + +[blast] +density_in=1.0 +density_out=1.2 +radius=0.25 + +[output] +outputDir=./ +outputPrefix=test_vtk +outputVtkAscii=false +write_variables=rho,rho_vx +write_mesh_info=yes +vtk_enabled=yes +hdf5_enabled=no + +[other] +implementationVersion=0 diff --git a/test/kokkos/CMakeLists.txt b/test/kokkos/CMakeLists.txt new file mode 100644 index 0000000..d8c0b22 --- /dev/null +++ b/test/kokkos/CMakeLists.txt @@ -0,0 +1,122 @@ +# +# Kokkos tests / examples +# + +add_executable(query_device_kokkos query_device_kokkos.cpp) +target_link_libraries(query_device_kokkos PUBLIC Kokkos::kokkos kalypsso::core_config) +if(KALYPSSO_CORE_USE_MPI) + target_link_libraries(query_device_kokkos PUBLIC MPI::MPI_CXX) +endif(KALYPSSO_CORE_USE_MPI) +add_test(NAME query_device_kokkos COMMAND query_device_kokkos) +set_property(TEST query_device_kokkos PROPERTY LABELS kokkos) +if(Kokkos_ENABLE_OPENMP) + set_property( + TEST query_device_kokkos PROPERTY ENVIRONMENT OMP_NUM_THREADS=${KALYPSSO_TEST_OMP_NUM_THREADS} + OMP_PROC_BIND=spread OMP_PLACES=threads) +endif() + +add_executable(test_kokkos_teamvector test_kokkos_teamvector.cpp) +target_link_libraries(test_kokkos_teamvector PUBLIC kalypsso::core Kokkos::kokkos) +if(KALYPSSO_CORE_USE_MPI) + target_link_libraries(test_kokkos_teamvector PUBLIC kalypsso::mpiUtils MPI::MPI_CXX) +endif(KALYPSSO_CORE_USE_MPI) +add_test(NAME kokkos_teamvector COMMAND test_kokkos_teamvector) +set_property(TEST kokkos_teamvector PROPERTY LABELS kokkos) +if(Kokkos_ENABLE_OPENMP) + set_property( + TEST kokkos_teamvector PROPERTY ENVIRONMENT OMP_NUM_THREADS=${KALYPSSO_TEST_OMP_NUM_THREADS} + OMP_PROC_BIND=spread OMP_PLACES=threads) +endif() + +add_executable(test_kokkos_mdrange test_kokkos_mdrange.cpp) +target_link_libraries(test_kokkos_mdrange PUBLIC kalypsso::core Kokkos::kokkos) +if(KALYPSSO_CORE_USE_MPI) + target_link_libraries(test_kokkos_mdrange PUBLIC kalypsso::mpiUtils MPI::MPI_CXX) +endif(KALYPSSO_CORE_USE_MPI) +add_test(NAME kokkos_mdrange COMMAND test_kokkos_mdrange) +set_property(TEST kokkos_mdrange PROPERTY LABELS kokkos) +if(Kokkos_ENABLE_OPENMP) + set_property( + TEST kokkos_mdrange PROPERTY ENVIRONMENT OMP_NUM_THREADS=${KALYPSSO_TEST_OMP_NUM_THREADS} + OMP_PROC_BIND=spread OMP_PLACES=threads) +endif() + +add_executable(test_kokkos_team_mdrange test_kokkos_team_mdrange.cpp) +target_link_libraries(test_kokkos_team_mdrange PUBLIC kalypsso::core Kokkos::kokkos) +if(KALYPSSO_CORE_USE_MPI) + target_link_libraries(test_kokkos_team_mdrange PUBLIC kalypsso::mpiUtils MPI::MPI_CXX) +endif(KALYPSSO_CORE_USE_MPI) +add_test(NAME kokkos_team_mdrange COMMAND test_kokkos_team_mdrange) +set_property(TEST kokkos_team_mdrange PROPERTY LABELS kokkos) +if(Kokkos_ENABLE_OPENMP) + set_property( + TEST kokkos_team_mdrange PROPERTY ENVIRONMENT OMP_NUM_THREADS=${KALYPSSO_TEST_OMP_NUM_THREADS} + OMP_PROC_BIND=spread OMP_PLACES=threads) +endif() + +# ################################################################################################## +add_executable(test_unordered_map test_unordered_map.cpp) +target_link_libraries(test_unordered_map PUBLIC Kokkos::kokkos kalypsso::core_config) +add_test(NAME unordered_map COMMAND test_unordered_map) +set_property(TEST unordered_map PROPERTY LABELS kokkos) +if(Kokkos_ENABLE_OPENMP) + set_property( + TEST unordered_map PROPERTY ENVIRONMENT OMP_NUM_THREADS=${KALYPSSO_TEST_OMP_NUM_THREADS} + OMP_PROC_BIND=spread OMP_PLACES=threads) +endif() + +# ################################################################################################## +add_executable(test_morton_key test_morton_key.cpp) +target_link_libraries(test_morton_key PUBLIC Kokkos::kokkos kalypsso::core_config) +add_test(NAME morton_key COMMAND test_morton_key) +set_property(TEST morton_key PROPERTY LABELS kokkos) +if(Kokkos_ENABLE_OPENMP) + set_property( + TEST morton_key PROPERTY ENVIRONMENT OMP_NUM_THREADS=${KALYPSSO_TEST_OMP_NUM_THREADS} + OMP_PROC_BIND=spread OMP_PLACES=threads) +endif() + +# ################################################################################################## +if(KALYPSSO_CORE_USE_DOUBLE) + add_executable(compute_mandelbrot_amr compute_mandelbrot_amr.cpp) + target_link_libraries(compute_mandelbrot_amr PUBLIC Kokkos::kokkos kalypsso::io kalypsso::core + kalypsso::core_config) + + # cmake-format: off +# if(KALYPSSO_CORE_USE_MPI) +# target_link_libraries(compute_mandelbrot_amr PUBLIC +# kalypsso::mpiUtils) +# endif(KALYPSSO_CORE_USE_MPI) +# add_test(NAME compute_mandelbrot_amr +# COMMAND compute_mandelbrot_amr) +# set_property(TEST compute_mandelbrot_amr +# PROPERTY LABELS kokkos) +# if(Kokkos_ENABLE_OPENMP) +# set_property(TEST compute_mandelbrot_amr PROPERTY ENVIRONMENT +# OMP_NUM_THREADS=${KALYPSSO_TEST_OMP_NUM_THREADS} OMP_PROC_BIND=spread +# OMP_PLACES=threads) +# endif() +# cmake-format: on +endif() + +# ################################################################################################## +add_executable(test_parallel_scan test_parallel_scan.cpp) +target_link_libraries(test_parallel_scan PUBLIC Kokkos::kokkos kalypsso::core) + +if(KALYPSSO_CORE_USE_MPI) + target_link_libraries(test_parallel_scan PUBLIC kalypsso::mpiUtils) +endif(KALYPSSO_CORE_USE_MPI) + +add_test(NAME parallel_scan COMMAND test_parallel_scan) +set_property(TEST parallel_scan PROPERTY LABELS kokkos) +if(Kokkos_ENABLE_OPENMP) + set_property( + TEST parallel_scan PROPERTY ENVIRONMENT OMP_NUM_THREADS=${KALYPSSO_TEST_OMP_NUM_THREADS} + OMP_PROC_BIND=spread OMP_PLACES=threads) +endif() + +# ################################################################################################## +if(KALYPSSO_CORE_USE_MPI) + add_executable(test_kokkos_mpi test_kokkos_mpi.cpp) + target_link_libraries(test_kokkos_mpi PUBLIC Kokkos::kokkos kalypsso::core kalypsso::mpiUtils) +endif(KALYPSSO_CORE_USE_MPI) diff --git a/test/kokkos/amr_key.h b/test/kokkos/amr_key.h new file mode 100644 index 0000000..dd8d0d5 --- /dev/null +++ b/test/kokkos/amr_key.h @@ -0,0 +1,235 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * Design a "key" type to be used as entry to our adaptive mesh refinement + * hash-table data structure. + * + * Let's try two types of key: + * - morton index + tree + * - morton index + level + tree + * + * The first type should be useful to store all the leaves of tree. + * The second type could be useful to store additionally level ghost cells. + * Indeed this allows to have cells of different sizes covering the same + * location (same x,y,z but different levels). + */ +#ifndef KALYPSSO_TEST_SHARED_KEY_TYPE_H +#define KALYPSSO_TEST_SHARED_KEY_TYPE_H + +#include + +namespace kalypsso +{ + +/** + * Cell status. + * + * - "ghost level cells" are inserted in hashmap, to make all regular cell have + * a regular neighborhood; they inserted by interpolated the payload data. + * + * - "ghost external cell" hold the border condition; it inserted in the + * hashmap when visiting a cell that "touches the external border" and that + * hold the very same Morton key, same level, only the payload data is different + * + */ +enum CellStatus +{ + CELL_INVALID = 0, + CELL_REGULAR = 1, /*!< a regular cell */ + CELL_GHOST_LEVEL = 2, /*!< a cell inserted in hashmap to make neighbor cell surrounded by cells + that have the same level */ + CELL_GHOST_MPI = 3, /*!< an MPI ghost cell */ + CELL_GHOST_EXTERNAL = 4, /*!< a cell outside the external domain */ + CELL_TO_BE_REMOVED = 5, /*!< this cell should be removed soon */ + CELL_UNINITIALIZED = 6 /*!< this cell should be initialed soon */ +}; + +/** + * Define the key type as entry to our Kokkos::UnorderedMap data structure. + * + * keys[0] holds the morton index computed inside the tree the cell belongs to. + * keys[1] holds additional information: + * - the last 8 bits encode the level (only 5 bits is required, + * since in 2D MAXLEVEL is 29). + * - the next 16 LSB encode the tree id; so only 2^16=65536 trees are allowed + * for now, but it should sufficient for most application + * - the remaining bits are unused + * + * Just for clarification, this key type is not a morton key since here + * cell level is encoded. + * + * \sa amr_key_simple_t + */ +struct amr_key_t +{ + + /** bit mask to extract LEVEL (the last 8 bits) */ + static constexpr uint64_t LEVEL_MASK = 0x00000000000000FF; + + /** bit mask to extract TREEID (16 bits) */ + static constexpr uint64_t TREE_ID_MASK = 0x0000000000FFFF00; + + /** bit mask to extract CELL_STATUS */ + // static constexpr uint64_t CELL_STATUS_MASK = 0x000000000F000000; + + uint64_t keys[2]; + + KOKKOS_INLINE_FUNCTION + amr_key_t() + : keys{ 0, 0 } + {} + + KOKKOS_INLINE_FUNCTION + amr_key_t(uint64_t key1, uint64_t key2) + : keys{ key1, key2 } + {} + + KOKKOS_INLINE_FUNCTION + uint64_t + operator[](size_t i) const + { + return keys[i]; + } + + KOKKOS_INLINE_FUNCTION + uint64_t & + operator[](size_t i) + { + return keys[i]; + } + + /** extract Morton key */ + KOKKOS_INLINE_FUNCTION + uint64_t + get_morton() + { + return keys[0]; + } // get_morton + + /** extract level */ + KOKKOS_INLINE_FUNCTION + uint8_t + get_level() + { + return (keys[1] & LEVEL_MASK); + } // get_level + /** extract treeId */ + KOKKOS_INLINE_FUNCTION + uint16_t + get_treeId() + { + return (keys[1] & TREE_ID_MASK) >> 8; + } // get_treeid + + KOKKOS_INLINE_FUNCTION + bool + operator<(const amr_key_t & other) const + { + + return (keys[1] < other.keys[1]) or (keys[1] == other.keys[1] and keys[0] < other.keys[0]); + } + KOKKOS_INLINE_FUNCTION + bool + operator==(const amr_key_t & other) const + { + + return (keys[1] == other.keys[1]) and (keys[0] == other.keys[0]); + } +}; // struct amr_key_t + +/** encode level and treeId in an uint64_t integer */ +KOKKOS_INLINE_FUNCTION +static uint64_t +encode_level_tree(int level, int treeId) +{ + uint64_t res = 0; + + // first 8 bits for level + res = (level & amr_key_t::LEVEL_MASK); + + // then 16 bits for treeId + res = res | ((treeId << 8) & amr_key_t::TREE_ID_MASK); + + return res; + +} // encode_level_tree + + +/** + * Define the Morton key type encoding the local tree morton index with tree id. + * + * keys[0] holds the morton index computed inside the tree the cell belongs to. + * keys[1] holds additional information: + * - the last 16 least significant bits encode the tree id; + * so only 2^16=65536 trees are allowed + * for now, but it should sufficient for most application + * - the remaining bits are unused + */ +struct morton_key_t +{ + /** bit mask to extract TREEID (the last 16 bits) */ + static constexpr uint64_t TREE_ID_MASK = 0x000000000000FFFF; + + uint64_t keys[2]; + + KOKKOS_INLINE_FUNCTION + morton_key_t() + : keys{ 0, 0 } + {} + + KOKKOS_INLINE_FUNCTION + morton_key_t(uint64_t key1, uint64_t key2) + : keys{ key1, key2 } + {} + + KOKKOS_INLINE_FUNCTION + uint64_t + operator[](size_t i) const + { + return keys[i]; + } + KOKKOS_INLINE_FUNCTION + uint64_t & + operator[](size_t i) + { + return keys[i]; + } + + + /** extract Morton key */ + KOKKOS_INLINE_FUNCTION + uint64_t + get_morton() + { + return keys[0]; + } // get_morton + + /** extract treeId */ + KOKKOS_INLINE_FUNCTION + uint16_t + get_treeId() + { + return (keys[1] & TREE_ID_MASK); + } // get_treeid + +}; // struct morton_key_t + +/** encode treeId in an uint64_t integer */ +KOKKOS_INLINE_FUNCTION +static uint64_t +encode_tree(int treeId) +{ + uint64_t res = 0; + + // 16 bits for level + res = (treeId & morton_key_t::TREE_ID_MASK); + + return res; + +} // encode_tree + +} // namespace kalypsso + +#endif // KALYPSSO_TEST_SHARED_KEY_TYPE_H diff --git a/test/kokkos/compute_mandelbrot_amr.cpp b/test/kokkos/compute_mandelbrot_amr.cpp new file mode 100644 index 0000000..baed451 --- /dev/null +++ b/test/kokkos/compute_mandelbrot_amr.cpp @@ -0,0 +1,1026 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * Compute the Mandelbrot set on an adaptive mesh. + * + * We do not try to seek for performance. + * Adaptive mesh is stored using an Kokkos::UnorderedMap class where + * inserted key,value pairs are made of: + * - key is actually the Morton key (using type morton_key_t) + * - value is a structure containing different fields. + * + * \sa test_unordered_map_io3.cpp + * + */ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + +// MPI support +#ifdef KALYPSSO_CORE_USE_MPI +# include +#endif // KALYPSSO_CORE_USE_MPI + +// VTK IO implementation +#include // for VTK_WRITE_ENUM class +#include + +namespace kalypsso +{ + +/** typedef Point holding coordinates of a point. */ +template +using Point = std::array; + + +enum index_status_t +{ + INDEX_UNINITIALIZED = -1 +}; + +/** + * Some parameters specific to Mandelbrot set computation + */ + +/// physical domain extent +static constexpr double xmin = -2.25; +static constexpr double xmax = 1.25; +static constexpr double ymin = -1.50; +static constexpr double ymax = 1.50; + +static constexpr double deltaX = xmax - xmin; +static constexpr double deltaY = ymax - ymin; + +/// rescale x coordinate from unit square to physical domain +KOKKOS_INLINE_FUNCTION +double +scaleX(double x) +{ + return xmin + deltaX * x; +} + +/// rescale y coordinate from unit square to physical domain +KOKKOS_INLINE_FUNCTION +double +scaleY(double y) +{ + return ymin + deltaY * y; +} + +/// refinement threshold +static constexpr double epsilon = 0.01; + +// ======================================================================== +// ======================================================================== +/** + * Compute number of iterations of \f$ z_{n+1} = z_n^2 + c \f$ starting with + * \f$ z_0=c\f$ to reach 4 in modulus. + */ +KOKKOS_INLINE_FUNCTION +double +compute_nb_iters(double cx, double cy) +{ + // maximum number of iterations + constexpr int NMAX = 512; + + // init number of iterations + int j = 0; + + double norm = (cx * cx + cy * cy); + double zx = cx; + double zy = cy; + double tmp; + + while (j <= NMAX and norm < 4) + { + tmp = (zx * zx) - (zy * zy) + cx; // Real part + zy = (2. * zx * zy) + cy; // Imag part + zx = tmp; + j++; + norm = (zx * zx + zy * zy); + } + + return (double)j; + +} // compute_nb_iters + +bool VERBOSE = false; + +/** + * Minimalist metadata to stored in hash table for AMR. + */ +struct metadata_t +{ + + //! genuine Morton key (space location + tree encoding) + morton_key_t key; + + //! address where to stored heavy data; optimally index ranges from 0 + //! to N-1, where N is the total number of keys, and index is "ordered" + //! by the morton index (total order with space locality preserving) + int64_t index; + + //! data + double data; + + //! metadata used to stored cell status (uninitialized, To_be_removed, ...) + CellStatus status; + + + KOKKOS_INLINE_FUNCTION + metadata_t() + : key() + , index(-1) + , data(0.0) + , status(CELL_INVALID) + {} + + KOKKOS_INLINE_FUNCTION + metadata_t(morton_key_t _key, int64_t _index, double _data, CellStatus _status) + : key(_key) + , index(_index) + , data(_data) + , status(_status) + {} + +}; // struct metadata_t + +template +struct MandelbrotConfig +{ + /** + * MandelbrotMap uses key = amr_key_t (morton + tree + level), this allows + * to have temporarily items with same location but different sizes + * (i.e. different levels) + * this happens e.g. while performing refine operation). + */ + using MandelbrotMap = typename Kokkos::UnorderedMap; +}; + + +// ========================================================================= +// ========================================================================= +/** + * Functor to compute a Kokkos::UnorderedMap size (number of valid entries). + * + * Not really needed, since the functionality already exists as + * UnorderedMap method named size(). + * + */ +template +struct ComputeMapSize : public MandelbrotConfig +{ + + using exec_space = typename device_t::execution_space; + using MandelbrotMap_t = typename MandelbrotConfig::MandelbrotMap; + MandelbrotMap_t mandelbrotMap; + + ComputeMapSize(MandelbrotMap_t mandelbrotMap) + : mandelbrotMap(mandelbrotMap) + {} + + static uint32_t + getSize(MandelbrotMap_t _mandelbrotMap) + { + uint32_t size = 0; + exec_space space; + ComputeMapSize functor(_mandelbrotMap); + Kokkos::parallel_reduce("ComputeMapSize", + Kokkos::RangePolicy(space, 0, _mandelbrotMap.capacity()), + functor, + size); + space.fence(); + return size; + } + + /* + * 2D and 3D versions. + */ + KOKKOS_INLINE_FUNCTION + void + operator()(const uint32_t & i, uint32_t & count) const + { + if (mandelbrotMap.valid_at(i)) + ++count; + + } // operator() - 2d/3d + + KOKKOS_INLINE_FUNCTION + void + init(uint32_t & update) const + { + update = 0; + } + + KOKKOS_INLINE_FUNCTION +#if KOKKOS_VERSION_MAJOR > 3 + void + join(uint32_t & update, const uint32_t & input) const + { + update += input; + } +#else + void + join(volatile uint32_t & update, volatile const uint32_t & input) const + { + update += input; + } +#endif + +}; // struct ComputeMapSize + +// ========================================================================= +// ========================================================================= +/** + * Functor to initialize a MandelbrotMap with + * a uniform coarse mesh. + * + * kind of bootstrap. + * + * Remind that + * - amr_key_t encodes space + level + tree + * - morton_key_t encodes space + tree + * Please note that they use very different encoding. + * + * TODO / Future version: update to take into account TreeId / connectivity. + */ +template +struct FillCoarseMap : public MandelbrotConfig +{ + + using exec_space = typename device_t::execution_space; + using MandelbrotMap_t = typename MandelbrotConfig::MandelbrotMap; + MandelbrotMap_t mandelbrotMap; + int level_min, level_max; + int N; // linear size : 2**level_min + + FillCoarseMap(MandelbrotMap_t mandelbrotMap, int level_min, int level_max) + : mandelbrotMap(mandelbrotMap) + , level_min(level_min) + , level_max(level_max) + , N(1 << level_min) + { + int size = N * N; + Kokkos::parallel_for("FillCoarseMap", Kokkos::RangePolicy(0, size), *this); + } + + /* + * 2D version. + */ + KOKKOS_INLINE_FUNCTION + void + operator()(const int & i) const + { + int iy = i / N; + int ix = i - iy * N; + + // in unit of the smallest resolution (for genuine morton key) + int mx = ix << (level_max - level_min); + int my = iy << (level_max - level_min); + + const int treeId = 0; + + int64_t key1a = compute_morton_key(ix, iy); + int64_t key1b = encode_level_tree(level_min, treeId); + int64_t key2a = compute_morton_key(mx, my); + int64_t key2b = encode_tree(0); + + amr_key_t key(key1a, key1b); + morton_key_t mkey(key2a, key2b); + + // compute Mandelbrot set "pixel value" + real_t dx = 1.0 / N; + real_t dy = 1.0 / N; + real_t x = scaleX((ix + 0.5) * dx); + real_t y = scaleY((iy + 0.5) * dy); + double data = compute_nb_iters(x, y); + + metadata_t value(mkey, INDEX_UNINITIALIZED, data, CELL_REGULAR); + + mandelbrotMap.insert(key, value); + + } // operator() + +}; // struct FillCoarseMap + +// ========================================================================= +// ========================================================================= +/** + * Functor to refine a MandelbrotMap. + * + * the refinement criterion is adapted from the Mariani-Silver Algorithm, i.e. + * for each each we compare the value at center with the average pixel of the + * four sub-cell; if they agree up to epsilon, we do not refine; if they + * are strongly different, it means we need to refine (so new cells are + * inserted, and parent cell is removed). + */ +template +struct Refine : public MandelbrotConfig +{ + + enum refine_mode_t + { + INSERT_REFINED_CELLS, + DELETE_OLD_CELLS, + RESET_STATUS + }; + + using exec_space = typename device_t::execution_space; + using MandelbrotMap_t = typename MandelbrotConfig::MandelbrotMap; + + /** + * a Kokkos::UnorderedMap where + * - key is morton+treeId + * - value is aggregate key + level + */ + MandelbrotMap_t mandelbrotMap; + + //! min and max refinement level + int level_min, level_max; + + /** + * refine mode: erase or insert; we can't erase and insert in an + * Unordered map at the same time + */ + refine_mode_t refine_mode; + + //! constructor + Refine(MandelbrotMap_t mandelbrotMap, int level_min, int level_max, refine_mode_t refine_mode) + : mandelbrotMap(mandelbrotMap) + , level_min(level_min) + , level_max(level_max) + , refine_mode(refine_mode) + {} + + /** + * \param[in,out] _mandelbrotMap is the hash table which is use to perform refinement + * \param[in] level_min + * \param[in] level_max + * + */ + static void + apply(MandelbrotMap_t _mandelbrotMap, int level_min, int level_max) + { + + // step 0 : create functor + Refine functor(_mandelbrotMap, level_min, level_max, Refine::INSERT_REFINED_CELLS); + + // for each level + int nbIter = level_max - level_min; + for (int iter = 0; iter < nbIter; ++iter) + { + + // monitoring map size + printf("[iter %d] Map size=%d (end)\n", iter, _mandelbrotMap.size()); + + // + // step1: + // - check if new refine cells need to be inserted, + // - insert them, + // - and mark old coarse cell for removal + // + functor.refine_mode = Refine::INSERT_REFINED_CELLS; + + // perform refinement + Kokkos::parallel_for("Refine::INSERT_REFINED_CELLS", + Kokkos::RangePolicy(0, _mandelbrotMap.capacity()), + functor); + + if (_mandelbrotMap.failed_insert()) + printf("====> Something went wrong in refinement operation (you probably need to resize up " + "the hash table)\n"); + + // + // step2: remove old coarse cells + // + + // check erasable state - should be false (default) + if (VERBOSE) + std::cout << "is data map erasable ? " << _mandelbrotMap.erasable() << "\n"; + + // _mandelbrotMap must be in erasable state + _mandelbrotMap.begin_erase(); + + // check erasable state again - should be true now + if (VERBOSE) + std::cout << "is data map erasable ? " << _mandelbrotMap.erasable() << "\n"; + + // perform refinement + functor.refine_mode = Refine::DELETE_OLD_CELLS; + Kokkos::parallel_for("Refine::DELETE_OLD_CELLS", + Kokkos::RangePolicy(0, _mandelbrotMap.capacity()), + functor); + + // actually perform the erase operations + _mandelbrotMap.end_erase(); + + // check erasable state again - should be back to false + if (VERBOSE) + std::cout << "is data map erasable ? " << _mandelbrotMap.erasable() << "\n"; + + // + // step3: reset cell status + // + // VERY strange mandelbrotMap.size() gives the wrong result ?! To be analyzed + // int map_size = _mandelbrotMap.size(); + int map_size = ComputeMapSize::getSize(_mandelbrotMap); + printf("[iter %d] Map size=%d (end)\n", iter, map_size); + functor.refine_mode = Refine::RESET_STATUS; + Kokkos::parallel_for("Refine::RESET_STATUS", + Kokkos::RangePolicy(0, _mandelbrotMap.capacity()), + functor); + + Kokkos::fence(); + + } // end for iter + + } // apply + + + KOKKOS_INLINE_FUNCTION + void + insert_refined_cells(const int & i) const + { + + // retrieve key,value pair + amr_key_t key = mandelbrotMap.key_at(i); + metadata_t value = mandelbrotMap.value_at(i); + + CellStatus status = value.status; + + /* + * Avoid uninitialized cells (these are newly inserted cell) + * and also for invalid cells (sometimes, don't know if it is a bug, + * newly inserted cells have wrong data, i.e. status is zero + * which is exactly CELL_INVALID !?) + */ + if (status != CELL_UNINITIALIZED and status != CELL_INVALID) + { + + uint8_t level = key.get_level(); + uint16_t treeId = key.get_treeId(); + + uint64_t morton = key.get_morton(); + + // decode morton index + // ix and iy should lie in [0, 2**level_max-1] + uint32_t ix = morton_extract_bits<2, IX>(morton); + uint32_t iy = morton_extract_bits<2, IY>(morton); + + int N = 1 << level; + real_t dx = 1.0 / N; + real_t dy = 1.0 / N; + + // compute cell-center x,y coordinates in real space [0,1]^2 + real_t xc = (ix + 0.5) * dx; + real_t yc = (iy + 0.5) * dy; + real_t x = scaleX(xc); + real_t y = scaleY(yc); + + // determine if refinement is needed + // compute nb iter at cell center + double datac = compute_nb_iters(x, y); + + // compute nb iter at sub-cell centers + double data[4], average = 0.0; + for (int index = 0; index < 4; ++index) + { + + int di = 2 * (index & 0x1) - 1; + int dj = 2 * ((index >> 1) & 0x1) - 1; + x = scaleX(xc + di * 0.25 * dx); + y = scaleY(yc + dj * 0.25 * dy); + data[index] = compute_nb_iters(x, y); + average += data[index]; + } + average /= 4; + + bool refinement_needed = false; + if (datac / average > 1 + epsilon or datac / average < 1 - epsilon) + refinement_needed = true; + + if (refinement_needed) + { + // refinement is need + // create/insert new cells at fine level with metadata + + // build the new child (keys, values) and insert + + // if(level==5) + // printf("HOUSTON %d %d %ld -- %ld %d %d\n",i,value.status,value.index, + // value.key[0], + // morton_extract_bits(value.key[0]), + // morton_extract_bits(value.key[0]) ); + + { + int ixx = ix << 1; + int iyy = iy << 1; + amr_key_t ckey(compute_morton_key(ixx, iyy), encode_level_tree(level + 1, treeId)); + + int mx = ixx << (level_max - level - 1); + int my = iyy << (level_max - level - 1); + morton_key_t mkey(compute_morton_key(mx, my), encode_tree(treeId)); + + metadata_t child_value(mkey, INDEX_UNINITIALIZED, data[0], CELL_UNINITIALIZED); + mandelbrotMap.insert(ckey, child_value); + } + + { + int ixx = ix << 1 | 0x1; + int iyy = iy << 1; + amr_key_t ckey(compute_morton_key(ixx, iyy), encode_level_tree(level + 1, treeId)); + + int mx = ixx << (level_max - level - 1); + int my = iyy << (level_max - level - 1); + morton_key_t mkey(compute_morton_key(mx, my), encode_tree(treeId)); + + metadata_t child_value(mkey, INDEX_UNINITIALIZED, data[1], CELL_UNINITIALIZED); + mandelbrotMap.insert(ckey, child_value); + } + + { + int ixx = ix << 1; + int iyy = iy << 1 | 0x1; + amr_key_t ckey(compute_morton_key(ixx, iyy), encode_level_tree(level + 1, treeId)); + + int mx = ixx << (level_max - level - 1); + int my = iyy << (level_max - level - 1); + morton_key_t mkey(compute_morton_key(mx, my), encode_tree(treeId)); + + metadata_t child_value(mkey, INDEX_UNINITIALIZED, data[2], CELL_UNINITIALIZED); + mandelbrotMap.insert(ckey, child_value); + } + + { + int ixx = ix << 1 | 0x1; + int iyy = iy << 1 | 0x1; + amr_key_t ckey(compute_morton_key(ixx, iyy), encode_level_tree(level + 1, treeId)); + + int mx = ixx << (level_max - level - 1); + int my = iyy << (level_max - level - 1); + morton_key_t mkey(compute_morton_key(mx, my), encode_tree(treeId)); + + metadata_t child_value(mkey, INDEX_UNINITIALIZED, data[3], CELL_UNINITIALIZED); + mandelbrotMap.insert(ckey, child_value); + } + + // mark coarse cell for removal + value.status = CELL_TO_BE_REMOVED; + mandelbrotMap.value_at(i) = value; + + } // end if refinement needed + + } // end status != CELL_UNINITIALIZED + + } // insert_refined_cells + + KOKKOS_INLINE_FUNCTION + void + delete_old_cells(const int & i) const + { + + // read key/value at current i + amr_key_t key = mandelbrotMap.key_at(i); + metadata_t value = mandelbrotMap.value_at(i); + + CellStatus status = value.status; + + // if value is CELL_TO_BE_REMOVED, then call erase + // remove cell at coarse level (erase method returns true / false depending + // if current kernel is in between calls to begin_erase / end_erase + if (status == CELL_TO_BE_REMOVED) + { + mandelbrotMap.erase(key); + } + + } // delete_old_cells + + KOKKOS_INLINE_FUNCTION + void + reset_status(const int & i) const + { + + // read key/value at current i + metadata_t value = mandelbrotMap.value_at(i); + + CellStatus status = value.status; + + // if value is CELL_TO_BE_REMOVED, then call erase + // remove cell at coarse level (erase method returns true / false depending + // if current kernel is in between calls to begin_erase / end_erase + if (status == CELL_UNINITIALIZED) + { + value.status = CELL_REGULAR; + mandelbrotMap.value_at(i) = value; + } + + } // reset_status + + /* + * main mandelbrot functor entry point. + */ + //! functor + KOKKOS_INLINE_FUNCTION + void + operator()(const int & i) const + { + + // check if we have a valid (key,value) pair at current iterator i + if (mandelbrotMap.valid_at(i)) + { + + if (refine_mode == INSERT_REFINED_CELLS) + { + insert_refined_cells(i); + } + else if (refine_mode == DELETE_OLD_CELLS) + { + delete_old_cells(i); + } + else if (refine_mode == RESET_STATUS) + { + reset_status(i); + } + + } // end valid_at + + } // end operator() + +}; // struct Refine + +// ========================================================================= +// ========================================================================= +/** + * dump mandelbrot set (given as an Kokkos::UnorderedMap). + * + * file format is vtk unstructured grid. + */ +template +struct VtkWriter : public MandelbrotConfig +{ + + using exec_space = typename device_t::execution_space; + using MandelbrotMap_t = typename MandelbrotConfig::MandelbrotMap; + + static void + dump(const std::string & filename, MandelbrotMap_t mandelbrotMap) + { + + std::cout << "dump vtk 2d\n"; + std::cout << "mandelbrotMap.size()=" << mandelbrotMap.size() << "\n"; + + int nbNodesPerCells = 4; // 2d + + // compute total number of cells + uint64_t nbCells = mandelbrotMap.size(); + + // compute total number of vertices + uint64_t nbNodes = nbCells * nbNodesPerCells; + + // allocate a 2d device View of size : number of nodes by 3 + // (x,y,z) of a given node + using Nodes_pos = typename Kokkos::View; + using Nodes_pos_host = typename Nodes_pos::HostMirror; + + Nodes_pos nodes_pos = Nodes_pos("nodes_position", nbNodes); + + // convert nodes_pos to a std::vector (we will need to improve / avoid this later) + std::vector> nodes_coord(nbNodes); + + // Kokkos view of keys + using KeyVec = typename Kokkos::View; + // using KeyVec_h = Kokkos::View::HostMirror; + + KeyVec keyVec = KeyVec("vector_of_keys", mandelbrotMap.size()); + + using CellData = typename Kokkos::View; + + CellData data_levels = CellData("data_levels", mandelbrotMap.size()); + CellData data_mkeys = CellData("data_mkeys", mandelbrotMap.size()); + CellData data_mindex = CellData("data_mindex", mandelbrotMap.size()); + CellData data_mandelbrot = CellData("data_mandelbrot", mandelbrotMap.size()); + + // create a vector of keys + some example cell data + Kokkos::parallel_scan( + "copy_kokkos_unordered_map_to_view", + Kokkos::RangePolicy(0, mandelbrotMap.capacity()), + KOKKOS_LAMBDA(const int & i, int & ivec, const bool final) { + if (mandelbrotMap.valid_at(i)) + { + if (final) + { + amr_key_t key = mandelbrotMap.key_at(i); + metadata_t value = mandelbrotMap.value_at(i); + keyVec(ivec) = key; + data_levels(ivec) = 1.0 * key.get_level(); + data_mkeys(ivec) = 1.0 * value.key[0]; + data_mindex(ivec) = 1.0 * value.index; + data_mandelbrot(ivec) = value.data; + } + + ivec++; + } + }); + + printf("keyVec size is %ld\n", keyVec.size()); + + // get nodes position + Kokkos::parallel_for( + "get nodes positions", + Kokkos::RangePolicy(0, keyVec.extent(0)), + KOKKOS_LAMBDA(const int & i) { + amr_key_t key = keyVec(i); + uint64_t morton = key.get_morton(); + uint32_t ix = morton_extract_bits<2, IX>(morton); + uint32_t iy = morton_extract_bits<2, IY>(morton); + + int level = key.get_level(); + int N = 1 << level; + + real_t dx = 1.0 / N; + real_t dy = 1.0 / N; + + int64_t ii = i; + nodes_pos(ii * nbNodesPerCells + 0, IX) = ix * dx; + nodes_pos(ii * nbNodesPerCells + 0, IY) = iy * dy; + nodes_pos(ii * nbNodesPerCells + 0, IZ) = 0.0; + + nodes_pos(ii * nbNodesPerCells + 1, IX) = ix * dx + dx; + nodes_pos(ii * nbNodesPerCells + 1, IY) = iy * dy; + nodes_pos(ii * nbNodesPerCells + 1, IZ) = 0.0; + + nodes_pos(ii * nbNodesPerCells + 2, IX) = ix * dx + dx; + nodes_pos(ii * nbNodesPerCells + 2, IY) = iy * dy + dy; + nodes_pos(ii * nbNodesPerCells + 2, IZ) = 0.0; + + nodes_pos(ii * nbNodesPerCells + 3, IX) = ix * dx; + nodes_pos(ii * nbNodesPerCells + 3, IY) = iy * dy + dy; + nodes_pos(ii * nbNodesPerCells + 3, IZ) = 0.0; + }); + + // convert nodes_pos to nodes_coord (On host with OpenMP) + { + Nodes_pos_host nodes_pos_host = Kokkos::create_mirror(nodes_pos); + + Kokkos::deep_copy(nodes_pos_host, nodes_pos); + + Kokkos::parallel_for( + "Copy node positions data", + Kokkos::RangePolicy(0, nbNodes), + [&](const int i) { + nodes_coord[i] = { nodes_pos_host(i, IX), nodes_pos_host(i, IY), nodes_pos_host(i, IZ) }; + }); + } + + ConfigMap config_map("./test_io_vtk_2d.ini"); + config_map.setString("output", "outputPrefix", "mandelbrot_set"); + kalypsso::io::VTKWriter vtkWriter(config_map, nbCells); + vtkWriter.open_file(); + vtkWriter.write_header(); + vtkWriter.write_metadata(0, 0.0); + vtkWriter.write_piece_header(nbNodes); + + vtkWriter.write_geometry<2>(nodes_coord); + vtkWriter.write_connectivity<2>(); + + vtkWriter.open_data(); + // write cell data - meta + heavy + // vtkWriter.write_cell_data("level", cell_levels); + vtkWriter.write_cell_data("level", data_levels); + vtkWriter.write_cell_data("morton_index", data_mkeys); + vtkWriter.write_cell_data("memory_index", data_mindex); + vtkWriter.write_cell_data("mandelbrot", data_mandelbrot); + + vtkWriter.close_data(); + + // finally closing the file ! + vtkWriter.write_piece_footer(); + vtkWriter.close_grid(); + vtkWriter.write_footer(); + vtkWriter.close_file(); + + } // dump + +}; // struct VtkWriter + +// ========================================================================= +// ========================================================================= +/** + * Driver class for Mandelbrot set computation. + */ +template +struct MandelbrotCompute : public MandelbrotConfig +{ + using MandelbrotMap_t = typename MandelbrotConfig::MandelbrotMap; + using exec_space = typename device_t::execution_space; + + static void + run(int level_min, int level_max, int max_capacity_prefactor) + { + + { + exec_space space; + std::cout << "===============================================\n"; + std::cout << "===== Compute Mandelbrot Set using " << space.name() << "\n"; + std::cout << "===============================================\n"; + } + + std::cout << "Level min = " << level_min << "\n"; + std::cout << "Level max = " << level_max << "\n"; + + // linear size along a direction + int N = 1 << level_min; + + // an unordered map with metadata + MandelbrotMap_t mandelbrotMap; + + // maximum capacity of the hash map container + uint64_t total_capacity = max_capacity_prefactor * N * N; + + std::cout << "Creating a metadata map with nLevels=" << level_min << " and capacity of " + << total_capacity << " elements\n"; + + // allocate some space in the hash map + mandelbrotMap.rehash(total_capacity); + + // play with Kokkos API for UnorderedMap + std::cout << "is mandelbrotMap insertable ? " << mandelbrotMap.is_insertable_map << "\n"; + std::cout << "is mandelbrotMap modifiable ? " << mandelbrotMap.is_modifiable_map << "\n"; + + std::cout << "mandelbrotMap.size() = " << mandelbrotMap.size() << std::endl; + std::cout << "mandelbrotMap.capacity() = " << mandelbrotMap.capacity() << " (max size)" + << std::endl; + + // just initialize mandelbrotMap with a coarse uniform grid + FillCoarseMap fill(mandelbrotMap, level_min, level_max); + + std::cout << "[before refine] number of cells in mandelbrotMap " << mandelbrotMap.size() << " " + << "\n"; + + // refine nbIter times + Refine::apply(mandelbrotMap, level_min, level_max); + Kokkos::fence(); + std::cout << "[after refine] number of cells in mandelbrotMap " << mandelbrotMap.size() << " " + << "\n"; + + uint64_t Nsquare = 1 << 2 * level_max; + std::cout << "Sparsity of Mandelbrot set : " << 100.0 * mandelbrotMap.size() / Nsquare << "%\n"; + + // create array (Kokkos::View) of keys + using KeyVec = typename Kokkos::View; + + // create array of morton key (TODO replace uint64_t by morton_key_t) + using KeyVec2 = typename Kokkos::View; + // using KeyVec_h = Kokkos::View::HostMirror; + + KeyVec keyVec = KeyVec("keys", mandelbrotMap.size()); + KeyVec2 sorted_keyVec = KeyVec2("sorted_keys", mandelbrotMap.size()); + + using Morton2AMR = typename Kokkos::UnorderedMap; + Morton2AMR morton2amr; + morton2amr.rehash(mandelbrotMap.capacity()); + + // fill keyVec, init sorted_keyVec and morton2amr + Kokkos::parallel_scan( + "copy_kokkos_unordered_map_to_view", + Kokkos::RangePolicy(0, mandelbrotMap.capacity()), + KOKKOS_LAMBDA(const int & i, int & ivec, const bool final) { + if (mandelbrotMap.valid_at(i)) + { + if (final) + { + keyVec(ivec) = mandelbrotMap.key_at(i); + metadata_t value = mandelbrotMap.value_at(i); + + uint64_t mkey = value.key[0]; + sorted_keyVec(ivec) = mkey; + morton2amr.insert(mkey, mandelbrotMap.key_at(i)); + } + + ivec++; + } + }); + + printf("keyVec size is %ld\n", keyVec.size()); + + // for(size_t i=0; i(0, sorted_keyVec.size()), + KOKKOS_LAMBDA(const int & i) { + // read morton key along Z-curve + uint64_t mkey = sorted_keyVec(i); + + // find corresponding amr key by hash table loopkup + size_t ikey = morton2amr.find(mkey); + amr_key_t amr_key = morton2amr.value_at(ikey); + + // insert index into metadata + ikey = mandelbrotMap.find(amr_key); + + // ikey should always be here + metadata_t value = mandelbrotMap.value_at(ikey); + + value.index = i; + + mandelbrotMap.value_at(ikey) = value; + }); + + // dump in vtk file format + VtkWriter::dump("mandelbrot_set_2d.vtk", mandelbrotMap); + + } // run + +}; // struct MandelbrotCompute + +} // namespace kalypsso + +// ========================================================================= +// ========================================================================= +// ========================================================================= +// ========================================================================= +int +main(int argc, char * argv[]) +{ + + // Create MPI session if MPI enabled +#ifdef KALYPSSO_CORE_USE_MPI + kalypsso::GlobalMpiSession mpiSession(argc, argv); +#endif // KALYPSSO_CORE_USE_MPI + + { + Kokkos::initialize(argc, argv); + + { + std::cout << "##########################\n"; + std::cout << "KOKKOS CONFIG \n"; + std::cout << "##########################\n"; + + std::ostringstream msg; + std::cout << "Kokkos configuration" << std::endl; + if (Kokkos::hwloc::available()) + { + msg << "hwloc( NUMA[" << Kokkos::hwloc::get_available_numa_count() << "] x CORE[" + << Kokkos::hwloc::get_available_cores_per_numa() << "] x HT[" + << Kokkos::hwloc::get_available_threads_per_core() << "] )" << std::endl; + } + Kokkos::print_configuration(msg); + + std::cout << msg.str(); + std::cout << "##########################\n"; + } + + + // analyze command line arguments : + // + // - level_min ==> coarse grid is (2**level_min)x(2**level_min) + // - level_max + // - max_capacity_prefactor : used to preallocated the hash table + // hash table size is coarse grid size x max_capacity_prefactor + const int level_min = argc > 1 ? std::atoi(argv[1]) : 6; + const int level_max = argc > 2 ? std::atoi(argv[2]) : 10; + const int max_capacity_prefactor = argc > 3 ? std::atoi(argv[3]) : 500; + + kalypsso::MandelbrotCompute::run( + level_min, level_max, max_capacity_prefactor); + + // kalypsso::MandelbrotCompute::run( + // level_min, level_max, max_capacity_prefactor); + // kalypsso::MandelbrotCompute::run( + // level_min, level_max, max_capacity_prefactor); + + Kokkos::finalize(); + } + + return EXIT_SUCCESS; + +} // end main diff --git a/test/kokkos/query_device_kokkos.cpp b/test/kokkos/query_device_kokkos.cpp new file mode 100644 index 0000000..013e9a7 --- /dev/null +++ b/test/kokkos/query_device_kokkos.cpp @@ -0,0 +1,99 @@ +// Copyright (2014) Sandia Corporation +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include + +#include +#include + +#if defined(KALYPSSO_CORE_USE_MPI) +# include +#endif // KALYPSSO_CORE_USE_MPI + +#include + +#ifndef UNUSED +# define UNUSED(x) ((void)(x)) +#endif + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +int +main(int argc, char ** argv) +{ + + UNUSED(argc); + UNUSED(argv); + + std::ostringstream msg; + + [[maybe_unused]] int mpi_rank = 0; + [[maybe_unused]] int nRanks = 1; + +#if defined(KALYPSSO_CORE_USE_MPI) + + MPI_Init(&argc, &argv); + + MPI_Comm_rank(MPI_COMM_WORLD, &mpi_rank); + MPI_Comm_size(MPI_COMM_WORLD, &nRanks); + + msg << "MPI rank(" << mpi_rank << ") "; + +#endif // KALYPSSO_CORE_USE_MPI + + Kokkos::initialize(argc, argv); + +#ifdef KOKKOS_ENABLE_CUDA + { + + // // get device count + // int devCount; + // cudaGetDeviceCount(&devCount); + + // int devId = mpi_rank % devCount; + // cudaSetDevice(devId); + + // To enable kokkos accessing multiple GPUs don't forget to + // add option "--ndevices=X" where X is the number of GPUs + // you want to use per node. + + // on a large cluster, the scheduler should assign resources + // in a way that each MPI task is mapped to a different GPU + // let's cross-checked that: + + int cudaDeviceId; + cudaGetDevice(&cudaDeviceId); + std::cout << "I'm MPI task #" << mpi_rank << " (out of " << nRanks << ")" << " pinned to GPU #" + << cudaDeviceId << "\n"; + } +#endif // KOKKOS_ENABLE_CUDA + + msg << "{" << std::endl; + + if (Kokkos::hwloc::available()) + { + msg << "hwloc( NUMA[" << Kokkos::hwloc::get_available_numa_count() << "] x CORE[" + << Kokkos::hwloc::get_available_cores_per_numa() << "] x HT[" + << Kokkos::hwloc::get_available_threads_per_core() << "] )" << std::endl; + } + + Kokkos::print_configuration(msg); + + msg << "}" << std::endl; + + std::cout << msg.str(); + + Kokkos::finalize(); + +#if defined(KALYPSSO_CORE_USE_MPI) + + MPI_Finalize(); + +#endif + + return 0; +} diff --git a/test/kokkos/test_kokkos_mdrange.cpp b/test/kokkos/test_kokkos_mdrange.cpp new file mode 100644 index 0000000..570e7e4 --- /dev/null +++ b/test/kokkos/test_kokkos_mdrange.cpp @@ -0,0 +1,185 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file test_kokkos_mdrange.cpp + */ + +#include +#include + +#include // for std::make_pair +#include // for std::cout + +// ======================================================================= +// ======================================================================= +template +void +run_test(uint32_t nx, uint32_t ny) +{ + + using exec_space = typename device_t::execution_space; + + using Data_t = Kokkos::View; + // using DataHost_t = typename Data_t::HostMirror; + + /* + * TestKokkosMdrangeForFunctor + * + * Just playing with the Kokkos::Rank template parameters named OuterDir and InnerDir. + * - InnerDir controls the mapping of the thread iteration ids within a tile (a tile maybe 2d, 3d, + * ...) + * - OuterDir controls the mapping of the tiles iteration index (the tiles set maybe 2d, 3d, ...) + * + * Here what we illustrate is the fact that: as Data_t is a 2d view with left layout, its best to + * use InnerDir = Kokkos::Iterate::Left, OuterDir doesn't really matter here. + */ + { + std::cout << "// ======================================\n"; + std::cout << "Testing TestKokkosMdrangeForFunctor with (nx,ny)= (" << nx << "," << ny << ")\n"; + std::cout << "// ======================================\n"; + + // create and init test data + auto data = Data_t("test_data", nx, ny); + Kokkos::parallel_for( + "init_test_data", + Kokkos::MDRangePolicy>({ 0, 0 }, { nx, ny }), + KOKKOS_LAMBDA(uint32_t i, uint32_t j) { data(i, j) = i + j + 1; }); + + // Kokkos::fence(); + + printf("print using default layouts\n"); + + Kokkos::parallel_for( + "print_results", + Kokkos::MDRangePolicy>({ 0, 0 }, { nx, ny }), + KOKKOS_LAMBDA(uint32_t i, uint32_t j) { + // std::cout << i << " " << j << " " << data(i, j) << "\n"; + printf("data(%d,%d)=%d at 0x%p\n", i, j, data(i, j), &(data(i, j))); + }); + + printf("print using outer layouts=left and inner layout=default\n"); + Kokkos::parallel_for( + "print_results", + Kokkos::MDRangePolicy>( + { 0, 0 }, { nx, ny }), + KOKKOS_LAMBDA(uint32_t i, uint32_t j) { + // std::cout << i << " " << j << " " << data(i, j) << "\n"; + printf("data(%d,%d)=%d at 0x%p\n", i, j, data(i, j), &(data(i, j))); + }); + + printf("print using outer layouts=left and inner layout=left\n"); + Kokkos::parallel_for( + "print_results", + Kokkos::MDRangePolicy>( + { 0, 0 }, { nx, ny }), + KOKKOS_LAMBDA(uint32_t i, uint32_t j) { + // std::cout << i << " " << j << " " << data(i, j) << "\n"; + printf("data(%d,%d)=%d at 0x%p\n", i, j, data(i, j), &(data(i, j))); + }); + + printf("print using outer layouts=left and inner layout=right\n"); + Kokkos::parallel_for( + "print_results", + Kokkos::MDRangePolicy>( + { 0, 0 }, { nx, ny }), + KOKKOS_LAMBDA(uint32_t i, uint32_t j) { + // std::cout << i << " " << j << " " << data(i, j) << "\n"; + printf("data(%d,%d)=%d at 0x%p\n", i, j, data(i, j), &(data(i, j))); + }); + + printf("print using outer layouts=right and inner layout=left\n"); + Kokkos::parallel_for( + "print_results", + Kokkos::MDRangePolicy>( + { 0, 0 }, { nx, ny }), + KOKKOS_LAMBDA(uint32_t i, uint32_t j) { + // std::cout << i << " " << j << " " << data(i, j) << "\n"; + printf("data(%d,%d)=%d at 0x%p\n", i, j, data(i, j), &(data(i, j))); + }); + + printf("print using outer layouts=right and inner layout=right\n"); + Kokkos::parallel_for( + "print_results", + Kokkos::MDRangePolicy>( + { 0, 0 }, { nx, ny }), + KOKKOS_LAMBDA(uint32_t i, uint32_t j) { + // std::cout << i << " " << j << " " << data(i, j) << "\n"; + printf("data(%d,%d)=%d at 0x%p\n", i, j, data(i, j), &(data(i, j))); + }); + + printf("print using all layouts=right\n"); + Kokkos::parallel_for( + "print_results", + Kokkos::MDRangePolicy>({ 0, 0 }, + { nx, ny }), + KOKKOS_LAMBDA(uint32_t i, uint32_t j) { + // std::cout << i << " " << j << " " << data(i, j) << "\n"; + printf("data(%d,%d)=%d at 0x%p\n", i, j, data(i, j), &(data(i, j))); + }); + + printf("print using all layouts=left\n"); + Kokkos::parallel_for( + "print_results", + Kokkos::MDRangePolicy>({ 0, 0 }, + { nx, ny }), + KOKKOS_LAMBDA(uint32_t i, uint32_t j) { + // std::cout << i << " " << j << " " << data(i, j) << "\n"; + printf("data(%d,%d)=%d at 0x%p\n", i, j, data(i, j), &(data(i, j))); + }); + + // Kokkos::fence(); + } + +} // run_test + +// ======================================================================= +// ======================================================================= +int +main(int argc, char * argv[]) +{ + + Kokkos::initialize(argc, argv); + + { + std::cout << "##########################\n"; + std::cout << "KOKKOS CONFIG \n"; + std::cout << "##########################\n"; + + std::ostringstream msg; + std::cout << "Kokkos configuration" << std::endl; + if (Kokkos::hwloc::available()) + { + msg << "hwloc( NUMA[" << Kokkos::hwloc::get_available_numa_count() << "] x CORE[" + << Kokkos::hwloc::get_available_cores_per_numa() << "] x HT[" + << Kokkos::hwloc::get_available_threads_per_core() << "] )" << std::endl; + } + Kokkos::print_configuration(msg); + std::cout << msg.str(); + std::cout << "##########################\n"; + + } // end kokkos config + + // using DefaultDevice = + // Kokkos::Device; + +#ifdef KOKKOS_ENABLE_SERIAL + using DefaultDevice = Kokkos::Serial; + // Kokkos::Device; + + uint32_t nx = 5; + uint32_t ny = 3; + + run_test(nx, ny); +#endif + + Kokkos::finalize(); + + return EXIT_SUCCESS; +} diff --git a/test/kokkos/test_kokkos_mpi.cpp b/test/kokkos/test_kokkos_mpi.cpp new file mode 100644 index 0000000..d205d33 --- /dev/null +++ b/test/kokkos/test_kokkos_mpi.cpp @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * A simple MPI_Send/Recv just for cross-checking MPI implementation is cuda-aware. + */ + +#include +#include +#include +#include // choose between single and double precision + +#include // for std::make_pair +#include // for std::cout + +template +void +run_test(int argc, char * argv[]) +{ + using data_t = Kokkos::View; + + const int N = 100; + + // create parallel environment (p4est, MPI, kokkos, ...) + kalypsso::ParallelEnv par_env(argc, argv); + + if (par_env.size() < 2) + { + if (par_env.rank() == 0) + printf("ERROR: MPI communicator must have at least 2 MPI ranks.\n"); + return; + } + + auto data0 = data_t("data0", N); + auto data1 = data_t("data1", N); + + if (par_env.rank() == 0) + { + Kokkos::parallel_for( + "init", Kokkos::RangePolicy<>(0, N), KOKKOS_LAMBDA(uint32_t i) { data0(i) = N - i; }); + } + + // send data0 from MPI rank0 to rank1 + if (par_env.rank() == 0) + { + MPI_Request req = par_env.comm().MPI_Isend(data0, 1, 911); + par_env.comm().MPI_Waitall(1, &req); + } + else + { + MPI_Request req = par_env.comm().MPI_Irecv(data1, 0, 911); + par_env.comm().MPI_Waitall(1, &req); + } + + if (par_env.rank() == 1) + { + Kokkos::parallel_for( + "cross-check", Kokkos::RangePolicy<>(0, N), KOKKOS_LAMBDA(uint32_t i) { + printf("[MPI rank=1]: %d %d\n", i, data1(i)); + }); + } + +} // run_test + +// ======================================================================= +// ======================================================================= +int +main(int argc, char * argv[]) +{ + run_test(argc, argv); + + return EXIT_SUCCESS; +} diff --git a/test/kokkos/test_kokkos_team_mdrange.cpp b/test/kokkos/test_kokkos_team_mdrange.cpp new file mode 100644 index 0000000..22e7a35 --- /dev/null +++ b/test/kokkos/test_kokkos_team_mdrange.cpp @@ -0,0 +1,135 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file test_kokkos_team_mdrange.cpp + */ + +#include +#include + +#include // for std::make_pair +#include // for std::cout + +// ======================================================================= +// ======================================================================= +template +void +run_test(uint32_t nx, uint32_t ny, uint32_t nz) +{ + + using exec_space = typename device_t::execution_space; + + using Data_t = Kokkos::View; + + /* + * TestKokkosTeamMdrangeForFunctor + * + * Just playing with the Kokkos::Rank template parameters named OuterDir and InnerDir. + * - InnerDir controls the mapping of the thread iteration ids within a tile (a tile maybe 2d, 3d, + * ...) + * - OuterDir controls the mapping of the tiles iteration index (the tiles set maybe 2d, 3d, ...) + * + * Here what we illustrate is the fact that: as Data_t is a 2d view with left layout, its best to + * use InnerDir = Kokkos::Iterate::Left, OuterDir doesn't really matter here. + */ + { + std::cout << "// ======================================\n"; + std::cout << "Testing TestKokkosTeamMdrangeForFunctor with (nx,ny)= (" << nx << "," << ny << "," + << nz << ")\n"; + std::cout << "// ======================================\n"; + + // create and init test data + auto data = Data_t("test_data", nx, ny, nz); + + using team_policy_t = Kokkos::TeamPolicy>; + using thread_t = typename team_policy_t::member_type; + + Kokkos::parallel_for( + "init_test_data - outer", + team_policy_t(nz, Kokkos::AUTO()), + KOKKOS_LAMBDA(const thread_t & member) { + uint32_t k = member.league_rank(); + + Kokkos::parallel_for( + Kokkos::TeamVectorMDRange, + thread_t>(member, nx, ny), + [=](uint32_t i, uint32_t j) { data(i, j, k) = i + j + k + 1; }); + }); + + printf("Print values:\n"); + Kokkos::parallel_for( + "init_test_data - outer", + team_policy_t(nz, Kokkos::AUTO()), + KOKKOS_LAMBDA(const thread_t & member) { + uint32_t k = member.league_rank(); + + Kokkos::parallel_for( + Kokkos::TeamVectorMDRange, + thread_t>(member, nx, ny), + [=](uint32_t i, uint32_t j) { printf("%d %d %d = %d\n", i, j, k, data(i, j, k)); }); + }); + + printf("Print values of the inner 2x2x2 sub-domain:\n"); + Kokkos::parallel_for( + "init_test_data - outer", + team_policy_t(2, Kokkos::AUTO()), + KOKKOS_LAMBDA(const thread_t & member) { + uint32_t k = member.league_rank(); + + Kokkos::parallel_for( + Kokkos::TeamVectorMDRange, + thread_t>(member, 2, 2), + [=](uint32_t i, uint32_t j) { + printf("%d %d %d = %d\n", i + 1, j + 1, k + 1, data(i + 1, j + 1, k + 1)); + }); + }); + } +} // run_test + +// ======================================================================= +// ======================================================================= +int +main(int argc, char * argv[]) +{ + + Kokkos::initialize(argc, argv); + + { + std::cout << "##########################\n"; + std::cout << "KOKKOS CONFIG \n"; + std::cout << "##########################\n"; + + std::ostringstream msg; + std::cout << "Kokkos configuration" << std::endl; + if (Kokkos::hwloc::available()) + { + msg << "hwloc( NUMA[" << Kokkos::hwloc::get_available_numa_count() << "] x CORE[" + << Kokkos::hwloc::get_available_cores_per_numa() << "] x HT[" + << Kokkos::hwloc::get_available_threads_per_core() << "] )" << std::endl; + } + Kokkos::print_configuration(msg); + std::cout << msg.str(); + std::cout << "##########################\n"; + + } // end kokkos config + + // using DefaultDevice = + // Kokkos::Device; + +#ifdef KOKKOS_ENABLE_SERIAL + using DefaultDevice = Kokkos::Serial; + // Kokkos::Device; + + uint32_t nx = 4; + uint32_t ny = 4; + uint32_t nz = 4; + + run_test(nx, ny, nz); +#endif + + Kokkos::finalize(); + + return EXIT_SUCCESS; +} diff --git a/test/kokkos/test_kokkos_teamvector.cpp b/test/kokkos/test_kokkos_teamvector.cpp new file mode 100644 index 0000000..33fe159 --- /dev/null +++ b/test/kokkos/test_kokkos_teamvector.cpp @@ -0,0 +1,489 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file test_kokkos_teamvector.cpp + */ + +#include +#include + +#include +#ifdef KALYPSSO_CORE_USE_MPI +# include +# include +#endif // KALYPSSO_CORE_USE_MPI + +#include + +template +struct TestFunctorBase +{ + using Data_t = Kokkos::View; + using DataHost_t = typename Data_t::HostMirror; +}; + +/*************************************************/ +/*************************************************/ +/*************************************************/ +/** + * Kokkos team vector - parallel for + * + * \note in this test, data array size must be a multiple of nbBlocks + */ +template +class TestKokkosTeamVectorForFunctor : public TestFunctorBase +{ + +private: + uint32_t nbTeams; //!< number of thread teams + +public: + using exec_space_t = typename device_t::execution_space; + using Data_t = typename TestFunctorBase::Data_t; + using team_policy_t = Kokkos::TeamPolicy; + using thread_t = typename team_policy_t::member_type; + + void + setNbTeams(uint32_t nbTeams_) + { + nbTeams = nbTeams_; + } + + /** + * test parallel for functor + * + */ + TestKokkosTeamVectorForFunctor(Data_t data, uint32_t bSize) + : data(data) + , bSize(bSize) + { + nbBlocks = (data.extent(0) + bSize - 1) / bSize; + } + + // static method which does it all: create and execute functor + static void + apply(Data_t data, uint32_t bSize) + { + + TestKokkosTeamVectorForFunctor functor(data, bSize); + + // kokkos execution policy + uint32_t nbTeams_ = 16; + functor.setNbTeams(nbTeams_); + + team_policy_t policy(nbTeams_, Kokkos::AUTO() /* team size chosen by kokkos */); + + Kokkos::parallel_for("TestKokkosTeamVectorForFunctor", policy, functor); + } + + KOKKOS_INLINE_FUNCTION + void + operator()(thread_t member) const + { + + uint32_t iBlock = member.league_rank(); + + while (iBlock < nbBlocks) + { + + Kokkos::parallel_for(Kokkos::TeamVectorRange(member, bSize), [=](const int32_t index) { + // copy q state in q global + data(index + iBlock * bSize) += 12; + }); // end TeamVectorRange + + iBlock += nbTeams; + + } // end while iBlock < nbBlocks + + } // operator + + //! heavy data + Data_t data; + + //! block size + uint32_t bSize; + + //! number of blocks + uint32_t nbBlocks; + +}; // TestKokkosTeamVectorForFunctor + +/*************************************************/ +/*************************************************/ +/*************************************************/ +/** + * Kokkos team vector - parallel reduce inside block + * + * \note in this test, data array size must be a multiple of nbBlocks + */ +template +class TestKokkosTeamVectorReduceFunctor : public TestFunctorBase +{ + +private: + uint32_t nbTeams; //!< number of thread teams + +public: + using exec_space_t = typename device_t::execution_space; + using Data_t = typename TestFunctorBase::Data_t; + using team_policy_t = Kokkos::TeamPolicy; + using thread_t = typename team_policy_t::member_type; + + void + setNbTeams(uint32_t nbTeams_) + { + nbTeams = nbTeams_; + } + + /** + * test parallel reduce functor + * + */ + TestKokkosTeamVectorReduceFunctor(Data_t data, uint32_t bSize) + : data(data) + , bSize(bSize) + { + nbBlocks = (data.extent(0) + bSize - 1) / bSize; + } + + // static method which does it all: create and execute functor + static void + apply(Data_t data, uint32_t bSize) + { + + TestKokkosTeamVectorReduceFunctor functor(data, bSize); + + // kokkos execution policy + uint32_t nbTeams_ = 16; + functor.setNbTeams(nbTeams_); + + team_policy_t policy(nbTeams_, Kokkos::AUTO() /* team size chosen by kokkos */); + + Kokkos::parallel_for("TestKokkosTeamVectorReduceFunctor", policy, functor); + } + + KOKKOS_INLINE_FUNCTION + void + operator()(thread_t member) const + { + + uint32_t iBlock = member.league_rank(); + + while (iBlock < nbBlocks) + { + + int sum = 0; + + Kokkos::parallel_reduce( + Kokkos::TeamVectorRange(member, bSize), + [=](const int32_t index, int & local_sum) { + // copy q state in q global + local_sum += data(index + iBlock * bSize); + }, + sum); // end TeamVectorRange + + // check results : + int32_t diff = sum - (bSize - 1) * bSize / 2 - iBlock * bSize * bSize; + bool valid = diff == 0 ? true : false; + + if (member.team_rank() == 0) + { + // std::cout << iBlock << ": res=" << sum << " results valid ? " << valid << "\n"; + printf("%d: res=%d results valid ? %d\n", iBlock, sum, valid); + } + + iBlock += nbTeams; + + } // end while iBlock < nbBlocks + + } // operator + + //! heavy data + Data_t data; + + //! block size + uint32_t bSize; + + //! number of blocks + uint32_t nbBlocks; + +}; // TestKokkosTeamVectorReduceFunctor + +/*************************************************/ +/*************************************************/ +/*************************************************/ +/** + * Kokkos team vector - parallel reduce. + * + * Do a hierarchical reduce, i.e. first reduce inside a team, then a global reduce. + * + * \note in this test, data array size must be a multiple of nbBlocks + */ +template +class TestKokkosTeamVectorReduceFunctor2 : public TestFunctorBase +{ + +private: + uint32_t nbTeams; //!< number of thread teams + +public: + using exec_space_t = typename device_t::execution_space; + using Data_t = typename TestFunctorBase::Data_t; + using team_policy_t = Kokkos::TeamPolicy; + using thread_t = typename team_policy_t::member_type; + + void + setNbTeams(uint32_t nbTeams_) + { + nbTeams = nbTeams_; + } + + /** + * test parallel reduce functor + */ + TestKokkosTeamVectorReduceFunctor2(Data_t data, uint32_t bSize) + : data(data) + , bSize(bSize) + { + nbBlocks = (data.extent(0) + bSize - 1) / bSize; + } + + // static method which does it all: create and execute functor + static void + apply(Data_t data, uint32_t bSize) + { + + TestKokkosTeamVectorReduceFunctor2 functor(data, bSize); + + // kokkos execution policy + uint32_t nbTeams_ = 16; + functor.setNbTeams(nbTeams_); + + team_policy_t policy(nbTeams_, Kokkos::AUTO() /* team size chosen by kokkos */); + + // initialize reduce value to something really small + Kokkos::MaxLoc::value_type result; + + Kokkos::parallel_reduce("TestKokkosTeamVectorReduceFunctor2", + policy, + functor, + Kokkos::MaxLoc(result)); + + printf("maximum value is %d at location %d\n", result.val, result.loc); + } + + KOKKOS_INLINE_FUNCTION + void + operator()(thread_t member, Kokkos::MaxLoc::value_type & max_value) const + { + + uint32_t iBlock = member.league_rank(); + uint32_t index = member.team_rank(); + + auto result = max_value; + + while (iBlock < nbBlocks) + { + + while (index < bSize) + { + + // printf("[debug] index=%d iBlock=%d bSize=%d | data=%d\n",index,iBlock,bSize,data(index + + // iBlock * bSize)); + + // update result + if (data(index + iBlock * bSize) > result.val) + { + result.val = data(index + iBlock * bSize); + result.loc = index + iBlock * bSize; + } + + index += member.team_size(); + } + + iBlock += nbTeams; + + } // end while iBlock < nbBlocks + + // update global reduced value + if (max_value.val < result.val) + { + max_value.val = result.val; + max_value.loc = result.loc; + } + + } // operator + + //! heavy data + Data_t data; + + //! block size + uint32_t bSize; + + //! number of blocks + uint32_t nbBlocks; + +}; // TestKokkosTeamVectorReduceFunctor2 + +// ======================================================================= +// ======================================================================= +template +void +run_test(uint32_t bSize, uint32_t nbBlocks) +{ + + using exec_space = typename device_t::execution_space; + using Data_t = typename TestFunctorBase::Data_t; + + /* + * TestKokkosTeamVectorForFunctor + */ + { + std::cout << "// ======================================\n"; + std::cout << "Testing TestKokkosTeamVectorForFunctor...\n"; + std::cout << "// ======================================\n"; + + uint32_t dataSize = bSize * nbBlocks; + + // create and init test data + Data_t data = Data_t("test_data", dataSize); + Kokkos::parallel_for( + "init_test_data", Kokkos::RangePolicy(0, dataSize), KOKKOS_LAMBDA(uint32_t i) { + data(i) = i; + }); + + // Kokkos::fence(); + // Kokkos::parallel_for("print_results", Kokkos::RangePolicy(0, dataSize), + // KOKKOS_LAMBDA(uint32_t i) { + // std::cout << i << " " << data(i) << "\n"; + // }); + + TestKokkosTeamVectorForFunctor::apply(data, bSize); + + Kokkos::fence(); + + Kokkos::parallel_for( + "print_results", Kokkos::RangePolicy(0, dataSize), KOKKOS_LAMBDA(uint32_t i) { + // std::cout << i << " " << data(i) << "\n"; + printf("%d %d\n", i, data(i)); + }); + } + + /* + * TestKokkosTeamVectorReduceFunctor + */ + { + std::cout << "// ======================================\n"; + std::cout << "Testing TestKokkosTeamVectorReduceFunctor...\n"; + std::cout << "// ======================================\n"; + + uint32_t dataSize = bSize * nbBlocks; + + // create and init test data + Data_t data = Data_t("test_data", dataSize); + Kokkos::parallel_for( + "init_test_data", Kokkos::RangePolicy(0, dataSize), KOKKOS_LAMBDA(uint32_t i) { + data(i) = i; + }); + + // Kokkos::fence(); + // Kokkos::parallel_for("print_results", Kokkos::RangePolicy(0, dataSize), + // KOKKOS_LAMBDA(uint32_t i) { + // std::cout << i << " " << data(i) << "\n"; + // }); + + TestKokkosTeamVectorReduceFunctor::apply(data, bSize); + } + + /* + * TestKokkosTeamVectorReduceFunctor2 - this i a "max" reduction + */ + { + std::cout << "// ======================================\n"; + std::cout << "Testing TestKokkosTeamVectorReduceFunctor2...\n"; + std::cout << "// ======================================\n"; + + uint32_t dataSize = bSize * nbBlocks; + + // create and init test data + Data_t data = Data_t("test_data", dataSize); + Kokkos::parallel_for( + "init_test_data", Kokkos::RangePolicy(0, dataSize), KOKKOS_LAMBDA(uint32_t i) { + data(i) = 12 - (i - 13) * (i - 15); + }); + + TestKokkosTeamVectorReduceFunctor2::apply(data, bSize); + } + +} // run_test + +// ======================================================================= +// ======================================================================= +int +main(int argc, char * argv[]) +{ + + // Create MPI session if MPI enabled +#ifdef KALYPSSO_CORE_USE_MPI + kalypsso::GlobalMpiSession mpiSession(argc, argv); +#endif // KALYPSSO_CORE_USE_MPI + + Kokkos::initialize(argc, argv); + + [[maybe_unused]] int rank = 0; + [[maybe_unused]] int nRanks = 1; + + { + std::cout << "##########################\n"; + std::cout << "KOKKOS CONFIG \n"; + std::cout << "##########################\n"; + + std::ostringstream msg; + std::cout << "Kokkos configuration" << std::endl; + if (Kokkos::hwloc::available()) + { + msg << "hwloc( NUMA[" << Kokkos::hwloc::get_available_numa_count() << "] x CORE[" + << Kokkos::hwloc::get_available_cores_per_numa() << "] x HT[" + << Kokkos::hwloc::get_available_threads_per_core() << "] )" << std::endl; + } + Kokkos::print_configuration(msg); + std::cout << msg.str(); + std::cout << "##########################\n"; + +#ifdef KALYPSSO_CORE_USE_MPI + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &nRanks); +# ifdef KOKKOS_ENABLE_CUDA + { + + // To enable kokkos accessing multiple GPUs don't forget to + // add option "--ndevices=X" where X is the number of GPUs + // you want to use per node. + + // on a large cluster, the scheduler should assign resources + // in a way that each MPI task is mapped to a different GPU + // let's cross-checked that: + + int cudaDeviceId; + cudaGetDevice(&cudaDeviceId); + std::cout << "I'm MPI task #" << rank << " (out of " << nRanks << ")" << " pinned to GPU #" + << cudaDeviceId << "\n"; + } +# endif // KOKKOS_ENABLE_CUDA +#endif // KALYPSSO_CORE_USE_MPI + } // end kokkos config + + uint32_t bSize = 4; + uint32_t nbBlocks = 32; + + using DefaultDevice = + Kokkos::Device; + + run_test(bSize, nbBlocks); + + Kokkos::finalize(); + + return EXIT_SUCCESS; +} diff --git a/test/kokkos/test_morton_key.cpp b/test/kokkos/test_morton_key.cpp new file mode 100644 index 0000000..c604f01 --- /dev/null +++ b/test/kokkos/test_morton_key.cpp @@ -0,0 +1,138 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * This executable is used to test Morton key routines + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +namespace kalypsso +{ + +/* + * + * Main test using scheme order as template parameter. + * order is the number of solution points per direction. + * + */ +void +test_morton_2d() +{ + + std::cout << "===========\n"; + std::cout << "=====2D====\n"; + std::cout << "===========\n"; + + uint32_t ix = 15; + uint32_t iy = 7; + + uint64_t ix_s = splitBy3<2>(ix); + uint64_t iy_s = splitBy3<2>(iy); + + std::cout << "Coord: " << ix << "," << iy << "\n"; + std::cout << "splitBy3<2>(" << ix << "=" << std::bitset<4>(ix) << ")=" << ix_s << "=" + << std::bitset<12>(ix_s) << "\n"; + std::cout << "splitBy3<2>(" << iy << "=" << std::bitset<4>(iy) << ")=" << iy_s << "=" + << std::bitset<12>(iy_s) << "\n"; + + std::cout << "Morton index of (" << ix << "," << iy << ") = " << compute_morton_key(ix, iy) << "=" + << std::bitset<8>(compute_morton_key(ix, iy)) << "\n"; + + + uint64_t key = compute_morton_key(ix, iy); + std::cout << "Reverse morton key:\n"; + std::cout << "key = " << key << " ==> extracted x,y = " << morton_extract_bits<2, IX>(key) << "," + << morton_extract_bits<2, IY>(key) << "\n"; + +} // test_morton_2d + + +void +test_morton_3d() +{ + + std::cout << "===========\n"; + std::cout << "=====3D====\n"; + std::cout << "===========\n"; + + uint32_t ix = 15; + uint64_t ix_s = splitBy3<3>(ix); + uint32_t iy = 7; + uint64_t iy_s = splitBy3<3>(iy); + uint32_t iz = 2; + uint64_t iz_s = splitBy3<3>(iz); + + std::cout << "splitBy3<3>(" << ix << "=" << std::bitset<4>(ix) << ")=" << ix_s << "=" + << std::bitset<12>(ix_s) << "\n"; + std::cout << "splitBy3<3>(" << iy << "=" << std::bitset<4>(iy) << ")=" << iy_s << "=" + << std::bitset<12>(iy_s) << "\n"; + std::cout << "splitBy3<3>(" << iz << "=" << std::bitset<4>(iz) << ")=" << iz_s << "=" + << std::bitset<12>(iz_s) << "\n"; + + std::cout << "Morton index of (" << ix << "," << iy << "," << iz + << ") = " << compute_morton_key(ix, iy, iz) << "=" + << std::bitset<12>(compute_morton_key(ix, iy, iz)) << "\n"; + + uint64_t key = compute_morton_key(ix, iy, iz); + std::cout << "Reverse morton key:\n"; + std::cout << "key = " << key << " ==> extracted x,y,z = " << morton_extract_bits<3, IX>(key) + << "," << morton_extract_bits<3, IY>(key) << "," << morton_extract_bits<3, IZ>(key) + << "\n"; + +} // test_morton_3d + +} // namespace kalypsso + +/*************************************************/ +/*************************************************/ +/*************************************************/ +int +main(int argc, char * argv[]) +{ + + Kokkos::initialize(argc, argv); + + { + std::cout << "##########################\n"; + std::cout << "KOKKOS CONFIG \n"; + std::cout << "##########################\n"; + + std::ostringstream msg; + std::cout << "Kokkos configuration" << std::endl; + if (Kokkos::hwloc::available()) + { + msg << "hwloc( NUMA[" << Kokkos::hwloc::get_available_numa_count() << "] x CORE[" + << Kokkos::hwloc::get_available_cores_per_numa() << "] x HT[" + << Kokkos::hwloc::get_available_threads_per_core() << "] )" << std::endl; + } + Kokkos::print_configuration(msg); + + std::cout << msg.str(); + std::cout << "##########################\n"; + } + + + // instantiate some tests + kalypsso::test_morton_2d(); + kalypsso::test_morton_3d(); + + Kokkos::finalize(); + + return EXIT_SUCCESS; + +} // end main diff --git a/test/kokkos/test_parallel_scan.cpp b/test/kokkos/test_parallel_scan.cpp new file mode 100644 index 0000000..32a752d --- /dev/null +++ b/test/kokkos/test_parallel_scan.cpp @@ -0,0 +1,287 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * This executable is used to test kokkos parallel_scan + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifdef KALYPSSO_CORE_USE_MPI +# include +#endif // KALYPSSO_CORE_USE_MPI +#include + +// ================================================================================ +// ================================================================================ +/* + * + * Main test using scheme order as template parameter. + * order is the number of solution points per direction. + * + */ +template +void +test_parallel_scan(int N) +{ + // initialize data + using data_t = Kokkos::View; + auto data = data_t("some data", N); + Kokkos::parallel_for( + "InitializeData", Kokkos::RangePolicy(0, N), KOKKOS_LAMBDA(const int & i) { + data(i) = i % 2 ? 1 : -1; + }); + + auto data_host = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, data); + + auto scanned_data = data_t("scanned_data", N); + + int64_t result; + + Kokkos::parallel_scan( + "Parallel_scan", + Kokkos::RangePolicy(0, N), + KOKKOS_LAMBDA(const int & i, int64_t & partial_sum, const bool is_final) { + if (is_final) + { + scanned_data(i) = partial_sum; + } + + partial_sum += data(i); + }, + result); + + auto scanned_data_host = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, scanned_data); + + for (int i = 0; i < N; ++i) + printf("[%d] %ld %ld\n", i, data_host(i), scanned_data_host(i)); + +} // test_parallel_scan + +// ================================================================================ +// ================================================================================ +/* + * + * variant test: just performing 2 interleaved scans + * + */ +template +struct CustomScan +{ + using exec_space_t = exec_space; + using data_t = Kokkos::View; + data_t data_in, data_out; + + CustomScan(data_t data1, data_t data2) + : data_in(data1) + , data_out(data2) + {} + + static void + run(data_t data1, data_t data2) + { + auto functor = CustomScan(data1, data2); + const auto N = data1.size(); + + Kokkos::parallel_scan("CustomScan", Kokkos::RangePolicy(0, N), functor); + } + + struct Res + { + int64_t res1; + int64_t res2; + Res() + : res1(0) + , res2(0) + {} + }; + + KOKKOS_INLINE_FUNCTION + void + init(Res & update) const + { + update.res1 = 0; + update.res2 = 0; + } + + KOKKOS_INLINE_FUNCTION + void + join(Res & update, const Res & input) const + { + update.res1 += input.res1; + update.res2 += input.res2; + } + + KOKKOS_INLINE_FUNCTION + void + operator()(const size_t i, Res & update, const bool is_final) const + { + if (i % 2 == 0) + { + if (is_final) + { + data_out(i) = update.res1; + } + update.res1 += data_in(i); + } + else + { + if (is_final) + { + data_out(i) = update.res2; + } + update.res2 += data_in(i); + } + } +}; // struct CustomScan + +// ================================================================================ +// ================================================================================ +template +void +test_parallel_custom_scan(int N) +{ + // initialize data + using data_t = Kokkos::View; + auto data = data_t("some data", N); + Kokkos::parallel_for( + "InitializeData", Kokkos::RangePolicy(0, N), KOKKOS_LAMBDA(const int & i) { + // data(i) = i % 2 ? 1 : -1; + data(i) = 2 * i; + if (i % 5 == 0) + data(i) = 0; + }); + + auto data_host = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, data); + + auto scanned_data = data_t("scanned_data", N); + + CustomScan::run(data, scanned_data); + + auto scanned_data_host = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, scanned_data); + + for (int i = 0; i < N; ++i) + printf("[%d] %ld %ld\n", i, data_host(i), scanned_data_host(i)); + +} // test_parallel_custom_scan + +// ================================================================================ +// ================================================================================ +/* + * A custom scan operation where input data will be modified only if it is zero. + * In that case, data will be replaced by the last non-zero value + * + */ +template +struct CustomScan2 +{ + + using exec_space_t = exec_space; + using data_t = Kokkos::View; + data_t data; + using value_t = typename data_t::value_type; + + CustomScan2(data_t data_) + : data(data_) + {} + + static void + run(data_t data_) + { + const auto N = data_.size(); + + Kokkos::parallel_scan( + "CustomScan2", Kokkos::RangePolicy(0, N), CustomScan2(data_)); + } + + KOKKOS_INLINE_FUNCTION + void + join(value_t & update, const value_t & input) const + { + if (input > update) + update = input; + } + + KOKKOS_INLINE_FUNCTION + void + operator()(const size_t i, value_t & update, const bool is_final) const + { + value_t value = data(i); + if (value != 0) + { + update = value; + } + else if (is_final) + { + data(i) = value_t(update); + } + } +}; // struct CustomScan2 + +// ================================================================================ +// ================================================================================ +template +void +test_parallel_custom_scan2(int N) +{ + // initialize data + using data_t = Kokkos::View; + auto data = data_t("some data", N); + Kokkos::parallel_for( + "InitializeData", Kokkos::RangePolicy(0, N), KOKKOS_LAMBDA(const int & i) { + // data(i) = i % 2 ? 1 : -1; + data(i) = N - 2 * i; + if (i % 5 == 0) + data(i) = 0; + }); + + auto data_host = Kokkos::create_mirror(Kokkos::HostSpace{}, data); + Kokkos::deep_copy(data_host, data); + + CustomScan2::run(data); + + auto scanned_data_host = Kokkos::create_mirror(Kokkos::HostSpace{}, data); + Kokkos::deep_copy(scanned_data_host, data); + + for (int i = 0; i < N; ++i) + printf("[%d] %ld %ld\n", i, data_host(i), scanned_data_host(i)); + +} // test_parallel_custom_scan + +// ================================================================================ +// ================================================================================ +// ================================================================================ +int +main(int argc, char * argv[]) +{ + + const int N = argc > 1 ? std::atoi(argv[1]) : 100; + + { + kalypsso::ParallelEnv par_env(argc, argv); + // instantiate tests + std::cout << "==================================================\n"; + std::cout << "Regular scan:\n"; + test_parallel_scan(N); + + std::cout << "==================================================\n"; + std::cout << "Custom scan:\n"; + test_parallel_custom_scan(N); + + std::cout << "==================================================\n"; + std::cout << "Custom scan2:\n"; + test_parallel_custom_scan2(N); + } + + return EXIT_SUCCESS; +} diff --git a/test/kokkos/test_unordered_map.cpp b/test/kokkos/test_unordered_map.cpp new file mode 100644 index 0000000..7b901d6 --- /dev/null +++ b/test/kokkos/test_unordered_map.cpp @@ -0,0 +1,201 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * This executable is used to test Kokkos::UnorderedMap + */ + +#include +#include +#include +#include +#include + +#include "kalypsso/core/real_type.h" +#include "kalypsso/core/kokkos_shared.h" +#include "kalypsso/core/enums.h" + +#include + +using DataMap = Kokkos::UnorderedMap; + +/** + * Functor to fill a Kokkos::UnorderedMap + */ +template +struct fill_map +{ + + DataMap dataMap; + int init_size; + + fill_map(DataMap i_dataMap, int i_init_size) + : dataMap(i_dataMap) + , init_size(i_init_size) + { + Kokkos::parallel_for(init_size, *this); + } + + /* + * 2D version. + */ + //! functor for 2d + template + KOKKOS_INLINE_FUNCTION void + operator()(const typename std::enable_if::type & i) const + { + uint64_t key = (uint64_t)i; + uint64_t value = 2 * key; + dataMap.insert(key, value); + } + + /* + * 3D version. + */ + //! functor for 3d + template + KOKKOS_INLINE_FUNCTION void + operator()(const typename std::enable_if::type & i) const + { + uint64_t key = (uint64_t)i; + uint64_t value = 3 * key; + dataMap.insert(key, value); + } + +}; // struct fill_map + + +/* + * + * Main test using scheme order as template parameter. + * order is the number of solution points per direction. + * + */ +void +test_unordered_map_2d(int N) +{ + + std::cout << "===========\n"; + std::cout << "=====2D====\n"; + std::cout << "===========\n"; + + DataMap dataMap(2 * N * N); + + std::cout << "dataMap.size() = " << dataMap.size() << std::endl; + std::cout << "dataMap.capacity() = " << dataMap.capacity() << " (max size)" << std::endl; + + fill_map<2> fill(dataMap, N * N); + + std::cout << "After fill_map\n"; + std::cout << "dataMap.size() = " << dataMap.size() << std::endl; + std::cout << "dataMap.capacity() = " << dataMap.capacity() << " (max size)" << std::endl; + + DataMap::HostMirror dataMapOnHost(dataMap.capacity()); + Kokkos::deep_copy(dataMapOnHost, dataMap); + + for (std::size_t i = 0; i < dataMapOnHost.capacity(); ++i) + { + if (dataMapOnHost.valid_at(i)) + { + std::cout << i << " " << "dataMapOnHost[" << dataMapOnHost.key_at(i) + << "]=" << dataMapOnHost.value_at(i) << "\n"; + } + } + + // resize hashmap + dataMapOnHost.rehash(dataMap.capacity() * 2); + + // print again (should be unchanged) + std::cout << "Print again after rehash.... (key_at have changed)\n"; + for (std::size_t i = 0; i < dataMapOnHost.capacity(); ++i) + { + if (dataMapOnHost.valid_at(i)) + { + std::cout << i << " " << "dataMapOnHost[" << dataMapOnHost.key_at(i) + << "]=" << dataMapOnHost.value_at(i) << "\n"; + } + } + + // print using Kokkos + if constexpr (0 == 1) + { + // std::cout << "Printing dataMap using Kokkos::Impl::UnorderedMapPrint:\n"; + Kokkos::Impl::UnorderedMapPrint printer(dataMap); + printer.apply(); + } + +} // test_unordered_map_2d + + +void +test_unordered_map_3d(int N) +{ + + std::cout << "===========\n"; + std::cout << "=====3D====\n"; + std::cout << "===========\n"; + + DataMap dataMap(2 * N * N * N); + + std::cout << "dataMap.size() = " << dataMap.size() << std::endl; + std::cout << "dataMap.capacity() = " << dataMap.capacity() << " (max size)" << std::endl; + + fill_map<3> fill(dataMap, N * N * N); + + std::cout << "After fill_map\n"; + std::cout << "dataMap.size() = " << dataMap.size() << std::endl; + std::cout << "dataMap.capacity() = " << dataMap.capacity() << " (max size)" << std::endl; + + DataMap::HostMirror dataMapOnHost(dataMap.capacity()); + Kokkos::deep_copy(dataMapOnHost, dataMap); + + for (std::size_t i = 0; i < dataMapOnHost.capacity(); ++i) + { + if (dataMapOnHost.valid_at(i)) + { + std::cout << i << " " << "dataMapOnHost[" << dataMapOnHost.key_at(i) + << "]=" << dataMapOnHost.value_at(i) << "\n"; + } + } + +} // test_unordered_map_3d + +/*************************************************/ +/*************************************************/ +/*************************************************/ +int +main(int argc, char * argv[]) +{ + + Kokkos::initialize(argc, argv); + + { + std::cout << "##########################\n"; + std::cout << "KOKKOS CONFIG \n"; + std::cout << "##########################\n"; + + std::ostringstream msg; + std::cout << "Kokkos configuration" << std::endl; + if (Kokkos::hwloc::available()) + { + msg << "hwloc( NUMA[" << Kokkos::hwloc::get_available_numa_count() << "] x CORE[" + << Kokkos::hwloc::get_available_cores_per_numa() << "] x HT[" + << Kokkos::hwloc::get_available_threads_per_core() << "] )" << std::endl; + } + Kokkos::print_configuration(msg); + + std::cout << msg.str(); + std::cout << "##########################\n"; + } + + + // instantiate some tests + test_unordered_map_2d(5); + + test_unordered_map_3d(4); + + Kokkos::finalize(); + + return EXIT_SUCCESS; +} diff --git a/test/monitoring/CMakeLists.txt b/test/monitoring/CMakeLists.txt new file mode 100644 index 0000000..a9c68e4 --- /dev/null +++ b/test/monitoring/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(test_profiling test_profiling.cpp) + +target_link_libraries(test_profiling PUBLIC kalypsso::core kalypsso::config kalypsso::monitoring) + +configure_file(test_config.ini test_config.ini COPYONLY) diff --git a/test/monitoring/test_config.ini b/test/monitoring/test_config.ini new file mode 100644 index 0000000..a9f6508 --- /dev/null +++ b/test/monitoring/test_config.ini @@ -0,0 +1,71 @@ +[run] +dimension=2 +tEnd=0.5 +nStepmax=10 + +# noutput equals -1 means we dump data at every time steps +nOutput=-1 + +[amr] +level_min=4 +level_max=6 + +use_block_data=yes +bx=4 +by=4 + +# init step may contain calls to globalRefine +# if the following option is true, we interleave +# calls to load balancing in between globalRefine +# if the following option is false, a single load +# balancing operation is done at the end of +# globalRefine steps +enable_load_balance_during_init=false + +epsilon_refine=0.01 +epsilon_coarsen=0.03 + +connectivity = brick + +[p4est_connectivity] +nbrick_x=3 +nbrick_y=2 + +periodic_x=1 +periodic_y=1 + +[mesh] +xmin=0.0 +ymin=0.0 +scaling_factor=1.0 + +boundary_type_xmin=WALL +boundary_type_xmax=WALL + +boundary_type_ymin=WALL +boundary_type_ymax=WALL + +[hydro] +gamma0=1.666 +cfl=0.8 +niter_riemann=10 +slope_type=2 +problem=blast +riemann=HLLC + +[blast] +density_in=1.0 +density_out=1.2 +radius=0.25 + +[output] +outputDir=./ +outputPrefix=test_hdf5 +outputVtkAscii=false +write_variables=rho,rho_vx +write_mesh_info=yes +vtk_enabled=no +hdf5_enabled=yes + +[other] +implementationVersion=0 diff --git a/test/monitoring/test_profiling.cpp b/test/monitoring/test_profiling.cpp new file mode 100644 index 0000000..f6d3fa2 --- /dev/null +++ b/test/monitoring/test_profiling.cpp @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * Just checking one can use nvtx annotations regardless Kokkos backend used (OpenMP or CUDA). + */ + +#include + +#include +#include + +#include +#include + +namespace kalypsso +{ +template +void +run_test(ParallelEnv const & par_env) +{ + using exec_space = typename device_t::execution_space; + using data_t = Kokkos::View; + + ProfilingManager profiling_mgr(par_env); + + profiling_mgr.get_whole_region().start(); + + auto & timer = + profiling_mgr.get_region("Kalypsso::Alloc", ProfilingRegion::TIMER_DEVICE, Color_t::FullBlue()); + timer.start(); + const int data_size = 1000000; + data_t data("some data", data_size); + timer.stop(); + + auto & timer2 = profiling_mgr.get_region( + "Kalypsso::Compute", ProfilingRegion::TIMER_DEVICE, Color_t::FullRed()); + timer2.start(); + Kokkos::parallel_for( + "test", Kokkos::RangePolicy(0, data_size), KOKKOS_LAMBDA(const int i) { + data(i) = 4. * i * i - 8. * i + Kokkos::sin(5.2 * i); + }); + timer2.stop(); + + auto & timer3 = profiling_mgr.get_region( + "Kalypsso::Result", ProfilingRegion::TIMER_DEVICE, Color_t::FullGreen()); + timer3.start(); + auto data_host = Kokkos::create_mirror_view(data); + printf("data(%d)=%f\n", 42, data_host(42)); + timer3.stop(); + + profiling_mgr.get_whole_region().stop(); + + profiling_mgr.print_timings(); + +} // run_test + +} // namespace kalypsso + +// ============================================================================= +// ============================================================================= +// ============================================================================= +int +main(int argc, char * argv[]) +{ + // initialize mpi, kokkos and p4est, use default MPI communicator : MPI_COMM_WORLD + kalypsso::ParallelEnv par_env(argc, argv); + + { + kalypsso::run_test(par_env); + } + + return EXIT_SUCCESS; +} diff --git a/test/p4est_simple/CMakeLists.txt b/test/p4est_simple/CMakeLists.txt new file mode 100644 index 0000000..edd26da --- /dev/null +++ b/test/p4est_simple/CMakeLists.txt @@ -0,0 +1,44 @@ +add_executable(simple_2d "") +target_sources(simple_2d PUBLIC simple_2d.cpp) +target_link_libraries(simple_2d kalypsso::p4est_wrap kalypsso::config kalypsso::core_config) + +# set(TEST_P4EST_CONFIGS unit three evil evil3 pillow moebius star cubed disk periodic rotwrap +# disk2d shell2d) +set(TEST_P4EST_CONFIGS unit shell2d) + +foreach(test_p4est_config IN LISTS TEST_P4EST_CONFIGS) + add_test(NAME p4est_simple_${test_p4est_config} + COMMAND ${TEST_MPI_PREFIX_CMD} simple_2d ${test_p4est_config} + ${KALYPSSO_TEST_AMR_MAX_LEVEL}) + set_property(TEST p4est_simple_${test_p4est_config} PROPERTY LABELS p4est 2D) + + if(Kokkos_ENABLE_OPENMP) + set_property( + TEST p4est_simple_${test_p4est_config} + PROPERTY ENVIRONMENT OMP_NUM_THREADS=${KALYPSSO_TEST_OMP_NUM_THREADS} OMP_PROC_BIND=spread + OMP_PLACES=threads) + endif() + +endforeach() + +add_executable(simple_3d "") +target_sources(simple_3d PUBLIC simple_3d.cpp) +target_link_libraries(simple_3d kalypsso::p4est_wrap kalypsso::config kalypsso::core_config) + +# set(TEST_P8EST_CONFIGS unit periodic rotwrap twocubes twowrap rotcubes shell sphere) +set(TEST_P8EST_CONFIGS unit sphere) + +foreach(test_p8est_config IN LISTS TEST_P8EST_CONFIGS) + add_test(NAME p8est_simple_${test_p8est_config} + COMMAND ${TEST_MPI_PREFIX_CMD} simple_3d ${test_p8est_config} + ${KALYPSSO_TEST_AMR_MAX_LEVEL}) + set_property(TEST p8est_simple_${test_p8est_config} PROPERTY LABELS p8est 3D) + + if(Kokkos_ENABLE_OPENMP) + set_property( + TEST p8est_simple_${test_p8est_config} + PROPERTY ENVIRONMENT OMP_NUM_THREADS=${KALYPSSO_TEST_OMP_NUM_THREADS} OMP_PROC_BIND=spread + OMP_PLACES=threads) + endif() + +endforeach() diff --git a/test/p4est_simple/simple_2d.cpp b/test/p4est_simple/simple_2d.cpp new file mode 100644 index 0000000..d299965 --- /dev/null +++ b/test/p4est_simple/simple_2d.cpp @@ -0,0 +1,549 @@ +// Copyright (C) 2010 The University of Texas System +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/* + * Usage: p4est_simple + * possible configurations: + * o unit Refinement on the unit square. + * o three Refinement on a forest with three trees. + * o evil Check second round of refinement with np=5 level=7 + * o evil3 Check second round of refinement on three trees + * o pillow Refinement on a 2-tree pillow-shaped domain. + * o moebius Refinement on a 5-tree Moebius band. + * o star Refinement on a 6-tree star shaped domain. + * o cubed Refinement on a 6-tree cubed sphere surface. + * o disk Refinement on a 5-tree spherical disk. + * o periodic Refinement on the unit square with all-periodic b.c. + * o rotwrap Refinement on the unit square with weird periodic b.c. + */ + +/* + * Additional geometry defined in CanoP: + * o disk2d Refinement of a 5-tree mapping the unit disk. + * o shell2d Similar to 3d shell + * + * Refinement based on radius. + * + */ + +#include +#include +#include + +#include +#include "connectivity.h" +#include "geometry.h" + +/* default parameters for p4est_vtk_write_file */ +static const int p4est_vtk_write_tree = 1; +static const int p4est_vtk_write_level = 1; +static const int p4est_vtk_write_rank = 1; +static const int p4est_vtk_wrap_rank = 0; + + +typedef enum +{ + P4EST_CONFIG_NULL, + P4EST_CONFIG_UNIT, + P4EST_CONFIG_THREE, + P4EST_CONFIG_EVIL, + P4EST_CONFIG_EVIL3, + P4EST_CONFIG_PILLOW, + P4EST_CONFIG_MOEBIUS, + P4EST_CONFIG_STAR, + P4EST_CONFIG_CUBED, + P4EST_CONFIG_DISK, + P4EST_CONFIG_PERIODIC, + P4EST_CONFIG_ROTWRAP, + P4EST_CONFIG_DISK2D, + P4EST_CONFIG_SHELL2D, +} simple_config_t; + +typedef struct +{ + simple_config_t config; + int mpisize; + int level; + unsigned checksum; +} simple_regression_t; + +typedef struct +{ + p4est_topidx_t a; +} user_data_t; + +typedef struct +{ + sc_MPI_Comm mpicomm; + int mpisize; + int mpirank; +} mpi_context_t; + +static int refine_level = 0; + +/* *INDENT-OFF* */ +// clang-format off +static const simple_regression_t regression[] = +{ { P4EST_CONFIG_THREE, 1, 7, 0xa8d85863U }, + { P4EST_CONFIG_THREE, 2, 7, 0xa8d85863U }, + { P4EST_CONFIG_THREE, 3, 7, 0xa8d85863U }, + { P4EST_CONFIG_THREE, 4, 7, 0x20fb58edU }, + { P4EST_CONFIG_MOEBIUS, 1, 6, 0x98ab6cb2U }, + { P4EST_CONFIG_MOEBIUS, 3, 6, 0x98ab6cb2U }, + { P4EST_CONFIG_MOEBIUS, 5, 6, 0x98ab6cb2U }, + { P4EST_CONFIG_MOEBIUS, 6, 6, 0x6d2d6d6cU }, + { P4EST_CONFIG_STAR, 5, 6, 0x38d3736fU }, + { P4EST_CONFIG_STAR, 5, 7, 0xfb97aadfU }, + { P4EST_CONFIG_CUBED, 4, 3, 0x85581649U }, + { P4EST_CONFIG_CUBED, 5, 5, 0x64a1d105U }, + { P4EST_CONFIG_DISK, 5, 4, 0x4995411dU }, + { P4EST_CONFIG_DISK, 2, 6, 0x3f758706U }, + { P4EST_CONFIG_ROTWRAP, 1, 6, 0x9dd600c5U }, + { P4EST_CONFIG_ROTWRAP, 3, 6, 0x9dd600c5U }, + { P4EST_CONFIG_NULL, 0, 0, 0 } }; +/* *INDENT-ON* */ +// clang-format on + +static void +init_fn(p4est_t * p4est, p4est_topidx_t which_tree, p4est_quadrant_t * quadrant) +{ + user_data_t * data = (user_data_t *)quadrant->p.user_data; + + data->a = which_tree; +} + +static int +refine_normal_fn(p4est_t * p4est, p4est_topidx_t which_tree, p4est_quadrant_t * quadrant) +{ + if ((int)quadrant->level >= (refine_level - (int)(which_tree % 3))) + { + return 0; + } + if (quadrant->level == 1 && p4est_quadrant_child_id(quadrant) == 3) + { + return 1; + } + if (quadrant->x == P4EST_LAST_OFFSET(2) && quadrant->y == P4EST_LAST_OFFSET(2)) + { + return 1; + } + if (quadrant->x >= P4EST_QUADRANT_LEN(2)) + { + return 0; + } + + return 1; +} + +static int +refine_evil_fn(p4est_t * p4est, p4est_topidx_t which_tree, p4est_quadrant_t * quadrant) +{ + if ((int)quadrant->level >= refine_level) + { + return 0; + } + if (p4est->mpirank <= 1) + { + return 1; + } + + return 0; +} + +static int +refine_evil3_fn(p4est_t * p4est, p4est_topidx_t which_tree, p4est_quadrant_t * quadrant) +{ + p4est_qcoord_t u2; + p4est_quadrant_t ref; + + P4EST_QUADRANT_INIT(&ref); + + u2 = P4EST_QUADRANT_LEN(2); + + if (which_tree == 0) + { + ref.x = 3 * u2; + ref.y = 2 * u2; + } + else if (which_tree == 1) + { + ref.x = 2 * u2; + ref.y = 3 * u2; + } + ref.level = 2; + + if ((int)quadrant->level >= refine_level) + { + return 0; + } + if ((which_tree == 0 || which_tree == 1) && + (p4est_quadrant_is_equal(&ref, quadrant) || p4est_quadrant_is_ancestor(&ref, quadrant))) + { + return 1; + } + + return 0; +} + +static int +coarsen_evil_fn(p4est_t * p4est, p4est_topidx_t which_tree, p4est_quadrant_t * q[]) +{ + if (p4est->mpirank >= 2) + { + return 1; + } + + return 0; +} + +static int +refine_radius_fn(p4est_t * p4est, p4est_topidx_t which_tree, p4est_quadrant_t * quadrant) +{ + + /* stop criterion */ + if ((int)quadrant->level >= refine_level) + { + return 0; + } + + p4est_geometry_t * geom = (p4est_geometry_t *)p4est->user_pointer; + + /* logical coordinates */ + double xyz[3] = { 0, 0, 0 }; + + /* physical coordinates (after geometry mapping )*/ + double XYZ[3] = { 0, 0, 0 }; + + /* half-size of the cell in logical coordinate */ + double h2 = 0.5 * P4EST_QUADRANT_LEN(quadrant->level) / P4EST_ROOT_LEN; + const double intsize = 1.0 / P4EST_ROOT_LEN; + + /* + * get coordinates at cell center + */ + xyz[0] = intsize * quadrant->x + h2; + xyz[1] = intsize * quadrant->y + h2; +#ifdef P4_TO_P8 + xyz[2] = intsize * quadrant->z + h2; +#endif + + // apply mapping from logical coordinates to physical coordinates + geom->X(geom, which_tree, xyz, XYZ); + + double radius = sqrt(XYZ[0] * XYZ[0] + XYZ[1] * XYZ[1]); + + /*if (radius<0.5) + return 0;*/ + if (radius < 0.7 and quadrant->level > 4) + return 0; + if (radius < 0.8 and quadrant->level > 5) + return 0; + if (radius < 0.9 and quadrant->level > 6) + return 0; + + return 1; +} + +/* + * A slightly modified output routine, to be able to change the scale parameter. + */ +void +my_p4est_vtk_write_file(p4est_t * p4est, + p4est_geometry_t * geom, + const char * filename, + double scale) +{ + int retval; + p4est_vtk_context_t * cont; + + /* allocate context and set parameters */ + cont = p4est_vtk_context_new(p4est, filename); + + p4est_vtk_context_set_scale(cont, scale); + + p4est_vtk_context_set_geom(cont, geom); + + /* We do not write point data, so it is safe to set continuous to true. + * This will not save any space though since the default scale is < 1. */ + p4est_vtk_context_set_continuous(cont, 1); + + /* write header, that is, vertex positions and quadrant-to-vertex map */ + cont = p4est_vtk_write_header(cont); + SC_CHECK_ABORT(cont != NULL, P4EST_STRING "_vtk: Error writing header"); + + /* write the tree/level/rank data */ + cont = p4est_vtk_write_cell_dataf(cont, + p4est_vtk_write_tree, + p4est_vtk_write_level, + p4est_vtk_write_rank, + p4est_vtk_wrap_rank, + 0, + 0, + cont); + SC_CHECK_ABORT(cont != NULL, P4EST_STRING "_vtk: Error writing cell data"); + + /* properly write rest of the files' contents */ + retval = p4est_vtk_write_footer(cont); + SC_CHECK_ABORT(!retval, P4EST_STRING "_vtk: Error writing footer"); +} + + +int +main(int argc, char ** argv) +{ + int mpiret; + int wrongusage; + unsigned crc; + const char * usage; + mpi_context_t mpi_context, *mpi = &mpi_context; + p4est_t * p4est; + p4est_connectivity_t * connectivity; + p4est_geometry_t * geom; + p4est_refine_t refine_fn; + p4est_coarsen_t coarsen_fn; + simple_config_t config; + const simple_regression_t * r; + + /* initialize MPI and p4est internals */ + mpiret = sc_MPI_Init(&argc, &argv); + SC_CHECK_MPI(mpiret); + mpi->mpicomm = sc_MPI_COMM_WORLD; + mpiret = sc_MPI_Comm_size(mpi->mpicomm, &mpi->mpisize); + SC_CHECK_MPI(mpiret); + mpiret = sc_MPI_Comm_rank(mpi->mpicomm, &mpi->mpirank); + SC_CHECK_MPI(mpiret); + + sc_init(mpi->mpicomm, 1, 1, NULL, SC_LP_DEFAULT); + p4est_init(NULL, SC_LP_DEFAULT); + + /* process command line arguments */ + usage = "Arguments: \n" + " Configuration can be any of\n" + " unit|three|evil|evil3|pillow|moebius|\n" + " star|cubed|disk|periodic|rotwrap|disk2d|shell2d\n" + " Level controls the maximum depth of refinement\n"; + wrongusage = 0; + config = P4EST_CONFIG_NULL; + if (!wrongusage && argc < 3) + { + wrongusage = 1; + } + if (!wrongusage) + { + if (!strcmp(argv[1], "unit")) + { + config = P4EST_CONFIG_UNIT; + } + else if (!strcmp(argv[1], "three")) + { + config = P4EST_CONFIG_THREE; + } + else if (!strcmp(argv[1], "evil")) + { + config = P4EST_CONFIG_EVIL; + } + else if (!strcmp(argv[1], "evil3")) + { + config = P4EST_CONFIG_EVIL3; + } + else if (!strcmp(argv[1], "pillow")) + { + config = P4EST_CONFIG_PILLOW; + } + else if (!strcmp(argv[1], "moebius")) + { + config = P4EST_CONFIG_MOEBIUS; + } + else if (!strcmp(argv[1], "star")) + { + config = P4EST_CONFIG_STAR; + } + else if (!strcmp(argv[1], "cubed")) + { + config = P4EST_CONFIG_CUBED; + } + else if (!strcmp(argv[1], "disk")) + { + config = P4EST_CONFIG_DISK; + } + else if (!strcmp(argv[1], "periodic")) + { + config = P4EST_CONFIG_PERIODIC; + } + else if (!strcmp(argv[1], "rotwrap")) + { + config = P4EST_CONFIG_ROTWRAP; + } + else if (!strcmp(argv[1], "disk2d")) + { + config = P4EST_CONFIG_DISK2D; + } + else if (!strcmp(argv[1], "shell2d")) + { + config = P4EST_CONFIG_SHELL2D; + } + else + { + wrongusage = 1; + } + } + if (wrongusage) + { + P4EST_GLOBAL_LERROR(usage); + sc_abort_collective("Usage error"); + } + + /* assign variables based on configuration */ + refine_level = atoi(argv[2]); + geom = NULL; + + if (config == P4EST_CONFIG_EVIL) + { + refine_fn = refine_evil_fn; + coarsen_fn = coarsen_evil_fn; + } + else if (config == P4EST_CONFIG_EVIL3) + { + refine_fn = refine_evil3_fn; + coarsen_fn = NULL; + } + else + { + refine_fn = refine_normal_fn; + coarsen_fn = NULL; + } + + /* create connectivity and forest structures */ + if (config == P4EST_CONFIG_THREE || config == P4EST_CONFIG_EVIL3) + { + connectivity = p4est_connectivity_new_corner(); + } + else if (config == P4EST_CONFIG_PILLOW) + { + connectivity = p4est_connectivity_new_pillow(); + } + else if (config == P4EST_CONFIG_MOEBIUS) + { + connectivity = p4est_connectivity_new_moebius(); + } + else if (config == P4EST_CONFIG_STAR) + { + connectivity = p4est_connectivity_new_star(); + } + else if (config == P4EST_CONFIG_CUBED) + { + connectivity = p4est_connectivity_new_cubed(); + } + else if (config == P4EST_CONFIG_DISK) + { +#if defined(KALYPSSO_CORE_USE_OLD_P4EST_API) + connectivity = p4est_connectivity_new_disk(); +#else + // non periodic disk + connectivity = p4est_connectivity_new_disk(0, 0); +#endif // USE_OLD_P4EST_API + } + else if (config == P4EST_CONFIG_PERIODIC) + { + connectivity = p4est_connectivity_new_periodic(); + } + else if (config == P4EST_CONFIG_ROTWRAP) + { + connectivity = p4est_connectivity_new_rotwrap(); + } + else if (config == P4EST_CONFIG_DISK2D) + { + double R0 = 0.44; + double R1 = 1.0; + if (argc >= 4) + R0 = atof(argv[3]); + if (argc >= 5) + R1 = atof(argv[4]); + connectivity = p4est_connectivity_new_disk2d(); + geom = p4est_geometry_new_disk2d(connectivity, R0, R1); + refine_fn = refine_radius_fn; + coarsen_fn = NULL; + } + else if (config == P4EST_CONFIG_SHELL2D) + { + double R0 = 0.44; + double R1 = 1.0; + if (argc >= 4) + R0 = atof(argv[3]); + if (argc >= 5) + R1 = atof(argv[4]); + connectivity = p4est_connectivity_new_shell2d(); + geom = p4est_geometry_new_shell2d(connectivity, R0, R1); + refine_fn = refine_radius_fn; + coarsen_fn = NULL; + } + else + { + connectivity = p4est_connectivity_new_unitsquare(); + } + + /* + * set geometry as p4est user pointer so that we can + * retrieve it in the callbacks. + */ + p4est = p4est_new_ext(mpi->mpicomm, connectivity, 15, 0, 0, sizeof(user_data_t), init_fn, geom); + + double scale = 1.0; + + my_p4est_vtk_write_file(p4est, geom, "simple_2d_new", scale); + + /* refinement and coarsening */ + p4est_refine(p4est, 1, refine_fn, init_fn); + if (coarsen_fn != NULL) + { + p4est_coarsen(p4est, 1, coarsen_fn, init_fn); + } + my_p4est_vtk_write_file(p4est, geom, "simple_2d_refined", scale); + + /* balance */ + p4est_balance(p4est, P4EST_CONNECT_FULL, init_fn); + my_p4est_vtk_write_file(p4est, geom, "simple_2d_balanced", scale); + crc = p4est_checksum(p4est); + + /* partition */ + p4est_partition(p4est, 0, NULL); + my_p4est_vtk_write_file(p4est, geom, "simple_2d_partition", scale); + +#ifdef P4EST_ENABLE_DEBUG + /* rebalance should not change checksum */ + p4est_balance(p4est, P4EST_CONNECT_FULL, init_fn); + P4EST_ASSERT(p4est_checksum(p4est) == crc); +#endif + + /* print and verify forest checksum */ + P4EST_GLOBAL_STATISTICSF("Tree checksum 0x%08x\n", crc); + if (mpi->mpirank == 0) + { + for (r = regression; r->config != P4EST_CONFIG_NULL; ++r) + { + if (r->config != config || r->mpisize != mpi->mpisize || r->level != refine_level) + continue; + SC_CHECK_ABORT(crc == r->checksum, "Checksum mismatch"); + P4EST_GLOBAL_INFO("Checksum regression OK\n"); + break; + } + } + + /* destroy the p4est and its connectivity structure */ + p4est_destroy(p4est); + if (geom != NULL) + { + p4est_geometry_destroy(geom); + } + p4est_connectivity_destroy(connectivity); + + /* clean up and exit */ + sc_finalize(); + + mpiret = sc_MPI_Finalize(); + SC_CHECK_MPI(mpiret); + + return 0; +} diff --git a/test/p4est_simple/simple_3d.cpp b/test/p4est_simple/simple_3d.cpp new file mode 100644 index 0000000..0f7e8e8 --- /dev/null +++ b/test/p4est_simple/simple_3d.cpp @@ -0,0 +1,447 @@ +// Copyright (C) 2010 The University of Texas System +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/* + * Usage: p8est_simple + * possible configurations: + * o unit The unit cube. + * o periodic The unit cube with all-periodic boundary conditions. + * o rotwrap The unit cube with various self-periodic b.c. + * o twocubes Two connected cubes. + * o twowrap Two cubes with periodically identified far ends. + * o rotcubes A collection of six connected rotated cubes. + * o shell A 24-tree discretization of a hollow sphere. + * o sphere A 13-tree discretization of a solid sphere. + */ + +#define VTK_OUTPUT 1 + +#include +#include + +#ifdef VTK_OUTPUT +# include +#endif + +/* default parameters for p8est_vtk_write_file */ +static const int p8est_vtk_write_tree = 1; +static const int p8est_vtk_write_level = 1; +static const int p8est_vtk_write_rank = 1; +static const int p8est_vtk_wrap_rank = 0; + + +typedef enum +{ + P8EST_CONFIG_NULL, + P8EST_CONFIG_UNIT, + P8EST_CONFIG_PERIODIC, + P8EST_CONFIG_ROTWRAP, + P8EST_CONFIG_TWOCUBES, + P8EST_CONFIG_TWOWRAP, + P8EST_CONFIG_ROTCUBES, + P8EST_CONFIG_SHELL, + P8EST_CONFIG_SPHERE +} simple_config_t; + +typedef struct +{ + simple_config_t config; + int mpisize; + int level; + unsigned checksum; +} simple_regression_t; + +typedef struct +{ + p4est_topidx_t a; +} user_data_t; + +typedef struct +{ + sc_MPI_Comm mpicomm; + int mpisize; + int mpirank; +} mpi_context_t; + +static int refine_level = 0; + +/* *INDENT-OFF* */ +// clang-format off +static const simple_regression_t regression[] = +{ { P8EST_CONFIG_UNIT, 1, 7, 0x88fc2229U }, + { P8EST_CONFIG_UNIT, 3, 6, 0xce19fee3U }, + { P8EST_CONFIG_TWOCUBES, 1, 4, 0xd9e96b31U }, + { P8EST_CONFIG_TWOCUBES, 3, 5, 0xe8b16b4aU }, + { P8EST_CONFIG_TWOWRAP, 1, 4, 0xd3e06e2fU }, + { P8EST_CONFIG_TWOWRAP, 5, 5, 0x920ecd43U }, + { P8EST_CONFIG_PERIODIC, 1, 4, 0x28304c83U }, + { P8EST_CONFIG_PERIODIC, 7, 4, 0x28304c83U }, + { P8EST_CONFIG_PERIODIC, 3, 5, 0xe4d123b2U }, + { P8EST_CONFIG_PERIODIC, 6, 6, 0x81c22cc6U }, + { P8EST_CONFIG_ROTWRAP, 1, 5, 0xe4d123b2U }, + { P8EST_CONFIG_ROTWRAP, 3, 5, 0xe4d123b2U }, + { P8EST_CONFIG_ROTWRAP, 5, 6, 0x81c22cc6U }, + { P8EST_CONFIG_ROTCUBES, 1, 5, 0x5c497bdaU }, + { P8EST_CONFIG_ROTCUBES, 3, 5, 0x5c497bdaU }, + { P8EST_CONFIG_ROTCUBES, 5, 6, 0x00530556U }, + { P8EST_CONFIG_ROTCUBES, 7, 1, 0x47f00071U }, + { P8EST_CONFIG_ROTCUBES, 7, 6, 0x00530556U }, + { P8EST_CONFIG_ROTCUBES, 7, 7, 0x84730f31U }, + { P8EST_CONFIG_ROTCUBES, 9, 1, 0x00600001U }, + { P8EST_CONFIG_NULL, 0, 0, 0 } }; +/* *INDENT-ON* */ +// clang-format on + +static void +init_fn(p8est_t * p8est, p4est_topidx_t which_tree, p8est_quadrant_t * quadrant) +{ + user_data_t * data = (user_data_t *)quadrant->p.user_data; + + data->a = which_tree; +} + +static int +refine_sparse_fn(p8est_t * p8est, p4est_topidx_t which_tree, p8est_quadrant_t * quadrant) +{ + if (which_tree != 0) + { + return 0; + } + if ((int)quadrant->level >= refine_level) + { + return 0; + } + if (quadrant->level == 0) + { + return 1; + } + if (quadrant->x < P8EST_QUADRANT_LEN(2) && quadrant->y > 0 && quadrant->z < P8EST_QUADRANT_LEN(2)) + { + return 1; + } + + return 0; +} + +static int +refine_normal_fn(p8est_t * p8est, p4est_topidx_t which_tree, p8est_quadrant_t * quadrant) +{ + if ((int)quadrant->level >= (refine_level - (int)(which_tree % 3))) + { + return 0; + } + if (quadrant->level == 1 && p8est_quadrant_child_id(quadrant) == 3) + { + return 1; + } + if (quadrant->x == P8EST_LAST_OFFSET(2) && quadrant->y == P8EST_LAST_OFFSET(2)) + { + return 1; + } + if (quadrant->z >= P8EST_QUADRANT_LEN(2)) + { + return 0; + } + + return 1; +} + +static int +refine_radius_fn(p8est_t * p8est, p4est_topidx_t which_tree, p8est_quadrant_t * quadrant) +{ + + /* stop criterion */ + if ((int)quadrant->level >= refine_level) + { + return 0; + } + + p8est_geometry_t * geom = (p8est_geometry_t *)p8est->user_pointer; + + /* logical coordinates */ + double xyz[3] = { 0, 0, 0 }; + + /* physical coordinates (after geometry mapping )*/ + double XYZ[3] = { 0, 0, 0 }; + + /* half-size of the cell in logical coordinate */ + double h2 = 0.5 * P8EST_QUADRANT_LEN(quadrant->level) / P8EST_ROOT_LEN; + const double intsize = 1.0 / P8EST_ROOT_LEN; + + /* + * get coordinates at cell center + */ + xyz[0] = intsize * quadrant->x + h2; + xyz[1] = intsize * quadrant->y + h2; + xyz[2] = intsize * quadrant->z + h2; + + // apply mapping from logical coordinates to physical coordinates + geom->X(geom, which_tree, xyz, XYZ); + + double radius = sqrt(XYZ[0] * XYZ[0] + XYZ[1] * XYZ[1] + XYZ[2] * XYZ[2]); + + + /*if (radius<0.5) + return 0;*/ + if (radius < 0.7 and quadrant->level > 3) + return 0; + if (radius < 0.85 and quadrant->level > 4) + return 0; + if (radius < 0.95 and quadrant->level > 5) + return 0; + if (radius < 0.99 and quadrant->level > 6) + return 0; + + return 1; +} + +/* + * A slightly modified output routine, to be able to change the scale parameter. + */ +void +my_p8est_vtk_write_file(p8est_t * p8est, + p8est_geometry_t * geom, + const char * filename, + double scale) +{ + int retval; + p8est_vtk_context_t * cont; + + /* allocate context and set parameters */ + cont = p8est_vtk_context_new(p8est, filename); + + p8est_vtk_context_set_scale(cont, scale); + + p8est_vtk_context_set_geom(cont, geom); + + /* We do not write point data, so it is safe to set continuous to true. + * This will not save any space though since the default scale is < 1. */ + p8est_vtk_context_set_continuous(cont, 1); + + /* write header, that is, vertex positions and quadrant-to-vertex map */ + cont = p8est_vtk_write_header(cont); + SC_CHECK_ABORT(cont != NULL, P8EST_STRING "_vtk: Error writing header"); + + /* write the tree/level/rank data */ + cont = p8est_vtk_write_cell_dataf(cont, + p8est_vtk_write_tree, + p8est_vtk_write_level, + p8est_vtk_write_rank, + p8est_vtk_wrap_rank, + 0, + 0, + cont); + SC_CHECK_ABORT(cont != NULL, P8EST_STRING "_vtk: Error writing cell data"); + + /* properly write rest of the files' contents */ + retval = p8est_vtk_write_footer(cont); + SC_CHECK_ABORT(!retval, P8EST_STRING "_vtk: Error writing footer"); +} + +int +main(int argc, char ** argv) +{ + int mpiret; + int wrongusage; + unsigned crc; + const char * usage; + mpi_context_t mpi_context, *mpi = &mpi_context; + p8est_t * p8est; + p8est_connectivity_t * connectivity; + p8est_geometry_t * geom; + p8est_refine_t refine_fn; + p8est_coarsen_t coarsen_fn; + simple_config_t config; + const simple_regression_t * r; + + /* initialize MPI and p4est internals */ + mpiret = sc_MPI_Init(&argc, &argv); + SC_CHECK_MPI(mpiret); + mpi->mpicomm = sc_MPI_COMM_WORLD; + mpiret = sc_MPI_Comm_size(mpi->mpicomm, &mpi->mpisize); + SC_CHECK_MPI(mpiret); + mpiret = sc_MPI_Comm_rank(mpi->mpicomm, &mpi->mpirank); + SC_CHECK_MPI(mpiret); + + sc_init(mpi->mpicomm, 1, 1, NULL, SC_LP_DEFAULT); + p4est_init(NULL, SC_LP_DEFAULT); + + /* process command line arguments */ + usage = "Arguments: \n" + " Configuration can be any of\n" + " unit|periodic|rotwrap|twocubes|twowrap|rotcubes|shell|sphere\n" + " Level controls the maximum depth of refinement\n"; + wrongusage = 0; + config = P8EST_CONFIG_NULL; + if (!wrongusage && argc != 3) + { + wrongusage = 1; + } + if (!wrongusage) + { + if (!strcmp(argv[1], "unit")) + { + config = P8EST_CONFIG_UNIT; + } + else if (!strcmp(argv[1], "periodic")) + { + config = P8EST_CONFIG_PERIODIC; + } + else if (!strcmp(argv[1], "rotwrap")) + { + config = P8EST_CONFIG_ROTWRAP; + } + else if (!strcmp(argv[1], "twocubes")) + { + config = P8EST_CONFIG_TWOCUBES; + } + else if (!strcmp(argv[1], "twowrap")) + { + config = P8EST_CONFIG_TWOWRAP; + } + else if (!strcmp(argv[1], "rotcubes")) + { + config = P8EST_CONFIG_ROTCUBES; + } + else if (!strcmp(argv[1], "shell")) + { + config = P8EST_CONFIG_SHELL; + } + else if (!strcmp(argv[1], "sphere")) + { + config = P8EST_CONFIG_SPHERE; + } + else + { + wrongusage = 1; + } + } + if (wrongusage) + { + P4EST_GLOBAL_LERROR(usage); + sc_abort_collective("Usage error"); + } + + /* assign variables based on configuration */ + refine_level = atoi(argv[2]); + refine_fn = refine_normal_fn; + coarsen_fn = NULL; + + /* create connectivity and forest structures */ + geom = NULL; + if (config == P8EST_CONFIG_PERIODIC) + { + connectivity = p8est_connectivity_new_periodic(); + } + else if (config == P8EST_CONFIG_ROTWRAP) + { + connectivity = p8est_connectivity_new_rotwrap(); + } + else if (config == P8EST_CONFIG_TWOCUBES) + { + connectivity = p8est_connectivity_new_twocubes(); + refine_fn = refine_sparse_fn; + } + else if (config == P8EST_CONFIG_TWOWRAP) + { + connectivity = p8est_connectivity_new_twowrap(); + refine_fn = refine_sparse_fn; + } + else if (config == P8EST_CONFIG_ROTCUBES) + { + connectivity = p8est_connectivity_new_rotcubes(); + } + else if (config == P8EST_CONFIG_SHELL) + { + connectivity = p8est_connectivity_new_shell(); + geom = p8est_geometry_new_shell(connectivity, 1., .44); + refine_fn = refine_radius_fn; + coarsen_fn = NULL; + } + else if (config == P8EST_CONFIG_SPHERE) + { + connectivity = p8est_connectivity_new_sphere(); + // geom = p8est_geometry_new_sphere (connectivity, 1., 0.191728, 0.039856); + geom = p8est_geometry_new_sphere(connectivity, 1., 0.7, 0.5); + refine_fn = refine_radius_fn; + coarsen_fn = NULL; + } + else + { + connectivity = p8est_connectivity_new_unitcube(); + } + + /* + * set geometry as p4est user pointer so that we can + * retrieve it in the callbacks. + */ + p8est = p8est_new_ext(mpi->mpicomm, connectivity, 4, 0, 0, sizeof(user_data_t), init_fn, geom); + + double scale = 1.0; + +#ifdef VTK_OUTPUT + my_p8est_vtk_write_file(p8est, geom, "simple_3d_new", scale); +#endif + + /* refinement and coarsening */ + p8est_refine(p8est, 1, refine_fn, init_fn); + if (coarsen_fn != NULL) + { + p8est_coarsen(p8est, 1, coarsen_fn, init_fn); + } +#ifdef VTK_OUTPUT + my_p8est_vtk_write_file(p8est, geom, "simple_3d_refined", scale); +#endif + + /* balance */ + p8est_balance(p8est, P8EST_CONNECT_FULL, init_fn); +#ifdef VTK_OUTPUT + my_p8est_vtk_write_file(p8est, geom, "simple_3d_balanced", scale); +#endif + + crc = p8est_checksum(p8est); + + /* partition */ + p8est_partition(p8est, 0, NULL); +#ifdef VTK_OUTPUT + my_p8est_vtk_write_file(p8est, geom, "simple_3d_partition", scale); +#endif + +#ifdef P4EST_ENABLE_DEBUG + /* rebalance should not change checksum */ + p8est_balance(p8est, P8EST_CONNECT_FULL, init_fn); + P4EST_ASSERT(p8est_checksum(p8est) == crc); +#endif + + /* print and verify forest checksum */ + P4EST_GLOBAL_STATISTICSF("Tree checksum 0x%08x\n", crc); + if (mpi->mpirank == 0) + { + for (r = regression; r->config != P8EST_CONFIG_NULL; ++r) + { + if (r->config != config || r->mpisize != mpi->mpisize || r->level != refine_level) + continue; + SC_CHECK_ABORT(crc == r->checksum, "Checksum mismatch"); + P4EST_GLOBAL_INFO("Checksum regression OK\n"); + break; + } + } + + /* destroy the p8est and its connectivity structure */ + p8est_destroy(p8est); + if (geom != NULL) + { + p8est_geometry_destroy(geom); + } + p8est_connectivity_destroy(connectivity); + + /* clean up and exit */ + sc_finalize(); + + mpiret = sc_MPI_Finalize(); + SC_CHECK_MPI(mpiret); + + return 0; +} diff --git a/test/p4est_wrapper/CMakeLists.txt b/test/p4est_wrapper/CMakeLists.txt new file mode 100644 index 0000000..28c4f53 --- /dev/null +++ b/test/p4est_wrapper/CMakeLists.txt @@ -0,0 +1,19 @@ +add_executable(test_p4est_wrapper "") +target_sources(test_p4est_wrapper PUBLIC test_p4est_wrapper.cpp) +target_link_libraries(test_p4est_wrapper PUBLIC kalypsso::p4est_wrap kalypsso::config + kalypsso::core_config) + +file(GLOB files_to_remove ${CMAKE_CURRENT_BINARY_DIR}/*.vtu ${CMAKE_CURRENT_BINARY_DIR}/*.pvtu + ${CMAKE_CURRENT_BINARY_DIR}/*.visit) +set_property( + DIRECTORY + APPEND + PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${files_to_remove}) + +add_test(NAME p4est_wrapper_brick COMMAND test_p4est_wrapper brick ${KALYPSSO_TEST_AMR_MAX_LEVEL} 3 + 3) +set_property(TEST p4est_wrapper_brick PROPERTY LABELS p4est_wrapper 2D) + +add_test(NAME p4est_wrapper_brick3 COMMAND test_p4est_wrapper brick3 ${KALYPSSO_TEST_AMR_MAX_LEVEL} + 4 3 2) +set_property(TEST p4est_wrapper_brick3 PROPERTY LABELS p4est_wrapper 3D) diff --git a/test/p4est_wrapper/test_p4est_wrapper.cpp b/test/p4est_wrapper/test_p4est_wrapper.cpp new file mode 100644 index 0000000..b71be57 --- /dev/null +++ b/test/p4est_wrapper/test_p4est_wrapper.cpp @@ -0,0 +1,1014 @@ +// Copyright (C) 2010 The University of Texas System +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/* + * Usage: test_p4est_wrapper + * + * possible 2D configurations: + * o unit Refinement on the unit square. + * o three Refinement on a forest with three trees. + * o brick Refinement on a regular grid of trees (orchard) + * o evil Check second round of refinement with np=5 level=7 + * o evil3 Check second round of refinement on three trees + * o pillow Refinement on a 2-tree pillow-shaped domain. + * o moebius Refinement on a 5-tree Moebius band. + * o star Refinement on a 6-tree star shaped domain. + * o cubed Refinement on a 6-tree cubed sphere surface. + * o disk Refinement on a 5-tree spherical disk. + * o periodic Refinement on the unit square with all-periodic b.c. + * o rotwrap Refinement on the unit square with weird periodic b.c. + * o icosahedron Refinement on the sphere + * o shell2d Refinement on a 2d shell with geometry. + * o disk2d Refinement on a 2d disk with geometry. + * + * possible 3D configurations: + * o unit3 The unit cube. + * o brick3 Refinement on a regular grid of trees (orchard) + * o periodic3 The unit cube with all-periodic boundary conditions. + * o rotwrap3 The unit cube with various self-periodic b.c. + * o twocubes3 Two connected cubes. + * o twowrap3 Two cubes with periodically identified far ends. + * o rotcubes3 A collection of six connected rotated cubes. + * o shell3 A 24-tree discretization of a hollow sphere. + * o sphere3 A 13-tree discretization of a solid sphere. + * o torus A configurable discretization of a solid torus. + */ + +/* + * Additional 2d geometry defined in kalypsso / CanoP: + * o disk2d Refinement of a 5-tree mapping the unit disk. + * o shell2d Similar to 3d shell + * + * Refinement based on radius. + * + */ + +#include + +#include +#include +#include + +#include +#include + + +// default parameters for p4est_vtk_write_file +static const int p4est_vtk_write_tree = 1; +static const int p4est_vtk_write_level = 1; +static const int p4est_vtk_write_rank = 1; +static const int p4est_vtk_wrap_rank = 0; + +enum simple_config_t +{ + // 2D + P4EST_CONFIG_NULL, + P4EST_CONFIG_UNIT, + P4EST_CONFIG_THREE, + P4EST_CONFIG_BRICK, + P4EST_CONFIG_EVIL, + P4EST_CONFIG_EVIL3, + P4EST_CONFIG_PILLOW, + P4EST_CONFIG_MOEBIUS, + P4EST_CONFIG_STAR, + P4EST_CONFIG_CUBED, + P4EST_CONFIG_DISK, + P4EST_CONFIG_PERIODIC, + P4EST_CONFIG_ROTWRAP, + P4EST_CONFIG_ICOSAHEDRON, + P4EST_CONFIG_SHELL2D, + P4EST_CONFIG_DISK2D, + // 3D + P8EST_CONFIG_NULL, + P8EST_CONFIG_UNIT, + P8EST_CONFIG_PERIODIC, + P8EST_CONFIG_BRICK, + P8EST_CONFIG_ROTWRAP, + P8EST_CONFIG_TWOCUBES, + P8EST_CONFIG_TWOWRAP, + P8EST_CONFIG_ROTCUBES, + P8EST_CONFIG_SHELL, + P8EST_CONFIG_SPHERE, + P8EST_CONFIG_TORUS +}; + +struct simple_regression_t +{ + simple_config_t config; + int mpisize; + int level; + unsigned checksum; +}; + +struct user_data_t +{ + double x; // physical space +}; + +struct mpi_context_t +{ + sc_MPI_Comm mpicomm; + int mpisize; + int mpirank; +}; + +static int refine_level = 0; + +template +void +quadrant_center_vertex(typename kalypsso::p4est::Wrapper::connectivity_t * connectivity, + typename kalypsso::p4est::Wrapper::geometry_t * geom, + typename kalypsso::p4est::topidx_t tree, + typename kalypsso::p4est::Wrapper::quadrant_t * quad, + double xyz[3]) +{ + + using namespace kalypsso::p4est; + + uint32_t root_len = Wrapper::ROOT_LEN; + uint32_t quad_len = Wrapper::QUADRANT_LEN(quad->level); + + qcoord_t half_length = quad_len / 2; + + double h2 = 0.5 * quad_len / root_len; + const double intsize = 1.0 / root_len; + + if (geom != nullptr) + { + + double xyz_logic[3] = { 0., 0., 0. }; + + /* + * get coordinates at cell center + */ + xyz_logic[0] = intsize * get_x(quad) + h2; + xyz_logic[1] = intsize * get_y(quad) + h2; + xyz_logic[2] = dim == 3 ? intsize * get_z(quad) + h2 : 0.0; + + // from logical coordinates to physical coordinates + geom->X(geom, tree, xyz_logic, xyz); + } + else + { // regular cartesian geometry + + qcoord_t xyz_logic[3] = { get_x(quad) + half_length, + get_y(quad) + half_length, + get_z(quad) + half_length }; + + Wrapper::qcoord_to_vertex(connectivity, tree, xyz_logic, xyz); + + } // end cartesian geometry + +} // quadrant_center_vertex + +template +static void +init_fn(typename kalypsso::p4est::Wrapper::forest_t * p4est, + typename kalypsso::p4est::topidx_t which_tree, + typename kalypsso::p4est::Wrapper::quadrant_t * quadrant) +{ + + using namespace kalypsso::p4est; + + using connectivity_t = typename Wrapper::connectivity_t; + using geometry_t = typename Wrapper::geometry_t; + + user_data_t * data = (user_data_t *)quadrant->p.user_data; + + // compute physical space coordinates, and put "x" into user_data + + connectivity_t * conn = (connectivity_t *)p4est->connectivity; + geometry_t * geom = (geometry_t *)p4est->user_pointer; + + double XYZ[3]; + + quadrant_center_vertex(conn, geom, which_tree, quadrant, XYZ); + + data->x = XYZ[0]; +} + +template +static int +refine_normal_fn(typename kalypsso::p4est::Wrapper::forest_t * p4est, + typename kalypsso::p4est::topidx_t which_tree, + typename kalypsso::p4est::Wrapper::quadrant_t * quadrant) +{ + using namespace kalypsso::p4est; + + if ((int)quadrant->level >= (refine_level - (int)(which_tree % 3))) + { + return 0; + } + if (quadrant->level == 1 && Wrapper::quadrant_child_id(quadrant) == 3) + { + return 1; + } + if (quadrant->x == P4EST_LAST_OFFSET(2) && quadrant->y == P4EST_LAST_OFFSET(2)) + { + return 1; + } + + if (dim == 2) + { + if (get_x(quadrant) >= (int)Wrapper::QUADRANT_LEN(2)) + { + return 0; + } + } + else + { + if (get_z(quadrant) >= (int)Wrapper::QUADRANT_LEN(2)) + { + return 0; + } + } + + return 1; +} + +template +static int +refine_sparse_fn(typename kalypsso::p4est::Wrapper::forest_t * forest, + typename kalypsso::p4est::topidx_t which_tree, + typename kalypsso::p4est::Wrapper::quadrant_t * quadrant) +{ + using namespace kalypsso::p4est; + // uint32_t quad_len = Wrapper::QUADRANT_LEN (quadrant->level); + + if (which_tree != 0) + { + return 0; + } + if ((int)quadrant->level >= refine_level) + { + return 0; + } + if (quadrant->level == 0) + { + return 1; + } + if (dim == 2) + { + if (get_x(quadrant) < (int)Wrapper::QUADRANT_LEN(2) && get_y(quadrant) > 0) + { + return 1; + } + } + else + { + if (get_x(quadrant) < (int)Wrapper::QUADRANT_LEN(2) && get_y(quadrant) > 0 and + get_z(quadrant) < (int)Wrapper::QUADRANT_LEN(2)) + { + return 1; + } + } + + return 0; +} + +template +static int +refine_evil_fn(typename kalypsso::p4est::Wrapper::forest_t * forest, + typename kalypsso::p4est::topidx_t which_tree, + typename kalypsso::p4est::Wrapper::quadrant_t * quadrant) +{ + if ((int)quadrant->level >= refine_level) + { + return 0; + } + if (forest->mpirank <= 1) + { + return 1; + } + + return 0; +} + +template +static int +refine_evil3_fn(typename kalypsso::p4est::Wrapper::forest_t * p4est, + typename kalypsso::p4est::topidx_t which_tree, + typename kalypsso::p4est::Wrapper::quadrant_t * quadrant) +{ + + using namespace kalypsso::p4est; + + qcoord_t u2; + typename Wrapper::quadrant_t ref; + + P4EST_QUADRANT_INIT(&ref); + + u2 = Wrapper::QUADRANT_LEN(2); + + if (which_tree == 0) + { + ref.x = 3 * u2; + ref.y = 2 * u2; + } + else if (which_tree == 1) + { + ref.x = 2 * u2; + ref.y = 3 * u2; + } + ref.level = 2; + + if ((int)quadrant->level >= refine_level) + { + return 0; + } + if ((which_tree == 0 || which_tree == 1) && (Wrapper::quadrant_is_equal(&ref, quadrant) || + Wrapper::quadrant_is_ancestor(&ref, quadrant))) + { + return 1; + } + + return 0; +} + +template +static int +coarsen_evil_fn(typename kalypsso::p4est::Wrapper::forest_t * forest, + typename kalypsso::p4est::topidx_t which_tree, + typename kalypsso::p4est::Wrapper::quadrant_t * quadrant[]) +{ + if (forest->mpirank >= 2) + { + return 1; + } + + return 0; +} + +template +static int +refine_radius_fn(typename kalypsso::p4est::Wrapper::forest_t * forest, + typename kalypsso::p4est::topidx_t which_tree, + typename kalypsso::p4est::Wrapper::quadrant_t * quadrant) +{ + + using namespace kalypsso::p4est; + using geometry_t = typename Wrapper::geometry_t; + + uint32_t root_len = Wrapper::ROOT_LEN; + uint32_t quad_len = Wrapper::QUADRANT_LEN(quadrant->level); + + // stop criterion + if ((int)quadrant->level >= refine_level) + { + return 0; + } + + geometry_t * geom = (geometry_t *)forest->user_pointer; + + // logical coordinates + double xyz[3] = { 0, 0, 0 }; + + // physical coordinates (after geometry mapping ) + double XYZ[3] = { 0, 0, 0 }; + + // half-size of the cell in logical coordinate + double h2 = 0.5 * quad_len / root_len; + + const double intsize = 1.0 / root_len; + + /* + * get coordinates at cell center + */ + xyz[0] = intsize * get_x(quadrant) + h2; + xyz[1] = intsize * get_y(quadrant) + h2; + if (dim == 3) + xyz[2] = intsize * get_z(quadrant) + h2; + + // apply mapping from logical coordinates to physical coordinates + geom->X(geom, which_tree, xyz, XYZ); + + double radius = 0; + if (dim == 3) + radius = sqrt(XYZ[0] * XYZ[0] + XYZ[1] * XYZ[1] + XYZ[2] * XYZ[2]); + else + radius = sqrt(XYZ[0] * XYZ[0] + XYZ[1] * XYZ[1]); + + if (dim == 2) + { + if (radius < 0.7 and quadrant->level > 4) + return 0; + if (radius < 0.8 and quadrant->level > 5) + return 0; + if (radius < 0.9 and quadrant->level > 6) + return 0; + } + else + { + if (radius < 0.7 and quadrant->level > 3) + return 0; + if (radius < 0.85 and quadrant->level > 4) + return 0; + if (radius < 0.95 and quadrant->level > 5) + return 0; + if (radius < 0.99 and quadrant->level > 6) + return 0; + } + + return 1; +} + +template +void +getdata_cb(typename kalypsso::p4est::Wrapper::volume_info_t * info, void * user_data) +{ + + using namespace kalypsso::p4est; + + using forest_t = typename Wrapper::forest_t; + using tree_t = typename Wrapper::tree_t; + using quadrant_t = typename Wrapper::quadrant_t; + + /* we passed the array of values to fill as the user_data in the call + to p4est_iterate */ + sc_array_t * scalar_data = (sc_array_t *)user_data; + double * this_data; + forest_t * forest = info->p4est; + quadrant_t * q = info->quad; + topidx_t which_tree = info->treeid; + locidx_t local_id = + info->quadid; /* this is the index of q *within its tree's numbering*. We want to convert it + its index for all the quadrants on this process, which we do below */ + tree_t * tree; + user_data_t * udata = (user_data_t *)q->p.user_data; + locidx_t arrayoffset; + + tree = (tree_t *)(forest->trees->array + sizeof(tree_t) * (size_t)which_tree); + + /* compute id relative inside current MPI process */ + local_id += tree->quadrants_offset; + arrayoffset = local_id; + + this_data = (double *)sc_array_index(scalar_data, arrayoffset); + this_data[0] = udata->x; + +} // getdata_cb + + +/* + * A slightly modified output routine, to be able to change the scale parameter. + */ +template +void +my_p4est_vtk_write_file(typename kalypsso::p4est::Wrapper::forest_t * forest, + typename kalypsso::p4est::Wrapper::geometry_t * geom, + std::string filename, + double scale) +{ + using namespace kalypsso::p4est; + + int retval; + typename Wrapper::vtk_context_t * cont; + + // allocate context and set parameters + cont = Wrapper::vtk_context_new(forest, filename.c_str()); + + Wrapper::vtk_context_set_scale(cont, scale); + + Wrapper::vtk_context_set_geom(cont, geom); + + // We do not write point data, so it is safe to set continuous to true. + // This will not save any space though since the default scale is < 1. + Wrapper::vtk_context_set_continuous(cont, 1); + + // write header, that is, vertex positions and quadrant-to-vertex map + cont = Wrapper::vtk_write_header(cont); + SC_CHECK_ABORT(cont != NULL, P4EST_STRING "_vtk: Error writing header"); + + // fill scalar array + sc_array_t * scalar_data; + scalar_data = sc_array_new_size(sizeof(double), forest->local_num_quadrants); + + // Use the iterator to visit every cell and fill scalar_data vector + Wrapper::iterate_volume(forest, nullptr, (void *)scalar_data, getdata_cb); + + // write the tree/level/rank data + cont = Wrapper::vtk_write_cell_dataf(cont, + p4est_vtk_write_tree, + p4est_vtk_write_level, + p4est_vtk_write_rank, + p4est_vtk_wrap_rank, + 1, // writing one scalar field + 0, // not writing any vector field + "xxx", + scalar_data, + cont); + SC_CHECK_ABORT(cont != nullptr, P4EST_STRING "_vtk: Error writing cell data"); + + // properly write rest of the files' contents + retval = Wrapper::vtk_write_footer(cont); + SC_CHECK_ABORT(!retval, P4EST_STRING "_vtk: Error writing footer"); + + sc_array_destroy(scalar_data); + + // !!! context is destroyed in write_footer !!! + // Wrapper::vtk_context_destroy(cont); +} + +/* ================================================================= */ +template +typename kalypsso::p4est::Wrapper::connectivity_t * +create_connectivity(simple_config_t config, int brick_dim[3]); + +template <> +kalypsso::p4est::Wrapper<2>::connectivity_t * +create_connectivity<2>(simple_config_t config, int brick_dim[3]) +{ + + typename kalypsso::p4est::Wrapper<2>::connectivity_t * connectivity = nullptr; + + if (config == P4EST_CONFIG_UNIT) + { + connectivity = p4est_connectivity_new_unitsquare(); + } + else if (config == P4EST_CONFIG_THREE || config == P4EST_CONFIG_EVIL3) + { + connectivity = p4est_connectivity_new_corner(); + } + else if (config == P4EST_CONFIG_BRICK) + { + connectivity = p4est_connectivity_new_brick(brick_dim[0], brick_dim[1], 1, 1); + } + else if (config == P4EST_CONFIG_PILLOW) + { + connectivity = p4est_connectivity_new_pillow(); + } + else if (config == P4EST_CONFIG_MOEBIUS) + { + connectivity = p4est_connectivity_new_moebius(); + } + else if (config == P4EST_CONFIG_STAR) + { + connectivity = p4est_connectivity_new_star(); + } + else if (config == P4EST_CONFIG_CUBED) + { + connectivity = p4est_connectivity_new_cubed(); + } + else if (config == P4EST_CONFIG_DISK) + { +#if defined(KALYPSSO_CORE_USE_OLD_P4EST_API) + connectivity = p4est_connectivity_new_disk(); +#else + // non periodic disk + connectivity = p4est_connectivity_new_disk(0, 0); +#endif // USE_OLD_P4EST_API + } + else if (config == P4EST_CONFIG_PERIODIC) + { + connectivity = p4est_connectivity_new_periodic(); + } + else if (config == P4EST_CONFIG_ROTWRAP) + { + connectivity = p4est_connectivity_new_rotwrap(); + } + else if (config == P4EST_CONFIG_ICOSAHEDRON) + { + connectivity = p4est_connectivity_new_icosahedron(); + } + else if (config == P4EST_CONFIG_SHELL2D) + { + connectivity = p4est_connectivity_new_shell2d(); + } + else if (config == P4EST_CONFIG_DISK2D) + { + connectivity = p4est_connectivity_new_disk2d(); + } + + return connectivity; +} + +template <> +kalypsso::p4est::Wrapper<3>::connectivity_t * +create_connectivity<3>(simple_config_t config, int brick_dim[3]) +{ + + kalypsso::p4est::Wrapper<3>::connectivity_t * connectivity = nullptr; + + if (config == P8EST_CONFIG_UNIT) + { + connectivity = p8est_connectivity_new_unitcube(); + } + else if (config == P8EST_CONFIG_PERIODIC) + { + connectivity = p8est_connectivity_new_periodic(); + } + else if (config == P8EST_CONFIG_BRICK) + { + connectivity = p8est_connectivity_new_brick(brick_dim[0], brick_dim[1], brick_dim[2], 1, 1, 1); + } + else if (config == P8EST_CONFIG_ROTWRAP) + { + connectivity = p8est_connectivity_new_rotwrap(); + } + else if (config == P8EST_CONFIG_TWOCUBES) + { + connectivity = p8est_connectivity_new_twocubes(); + } + else if (config == P8EST_CONFIG_TWOWRAP) + { + connectivity = p8est_connectivity_new_twowrap(); + } + else if (config == P8EST_CONFIG_ROTCUBES) + { + connectivity = p8est_connectivity_new_rotcubes(); + } + else if (config == P8EST_CONFIG_SHELL) + { + connectivity = p8est_connectivity_new_shell(); + } + else if (config == P8EST_CONFIG_SPHERE) + { + connectivity = p8est_connectivity_new_sphere(); + } + else if (config == P8EST_CONFIG_TORUS) + { + connectivity = p8est_connectivity_new_torus(8); + } + + return connectivity; +} + +/* ================================================================= */ +template +typename kalypsso::p4est::Wrapper::geometry_t * +create_geometry(simple_config_t config, + typename kalypsso::p4est::Wrapper::connectivity_t * connectivity, + double R0, + double R1); + +template <> +kalypsso::p4est::Wrapper<2>::geometry_t * +create_geometry<2>(simple_config_t config, + kalypsso::p4est::Wrapper<2>::connectivity_t * connectivity, + double R0, + double R1) +{ + + kalypsso::p4est::Wrapper<2>::geometry_t * geom = nullptr; + + if (config == P4EST_CONFIG_ICOSAHEDRON) + { + geom = p4est_geometry_new_icosahedron(connectivity, R0); + } + else if (config == P4EST_CONFIG_SHELL2D) + { + geom = p4est_geometry_new_shell2d(connectivity, R0, R1); + } + else if (config == P4EST_CONFIG_DISK2D) + { + geom = p4est_geometry_new_disk2d(connectivity, R0, R1); + } + + return geom; +} + +template <> +kalypsso::p4est::Wrapper<3>::geometry_t * +create_geometry<3>(simple_config_t config, + kalypsso::p4est::Wrapper<3>::connectivity_t * connectivity, + double R0, + double R1) +{ + + kalypsso::p4est::Wrapper<3>::geometry_t * geom = nullptr; + + if (config == P8EST_CONFIG_SHELL) + { + geom = p8est_geometry_new_shell(connectivity, 1., .44); + } + else if (config == P8EST_CONFIG_SPHERE) + { + geom = p8est_geometry_new_sphere(connectivity, 1., 0.7, 0.5); + } + else if (config == P8EST_CONFIG_TORUS) + { + geom = p8est_geometry_new_torus(connectivity, 0.44, 1.0, 3.0); + } + + return geom; +} + +/* ============================================================ */ +/* ============================================================ */ +/* ============================================================ */ +template +void +run_test(simple_config_t config, + std::string config_name, + mpi_context_t * mpi, + int argc, + char * argv[]) +{ + + if (dim == 2 and mpi->mpirank == 0) + printf("Running a 2D test\n"); + if (dim == 3 and mpi->mpirank == 0) + printf("Running a 3D test\n"); + + using namespace kalypsso::p4est; + + typename Wrapper::forest_t * forest; + typename Wrapper::connectivity_t * connectivity = nullptr; + typename Wrapper::geometry_t * geom = nullptr; + typename Wrapper::refine_cb_t refine_fn; + typename Wrapper::coarsen_cb_t coarsen_fn; + using balance_type_t = typename Wrapper::balance_type_t; + unsigned crc; + + // only meaningful when using brick connectivity + int brick_dim[3]; + brick_dim[0] = argc > 3 ? atoi(argv[3]) : 2; + brick_dim[1] = argc > 4 ? atoi(argv[4]) : 3; + brick_dim[2] = argc > 5 ? atoi(argv[5]) : 4; + + // create connectivity and forest structures + connectivity = create_connectivity(config, brick_dim); + + // create geometry + double R0 = 0.44; + double R1 = 1.0; + if (argc >= 4) + R0 = atof(argv[3]); + if (argc >= 5) + R1 = atof(argv[4]); + geom = create_geometry(config, connectivity, R0, R1); + + // assign refine_fn, coarsen_fn + refine_fn = refine_normal_fn; + coarsen_fn = nullptr; + + if (config == P4EST_CONFIG_EVIL) + { + refine_fn = refine_evil_fn; + coarsen_fn = coarsen_evil_fn; + } + else if (config == P4EST_CONFIG_EVIL3) + { + refine_fn = refine_evil3_fn; + coarsen_fn = nullptr; + } + else if (config == P4EST_CONFIG_ICOSAHEDRON) + { + // refine_fn = refine_icosahedron_fn; + coarsen_fn = nullptr; + } + else if (config == P4EST_CONFIG_DISK2D) + { + // refine_fn = refine_radius_fn; + coarsen_fn = nullptr; + } + else if (config == P4EST_CONFIG_SHELL2D) + { + // refine_fn = refine_radius_fn; + coarsen_fn = nullptr; + } + else if (config == P8EST_CONFIG_TWOCUBES) + { + refine_fn = refine_sparse_fn; + } + else if (config == P8EST_CONFIG_TWOWRAP) + { + refine_fn = refine_sparse_fn; + } + else + { + refine_fn = refine_normal_fn; + coarsen_fn = nullptr; + } + + /* + * set geometry as p4est user pointer so that we can + * retrieve it in the callbacks. + */ + forest = Wrapper::new_forest( + mpi->mpicomm, connectivity, dim == 2 ? 15 : 8, 0, 0, sizeof(user_data_t), init_fn, geom); + + double scale = 1.0; + + my_p4est_vtk_write_file(forest, geom, config_name + std::string("_new"), scale); + + // refinement and coarsening + Wrapper::refine(forest, 1, refine_fn, init_fn); + if (coarsen_fn != nullptr) + { + Wrapper::coarsen(forest, 1, coarsen_fn, init_fn); + } + my_p4est_vtk_write_file(forest, geom, config_name + std::string("_refined"), scale); + + // balance + Wrapper::balance( + forest, + (dim == 2 ? balance_type_t(P4EST_CONNECT_FULL) : balance_type_t(P8EST_CONNECT_FULL)), + init_fn); + my_p4est_vtk_write_file(forest, geom, config_name + std::string("_balanced"), scale); + crc = Wrapper::checksum(forest); + (void)crc; + + // partition + Wrapper::partition(forest, 0, nullptr); + my_p4est_vtk_write_file(forest, geom, config_name + std::string("_partition"), scale); + +#ifdef P4EST_ENABLE_DEBUG + // rebalance should not change checksum + Wrapper::balance( + forest, + (dim == 2 ? balance_type_t(P4EST_CONNECT_FULL) : balance_type_t(P8EST_CONNECT_FULL)), + init_fn); + P4EST_ASSERT(Wrapper::checksum(forest) == crc); +#endif + + // destroy the p4est and its connectivity structure + Wrapper::destroy(forest); + if (geom != nullptr) + { + Wrapper::geometry_destroy(geom); + } + Wrapper::connectivity_destroy(connectivity); + +} // run p4est test + + +// ====================================================== +// ====================================================== +// ====================================================== +int +main(int argc, char ** argv) +{ + + int mpiret; + mpi_context_t mpi_context, *mpi = &mpi_context; + simple_config_t config; + int wrongusage; + const char * usage; + + // initialize MPI and p4est internals + mpiret = sc_MPI_Init(&argc, &argv); + SC_CHECK_MPI(mpiret); + mpi->mpicomm = sc_MPI_COMM_WORLD; + mpiret = sc_MPI_Comm_size(mpi->mpicomm, &mpi->mpisize); + SC_CHECK_MPI(mpiret); + mpiret = sc_MPI_Comm_rank(mpi->mpicomm, &mpi->mpirank); + SC_CHECK_MPI(mpiret); + + sc_init(mpi->mpicomm, 1, 1, nullptr, SC_LP_DEFAULT); + p4est_init(nullptr, SC_LP_DEFAULT); + + // Process command line arguments + usage = + "Arguments: \n" + " 2D configuration can be any of\n" + " unit|three|brick|evil|evil3|pillow|moebius|\n" + " star|cubed|disk|periodic|rotwrap|isosahedron|disk2d|shell2d\n" + " 3D configuration can be any of\n" + " unit3|periodic3|brick3|rotwrap3|twocubes3|twowrap3|rotcubes3|shell3|sphere3|torus\n" + " Level controls the maximum depth of refinement\n" + "\n" + "Example run:\n" + " mpirun -np 3 ./test_p4est_wrapper unit 6\n" + " mpirun -np 6 ./test_p4est_wrapper brick 5 5 3\n" + " mpirun -np 4 ./test_p4est_wrapper torus 5\n"; + wrongusage = 0; + config = P4EST_CONFIG_NULL; + if (!wrongusage && argc < 3) + { + wrongusage = 1; + } + if (!wrongusage) + { + if (!strcmp(argv[1], "unit")) + { + config = P4EST_CONFIG_UNIT; + } + else if (!strcmp(argv[1], "three")) + { + config = P4EST_CONFIG_THREE; + } + else if (!strcmp(argv[1], "brick")) + { + config = P4EST_CONFIG_BRICK; + } + else if (!strcmp(argv[1], "evil")) + { + config = P4EST_CONFIG_EVIL; + } + else if (!strcmp(argv[1], "evil3")) + { + config = P4EST_CONFIG_EVIL3; + } + else if (!strcmp(argv[1], "pillow")) + { + config = P4EST_CONFIG_PILLOW; + } + else if (!strcmp(argv[1], "moebius")) + { + config = P4EST_CONFIG_MOEBIUS; + } + else if (!strcmp(argv[1], "star")) + { + config = P4EST_CONFIG_STAR; + } + else if (!strcmp(argv[1], "cubed")) + { + config = P4EST_CONFIG_CUBED; + } + else if (!strcmp(argv[1], "disk")) + { + config = P4EST_CONFIG_DISK; + } + else if (!strcmp(argv[1], "periodic")) + { + config = P4EST_CONFIG_PERIODIC; + } + else if (!strcmp(argv[1], "rotwrap")) + { + config = P4EST_CONFIG_ROTWRAP; + } + else if (!strcmp(argv[1], "icosahedron")) + { + config = P4EST_CONFIG_ICOSAHEDRON; + } + else if (!strcmp(argv[1], "shell2d")) + { + config = P4EST_CONFIG_SHELL2D; + } + else if (!strcmp(argv[1], "disk2d")) + { + config = P4EST_CONFIG_DISK2D; + } + // 3D config + else if (!strcmp(argv[1], "unit3")) + { + config = P8EST_CONFIG_UNIT; + } + else if (!strcmp(argv[1], "periodic3")) + { + config = P8EST_CONFIG_PERIODIC; + } + else if (!strcmp(argv[1], "brick3")) + { + config = P8EST_CONFIG_BRICK; + } + else if (!strcmp(argv[1], "rotwrap3")) + { + config = P8EST_CONFIG_ROTWRAP; + } + else if (!strcmp(argv[1], "twocubes3")) + { + config = P8EST_CONFIG_TWOCUBES; + } + else if (!strcmp(argv[1], "twowrap3")) + { + config = P8EST_CONFIG_TWOWRAP; + } + else if (!strcmp(argv[1], "rotcubes3")) + { + config = P8EST_CONFIG_ROTCUBES; + } + else if (!strcmp(argv[1], "shell3")) + { + config = P8EST_CONFIG_SHELL; + } + else if (!strcmp(argv[1], "sphere3")) + { + config = P8EST_CONFIG_SPHERE; + } + else if (!strcmp(argv[1], "torus")) + { + config = P8EST_CONFIG_TORUS; + } + else + { + wrongusage = 1; + } + } + if (wrongusage) + { + P4EST_GLOBAL_LERROR(usage); + sc_abort_collective("Usage error"); + } + + // assign variables based on configuration + refine_level = atoi(argv[2]); + + + // run some tests + if (config <= P4EST_CONFIG_DISK2D) + run_test<2>(config, std::string(argv[1]), mpi, argc, argv); + else + run_test<3>(config, std::string(argv[1]), mpi, argc, argv); + + // clean up and exit + sc_finalize(); + + mpiret = sc_MPI_Finalize(); + SC_CHECK_MPI(mpiret); + + return 0; +} diff --git a/test/p4est_wrapper/test_p4est_wrapper_old.cpp b/test/p4est_wrapper/test_p4est_wrapper_old.cpp new file mode 100644 index 0000000..c5ce903 --- /dev/null +++ b/test/p4est_wrapper/test_p4est_wrapper_old.cpp @@ -0,0 +1,954 @@ +// Copyright (C) 2010 The University of Texas System +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/* + * Usage: test_p4est_wrapper + * + * possible 2D configurations: + * o unit Refinement on the unit square. + * o three Refinement on a forest with three trees. + * o evil Check second round of refinement with np=5 level=7 + * o evil3 Check second round of refinement on three trees + * o pillow Refinement on a 2-tree pillow-shaped domain. + * o moebius Refinement on a 5-tree Moebius band. + * o star Refinement on a 6-tree star shaped domain. + * o cubed Refinement on a 6-tree cubed sphere surface. + * o disk Refinement on a 5-tree spherical disk. + * o periodic Refinement on the unit square with all-periodic b.c. + * o rotwrap Refinement on the unit square with weird periodic b.c. + * + * possible 3D configurations: + * o unit3 The unit cube. + * o periodic3 The unit cube with all-periodic boundary conditions. + * o rotwrap3 The unit cube with various self-periodic b.c. + * o twocubes3 Two connected cubes. + * o twowrap3 Two cubes with periodically identified far ends. + * o rotcubes3 A collection of six connected rotated cubes. + * o shell3 A 24-tree discretization of a hollow sphere. + * o sphere3 A 13-tree discretization of a solid sphere. + */ + +/* + * Additional 2d geometry defined in khamr / CanoP: + * o disk2d Refinement of a 5-tree mapping the unit disk. + * o shell2d Similar to 3d shell + * + * Refinement based on radius. + * + */ + +#include + +// using namespace khamr; + +#include +#include + +#include> kalypsso / utils / p4est / connectivity.h> +#include + + +// default parameters for p4est_vtk_write_file +static const int p4est_vtk_write_tree = 1; +static const int p4est_vtk_write_level = 1; +static const int p4est_vtk_write_rank = 1; +static const int p4est_vtk_wrap_rank = 0; + +enum simple_config_t +{ + // 2D + P4EST_CONFIG_NULL, + P4EST_CONFIG_UNIT, + P4EST_CONFIG_THREE, + P4EST_CONFIG_EVIL, + P4EST_CONFIG_EVIL3, + P4EST_CONFIG_PILLOW, + P4EST_CONFIG_MOEBIUS, + P4EST_CONFIG_STAR, + P4EST_CONFIG_CUBED, + P4EST_CONFIG_DISK, + P4EST_CONFIG_PERIODIC, + P4EST_CONFIG_ROTWRAP, + P4EST_CONFIG_DISK2D, + P4EST_CONFIG_SHELL2D, + // 3D + P8EST_CONFIG_NULL, + P8EST_CONFIG_UNIT, + P8EST_CONFIG_PERIODIC, + P8EST_CONFIG_ROTWRAP, + P8EST_CONFIG_TWOCUBES, + P8EST_CONFIG_TWOWRAP, + P8EST_CONFIG_ROTCUBES, + P8EST_CONFIG_SHELL, + P8EST_CONFIG_SPHERE +}; + +struct simple_regression_t +{ + simple_config_t config; + int mpisize; + int level; + unsigned checksum; +}; + +struct user_data_t +{ + double x; // physical space +}; + +struct mpi_context_t +{ + sc_MPI_Comm mpicomm; + int mpisize; + int mpirank; +}; + +static int refine_level = 0; + +template +void +quadrant_center_vertex(typename khamr::p4est::Wrapper::connectivity_t * connectivity, + typename khamr::p4est::Wrapper::geometry_t * geom, + typename khamr::p4est::Wrapper::topidx_t tree, + typename khamr::p4est::Wrapper::quadrant_t * quad, + double xyz[3]) +{ + + using namespace khamr::p4est; + using qcoord_t = typename Wrapper::qcoord_t; + + uint32_t root_len = Wrapper::ROOT_LEN; + uint32_t quad_len = Wrapper::QUADRANT_LEN(quad->level); + + qcoord_t half_length = quad_len / 2; + + double h2 = 0.5 * quad_len / root_len; + const double intsize = 1.0 / root_len; + + if (geom != nullptr) + { + + double xyz_logic[3] = { 0., 0., 0. }; + + /* + * get coordinates at cell center + */ + xyz_logic[0] = intsize * Wrapper::get_x(quad) + h2; + xyz_logic[1] = intsize * Wrapper::get_y(quad) + h2; + xyz_logic[2] = dim == 3 ? intsize * Wrapper::get_z(quad) + h2 : 0.0; + + // from logical coordinates to physical coordinates + geom->X(geom, tree, xyz_logic, xyz); + } + else + { // regular cartesian geometry + + qcoord_t xyz_logic[3] = { Wrapper::get_x(quad) + half_length, + Wrapper::get_y(quad) + half_length, + Wrapper::get_z(quad) + half_length }; + + Wrapper::qcoord_to_vertex(connectivity, tree, xyz_logic, xyz); + + } // end cartesian geometry + +} // quadrant_center_vertex + +template +static void +init_fn(typename khamr::p4est::Wrapper::forest_t * p4est, + typename khamr::p4est::Wrapper::topidx_t which_tree, + typename khamr::p4est::Wrapper::quadrant_t * quadrant) +{ + + using namespace khamr::p4est; + + using connectivity_t = typename Wrapper::connectivity_t; + using geometry_t = typename Wrapper::geometry_t; + + uint32_t root_len = Wrapper::ROOT_LEN; + uint32_t quad_len = Wrapper::QUADRANT_LEN(quadrant->level); + + user_data_t * data = (user_data_t *)quadrant->p.user_data; + + // compute physical space coordinates, and put "x" into user_data + + connectivity_t * conn = (connectivity_t *)p4est->connectivity; + geometry_t * geom = (geometry_t *)p4est->user_pointer; + + double XYZ[3]; + + quadrant_center_vertex(conn, geom, which_tree, quadrant, XYZ); + + data->x = XYZ[0]; +} + +template +static int +refine_normal_fn(typename khamr::p4est::Wrapper::forest_t * p4est, + typename khamr::p4est::Wrapper::topidx_t which_tree, + typename khamr::p4est::Wrapper::quadrant_t * quadrant) +{ + using namespace khamr::p4est; + + if ((int)quadrant->level >= (refine_level - (int)(which_tree % 3))) + { + return 0; + } + if (quadrant->level == 1 && Wrapper::quadrant_child_id(quadrant) == 3) + { + return 1; + } + if (quadrant->x == P4EST_LAST_OFFSET(2) && quadrant->y == P4EST_LAST_OFFSET(2)) + { + return 1; + } + + if (dim == 2) + { + if (Wrapper::get_x(quadrant) >= Wrapper::QUADRANT_LEN(2)) + { + return 0; + } + } + else + { + if (Wrapper::get_z(quadrant) >= Wrapper::QUADRANT_LEN(2)) + { + return 0; + } + } + + return 1; +} + +template +static int +refine_sparse_fn(typename khamr::p4est::Wrapper::forest_t * forest, + typename khamr::p4est::Wrapper::topidx_t which_tree, + typename khamr::p4est::Wrapper::quadrant_t * quadrant) +{ + using namespace khamr::p4est; + uint32_t quad_len = Wrapper::QUADRANT_LEN(quadrant->level); + + if (which_tree != 0) + { + return 0; + } + if ((int)quadrant->level >= refine_level) + { + return 0; + } + if (quadrant->level == 0) + { + return 1; + } + if (dim == 2) + { + if (Wrapper::get_x(quadrant) < Wrapper::QUADRANT_LEN(2) && + Wrapper::get_y(quadrant) > 0) + { + return 1; + } + } + else + { + if (Wrapper::get_x(quadrant) < Wrapper::QUADRANT_LEN(2) && + Wrapper::get_y(quadrant) > 0 && + Wrapper::get_z(quadrant) < Wrapper::QUADRANT_LEN(2)) + { + return 1; + } + } + + return 0; +} + +template +static int +refine_evil_fn(typename khamr::p4est::Wrapper::forest_t * forest, + typename khamr::p4est::Wrapper::topidx_t which_tree, + typename khamr::p4est::Wrapper::quadrant_t * quadrant) +{ + if ((int)quadrant->level >= refine_level) + { + return 0; + } + if (forest->mpirank <= 1) + { + return 1; + } + + return 0; +} + +template +static int +refine_evil3_fn(typename khamr::p4est::Wrapper::forest_t * p4est, + typename khamr::p4est::Wrapper::topidx_t which_tree, + typename khamr::p4est::Wrapper::quadrant_t * quadrant) +{ + + using namespace khamr::p4est; + + typename Wrapper::qcoord_t u2; + typename Wrapper::quadrant_t ref; + + P4EST_QUADRANT_INIT(&ref); + + u2 = Wrapper::QUADRANT_LEN(2); + + if (which_tree == 0) + { + ref.x = 3 * u2; + ref.y = 2 * u2; + } + else if (which_tree == 1) + { + ref.x = 2 * u2; + ref.y = 3 * u2; + } + ref.level = 2; + + if ((int)quadrant->level >= refine_level) + { + return 0; + } + if ((which_tree == 0 || which_tree == 1) && (Wrapper::quadrant_is_equal(&ref, quadrant) || + Wrapper::quadrant_is_ancestor(&ref, quadrant))) + { + return 1; + } + + return 0; +} + +template +static int +coarsen_evil_fn(typename khamr::p4est::Wrapper::forest_t * forest, + typename khamr::p4est::Wrapper::topidx_t which_tree, + typename khamr::p4est::Wrapper::quadrant_t * quadrant[]) +{ + if (forest->mpirank >= 2) + { + return 1; + } + + return 0; +} + +template +static int +refine_radius_fn(typename khamr::p4est::Wrapper::forest_t * forest, + typename khamr::p4est::Wrapper::topidx_t which_tree, + typename khamr::p4est::Wrapper::quadrant_t * quadrant) +{ + + using namespace khamr::p4est; + using geometry_t = typename Wrapper::geometry_t; + + uint32_t root_len = Wrapper::ROOT_LEN; + uint32_t quad_len = Wrapper::QUADRANT_LEN(quadrant->level); + + // stop criterion + if ((int)quadrant->level >= refine_level) + { + return 0; + } + + geometry_t * geom = (geometry_t *)forest->user_pointer; + + // logical coordinates + double xyz[3] = { 0, 0, 0 }; + + // physical coordinates (after geometry mapping ) + double XYZ[3] = { 0, 0, 0 }; + + // half-size of the cell in logical coordinate + double h2 = 0.5 * quad_len / root_len; + + const double intsize = 1.0 / root_len; + + /* + * get coordinates at cell center + */ + xyz[0] = intsize * Wrapper::get_x(quadrant) + h2; + xyz[1] = intsize * Wrapper::get_y(quadrant) + h2; + if (dim == 3) + xyz[2] = intsize * Wrapper::get_z(quadrant) + h2; + + // apply mapping from logical coordinates to physical coordinates + geom->X(geom, which_tree, xyz, XYZ); + + double radius = 0; + if (dim == 3) + radius = sqrt(XYZ[0] * XYZ[0] + XYZ[1] * XYZ[1] + XYZ[2] * XYZ[2]); + else + radius = sqrt(XYZ[0] * XYZ[0] + XYZ[1] * XYZ[1]); + + if (dim == 2) + { + if (radius < 0.7 and quadrant->level > 4) + return 0; + if (radius < 0.8 and quadrant->level > 5) + return 0; + if (radius < 0.9 and quadrant->level > 6) + return 0; + } + else + { + if (radius < 0.7 and quadrant->level > 3) + return 0; + if (radius < 0.85 and quadrant->level > 4) + return 0; + if (radius < 0.95 and quadrant->level > 5) + return 0; + if (radius < 0.99 and quadrant->level > 6) + return 0; + } + + return 1; +} + +template +void +getdata_cb(typename khamr::p4est::Wrapper::volume_info_t * info, void * user_data) +{ + + using namespace khamr::p4est; + + using forest_t = typename Wrapper::forest_t; + using tree_t = typename Wrapper::tree_t; + using quadrant_t = typename Wrapper::quadrant_t; + using topidx_t = typename Wrapper::topidx_t; + using locidx_t = typename Wrapper::locidx_t; + + /* we passed the array of values to fill as the user_data in the call + to p4est_iterate */ + sc_array_t * scalar_data = (sc_array_t *)user_data; + double * this_data; + forest_t * forest = info->p4est; + quadrant_t * q = info->quad; + topidx_t which_tree = info->treeid; + locidx_t local_id = + info->quadid; /* this is the index of q *within its tree's numbering*. We want to convert it + its index for all the quadrants on this process, which we do below */ + tree_t * tree; + user_data_t * udata = (user_data_t *)q->p.user_data; + locidx_t arrayoffset; + + + tree = (tree_t *)(forest->trees->array + sizeof(tree_t) * (size_t)which_tree); + + /* compute id relative inside current MPI process */ + local_id += tree->quadrants_offset; + arrayoffset = local_id; + + this_data = (double *)sc_array_index(scalar_data, arrayoffset); + this_data[0] = udata->x; + +} // getdata_cb + + +/* + * A slightly modified output routine, to be able to change the scale parameter. + */ +template +void +my_p4est_vtk_write_file(typename khamr::p4est::Wrapper::forest_t * forest, + typename khamr::p4est::Wrapper::geometry_t * geom, + const char * filename, + double scale) +{ + using namespace khamr::p4est; + + int retval; + typename Wrapper::vtk_context_t * cont; + + // allocate context and set parameters + cont = Wrapper::vtk_context_new(forest, filename); + + Wrapper::vtk_context_set_scale(cont, scale); + + Wrapper::vtk_context_set_geom(cont, geom); + + // We do not write point data, so it is safe to set continuous to true. + // This will not save any space though since the default scale is < 1. + Wrapper::vtk_context_set_continuous(cont, 1); + + // write header, that is, vertex positions and quadrant-to-vertex map + cont = Wrapper::vtk_write_header(cont); + SC_CHECK_ABORT(cont != NULL, P4EST_STRING "_vtk: Error writing header"); + + // fill scalar array + sc_array_t * scalar_data; + scalar_data = sc_array_new_size(sizeof(double), forest->local_num_quadrants); + + // Use the iterator to visit every cell and fill scalar_data vector + Wrapper::iterate_volume(forest, (void *)scalar_data, getdata_cb); + + // write the tree/level/rank data + cont = Wrapper::vtk_write_cell_dataf(cont, + p4est_vtk_write_tree, + p4est_vtk_write_level, + p4est_vtk_write_rank, + p4est_vtk_wrap_rank, + 1, // writing one scalar field + 0, + "x", + scalar_data, + cont); + SC_CHECK_ABORT(cont != nullptr, P4EST_STRING "_vtk: Error writing cell data"); + + // properly write rest of the files' contents + retval = Wrapper::vtk_write_footer(cont); + SC_CHECK_ABORT(!retval, P4EST_STRING "_vtk: Error writing footer"); + + sc_array_destroy(scalar_data); + + // !!! context is destroyed in write_footer !!! + // Wrapper::vtk_context_destroy(cont); +} + +/* ================================================================= */ +template +typename khamr::p4est::Wrapper::connectivity_t * +create_connectivity(simple_config_t config); + +template <> +khamr::p4est::Wrapper<2>::connectivity_t * +create_connectivity<2>(simple_config_t config) +{ + + typename khamr::p4est::Wrapper<2>::connectivity_t * connectivity = nullptr; + + if (config == P4EST_CONFIG_UNIT) + { + connectivity = p4est_connectivity_new_unitsquare(); + } + else if (config == P4EST_CONFIG_THREE || config == P4EST_CONFIG_EVIL3) + { + connectivity = p4est_connectivity_new_corner(); + } + else if (config == P4EST_CONFIG_PILLOW) + { + connectivity = p4est_connectivity_new_pillow(); + } + else if (config == P4EST_CONFIG_MOEBIUS) + { + connectivity = p4est_connectivity_new_moebius(); + } + else if (config == P4EST_CONFIG_STAR) + { + connectivity = p4est_connectivity_new_star(); + } + else if (config == P4EST_CONFIG_CUBED) + { + connectivity = p4est_connectivity_new_cubed(); + } + else if (config == P4EST_CONFIG_DISK) + { + connectivity = p4est_connectivity_new_disk(); + } + else if (config == P4EST_CONFIG_PERIODIC) + { + connectivity = p4est_connectivity_new_periodic(); + } + else if (config == P4EST_CONFIG_ROTWRAP) + { + connectivity = p4est_connectivity_new_rotwrap(); + } + else if (config == P4EST_CONFIG_DISK2D) + { + connectivity = p4est_connectivity_new_disk2d(); + } + else if (config == P4EST_CONFIG_SHELL2D) + { + connectivity = p4est_connectivity_new_shell2d(); + } + + return connectivity; +} + +template <> +khamr::p4est::Wrapper<3>::connectivity_t * +create_connectivity<3>(simple_config_t config) +{ + + khamr::p4est::Wrapper<3>::connectivity_t * connectivity = nullptr; + + if (config == P8EST_CONFIG_UNIT) + { + connectivity = p8est_connectivity_new_unitcube(); + } + else if (config == P8EST_CONFIG_PERIODIC) + { + connectivity = p8est_connectivity_new_periodic(); + } + else if (config == P8EST_CONFIG_ROTWRAP) + { + connectivity = p8est_connectivity_new_rotwrap(); + } + else if (config == P8EST_CONFIG_TWOCUBES) + { + connectivity = p8est_connectivity_new_twocubes(); + } + else if (config == P8EST_CONFIG_TWOWRAP) + { + connectivity = p8est_connectivity_new_twowrap(); + } + else if (config == P8EST_CONFIG_ROTCUBES) + { + connectivity = p8est_connectivity_new_rotcubes(); + } + else if (config == P8EST_CONFIG_SHELL) + { + connectivity = p8est_connectivity_new_shell(); + } + else if (config == P8EST_CONFIG_SPHERE) + { + connectivity = p8est_connectivity_new_sphere(); + } + + return connectivity; +} + +/* ================================================================= */ +template +typename khamr::p4est::Wrapper::geometry_t * +create_geometry(simple_config_t config, + typename khamr::p4est::Wrapper::connectivity_t * connectivity, + double R0, + double R1); + +template <> +khamr::p4est::Wrapper<2>::geometry_t * +create_geometry<2>(simple_config_t config, + khamr::p4est::Wrapper<2>::connectivity_t * connectivity, + double R0, + double R1) +{ + + khamr::p4est::Wrapper<2>::geometry_t * geom = nullptr; + + if (config == P4EST_CONFIG_DISK2D) + { + geom = p4est_geometry_new_disk2d(connectivity, R0, R1); + } + else if (config == P4EST_CONFIG_SHELL2D) + { + geom = p4est_geometry_new_shell2d(connectivity, R0, R1); + } + + return geom; +} + +template <> +khamr::p4est::Wrapper<3>::geometry_t * +create_geometry<3>(simple_config_t config, + khamr::p4est::Wrapper<3>::connectivity_t * connectivity, + double R0, + double R1) +{ + + khamr::p4est::Wrapper<3>::geometry_t * geom = nullptr; + + if (config == P8EST_CONFIG_SHELL) + { + geom = p8est_geometry_new_shell(connectivity, 1., .44); + } + else if (config == P8EST_CONFIG_SPHERE) + { + geom = p8est_geometry_new_sphere(connectivity, 1., 0.7, 0.5); + } + + return geom; +} + +/* ============================================================ */ +/* ============================================================ */ +/* ============================================================ */ +template +void +run_test(simple_config_t config, mpi_context_t * mpi, int argc, char * argv[]) +{ + + if (dim == 2 and mpi->mpirank == 0) + printf("Running a 2D test\n"); + if (dim == 3 and mpi->mpirank == 0) + printf("Running a 3D test\n"); + + using namespace khamr::p4est; + + typename Wrapper::forest_t * forest; + typename Wrapper::connectivity_t * connectivity = nullptr; + typename Wrapper::geometry_t * geom = nullptr; + typename Wrapper::refine_cb_t refine_fn; + typename Wrapper::coarsen_cb_t coarsen_fn; + using balance_type_t = typename Wrapper::balance_type_t; + unsigned crc; + + + // create connectivity and forest structures + connectivity = create_connectivity(config); + + // create geometry + double R0 = 0.44; + double R1 = 1.0; + if (argc >= 4) + R0 = atof(argv[3]); + if (argc >= 5) + R1 = atof(argv[4]); + geom = create_geometry(config, connectivity, R0, R1); + + // assign refine_fn, coarsen_fn + refine_fn = refine_normal_fn; + coarsen_fn = nullptr; + + if (config == P4EST_CONFIG_EVIL) + { + refine_fn = refine_evil_fn; + coarsen_fn = coarsen_evil_fn; + } + else if (config == P4EST_CONFIG_EVIL3) + { + refine_fn = refine_evil3_fn; + coarsen_fn = nullptr; + } + else if (config == P4EST_CONFIG_DISK2D) + { + refine_fn = refine_radius_fn; + coarsen_fn = nullptr; + } + else if (config == P4EST_CONFIG_SHELL2D) + { + refine_fn = refine_radius_fn; + coarsen_fn = nullptr; + } + else if (config == P8EST_CONFIG_TWOCUBES) + { + refine_fn = refine_sparse_fn; + } + else if (config == P8EST_CONFIG_TWOWRAP) + { + refine_fn = refine_sparse_fn; + } + else + { + refine_fn = refine_normal_fn; + coarsen_fn = nullptr; + } + + /* + * set geometry as p4est user pointer so that we can + * retrieve it in the callbacks. + */ + forest = Wrapper::new_forest( + mpi->mpicomm, connectivity, dim == 2 ? 15 : 4, 0, 0, sizeof(user_data_t), init_fn, geom); + + double scale = 1.0; + + my_p4est_vtk_write_file(forest, geom, "test_new", scale); + + // refinement and coarsening + Wrapper::refine(forest, 1, refine_fn, init_fn); + if (coarsen_fn != nullptr) + { + Wrapper::coarsen(forest, 1, coarsen_fn, init_fn); + } + my_p4est_vtk_write_file(forest, geom, "test_refined", scale); + + // balance + Wrapper::balance( + forest, + (dim == 2 ? balance_type_t(P4EST_CONNECT_FULL) : balance_type_t(P8EST_CONNECT_FULL)), + init_fn); + my_p4est_vtk_write_file(forest, geom, "test_balanced", scale); + crc = Wrapper::checksum(forest); + + // partition + Wrapper::partition(forest, 0, nullptr); + my_p4est_vtk_write_file(forest, geom, "test_partition", scale); + +#ifdef P4EST_ENABLE_DEBUG + // rebalance should not change checksum + Wrapper::balance( + forest, + (dim == 2 ? balance_type_t(P4EST_CONNECT_FULL) : balance_type_t(P8EST_CONNECT_FULL)), + init_fn); + P4EST_ASSERT(p4est_checksum(forest) == crc); +#endif + + // destroy the p4est and its connectivity structure + Wrapper::destroy(forest); + if (geom != nullptr) + { + Wrapper::geometry_destroy(geom); + } + Wrapper::connectivity_destroy(connectivity); + +} // run p4est test + + +int +main(int argc, char ** argv) +{ + + int mpiret; + mpi_context_t mpi_context, *mpi = &mpi_context; + simple_config_t config; + bool is_a_2d_test = true; + int wrongusage; + const char * usage; + + // initialize MPI and p4est internals + mpiret = sc_MPI_Init(&argc, &argv); + SC_CHECK_MPI(mpiret); + mpi->mpicomm = sc_MPI_COMM_WORLD; + mpiret = sc_MPI_Comm_size(mpi->mpicomm, &mpi->mpisize); + SC_CHECK_MPI(mpiret); + mpiret = sc_MPI_Comm_rank(mpi->mpicomm, &mpi->mpirank); + SC_CHECK_MPI(mpiret); + + sc_init(mpi->mpicomm, 1, 1, nullptr, SC_LP_DEFAULT); + p4est_init(nullptr, SC_LP_DEFAULT); + + // Process command line arguments + usage = "Arguments: \n" + " 2D configuration can be any of\n" + " unit|three|evil|evil3|pillow|moebius|\n" + " star|cubed|disk|periodic|rotwrap|disk2d|shell2d\n" + " 3D configuration can be any of\n" + " unit3|periodic3|rotwrap3|twocubes3|twowrap3|rotcubes3|shell3|sphere3\n" + " Level controls the maximum depth of refinement\n"; + wrongusage = 0; + config = P4EST_CONFIG_NULL; + if (!wrongusage && argc < 3) + { + wrongusage = 1; + } + if (!wrongusage) + { + if (!strcmp(argv[1], "unit")) + { + config = P4EST_CONFIG_UNIT; + } + else if (!strcmp(argv[1], "three")) + { + config = P4EST_CONFIG_THREE; + } + else if (!strcmp(argv[1], "evil")) + { + config = P4EST_CONFIG_EVIL; + } + else if (!strcmp(argv[1], "evil3")) + { + config = P4EST_CONFIG_EVIL3; + } + else if (!strcmp(argv[1], "pillow")) + { + config = P4EST_CONFIG_PILLOW; + } + else if (!strcmp(argv[1], "moebius")) + { + config = P4EST_CONFIG_MOEBIUS; + } + else if (!strcmp(argv[1], "star")) + { + config = P4EST_CONFIG_STAR; + } + else if (!strcmp(argv[1], "cubed")) + { + config = P4EST_CONFIG_CUBED; + } + else if (!strcmp(argv[1], "disk")) + { + config = P4EST_CONFIG_DISK; + } + else if (!strcmp(argv[1], "periodic")) + { + config = P4EST_CONFIG_PERIODIC; + } + else if (!strcmp(argv[1], "rotwrap")) + { + config = P4EST_CONFIG_ROTWRAP; + } + else if (!strcmp(argv[1], "disk2d")) + { + config = P4EST_CONFIG_DISK2D; + } + else if (!strcmp(argv[1], "shell2d")) + { + config = P4EST_CONFIG_SHELL2D; + } + // 3D config + else if (!strcmp(argv[1], "unit3")) + { + config = P8EST_CONFIG_UNIT; + is_a_2d_test = false; + } + else if (!strcmp(argv[1], "periodic3")) + { + config = P8EST_CONFIG_PERIODIC; + is_a_2d_test = false; + } + else if (!strcmp(argv[1], "rotwrap3")) + { + config = P8EST_CONFIG_ROTWRAP; + is_a_2d_test = false; + } + else if (!strcmp(argv[1], "twocubes3")) + { + config = P8EST_CONFIG_TWOCUBES; + is_a_2d_test = false; + } + else if (!strcmp(argv[1], "twowrap3")) + { + config = P8EST_CONFIG_TWOWRAP; + is_a_2d_test = false; + } + else if (!strcmp(argv[1], "rotcubes3")) + { + config = P8EST_CONFIG_ROTCUBES; + is_a_2d_test = false; + } + else if (!strcmp(argv[1], "shell3")) + { + config = P8EST_CONFIG_SHELL; + is_a_2d_test = false; + } + else if (!strcmp(argv[1], "sphere3")) + { + config = P8EST_CONFIG_SPHERE; + is_a_2d_test = false; + } + else + { + wrongusage = 1; + } + } + if (wrongusage) + { + P4EST_GLOBAL_LERROR(usage); + sc_abort_collective("Usage error"); + } + + // assign variables based on configuration + refine_level = atoi(argv[2]); + + + // run some tests + if (config <= P4EST_CONFIG_SHELL2D) + run_test<2>(config, mpi, argc, argv); + else + run_test<3>(config, mpi, argc, argv); + + // clean up and exit + sc_finalize(); + + mpiret = sc_MPI_Finalize(); + SC_CHECK_MPI(mpiret); + + return 0; +} diff --git a/test/spdlog/CMakeLists.txt b/test/spdlog/CMakeLists.txt new file mode 100644 index 0000000..2e652ec --- /dev/null +++ b/test/spdlog/CMakeLists.txt @@ -0,0 +1,9 @@ +add_executable(test_spdlog "") +target_sources(test_spdlog PRIVATE test_spdlog.cpp) +target_compile_definitions(test_spdlog PRIVATE -DSPDLOG_ACTIVE_LEVEL=${KALYPSSO_CORE_LOG_LEVEL}) +target_link_libraries(test_spdlog PUBLIC kalypsso::core kalypsso::log) +add_test(NAME spdlog COMMAND test_spdlog) +set_property(TEST spdlog PROPERTY LABELS spdlog) +if(Kokkos_ENABLE_OPENMP) + set_property(TEST spdlog PROPERTY ENVIRONMENT OMP_PROC_BIND=spread OMP_PLACES=threads) +endif() diff --git a/test/spdlog/test_spdlog.cpp b/test/spdlog/test_spdlog.cpp new file mode 100644 index 0000000..fbbea47 --- /dev/null +++ b/test/spdlog/test_spdlog.cpp @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +#include + +namespace kalypsso +{ + +void +test_log() +{ + + // log with the default logger + SPDLOG_TRACE("Kalypsso trace log test {}", 3.141592); + SPDLOG_DEBUG("Kalypsso debug log test {}", 3.141592); + SPDLOG_INFO("Kalypsso info log test {}", 3.141592); + SPDLOG_WARN("Kalypsso warn log test {}", 3.141592); + SPDLOG_ERROR("Kalypsso error log test {}", 3.141592); + SPDLOG_CRITICAL("Kalypsso critical log test {}", 3.141592); + +} // test_log + +void +test_kalypsso_log_master() +{ + + // log with the default logger + KALYPSSO_TRACE("Kalypsso trace log test {}", 3.141592); + KALYPSSO_DEBUG("Kalypsso debug log test {}", 3.141592); + KALYPSSO_INFO("Kalypsso info log test {}", 3.141592); + KALYPSSO_WARN("Kalypsso warn log test {}", 3.141592); + KALYPSSO_ERROR("Kalypsso error log test {}", 3.141592); + KALYPSSO_CRITICAL("Kalypsso critical log test {}", 3.141592); + +} // test_kalypsso_log_master + +void +test_kalypsso_log_all() +{ + + // log with the default logger + KALYPSSO_TRACE_ALL("Kalypsso trace log test {}", 3.141592); + KALYPSSO_DEBUG_ALL("Kalypsso debug log test {}", 3.141592); + KALYPSSO_INFO_ALL("Kalypsso info log test {}", 3.141592); + KALYPSSO_WARN_ALL("Kalypsso warn log test {}", 3.141592); + KALYPSSO_ERROR_ALL("Kalypsso error log test {}", 3.141592); + KALYPSSO_CRITICAL_ALL("Kalypsso critical log test {}", 3.141592); + +} // test_kalypsso_log_all + +} // namespace kalypsso + + +int +main(int argc, char * argv[]) +{ + kalypsso::ParallelEnv par_env(argc, argv); + +#ifdef KALYPSSO_CORE_USE_SPDLOG + kalypsso::kalypsso_spdlog_config(argc, argv, par_env.rank(), par_env.size()); +#endif + + if (kalypsso::cmdline_arg_exists(argv, argv + argc, "--help")) + { + if (par_env.rank() == 0) + { + std::cout + << "You can change log levels by setting environment variable SPDLOG_LEVEL.\n" + << "kalypsso predefines two loggers: \"kalypsso_mpi_master\" and \"kalypsso_mpi_all\"\n" + << "e.g. to turn off login in logger \"kalypsso_mpi_all\":\n" + << " mpirun -np 3 ./test_spdlog SPDLOG_LEVEL=off,kalypsso_mpi_master=trace\n" + << "Available levels are: trace, debug, info, warn, error and critical\n"; + } + return 0; + } + + // kalypsso::test_log(); + kalypsso::test_kalypsso_log_master(); + kalypsso::test_kalypsso_log_all(); + + return EXIT_SUCCESS; +} diff --git a/test/test_common/CMakeLists.txt b/test/test_common/CMakeLists.txt new file mode 100644 index 0000000..0cf64b3 --- /dev/null +++ b/test/test_common/CMakeLists.txt @@ -0,0 +1,19 @@ +# ################################################################################################## +add_library( + AMRtest + InitialAMRSetup.h + InitialAMRSetup.cpp + DataWriter.h + DataWriter.cpp + FillOutside.h + FillOutside.cpp + FillOutside2.h + FillOutside2.cpp + test_func.h) + +target_include_directories(AMRtest PUBLIC ${CMAKE_SOURCE_DIR}/test) + +target_link_libraries(AMRtest PUBLIC Kokkos::kokkos kalypsso::core kalypsso::log) +target_link_libraries(AMRtest PRIVATE kalypsso_warnings) + +add_library(kalypsso::AMRtest ALIAS AMRtest) diff --git a/test/test_common/DataWriter.cpp b/test/test_common/DataWriter.cpp new file mode 100644 index 0000000..b2d61ab --- /dev/null +++ b/test/test_common/DataWriter.cpp @@ -0,0 +1,532 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file DataWriter.cpp + */ + +#include "DataWriter.h" + +namespace kalypsso +{ + +// ================================================================================ +// ================================================================================ +template +void +DataWriter::save(std::string filename, + DataArrayLeaf_t userdataLeaf, + DataArrayBlock_t userdataBlock, + const ConfigMap & config_map, + AMRmesh /*const*/ & amr_mesh, + model_t const & model) +{ +#ifdef KALYPSSO_CORE_USE_HDF5 + const auto fm = model.get_fieldmap(); + const auto id2names = model.get_id2names_map(); + const auto names2id = model.get_names2id_map(); + + auto userdataLeaf_host = Kokkos::create_mirror_view(userdataLeaf); + Kokkos::deep_copy(userdataLeaf_host, userdataLeaf); + + auto userdataBlock_host = DataArrayBlock_t::create_host_mirror_view_and_copy(userdataBlock); + + std::string outputDir = config_map.getString("output", "outputDir", "./"); + + HDF5_Xdmf_Writer_legacy_t writer(amr_mesh.forest(), + amr_mesh.geometry(), + config_map, + userdataBlock_host.block_size(), + get_shift(0)); + writer.update_mesh_info(); + writer.open(filename, outputDir); + writer.write_header(0.0); + + // write user the test data (all enabled scalar fields) + { + std::string write_variables = config_map.getString("output", "write_variables", ""); + + for (auto & it : id2names) + { + auto varId = static_cast(it.first); + auto name = id2names.at(varId); + if (write_variables.find(name) != std::string::npos) + { + writer.write_quadrant_attribute(userdataBlock_host, fm[varId], name); + } + } + } + + // close the file + writer.write_footer(); + writer.close(); + + // now write data attached to octree leaf only (e.g. leaf level) + writer.set_leaf_mode(); + writer.set_write_mesh_info(true); + + std::string filename2 = filename + "_quad"; + + writer.open(filename2, outputDir); + writer.write_header(0.0); + + // write user the test data (all scalar fields, here only one) + { + std::string write_variables = config_map.getString("output", "write_variables", ""); + + for (auto & it : id2names) + { + auto varId = static_cast(it.first); + auto name = id2names.at(varId); + if (write_variables.find(name) != std::string::npos) + { + writer.write_quadrant_attribute(userdataLeaf_host, fm[varId], name); + } + } + } + + // close the file + writer.write_footer(); + writer.close(); +#else + std::cout << "HDF5 output not available\n"; +#endif +} // save - HDF5_Xdmf_Writer_legacy + +// ================================================================================ +// ================================================================================ +template +void +DataWriter::save(std::string filename, + DataArrayLeaf_t userdataLeaf, + DataArrayBlock_t userdataBlock, + const ConfigMap & config_map, + const ParallelEnv & par_env, + std::shared_ptr> mesh_map, + bool use_outside_quads, + model_t const & model) +{ +#ifdef KALYPSSO_CORE_USE_HDF5 + const auto fm = model.get_fieldmap(); + const auto id2names = model.get_id2names_map(); + const auto names2id = model.get_names2id_map(); + + auto userdataLeaf_host = Kokkos::create_mirror_view(userdataLeaf); + Kokkos::deep_copy(userdataLeaf_host, userdataLeaf); + + auto userdataBlock_host = DataArrayBlock_t::create_host_mirror_view_and_copy(userdataBlock); + + std::string outputDir = config_map.getString("output", "outputDir", "./"); + + std::string filename_cell = filename + "_cell"; + std::string filename_leaf = filename + "_leaf"; + + if (use_outside_quads) + { + filename_cell = filename_cell + "_outside"; + filename_leaf = filename_leaf + "_outside"; + } + + HDF5_Xdmf_Writer_t writer(par_env, config_map, mesh_map); + + const auto amr_mesh_info = mesh_map->get_amr_mesh_info(); + + // + // block mode + // + writer.set_block_mode(); + writer.use_outside_quads(use_outside_quads); + writer.set_write_mesh_info(true); + writer.open(filename_cell, outputDir); + writer.write_header(0.0); + + const int32_t iOct_begin = + use_outside_quads ? amr_mesh_info.local_num_quadrants() + amr_mesh_info.local_num_ghosts() : 0; + const int32_t nbOcts = use_outside_quads ? amr_mesh_info.local_num_quadrants_outside() + : userdataBlock_host.num_quadrants(); + + + // write user data - block mode - all enabled scalar fields + { + std::string write_variables = config_map.getString("output", "write_variables", ""); + + for (auto & it : id2names) + { + auto varId = static_cast(it.first); + auto name = id2names.at(varId); + if (write_variables.find(name) != std::string::npos) + { + writer.write_quadrant_attribute(userdataBlock_host, fm[varId], name, iOct_begin, nbOcts); + } + } // end for it + } + + // close the file + writer.write_footer(); + writer.close(); + + // + // now write data attached to octree leaf only (e.g. leaf level) + // + writer.set_leaf_mode(); + writer.use_outside_quads(use_outside_quads); + writer.set_write_mesh_info(true); + writer.open(filename_leaf, outputDir); + writer.write_header(0.0); + + // write user the test data (all scalar fields, here only one) + { + std::string write_variables = config_map.getString("output", "write_variables", ""); + + // extract slice as a contiguous memory buffer + auto nbVar = userdataLeaf_host.extent(1); + + if (use_outside_quads) + { + using DataArrayLeaf_t = DataArrayLeaf; + using DataArrayLeafHost_t = typename DataArrayLeaf_t::host_mirror_type; + DataArrayLeafHost_t userdataLeaf_outside_host( + Kokkos::view_alloc(Kokkos::WithoutInitializing, "dummy_data_outside"), + static_cast(amr_mesh_info.local_num_quadrants_outside()), + nbVar); + + const auto offset = amr_mesh_info.local_num_quadrants() + amr_mesh_info.local_num_ghosts(); + + const auto nbOctOutside = amr_mesh_info.local_num_quadrants_outside(); + Kokkos::parallel_for( + "extract outside data", + Kokkos::RangePolicy(0, nbOctOutside * static_cast(nbVar)), + KOKKOS_LAMBDA(int64_t index) { + // index = iOct + nbOct * varId + const auto varId = static_cast(index / nbOctOutside); + const auto iOct = index - varId * nbOctOutside; + userdataLeaf_outside_host(iOct, fm[varId]) = userdataLeaf_host(iOct + offset, fm[varId]); + }); + + // make sure all kokkos kernels are done before performing actual writing + Kokkos::fence(); + + for (auto & it : id2names) + { + auto varId = static_cast(it.first); + auto name = id2names.at(varId); + if (write_variables.find(name) != std::string::npos) + { + writer.write_quadrant_attribute(userdataLeaf_outside_host, fm[varId], name); + } + } + } + else + { + for (auto & it : id2names) + { + auto varId = static_cast(it.first); + auto name = id2names.at(varId); + if (write_variables.find(name) != std::string::npos) + { + writer.write_quadrant_attribute(userdataLeaf_host, fm[varId], name); + } + } + } // end if use_outside_quads + } // end write leaf data + + // close the file + writer.write_footer(); + writer.close(); +#else + std::cout << "HDF5 output not available\n"; +#endif + +} // save - HDF5_Xdmf_Writer + +// // ================================================================================ +// // ================================================================================ +// template +// void +// DataWriter::save(std::string filename, +// DataArrayLeaf_t userdataLeaf, +// const ConfigMap & config_map, +// AMRmesh /*const*/ & amr_mesh, +// model_t const & model) +// { +// #ifdef KALYPSSO_CORE_USE_HDF5 +// const auto fm = model.get_fieldmap(); +// const auto id2names = model.get_id2names_map(); +// const auto names2id = model.get_names2id_map(); + +// auto userdataLeaf_host = Kokkos::create_mirror_view(userdataLeaf); +// Kokkos::deep_copy(userdataLeaf_host, userdataLeaf); + +// std::string outputDir = config_map.getString("output", "outputDir", "./"); + +// HDF5_Xdmf_Writer_legacy_t writer( +// amr_mesh.forest(), amr_mesh.geometry(), config_map, get_block_size(1), +// get_shift(0)); +// writer.update_mesh_info(); + +// // write data attached to octree leaf only (e.g. leaf level) +// writer.set_leaf_mode(); +// writer.set_write_mesh_info(true); + +// std::string filename2 = filename + "_quad"; + +// writer.open(filename2, outputDir); +// writer.write_header(0.0); + +// // write user the test data (all scalar fields, here only one) +// { +// std::string write_variables = config_map.getString("output", "write_variables", ""); + +// for (auto & it : id2names) +// { +// auto varId = static_cast(it.first); +// auto name = id2names.at(varId); +// if (write_variables.find(name) != std::string::npos) +// { +// writer.write_quadrant_attribute(userdataLeaf_host, fm[varId], name); +// } +// } +// } + +// // close the file +// writer.write_footer(); +// writer.close(); +// #else +// std::cout << "HDF5 output not available\n"; +// #endif + +// } // save + +// ================================================================================ +// ================================================================================ +template +void +DataWriter::save(std::string filename, + DataArrayBlock_t userdataBlock, + const ConfigMap & config_map, + /*const*/ AMRmesh & amr_mesh, + model_t const & model) +{ +#ifdef KALYPSSO_CORE_USE_HDF5 + const auto fm = model.get_fieldmap(); + const auto id2names = model.get_id2names_map(); + const auto names2id = model.get_names2id_map(); + + auto userdataBlock_host = DataArrayBlock_t::create_host_mirror_view_and_copy(userdataBlock); + + std::string outputDir = config_map.getString("output", "outputDir", "./"); + + HDF5_Xdmf_Writer_legacy_t writer(amr_mesh.forest(), + amr_mesh.geometry(), + config_map, + userdataBlock.block_size(), + get_shift(0)); + writer.update_mesh_info(); + writer.open(filename, outputDir); + writer.write_header(0.0); + + // write user the test data (all enabled scalar fields) + { + std::string write_variables = config_map.getString("output", "write_variables", ""); + + for (auto & it : id2names) + { + auto varId = static_cast(it.first); + auto name = id2names.at(varId); + + if (write_variables.find(name) != std::string::npos) + { + writer.write_quadrant_attribute(userdataBlock_host, fm[varId], name); + } + } + } + + // close the file + writer.write_footer(); + writer.close(); +#else + std::cout << "HDF5 output not available\n"; +#endif + +} // save - DataArrayBlock_t + +// ================================================================================ +// ================================================================================ +template +void +DataWriter::save_scalar(std::string filename, + DataArrayBlock_t userdataBlock, + int ivar, + const ConfigMap & config_map, + /*const*/ AMRmesh & amr_mesh) +{ +#ifdef KALYPSSO_CORE_USE_HDF5 + + auto userdataBlock_host = DataArrayBlock_t::create_host_mirror_view_and_copy(userdataBlock); + + std::string outputDir = config_map.getString("output", "outputDir", "./"); + + HDF5_Xdmf_Writer_legacy_t writer(amr_mesh.forest(), + amr_mesh.geometry(), + config_map, + userdataBlock.block_size(), + get_shift(0)); + + writer.update_mesh_info(); + writer.open(filename, outputDir); + writer.write_header(0.0); + + // write user the test data (all enabled scalar fields) + writer.write_quadrant_attribute(userdataBlock_host, ivar, "var_" + std::to_string(ivar)); + + // close the file + writer.write_footer(); + writer.close(); +#else + std::cout << "HDF5 output not available\n"; +#endif + +} // save - DataArrayBlock_t + +// ================================================================================ +// ================================================================================ +template +void +DataWriter::save(std::string filename, + DataArrayGhostedBlock_t userdata_ghosted_block, + const ConfigMap & config_map, + /*const*/ AMRmesh & amr_mesh, + bool save_full, + model_t const & model) +{ +#ifdef KALYPSSO_CORE_USE_HDF5 + const auto fm = model.get_fieldmap(); + const auto id2names = model.get_id2names_map(); + const auto names2id = model.get_names2id_map(); + + auto userdata_ghosted_block_host = + DataArrayBlock_t::create_host_mirror_view_and_copy(userdata_ghosted_block.data()); + + auto total_block_sizes = userdata_ghosted_block.ghosted_block_size(); + + std::string outputDir = config_map.getString("output", "outputDir", "./"); + + // default we save the overlapping part + const auto start_index = userdata_ghosted_block.get_start_overlap(); + const auto block_size_overlap = userdata_ghosted_block.get_block_size_overlap(); + + HDF5_Xdmf_Writer_legacy_t writer( + amr_mesh.forest(), amr_mesh.geometry(), config_map, block_size_overlap, start_index); + + writer.set_write_mesh_info(true); + + if (save_full) + { + writer.set_block_mode(total_block_sizes, get_shift(0)); + } + + writer.update_mesh_info(); + writer.open(filename, outputDir); + writer.write_header(0.0); + + // write user the test data (all enabled scalar fields) + { + std::string write_variables = config_map.getString("output", "write_variables", ""); + + for (auto & it : id2names) + { + auto varId = static_cast(it.first); + auto name = id2names.at(varId); + if (write_variables.find(name) != std::string::npos) + { + writer.write_quadrant_attribute(userdata_ghosted_block_host, fm[varId], name); + } + } + } + + // close the file + writer.write_footer(); + writer.close(); +#else + std::cout << "HDF5 output not available\n"; +#endif + +} // save - DataArrayGhostedBlock_t + +// ================================================================================ +// ================================================================================ +template +void +DataWriter::save(std::string filename, + FaceDataArrayBlock_t facedata, + const ConfigMap & config_map, + /*const*/ AMRmesh & amr_mesh, + std::string varname) +{ +#ifdef KALYPSSO_CORE_USE_HDF5 + std::string outputDir = config_map.getString("output", "outputDir", "./"); + + const auto celldata = FaceDataArrayBlock_t::to_DataArrayBlock(facedata); + const auto celldata_host = DataArrayBlock_t::create_host_mirror_view_and_copy(celldata); + + HDF5_Xdmf_Writer_legacy_t writer( + amr_mesh.forest(), amr_mesh.geometry(), config_map, celldata.block_size(), get_shift(0)); + + writer.update_mesh_info(); + writer.open(filename, outputDir); + writer.write_header(0.0); + + if constexpr (dim == 2) + { + writer.write_quadrant_attribute(celldata_host, 0, varname + "_x_left"); + writer.write_quadrant_attribute(celldata_host, 1, varname + "_x_right"); + writer.write_quadrant_attribute(celldata_host, 2, varname + "_y_left"); + writer.write_quadrant_attribute(celldata_host, 3, varname + "_y_right"); + writer.write_quadrant_attribute(celldata_host, 4, varname + "_z"); + } + else if constexpr (dim == 3) + { + writer.write_quadrant_attribute(celldata_host, 0, varname + "_x_left"); + writer.write_quadrant_attribute(celldata_host, 1, varname + "_x_right"); + writer.write_quadrant_attribute(celldata_host, 2, varname + "_y_left"); + writer.write_quadrant_attribute(celldata_host, 3, varname + "_y_right"); + writer.write_quadrant_attribute(celldata_host, 4, varname + "_z_left"); + writer.write_quadrant_attribute(celldata_host, 5, varname + "_z_right"); + } + + // const auto data_x = FaceDataArrayBlock_t::to_DataArrayBlockCentered(userdataBlock_face, + // IX); const auto data_x_host = DataArrayBlock_t::create_host_mirror_view_and_copy(data_x); + + // writer.write_quadrant_attribute(data_x_host, 0, varname + "_x"); + + // const auto data_y = FaceDataArrayBlock_t::to_DataArrayBlockCentered(userdataBlock_face, + // IY); const auto data_y_host = DataArrayBlock_t::create_host_mirror_view_and_copy(data_y); + // writer.write_quadrant_attribute(data_y_host, 0, varname + "_y"); + + // const auto data_z = FaceDataArrayBlock_t::to_DataArrayBlockCentered(userdataBlock_face, + // IZ); const auto data_z_host = DataArrayBlock_t::create_host_mirror_view_and_copy(data_z); + // writer.write_quadrant_attribute(data_z_host, 0, varname + "_z"); + + // close the file + writer.write_footer(); + writer.close(); +#else + std::cout << "HDF5 output not available\n"; +#endif + +} // save - FaceDataArrayBlock_t + +template class DataWriter<2, kalypsso::DefaultDevice, core::models::Hydro>; +template class DataWriter<3, kalypsso::DefaultDevice, core::models::Hydro>; + +// only instantiate those class when the default device is not on host +#if defined(KOKKOS_ENABLE_DEFAULT_DEVICE_TYPE_OPENMP) || \ + defined(KOKKOS_ENABLE_DEFAULT_DEVICE_TYPE_THREADS) || \ + defined(KOKKOS_ENABLE_DEFAULT_DEVICE_TYPE_SERIAL) +#else +template class DataWriter<2, kalypsso::HostDevice, core::models::Hydro>; +template class DataWriter<3, kalypsso::HostDevice, core::models::Hydro>; +#endif + +} // namespace kalypsso diff --git a/test/test_common/DataWriter.h b/test/test_common/DataWriter.h new file mode 100644 index 0000000..b5aade9 --- /dev/null +++ b/test/test_common/DataWriter.h @@ -0,0 +1,153 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file DataWriter.h + */ +#ifndef KALYPSSO_TEST_DATAWRITER_H +#define KALYPSSO_TEST_DATAWRITER_H + +#include // for KALYPSSO_CORE_USE_HDF5, ... +#include +#include +#include +#include +#include + +#include +#include +#ifdef KALYPSSO_CORE_USE_HDF5 +# include +# include +#endif + +#include + +namespace kalypsso +{ + +// ============================================================= +// ============================================================= +template +struct DataWriter +{ + //! type alias to access p4est C API (2D or 3D) + using Wrapper = typename p4est::Wrapper; + using forest_t = typename Wrapper::forest_t; + using ghost_t = typename Wrapper::ghost_t; + + using DataArrayLeaf_t = DataArrayLeaf; + using DataArrayBlockLegacy_t = DataArrayBlockLegacy; + using DataArrayBlock_t = DataArrayBlock; + using DataArrayGhostedBlock_t = DataArrayGhostedBlock; + using FaceDataArrayBlock_t = FaceDataArrayBlock; + +#ifdef KALYPSSO_CORE_USE_HDF5 + using HDF5_Xdmf_Writer_legacy_t = HDF5_Xdmf_Writer_legacy; + using HDF5_Xdmf_Writer_t = HDF5_Xdmf_Writer; +#endif + + // ================================================================================ + // ================================================================================ + /** + * Save both block and leaf data using HDF5_Xdmf_Writer. + */ + static void + save(std::string filename, + DataArrayLeaf_t userdataLeaf, + DataArrayBlock_t userdataBlock, + const ConfigMap & config_map, + AMRmesh /*const*/ & amr_mesh, + model_t const & model); + // ================================================================================ + // ================================================================================ + /** + * Save both block and leaf data using HDF5_Xdmf_Writer. + */ + static void + save(std::string filename, + DataArrayLeaf_t userdataLeaf, + DataArrayBlock_t userdataBlock, + const ConfigMap & config_map, + const ParallelEnv & par_env, + std::shared_ptr> mesh_map, + bool use_outside_quads, + model_t const & model); + // // ================================================================================ + // // ================================================================================ + // /** + // * Only save leaf data. + // */ + // static void + // save(std::string filename, + // DataArrayLeaf_t userdataLeaf, + // const ConfigMap & config_map, + // AMRmesh /*const*/ & amr_mesh, + // model_t const & model); + + // ================================================================================ + // ================================================================================ + /** + * Save only block data. + */ + static void + save(std::string filename, + DataArrayBlock_t userdataBlock, + const ConfigMap & config_map, + /*const*/ AMRmesh & amr_mesh, + model_t const & model); + + // ================================================================================ + // ================================================================================ + /** + * Save a scalar block data (no field map). + */ + static void + save_scalar(std::string filename, + DataArrayBlock_t userdataBlock, + int ivar, + const ConfigMap & config_map, + /*const*/ AMRmesh & amr_mesh); + + // ================================================================================ + // ================================================================================ + /** + * Save a ghosted block data. + */ + static void + save(std::string filename, + DataArrayGhostedBlock_t userdata_ghosted_block, + const ConfigMap & config_map, + /*const*/ AMRmesh & amr_mesh, + bool save_full, + model_t const & model); + + // ================================================================================ + // ================================================================================ + /** + * Save only block data of face-centered data. + */ + static void + save(std::string filename, + FaceDataArrayBlock_t facedata, + const ConfigMap & config_map, + /*const*/ AMRmesh & amr_mesh, + std::string varname); + +}; // struct DataWriter + +extern template class DataWriter<2, kalypsso::DefaultDevice, core::models::Hydro>; +extern template class DataWriter<3, kalypsso::DefaultDevice, core::models::Hydro>; + +// only instantiate those class when the default device is not on host +#if defined(KOKKOS_ENABLE_DEFAULT_DEVICE_TYPE_OPENMP) || \ + defined(KOKKOS_ENABLE_DEFAULT_DEVICE_TYPE_THREADS) || \ + defined(KOKKOS_ENABLE_DEFAULT_DEVICE_TYPE_SERIAL) +#else +extern template class DataWriter<2, kalypsso::HostDevice, core::models::Hydro>; +extern template class DataWriter<3, kalypsso::HostDevice, core::models::Hydro>; +#endif +} // namespace kalypsso + +#endif // KALYPSSO_TEST_DATAWRITER_H diff --git a/test/test_common/FillOutside.cpp b/test/test_common/FillOutside.cpp new file mode 100644 index 0000000..f1d2073 --- /dev/null +++ b/test/test_common/FillOutside.cpp @@ -0,0 +1,268 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file FillOutside.cpp + * + * Implement border conditions (other than periodic) for Hydrodynamics. + */ + +#include "FillOutside.h" + +#include // key_to_value + +namespace kalypsso +{ + +namespace test +{ + +// ============================================================================== +// ============================================================================== +template +FillOutsideCellFunctor::FillOutsideCellFunctor( + ConfigMap const & config_map, + amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + AMRMeshInfo amr_mesh_info, + DataArrayBlock_t userdata, + block_size_t block_sizes, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + bc_array_t bc_types, + AnalyticalBC const & f) + : m_amr_hashmap_device(amr_hashmap) + , m_orchard_keys_device(orchard_keys) + , m_amr_mesh_info(amr_mesh_info) + , m_userdata(userdata) + , m_block_sizes(block_sizes) + , m_brick_sizes(brick_sizes) + , m_is_brick_periodic(is_brick_periodic) + , m_bc_types(bc_types) + , m_f(f) + , m_scaling_factor(get_scaling_factor(config_map)) + , m_xyz_min(get_xyz_min(config_map)) +{} + +// ============================================================== +// ============================================================== +template +void +FillOutsideCellFunctor::apply( + ConfigMap const & config_map, + amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + AMRMeshInfo amr_mesh_info, + DataArrayBlock_t userdata, + block_size_t block_sizes, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + bc_array_t bc_types) +{ + + // create compute functor + FillOutsideCellFunctor functor(config_map, + amr_hashmap, + orchard_keys, + amr_mesh_info, + userdata, + block_sizes, + brick_sizes, + is_brick_periodic, + bc_types); + + const auto nbCellsPerLeaf = userdata.num_cells(); + + // only count outside leaves (beyond external border) + const auto totalNumberOfOutsideCells = + nbCellsPerLeaf * amr_mesh_info.total_local_number_of_outside_quads(); + + Kokkos::parallel_for( + "FillOutsideFunctor", Kokkos::RangePolicy(0, totalNumberOfOutsideCells), functor); + +} // apply + +// ============================================================== +// ============================================================== +template +void +FillOutsideCellFunctor::apply( + ConfigMap const & config_map, + amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + AMRMeshInfo amr_mesh_info, + DataArrayBlock_t userdata, + block_size_t block_sizes, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + bc_array_t bc_types, + const AnalyticalBC & f) +{ + // create compute functor + FillOutsideCellFunctor functor(config_map, + amr_hashmap, + orchard_keys, + amr_mesh_info, + userdata, + block_sizes, + brick_sizes, + is_brick_periodic, + bc_types, + f); + + const auto nbCellsPerLeaf = userdata.num_cells(); + + // only count outside leaves (beyond external border) + const auto totalNumberOfOutsideCells = + nbCellsPerLeaf * amr_mesh_info.total_local_number_of_outside_quads(); + + Kokkos::parallel_for( + "FillOutsideFunctor", Kokkos::RangePolicy(0, totalNumberOfOutsideCells), functor); + +} // apply - analytical border conditions + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +FillOutsideCellFunctor::operator()(const index_t & global_index) const +{ + + const auto nbCellsPerLeaf = m_userdata.num_cells(); + + // iOct_local, by design, is associated to an outside quadrant + const auto iOct_outside = + global_index / nbCellsPerLeaf + m_amr_mesh_info.first_outside_quad_local_id(); + + const auto cellindex_out = global_index - nbCellsPerLeaf * (global_index / nbCellsPerLeaf); + const auto coord_out = cellindex_to_coord(cellindex_out, m_block_sizes); + + // get orchard key corresponding to visited outside quadrants + const auto key_outside = m_orchard_keys_device(iOct_outside); + + // when computing inside quadrant key, we always use a "virtual" key computed as the + // periodic image of the outside quadrant; so here we need is_periodic to be array of + // "true" + constexpr auto is_periodic = get_bool_array(true); + + // quadrant is at domain border, compute outside normal at the periodic image of key_outside + auto outside_normal = + orchard_key_t::get_outside_normal(key_outside, m_brick_sizes, is_periodic); + + outside_normal[IX] = outside_normal[IX] * orchard_key_t::is_touching_face_X(key_outside); + outside_normal[IY] = outside_normal[IY] * orchard_key_t::is_touching_face_Y(key_outside); + if constexpr (dim == 3) + { + outside_normal[IZ] = outside_normal[IZ] * orchard_key_t::is_touching_face_Z(key_outside); + } + + // compute corresponding inside quadrant (just across external border, i.e. along outside + // normal, but opposite direction) + auto key_inside = orchard_key_t::get_neighbor_key_same_level( + key_outside, outside_normal, m_brick_sizes, m_is_brick_periodic); + orchard_key_t::reset_outside_bits(key_inside); + + // get iOct_inside from the unordered map + const auto key_status = key_to_value(key_inside, m_amr_hashmap_device); + + [[maybe_unused]] auto const & is_valid_key = key_status.first; + + // make sure key_inside actually exist in map + // if it doesn't we have a serious logical problem + KOKKOS_ASSERT(is_valid_key && + "[kalypsso::FillOutsideCellFunctor] key_inside does not exist in hashmap !?"); + + const auto iOct_inside = key_status.second; + + // make sure iOct_inside is actually inside + KOKKOS_ASSERT( + iOct_inside < m_amr_mesh_info.first_outside_quad_local_id() && + "[kalypsso::FillOutsideCellFunctor] iOct does not identify an octant inside domain ?!"); + + // + // now we can fill outside userdata according to border condition + // + + // get list of faces + const auto faces = Face::get_all_faces(); + + for (const auto face : faces) + { + // normal direction + const Dir::dir_t dir = face / 2; + + if (orchard_key_t::is_at_domain_border(key_inside, face, m_brick_sizes) and + orchard_key_t::is_outside_dir(key_outside, dir)) + { + if (m_bc_types[face]._to_integral() == +BC_HYDRO::ZERO_GRADIENT) + { + auto coord_in = coord_out; + coord_in[dir] = Face::is_left_face(face) ? 0 : m_block_sizes[dir] - 1; + auto cellindex_in = coord_to_cellindex(coord_in, m_block_sizes); + + for (int32_t ivar = 0; ivar < m_userdata.num_vars(); ++ivar) + m_userdata(cellindex_out, ivar, iOct_outside) = + m_userdata(cellindex_in, ivar, iOct_inside); + } + else if (m_bc_types[face]._to_integral() == +BC_HYDRO::WALL) + { + auto coord_in = coord_out; + + coord_in[dir] = m_block_sizes[dir] - 1 - coord_out[dir]; + + auto cellindex_in = coord_to_cellindex(coord_in, m_block_sizes); + + // copy data and negate normal velocity + for (int32_t ivar = 0; ivar < m_userdata.num_vars(); ++ivar) + { + real_t scale = KALYPSSO_NUM(1.0); + if (ivar == (Hydro::IU + dir)) + { + scale = KALYPSSO_NUM(-1.0); + } + m_userdata(cellindex_out, ivar, iOct_outside) = + scale * m_userdata(cellindex_in, ivar, iOct_inside); + } // end for ivar + + } // end BC_HYDRO::WALL + else if (m_bc_types[face]._to_integral() == +BC_HYDRO::ANALYTICAL) + { + + const auto xyz_corner = outside_key_to_vertex_coord(key_outside, false, m_brick_sizes); + + const auto xyz_cell_vertex = compute_cell_coordinates( + orchard_key_t::level(key_outside), xyz_corner, coord_out, m_block_sizes); + + const auto xyz_cell = + vertex_coord_to_real_space(xyz_cell_vertex, m_scaling_factor, m_xyz_min); + + for (int32_t ivar = 0; ivar < m_userdata.num_vars(); ++ivar) + { + if constexpr (dim == 2) + { + m_userdata(cellindex_out, ivar, iOct_outside) = m_f(xyz_cell[IX], xyz_cell[IY], ivar); + } + else if constexpr (dim == 3) + { + m_userdata(cellindex_out, ivar, iOct_outside) = + m_f(xyz_cell[IX], xyz_cell[IY], xyz_cell[IZ], ivar); + } + } + } // end BC_HYDRO::WALL + } // end if is_at_domain_border + } // for faces + +} // operator() + +// explicit template instantiation +template class FillOutsideCellFunctor<2, kalypsso::DefaultDevice>; +template class FillOutsideCellFunctor<3, kalypsso::DefaultDevice>; +template class FillOutsideCellFunctor<2, kalypsso::DefaultDevice, InitFunc1>; +template class FillOutsideCellFunctor<3, kalypsso::DefaultDevice, InitFunc1>; +template class FillOutsideCellFunctor<2, kalypsso::DefaultDevice, InitFunc2>; +template class FillOutsideCellFunctor<3, kalypsso::DefaultDevice, InitFunc2>; + +} // namespace test + +} // namespace kalypsso diff --git a/test/test_common/FillOutside.h b/test/test_common/FillOutside.h new file mode 100644 index 0000000..4ce2579 --- /dev/null +++ b/test/test_common/FillOutside.h @@ -0,0 +1,262 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file FillOutside.h + * + * Implement border conditions (other than periodic) for Hydrodynamics. + */ +#ifndef KALYPSSO_TEST_FILLOUTSIDE_H_ +#define KALYPSSO_TEST_FILLOUTSIDE_H_ + +#include + +#include // for DataArray, DataArrayHost, DataArrayGhostedBlock + +#include +#include +#include +#include + +#include // for definition of function cellindex_to_coord and coord_to_cellindex +#include +#include // for InitFunc1 and InitFunc2 + +#include + +#include <../better-enums/enum.h> + +namespace kalypsso +{ + +namespace test +{ + +/** + * Define a (better) enum to list supported boundary conditions. + * + * - NONE: don't fill this border + * - PERIODIC : don't fill this border (it will be filled by MPI ghost exchange operations). + * - ZERO_GRADIENT: it means here that we fill outside cells with a copy of the "last" cell inside + * domain. + * - WALL : it means here that we fill outside cells with wall boundary condition (normal + * velocity is negated, other variable are copied using planar symmetry; at edge we use + * axial symmetry and at corners, central symmetry). + * - BC_CUSTOM : provide a pointwise functor (TODO) + * + * \todo should we consider : + * - DIRICHLET (constant value at border) ? + * - NEUMANN (constant gradient at border) ? + * + * \note a (better) enum cannot be defined inside a class (that is a bit annoying) + */ +// clang-format off +BETTER_ENUM(BC_HYDRO, uint32_t, + NONE, + PERIODIC, + ZERO_GRADIENT, + WALL, + ANALYTICAL, + CUSTOM + ) +// clang-format on + +// ============================================================================== +// ============================================================================== +/** + * \class FillOutsideCellFunctor + * + * A simple prototyping boundary condition class. + * + * \note periodic boundary condition is a special case that is not managed here. + * \note in a given direction, either all opposite sides are periodic, or none. + * + * \tparam AnalyticalBC must be a functor with 2 operators(), so that, if "f" is an instance of + * AnalyticalBC, then f(x,y,var) and f(x,y,z,var) will provide analytical values (for both + * 2d and 3D) + * + * How are filled cells in corner (2d) ? + * corner are currently filled twice, border condition along Y border prevail X border. + * Same for 3D. + * + * -------------------------------- + * | 3 | + * |______________________________| + * | | | | + * | | | | + * | 1 | | 2 | + * | | | | + * | | | | + * |____|____________________|____| + * | 4 | + * |______________________________| + * + * In you need a border condition with more control of how corner blocks are filled, you need to + * modify/customize this prototype border condition functor. + */ +template +class FillOutsideCellFunctor +{ +public: + using exec_space = typename device_t::execution_space; + using index_t = int32_t; + + using amr_hashmap_t = typename hashmap_base_t::map_t; + using orchard_key_view_t = typename orchard_key_base_t::view_t; + + using DataArrayBlock_t = DataArrayBlock; + + // makes enum Hydro::VarId available + using Hydro = kalypsso::core::models::Hydro; + + using bc_array_t = BorderConditionsConfig::bc_array_t; + + /** + * + * \param[in] amr_hashmap unordered map from orchard key to memory index for owned and ghost + * quadrants + * \param[in] orchard_keys array of orchard key ordered by Morton order + * \param[in] amr_info gives the number of owned, ghost, outside, ghost_outside quads + * \param[in,out] userdata_out data array which we want to fill the block ghosts cells + * \param[in] brick_sizes is an array of p4est brick connectivity sizes (number of trees + * in each dimension) + * \param[in] boundary condition type (for each boundary: xmin, xmax, ymin, ymax, ...) + * + * This constructor only specify BC type for faces. Corners (and edges) will be filled with zero + * gradient (default). + * + * \todo: add another constructor containing BC type for all geometrical elements (faces, edges + * and corners). This should be think through. + * + */ + FillOutsideCellFunctor(ConfigMap const & config_map, + amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + AMRMeshInfo amr_mesh_info, + DataArrayBlock_t userdata, + block_size_t block_sizes, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + bc_array_t bc_types, + AnalyticalBC const & f = AnalyticalBC{}); + + // ============================================================== + // ============================================================== + //! static method which does it all: create and execute functor with range policy + static void + apply(ConfigMap const & config_map, + amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + AMRMeshInfo amr_mesh_info, + DataArrayBlock_t userdata, + block_size_t block_sizes, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + bc_array_t bc_types); + + // ============================================================== + // ============================================================== + //! special case: apply analytical border condition specified with a functor + //! + static void + apply(ConfigMap const & config_map, + amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + AMRMeshInfo amr_mesh_info, + DataArrayBlock_t userdata, + block_size_t block_sizes, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + bc_array_t bc_types, + const AnalyticalBC & f); + + // ============================================================== + // ============================================================== + /** + * range policy functor to sweep outside leaves. + * + * Just explaining inside/outside mirroring. + * _ + * ____________________|1|________ + * | |2| | + * | | + * | | + * | _ | + * |____________________|3|________| + * |4| + * + * Reminder: + * + * - 1 and 4 are outside quadrants + * - 2 and 3 are inside quadrants + * - 1 and 3 have the same orchard key, except the outside status bits + * - 2 and 4 have the same orchard key, except the outside status bits + * + * Suppose we have orchard key of 1 (say key1) and we want to compute orchard key of quadrant 2: + * + * 1. from key1, compute key3 (just change outside status bits) + * 2. compute outside normal from key3 (which the same as outside normal of key1, since key1==key3 + * and compute normal does not depend on outside status) + * 3. use outside normal to compute key4 from key3 + * 4. deduce key2 from key4 (same orchard key, different outside bit status) + * + */ + KOKKOS_INLINE_FUNCTION void + operator()(const index_t & global_index) const; + +private: + //! AMR unordered map which maps orchard keys to quadrant number for all key in the mesh + //! (owned quadrants and ghost quadrants) + amr_hashmap_t m_amr_hashmap_device; + + //! list of orchard key of the mesh + orchard_key_view_t m_orchard_keys_device; + + //! AMR mesh information (number of owned quadrants, number of ghosts quadrants, number of + //! outside quads, number of outside ghosts, etc...) + AMRMeshInfo m_amr_mesh_info; + + //! a block data array (no ghosts, sizes= bx,by,bz) + DataArrayBlock_t m_userdata; + + //! block sizes + const block_size_t m_block_sizes; + + //! p4est brick connectivity sizes + const brick_size_t m_brick_sizes; + + //! foer each direction, state if mesh is periodic + const Kokkos::Array m_is_brick_periodic; + + //! border conditions array (one for each face, edge, corner) + const bc_array_t m_bc_types; + + //! only used when doing analytical border conditions (only really useful for test and debug) + const AnalyticalBC & m_f; + + //! get geometrical scaling factor + const real_t m_scaling_factor; + + //! get domain lower left corner + const Kokkos::Array m_xyz_min; + +}; // class FillOutsideCellFunctor + +// explicit template instantiation +extern template class FillOutsideCellFunctor<2, kalypsso::DefaultDevice>; +extern template class FillOutsideCellFunctor<3, kalypsso::DefaultDevice>; + +// this may not be handy: +// we have to declare here, and instantiate in FillOutside.cpp all class for a particular desired +// init functor. +extern template class FillOutsideCellFunctor<2, kalypsso::DefaultDevice, InitFunc1>; +extern template class FillOutsideCellFunctor<3, kalypsso::DefaultDevice, InitFunc1>; +extern template class FillOutsideCellFunctor<2, kalypsso::DefaultDevice, InitFunc2>; +extern template class FillOutsideCellFunctor<3, kalypsso::DefaultDevice, InitFunc2>; + +} // namespace test + +} // namespace kalypsso + +#endif // KALYPSSO_TEST_FILLOUTSIDE_H_ diff --git a/test/test_common/FillOutside2.cpp b/test/test_common/FillOutside2.cpp new file mode 100644 index 0000000..1874e51 --- /dev/null +++ b/test/test_common/FillOutside2.cpp @@ -0,0 +1,263 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file FillOutside2.cpp + * + * Implement border conditions (other than periodic) for Hydrodynamics. + */ + +#include "FillOutside2.h" + +#include // key_to_value + +namespace kalypsso +{ + +namespace test +{ + +// ============================================================================== +// ============================================================================== +template +FillOutside2CellFunctor::FillOutside2CellFunctor( + ConfigMap const & config_map, + amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + AMRMeshInfo amr_mesh_info, + DataArrayGhostedBlock_t userdata, + block_size_t block_sizes, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + bc_array_t bc_types, + AnalyticalBC const & f) + : m_amr_hashmap_device(amr_hashmap) + , m_orchard_keys_device(orchard_keys) + , m_amr_mesh_info(amr_mesh_info) + , m_userdata(userdata) + , m_block_sizes(block_sizes) + , m_brick_sizes(brick_sizes) + , m_is_brick_periodic(is_brick_periodic) + , m_bc_types(bc_types) + , m_f(f) + , m_scaling_factor(get_scaling_factor(config_map)) + , m_xyz_min(get_xyz_min(config_map)) +{} + +// ============================================================== +// ============================================================== +template +void +FillOutside2CellFunctor::apply( + ConfigMap const & config_map, + amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + AMRMeshInfo amr_mesh_info, + DataArrayGhostedBlock_t userdata, + block_size_t block_sizes, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + bc_array_t bc_types) +{ + + // create compute functor + FillOutside2CellFunctor functor(config_map, + amr_hashmap, + orchard_keys, + amr_mesh_info, + userdata, + block_sizes, + brick_sizes, + is_brick_periodic, + bc_types); + + const auto nbCellsPerLeaf = userdata.num_cells_inner(); + + // only count outside leaves (beyond external border) + const auto totalNumberOfOutsideCells = + nbCellsPerLeaf * amr_mesh_info.total_local_number_of_outside_quads(); + + Kokkos::parallel_for( + "FillOutside2Functor", Kokkos::RangePolicy(0, totalNumberOfOutsideCells), functor); + +} // apply + +// ============================================================== +// ============================================================== +template +void +FillOutside2CellFunctor::apply( + ConfigMap const & config_map, + amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + AMRMeshInfo amr_mesh_info, + DataArrayGhostedBlock_t userdata, + block_size_t block_sizes, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + bc_array_t bc_types, + const AnalyticalBC & f) +{ + // create compute functor + FillOutside2CellFunctor functor(config_map, + amr_hashmap, + orchard_keys, + amr_mesh_info, + userdata, + block_sizes, + brick_sizes, + is_brick_periodic, + bc_types, + f); + + const auto nbCellsPerLeaf = userdata.num_cells_inner(); + + // only count outside leaves (beyond external border) + const auto totalNumberOfOutsideCells = + nbCellsPerLeaf * amr_mesh_info.total_local_number_of_outside_quads(); + + Kokkos::parallel_for( + "FillOutside2Functor", Kokkos::RangePolicy(0, totalNumberOfOutsideCells), functor); + +} // apply - analytical border conditions + +// ============================================================== +// ============================================================== +template +KOKKOS_INLINE_FUNCTION void +FillOutside2CellFunctor::operator()(const index_t & global_index) const +{ + + const auto nbCellsPerLeaf = m_userdata.num_cells_inner(); + + // iOct_local, by design, is associated to an outside quadrant + const auto iOct_outside = + global_index / nbCellsPerLeaf + m_amr_mesh_info.first_outside_quad_local_id(); + + const auto cellindex_out = global_index - nbCellsPerLeaf * (global_index / nbCellsPerLeaf); + const auto coord_out = cellindex_to_coord(cellindex_out, m_block_sizes); + + // get orchard key corresponding to visited outside quadrants + const auto key_outside = m_orchard_keys_device(iOct_outside); + + // when computing inside quadrant key, we always use a "virtual" key computed as the + // periodic image of the outside quadrant; so here we need is_periodic to be array of + // "true" + constexpr auto is_periodic = get_bool_array(true); + + // quadrant is at domain border, compute outside normal at the periodic image of key_outside + auto outside_normal = + orchard_key_t::get_outside_normal(key_outside, m_brick_sizes, is_periodic); + + outside_normal[IX] = outside_normal[IX] * orchard_key_t::is_touching_face_X(key_outside); + outside_normal[IY] = outside_normal[IY] * orchard_key_t::is_touching_face_Y(key_outside); + if constexpr (dim == 3) + { + outside_normal[IZ] = outside_normal[IZ] * orchard_key_t::is_touching_face_Z(key_outside); + } + + // compute corresponding inside quadrant (just across external border, i.e. along outside + // normal, but opposite direction) + auto key_inside = orchard_key_t::get_neighbor_key_same_level( + key_outside, outside_normal, m_brick_sizes, m_is_brick_periodic); + orchard_key_t::reset_outside_bits(key_inside); + + // get iOct_inside from the unordered map + const auto key_status = key_to_value(key_inside, m_amr_hashmap_device); + + [[maybe_unused]] auto const & is_valid_key = key_status.first; + + // make sure key_inside actually exist in map + // if it doesn't we have a serious logical problem + KOKKOS_ASSERT(is_valid_key && + "[kalypsso::FillOutside2CellFunctor] key_inside does not exist in hashmap !?"); + + const auto iOct_inside = key_status.second; + + // make sure iOct_inside is actually inside + KOKKOS_ASSERT( + iOct_inside < m_amr_mesh_info.first_outside_quad_local_id() && + "[kalypsso::FillOutside2CellFunctor] iOct does not identify an octant inside domain ?!"); + + // + // now we can fill outside userdata according to border condition + // + + // get list of faces + const auto faces = Face::get_all_faces(); + + for (const auto face : faces) + { + // normal direction + const Dir::dir_t dir = face / 2; + + if (orchard_key_t::is_at_domain_border(key_inside, face, m_brick_sizes) and + orchard_key_t::is_outside_dir(key_outside, dir)) + { + if (m_bc_types[face]._to_integral() == +BC_HYDRO::ZERO_GRADIENT) + { + auto coord_in = coord_out; + coord_in[dir] = Face::is_left_face(face) ? 0 : m_block_sizes[dir] - 1; + + for (int32_t ivar = 0; ivar < m_userdata.num_vars(); ++ivar) + m_userdata(coord_out, ivar, iOct_outside) = m_userdata(coord_in, ivar, iOct_inside); + } + else if (m_bc_types[face]._to_integral() == +BC_HYDRO::WALL) + { + auto coord_in = coord_out; + coord_in[dir] = m_block_sizes[dir] - 1 - coord_out[dir]; + + // copy data and negate normal velocity + for (int32_t ivar = 0; ivar < m_userdata.num_vars(); ++ivar) + { + real_t scale = KALYPSSO_NUM(1.0); + if (ivar == (Hydro::IU + dir)) + { + scale = KALYPSSO_NUM(-1.0); + } + m_userdata(coord_out, ivar, iOct_outside) = + scale * m_userdata(coord_in, ivar, iOct_inside); + } // end for ivar + + } // end BC_HYDRO::WALL + else if (m_bc_types[face]._to_integral() == +BC_HYDRO::ANALYTICAL) + { + + const auto xyz_corner = outside_key_to_vertex_coord(key_outside, false, m_brick_sizes); + + const auto xyz_cell_vertex = compute_cell_coordinates( + orchard_key_t::level(key_outside), xyz_corner, coord_out, m_block_sizes); + + const auto xyz_cell = + vertex_coord_to_real_space(xyz_cell_vertex, m_scaling_factor, m_xyz_min); + + for (int32_t ivar = 0; ivar < m_userdata.num_vars(); ++ivar) + { + if constexpr (dim == 2) + { + m_userdata(coord_out, ivar, iOct_outside) = m_f(xyz_cell[IX], xyz_cell[IY], ivar); + } + else if constexpr (dim == 3) + { + m_userdata(coord_out, ivar, iOct_outside) = + m_f(xyz_cell[IX], xyz_cell[IY], xyz_cell[IZ], ivar); + } + } + } // end BC_HYDRO::WALL + } // end if is_at_domain_border + } // for faces + +} // operator() + +// explicit template instantiation +template class FillOutside2CellFunctor<2, kalypsso::DefaultDevice>; +template class FillOutside2CellFunctor<3, kalypsso::DefaultDevice>; +template class FillOutside2CellFunctor<2, kalypsso::DefaultDevice, InitFunc1>; +template class FillOutside2CellFunctor<3, kalypsso::DefaultDevice, InitFunc1>; +template class FillOutside2CellFunctor<2, kalypsso::DefaultDevice, InitFunc2>; +template class FillOutside2CellFunctor<3, kalypsso::DefaultDevice, InitFunc2>; + +} // namespace test + +} // namespace kalypsso diff --git a/test/test_common/FillOutside2.h b/test/test_common/FillOutside2.h new file mode 100644 index 0000000..d20e882 --- /dev/null +++ b/test/test_common/FillOutside2.h @@ -0,0 +1,265 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file FillOutside2.h + * + * Implement border conditions (other than periodic) for Hydrodynamics. + * + * Same as FillOutside but using DataArrayGhostedBlock and filling only the inner part of the block + * of cells. + */ +#ifndef KALYPSSO_TEST_FILLOUTSIDE2_H_ +#define KALYPSSO_TEST_FILLOUTSIDE2_H_ + +#include + +#include // for DataArray, DataArrayHost, DataArrayGhostedBlock + +#include +#include +#include +#include + +#include // for definition of function cellindex_to_coord and coord_to_cellindex +#include +#include // for InitFunc1 and InitFunc2 + +#include + +#include <../better-enums/enum.h> + +namespace kalypsso +{ + +namespace test +{ + +/** + * Define a (better) enum to list supported boundary conditions. + * + * - NONE: don't fill this border + * - PERIODIC : don't fill this border (it will be filled by MPI ghost exchange operations). + * - ZERO_GRADIENT: it means here that we fill outside cells with a copy of the "last" cell inside + * domain. + * - WALL : it means here that we fill outside cells with wall boundary condition (normal + * velocity is negated, other variable are copied using planar symmetry; at edge we use + * axial symmetry and at corners, central symmetry). + * - BC_CUSTOM : provide a pointwise functor (TODO) + * + * \todo should we consider : + * - DIRICHLET (constant value at border) ? + * - NEUMANN (constant gradient at border) ? + * + * \note a (better) enum cannot be defined inside a class (that is a bit annoying) + */ +// clang-format off +BETTER_ENUM(BC_HYDRO, uint32_t, + NONE, + PERIODIC, + ZERO_GRADIENT, + WALL, + ANALYTICAL, + CUSTOM + ) +// clang-format on + +// ============================================================================== +// ============================================================================== +/** + * \class FillOutside2CellFunctor + * + * A simple prototyping boundary condition class. + * + * \note periodic boundary condition is a special case that is not managed here. + * \note in a given direction, either all opposite sides are periodic, or none. + * + * \tparam AnalyticalBC must be a functor with 2 operators(), so that, if "f" is an instance of + * AnalyticalBC, then f(x,y,var) and f(x,y,z,var) will provide analytical values (for both + * 2d and 3D) + * + * How are filled cells in corner (2d) ? + * corner are currently filled twice, border condition along Y border prevail X border. + * Same for 3D. + * + * -------------------------------- + * | 3 | + * |______________________________| + * | | | | + * | | | | + * | 1 | | 2 | + * | | | | + * | | | | + * |____|____________________|____| + * | 4 | + * |______________________________| + * + * In you need a border condition with more control of how corner blocks are filled, you need to + * modify/customize this prototype border condition functor. + */ +template +class FillOutside2CellFunctor +{ +public: + using exec_space = typename device_t::execution_space; + using index_t = int32_t; + + using amr_hashmap_t = typename hashmap_base_t::map_t; + using orchard_key_view_t = typename orchard_key_base_t::view_t; + + using DataArrayGhostedBlock_t = DataArrayGhostedBlock; + + // makes enum Hydro::VarId available + using Hydro = kalypsso::core::models::Hydro; + + using bc_array_t = BorderConditionsConfig::bc_array_t; + + /** + * + * \param[in] amr_hashmap unordered map from orchard key to memory index for owned and ghost + * quadrants + * \param[in] orchard_keys array of orchard key ordered by Morton order + * \param[in] amr_info gives the number of owned, ghost, outside, ghost_outside quads + * \param[in,out] userdata_out data array which we want to fill the block ghosts cells + * \param[in] brick_sizes is an array of p4est brick connectivity sizes (number of trees + * in each dimension) + * \param[in] boundary condition type (for each boundary: xmin, xmax, ymin, ymax, ...) + * + * This constructor only specify BC type for faces. Corners (and edges) will be filled with zero + * gradient (default). + * + * \todo: add another constructor containing BC type for all geometrical elements (faces, edges + * and corners). This should be think through. + * + */ + FillOutside2CellFunctor(ConfigMap const & config_map, + amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + AMRMeshInfo amr_mesh_info, + DataArrayGhostedBlock_t userdata, + block_size_t block_sizes, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + bc_array_t bc_types, + AnalyticalBC const & f = AnalyticalBC{}); + + // ============================================================== + // ============================================================== + //! static method which does it all: create and execute functor with range policy + static void + apply(ConfigMap const & config_map, + amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + AMRMeshInfo amr_mesh_info, + DataArrayGhostedBlock_t userdata, + block_size_t block_sizes, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + bc_array_t bc_types); + + // ============================================================== + // ============================================================== + //! special case: apply analytical border condition specified with a functor + //! + static void + apply(ConfigMap const & config_map, + amr_hashmap_t amr_hashmap, + orchard_key_view_t orchard_keys, + AMRMeshInfo amr_mesh_info, + DataArrayGhostedBlock_t userdata, + block_size_t block_sizes, + brick_size_t brick_sizes, + Kokkos::Array is_brick_periodic, + bc_array_t bc_types, + const AnalyticalBC & f); + + // ============================================================== + // ============================================================== + /** + * range policy functor to sweep outside leaves. + * + * Just explaining inside/outside mirroring. + * _ + * ____________________|1|________ + * | |2| | + * | | + * | | + * | _ | + * |____________________|3|________| + * |4| + * + * Reminder: + * + * - 1 and 4 are outside quadrants + * - 2 and 3 are inside quadrants + * - 1 and 3 have the same orchard key, except the outside status bits + * - 2 and 4 have the same orchard key, except the outside status bits + * + * Suppose we have orchard key of 1 (say key1) and we want to compute orchard key of quadrant 2: + * + * 1. from key1, compute key3 (just change outside status bits) + * 2. compute outside normal from key3 (which the same as outside normal of key1, since key1==key3 + * and compute normal does not depend on outside status) + * 3. use outside normal to compute key4 from key3 + * 4. deduce key2 from key4 (same orchard key, different outside bit status) + * + */ + KOKKOS_INLINE_FUNCTION void + operator()(const index_t & global_index) const; + +private: + //! AMR unordered map which maps orchard keys to quadrant number for all key in the mesh + //! (owned quadrants and ghost quadrants) + amr_hashmap_t m_amr_hashmap_device; + + //! list of orchard key of the mesh + orchard_key_view_t m_orchard_keys_device; + + //! AMR mesh information (number of owned quadrants, number of ghosts quadrants, number of + //! outside quads, number of outside ghosts, etc...) + AMRMeshInfo m_amr_mesh_info; + + //! a block data array (no ghosts, sizes= bx,by,bz) + DataArrayGhostedBlock_t m_userdata; + + //! block sizes + const block_size_t m_block_sizes; + + //! p4est brick connectivity sizes + const brick_size_t m_brick_sizes; + + //! foer each direction, state if mesh is periodic + const Kokkos::Array m_is_brick_periodic; + + //! border conditions array (one for each face, edge, corner) + const bc_array_t m_bc_types; + + //! only used when doing analytical border conditions (only really useful for test and debug) + const AnalyticalBC & m_f; + + //! get geometrical scaling factor + const real_t m_scaling_factor; + + //! get domain lower left corner + const Kokkos::Array m_xyz_min; + +}; // class FillOutside2CellFunctor + +// explicit template instantiation +extern template class FillOutside2CellFunctor<2, kalypsso::DefaultDevice>; +extern template class FillOutside2CellFunctor<3, kalypsso::DefaultDevice>; + +// this may not be handy: +// we have to declare here, and instantiate in FillOutside2.cpp all class for a particular desired +// init functor. +extern template class FillOutside2CellFunctor<2, kalypsso::DefaultDevice, InitFunc1>; +extern template class FillOutside2CellFunctor<3, kalypsso::DefaultDevice, InitFunc1>; +extern template class FillOutside2CellFunctor<2, kalypsso::DefaultDevice, InitFunc2>; +extern template class FillOutside2CellFunctor<3, kalypsso::DefaultDevice, InitFunc2>; + +} // namespace test + +} // namespace kalypsso + +#endif // KALYPSSO_TEST_FILLOUTSIDE2_H_ diff --git a/test/test_common/InitialAMRSetup.cpp b/test/test_common/InitialAMRSetup.cpp new file mode 100644 index 0000000..13988b5 --- /dev/null +++ b/test/test_common/InitialAMRSetup.cpp @@ -0,0 +1,1059 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file InitialAMRSetup.cpp + * + * this is not a test, but we define a base class for multiple tests + */ +#include "InitialAMRSetup.h" + +namespace kalypsso +{ + +int InitialAMRSetupBase::refine_level = 0; + +// ============================================================================= +// ============================================================================= +template +InitialAMRSetup::InitialAMRSetup(const ParallelEnv & par_env, + const ConfigMap & config_map, + const Function f) + : m_par_env(par_env) + , m_config_map(config_map) + , m_amr_mesh(new AMRmesh(par_env, config_map)) + , m_mesh_map(new MeshMap(config_map, par_env)) + , m_f(f) +{ + + if (InitialAMRSetupBase::refine_level == 0) + { + if (m_par_env.rank() == 0) + { + std::cout << "WARNING: InitialAMRSetupBase::refine was not set in main. Using default " + "value instead (i.e. 6)\n"; + } + InitialAMRSetupBase::refine_level = 6; + } + + auto conn_name = m_config_map.getString("amr", "connectivity", "invalid_connectivity"); + if (conn_name == "brick") + { + m_brick_sizes = get_brick_sizes(m_config_map); + + m_is_brick_periodic[IX] = static_cast( + m_config_map.getInteger("p4est_connectivity", "periodic_x", CONNECTIVITY_PERIODIC_FALSE)); + m_is_brick_periodic[IY] = static_cast( + m_config_map.getInteger("p4est_connectivity", "periodic_y", CONNECTIVITY_PERIODIC_FALSE)); + if constexpr (dim == 3) + m_is_brick_periodic[IZ] = static_cast( + m_config_map.getInteger("p4est_connectivity", "periodic_z", CONNECTIVITY_PERIODIC_FALSE)); + + // get block sizes + const auto bx = m_config_map.getInteger("amr", "bx", 1); + const auto by = m_config_map.getInteger("amr", "by", 1); + const auto bz = m_config_map.getInteger("amr", "bz", 1); + + m_block_sizes[IX] = bx; + m_block_sizes[IY] = by; + if constexpr (dim == 3) + m_block_sizes[IZ] = bz; + + // get ghost block sizes + const auto gx = m_config_map.getInteger("amr", "gx", 2); + const auto gy = m_config_map.getInteger("amr", "gy", 2); + const auto gz = m_config_map.getInteger("amr", "gz", 2); + + m_ghost_sizes[IX] = gx; + m_ghost_sizes[IY] = gy; + if constexpr (dim == 3) + m_ghost_sizes[IZ] = gz; + ; + } +} // InitialAMRSetup + +// ============================================================================= +// ============================================================================= +template +void +InitialAMRSetup::setup_initial_mesh(bool norefine) +{ + + auto forest = m_amr_mesh->forest(); + auto geom = m_amr_mesh->geometry(); + + auto conn_name = m_config_map.getString("amr", "connectivity", "invalid_connectivity"); + assertm(conn_name == "brick", "This test requires p4est connectivity to be \"brick\" !"); + + auto refine_type = m_config_map.getString("amr", "refine_type", "normal"); + + [[maybe_unused]] auto geom_name = geom == nullptr ? "no_geometry" : geom->name; + + KALYPSSO_INFO( + "Running a {}D test with connectivity {} and geometry {}", dim, conn_name.c_str(), geom_name); + + // + // apply some simple initial refinement + // + { + Kokkos::Profiling::ScopedRegion prof("initial_AMR_p4est"); + if (norefine) + p4est::Wrapper::refine(forest, 1, norefine_fn, nullptr); + else + { + if (refine_type == "normal") + p4est::Wrapper::refine(forest, 1, refine_normal_fn, nullptr); + else if (refine_type == "simple") + p4est::Wrapper::refine(forest, 1, refine_simple_fn, nullptr); + } + p4est::Wrapper::coarsen(forest, 1, coarsen_normal_fn, nullptr); + p4est::Wrapper::balance(forest, p4est::Wrapper::CONNECT_FULL, nullptr); + } + + // init mesh ghost + m_amr_mesh->reset_ghost(); + auto ghost = m_amr_mesh->ghost(); + + KALYPSSO_INFO_ALL("before our own AMR cycle, local_num_quadrants = {}, local_num_ghosts = {}", + m_amr_mesh->local_num_quadrants(), + m_amr_mesh->local_num_ghosts()); + + // update number of outside quad + m_mesh_map->compute_outside_quad_info(forest, ghost); + + // update MeshMap orchard keys array + m_mesh_map->update_orchard_keys(forest, ghost); + + // fill device hash map + m_mesh_map->update_hashmap(forest, ghost); + + // compute number of quadrants per types (owned, ghost, outside, outside_ghost) + m_mesh_map->update_amr_mesh_info(forest, ghost); + + KALYPSSO_INFO_ALL("Forest has {} global octants, {} local octants.", + m_mesh_map->get_amr_mesh_info().global_num_quadrants(), + m_mesh_map->get_amr_mesh_info().local_num_quadrants()); + +} // setup_initial_mesh + +// ============================================================================= +// ============================================================================= +template +auto +InitialAMRSetup::setup_initial_data_leaf( + typename MeshMap::orchard_key_view_t orchard_keys_device) -> DataArrayLeaf_t +{ + + // + // create some data + // + + // now generate some user data (block based AMR) + HydroParams params = HydroParams(m_config_map); + + const int nbvar = nbvar_hydro(); + + // get domain lower left corner + const auto xyz_min = get_xyz_min(m_config_map); + + // get geometrical scaling factor + const auto scaling_factor = get_scaling_factor(m_config_map); + + // + // create some test user data (on leaf, not cells for simplicity), and upload to device + // + DataArrayLeaf_t userdataLeaf = DataArrayLeaf_t( + "test_data", + static_cast(m_mesh_map->get_amr_mesh_info().local_num_quadrants_total()), + static_cast(nbvar)); + + { + Kokkos::parallel_for( + "Fill_test_data", + Kokkos::RangePolicy(0, m_mesh_map->get_amr_mesh_info().local_num_quadrants()), + KOKKOS_LAMBDA(const int i) { + constexpr bool use_center = false; + const auto key = orchard_keys_device(i); + const auto xyz_vertex = orchard_key_to_vertex_coord(key, use_center); + const auto xyz = vertex_coord_to_real_space(xyz_vertex, scaling_factor, xyz_min); + userdataLeaf(i, 0) = xyz[IX] + xyz[IY]; + userdataLeaf(i, 1) = xyz[IX]; + userdataLeaf(i, 2) = xyz[IY]; + userdataLeaf(i, 3) = KALYPSSO_NUM(1.0) * static_cast(i); + }); + } + + return userdataLeaf; + +} // setup_initial_data_leaf + +// ============================================================================= +// ============================================================================= +template +auto +InitialAMRSetup::setup_initial_data_block_new( + typename MeshMap::orchard_key_view_t orchard_keys_device) -> DataArrayBlock_t +{ + // + // create some data + // + + // now generate some user data (block based AMR) + HydroParams params = HydroParams(m_config_map); + + const int nbvar = nbvar_hydro(); + + // get block sizes + const auto bx = m_config_map.getInteger("amr", "bx", 1); + const auto by = m_config_map.getInteger("amr", "by", 1); + const auto bz = m_config_map.getInteger("amr", "bz", 1); + + const auto bSizes = [=]() { + if constexpr (dim == 2) + return block_size_t<2>{ bx, by }; + if constexpr (dim == 3) + return block_size_t<3>{ bx, by, bz }; + }(); + + + // get domain lower left corner + const auto xyz_min = get_xyz_min(m_config_map); + + // get geometrical scaling factor + const auto scaling_factor = get_scaling_factor(m_config_map); + + // + // create some test user data (on cells), and upload to device + // + auto userdataBlock = + DataArrayBlock_t("test_data_block", + bSizes, // dim == 2 ? bx * by : bx * by * bz, + nbvar, + m_mesh_map->get_amr_mesh_info().local_num_quadrants_total()); + + { + // number of quadrants in current MPI process + const auto nbOcts = m_mesh_map->get_amr_mesh_info().local_num_quadrants(); + + // Kokkos team policy type alias + using team_policy_t = Kokkos::TeamPolicy>; + + team_policy_t policy(nbOcts, + Kokkos::AUTO() /* number of threads per team is chosen by kokkos */); + using thread_t = typename team_policy_t::member_type; + + auto block_sizes = m_block_sizes; + auto f = m_f; + + Kokkos::parallel_for( + "Fill_test_data_block", policy, KOKKOS_LAMBDA(const thread_t & member) { + // block sizes (bx,by,bz) are captured by the lambda + + // get lower left corner real space coordinates + constexpr bool use_center = false; + + // number of cells per octant + const auto nbCells = dim == 2 ? bx * by : bx * by * bz; + + // the first octant to process is indexed by the team id + int32_t iOct = member.league_rank(); + + // get orchard key of current block/octant + const auto key = orchard_keys_device(iOct); + + // get octant level + const auto level = orchard_key_t::level(key); + + // compute cell length in real space + // const double dx_cell = compute_cell_length(level, bx); + + // get real space coordinates of lower left corner of the block + auto xyz_corner_vertex = orchard_key_to_vertex_coord(key, use_center); + + // the only reason of the following dummy code to be here, is that cuda nvcc compile + // doesn't support capturing variables inside the inner lambda inside a constexpr if + // + // strangely, nvc++ is ok and don't need that + // TODO: remove theses lines when nvcc will have support for this. +#ifdef __NVCC__ + [[maybe_unused]] int dummy = 0; + if (f(0, 0, 0) != f(0, 0, 0) or userdataBlock(0, 0, 0) != userdataBlock(0, 0, 0)) + dummy++; +#endif + + // initialize cell id + Kokkos::parallel_for(Kokkos::TeamVectorRange(member, nbCells), [=](const int32_t icell) { + // compute ix,iy,iz of local cell inside + // block from index + auto iCoord = icell_to_icoord(icell, bx); + const auto xyz_cell_vertex = + compute_cell_coordinates(level, xyz_corner_vertex, iCoord, block_sizes); + const auto xyz_cell = + vertex_coord_to_real_space(xyz_cell_vertex, scaling_factor, xyz_min); + for (int ivar = 0; ivar < nbvar; ++ivar) + { + if constexpr (dim == 2) + userdataBlock(icell, ivar, iOct) = f(xyz_cell[IX], xyz_cell[IY], ivar); + if constexpr (dim == 3) + userdataBlock(icell, ivar, iOct) = f(xyz_cell[IX], xyz_cell[IY], xyz_cell[IZ], ivar); + } + }); // end TeamVectorRange + }); + } + + return userdataBlock; +} // setup_initial_data_block_new + +// ============================================================================= +// ============================================================================= +template +auto +InitialAMRSetup::setup_initial_data_block_flux( + typename MeshMap::orchard_key_view_t orchard_keys_device, + int direction) -> DataArrayBlock_t +{ + // + // create some data + // + + // now generate some user data (block based AMR) + HydroParams params = HydroParams(m_config_map); + + const int nbvar = nbvar_hydro(); + + // get block sizes + const auto bx = m_config_map.getInteger("amr", "bx", 1); + const auto by = m_config_map.getInteger("amr", "by", 1); + const auto bz = m_config_map.getInteger("amr", "bz", 1); + + const auto bSizes = [=]() { + if constexpr (dim == 2) + return block_size_t<2>{ bx, by }; + if constexpr (dim == 3) + return block_size_t<3>{ bx, by, bz }; + }(); + + // flux size + auto fSizes = bSizes; + fSizes[direction]++; + + // get domain lower left corner + const auto xyz_min = get_xyz_min(m_config_map); + + // get geometrical scaling factor + const auto scaling_factor = get_scaling_factor(m_config_map); + + // + // create some test user data (flux), and upload to device + // + auto userdata_flux = DataArrayBlock_t( + "test_data_block", fSizes, nbvar, m_mesh_map->get_amr_mesh_info().local_num_quadrants_total()); + + { + // number of quadrants in current MPI process + const auto nbOcts = m_mesh_map->get_amr_mesh_info().local_num_quadrants(); + + const auto nbIters = nbOcts * userdata_flux.num_cells(); + + // auto block_sizes = m_block_sizes; + auto f = m_f; + + Kokkos::parallel_for( + "Fill_test_data_block", + Kokkos::RangePolicy(0, nbIters), + KOKKOS_LAMBDA(const int32_t & global_index) { + auto const iOct = global_index / userdata_flux.num_cells(); + auto const flux_index = global_index - iOct * userdata_flux.num_cells(); + + // the only reason of the following dummy code to be here, is that cuda nvcc compile + // doesn't support capturing variables inside the inner lambda inside a constexpr if + // + // strangely, nvc++ is ok and don't need that + // TODO: remove theses lines when nvcc will have support for this. +#ifdef __NVCC__ + [[maybe_unused]] int dummy = 0; + if (f(0, 0, 0) == f(0, 0, 0)) + dummy++; +#endif + + // get orchard key of current block/octant + const auto key = orchard_keys_device(iOct); + + // compute ix,iy,iz of local cell inside + // block from index + const auto ijk = cell_index_unravel(flux_index, fSizes); + const auto face_multiindex = to_face_multiindex(ijk, direction); + + const auto xyz_face = orchard_key_to_facecenter_real_space( + key, face_multiindex, bSizes, scaling_factor, xyz_min); + + for (int ivar = 0; ivar < nbvar; ++ivar) + { + if constexpr (dim == 2) + { + userdata_flux(flux_index, ivar, iOct) = f(xyz_face[IX], xyz_face[IY], ivar); + } + else if constexpr (dim == 3) + { + userdata_flux(flux_index, ivar, iOct) = + f(xyz_face[IX], xyz_face[IY], xyz_face[IZ], ivar); + } + } + }); + } + + return userdata_flux; + +} // setup_initial_data_block_flux + +// ============================================================================= +// ============================================================================= +template +auto +InitialAMRSetup::setup_initial_data_block_face( + typename MeshMap::orchard_key_view_t orchard_keys_device, + bool use_face_averaged_values) -> FaceDataArrayBlock_t +{ + // + // create some data + // + + // now generate some user data (block based AMR) + HydroParams params = HydroParams(m_config_map); + + // get domain lower left corner + const auto xyz_min = get_xyz_min(m_config_map); + + // get geometrical scaling factor + const auto scaling_factor = get_scaling_factor(m_config_map); + + const auto num_octants = m_mesh_map->get_amr_mesh_info().local_num_quadrants_total(); + + // + // create some test user data (on cells), and upload to device + // + FaceDataArrayBlock_t userdataBlock = + FaceDataArrayBlock_t("test_data_block", m_block_sizes, num_octants); + + const auto nbFacesPerLeaf = userdataBlock.num_elements_per_octant(); + + const auto total_num_faces = num_octants * nbFacesPerLeaf; + + { + // number of quadrants in current MPI process + const auto block_sizes = m_block_sizes; + auto f = m_f; + + Kokkos::parallel_for( + "Fill_test_data_block", + Kokkos::RangePolicy(0, total_num_faces), + KOKKOS_LAMBDA(const int32_t & global_index) { + // block sizes (bx,by,bz) are captured by the lambda + + const auto iOct = global_index / nbFacesPerLeaf; + const auto face_flat_index = global_index - iOct * nbFacesPerLeaf; + + const auto face_indexes = face_flat_index_unravel( + face_flat_index, block_sizes, userdataBlock.offsets(), userdataBlock.shift()); + + // get orchard key of current block/octant + const auto key = orchard_keys_device(iOct); + + auto const & bSize = block_sizes[IX]; + + // compute physical x,y,z for that face center + const auto xyz = orchard_key_to_facecenter_real_space( + key, face_indexes, bSize, scaling_factor, xyz_min); + + const auto level = orchard_key_t::level(key); + + // compute cell size + const auto dx = compute_cell_length(level, block_sizes[IX]) * scaling_factor; + + [[maybe_unused]] const auto use_face_averaged_values_d = use_face_averaged_values; + + // the only reason of the following dummy code to be here, is that cuda nvcc compile + // doesn't support capturing variables inside the inner lambda inside a constexpr if + // + // strangely, nvc++ is ok and don't need that + // TODO: remove theses lines when nvcc will have support for this. +#ifdef __NVCC__ + [[maybe_unused]] int dummy = 0; + if (f(0, 0, 0) != f(0, 0, 0)) + dummy++; +#endif + + if constexpr (dim == 2) + { + + auto const & i = face_indexes[IX]; + auto const & j = face_indexes[IY]; + auto const & ivar = face_indexes[dim]; + + if constexpr (f.has_face_averaged_values) + { + if (use_face_averaged_values_d) + userdataBlock(i, j, ivar, iOct) = f.faverage(xyz[IX], xyz[IY], dx, dx, ivar); + else + userdataBlock(i, j, ivar, iOct) = f(xyz[IX], xyz[IY], ivar); + } + else + { + userdataBlock(i, j, ivar, iOct) = f(xyz[IX], xyz[IY], ivar); + } + } + else if constexpr (dim == 3) + { + auto const & i = face_indexes[IX]; + auto const & j = face_indexes[IY]; + auto const & k = face_indexes[IZ]; + auto const & ivar = face_indexes[dim]; + + userdataBlock(i, j, k, ivar, iOct) = f(xyz[IX], xyz[IY], xyz[IZ], ivar); + } + }); + } + + return userdataBlock; +} // setup_initial_data_block_face + +// ============================================================================= +// ============================================================================= +template +auto +InitialAMRSetup::setup_initial_data_ghosted_block( + typename MeshMap::orchard_key_view_t orchard_keys_device, + bool fill_ghosts) -> DataArrayGhostedBlock_t +{ + // + // create some data + // + + // now generate some user data (block based AMR) + HydroParams params = HydroParams(m_config_map); + + const int nbvar = nbvar_hydro(); + + // + // create some test user data (on cells, taking into account ghost cell, set to zero), and + // upload to device + // + auto ghosted_block_sizes = m_block_sizes; + ghosted_block_sizes[IX] += 2 * m_ghost_sizes[IX]; + ghosted_block_sizes[IY] += 2 * m_ghost_sizes[IY]; + if constexpr (dim == 3) + { + ghosted_block_sizes[IZ] += 2 * m_ghost_sizes[IZ]; + } + + shift_t shift; + shift[IX] = -m_ghost_sizes[IX]; + shift[IY] = -m_ghost_sizes[IY]; + if constexpr (dim == 3) + { + shift[IZ] = -m_ghost_sizes[IZ]; + } + + auto userdata_ghosted_block = + DataArrayGhostedBlock_t(m_block_sizes, + ghosted_block_sizes, + shift, + "test_data_ghosted_block", + nbvar, + m_mesh_map->get_amr_mesh_info().local_num_quadrants_total()); + + auto block_sizes = m_block_sizes; + auto brick_sizes = m_brick_sizes; + + auto ghosted_block_size = userdata_ghosted_block.ghosted_block_size(); + + // get domain lower left corner + const auto xyz_min = get_xyz_min(m_config_map); + + // get geometrical scaling factor + const auto scaling_factor = get_scaling_factor(m_config_map); + + { + // number of quadrants in current MPI process + const auto nbOcts = m_amr_mesh->local_num_quadrants_total(); + const auto num_vars = nbvar; + + const auto tx = ghosted_block_size[IX]; + const auto ty = ghosted_block_size[IY]; + const auto tz = [=]() { + if constexpr (dim == 2) + return 1; + if constexpr (dim == 3) + return ghosted_block_size[IZ]; + }(); + + const int32_t bx = m_block_sizes[IX]; + const int32_t by = m_block_sizes[IY]; + const int32_t bz = [=]() { + if constexpr (dim == 3) + return m_block_sizes[IZ]; + else + return 1; + }(); + + const int32_t gx = m_ghost_sizes[IX]; + const int32_t gy = m_ghost_sizes[IY]; + const int32_t gz = [=]() { + if constexpr (dim == 3) + return m_ghost_sizes[IZ]; + else + return 1; + }(); + + // Kokkos team policy type alias + using team_policy_t = Kokkos::TeamPolicy>; + + team_policy_t policy(nbOcts * num_vars, + Kokkos::AUTO() /* number of threads per team is chosen by kokkos */); + using thread_t = typename team_policy_t::member_type; + + auto f = m_f; + + const auto first_outside_quad_local_id = + m_mesh_map->get_amr_mesh_info().first_outside_quad_local_id(); + + const auto is_brick_periodic = m_is_brick_periodic; + + Kokkos::parallel_for( + "Fill_test_data_block", policy, KOKKOS_LAMBDA(const thread_t & member) { + // block sizes (bx,by,bz) are captured by the lambda + + // get lower left corner real space coordinates + constexpr bool use_center = false; + + // number of cells per octant (ghost included) + // const uint32_t nbCells = dim == 2 ? tx * ty : tx * ty * tz; + + // get (octant id, ivar) from the team id + // using left indexing, we get + // tmp = ivar + iOct * num_vars + const auto tmp = member.league_rank(); + const auto iOct = tmp / num_vars; + const auto ivar = tmp - iOct * num_vars; + + // get orchard key of current block/octant + const auto key = orchard_keys_device(iOct); + + // get octant level + const auto level = orchard_key_t::level(key); + + // compute cell length in real space + // const double dx_cell = compute_cell_length(level, bx); + + // get real space coordinates of lower left corner of the block + const auto xyz_corner = (iOct >= first_outside_quad_local_id) + ? outside_key_to_vertex_coord(key, use_center, brick_sizes) + : orchard_key_to_vertex_coord(key, use_center); + + // the only reason of the following dummy code to be here, is that cuda nvcc compile + // doesn't support capturing variables inside the inner lambda inside a constexpr if + // + // strangely, nvc++ is ok and don't need that + // TODO: remove theses lines when nvcc will have support for this. +#ifdef __NVCC__ + [[maybe_unused]] int dummy = 0; + if (tx == 0 or ty == 0 or tz == 0 or gx == 0 or gy == 0 or gz == 0 or bx == 0 or by == 0 or + bz == 0 or userdata_ghosted_block.num_cells() == 0 or block_sizes[IX] == 0 or + fill_ghosts != fill_ghosts or f(0, 0, 0) != f(0, 0, 0) or brick_sizes[IX] == 0 or + is_brick_periodic[IX] == 0 or scaling_factor != scaling_factor or + xyz_min[0] != xyz_min[0]) + dummy++; +#endif + + // clang-format off + if constexpr (dim == 2) + { + Kokkos::parallel_for( + Kokkos::TeamVectorMDRange< + Kokkos::Rank<2, Kokkos::Iterate::Left, Kokkos::Iterate::Left>, + thread_t>(member, tx, ty), + [=](const int32_t i, const int32_t j) { + Kokkos::Array iCoord{ i - gx, j - gy }; + auto xyz_cell_vertex = compute_cell_coordinates( + level, xyz_corner, iCoord, block_sizes); + + // take into account mesh periodicity + if (is_brick_periodic[IX]) + { + if (xyz_cell_vertex[IX] < 0) + xyz_cell_vertex[IX] += static_cast(brick_sizes[IX]); + if (xyz_cell_vertex[IX] > brick_sizes[IX]) + xyz_cell_vertex[IX] -= static_cast(brick_sizes[IX]); + } + if (is_brick_periodic[IY]) + { + if (xyz_cell_vertex[IY] < 0) + xyz_cell_vertex[IY] += static_cast(brick_sizes[IY]); + if (xyz_cell_vertex[IY] > brick_sizes[IY]) + xyz_cell_vertex[IY] += static_cast(-brick_sizes[IY]); + } + const auto xyz_cell = vertex_coord_to_real_space(xyz_cell_vertex, scaling_factor, xyz_min); + + if (i >= gx and i < gx + bx and j >= gy and j < gy + by) + { + userdata_ghosted_block(i-gx, j-gy, ivar, iOct) = + f(xyz_cell[IX], xyz_cell[IY], ivar); + } + else + { + userdata_ghosted_block(i-gx, j-gy, ivar, iOct) = + fill_ghosts ? f(xyz_cell[IX], xyz_cell[IY], ivar) : 0; + } + + }); // end TeamVectorRange + } + // clang-format on + + else if constexpr (dim == 3) + { + Kokkos::parallel_for( + Kokkos::TeamVectorMDRange, + thread_t>(member, tx, ty, tz), + [=](const int32_t i, const int32_t j, const int32_t k) { + Kokkos::Array iCoord{ i - gx, j - gy, k - gz }; + auto xyz_cell_vertex = + compute_cell_coordinates(level, xyz_corner, iCoord, block_sizes); + + // take into account mesh periodicity + if (is_brick_periodic[IX]) + { + if (xyz_cell_vertex[IX] < 0) + xyz_cell_vertex[IX] += static_cast(brick_sizes[IX]); + if (xyz_cell_vertex[IX] > brick_sizes[IX]) + xyz_cell_vertex[IX] -= static_cast(brick_sizes[IX]); + } + if (is_brick_periodic[IY]) + { + if (xyz_cell_vertex[IY] < 0) + xyz_cell_vertex[IY] += static_cast(brick_sizes[IY]); + if (xyz_cell_vertex[IY] > brick_sizes[IY]) + xyz_cell_vertex[IY] += static_cast(-brick_sizes[IY]); + } + if (is_brick_periodic[IZ]) + { + if (xyz_cell_vertex[IZ] < 0) + xyz_cell_vertex[IZ] += static_cast(brick_sizes[IZ]); + if (xyz_cell_vertex[IZ] > brick_sizes[IZ]) + xyz_cell_vertex[IZ] += static_cast(-brick_sizes[IZ]); + } + + const auto xyz_cell = + vertex_coord_to_real_space(xyz_cell_vertex, scaling_factor, xyz_min); + + if (i >= gx and i < gx + bx and j >= gy and j < gy + by and k >= gz and k < gz + bz) + { + userdata_ghosted_block(i - gx, j - gy, k - gz, ivar, iOct) = + f(xyz_cell[IX], xyz_cell[IY], xyz_cell[IZ], ivar); + } + else + { + userdata_ghosted_block(i - gx, j - gy, k - gz, ivar, iOct) = + fill_ghosts ? f(xyz_cell[IX], xyz_cell[IY], xyz_cell[IZ], ivar) : 0; + } + }); // end TeamVectorRange + } + }); + } + + return userdata_ghosted_block; + +} // setup_initial_data_ghosted_block + +// ============================================================================= +// ============================================================================= +template +auto +InitialAMRSetup::compute_diff_ghosted_block( + typename MeshMap::orchard_key_view_t orchard_keys_device, + DataArrayGhostedBlock_t data1, + DataArrayGhostedBlock_t data2) -> DataArrayGhostedBlock_t +{ + // KOKKOS_ASSERT(data1.num_vars == data2.num_vars); + + using DirectAccess = typename DataArrayGhostedBlock_t::DirectAccess; + + const auto block_sizes = m_block_sizes; + const auto brick_sizes = m_brick_sizes; + + const auto num_vars = data1.num_vars(); + const auto num_octs = data1.num_quadrants(); + + const auto total_block_size = data1.ghosted_block_size(); + + const auto tx = total_block_size[IX]; + const auto ty = total_block_size[IY]; + const auto tz = [=]() { + if constexpr (dim == 2) + return 1; + if constexpr (dim == 3) + return total_block_size[IZ]; + }(); + + // shift + const int32_t sx = data1.shift()[IX]; + const int32_t sy = data1.shift()[IY]; + [[maybe_unused]] const int32_t sz = [=]() { + if constexpr (dim == 3) + return data1.shift()[IZ]; + else + return 0; + }(); + + // get domain lower left corner + const auto xyz_min = get_xyz_min(m_config_map); + + // get geometrical scaling factor + const auto scaling_factor = get_scaling_factor(m_config_map); + + const auto first_outside_quad_local_id = + m_mesh_map->get_amr_mesh_info().first_outside_quad_local_id(); + + const auto is_brick_periodic = m_is_brick_periodic; + + auto f = m_f; + + DataArrayGhostedBlock_t data_diff = DataArrayGhostedBlock_t(data1.block_size(), + data1.ghosted_block_size(), + data1.shift(), + "userdata_ghosted_block_diff", + num_vars, + num_octs); + + constexpr real_t tol = KALYPSSO_NUM(1e-7); + + // Kokkos team policy type alias + using team_policy_t = Kokkos::TeamPolicy>; + + team_policy_t policy(num_octs * num_vars, + Kokkos::AUTO() /* number of threads per team is chosen by kokkos */); + using thread_t = typename team_policy_t::member_type; + + Kokkos::parallel_for( + "compare data", policy, KOKKOS_LAMBDA(const thread_t & member) { + // get lower left corner real space coordinates + constexpr bool use_center = false; + + const auto tmp = member.league_rank(); + const auto iOct = tmp / num_vars; + const auto ivar = tmp - iOct * num_vars; + + // get orchard key of current block/octant + const auto key = orchard_keys_device(iOct); + + // get octant level + const auto level = orchard_key_t::level(key); + + // get real space coordinates of lower left corner of the block + const auto xyz_corner = (iOct >= first_outside_quad_local_id) + ? outside_key_to_vertex_coord(key, use_center, brick_sizes) + : orchard_key_to_vertex_coord(key, use_center); + + // get the cell length + real_t dx = compute_cell_length(level, block_sizes[IX]) * scaling_factor; + real_t dy = compute_cell_length(level, block_sizes[IY]) * scaling_factor; + + // the only reason of the following dummy code to be here, is that cuda nvcc compile doesn't + // support capturing variables inside the inner lambda inside a constexpr if + // + // strangely, nvc++ is ok and don't need that + // TODO: remove theses lines when nvcc will have support for this. +#ifdef __NVCC__ + [[maybe_unused]] int dummy = 0; + if (tx == 0 or ty == 0 or tz == 0 or sx == 0 or sy == 0 or sz == 0 or + data1.num_cells() == 0 or data2.num_cells() == 0 or data_diff.num_cells() == 0 or + block_sizes[IX] == 0 or brick_sizes[IX] == 0 or tol < 0 or f(0, 0, 0) != f(0, 0, 0) or + is_brick_periodic[IX] == 0 or scaling_factor != scaling_factor or + xyz_min[0] != xyz_min[0]) + dummy++; +#endif + if constexpr (dim == 2) + { + Kokkos::parallel_for( + Kokkos::TeamVectorMDRange, + thread_t>(member, tx, ty), + [=](const int32_t i, const int32_t j) { + data_diff(i, j, ivar, iOct, DirectAccess{}) = + data1(i, j, ivar, iOct, DirectAccess{}) - data2(i, j, ivar, iOct, DirectAccess{}); + + // try to evaluate if we can account the difference by the + // coarse-graining that appear in the ghost layer surrounding a + // small with coarser block neighbor if the difference is still + // large, it means we have a bug + + if (fabs(data_diff(i, j, ivar, iOct, DirectAccess{})) > tol) + { + Kokkos::Array iCoord{ i + sx, j + sy }; + auto xyz_cell_vertex = + compute_cell_coordinates(level, xyz_corner, iCoord, block_sizes); + + if (is_brick_periodic[IX]) + { + if (xyz_cell_vertex[IX] < 0) + xyz_cell_vertex[IX] += static_cast(brick_sizes[IX]); + if (xyz_cell_vertex[IX] > brick_sizes[IX]) + xyz_cell_vertex[IX] -= static_cast(brick_sizes[IX]); + } + if (is_brick_periodic[IY]) + { + if (xyz_cell_vertex[IY] < 0) + xyz_cell_vertex[IY] += static_cast(brick_sizes[IY]); + if (xyz_cell_vertex[IY] > brick_sizes[IY]) + xyz_cell_vertex[IY] += static_cast(-brick_sizes[IY]); + } + + auto & xx = xyz_cell_vertex[IX]; + if ((-sx) % 2 == 0) + { + xx = (i % 2 == 0) ? xx + dx / 2 : xx - dx / 2; + } + else + { + xx = (i % 2 == 0) ? xx - dx / 2 : xx + dx / 2; + } + + auto & yy = xyz_cell_vertex[IY]; + if ((-sy) % 2 == 0) + { + yy = (j % 2 == 0) ? yy + dy / 2 : yy - dy / 2; + } + else + { + yy = (j % 2 == 0) ? yy - dy / 2 : yy + dy / 2; + } + + const auto xyz_cell = + vertex_coord_to_real_space(xyz_cell_vertex, scaling_factor, xyz_min); + + data_diff(i, j, ivar, iOct, DirectAccess{}) = + data1(i, j, ivar, iOct, DirectAccess{}) - f(xyz_cell[IX], xyz_cell[IY], ivar); + } + }); + } + else if constexpr (dim == 3) + { + real_t dz = compute_cell_length(level, block_sizes[IZ]) * scaling_factor; + + Kokkos::parallel_for( + Kokkos::TeamVectorMDRange, + thread_t>(member, tx, ty, tz), + [=](const int32_t i, const int32_t j, const int32_t k) { + data_diff(i, j, k, ivar, iOct, DirectAccess{}) = + data1(i, j, k, ivar, iOct, DirectAccess{}) - + data2(i, j, k, ivar, iOct, DirectAccess{}); + // try to evaluate if we can account the difference by the + // coarse-graining that appear in the ghost layer surrounding a + // small with coarser block neighbor if the difference is still + // large, it means we have a bug + + if (fabs(data_diff(i, j, k, ivar, iOct, DirectAccess{})) > tol) + { + Kokkos::Array iCoord{ i + sx, j + sy, k + sz }; + auto xyz_cell_vertex = + compute_cell_coordinates(level, xyz_corner, iCoord, block_sizes); + + if (is_brick_periodic[IX]) + { + if (xyz_cell_vertex[IX] < 0) + xyz_cell_vertex[IX] += static_cast(brick_sizes[IX]); + if (xyz_cell_vertex[IX] > brick_sizes[IX]) + xyz_cell_vertex[IX] -= static_cast(brick_sizes[IX]); + } + + if (is_brick_periodic[IY]) + { + if (xyz_cell_vertex[IY] < 0) + xyz_cell_vertex[IY] += static_cast(brick_sizes[IY]); + if (xyz_cell_vertex[IY] > brick_sizes[IY]) + xyz_cell_vertex[IY] += static_cast(-brick_sizes[IY]); + } + + if (is_brick_periodic[IZ]) + { + if (xyz_cell_vertex[IZ] < 0) + xyz_cell_vertex[IZ] += static_cast(brick_sizes[IZ]); + if (xyz_cell_vertex[IZ] > brick_sizes[IZ]) + xyz_cell_vertex[IZ] += static_cast(-brick_sizes[IZ]); + } + + auto & xx = xyz_cell_vertex[IX]; + if ((-sx) % 2 == 0) + { + xx = (i % 2 == 0) ? xx + dx / 2 : xx - dx / 2; + } + else + { + xx = (i % 2 == 0) ? xx - dx / 2 : xx + dx / 2; + } + + auto & yy = xyz_cell_vertex[IY]; + if ((-sy) % 2 == 0) + { + yy = (j % 2 == 0) ? yy + dy / 2 : yy - dy / 2; + } + else + { + yy = (j % 2 == 0) ? yy - dy / 2 : yy + dy / 2; + } + + auto & zz = xyz_cell_vertex[IZ]; + if ((-sz) % 2 == 0) + { + zz = (k % 2 == 0) ? zz + dz / 2 : zz - dz / 2; + } + else + { + zz = (k % 2 == 0) ? zz - dz / 2 : zz + dz / 2; + } + + const auto xyz_cell = + vertex_coord_to_real_space(xyz_cell_vertex, scaling_factor, xyz_min); + + data_diff(i, j, k, ivar, iOct, DirectAccess{}) = + data1(i, j, k, ivar, iOct, DirectAccess{}) - + f(xyz_cell[IX], xyz_cell[IY], xyz_cell[IZ], ivar); + } + }); + } + }); + + return data_diff; + +} // compute_diff_ghosted_block + +template class InitialAMRSetup<2, kalypsso::DefaultDevice, InitGaussian>; +template class InitialAMRSetup<3, kalypsso::DefaultDevice, InitGaussian>; + +template class InitialAMRSetup<2, kalypsso::DefaultDevice, InitHat>; +template class InitialAMRSetup<3, kalypsso::DefaultDevice, InitHat>; + +template class InitialAMRSetup<2, kalypsso::DefaultDevice, InitFuncParabola>; +template class InitialAMRSetup<3, kalypsso::DefaultDevice, InitFuncParabola>; + +template class InitialAMRSetup<2, kalypsso::DefaultDevice, InitFuncSineWave>; +template class InitialAMRSetup<3, kalypsso::DefaultDevice, InitFuncSineWave>; + +template class InitialAMRSetup<2, kalypsso::DefaultDevice, InitFunc1>; +template class InitialAMRSetup<3, kalypsso::DefaultDevice, InitFunc1>; + +template class InitialAMRSetup<2, kalypsso::DefaultDevice, InitFunc2>; +template class InitialAMRSetup<3, kalypsso::DefaultDevice, InitFunc2>; + +template class InitialAMRSetup<2, kalypsso::DefaultDevice, InitFunc3>; +template class InitialAMRSetup<3, kalypsso::DefaultDevice, InitFunc3>; + +// only instantiate those class when the default device is not on host +#if defined(KOKKOS_ENABLE_DEFAULT_DEVICE_TYPE_OPENMP) || \ + defined(KOKKOS_ENABLE_DEFAULT_DEVICE_TYPE_THREADS) || \ + defined(KOKKOS_ENABLE_DEFAULT_DEVICE_TYPE_SERIAL) +#else +template class InitialAMRSetup<2, kalypsso::HostDevice, InitFuncSineWave>; +template class InitialAMRSetup<3, kalypsso::HostDevice, InitFuncSineWave>; +#endif + +} // namespace kalypsso diff --git a/test/test_common/InitialAMRSetup.h b/test/test_common/InitialAMRSetup.h new file mode 100644 index 0000000..52e0fae --- /dev/null +++ b/test/test_common/InitialAMRSetup.h @@ -0,0 +1,401 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file InitialAMRSetup.h + * + * Define a common base class for doing actual tests. + * + * - predefine a p4est mesh with some initial refined mesh + */ + +#ifndef KALYPSSO_TEST_AMRMESH_INITIALAMRSETUP_H_ +#define KALYPSSO_TEST_AMRMESH_INITIALAMRSETUP_H_ + +#include + +#include // for KALYPSSO_ASSERT +#include +#include +#include +#include +#include +#include +#include +#include +#include // for InitFunc1, InitFunc2, ... +#include + +#include "test_func.h" + +#include +#include +#include +#include + +#include // for uint8_t, uint32_t, etc... +#include + +namespace kalypsso +{ + +// ============================================================================= +// ============================================================================= +struct InitialAMRSetupBase +{ + static int refine_level; +}; + +// ============================================================================= +// ============================================================================= +template +int +refine_normal_fn(typename p4est::Wrapper::forest_t * forest, + typename p4est::topidx_t which_tree, + typename p4est::Wrapper::quadrant_t * quadrant) +{ + using Wrapper = typename p4est::Wrapper; + + using p4est_userdata_t = typename AMRmesh::p4est_userdata_t; + + p4est_userdata_t * p4est_userdata = static_cast(forest->user_pointer); + [[maybe_unused]] const auto level_min = p4est_userdata->level_min; + const auto level_max = p4est_userdata->level_max; + + // when reaching max level, do not refine anymore + if (quadrant->level == level_max) + return 0; + + if constexpr (dim == 3) + { + if (quadrant->level >= 1 && + (Wrapper::quadrant_child_id(quadrant) == 4 or Wrapper::quadrant_child_id(quadrant) == 6)) + { + return 1; + } + } + + if constexpr (dim == 2) + { + if (which_tree == 0 and static_cast(quadrant->level) < 4) + { + double x = static_cast(p4est::get_x(quadrant)) / Wrapper::ROOT_LEN; + double y = static_cast(p4est::get_y(quadrant)) / Wrapper::ROOT_LEN; + if (x > 0.25 and x < 0.35 and y > 0.85 and y < 0.95) + return 1; + } + } + + if (which_tree == 1 and static_cast(quadrant->level) < 4) + { + double x = static_cast(p4est::get_x(quadrant)) / Wrapper::ROOT_LEN; + double y = static_cast(p4est::get_y(quadrant)) / Wrapper::ROOT_LEN; + if (x > 0.45 and x < 0.55 and y > 0.45 and y < 0.55) + return 1; + } + + if (static_cast(quadrant->level) >= + (InitialAMRSetupBase::refine_level - static_cast(which_tree) % 3)) + { + return 0; + } + if (quadrant->level == 1 && + (Wrapper::quadrant_child_id(quadrant) == 3 or Wrapper::quadrant_child_id(quadrant) == 5)) + { + return 1; + } + + if constexpr (dim == 2) + { + if (quadrant->x == P4EST_LAST_OFFSET(2) && quadrant->y == P4EST_LAST_OFFSET(2)) + { + return 1; + } + } + else if constexpr (dim == 3) + { + if (quadrant->x == P4EST_LAST_OFFSET(2) && quadrant->y == P4EST_LAST_OFFSET(2) && + quadrant->z != P4EST_LAST_OFFSET(2)) + { + return 1; + } + } + + if constexpr (dim == 2) + { + if (p4est::get_x(quadrant) >= static_cast(Wrapper::QUADRANT_LEN(2))) + { + return 0; + } + } + else + { + if (p4est::get_z(quadrant) >= static_cast(Wrapper::QUADRANT_LEN(2)) or + p4est::get_y(quadrant) <= static_cast(Wrapper::QUADRANT_LEN(2))) + { + return 0; + } + } + + return 1; + +} // refine_normal_fn + +// ============================================================================= +// ============================================================================= +template +int +refine_simple_fn(typename p4est::Wrapper::forest_t * forest, + typename p4est::topidx_t which_tree, + typename p4est::Wrapper::quadrant_t * quadrant) +{ + using Wrapper = typename p4est::Wrapper; + + using p4est_userdata_t = typename AMRmesh::p4est_userdata_t; + + p4est_userdata_t * p4est_userdata = static_cast(forest->user_pointer); + [[maybe_unused]] const auto level_min = p4est_userdata->level_min; + const auto level_max = p4est_userdata->level_max; + + // when reaching max level, do not refine anymore + if (quadrant->level == level_max) + return 0; + + if (which_tree == 0) + { + if (quadrant->level < 1) + return 1; + + if (quadrant->level == 1) + { + if constexpr (dim == 2) + { + if ((p4est::get_x(quadrant) == Wrapper::ROOT_LEN / 2 and p4est::get_y(quadrant) == 0) or + (p4est::get_y(quadrant) == Wrapper::ROOT_LEN / 2 and p4est::get_x(quadrant) == 0)) + return 1; + } + else if constexpr (dim == 3) + { + if ((p4est::get_x(quadrant) == Wrapper::ROOT_LEN / 2 and p4est::get_y(quadrant) == 0 and + p4est::get_z(quadrant) == 0) or + (p4est::get_y(quadrant) == Wrapper::ROOT_LEN / 2 and p4est::get_x(quadrant) == 0 and + p4est::get_z(quadrant) == 0)) + return 1; + } + } + } + + return 0; + +} // refine_simple_fn + +// ============================================================================= +// ============================================================================= +template +int +norefine_fn([[maybe_unused]] typename p4est::Wrapper::forest_t * forest, + [[maybe_unused]] typename p4est::topidx_t which_tree, + [[maybe_unused]] typename p4est::Wrapper::quadrant_t * quadrant) +{ + return 0; +} // norefine_fn + +// ============================================================================= +// ============================================================================= +template +int +coarsen_normal_fn([[maybe_unused]] typename p4est::Wrapper::forest_t * forest, + [[maybe_unused]] typename p4est::topidx_t which_tree, + [[maybe_unused]] typename p4est::Wrapper::quadrant_t * quadrant[]) +{ + // currently, no coarsening + + return 0; +} // coarsen_normal_fn + + +// ============================================================================= +// ============================================================================= +template +class InitialAMRSetup : public InitialAMRSetupBase +{ +public: + using DataArrayLeaf_t = DataArrayLeaf; + // using DataArrayLeafHost_t = DataArrayLeafHost; + + using DataArrayBlock_t = DataArrayBlock; + // using DataArrayBlockHost_t = DataArrayBlockHost; + + using DataArrayGhostedBlock_t = DataArrayGhostedBlock; + + using FaceDataArrayBlock_t = FaceDataArrayBlock; + + using exec_space = typename device_t::execution_space; + + InitialAMRSetup() = delete; + InitialAMRSetup(const ParallelEnv & par_env, const ConfigMap & config_map, const Function f); + + // ============================================================= + // ============================================================= + void + setup_initial_mesh(bool norefine = false); + + // ============================================================= + // ============================================================= + auto + setup_initial_data_leaf(typename MeshMap::orchard_key_view_t orchard_keys_device) + -> DataArrayLeaf_t; + + // ============================================================= + // ============================================================= + auto + setup_initial_data_block_new( + typename MeshMap::orchard_key_view_t orchard_keys_device) -> DataArrayBlock_t; + + // ============================================================= + // ============================================================= + auto + setup_initial_data_block_flux( + typename MeshMap::orchard_key_view_t orchard_keys_device, + int direction) -> DataArrayBlock_t; + + // ============================================================= + // ============================================================= + auto + setup_initial_data_block_face( + typename MeshMap::orchard_key_view_t orchard_keys_device, + bool use_face_averaged_values) -> FaceDataArrayBlock_t; + + // ============================================================= + // ============================================================= + auto + setup_initial_data_ghosted_block( + typename MeshMap::orchard_key_view_t orchard_keys_device, + bool fill_ghosts) -> DataArrayGhostedBlock_t; + + // ============================================================= + // ============================================================= + auto + compute_diff_ghosted_block( + typename MeshMap::orchard_key_view_t orchard_keys_device, + DataArrayGhostedBlock_t data1, + DataArrayGhostedBlock_t data2) -> DataArrayGhostedBlock_t; + + AMRmesh & + mesh() + { + return *m_amr_mesh; + } + + auto + mesh_map() const + { + return m_mesh_map; + } + + brick_size_t + brick_sizes() const + { + return m_brick_sizes; + } + + // Kokkos::Array + // brick_sizes_uint8() const + // { + // if constexpr (dim == 2) + // return Kokkos::Array{ (uint8_t)m_brick_sizes[0], (uint8_t)m_brick_sizes[1] }; + // else if constexpr (dim == 3) + // return Kokkos::Array{ (uint8_t)m_brick_sizes[0], + // (uint8_t)m_brick_sizes[1], + // (uint8_t)m_brick_sizes[2] }; + // } + + decltype(auto) + is_brick_periodic() const + { + return m_is_brick_periodic; + } + + decltype(auto) + block_sizes() const + { + return m_block_sizes; + } + + decltype(auto) + ghost_sizes() const + { + return m_ghost_sizes; + } + + decltype(auto) + amr_mesh_info() const + { + return m_mesh_map->get_amr_mesh_info(); + } + +private: + //! parallel environment + const ParallelEnv & m_par_env; + + //! config map (input parameter) + ConfigMap m_config_map; + + std::shared_ptr> m_amr_mesh; + + //! a MeshMap object so that we can extract the orchard keys as a kokkos view + std::shared_ptr> m_mesh_map; + + //! p4est connectivity sizes. + //! brick sizes are used to transform logical coordinates into vertex space (real space) + // Kokkos::Array m_brick_sizes; + brick_size_t m_brick_sizes; + + //! array of bool to tell if mesh is periodic or not + Kokkos::Array m_is_brick_periodic; + + //! block sizes + block_size_t m_block_sizes; + + //! block sizes + block_size_t m_ghost_sizes; + + //! pointwise init functor + const Function m_f; + +}; // class InitialAMRSetup + +extern template class InitialAMRSetup<2, kalypsso::DefaultDevice, InitGaussian>; +extern template class InitialAMRSetup<3, kalypsso::DefaultDevice, InitGaussian>; + +extern template class InitialAMRSetup<2, kalypsso::DefaultDevice, InitHat>; +extern template class InitialAMRSetup<3, kalypsso::DefaultDevice, InitHat>; + +extern template class InitialAMRSetup<2, kalypsso::DefaultDevice, InitFuncParabola>; +extern template class InitialAMRSetup<3, kalypsso::DefaultDevice, InitFuncParabola>; + +extern template class InitialAMRSetup<2, kalypsso::DefaultDevice, InitFuncSineWave>; +extern template class InitialAMRSetup<3, kalypsso::DefaultDevice, InitFuncSineWave>; + +extern template class InitialAMRSetup<2, kalypsso::DefaultDevice, InitFunc1>; +extern template class InitialAMRSetup<3, kalypsso::DefaultDevice, InitFunc1>; + +extern template class InitialAMRSetup<2, kalypsso::DefaultDevice, InitFunc2>; +extern template class InitialAMRSetup<3, kalypsso::DefaultDevice, InitFunc2>; + +extern template class InitialAMRSetup<2, kalypsso::DefaultDevice, InitFunc3>; +extern template class InitialAMRSetup<3, kalypsso::DefaultDevice, InitFunc3>; + +// only instantiate those class when the default device is not on host +#if defined(KOKKOS_ENABLE_DEFAULT_DEVICE_TYPE_OPENMP) || \ + defined(KOKKOS_ENABLE_DEFAULT_DEVICE_TYPE_THREADS) || \ + defined(KOKKOS_ENABLE_DEFAULT_DEVICE_TYPE_SERIAL) +#else +extern template class InitialAMRSetup<2, kalypsso::HostDevice, InitFuncSineWave>; +extern template class InitialAMRSetup<3, kalypsso::HostDevice, InitFuncSineWave>; +#endif + +} // namespace kalypsso + +#endif // KALYPSSO_TEST_AMRMESH_INITIALAMRSETUP_H_ diff --git a/test/test_common/test_func.h b/test/test_common/test_func.h new file mode 100644 index 0000000..2090350 --- /dev/null +++ b/test/test_common/test_func.h @@ -0,0 +1,222 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file test_func.h + */ +#ifndef KALYPSSO_TEST_TESTCOMMON_TESTFUNC_H_ +#define KALYPSSO_TEST_TESTCOMMON_TESTFUNC_H_ + +#include // for KALYPSSO_ASSERT +#include +#include + +namespace kalypsso +{ + +// ============================================================================= +// ============================================================================= +//! pointwise init functor to init data with Gaussian shape +struct InitGaussian +{ + static constexpr bool has_face_averaged_values = false; + + InitGaussian(real_t sigma_) + : sigma(sigma_) + {} + + real_t sigma; + + KOKKOS_FUNCTION auto + operator()(real_t x, real_t y, int var) const + { + constexpr real_t x0 = KALYPSSO_NUM(0.5); + constexpr real_t y0 = KALYPSSO_NUM(0.5); + // constexpr real_t sigma = KALYPSSO_NUM(0.3); + + if (var == 0) + return exp(-((x - x0) * (x - x0) + (y - y0) * (y - y0)) / 2 / sigma / sigma) / sigma / + sqrt(2 * PI_F); + else + return KALYPSSO_NUM(0.0); + } + + KOKKOS_FUNCTION auto + faverage([[maybe_unused]] real_t x, + [[maybe_unused]] real_t y, + [[maybe_unused]] real_t delta_x, + [[maybe_unused]] real_t delta_y, + [[maybe_unused]] int var) const + { + return ZERO_F; + } + + KOKKOS_FUNCTION auto + operator()(real_t x, real_t y, real_t z, int var) const + { + constexpr real_t x0 = KALYPSSO_NUM(0.4); + constexpr real_t y0 = KALYPSSO_NUM(0.8); + constexpr real_t z0 = KALYPSSO_NUM(0.9); + // constexpr real_t sigma = KALYPSSO_NUM(0.3); + if (var == 0) + return exp(-((x - x0) * (x - x0) + (y - y0) * (y - y0) + (z - z0) * (z - z0)) / 2 / sigma / + sigma) / + sigma / sqrt(2 * PI_F); + else + return KALYPSSO_NUM(0.0); + } +}; // struct InitGaussian + +// ============================================================================= +// ============================================================================= +//! pointwise init functor to init data with Hat shape +struct InitHat +{ + static constexpr bool has_face_averaged_values = false; + + InitHat(real_t radius_) + : radius(radius_) + {} + + real_t radius; + + KOKKOS_FUNCTION auto + operator()(real_t x, real_t y, int var) const + { + constexpr real_t x0 = KALYPSSO_NUM(0.5); + constexpr real_t y0 = KALYPSSO_NUM(0.5); + // constexpr real_t radius = KALYPSSO_NUM(0.3); + + if (var == 0) + return ((x - x0) * (x - x0) + (y - y0) * (y - y0)) < radius * radius ? KALYPSSO_NUM(1.0) + : KALYPSSO_NUM(0.0); + else + return KALYPSSO_NUM(0.0); + } + + KOKKOS_FUNCTION auto + faverage([[maybe_unused]] real_t x, + [[maybe_unused]] real_t y, + [[maybe_unused]] real_t delta_x, + [[maybe_unused]] real_t delta_y, + [[maybe_unused]] int var) const + { + return ZERO_F; + } + + KOKKOS_FUNCTION auto + operator()(real_t x, real_t y, real_t z, int var) const + { + constexpr real_t x0 = KALYPSSO_NUM(0.4); + constexpr real_t y0 = KALYPSSO_NUM(0.8); + constexpr real_t z0 = KALYPSSO_NUM(0.9); + // constexpr real_t radius = KALYPSSO_NUM(0.3); + if (var == 0) + return ((x - x0) * (x - x0) + (y - y0) * (y - y0) + (z - z0) * (z - z0)) < radius * radius + ? KALYPSSO_NUM(1.0) + : KALYPSSO_NUM(0.0); + else + return KALYPSSO_NUM(0.0); + } +}; // struct InitHat + +// ============================================================================= +// ============================================================================= +//! pointwise init functor to init data with 2nd order polynomial +struct InitFuncParabola +{ + static constexpr bool has_face_averaged_values = false; + + KOKKOS_FUNCTION auto + operator()(real_t x, real_t y, int var) const + { + if (var == 0 or var == 1) + return 3 * x * x + y * y; + else if (var == 2) + return x; + else if (var == 3) + return KALYPSSO_NUM(8.0); // exact value of Laplacian(3*x*x+y*y) + else + return KALYPSSO_NUM(0.0); + } + + KOKKOS_FUNCTION auto + faverage([[maybe_unused]] real_t x, + [[maybe_unused]] real_t y, + [[maybe_unused]] real_t delta_x, + [[maybe_unused]] real_t delta_y, + [[maybe_unused]] int var) const + { + return ZERO_F; + } + + KOKKOS_FUNCTION auto + operator()(real_t x, real_t y, real_t z, int var) const + { + if (var == 0 or var == 1) + return static_cast(3) * x * x + y * y + KALYPSSO_NUM(0.5) * z * z; + else if (var == 2) + return x; + else if (var == 3) + return y; + else if (var == 4) + return KALYPSSO_NUM(9.0); // exact value of Laplacian(3*x*x+y*y+0.5*z*z) + else + return KALYPSSO_NUM(0.0); + } +}; // struct InitFuncParabola + +// ============================================================================= +// ============================================================================= +//! pointwise init functor to init data with sine wave +struct InitFuncSineWave +{ + static constexpr bool has_face_averaged_values = false; + + static constexpr real_t k = KALYPSSO_NUM(1.0); + + KOKKOS_FUNCTION auto + operator()(real_t x, real_t y, int var) const + { + if (var == 0 or var == 1) + return Kokkos::sin(2 * PI_F * k * (x + y)); + else if (var == 2) + return x; + else if (var == 3) + return KALYPSSO_NUM(-8.0) * PI_F * PI_F * k * k * + Kokkos::sin(2 * PI_F * k * (x + y)); // exact value of laplacian(sin(x+y)) + else + return KALYPSSO_NUM(0.0); + } + + KOKKOS_FUNCTION auto + faverage([[maybe_unused]] real_t x, + [[maybe_unused]] real_t y, + [[maybe_unused]] real_t delta_x, + [[maybe_unused]] real_t delta_y, + [[maybe_unused]] int var) const + { + return ZERO_F; + } + + KOKKOS_FUNCTION auto + operator()(real_t x, real_t y, real_t z, int var) const + { + if (var == 0 or var == 1) + return Kokkos::sin(2 * PI_F * k * (x + y + z)); + else if (var == 2) + return x; + else if (var == 3) + return y; + else if (var == 4) + return static_cast(-12) * PI_F * PI_F * k * k * + Kokkos::sin(2 * PI_F * k * (x + y + z)); // exact value of laplacian(sin(x+y)) + else + return KALYPSSO_NUM(0.0); + } +}; // struct InitFuncSineWave + +} // namespace kalypsso + +#endif // KALYPSSO_TEST_TESTCOMMON_TESTFUNC_H_ diff --git a/test/utils/CMakeLists.txt b/test/utils/CMakeLists.txt new file mode 100644 index 0000000..9040988 --- /dev/null +++ b/test/utils/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(config) diff --git a/test/utils/config/CMakeLists.txt b/test/utils/config/CMakeLists.txt new file mode 100644 index 0000000..3046413 --- /dev/null +++ b/test/utils/config/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(ConfigMapTest ConfigMapTest.cpp) +target_link_libraries(ConfigMapTest kalypsso::core_config kalypsso::config) diff --git a/test/utils/config/ConfigMapTest.cpp b/test/utils/config/ConfigMapTest.cpp new file mode 100644 index 0000000..a4c5c69 --- /dev/null +++ b/test/utils/config/ConfigMapTest.cpp @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include + +namespace kalypsso +{ + +int +config_map_test() +{ + // make test.ini file + std::fstream iniFile; + iniFile.open("./test.ini", std::ios_base::out); + iniFile << "; Test config file for ini_test.c" << std::endl; + + iniFile << "[Protocol] ; Protocol configuration" << std::endl; + iniFile << "Version=6 ; IPv6" << std::endl; + + iniFile << "[User]" << std::endl; + iniFile << "Name = Bob Smith ; Spaces around '=' are stripped" << std::endl; + iniFile << "Email = bob@smith.com ; And comments (like this) ignored" << std::endl; + iniFile.close(); + + // create a INIReader instance + ConfigMap reader("./test.ini"); + + if (reader.parse_error() < 0) + { + std::cout << "Can't load 'test.ini'\n"; + return 1; + } + std::cout << "Config loaded from 'test.ini': version=" + << reader.getInteger("protocol", "version", -1) + << ", name=" << reader.getString("user", "name", "UNKNOWN") + << ", email=" << reader.getString("user", "email", "UNKNOWN") << "\n"; + + ConfigMap reader2 = reader; + std::cout << std::endl; + std::cout << "Config loaded from reader2: version=" + << reader.getInteger("protocol", "version", -1) + << ", name=" << reader.getString("user", "name", "UNKNOWN") + << ", email=" << reader.getString("user", "email", "UNKNOWN") << "\n"; + + return 0; +} + +} // namespace kalypsso + +// ======================================================================== +// ======================================================================== +int +main([[maybe_unused]] int argc, [[maybe_unused]] char * argv[]) +{ + + const auto res = kalypsso::config_map_test(); + + return res; +} diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000..dfae5ec --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,3 @@ +if(KALYPSSO_BUILD_PARAVIEW_PLUGIN) + add_subdirectory(ParaViewKalypssoPlugin) +endif() diff --git a/tools/ParaViewKalypssoPlugin/CMakeLists.txt b/tools/ParaViewKalypssoPlugin/CMakeLists.txt new file mode 100644 index 0000000..c64d6d2 --- /dev/null +++ b/tools/ParaViewKalypssoPlugin/CMakeLists.txt @@ -0,0 +1,43 @@ +cmake_minimum_required(VERSION 3.18) + +project(KalypssoHDF5ReaderPlugin C CXX Fortran) + +include(GNUInstallDirs) +set(BUILD_SHARED_LIBS ON) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +find_package(ParaView REQUIRED) + +if(PARAVIEW_USE_MPI) + find_package(MPI) +endif() + +paraview_plugin_scan( + PLUGIN_FILES + "${CMAKE_CURRENT_SOURCE_DIR}/src/paraview.plugin" + PROVIDES_PLUGINS + plugins + REQUIRES_MODULES + required_modules + ENABLE_BY_DEFAULT + ON) + +foreach(module IN LISTS required_modules) + if(NOT TARGET "${module}") + message("Missing required module: ${module}") + return() + endif() +endforeach() + +paraview_plugin_build( + RUNTIME_DESTINATION + "${CMAKE_INSTALL_BINDIR}" + LIBRARY_DESTINATION + "${CMAKE_INSTALL_LIBDIR}" + LIBRARY_SUBDIRECTORY + "${PARAVIEW_PLUGIN_SUBDIR}" + PLUGINS + ${plugins}) diff --git a/tools/ParaViewKalypssoPlugin/Readme.md b/tools/ParaViewKalypssoPlugin/Readme.md new file mode 100644 index 0000000..89d0047 --- /dev/null +++ b/tools/ParaViewKalypssoPlugin/Readme.md @@ -0,0 +1,41 @@ +# Paraview plugin for loading kalypsso HDF5 data + +## Motivations + +The purpose of this Paraview plugin is to provide an alternative way of loading kalypsso data into paraview. + +### Reducing file size + +Currently kalypsso output uses XDMF+HDF5 file format, and ParaView loads data as a VTU object (VTK Unstructured Grid). Let's just compute the file size overhead to store the mesh in the HDF5 file. A given AMR mesh contains $N$ octants, $(n+1)^d$ points per octant, where $n$ is the block size and $d=2,3$ is dimension. This means, the mesh is represented by +- an array of points coordinates of size $3 N (n+1)^d$ using float +- an array of cell connectivity of size $2^d N n^d$ using integer +while each physics field (rho, momentum, ...) is represented by an array of doubles of size $N n^d$ + +For a pure 3D hydrodynamics simulation (5 fields), the mesh size overhead is roughly $11 Nn^3$ whereas the physics fields size is $5 N n^d$. + +We could avoid dumping the mesh into the hdf5 file. Instead, we could just dump the orchard keys (1 per octant, so just an array of $N$ 64 bits integer), since the mesh point coordinates can be reconstructed from it. + +### Use a VTK AMR data format + +Current kalypsso xdmf+hdf5 output are loaded into paraview as an unstructured grid. It would be good to be able to use one of the three native AMR data formats available in VTK, namely: + +- [vtkNonOverlappingAMR](https://vtk.org/doc/nightly/html/classvtkNonOverlappingAMR.html) : this class is perfectly adapted to kalypsso data structure +- [vtkOverlappingAMR](https://vtk.org/doc/nightly/html/classvtkOverlappingAMR.html): irrelevant here +- [vtkHyperTreeGrid](https://vtk.org/doc/nightly/html/classvtkHyperTreeGrid.html): requires some work to adapt kalypsso data structure to HTG. + +We will first focus on designing a paraview plugin for loading kalypsso HDF5 file and fill a `vtkNonOverlappingAMR`. Later we will consider an upgrade of the plugin to data into a HTG. + +### Resources + +Some links to help writing a ParaView plugin: + +- https://kitware.github.io/paraview-docs/nightly/cxx/PluginHowto.html +- https://www.paraview.org/Wiki/ParaView/Plugin_HowTo (this is deprecated) + +Some examples by J. Favre: +- https://github.com/jfavre/ParaViewSalvusPlugin +- https://github.com/jfavre/ParaViewNek5000Plugin + +### Additional notes + +See if the ParaView plugin code could be mutualized with a potential catalyst plugin. diff --git a/tools/ParaViewKalypssoPlugin/src/CMakeLists.txt b/tools/ParaViewKalypssoPlugin/src/CMakeLists.txt new file mode 100644 index 0000000..97ef133 --- /dev/null +++ b/tools/ParaViewKalypssoPlugin/src/CMakeLists.txt @@ -0,0 +1,14 @@ +paraview_add_plugin( + pvKalypssoHDF5Reader + VERSION + "1.0" + REQUIRED_ON_SERVER + MODULES + pvKalypssoHDF5Reader::KalypssoHDF5Reader + MODULE_FILES + "${CMAKE_CURRENT_SOURCE_DIR}/Reader/vtk.module") + +option(KALYPSSO_PVPLUGIN_BUILD_TESTING "Build Plugin Testing" OFF) +if(KALYPSSO_PVPLUGIN_BUILD_TESTING AND BUILD_SHARED_LIBS) + add_subdirectory(Testing) +endif() diff --git a/tools/ParaViewKalypssoPlugin/src/Reader/CMakeLists.txt b/tools/ParaViewKalypssoPlugin/src/Reader/CMakeLists.txt new file mode 100644 index 0000000..b433be4 --- /dev/null +++ b/tools/ParaViewKalypssoPlugin/src/Reader/CMakeLists.txt @@ -0,0 +1,22 @@ +set(VTK_KALYPSSO_HDF5_READER_CLASSES vtkKalypssoHDF5Reader) + +set(VTK_KALYPSSO_HDF5_READER_SOURCES vtkKalypssoHDF5Reader.cxx) + +set(VTK_KALYPSSO_HDF5_READER_HEADERS vtkKalypssoHDF5Reader.h) + +vtk_module_add_module( + pvKalypssoHDF5Reader::KalypssoHDF5Reader + CLASSES + ${VTK_KALYPSSO_HDF5_READER_CLASSES} + SOURCES + ${VTK_KALYPSSO_HDF5_READER_SOURCES} + HEADERS + ${VTK_KALYPSSO_HDF5_READER_HEADERS}) + +# vtk_module_third_party_external( PACKAGE Kokkos TARGETS Kokkos::kokkos) + +# vtk_module_find_package( PACKAGE Kokkos ) + +vtk_module_link(pvKalypssoHDF5Reader::KalypssoHDF5Reader PUBLIC HighFive::HighFive kalypsso::core) + +paraview_add_server_manager_xmls(XMLS KalypssoHDF5_Server.xml) diff --git a/tools/ParaViewKalypssoPlugin/src/Reader/KalypssoHDF5_Server.xml b/tools/ParaViewKalypssoPlugin/src/Reader/KalypssoHDF5_Server.xml new file mode 100644 index 0000000..de7906b --- /dev/null +++ b/tools/ParaViewKalypssoPlugin/src/Reader/KalypssoHDF5_Server.xml @@ -0,0 +1,74 @@ + + + + + short_help="Reads an HDF5 file" + long_help="Reads an HDF5 file"> + This reader reads HDF5 files, and the output is a vtkNonOverlappingAMR. + + + + + + + + This reader supports demand-driven heavy data reading i.e. downstream + pipeline can request specific blocks from the AMR using + vtkCompositeDataPipeline::UPDATE_COMPOSITE_INDICES() key in + RequestUpdateExtent() pass. However, when down-stream doesn't provide + any specific keys, the default behavior can be setup to read at-most N + levels by default. The number of levels read can be set using this + method. Set this to 0 to imply no limit. Default is 1. + + + + + + + + + + + + + + + This property contains a list of the cell-centered arrays to read. + + + + + + + Available timestep values. + + + + + + + + + diff --git a/tools/ParaViewKalypssoPlugin/src/Reader/vtk.module b/tools/ParaViewKalypssoPlugin/src/Reader/vtk.module new file mode 100644 index 0000000..ea7723f --- /dev/null +++ b/tools/ParaViewKalypssoPlugin/src/Reader/vtk.module @@ -0,0 +1,12 @@ +NAME + pvKalypssoHDF5Reader::KalypssoHDF5Reader +LIBRARY_NAME + KalypssoHDF5Reader +DEPENDS + VTK::CommonCore + VTK::CommonDataModel + VTK::CommonExecutionModel + VTK::CommonMisc +PRIVATE_DEPENDS + VTK::hdf5 + VTK::mpi diff --git a/tools/ParaViewKalypssoPlugin/src/Reader/vtkKalypssoHDF5Reader.cxx b/tools/ParaViewKalypssoPlugin/src/Reader/vtkKalypssoHDF5Reader.cxx new file mode 100644 index 0000000..d9f88ff --- /dev/null +++ b/tools/ParaViewKalypssoPlugin/src/Reader/vtkKalypssoHDF5Reader.cxx @@ -0,0 +1,534 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#define PARALLEL_DEBUG + +#include "vtkKalypssoHDF5Reader.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +vtkStandardNewMacro(vtkKalypssoHDF5Reader); + +// ========================================================================= +// ========================================================================= +// Computes the cell center for the cell corresponding to cellIdx w.r.t. +// the given grid. The cell center is stored in the supplied buffer c. +std::array +ComputeCellCenter(vtkUniformGrid * grid, const int cellIdx) +{ + assert("pre: grid != NULL" && (grid != nullptr)); + assert("pre: cellIdx in bounds" && (cellIdx >= 0) && (cellIdx < grid->GetNumberOfCells())); + + vtkCell * myCell = grid->GetCell(cellIdx); + assert("post: cell is NULL" && (myCell != nullptr)); + + double pCenter[3]; + double * weights = new double[myCell->GetNumberOfPoints()]; + int subId = myCell->GetParametricCenter(pCenter); + + std::array c; + myCell->EvaluateLocation(subId, pCenter, c.data(), weights); + delete[] weights; + return c; +} + +// ========================================================================= +// ========================================================================= +void +attach_dummy_data_to_grid(vtkUniformGrid * grid, int dim, int block_size) +{ + + vtkDoubleArray * user_data = vtkDoubleArray::New(); + user_data->SetName("user_data"); + user_data->SetNumberOfComponents(1); + auto nbTuples = dim == 2 ? block_size * block_size : block_size * block_size * block_size; + user_data->SetNumberOfTuples(nbTuples); + + for (int cellIdx = 0; cellIdx < grid->GetNumberOfCells(); ++cellIdx) + { + auto center = ComputeCellCenter(grid, cellIdx); + + const auto value = center[0] + center[1] + center[2]; + + user_data->SetTuple1(cellIdx, value); + } + + grid->GetCellData()->AddArray(user_data); + user_data->Delete(); +} + +// +// PUBLIC MEMBERS +// + +// ================================================================================ +// ================================================================================ +void +vtkKalypssoHDF5Reader::PrintSelf(ostream & os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); + os << indent << "FileName: " << (this->FileName ? this->FileName : "(NULL)") << std::endl; + os << indent << "dim: " << this->dim << std::endl; + os << indent << "level_min: " << this->level_min << std::endl; + os << indent << "level_max: " << this->level_max << std::endl; + os << indent << "max level to read: " << this->MaximumLevelsToReadByDefault << std::endl; + + os << indent << "level_histogram:"; + for (auto num_level : this->level_histogram) + os << " " << num_level; + os << std::endl; + + os << indent << "block_sizes:"; + for (auto size : this->block_sizes) + os << " " << size; + os << std::endl; + + os << indent << "brick_sizes:"; + for (auto size : this->brick_sizes) + os << " " << size; + os << std::endl; + + os << indent << "scaling_factor: " << this->scaling_factor << std::endl; + + os << indent << "xyz_min:"; + for (auto coord : this->xyz_min) + os << " " << coord; + os << std::endl; +} + +// ================================================================================ +// ================================================================================ +int +vtkKalypssoHDF5Reader::GetNumberOfCellArrays() +{ + return this->CellDataArraySelection->GetNumberOfArrays(); +} + +// ================================================================================ +// ================================================================================ +const char * +vtkKalypssoHDF5Reader::GetCellArrayName(int index) +{ + return this->CellDataArraySelection->GetArrayName(index); +} + +// ================================================================================ +// ================================================================================ +int +vtkKalypssoHDF5Reader::GetCellArrayStatus(const char * name) +{ + return this->CellDataArraySelection->ArrayIsEnabled(name); +} + +// ================================================================================ +// ================================================================================ +void +vtkKalypssoHDF5Reader::SetCellArrayStatus(const char * name, int status) +{ + if (status) + { + this->CellDataArraySelection->EnableArray(name); + } + else + { + this->CellDataArraySelection->DisableArray(name); + } +} + +// ================================================================================ +// ================================================================================ +void +vtkKalypssoHDF5Reader::SelectionModifiedCallback(vtkObject *, + unsigned long, + void * clientdata, + void *) +{ + static_cast(clientdata)->Modified(); +} + +// ================================================================================ +// ================================================================================ +void +vtkKalypssoHDF5Reader::EnableCellArray(const char * name) +{ + this->SetCellArrayStatus(name, 1); +} + +// ================================================================================ +// ================================================================================ +void +vtkKalypssoHDF5Reader::DisableCellArray(const char * name) +{ + this->SetCellArrayStatus(name, 0); +} + +// ================================================================================ +// ================================================================================ +void +vtkKalypssoHDF5Reader::EnableAllCellArrays() +{ + this->CellDataArraySelection->EnableAllArrays(); +} + +// ================================================================================ +// ================================================================================ +void +vtkKalypssoHDF5Reader::DisableAllCellArrays() +{ + this->CellDataArraySelection->DisableAllArrays(); +} + +// ================================================================================ +// ================================================================================ +void +vtkKalypssoHDF5Reader::SetFileName(const char * fileName) +{ + if (this->FileName != nullptr) + { + delete[] this->FileName; + this->FileName = nullptr; + } + this->FileName = new char[strlen(fileName) + 1]; + strcpy(this->FileName, fileName); + this->FileName[strlen(fileName)] = '\0'; + + this->ReadMetaData(); + this->SetUpDataArraySelections(); + this->InitializeArraySelections(); + + // this->Modified(); +} + +// +// PROTECTED MEMBERS +// + +// ================================================================================ +// ================================================================================ +vtkKalypssoHDF5Reader::vtkKalypssoHDF5Reader() +{ + this->InitialRequest = true; + this->FileName = nullptr; + this->MetaDataLoaded = false; + this->DebugOff(); + this->SetNumberOfInputPorts(0); + this->SetNumberOfOutputPorts(1); + this->CellDataArraySelection = vtkDataArraySelection::New(); + this->SelectionObserver = vtkCallbackCommand::New(); + this->SelectionObserver->SetCallback(&vtkKalypssoHDF5Reader::SelectionModifiedCallback); + this->SelectionObserver->SetClientData(this); + this->CellDataArraySelection->AddObserver(vtkCommand::ModifiedEvent, this->SelectionObserver); + this->NumberOfTimeSteps = 0; + this->TimeStep = 0; + this->ActualTimeStep = 0; + this->TimeStepTolerance = 1E-6; + + // all these will be populated in RequestInformation + this->dim = 0; + this->level_min = 0; + this->level_max = 0; + + this->scaling_factor = 1.0; + this->xyz_min.assign(3, 0.0); + + this->MaximumLevelsToReadByDefault = 1; + + // default values (should be initialized by reading hdf5 file) + this->varnames = {}; +} + +// ================================================================================ +// ================================================================================ +vtkKalypssoHDF5Reader::~vtkKalypssoHDF5Reader() +{ + vtkDebugMacro(<< "cleaning up inside destructor"); + if (this->FileName) + delete[] this->FileName; + + this->CellDataArraySelection->RemoveObserver(this->SelectionObserver); + this->CellDataArraySelection->Delete(); + this->CellDataArraySelection = nullptr; + this->SelectionObserver->Delete(); +} + +// ================================================================================ +// ================================================================================ +int +vtkKalypssoHDF5Reader::CanReadFile(const char * fname) +{ + bool valid = true; + try + { + // check that file can be open, and that group amr and celldata exist + HighFive::File file(std::string(this->FileName), HighFive::File::ReadOnly); + auto amr = file.getGroup("/amr"); + auto celldata = file.getGroup("/celldata"); + } + catch (const std::exception & e) + { + std::cerr << e.what() << std::endl; + valid = false; + } + return valid; +} + +// ================================================================================ +// ================================================================================ +void +vtkKalypssoHDF5Reader::ReadMetaData() +{ + HighFive::File file(std::string(this->FileName), HighFive::File::ReadOnly); + + auto celldata_group = file.getGroup("/celldata"); + + // get a list of available cell data + this->varnames = celldata_group.listObjectNames(); + // for (auto const & var_name : this->varnames) + // std::cout << var_name << "\n"; +} + +// ================================================================================ +// ================================================================================ +void +vtkKalypssoHDF5Reader::SetUpDataArraySelections() +{ + for (auto warn : this->varnames) + this->CellDataArraySelection->AddArray(warn.c_str()); +} + +// ================================================================================ +// ================================================================================ +void +vtkKalypssoHDF5Reader::InitializeArraySelections() +{ + if (this->InitialRequest) + { + this->CellDataArraySelection->DisableAllArrays(); + this->InitialRequest = false; + } +} +// ================================================================================ +// ================================================================================ +int +vtkKalypssoHDF5Reader::RequestInformation(vtkInformation * request, + vtkInformationVector ** inputVector, + vtkInformationVector * outputVector) +{ + if (this->MetaDataLoaded) + { + return (1); + } + + this->Superclass::RequestInformation(request, inputVector, outputVector); + + vtkDebugMacro(<< "vtkKalypssoHDF5Reader::RequestInformation: Parsing file " << this->FileName + << " for fields."); + + HighFive::File file(std::string(this->FileName), HighFive::File::ReadOnly); + + vtkInformation * outInfo = outputVector->GetInformationObject(0); + outInfo->Set(CAN_HANDLE_PIECE_REQUEST(), 1); + + // amr_output is allocated in RequestDataObject + vtkDataObject * doOutput = outInfo->Get(vtkDataObject::DATA_OBJECT()); + vtkNonOverlappingAMR * amr_output = vtkNonOverlappingAMR::SafeDownCast(doOutput); + + // don't know yet if that is useful for us + // outInfo->Set(vtkDataObject::DATA_NUMBER_OF_GHOST_LEVELS(), 0); + + auto amr = file.getGroup("/amr"); + + // get dimension + amr.getAttribute("dim").read(this->dim); + + // get level_min / level_max + amr.getAttribute("level_min").read(this->level_min); + amr.getAttribute("level_max").read(this->level_max); + + // get number of blocks per levels (level histogram is a HDF5 attribute in group "/amr") + amr.getAttribute("level_histogram").read(this->level_histogram); + + // get block and brick sizes + amr.getAttribute("block_sizes").read(this->block_sizes); + amr.getAttribute("brick_sizes").read(this->brick_sizes); + + amr.getAttribute("scaling_factor").read(this->scaling_factor); + amr.getAttribute("xyz_min").read(this->xyz_min); + + HighFive::DataSet amr_keys_ds = file.getDataSet("/amr/keys"); + amr_keys_ds.read(this->amr_keys); + + HighFive::DataSet amr_level_indexes_ds = file.getDataSet("/amr/level_indexes"); + amr_level_indexes_ds.read(this->amr_level_indexes); + + const auto num_levels = this->level_max - this->level_min + 1; + amr_output->Initialize(num_levels, this->level_histogram.data()); + + if (this->dim == 2) + { + this->setup_geometry<2>(amr_output); + } + else if (this->dim == 3) + { + this->setup_geometry<3>(amr_output); + } + + // see later if we want to handle multiple time steps + this->NumberOfTimeSteps = 1; + this->TimeStepValues.assign(this->NumberOfTimeSteps, 0.0); + for (int i = 0; i < NumberOfTimeSteps; i++) + this->TimeStepValues[i] = 0; // t_start + i * (dt); + + double timeRange[2]; + timeRange[0] = this->TimeStepValues.front(); + timeRange[1] = this->TimeStepValues.back(); + + outInfo->Set(vtkStreamingDemandDrivenPipeline::TIME_RANGE(), timeRange, 2); + outInfo->Set(vtkStreamingDemandDrivenPipeline::TIME_STEPS(), + this->TimeStepValues.data(), + static_cast(this->TimeStepValues.size())); + + this->MetaDataLoaded = true; + + return 1; +} + +// ================================================================================ +// ================================================================================ +int +vtkKalypssoHDF5Reader::RequestDataObject(vtkInformation * vtkNotUsed(request), + vtkInformationVector ** vtkNotUsed(inputVector), + vtkInformationVector * outputVector) +{ + vtkDataObject * output = vtkDataObject::GetData(outputVector, 0); + if (!output || !output->IsA("vtkNonOverlappingAMR")) + { + vtkDataObject * newDO = vtkDataObjectTypes::NewDataObject("vtkNonOverlappingAMR"); + if (newDO) + { + outputVector->GetInformationObject(0)->Set(vtkDataObject::DATA_OBJECT(), newDO); + newDO->FastDelete(); + return 1; + } + } + + return 1; +} + +// ================================================================================ +// ================================================================================ +int +vtkKalypssoHDF5Reader::RequestData(vtkInformation * vtkNotUsed(request), + vtkInformationVector ** vtkNotUsed(inputVector), + vtkInformationVector * outputVector) +{ + vtkTimerLog::MarkStartEvent("vtkKalypssoHDF5Reader::RequestData"); + + vtkDebugMacro(<< "RequestData(BEGIN)"); + vtkInformation * outInfo = outputVector->GetInformationObject(0); + vtkDataObject * doOutput = outInfo->Get(vtkDataObject::DATA_OBJECT()); + vtkNonOverlappingAMR * amr_output = vtkNonOverlappingAMR::SafeDownCast(doOutput); + + // populate vtkNonOverlappingAMR geometry information + const auto num_levels = this->level_max - this->level_min + 1; + amr_output->Initialize(num_levels, this->level_histogram.data()); + + if (this->dim == 2) + { + this->setup_geometry<2>(amr_output); + } + else if (this->dim == 3) + { + this->setup_geometry<3>(amr_output); + } + + + // int piece = outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_PIECE_NUMBER()); + // int numPieces = outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_PIECES()); + + this->UpdateProgress(0.0); + + // TODO: make sure handling multiple time steps is ok here + this->ActualTimeStep = 0; + double requestedTimeValue = 0.0; + if (outInfo->Has(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP())) + { + requestedTimeValue = outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP()); + + for (int i = 0; i < this->NumberOfTimeSteps; i++) + { + if (fabs(requestedTimeValue - this->TimeStepValues[i]) < this->TimeStepTolerance) + { + this->ActualTimeStep = i; + break; + } + } + amr_output->GetInformation()->Set(vtkDataObject::DATA_TIME_STEP(), requestedTimeValue); + } + std::cout << "requestedTimeValue " << requestedTimeValue << endl; + std::cout << "this->ActualTimeStep " << this->ActualTimeStep << endl; + + if (!this->FileName) + { + vtkErrorMacro(<< "error reading header specified!"); + return 0; + } + + // this is where we populated AMR "heavy" data + if (this->dim == 2) + { + this->Load_Variables<2>(amr_output); + } + else if (dim == 3) + { + this->Load_Variables<3>(amr_output); + } + + this->UpdateProgress(1.0); + + vtkTimerLog::MarkEndEvent("vtkKalypssoHDF5Reader::RequestData"); + + return 1; +} + +// ================================================================================ +// ================================================================================ +bool +vtkKalypssoHDF5Reader::Is_Variable_Enabled(const char * vname) +{ + return GetCellArrayStatus(vname); +} diff --git a/tools/ParaViewKalypssoPlugin/src/Reader/vtkKalypssoHDF5Reader.h b/tools/ParaViewKalypssoPlugin/src/Reader/vtkKalypssoHDF5Reader.h new file mode 100644 index 0000000..4c25709 --- /dev/null +++ b/tools/ParaViewKalypssoPlugin/src/Reader/vtkKalypssoHDF5Reader.h @@ -0,0 +1,367 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file vtkKalypssoHDF5Reader + * + * vtkKalypssoHDF5Reader is a source object that reads a kalypsso HDF5 file + * and builds a vtkNonOverlappingAMR object. + * + * This plugins is inspired by the following sources from ParaView sources: + * + * - the Plugins and Examples/Plugins subfolders + * - class vtkAMRBaseReader + * - class vtkXMLUniformGridAMRReader (for RequestDataObject) + * + */ +#ifndef KALYPSSO_PARAVIEW_PLUGIN_VTKKALYPSSOHDF5READER_H +#define KALYPSSO_PARAVIEW_PLUGIN_VTKKALYPSSOHDF5READER_H + +#include "KalypssoHDF5ReaderModule.h" // for export macro +#include + +#include +#include +#include +#include +#include +#include + +class vtkCallbackCommand; + +// from kalypsso +#include +#include // for compute_cell_length + +#include + +#include +#include + +class vtkDataArraySelection; + +//! compute cell center (only use here for debugging when creating a simple scalar field) +std::array +ComputeCellCenter(vtkUniformGrid * grid, const int cellIdx); + +//! attaching scalar data to a uniform grid (i.e. a block of our vtkNonUniformAMR) +void +attach_dummy_data_to_grid(vtkUniformGrid * grid, int dim, int block_size); + +// ================================================================================ +// ================================================================================ +// ================================================================================ +class KALYPSSOHDF5READER_EXPORT vtkKalypssoHDF5Reader : public vtkNonOverlappingAMRAlgorithm +{ +public: + static vtkKalypssoHDF5Reader * + New(); + + void + PrintSelf(ostream & os, vtkIndent indent) override; + + vtkTypeMacro(vtkKalypssoHDF5Reader, vtkNonOverlappingAMRAlgorithm); + + ///@{ + /** + * This reader supports demand-driven heavy data reading i.e. downstream + * pipeline can request specific blocks from the AMR using + * vtkCompositeDataPipeline::UPDATE_COMPOSITE_INDICES() key in + * RequestUpdateExtent() pass. However, when down-stream doesn't provide any + * specific keys, the default behavior can be setup to read at-most N levels + * by default. The number of levels read can be set using this method. + * Set this to 0 to imply no limit. Default is 0. + */ + vtkSetMacro(MaximumLevelsToReadByDefault, unsigned int); + vtkGetMacro(MaximumLevelsToReadByDefault, unsigned int); + ///@} + + + vtkGetObjectMacro(CellDataArraySelection, vtkDataArraySelection); + + //! The observer to modify this object when the array selection is modified. + vtkCallbackCommand * SelectionObserver; + + // Callback registered with the SelectionObserver. + static void + SelectionModifiedCallback(vtkObject * caller, + unsigned long eid, + void * clientdata, + void * calldata); + + int + GetNumberOfCellArrays(); + + const char * + GetCellArrayName(int index); + + int + GetCellArrayStatus(const char * name); + + void + SetCellArrayStatus(const char * name, int status); + + void + EnableAllCellArrays(); + + void + DisableAllCellArrays(); + + void + EnableCellArray(const char * name); + + void + DisableCellArray(const char * name); + + vtkGetFilePathMacro(FileName); + virtual void + SetFileName(VTK_FILEPATH const char * fileName); + +protected: + vtkKalypssoHDF5Reader(); + ~vtkKalypssoHDF5Reader(); + + //! a simple test to see if hdf5 file exists, and is "valid" (i.e. contains a HDF group named + //! "amr" at root level). + int + CanReadFile(VTK_FILEPATH const char * fname); + + void + ReadMetaData(); + + void + SetUpDataArraySelections(); + + void + InitializeArraySelections(); + + int + RequestInformation(vtkInformation * request, + vtkInformationVector ** inputVector, + vtkInformationVector * outputVector); + + //! allocate a vtkNonOverlappingAMR + int + RequestDataObject(vtkInformation * request, + vtkInformationVector ** inputVector, + vtkInformationVector * outputVector); + + int + RequestData(vtkInformation *, vtkInformationVector **, vtkInformationVector *); + + bool InitialRequest; + char * FileName; + + bool MetaDataLoaded; + + vtkDataArraySelection * CellDataArraySelection; + + int dim; + int level_min; + int level_max; + std::vector level_histogram; + + std::vector block_sizes; + std::vector brick_sizes; + + double scaling_factor; + std::vector xyz_min; + + std::vector amr_keys; + std::vector amr_level_indexes; + + unsigned int MaximumLevelsToReadByDefault; + + bool + Is_Variable_Enabled(const char * vname); + + template + void + Load_Variables(vtkNonOverlappingAMR * output); + +private: + vtkKalypssoHDF5Reader(const vtkKalypssoHDF5Reader &) = delete; + + template + void + setup_geometry(vtkNonOverlappingAMR * amr); + + void + operator=(const vtkKalypssoHDF5Reader &) = delete; + + std::vector varnames; + std::vector TimeStepValues; + int NumberOfTimeSteps; + int TimeStep; + int ActualTimeStep; + double TimeStepTolerance; +}; // class vtkKalypssoHDF5Reader + +// ================================================================= +// ================================================================= +template +void +vtkKalypssoHDF5Reader::setup_geometry(vtkNonOverlappingAMR * amr) +{ + + Kokkos::Array xyz_min_k; + xyz_min_k[kalypsso::IX] = this->xyz_min[0]; + xyz_min_k[kalypsso::IY] = this->xyz_min[1]; + if constexpr (DIM == 3) + { + xyz_min_k[kalypsso::IZ] = this->xyz_min[2]; + } + + for (size_t i = 0; i < this->amr_keys.size(); ++i) + { + const auto key = this->amr_keys[i]; + const auto level = kalypsso::orchard_key_t::level(key); + + if (level - this->level_min <= this->MaximumLevelsToReadByDefault) + { + + // compute physical x,y,z for the block origin (lower left corner) + constexpr auto centering = false; + const auto xyz_vertex = kalypsso::orchard_key_to_vertex_coord(key, centering); + const auto xyz = + kalypsso::vertex_coord_to_real_space(xyz_vertex, this->scaling_factor, xyz_min_k); + + // compute cell size (assume dx=dy=dz, i.e. block_sizes are the same along all directions) + const auto dx = kalypsso::compute_cell_length(level, this->block_sizes[kalypsso::IX]) * + this->scaling_factor; + + std::array origin{ xyz[kalypsso::IX], xyz[kalypsso::IY], 0.0 }; + std::array spacing{ dx, dx, 0.0 }; + if constexpr (DIM == 3) + { + origin[2] = xyz[kalypsso::IZ]; + spacing[2] = dx; + } + + std::array lo{ 0, 0, 0 }; + std::array hi{ this->block_sizes[kalypsso::IX] - 1, + this->block_sizes[kalypsso::IY] - 1, + 0 }; + if constexpr (DIM == 3) + { + hi[2] = this->block_sizes[kalypsso::IZ] - 1; + } + + vtkAMRBox box(lo.data(), hi.data()); + vtkNew ug; + ug->Initialize(&box, origin.data(), spacing.data()); + // attach_dummy_data_to_grid(ug, DIM, this->block_sizes[kalypsso::IX]); + amr->SetDataSet(level - this->level_min, this->amr_level_indexes[i], ug); + } + } +} // vtkKalypssoHDF5Reader::setup_geometry + +// ================================================================================ +// ================================================================================ +template +void +vtkKalypssoHDF5Reader::Load_Variables(vtkNonOverlappingAMR * amr_output) +{ + + size_t numcells_per_block = this->block_sizes[kalypsso::IX] * this->block_sizes[kalypsso::IY]; + if constexpr (DIM == 3) + { + numcells_per_block *= this->block_sizes[kalypsso::IZ]; + } + + Kokkos::Array xyz_min_k; + xyz_min_k[kalypsso::IX] = this->xyz_min[0]; + xyz_min_k[kalypsso::IY] = this->xyz_min[1]; + if constexpr (DIM == 3) + { + xyz_min_k[kalypsso::IZ] = this->xyz_min[2]; + } + + // open hdf5 file + HighFive::File file(std::string(this->FileName), HighFive::File::ReadOnly); + + // populate AMR structure block by block + // loop over all AMR blocks (identified by an orchard key) + for (size_t i = 0; i < this->amr_keys.size(); ++i) + { + const auto key = this->amr_keys[i]; + const auto level = kalypsso::orchard_key_t::level(key); + + if (level - this->level_min <= this->MaximumLevelsToReadByDefault) + { + // compute physical x,y,z for the block origin (lower left corner) + constexpr auto centering = false; + const auto xyz_vertex = kalypsso::orchard_key_to_vertex_coord(key, centering); + const auto xyz = + kalypsso::vertex_coord_to_real_space(xyz_vertex, this->scaling_factor, xyz_min_k); + + // compute cell size (assume dx=dy=dz, i.e. block_sizes are the same along all directions) + const auto dx = kalypsso::compute_cell_length(level, this->block_sizes[kalypsso::IX]) * + this->scaling_factor; + + std::array origin{ xyz[kalypsso::IX], xyz[kalypsso::IY], 0.0 }; + std::array spacing{ dx, dx, 0.0 }; + if constexpr (DIM == 3) + { + origin[2] = xyz[kalypsso::IZ]; + spacing[2] = dx; + } + + std::array lo{ 0, 0, 0 }; + std::array hi{ this->block_sizes[kalypsso::IX] - 1, + this->block_sizes[kalypsso::IY] - 1, + 0 }; + if constexpr (DIM == 3) + { + hi[2] = this->block_sizes[kalypsso::IZ] - 1; + } + + vtkAMRBox box(lo.data(), hi.data()); + + // create a unoform grid that will contain all data fields + vtkNew ug; + ug->Initialize(&box, origin.data(), spacing.data()); + + // loop over all enabled variables, read data as needed + for (size_t ivar = 0; ivar < this->varnames.size(); ivar++) + { + const char * vname = this->varnames[ivar].c_str(); + + if (this->Is_Variable_Enabled(vname)) // is variable enabled in the ParaView GUI + { + + auto dataset_name = std::string("/celldata/") + this->varnames[ivar]; + HighFive::DataSet dataset = file.getDataSet(dataset_name); + + std::vector offset{ i * numcells_per_block }; + std::vector size{ numcells_per_block }; + HighFive::Selection dataset_slice = dataset.select(offset, size); + + std::vector data; + dataset_slice.read(data); + + { + vtkDoubleArray * user_data = vtkDoubleArray::New(); + user_data->SetName(vname); + user_data->SetNumberOfComponents(1); + user_data->SetNumberOfTuples(numcells_per_block); + + for (int cellIdx = 0; cellIdx < ug->GetNumberOfCells(); ++cellIdx) + { + user_data->SetTuple1(cellIdx, data[cellIdx]); + } + + ug->GetCellData()->AddArray(user_data); + user_data->Delete(); + } + } + } // end for ivar + + amr_output->SetDataSet(level - this->level_min, this->amr_level_indexes[i], ug); + } + } // end for AMR keys/blocks + +} // vtkKalypssoHDF5Reader::Load_Variables + +#endif // KALYPSSO_PARAVIEW_PLUGIN_VTKKALYPSSOHDF5READER_H diff --git a/tools/ParaViewKalypssoPlugin/src/Testing/CMakeLists.txt b/tools/ParaViewKalypssoPlugin/src/Testing/CMakeLists.txt new file mode 100644 index 0000000..35f9732 --- /dev/null +++ b/tools/ParaViewKalypssoPlugin/src/Testing/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(Cxx) diff --git a/tools/ParaViewKalypssoPlugin/src/Testing/Cxx/CMakeLists.txt b/tools/ParaViewKalypssoPlugin/src/Testing/Cxx/CMakeLists.txt new file mode 100644 index 0000000..cae666f --- /dev/null +++ b/tools/ParaViewKalypssoPlugin/src/Testing/Cxx/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(TestKalypssoHDF5Reader TestKalypssoHDF5Reader.cxx) +target_link_libraries( + TestKalypssoHDF5Reader + PUBLIC KalypssoHDF5Reader + PRIVATE VTK::CommonCore VTK::CommonDataModel VTK::IOCore VTK::IOGeometry VTK::IOXML + VTK::IOXMLParser) diff --git a/tools/ParaViewKalypssoPlugin/src/Testing/Cxx/TestKalypssoHDF5Reader.cxx b/tools/ParaViewKalypssoPlugin/src/Testing/Cxx/TestKalypssoHDF5Reader.cxx new file mode 100644 index 0000000..18c55b0 --- /dev/null +++ b/tools/ParaViewKalypssoPlugin/src/Testing/Cxx/TestKalypssoHDF5Reader.cxx @@ -0,0 +1,111 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +// #include +// #include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +int +vtkIOKalypssoCxxTests(int argc, char ** argv) +{ + std::string filein; + std::string varname; + + // double TimeStep = 4.2898e-05; + + vtksys::CommandLineArguments args; + args.Initialize(argc, argv); + args.AddArgument("-f", + vtksys::CommandLineArguments::SPACE_ARGUMENT, + &filein, + "(names of the kalypsso (HDF5) files to read)"); + args.AddArgument("-var", + vtksys::CommandLineArguments::SPACE_ARGUMENT, + &varname, + "(the name of the SCALAR variable to display)"); + + if (!args.Parse() || argc == 1 || filein.empty()) + { + std::cerr << "\nTestKalypssoHDF5Reader: \n" + << "options are:\n"; + std::cerr << args.GetHelp() << "\n"; + return EXIT_FAILURE; + } + + if (!vtksys::SystemTools::FileExists(filein.c_str())) + { + std::cerr << "\nFile " << filein.c_str() << " does not exist\n\n"; + return EXIT_FAILURE; + } + + vtkNew reader; + reader->DebugOff(); + reader->SetFileName(filein.c_str()); + reader->UpdateInformation(); + + for (auto i = 0; i < reader->GetNumberOfCellArrays(); i++) + std::cout << "found array (" << i << ") = " << reader->GetCellArrayName(i) << "\n"; + reader->DisableAllCellArrays(); + reader->SetCellArrayStatus("rho", 1); + reader->Update(); + + // just for cross-check + { + vtkNew producer; + producer->SetOutput(reader->GetOutput()); + + std::string filename = "./test_NonOverlappingAMR_2d.vth"; + vtkNew writer; + writer->SetInputConnection(producer->GetOutputPort()); + writer->SetFileName(filename.c_str()); + writer->Write(); + } + + // reader->UpdateTimeStep(TimeStep); // time value + // reader->Update(); + + // double range[2]; + + // if (varname.size()) + // { + // reader->GetOutput()->GetPointData()->GetArray(0)->GetRange(range); + // std::cerr << varname.c_str() << ": scalar range = [" << range[0] << ", " << range[1] << + // "]\n"; + // } + + + std::cout << *reader; + /* + VTK_CREATE(vtkDataSetWriter, writer); + writer->SetInputData(FirstBlock); + writer->SetFileTypeToBinary(); + writer->SetFileName("/tmp/foo.vtk"); + writer->Write(); + */ + + return EXIT_SUCCESS; +} + +// ================================================================= +// ================================================================= +int +main(int argc, char ** argv) +{ + const auto status = vtkIOKalypssoCxxTests(argc, argv); + return status; +} diff --git a/tools/ParaViewKalypssoPlugin/src/paraview.plugin b/tools/ParaViewKalypssoPlugin/src/paraview.plugin new file mode 100644 index 0000000..8ce02c0 --- /dev/null +++ b/tools/ParaViewKalypssoPlugin/src/paraview.plugin @@ -0,0 +1,8 @@ +NAME + pvKalypssoHDF5Reader +DESCRIPTION + Reader for visualization of Kalypsso HDF5 data +REQUIRES_MODULES + VTK::CommonCore + VTK::CommonExecutionModel + VTK::hdf5 diff --git a/tools/archive/Readme.md b/tools/archive/Readme.md new file mode 100644 index 0000000..a1ff29b --- /dev/null +++ b/tools/archive/Readme.md @@ -0,0 +1,13 @@ + + +# Make a tarball release that include git submodules + +You don't need to be at source top level to run this, from current directory is ok. + +```shell +./git_archive_all.py --force-submodules kalypsso-core-1.0.tar.gz +``` diff --git a/tools/archive/git_archive_all.py b/tools/archive/git_archive_all.py new file mode 100755 index 0000000..0ceca82 --- /dev/null +++ b/tools/archive/git_archive_all.py @@ -0,0 +1,671 @@ +#! /usr/bin/env python +# coding=utf-8 + +# The MIT License (MIT) +# +# Copyright (c) 2010 Ilya Kulakov +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +from __future__ import print_function +from __future__ import unicode_literals + +import logging +from os import environ, extsep, path, readlink +from subprocess import CalledProcessError, Popen, PIPE +import sys +import re + +__version__ = "1.23.1" + + +try: + # Python 3.2+ + from os import fsdecode +except ImportError: + def fsdecode(filename): + if not isinstance(filename, unicode): + return filename.decode(sys.getfilesystemencoding(), 'strict') + else: + return filename + +try: + # Python 3.2+ + from os import fsencode +except ImportError: + def fsencode(filename): + if not isinstance(filename, bytes): + return filename.encode(sys.getfilesystemencoding(), 'strict') + else: + return filename + + +def git_fsdecode(filename): + """ + Decode filename from git output into str. + """ + if sys.platform.startswith('win32'): + return filename.decode('utf-8') + else: + return fsdecode(filename) + + +def git_fsencode(filename): + """ + Encode filename from str into git input. + """ + if sys.platform.startswith('win32'): + return filename.encode('utf-8') + else: + return fsencode(filename) + + +try: + # Python 3.6+ + from os import fspath as _fspath + + def fspath(filename, decoder=fsdecode, encoder=fsencode): + """ + Convert filename into bytes or str, depending on what's the best type + to represent paths for current Python and platform. + """ + # Python 3.6+: str can represent any path (PEP 383) + # str is not required on Windows (PEP 529) + # Decoding is still applied for consistency and to follow PEP 519 recommendation. + return decoder(_fspath(filename)) +except ImportError: + def fspath(filename, decoder=fsdecode, encoder=fsencode): + # Python 3.4 and 3.5: str can represent any path (PEP 383), + # but str is required on Windows (no PEP 529) + # + # Python 2.6 and 2.7: str cannot represent any path (no PEP 383), + # str is required on Windows (no PEP 529) + # bytes is required on POSIX (no PEP 383) + if sys.version_info > (3,): + import pathlib + if isinstance(filename, pathlib.PurePath): + return str(filename) + else: + return decoder(filename) + elif sys.platform.startswith('win32'): + return decoder(filename) + else: + return encoder(filename) + + +def git_fspath(filename): + """ + fspath representation of git output. + """ + return fspath(filename, git_fsdecode, git_fsencode) + + +class GitArchiver(object): + """ + GitArchiver + + Scan a git repository and export all tracked files, and submodules. + Checks for .gitattributes files in each directory and uses 'export-ignore' + pattern entries for ignore files in the archive. + + >>> archiver = GitArchiver(main_repo_abspath='my/repo/path') + >>> archiver.create('output.zip') + """ + TARFILE_FORMATS = { + 'tar': 'w', + 'tbz2': 'w:bz2', + 'tgz': 'w:gz', + 'txz': 'w:xz', + 'bz2': 'w:bz2', + 'gz': 'w:gz', + 'xz': 'w:xz' + } + ZIPFILE_FORMATS = ('zip',) + + LOG = logging.getLogger('GitArchiver') + + def __init__(self, prefix='', exclude=True, force_sub=False, extra=None, main_repo_abspath=None, git_version=None): + """ + @param prefix: Prefix used to prepend all paths in the resulting archive. + Extra file paths are only prefixed if they are not relative. + E.g. if prefix is 'foo' and extra is ['bar', '/baz'] the resulting archive will look like this: + / + baz + foo/ + bar + + @param exclude: Determines whether archiver should follow rules specified in .gitattributes files. + + @param force_sub: Determines whether submodules are initialized and updated before archiving. + + @param extra: List of extra paths to include in the resulting archive. + + @param main_repo_abspath: Absolute path to the main repository (or one of subdirectories). + If given path is path to a subdirectory (but not a submodule directory!) it will be replaced + with abspath to top-level directory of the repository. + If None, current cwd is used. + + @param git_version: Version of Git that determines whether various workarounds are on. + If None, tries to resolve via Git's CLI. + """ + self._check_attr_gens = {} + self._ignored_paths_cache = {} + + if git_version is None: + git_version = self.get_git_version() + + if git_version is not None and git_version < (1, 6, 1): + raise ValueError("git of version 1.6.1 and higher is required") + + self.git_version = git_version + + if main_repo_abspath is None: + main_repo_abspath = path.abspath('') + elif not path.isabs(main_repo_abspath): + raise ValueError("main_repo_abspath must be an absolute path") + + self.main_repo_abspath = self.resolve_git_main_repo_abspath(main_repo_abspath) + + self.prefix = fspath(prefix) + self.exclude = exclude + self.extra = [fspath(e) for e in extra] if extra is not None else [] + self.force_sub = force_sub + + def create(self, output_path, dry_run=False, output_format=None, compresslevel=None): + """ + Create the archive at output_file_path. + + Type of the archive is determined either by extension of output_file_path or by output_format. + Supported formats are: gz, zip, bz2, xz, tar, tgz, txz + + @param output_path: Output file path. + + @param dry_run: Determines whether create should do nothing but print what it would archive. + + @param output_format: Determines format of the output archive. If None, format is determined from extension + of output_file_path. + + @param compresslevel: Optional compression level. Interpretation depends on the output format. + """ + output_path = fspath(output_path) + + if output_format is None: + file_name, file_ext = path.splitext(output_path) + output_format = file_ext[len(extsep):].lower() + self.LOG.debug("Output format is not explicitly set, determined format is {0}.".format(output_format)) + + if not dry_run: + if output_format in self.ZIPFILE_FORMATS: + from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED + + if compresslevel is not None: + if sys.version_info > (3, 7): + archive = ZipFile(path.abspath(output_path), 'w', compresslevel=compresslevel) + else: + raise ValueError("Compression level for zip archives requires Python 3.7+") + else: + archive = ZipFile(path.abspath(output_path), 'w') + + def add_file(file_path, arcname): + if not path.islink(file_path): + archive.write(file_path, arcname, ZIP_DEFLATED) + else: + i = ZipInfo(arcname) + i.create_system = 3 + i.external_attr = 0xA1ED0000 + archive.writestr(i, readlink(file_path)) + elif output_format in self.TARFILE_FORMATS: + import tarfile + + mode = self.TARFILE_FORMATS[output_format] + + if compresslevel is not None: + try: + archive = tarfile.open(path.abspath(output_path), mode, compresslevel=compresslevel) + except TypeError: + raise ValueError("{0} cannot be compressed".format(output_format)) + else: + archive = tarfile.open(path.abspath(output_path), mode) + + def add_file(file_path, arcname): + archive.add(file_path, arcname) + else: + raise ValueError("unknown format: {0}".format(output_format)) + + def archiver(file_path, arcname): + self.LOG.debug(fspath("{0} => {1}").format(file_path, arcname)) + add_file(file_path, arcname) + else: + archive = None + + def archiver(file_path, arcname): + self.LOG.info(fspath("{0} => {1}").format(file_path, arcname)) + + self.archive_all_files(archiver) + + if archive is not None: + archive.close() + + def is_file_excluded(self, repo_abspath, repo_file_path): + """ + Checks whether file at a given path is excluded. + + @param repo_abspath: Absolute path to the git repository. + + @param repo_file_path: Path to a file relative to repo_abspath. + + @return: True if file should be excluded. Otherwise False. + """ + if not self.exclude: + return False + + cache = self._ignored_paths_cache.setdefault(repo_abspath, {}) + + if repo_file_path not in cache: + next(self._check_attr_gens[repo_abspath]) + attrs = self._check_attr_gens[repo_abspath].send(repo_file_path) + export_ignore_attr = attrs['export-ignore'] + + if export_ignore_attr == b'set': + cache[repo_file_path] = True + elif export_ignore_attr == b'unset': + cache[repo_file_path] = False + else: + repo_file_dir_path = path.dirname(repo_file_path) + + if repo_file_dir_path: + cache[repo_file_path] = self.is_file_excluded(repo_abspath, repo_file_dir_path) + else: + cache[repo_file_path] = False + + return cache[repo_file_path] + + def archive_all_files(self, archiver): + """ + Archive all files using archiver. + + @param archiver: Callable that accepts 2 arguments: + abspath to file on the system and relative path within archive. + """ + for file_path in self.extra: + archiver(path.abspath(file_path), path.join(self.prefix, file_path)) + + for file_path in self.walk_git_files(): + archiver(path.join(self.main_repo_abspath, file_path), path.join(self.prefix, file_path)) + + def walk_git_files(self, repo_path=fspath('')): + """ + An iterator method that yields a file path relative to main_repo_abspath + for each file that should be included in the archive. + Skips those that match the exclusion patterns found in + any discovered .gitattributes files along the way. + + Recurs into submodules as well. + + @param repo_path: Path to the git submodule repository relative to main_repo_abspath. + + @return: Generator to traverse files under git control relative to main_repo_abspath. + """ + repo_abspath = path.join(self.main_repo_abspath, fspath(repo_path)) + assert repo_abspath not in self._check_attr_gens + self._check_attr_gens[repo_abspath] = self.check_git_attr(repo_abspath, ['export-ignore']) + + try: + repo_file_paths = self.list_repo_files(repo_abspath) + + for repo_file_path in repo_file_paths: + repo_file_abspath = path.join(repo_abspath, repo_file_path) # absolute file path + main_repo_file_path = path.join(repo_path, repo_file_path) # relative to main_repo_abspath + + if not path.islink(repo_file_abspath) and path.isdir(repo_file_abspath): + continue + + if self.is_file_excluded(repo_abspath, repo_file_path): + continue + + yield main_repo_file_path + + if self.force_sub: + self.run_git_shell('git submodule init', repo_abspath) + self.run_git_shell('git submodule update', repo_abspath) + + for repo_submodule_path in self.list_repo_submodules(repo_abspath): # relative to repo_path + if self.is_file_excluded(repo_abspath, repo_submodule_path): + continue + + main_repo_submodule_path = path.join(repo_path, repo_submodule_path) # relative to main_repo_abspath + + for main_repo_submodule_file_path in self.walk_git_files(main_repo_submodule_path): + repo_submodule_file_path = path.relpath(main_repo_submodule_file_path, repo_path) # relative to repo_path + + if self.is_file_excluded(repo_abspath, repo_submodule_file_path): + continue + + yield main_repo_submodule_file_path + finally: + self._check_attr_gens[repo_abspath].close() + del self._check_attr_gens[repo_abspath] + + def check_git_attr(self, repo_abspath, attrs): + """ + Generator that returns git attributes for received paths relative to repo_abspath. + + >>> archiver = GitArchiver(...) + >>> g = archiver.check_git_attr('repo_path', ['export-ignore']) + >>> next(g) + >>> attrs = g.send('relative_path') + >>> print(attrs['export-ignore']) + + @param repo_abspath: Absolute path to a git repository. + + @param attrs: Attributes to check + """ + def make_process(): + env = dict(environ, GIT_FLUSH='1') + cmd = 'git check-attr --stdin -z {0}'.format(' '.join(attrs)) + return Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, cwd=repo_abspath, env=env) + + def read_attrs(process, repo_file_path): + process.stdin.write(repo_file_path + b'\0') + process.stdin.flush() + + # For every attribute check-attr will output: NUL NUL NUL + path, attr, info = b'', b'', b'' + nuls_count = 0 + nuls_expected = 3 * len(attrs) + + while nuls_count != nuls_expected: + b = process.stdout.read(1) + + if b == b'' and process.poll() is not None: + raise RuntimeError("check-attr exited prematurely") + elif b == b'\0': + nuls_count += 1 + + if nuls_count % 3 == 0: + yield path, attr, info + + path, attr, info = b'', b'', b'' + elif nuls_count % 3 == 0: + path += b + elif nuls_count % 3 == 1: + attr += b + elif nuls_count % 3 == 2: + info += b + + def read_attrs_old(process, repo_file_path): + """ + Compatibility with versions 1.8.5 and below that do not recognize -z for output. + """ + process.stdin.write(repo_file_path + b'\0') + process.stdin.flush() + + # For every attribute check-attr will output: : : \n + # where is c-quoted + + path, attr, info = b'', b'', b'' + lines_count = 0 + lines_expected = len(attrs) + + while lines_count != lines_expected: + line = process.stdout.readline() + + info_start = line.rfind(b': ') + if info_start == -1: + raise RuntimeError("unexpected output of check-attr: {0}".format(line)) + + attr_start = line.rfind(b': ', 0, info_start) + if attr_start == -1: + raise RuntimeError("unexpected output of check-attr: {0}".format(line)) + + path = line[:attr_start] + attr = line[attr_start + 2:info_start] # trim leading ": " + info = line[info_start + 2:len(line) - 1] # trim leading ": " and trailing \n + yield path, attr, info + + lines_count += 1 + + if not attrs: + return + + process = make_process() + + if self.git_version is None or self.git_version > (1, 8, 5): + reader = read_attrs + else: + reader = read_attrs_old + + try: + while True: + repo_file_path = yield + repo_file_path = git_fsencode(fspath(repo_file_path)) + repo_file_attrs = {} + + for path, attr, value in reader(process, repo_file_path): + attr = attr.decode('utf-8') + repo_file_attrs[attr] = value + + yield repo_file_attrs + finally: + process.stdin.close() + process.wait() + + def resolve_git_main_repo_abspath(self, abspath): + """ + Return absolute path to the repo for a given path. + """ + try: + main_repo_abspath = self.run_git_shell('git rev-parse --show-toplevel', cwd=abspath).rstrip() + return path.abspath(git_fspath(main_repo_abspath)) + except CalledProcessError as e: + raise ValueError("{0} is not part of a git repository ({1})".format(abspath, e.returncode)) + + @classmethod + def run_git_shell(cls, cmd, cwd=None): + """ + Run git shell command, read output and decode it into a unicode string. + + @param cmd: Command to be executed. + + @param cwd: Working directory. + + @return: Output of the command. + + @raise CalledProcessError: Raises exception if return code of the command is non-zero. + """ + p = Popen(cmd, shell=True, stdout=PIPE, cwd=cwd) + output, _ = p.communicate() + + if p.returncode: + if sys.version_info > (2, 6): + raise CalledProcessError(returncode=p.returncode, cmd=cmd, output=output) + else: + raise CalledProcessError(returncode=p.returncode, cmd=cmd) + + return output + + @classmethod + def get_git_version(cls): + """ + Return version of git current shell points to. + + If version cannot be parsed None is returned. + """ + try: + output = cls.run_git_shell('git version') + except CalledProcessError: + cls.LOG.warning("Unable to get Git version.") + return None + + try: + version = output.split()[2] + except IndexError: + cls.LOG.warning("Unable to parse Git version \"%s\".", output) + return None + + try: + return tuple(int(v) if v.isdigit() else 0 for v in version.split(b'.')) + except ValueError: + cls.LOG.warning("Unable to parse Git version \"%s\".", version) + return None + + @classmethod + def list_repo_files(cls, repo_abspath): + """ + Return a list of all files as seen by git in a given repo. + """ + repo_file_paths = cls.run_git_shell( + 'git ls-files -z --cached --full-name --no-empty-directory', + cwd=repo_abspath + ) + repo_file_paths = repo_file_paths.split(b'\0')[:-1] + + if sys.platform.startswith('win32'): + repo_file_paths = (git_fspath(p.replace(b'/', b'\\')) for p in repo_file_paths) + else: + repo_file_paths = map(git_fspath, repo_file_paths) + + return repo_file_paths + + @classmethod + def list_repo_submodules(cls, repo_abspath): + """ + Return a list of all direct submodules as seen by git in a given repo. + """ + if sys.platform.startswith('win32'): + shell_command = 'git submodule foreach --quiet "\\"{0}\\" -c \\"from __future__ import print_function; print(\'"$sm_path"\', end=chr(0))\\""' + else: + shell_command = 'git submodule foreach --quiet \'"{0}" -c "from __future__ import print_function; print(\\"$sm_path\\", end=chr(0))"\'' + + python_exe = sys.executable or 'python' + shell_command = shell_command.format(python_exe) + + repo_submodule_paths = cls.run_git_shell(shell_command, cwd=repo_abspath) + repo_submodule_paths = repo_submodule_paths.split(b'\0')[:-1] + + if sys.platform.startswith('win32'): + repo_submodule_paths = (git_fspath(p.replace(b'/', b'\\')) for p in repo_submodule_paths) + else: + repo_submodule_paths = map(git_fspath, repo_submodule_paths) + + return repo_submodule_paths + + +def main(argv=None): + if argv is None: + argv = sys.argv + + from optparse import OptionParser, SUPPRESS_HELP + + parser = OptionParser( + usage="usage: %prog [-v] [-C BASE_REPO] [--prefix PREFIX] [--no-export-ignore]" + " [--force-submodules] [--include EXTRA1 ...] [--dry-run] [-0 | ... | -9] OUTPUT_FILE", + version="%prog {0}".format(__version__) + ) + + parser.add_option('--prefix', + type='string', + dest='prefix', + default=None, + help="""prepend PREFIX to each filename in the archive; + defaults to OUTPUT_FILE name""") + + parser.add_option('-C', + type='string', + dest='base_repo', + default=None, + help="""use BASE_REPO as the main git repository to archive; + defaults to the current directory when empty""") + + parser.add_option('-v', '--verbose', + action='store_true', + dest='verbose', + help='enable verbose mode') + + parser.add_option('--no-export-ignore', '--no-exclude', + action='store_false', + dest='exclude', + default=True, + help="ignore the [-]export-ignore attribute in .gitattributes") + + parser.add_option('--force-submodules', + action='store_true', + dest='force_sub', + help='force `git submodule init && git submodule update` at each level before iterating submodules') + + parser.add_option('--include', '--extra', + action='append', + dest='extra', + default=[], + help="additional files to include in the archive") + + parser.add_option('--dry-run', + action='store_true', + dest='dry_run', + help="show files to be archived without actually creating the archive") + + for i in range(10): + parser.add_option('-{0}'.format(i), + action='store_const', + const=i, + dest='compresslevel', + help=SUPPRESS_HELP) + + options, args = parser.parse_args(argv[1:]) + + if len(args) != 1: + parser.error("You must specify exactly one output file") + + output_file_path = args[0] + + if path.isdir(output_file_path): + parser.error("You cannot use directory as output") + + # avoid tarbomb + if options.prefix is not None: + options.prefix = path.join(options.prefix, '') + else: + output_name = path.basename(output_file_path) + output_name = re.sub( + '(\\.zip|\\.tar|\\.tbz2|\\.tgz|\\.txz|\\.bz2|\\.gz|\\.xz|\\.tar\\.bz2|\\.tar\\.gz|\\.tar\\.xz)$', + '', + output_name + ) or "Archive" + options.prefix = path.join(output_name, '') + + try: + handler = logging.StreamHandler(sys.stdout) + handler.setFormatter(logging.Formatter('%(message)s')) + GitArchiver.LOG.addHandler(handler) + GitArchiver.LOG.setLevel(logging.DEBUG if options.verbose else logging.INFO) + archiver = GitArchiver(options.prefix, + options.exclude, + options.force_sub, + options.extra, + path.abspath(options.base_repo) if options.base_repo is not None else None + ) + archiver.create(output_file_path, options.dry_run, compresslevel=options.compresslevel) + except Exception as e: + parser.exit(2, "{0}\n".format(e)) + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/pvpython/paraview_surface_plot_screenshot.py b/tools/pvpython/paraview_surface_plot_screenshot.py new file mode 100644 index 0000000..ae22952 --- /dev/null +++ b/tools/pvpython/paraview_surface_plot_screenshot.py @@ -0,0 +1,130 @@ +# +# Note: this script aimed to be run by pvbatch +# + +# -*- coding: utf-8 -*- + +import sys +import getopt +import os +import argparse + +#### import the simple module from the paraview +from paraview.simple import * + +def dump_screenshot(xdmf_filename, fieldname): + + # trace generated using paraview version 5.12.0 + #import paraview + #paraview.compatibility.major = 5 + #paraview.compatibility.minor = 12 + + #### disable automatic camera reset on 'Show' + paraview.simple._DisableFirstRenderCameraReset() + + # create a new 'XDMF Reader' + xmf_data = XDMFReader(registrationName='machin', FileNames=[xdmf_filename]) + + # get animation scene + animationScene1 = GetAnimationScene() + + # update animation scene based on data timesteps + animationScene1.UpdateAnimationUsingDataTimeSteps() + + # Properties modified on xmf_data + xmf_data.CellArrayStatus = [fieldname] + + # get active view + renderView1 = GetActiveViewOrCreate('RenderView') + + # show data in view + xmf_data_display = Show(xmf_data, renderView1, 'UnstructuredGridRepresentation') + + # trace defaults for the display properties. + xmf_data_display.Representation = 'Surface' + + # reset view to fit data + renderView1.ResetCamera(False, 0.9) + + #changing interaction mode based on data extents + renderView1.InteractionMode = '2D' + renderView1.CameraPosition = [0.5, 0.5, 3.35] + renderView1.CameraFocalPoint = [0.5, 0.5, 0.0] + + # get the material library + materialLibrary1 = GetMaterialLibrary() + + # show color bar/color legend + xmf_data_display.SetScalarBarVisibility(renderView1, True) + + # update the view to ensure updated data information + renderView1.Update() + + # get color transfer function/color map for fieldname + field_LUT = GetColorTransferFunction(fieldname) + + # get opacity transfer function/opacity map for fieldname + field_PWF = GetOpacityTransferFunction(fieldname) + + # get 2D transfer function for fieldname + field_TF2D = GetTransferFunction2D(fieldname) + + # Hide orientation axes + renderView1.OrientationAxesVisibility = 0 + + # hide color bar/color legend + xmf_data_display.SetScalarBarVisibility(renderView1, False) + + # get layout + layout1 = GetLayout() + + # layout/tab size in pixels + layout1.SetSize(1570, 1054) + + # current camera placement for renderView1 + renderView1.InteractionMode = '2D' + renderView1.CameraPosition = [0.5, 0.5, 3.35] + renderView1.CameraFocalPoint = [0.5, 0.5, 0.0] + renderView1.CameraParallelScale = 0.7071067811865476 + + # save screenshot + base_name, _ = os.path.splitext(xdmf_filename) + png_filename = base_name + ".png" + SaveScreenshot(filename=png_filename, viewOrLayout=renderView1, location=16, ImageResolution=[1570, 1054], TransparentBackground=1) + + #================================================================ + # addendum: following script captures some of the application + # state to faithfully reproduce the visualization during playback + #================================================================ + + #-------------------------------- + # saving layout sizes for layouts + + # layout/tab size in pixels + layout1.SetSize(1570, 1054) + + #----------------------------------- + # saving camera placements for views + + # current camera placement for renderView1 + renderView1.InteractionMode = '2D' + renderView1.CameraPosition = [0.5, 0.5, 3.35] + renderView1.CameraFocalPoint = [0.5, 0.5, 0.0] + renderView1.CameraParallelScale = 0.7071067811865476 + + +############################################################################### +if __name__ == "__main__": + '''Dump a surface plot into a PNG image. + + Example of use: + pvbatch paraview_surface_plot_screenshot.py --filename data.xmf --field rho + ''' + + + parser = argparse.ArgumentParser(description='Save surface plot into PNG image file.') + parser.add_argument('--filename', type=str, default='data.xmf', help='kalypsso output data file in XDFM+HDF5 format') + parser.add_argument('--field', type=str, default='rho', help='field name to display (e.g. rho)') + args = parser.parse_args() + + dump_screenshot(args.filename, args.field) diff --git a/tools/pvpython/paraview_surface_plot_screenshot_3d.py b/tools/pvpython/paraview_surface_plot_screenshot_3d.py new file mode 100644 index 0000000..61063ee --- /dev/null +++ b/tools/pvpython/paraview_surface_plot_screenshot_3d.py @@ -0,0 +1,147 @@ +# +# Note: this script aimed to be run by pvbatch +# + +# -*- coding: utf-8 -*- + +import sys +import getopt +import os +import argparse + +#### import the simple module from the paraview +from paraview.simple import * + +def dump_screenshot(xdmf_filename, fieldname): + + # trace generated using paraview version 5.12.0 + #import paraview + #paraview.compatibility.major = 5 + #paraview.compatibility.minor = 12 + + #### disable automatic camera reset on 'Show' + paraview.simple._DisableFirstRenderCameraReset() + + # create a new 'Xdmf3 Reader S' + xmf_data = XDMFReader(registrationName='my_data', FileNames=[xdmf_filename]) + + # Properties modified on xmf_data + xmf_data.CellArrayStatus = [fieldname] + + # get active view + renderView1 = GetActiveViewOrCreate('RenderView') + + # show data in view + xmf_dataDisplay = Show(xmf_data, renderView1, 'UnstructuredGridRepresentation') + + # trace defaults for the display properties. + xmf_dataDisplay.Representation = 'Surface' + + # reset view to fit data + renderView1.ResetCamera(False, 0.9) + + # get the material library + materialLibrary1 = GetMaterialLibrary() + + # Hide orientation axes + renderView1.OrientationAxesVisibility = 0 + + # show color bar/color legend + xmf_dataDisplay.SetScalarBarVisibility(renderView1, True) + + # update the view to ensure updated data information + renderView1.Update() + + # get color transfer function/color map for 'fieldname' + fieldnameLUT = GetColorTransferFunction(fieldname) + + # get color legend/bar for fieldnameLUT in view renderView1 + fieldnameLUTColorBar = GetScalarBar(fieldnameLUT, renderView1) + + # change scalar bar placement + fieldnameLUTColorBar.Position = [0.8883429204621378, 0.8430895725424731] + fieldnameLUTColorBar.ScalarBarLength = 0.3300000000000011 + + # get opacity transfer function/opacity map for 'fieldname' + fieldnamePWF = GetOpacityTransferFunction(fieldname) + + # get 2D transfer function for 'fieldname' + fieldnameTF2D = GetTransferFunction2D(fieldname) + + # create a new 'Clip' + clip1 = Clip(registrationName='Clip1', Input=xmf_data) + + # show data in view + clip1Display = Show(clip1, renderView1, 'UnstructuredGridRepresentation') + + # trace defaults for the display properties. + clip1Display.Representation = 'Surface' + + # hide data in view + Hide(xmf_data, renderView1) + + # show color bar/color legend + clip1Display.SetScalarBarVisibility(renderView1, True) + + # update the view to ensure updated data information + renderView1.Update() + + # set active source + SetActiveSource(xmf_data) + + # toggle interactive widget visibility (only when running from the GUI) + HideInteractiveWidgets(proxy=clip1.ClipType) + + # get layout + layout1 = GetLayout() + + # layout/tab size in pixels + layout1.SetSize(1090, 995) + + # current camera placement for renderView1 + renderView1.CameraPosition = [5.754480303226986, -2.973217941582438, 1.6824212321685303] + renderView1.CameraViewUp = [-0.22826967748316762, 0.10585123215171684, 0.9678266740453552] + renderView1.CameraParallelScale = 1.7320508075688772 + + #================================================================ + # addendum: following script captures some of the application + # state to faithfully reproduce the visualization during playback + #================================================================ + + #-------------------------------- + # saving layout sizes for layouts + + # layout/tab size in pixels + layout1.SetSize(1090, 995) + + #----------------------------------- + # saving camera placements for views + + # current camera placement for renderView1 + renderView1.CameraPosition = [5.754480303226986, -2.973217941582438, 1.6824212321685303] + renderView1.CameraViewUp = [-0.22826967748316762, 0.10585123215171684, 0.9678266740453552] + renderView1.CameraParallelScale = 1.7320508075688772 + + # save screenshot + base_name, _ = os.path.splitext(xdmf_filename) + png_filename = base_name + ".png" + SaveScreenshot(filename=png_filename, viewOrLayout=renderView1, location=16, ImageResolution=[1570, 1054], + TransparentBackground=1, + SaveInBackground=1, + EmbedParaViewState=1) + +############################################################################### +if __name__ == "__main__": + '''Dump 3d data as a surface plot into a PNG image. + + Example of use: + pvbatch paraview_surface_plot_screenshot_3d.py --filename data.xmf --field rho + ''' + + + parser = argparse.ArgumentParser(description='Save surface plot into PNG image file.') + parser.add_argument('--filename', type=str, default='data.xmf', help='kalypsso output data file in XDFM+HDF5 format') + parser.add_argument('--field', type=str, default='rho', help='field name to display (e.g. rho)') + args = parser.parse_args() + + dump_screenshot(args.filename, args.field) diff --git a/tools/xdmf/create_xdmf.py b/tools/xdmf/create_xdmf.py new file mode 100755 index 0000000..887c7f2 --- /dev/null +++ b/tools/xdmf/create_xdmf.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python + +# -*- coding: utf-8 -*- + +# SPDX-FileCopyrightText: 2025 kalypsso contributors +# +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +import sys, getopt, os +import argparse +import math + +from lxml import etree + +import h5py + +"""Helper tools for manipulating kalypsso's HDF5 output. + +The original behavior of kalypsso was to offer the user the possibility to save both +simulation data and mesh data (node coordinates and node connectivity). Here we provide +the possibility to recreate the mesh data and xdmf from a kalypsso hdf5 output that +contains only the AMR orchard keys. This will make hdf5 output lighter as it avoids +saving mesh node coordinates and mesh node connectivity data in hdf5 outputs. +""" + +class Hdf5Properties: + """Utility class for reading kalypsso hdf5 output files properties.""" + + def __init__(self, hdf5_name): + self.h5file = h5py.File(hdf5_name) + self.block_sizes = self.h5file["amr"].attrs["block_sizes"] + self.dim = self.block_sizes.size + self.keys = list(self.h5file.keys()) + + def dim(self): + return self.dim + + def time(self): + return self.h5file["run"].attrs["time"] + + def iteration(self): + return self.h5file["run"].attrs["iteration"] + + def num_leaves(self): + return self.h5file["amr"]["keys"].size + + def num_cells_per_leaf(self): + return math.prod(self.block_sizes) + + def num_nodes_per_leaf(self): + return math.prod(self.block_sizes+1) + + def global_num_cells(self): + return self.num_cells_per_leaf() * self.num_leaves() + + def connectivity_shape(self): + return self.h5file["unstructured_mesh"]["connectivity"].shape + + def coordinates_shape(self): + return self.h5file["unstructured_mesh"]["coordinates"].shape + + def are_mesh_data_available(self): + for k in self.keys: + if k == "unstructured_mesh": + return True + return False + +def get_number_type_str(data_typename): + if data_typename == "int32" or data_typename == "int": + return "Int", 4 + elif data_typename == "uint32" or data_typename == "uint": + return "UInt", 4 + elif data_typename == "uint64": + return "UInt", 8 + elif data_typename == "float32" or data_typename == "float": + return "Float", 4 + elif data_typename == "float64" or data_typename == "double": + return "Float", 8 + else: + return "Unknown", 0 + +def are_mesh_data_available(hdf5_name): + """Return true if mesh data (coordinates and connectivity) are available in hdf5 file. + """ + + # check that hdf5 file actually exists + if not os.path.exists(hdf5_name): + print("File {} doesn't exist !".format(hdf5_name)) + return False + else: + h5prop = Hdf5Properties(hdf5_name) + avail = h5prop.are_mesh_data_available() + print("Checking if mesh data are available {} : {}".format(hdf5_name,avail)) + return avail + +def create_xdmf(hdf5_name, mesh_data_are_in_separate_file): + '''Given an kalypsso output as a hdf5 file, (re-)create associated xdmf file (for reading in ParaView). + ''' + + # check that hdf5 file actually exists + if not os.path.exists(hdf5_name): + print("File {} doesn't exist !".format(hdf5_name)) + else: + print("Creating xdmf for {}".format(hdf5_name)) + + h5prop = Hdf5Properties(hdf5_name) + + base_name = os.path.basename(os.path.splitext(hdf5_name)[0]) + xmf_name = base_name+'.xmf' + + xmf = etree.Element('Xdmf') + xmf.set("Version", "2.0") + domain = etree.SubElement(xmf, "Domain") + + grid = etree.SubElement(domain, "Grid") + grid.set("Name", base_name) + grid.set("GridType", "Uniform") + + time = etree.SubElement(grid, "Time") + time.set("TimeType", "Single") + time.set("Value", "{}".format(h5prop.time())) + + topology = etree.SubElement(grid, "Topology") + topology.set("TopologyType", "Quadrilateral") + topology.set("NumberOfElements", "{}".format(h5prop.global_num_cells())) + + dataitem = etree.SubElement(topology, "DataItem") + dataitem.set("Dimensions", "{} {}".format(h5prop.connectivity_shape()[0], h5prop.connectivity_shape()[1])) + dataitem.set("DataType", "Int") + dataitem.set("Format", "HDF") + dataitem.text = base_name+'.h5'+':/unstructured_mesh/connectivity' + + geometry = etree.SubElement(grid, "Geometry") + geometry.set("GeometryType", "XYZ") + dataitem_g = etree.SubElement(geometry, "DataItem") + dataitem_g.set("Dimensions", "{} {}".format(h5prop.coordinates_shape()[0], h5prop.coordinates_shape()[1])) + dataitem_g.set("NumberType", "Float") + dataitem_g.set("Precision", "4") + dataitem_g.set("Format", "HDF") + dataitem_g.text = base_name+'.h5'+':/unstructured_mesh/coordinates' + + for item in h5prop.h5file["celldata"].keys(): + #print("{}".format(item)) + attr = etree.SubElement(grid, "Attribute") + attr.set("Name", item) + attr.set("AttributeType", "Scalar") + attr.set("Center", "Cell") + ditem = etree.SubElement(attr, "DataItem") + data_typename = h5prop.h5file["celldata"][item].dtype.name + number_type, precision = get_number_type_str(data_typename) + ditem.set("NumberType", "{}".format(number_type)) + ditem.set("Precision", "{}".format(precision)) + ditem.set("Format", "HDF") + ditem.set("Dimensions", "{}".format(h5prop.h5file["celldata"][item].shape[0])) + ditem.text = base_name+'.h5'+':/celldata/'+item + + doc = etree.ElementTree(xmf) + etree.indent(doc, space=" ") + doc.write(file=xmf_name, xml_declaration=True, + encoding='UTF-8', doctype='') + + +def create_mesh_data(hdf5_name): + # TODO + return 0 + +############################################################################### +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description='Create xdmf file for kalypsso hdf5 output.') + parser.add_argument('--hdf5', type=str, default='test_blast_2d_iter0000000.h5', help='hdf5 file name') + args = parser.parse_args() + + # should we create mesh data (in a separate hdf5 file) + if are_mesh_data_available(args.hdf5): + mesh_data_are_in_separate_file = False + create_xdmf(args.hdf5, mesh_data_are_in_separate_file) + else: + # create mesh data + #create_mesh_data(args.hdf5) + mesh_data_are_in_separate_file = True + create_xdmf(args.hdf5, mesh_data_are_in_separate_file) diff --git a/unit_test/CMakeLists.txt b/unit_test/CMakeLists.txt new file mode 100644 index 0000000..892f035 --- /dev/null +++ b/unit_test/CMakeLists.txt @@ -0,0 +1,138 @@ +# ################################################################################################## +# googletest (for unit testing) +# ################################################################################################## +include(FetchContent) +FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + # GIT_REPOSITORY git@github.com:google/googletest.git + GIT_TAG release-1.12.0 + SOURCE_DIR ${CMAKE_SOURCE_DIR}/external/googletest) +FetchContent_MakeAvailable(googletest) + +# make googletest cmake macros available +include(GoogleTest) + +set(TEST_USE_MPI_ON ON) +set(TEST_USE_MPI_OFF OFF) + +# ################################################################################################## +# +# function to simplify creating a unit test +# +# Parameters: * unit_test_name : the executable test name * test_use_mpi : boolean value to +# indicate if test uses MPI * all other arg are expected to be source files +# +# ################################################################################################## +function(kalypsso_add_test unit_test_name test_use_mpi) + add_executable(${unit_test_name} "") + target_sources(${unit_test_name} PRIVATE ${ARGN} kalypsso_unittest_utils.h) + + target_include_directories(${unit_test_name} PUBLIC ${PROJECT_SOURCE_DIR}/unit_test) + target_link_libraries( + ${unit_test_name} + PUBLIC kalypsso::config + Kokkos::kokkos + dl + kalypsso::monitoring + kalypsso::core + kalypsso::AMRtest + gtest) + + if(KALYPSSO_CORE_USE_MPI) + target_link_libraries(${unit_test_name} PUBLIC kalypsso::mpiUtils) + endif() + + # when test_use_mpi is ON, run the test with ${KALYPSSO_TEST_MPIEXEC_NUMPROCS} MPI processes + # KALYPSSO_TEST_MPIEXEC_NUMPROCS is defined in test/CMakeLists.txt; default value is currently 2 + if(KALYPSSO_CORE_USE_MPI) + if(${test_use_mpi}) + add_test( + NAME "${unit_test_name}_MPI" + COMMAND cmake -E env OMP_NUM_THREADS=4 ${MPIEXEC_EXECUTABLE} ${MPIEXEC_NUMPROC_FLAG} + ${KALYPSSO_TEST_MPIEXEC_NUMPROCS} ./${unit_test_name} + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/unit_test/) + else() + # test doesn't require MPI, so just use a single MPI process + add_test( + NAME "${unit_test_name}_MPI" + COMMAND cmake -E env OMP_NUM_THREADS=4 ${MPIEXEC_EXECUTABLE} ${MPIEXEC_NUMPROC_FLAG} 1 + ./${unit_test_name} + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/unit_test/) + endif() + else() + add_test( + NAME ${unit_test_name} + COMMAND cmake -E env OMP_NUM_THREADS=4 ./${unit_test_name} + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/unit_test/) + endif() + +endfunction(kalypsso_add_test) + +# ################################################################################################## +# +# here we create our tests +# +# ################################################################################################## + +# +# core +# +kalypsso_add_test(geometry_utils_test TEST_USE_MPI_OFF core/geometry_utils_test.cpp main.cpp) + +kalypsso_add_test(morton_utils_test TEST_USE_MPI_OFF core/morton_utils_test.cpp main.cpp) + +kalypsso_add_test(orchard_key_test TEST_USE_MPI_OFF core/orchard_key_test.cpp main.cpp) + +kalypsso_add_test(BitFieldInteger_test TEST_USE_MPI_OFF core/BitFieldInteger_test.cpp main.cpp) + +kalypsso_add_test(kalypsso_data_container_test TEST_USE_MPI_OFF + core/kalypsso_data_container_test.cpp main.cpp) + +kalypsso_add_test(DataArrayBlock_utils_test TEST_USE_MPI_OFF core/DataArrayBlock_utils_test.cpp + main.cpp) + +kalypsso_add_test(Kokkos_Array_extensions_test TEST_USE_MPI_OFF + core/Kokkos_Array_extensions_test.cpp main.cpp) + +kalypsso_add_test(mesh_utils_test TEST_USE_MPI_OFF core/mesh_utils_test.cpp main.cpp) + +kalypsso_add_test(EdgeDataArrayBlock_utils_test TEST_USE_MPI_OFF + core/EdgeDataArrayBlock_utils_test.cpp main.cpp) + +kalypsso_add_test(StencilHelper_test TEST_USE_MPI_OFF core/StencilHelper_test.cpp main.cpp) + +configure_file(${PROJECT_SOURCE_DIR}/test/AMRmesh/test_AMRmesh_brick_2d.ini StencilHelper_2d.ini + COPYONLY) +# configure_file(${PROJECT_SOURCE_DIR}/test/AMRmesh/test_AMRmesh_brick_3d.ini StencilHelper_3d.ini +# COPYONLY) +configure_file(StencilHelper_3d.ini StencilHelper_3d.ini COPYONLY) + +kalypsso_add_test(DataArrayBlockMultiVar_test TEST_USE_MPI_OFF core/DataArrayBlockMultiVar_test.cpp + main.cpp) + +kalypsso_add_test(MaterialPresence_test TEST_USE_MPI_OFF core/MaterialPresence_test.cpp main.cpp) + +kalypsso_add_test(interface_tracking_utils_test TEST_USE_MPI_OFF + core/vof/interface_tracking_utils_test.cpp main.cpp) +kalypsso_add_test(youngs_test TEST_USE_MPI_OFF core/vof/youngs_test.cpp main.cpp) + +# TODO: Must change "kalypsso_add_test" function because this explicitly requires MPI +# kalypsso_add_test(MaterialPresence_test_mpi TEST_USE_MPI_ON core/MaterialPresence_test_mpi.cpp +# main.cpp) + +# +# utils/config +# +kalypsso_add_test(ConfigMap_test TEST_USE_MPI_OFF utils/config/ConfigMap_test.cpp main.cpp) + +# +# utils/monitoring +# +kalypsso_add_test(HostTimer_test TEST_USE_MPI_OFF utils/monitoring/HostTimer_test.cpp main.cpp) + +# +# utils/mpi +# +kalypsso_add_test(ParallelEnv_test TEST_USE_MPI_ON utils/mpi/ParallelEnv_test.cpp + main_kalypsso_unittest.h main.cpp) diff --git a/unit_test/StencilHelper_3d.ini b/unit_test/StencilHelper_3d.ini new file mode 100644 index 0000000..1ba3f12 --- /dev/null +++ b/unit_test/StencilHelper_3d.ini @@ -0,0 +1,79 @@ +[run] +dimension=3 +tEnd=0.5 +nStepmax=10 + +# noutput equals -1 means we dump data at every time steps +nOutput=-1 + +[amr] +level_min=1 +level_max=3 + +use_block_data=yes +bx=4 +by=4 +bz=4 + +# init step may contain calls to globalRefine +# if the following option is true, we interleave +# calls to load balancing in between globalRefine +# if the following option is false, a single load +# balancing operation is done at the end of +# globalRefine steps +enable_load_balance_during_init=false + +epsilon_refine=0.01 +epsilon_coarsen=0.0025 + +connectivity = brick + +[p4est_connectivity] +nbrick_x=1 +nbrick_y=1 +nbrick_z=1 + +periodic_x=1 +periodic_y=1 +periodic_z=1 + +[mesh] +xmin=0.0 +ymin=0.0 +zmin=0.0 +scaling_factor=1.0 + +boundary_type_xmin=WALL +boundary_type_xmax=WALL + +boundary_type_ymin=WALL +boundary_type_ymax=WALL + +boundary_type_zmin=WALL +boundary_type_zmax=WALL + +[hydro] +gamma0=1.666 +cfl=0.8 +niter_riemann=10 +slope_type=2 +problem=blast +riemann=HLLC + +[blast] +density_in=1.0 +density_out=1.2 +radius=0.25 + +[output] +outputDir=./ +outputPrefix=test_hdf5 +outputVtkAscii=false +write_variables=rho,rho_vx +write_mesh_info=yes +vtk_enabled=no +hdf5_enabled=yes +write_iOct=yes + +[other] +implementationVersion=0 diff --git a/unit_test/core/BitFieldInteger_test.cpp b/unit_test/core/BitFieldInteger_test.cpp new file mode 100644 index 0000000..f517750 --- /dev/null +++ b/unit_test/core/BitFieldInteger_test.cpp @@ -0,0 +1,176 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// © Commissariat a l'Energie Atomique et aux Energies Alternatives (CEA) +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "kalypsso/core/BitFieldInteger.h" +#include "kalypsso/core/orchard_key.h" + +#include + +#include "gtest/gtest.h" + +namespace kalypsso +{ + +TEST(kalypsso_shared_test, BitFieldInteger) +{ + + class SomeData : public BitFieldInteger + { + public: + using BitFieldInteger::BitFieldInteger; + + DECLARE_CASTED_FIELD(f1, 0, 8, uint8_t) + DECLARE_CASTED_FIELD(f2, 8, 4, uint8_t) + DECLARE_CASTED_FIELD(f3, 12, 8, uint8_t) + DECLARE_CASTED_FIELD(f4, 20, 12, uint16_t) + }; + + uint32_t value = (1 << 8) + (1 << 12) + (1 << 13); + + auto v1 = SomeData::f1(value); + EXPECT_EQ(v1, 0) << "SomeData v1 failed"; + + auto v2 = SomeData::f2(value); + EXPECT_EQ(v2, 1) << "SomeData v2 failed"; + + auto v3 = SomeData::f3(value); + EXPECT_EQ(v3, 3) << "SomeData v3 failed"; + + // value = SomeData::cset_f1(value, 15); + SomeData::set_f1(value, 15); + auto v4 = SomeData::f1(value); + // fprintf(stderr, "[ ] v4 = %d | value = %ld | 0x%x\n", v4, value, value); + EXPECT_EQ(v4, 15) << "SomeData v4 failed"; + + SomeData::set_f2(value, 12); + auto v5 = SomeData::f2(value); + // fprintf(stderr, "[ ] v5 = %d, value = %ld | 0x%x\n", v5, value, value); + EXPECT_EQ(v5, 12) << "SomeData v5 failed"; + + auto v6 = SomeData::f1(value); + EXPECT_EQ(v6, 15) << "SomeData v6 failed"; + + SomeData::set_f3(value, 36); + auto v7 = SomeData::f3(value); + // fprintf(stderr, "[ ] v7 = %d, value = %ld | 0x%x\n", v7, value, value); + EXPECT_EQ(v7, 36) << "SomeData v7 failed"; + + value = 0x85301830; + + auto v8 = SomeData::f4(value); + EXPECT_EQ(v8, 2131) << "SomeData v8 failed"; + + v8++; + SomeData::set_f4(value, v8); + auto v9 = SomeData::f4(value); + EXPECT_EQ(v9, 2132) << "SomeData v9 failed"; + EXPECT_EQ(value, 0x85401830) << "SomeData v9 failed"; + +} // BitFieldInteger + +TEST(kalypsso_shared_test, BitFieldInteger2) +{ + + class SomeData2 : public BitFieldInteger + { + public: + using BitFieldInteger::BitFieldInteger; + + DECLARE_CASTED_FIELD(f1, 0, 12, uint16_t) + DECLARE_CASTED_FIELD(f2, 12, 8, uint8_t) + DECLARE_CASTED_FIELD(f3, 20, 20, uint32_t) + DECLARE_CASTED_FIELD(f4, 40, 24, uint32_t) + }; + + uint64_t value = (1LL << 8) + (1LL << 12) + (1LL << 13) + (1LL << 40); + + // fprintf(stderr, "value=%lu 0x%llx\n", value, value); + + auto v1 = SomeData2::f1(value); + EXPECT_EQ(v1, 256) << "SomeData2 v1 failed"; + + auto v2 = SomeData2::f2(value); + EXPECT_EQ(v2, 3) << "SomeData2 v2 failed"; + + auto v3 = SomeData2::f3(value); + EXPECT_EQ(v3, 0) << "SomeData2 v3 failed"; + + auto v31 = SomeData2::f4(value); + EXPECT_EQ(v31, 1) << "SomeData2 v31 failed"; + + // value = SomeData2::cset_f1(value, 15); + SomeData2::set_f1(value, 15); + auto v4 = SomeData2::f1(value); + // fprintf(stderr, "[ ] v4 = %d | value = %ld | 0x%llx\n", v4, value, value); + EXPECT_EQ(v4, 15) << "SomeData2 v4 failed"; + + SomeData2::set_f2(value, 12); + auto v5 = SomeData2::f2(value); + // fprintf(stderr, "[ ] v5 = %d, value = %ld | 0x%llx\n", v5, value, value); + EXPECT_EQ(v5, 12) << "SomeData2 v5 failed"; + + auto v6 = SomeData2::f1(value); + EXPECT_EQ(v6, 15) << "SomeData2 v6 failed"; + + SomeData2::set_f3(value, 36); + auto v7 = SomeData2::f3(value); + // fprintf(stderr, "[ ] v7 = %d, value = %ld | 0x%x\n", v7, value, value); + EXPECT_EQ(v7, 36) << "SomeData2 v7 failed"; + + value = 0x90000000000; + + auto v8 = SomeData2::f4(value); + EXPECT_EQ(v8, 9) << "SomeData2 v8 failed"; + + v8++; + SomeData2::set_f4(value, v8); + auto v9 = SomeData2::f4(value); + EXPECT_EQ(v9, 10) << "SomeData2 v9 failed"; + EXPECT_EQ(value, 0xA0000000000) << "SomeData2 v9 failed"; + +} // BitFieldInteger2 + +TEST(kalypsso_shared_test, Orchard) +{ + // class Orchard : public BitFieldInteger + // { + // public: + // using BitFieldInteger::BitFieldInteger; + // using key_t = uint64_t; + + // DECLARE_CASTED_FIELD(level, 0, 6, uint8_t) + // DECLARE_CASTED_FIELD(octant, 6, 44, uint64_t) + // DECLARE_CASTED_FIELD(tree, 50, 64, uint16_t) + // }; + + uint64_t value = (1LL << 11) + (1LL << 4); + + auto v1 = orchard_key_t<2>::level(value); + EXPECT_EQ(v1, 4) << "orchard_key_t<2> v1 failed"; + + auto v2 = orchard_key_t<2>::morton_octant(value); + EXPECT_EQ(v2, 8) << "orchard_key_t<2> v2 failed"; + + auto v3 = orchard_key_t<2>::morton_tree(value); + EXPECT_EQ(v3, 0) << "orchard_key_t<2> v3 failed"; + + Kokkos::Array tree_coord{ 0, 1 }; + Kokkos::Array octant_coord{ 2097152, 0 }; + // Kokkos::Array octant_coord{ 0, 0 }; + + uint16_t oct_lev = 4; + + uint64_t value2 = orchard_key_t<2>::encode_orchard(tree_coord, octant_coord, oct_lev); + + auto oct_lev2 = orchard_key_t<2>::level(value2); + // printf("value2=%llu value2=0x%llx | level = %llu\n", value2, value2, oct_lev2); + printf("value2=%lu value2=0x%lx | level = %d\n", value2, value2, oct_lev2); + + // uint64_t some_number = exponent2(40); + // printf("exponent2 : %llx\n", some_number); + +} // Orchard + +} // namespace kalypsso diff --git a/unit_test/core/DataArrayBlockMultiVar_test.cpp b/unit_test/core/DataArrayBlockMultiVar_test.cpp new file mode 100644 index 0000000..e98450f --- /dev/null +++ b/unit_test/core/DataArrayBlockMultiVar_test.cpp @@ -0,0 +1,423 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +#include "gtest/gtest.h" + +namespace kalypsso +{ + +// ================================================================================================ +// ================================================================================================ +void +run_data_array_block_multi_var_2d() +{ + constexpr int DIM = 2; + using DataArrayBlockMultiVar_t = DataArrayBlockMultiVar; + + const block_size_t block_size{ 3, 5 }; + const uint32_t num_octs_1 = 5; + Kokkos::View num_vars_1("Num vars per octant 1", num_octs_1); + + { + auto host_num_vars_1 = Kokkos::create_mirror_view(num_vars_1); + host_num_vars_1(0) = 2; // + host_num_vars_1(1) = 1; // +---+---+---+---+---+---+---+ + host_num_vars_1(2) = 0; // | 0 | 0 | 1 | 3 | 3 | 3 | 4 | + host_num_vars_1(3) = 3; // +---+---+---+---+---+---+---+ + host_num_vars_1(4) = 1; // + Kokkos::deep_copy(num_vars_1, host_num_vars_1); + } + + DataArrayBlockMultiVar_t data("data", block_size, num_vars_1); + + { // Metadata checks + auto storage = data.storage(); + auto offsets = data.offsets(); + + auto offsets_host = + DataArrayBlockMultiVar_t::Offsets_t::create_host_mirror_view_and_copy(offsets); + EXPECT_EQ(offsets.size(), num_octs_1 + 1); + EXPECT_EQ(offsets_host(0), 0); // <- always 0 + EXPECT_EQ(offsets_host(1), 2); + EXPECT_EQ(offsets_host(2), 3); + EXPECT_EQ(offsets_host(3), 3); + EXPECT_EQ(offsets_host(4), 6); + EXPECT_EQ(offsets_host(5), 7); // <- always the number of var blocks + EXPECT_EQ(storage.num_quadrants(), 7); + EXPECT_EQ(storage.num_vars(), 1); // <- always 1 + EXPECT_EQ(storage.num_cells(), Kokkos::dim_prod(block_size)); + EXPECT_EQ(storage.block_size(), block_size); + } + + { // Num vars + auto data_host = DataArrayBlockMultiVar_t::create_host_mirror_view_and_copy_offsets(data); + auto host_num_vars_1 = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, num_vars_1); + + for (uint32_t i_oct = 0; i_oct < host_num_vars_1.size(); i_oct++) + EXPECT_EQ(data_host.num_vars(i_oct), host_num_vars_1(i_oct)); + } + + { // Flat indices + auto data_host = DataArrayBlockMultiVar_t::create_host_mirror_view_and_copy(data); + + EXPECT_EQ(flat_mv_block_index(1, 0, data_host.offsets()), 1); + EXPECT_EQ(flat_mv_block_index(0, 1, data_host.offsets()), 2); + EXPECT_EQ(flat_mv_block_index(1, 3, data_host.offsets()), 4); + EXPECT_EQ(flat_mv_block_index(0, 4, data_host.offsets()), 6); + + MVBlockIndex_t ret; + + ret = flat_mv_block_index_unravel(1, data_host.offsets()); + EXPECT_EQ(ret[0], 1); + EXPECT_EQ(ret[1], 0); + + ret = flat_mv_block_index_unravel(2, data_host.offsets()); + EXPECT_EQ(ret[0], 0); + EXPECT_EQ(ret[1], 1); + + ret = flat_mv_block_index_unravel(3, data_host.offsets()); + EXPECT_EQ(ret[0], 0); + EXPECT_EQ(ret[1], 3); + + ret = flat_mv_block_index_unravel(5, data_host.offsets()); + EXPECT_EQ(ret[0], 2); + EXPECT_EQ(ret[1], 3); + + ret = flat_mv_block_index_unravel(6, data_host.offsets()); + EXPECT_EQ(ret[0], 0); + EXPECT_EQ(ret[1], 4); + + ret = flat_mv_block_index_unravel(1'000'000, data_host.offsets()); + EXPECT_EQ(ret[1], num_octs_1); + } + + const uint32_t num_octs_2 = num_octs_1 + 2; + Kokkos::View num_vars_2("Num vars per octant 2", num_octs_2); + + { + auto host_num_vars_2 = Kokkos::create_mirror_view(num_vars_2); + host_num_vars_2(0) = 0; // + host_num_vars_2(1) = 0; // +---+---+ + host_num_vars_2(2) = 2; // | 2 | 2 | + host_num_vars_2(3) = 0; // +---+---+ + host_num_vars_2(4) = 0; // + host_num_vars_2(5) = 0; // + host_num_vars_2(6) = 0; // + Kokkos::deep_copy(num_vars_2, host_num_vars_2); + } + + data.reorganize(num_vars_2); + + { // Metadata checks + auto storage = data.storage(); + auto offsets = data.offsets(); + + auto offsets_host = + DataArrayBlockMultiVar_t::Offsets_t::create_host_mirror_view_and_copy(offsets); + EXPECT_EQ(offsets.size(), num_octs_2 + 1); + EXPECT_EQ(offsets_host(0), 0); // <- always 0 + EXPECT_EQ(offsets_host(1), 0); + EXPECT_EQ(offsets_host(2), 0); + EXPECT_EQ(offsets_host(3), 2); + EXPECT_EQ(offsets_host(4), 2); + EXPECT_EQ(offsets_host(5), 2); + EXPECT_EQ(offsets_host(6), 2); + EXPECT_EQ(offsets_host(7), 2); // <- always the number of var blocks + EXPECT_EQ(storage.num_quadrants(), 2); + EXPECT_EQ(storage.num_vars(), 1); // <- always 1 + EXPECT_EQ(storage.num_cells(), Kokkos::dim_prod(block_size)); + EXPECT_EQ(storage.block_size(), block_size); + } + + { // Flat indices + auto data_host = DataArrayBlockMultiVar_t::create_host_mirror_view_and_copy(data); + + EXPECT_EQ(flat_mv_block_index(0, 2, data_host.offsets()), 0); + EXPECT_EQ(flat_mv_block_index(1, 2, data_host.offsets()), 1); + + MVBlockIndex_t ret; + + ret = flat_mv_block_index_unravel(0, data_host.offsets()); + EXPECT_EQ(ret[0], 0); + EXPECT_EQ(ret[1], 2); + + ret = flat_mv_block_index_unravel(1, data_host.offsets()); + EXPECT_EQ(ret[0], 1); + EXPECT_EQ(ret[1], 2); + + ret = flat_mv_block_index_unravel(1'000'000, data_host.offsets()); + EXPECT_EQ(ret[1], num_octs_2); + } + + data.reorganize(num_vars_1); + + { // Setup data + const auto nb_cells = data.num_cells() * data.storage().num_quadrants(); + Kokkos::RangePolicy policy(0, nb_cells); + + Kokkos::parallel_for( + "fill", policy, KOKKOS_LAMBDA(const size_t flat_index) { + const auto index = data.flat_index_unravel(flat_index); + data(index[IX], index[IY], index[DIM], index[DIM + 1]) = flat_index; + }); + } + + { // Data checks + auto data_host = DataArrayBlockMultiVar_t::create_host_mirror_view_and_copy(data); + + EXPECT_EQ(data_host(2, 2, 1, 0), 23); + EXPECT_EQ(data_host(2, 4, 0, 1), 44); + EXPECT_EQ(data_host(1, 0, 1, 3), 61); + EXPECT_EQ(data_host(1, 3, 0, 4), 100); + } + + data.reorganize_and_reset(num_vars_1); + + { // Data checks + auto data_host = DataArrayBlockMultiVar_t::create_host_mirror_view_and_copy(data); + + EXPECT_EQ(data_host(2, 2, 1, 0), 0); + EXPECT_EQ(data_host(2, 4, 0, 1), 0); + EXPECT_EQ(data_host(1, 0, 1, 3), 0); + EXPECT_EQ(data_host(1, 3, 0, 4), 0); + } + + block_size_t<5 - DIM> other_block_size{ 2, 2, 3 }; + DataArrayBlockMultiVar<5 - DIM, int, DefaultDevice> other_data("other data", other_block_size); + other_data.align_with(data); + + { // Metadata checks + auto storage = other_data.storage(); + auto offsets = other_data.offsets(); + + auto offsets_host = + DataArrayBlockMultiVar_t::Offsets_t::create_host_mirror_view_and_copy(offsets); + EXPECT_EQ(offsets.size(), num_octs_1 + 1); + EXPECT_EQ(offsets_host(0), 0); // <- always 0 + EXPECT_EQ(offsets_host(1), 2); + EXPECT_EQ(offsets_host(2), 3); + EXPECT_EQ(offsets_host(3), 3); + EXPECT_EQ(offsets_host(4), 6); + EXPECT_EQ(offsets_host(5), 7); // <- always the number of var blocks + EXPECT_EQ(storage.num_quadrants(), 7); + EXPECT_EQ(storage.num_vars(), 1); // <- always 1 + EXPECT_EQ(storage.num_cells(), Kokkos::dim_prod(other_block_size)); + EXPECT_EQ(storage.block_size(), other_block_size); + } +} + +// ================================================================================================ +// ================================================================================================ +void +run_data_array_block_multi_var_3d() +{ + constexpr int DIM = 3; + using DataArrayBlockMultiVar_t = DataArrayBlockMultiVar; + + const block_size_t block_size{ 4, 3, 2 }; + const uint32_t num_octs_1 = 5; + Kokkos::View num_vars_1("Num vars per octant", num_octs_1); + + { + auto host_num_vars_1 = Kokkos::create_mirror_view(num_vars_1); + host_num_vars_1(0) = 0; // + host_num_vars_1(1) = 3; // +---+---+---+---+---+---+---+---+ + host_num_vars_1(2) = 1; // | 1 | 1 | 1 | 2 | 3 | 3 | 3 | 3 | + host_num_vars_1(3) = 4; // +---+---+---+---+---+---+---+---+ + host_num_vars_1(4) = 0; // + Kokkos::deep_copy(num_vars_1, host_num_vars_1); + } + + DataArrayBlockMultiVar_t data("data", block_size, num_vars_1); + + { // Metadata checks + auto storage = data.storage(); + auto offsets = data.offsets(); + + auto offsets_host = + DataArrayBlockMultiVar_t::Offsets_t::create_host_mirror_view_and_copy(offsets); + EXPECT_EQ(offsets.size(), num_octs_1 + 1); + EXPECT_EQ(offsets_host(0), 0); // <- always 0 + EXPECT_EQ(offsets_host(1), 0); + EXPECT_EQ(offsets_host(2), 3); + EXPECT_EQ(offsets_host(3), 4); + EXPECT_EQ(offsets_host(4), 8); + EXPECT_EQ(offsets_host(5), 8); // <- always the number of var blocks + EXPECT_EQ(storage.num_quadrants(), 8); + EXPECT_EQ(storage.num_vars(), 1); // <- always 1 + EXPECT_EQ(storage.num_cells(), Kokkos::dim_prod(block_size)); + EXPECT_EQ(storage.block_size(), block_size); + } + + { // Num vars + auto data_host = DataArrayBlockMultiVar_t::create_host_mirror_view_and_copy_offsets(data); + auto host_num_vars_1 = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, num_vars_1); + + for (uint32_t i_oct = 0; i_oct < host_num_vars_1.size(); i_oct++) + EXPECT_EQ(data_host.num_vars(i_oct), host_num_vars_1(i_oct)); + } + + { // Flat indices + auto data_host = DataArrayBlockMultiVar_t::create_host_mirror_view_and_copy(data); + + EXPECT_EQ(flat_mv_block_index(2, 1, data_host.offsets()), 2); + EXPECT_EQ(flat_mv_block_index(0, 2, data_host.offsets()), 3); + EXPECT_EQ(flat_mv_block_index(3, 3, data_host.offsets()), 7); + + MVBlockIndex_t ret; + + ret = flat_mv_block_index_unravel(1, data_host.offsets()); + EXPECT_EQ(ret[0], 1); + EXPECT_EQ(ret[1], 1); + + ret = flat_mv_block_index_unravel(2, data_host.offsets()); + EXPECT_EQ(ret[0], 2); + EXPECT_EQ(ret[1], 1); + + ret = flat_mv_block_index_unravel(3, data_host.offsets()); + EXPECT_EQ(ret[0], 0); + EXPECT_EQ(ret[1], 2); + + ret = flat_mv_block_index_unravel(5, data_host.offsets()); + EXPECT_EQ(ret[0], 1); + EXPECT_EQ(ret[1], 3); + + ret = flat_mv_block_index_unravel(7, data_host.offsets()); + EXPECT_EQ(ret[0], 3); + EXPECT_EQ(ret[1], 3); + + ret = flat_mv_block_index_unravel(1'000'000, data_host.offsets()); + EXPECT_EQ(ret[1], num_octs_1); + } + + const uint32_t num_octs_2 = num_octs_1 + 2; + Kokkos::View num_vars_2("Num vars per octant 2", num_octs_2); + + { + auto host_num_vars_2 = Kokkos::create_mirror_view(num_vars_2); + host_num_vars_2(0) = 0; // + host_num_vars_2(1) = 0; // +---+---+ + host_num_vars_2(2) = 2; // | 2 | 2 | + host_num_vars_2(3) = 0; // +---+---+ + host_num_vars_2(4) = 0; // + host_num_vars_2(5) = 0; // + host_num_vars_2(6) = 0; // + Kokkos::deep_copy(num_vars_2, host_num_vars_2); + } + + data.reorganize(num_vars_2); + + { // Metadata checks + auto storage = data.storage(); + auto offsets = data.offsets(); + + auto offsets_host = + DataArrayBlockMultiVar_t::Offsets_t::create_host_mirror_view_and_copy(offsets); + EXPECT_EQ(offsets.size(), num_octs_2 + 1); + EXPECT_EQ(offsets_host(0), 0); // <- always 0 + EXPECT_EQ(offsets_host(1), 0); + EXPECT_EQ(offsets_host(2), 0); + EXPECT_EQ(offsets_host(3), 2); + EXPECT_EQ(offsets_host(4), 2); + EXPECT_EQ(offsets_host(5), 2); + EXPECT_EQ(offsets_host(6), 2); + EXPECT_EQ(offsets_host(7), 2); // <- always the number of var blocks + EXPECT_EQ(storage.num_quadrants(), 2); + EXPECT_EQ(storage.num_vars(), 1); // <- always 1 + EXPECT_EQ(storage.num_cells(), Kokkos::dim_prod(block_size)); + EXPECT_EQ(storage.block_size(), block_size); + } + + { // Flat indices + auto data_host = DataArrayBlockMultiVar_t::create_host_mirror_view_and_copy(data); + + EXPECT_EQ(flat_mv_block_index(0, 2, data_host.offsets()), 0); + EXPECT_EQ(flat_mv_block_index(1, 2, data_host.offsets()), 1); + + MVBlockIndex_t ret; + + ret = flat_mv_block_index_unravel(0, data_host.offsets()); + EXPECT_EQ(ret[0], 0); + EXPECT_EQ(ret[1], 2); + + ret = flat_mv_block_index_unravel(1, data_host.offsets()); + EXPECT_EQ(ret[0], 1); + EXPECT_EQ(ret[1], 2); + + ret = flat_mv_block_index_unravel(1'000'000, data_host.offsets()); + EXPECT_EQ(ret[1], num_octs_2); + } + + data.reorganize(num_vars_1); + + { // Setup data + const auto nb_cells = data.num_cells() * data.storage().num_quadrants(); + Kokkos::RangePolicy policy(0, nb_cells); + + Kokkos::parallel_for( + "fill", policy, KOKKOS_LAMBDA(const size_t flat_index) { + const auto index = data.flat_index_unravel(flat_index); + data(index[IX], index[IY], index[IZ], index[DIM], index[DIM + 1]) = flat_index; + }); + } + + { // Data checks + auto data_host = DataArrayBlockMultiVar_t::create_host_mirror_view_and_copy(data); + + EXPECT_EQ(data_host(3, 2, 1, 1, 1), 47); + EXPECT_EQ(data_host(0, 1, 1, 0, 2), 88); + EXPECT_EQ(data_host(3, 1, 0, 2, 3), 151); + } + + data.reorganize_and_reset(num_vars_1); + + { // Data checks + auto data_host = DataArrayBlockMultiVar_t::create_host_mirror_view_and_copy(data); + + EXPECT_EQ(data_host(3, 2, 1, 1, 1), 0); + EXPECT_EQ(data_host(0, 1, 1, 0, 2), 0); + EXPECT_EQ(data_host(3, 1, 0, 2, 3), 0); + } + + block_size_t<5 - DIM> other_block_size{ 2, 2 }; + DataArrayBlockMultiVar<5 - DIM, int, DefaultDevice> other_data("other data", other_block_size); + other_data.align_with(data); + + { // Metadata checks + auto storage = other_data.storage(); + auto offsets = other_data.offsets(); + + auto offsets_host = + DataArrayBlockMultiVar_t::Offsets_t::create_host_mirror_view_and_copy(offsets); + EXPECT_EQ(offsets.size(), num_octs_1 + 1); + EXPECT_EQ(offsets_host(0), 0); // <- always 0 + EXPECT_EQ(offsets_host(1), 0); + EXPECT_EQ(offsets_host(2), 3); + EXPECT_EQ(offsets_host(3), 4); + EXPECT_EQ(offsets_host(4), 8); + EXPECT_EQ(offsets_host(5), 8); // <- always the number of var blocks + EXPECT_EQ(storage.num_quadrants(), 8); + EXPECT_EQ(storage.num_vars(), 1); // <- always 1 + EXPECT_EQ(storage.num_cells(), Kokkos::dim_prod(other_block_size)); + EXPECT_EQ(storage.block_size(), other_block_size); + } +} + +// ================================================================================================ +// ================================================================================================ +TEST(kalypsso_shared_test, DataArrayBlockMultiVar_2d) +{ + run_data_array_block_multi_var_2d(); +} + +// ================================================================================================ +// ================================================================================================ +TEST(kalypsso_shared_test, DataArrayBlockMultiVar_3d) +{ + run_data_array_block_multi_var_3d(); +} + +} // namespace kalypsso diff --git a/unit_test/core/DataArrayBlock_utils_test.cpp b/unit_test/core/DataArrayBlock_utils_test.cpp new file mode 100644 index 0000000..90a60aa --- /dev/null +++ b/unit_test/core/DataArrayBlock_utils_test.cpp @@ -0,0 +1,225 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// © Commissariat a l'Energie Atomique et aux Energies Alternatives (CEA) +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +#include "gtest/gtest.h" + +namespace kalypsso +{ + +// ================================================================= +// ================================================================= +TEST(DataArrayBlock_utils, test_get_number_of_cells_on_face) +{ + // 2d + { + block_size_t<2> bSize{ 3, 5 }; + + EXPECT_EQ((get_number_of_cells_on_face<2, IX>(bSize) == 5), true); + EXPECT_EQ((get_number_of_cells_on_face<2, IY>(bSize) == 3), true); + } + + // 3d + { + block_size_t<3> bSize{ 7, 2, 5 }; + + EXPECT_EQ((get_number_of_cells_on_face<3, IX>(bSize) == 10), true); + EXPECT_EQ((get_number_of_cells_on_face<3, IY>(bSize) == 35), true); + EXPECT_EQ((get_number_of_cells_on_face<3, IZ>(bSize) == 14), true); + } +} + +// ================================================================= +// ================================================================= +TEST(DataArrayBlock_utils, test_get_number_of_surface_cells) +{ + // 2d + { + block_size_t<2> bSize{ 3, 5 }; + + EXPECT_EQ((get_number_of_surface_cells<2>(bSize) == 16), true); + } + + // 3d + { + block_size_t<3> bSize{ 3, 5, 4 }; + + EXPECT_EQ((get_number_of_surface_cells<3>(bSize) == 94), true); + } +} + +// ================================================================= +// ================================================================= +TEST(DataArrayBlock_utils, test_surface_flatindex_unravel) +{ + // 2d + { + block_size_t<2> bSize{ 4, 7 }; + + { + int32_t flat_index = 0; + auto ij = surface_flatindex_unravel_to_cell_ijk<2>(flat_index, bSize); + auto n = surface_flatindex_to_normal_vector<2>(flat_index, bSize); + + EXPECT_EQ(ij[IX] == 0, true); + EXPECT_EQ(ij[IY] == 0, true); + EXPECT_EQ(n[IX] == -1 and n[IY] == 0, true); + } + { + int32_t flat_index = 5; + auto ij = surface_flatindex_unravel_to_cell_ijk<2>(flat_index, bSize); + auto n = surface_flatindex_to_normal_vector<2>(flat_index, bSize); + + EXPECT_EQ(ij[IX] == 0, true); + EXPECT_EQ(ij[IY] == 5, true); + EXPECT_EQ(n[IX] == -1 and n[IY] == 0, true); + } + { + int32_t flat_index = 10; + auto ij = surface_flatindex_unravel_to_cell_ijk<2>(flat_index, bSize); + auto n = surface_flatindex_to_normal_vector<2>(flat_index, bSize); + + EXPECT_EQ(ij[IX] == 3, true); + EXPECT_EQ(ij[IY] == 3, true); + EXPECT_EQ(n[IX] == 1 and n[IY] == 0, true); + } + { + int32_t flat_index = 15; + auto ij = surface_flatindex_unravel_to_cell_ijk<2>(flat_index, bSize); + auto n = surface_flatindex_to_normal_vector<2>(flat_index, bSize); + + EXPECT_EQ(ij[IX] == 1, true); + EXPECT_EQ(ij[IY] == 0, true); + EXPECT_EQ(n[IX] == 0 and n[IY] == -1, true); + } + { + int32_t flat_index = 18; + auto ij = surface_flatindex_unravel_to_cell_ijk<2>(flat_index, bSize); + auto n = surface_flatindex_to_normal_vector<2>(flat_index, bSize); + + EXPECT_EQ(ij[IX] == 0, true); + EXPECT_EQ(ij[IY] == 6, true); + EXPECT_EQ(n[IX] == 0 and n[IY] == 1, true); + } + { + int32_t flat_index = 20; + auto ij = surface_flatindex_unravel_to_cell_ijk<2>(flat_index, bSize); + auto n = surface_flatindex_to_normal_vector<2>(flat_index, bSize); + + EXPECT_EQ(ij[IX] == 2, true); + EXPECT_EQ(ij[IY] == 6, true); + EXPECT_EQ(n[IX] == 0 and n[IY] == 1, true); + } + } + + // 3d + { + block_size_t<3> bSize{ 5, 4, 3 }; + + { + int32_t flat_index = 0; + auto ijk = surface_flatindex_unravel_to_cell_ijk<3>(flat_index, bSize); + auto n = surface_flatindex_to_normal_vector<3>(flat_index, bSize); + + EXPECT_EQ(ijk[IX] == 0, true); + EXPECT_EQ(ijk[IY] == 0, true); + EXPECT_EQ(ijk[IZ] == 0, true); + EXPECT_EQ(n[IX] == -1 and n[IY] == 0 and n[IZ] == 0, true); + } + { + int32_t flat_index = 18; + auto ijk = surface_flatindex_unravel_to_cell_ijk<3>(flat_index, bSize); + auto n = surface_flatindex_to_normal_vector<3>(flat_index, bSize); + + EXPECT_EQ(ijk[IX] == 4, true); + EXPECT_EQ(ijk[IY] == 2, true); + EXPECT_EQ(ijk[IZ] == 1, true); + EXPECT_EQ(n[IX] == 1 and n[IY] == 0 and n[IZ] == 0, true); + } + { + int32_t flat_index = 28; + auto ijk = surface_flatindex_unravel_to_cell_ijk<3>(flat_index, bSize); + auto n = surface_flatindex_to_normal_vector<3>(flat_index, bSize); + + EXPECT_EQ(ijk[IX] == 4, true); + EXPECT_EQ(ijk[IY] == 0, true); + EXPECT_EQ(ijk[IZ] == 0, true); + EXPECT_EQ(n[IX] == 0 and n[IY] == -1 and n[IZ] == 0, true); + } + { + int32_t flat_index = 38; + auto ijk = surface_flatindex_unravel_to_cell_ijk<3>(flat_index, bSize); + auto n = surface_flatindex_to_normal_vector<3>(flat_index, bSize); + + EXPECT_EQ(ijk[IX] == 4, true); + EXPECT_EQ(ijk[IY] == 0, true); + EXPECT_EQ(ijk[IZ] == 2, true); + EXPECT_EQ(n[IX] == 0 and n[IY] == -1 and n[IZ] == 0, true); + } + { + int32_t flat_index = 48; + auto ijk = surface_flatindex_unravel_to_cell_ijk<3>(flat_index, bSize); + auto n = surface_flatindex_to_normal_vector<3>(flat_index, bSize); + + EXPECT_EQ(ijk[IX] == 4, true); + EXPECT_EQ(ijk[IY] == 3, true); + EXPECT_EQ(ijk[IZ] == 1, true); + EXPECT_EQ(n[IX] == 0 and n[IY] == 1 and n[IZ] == 0, true); + } + { + int32_t flat_index = 58; + auto ijk = surface_flatindex_unravel_to_cell_ijk<3>(flat_index, bSize); + auto n = surface_flatindex_to_normal_vector<3>(flat_index, bSize); + + EXPECT_EQ(ijk[IX] == 4, true); + EXPECT_EQ(ijk[IY] == 0, true); + EXPECT_EQ(ijk[IZ] == 0, true); + EXPECT_EQ(n[IX] == 0 and n[IY] == 0 and n[IZ] == -1, true); + } + { + int32_t flat_index = 68; + auto ijk = surface_flatindex_unravel_to_cell_ijk<3>(flat_index, bSize); + auto n = surface_flatindex_to_normal_vector<3>(flat_index, bSize); + + EXPECT_EQ(ijk[IX] == 4, true); + EXPECT_EQ(ijk[IY] == 2, true); + EXPECT_EQ(ijk[IZ] == 0, true); + EXPECT_EQ(n[IX] == 0 and n[IY] == 0 and n[IZ] == -1, true); + } + { + int32_t flat_index = 78; + auto ijk = surface_flatindex_unravel_to_cell_ijk<3>(flat_index, bSize); + auto n = surface_flatindex_to_normal_vector<3>(flat_index, bSize); + + EXPECT_EQ(ijk[IX] == 4, true); + EXPECT_EQ(ijk[IY] == 0, true); + EXPECT_EQ(ijk[IZ] == 2, true); + EXPECT_EQ(n[IX] == 0 and n[IY] == 0 and n[IZ] == 1, true); + } + { + int32_t flat_index = 88; + auto ijk = surface_flatindex_unravel_to_cell_ijk<3>(flat_index, bSize); + auto n = surface_flatindex_to_normal_vector<3>(flat_index, bSize); + + EXPECT_EQ(ijk[IX] == 4, true); + EXPECT_EQ(ijk[IY] == 2, true); + EXPECT_EQ(ijk[IZ] == 2, true); + EXPECT_EQ(n[IX] == 0 and n[IY] == 0 and n[IZ] == 1, true); + } + { + int32_t flat_index = 93; + auto ijk = surface_flatindex_unravel_to_cell_ijk<3>(flat_index, bSize); + auto n = surface_flatindex_to_normal_vector<3>(flat_index, bSize); + + EXPECT_EQ(ijk[IX] == 4, true); + EXPECT_EQ(ijk[IY] == 3, true); + EXPECT_EQ(ijk[IZ] == 2, true); + EXPECT_EQ(n[IX] == 0 and n[IY] == 0 and n[IZ] == 1, true); + } + } +} + +} // namespace kalypsso diff --git a/unit_test/core/EdgeDataArrayBlock_utils_test.cpp b/unit_test/core/EdgeDataArrayBlock_utils_test.cpp new file mode 100644 index 0000000..e317d04 --- /dev/null +++ b/unit_test/core/EdgeDataArrayBlock_utils_test.cpp @@ -0,0 +1,204 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// © Commissariat a l'Energie Atomique et aux Energies Alternatives (CEA) +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +#include "gtest/gtest.h" + +namespace kalypsso +{ + +TEST(EdgeDataArrayBlock_utils, test_get_block_size) +{ + // 2d + { + const auto ebSize = get_edge_block_size<2>({ 3, 5 }, IX); + EXPECT_EQ(ebSize[IX] == 3, true); + EXPECT_EQ(ebSize[IY] == 6, true); + + const auto ebSize2 = get_edge_block_size<2>({ 3, 5 }, IY); + EXPECT_EQ(ebSize2[IX] == 4, true); + EXPECT_EQ(ebSize2[IY] == 5, true); + + const auto ebSize3 = get_edge_block_size<2>({ 3, 5 }, IZ); + EXPECT_EQ(ebSize3[IX] == 4, true); + EXPECT_EQ(ebSize3[IY] == 6, true); + } + + // 3d + { + const auto ebSize = get_edge_block_size<3>({ 3, 5, 4 }, IX); + EXPECT_EQ(ebSize[IX] == 3, true); + EXPECT_EQ(ebSize[IY] == 6, true); + EXPECT_EQ(ebSize[IZ] == 5, true); + + const auto ebSize2 = get_edge_block_size<3>({ 3, 5, 4 }, IZ); + EXPECT_EQ(ebSize2[IX] == 4, true); + EXPECT_EQ(ebSize2[IY] == 6, true); + EXPECT_EQ(ebSize2[IZ] == 4, true); + } +} + +TEST(EdgeDataArrayBlock_utils, test_edge_flat_index_unravel_emf) +{ + // 2d + { + block_size_t<2> bSize{ 3, 5 }; + + const auto offsets = compute_edge_flat_index_offsets_emf<2>(bSize); + EXPECT_EQ(offsets[0] == 0, true); + EXPECT_EQ(offsets[1] == 0, true); + EXPECT_EQ(offsets[2] == 0, true); + EXPECT_EQ(offsets[3] == 24, true); + + + // edge_multiindex_t ijk{ 2, 3, IZ }; + auto ijk = edge_flat_index_unravel_emf<2>(14, bSize, offsets); + EXPECT_EQ(ijk[IX] == 2, true); + EXPECT_EQ(ijk[IY] == 3, true); + } + + // 3d + { + block_size_t<3> bSize{ 3, 5, 4 }; + + const auto offsets = compute_edge_flat_index_offsets_emf<3>(bSize); + EXPECT_EQ(offsets[0] == 0, true); + EXPECT_EQ(offsets[1] == 90, true); + EXPECT_EQ(offsets[2] == 190, true); + EXPECT_EQ(offsets[3] == 286, true); + + // edge_multiindex_t ijk{ 2, 3, 1, IX }; + auto ijk = edge_flat_index_unravel_emf<3>(29, bSize, offsets); + EXPECT_EQ(ijk[IX] == 2, true); + EXPECT_EQ(ijk[IY] == 3, true); + EXPECT_EQ(ijk[IZ] == 1, true); + EXPECT_EQ(ijk[3] == IX, true); + + // edge_multiindex_t ijk2{ 1, 2, 1, IY }; + auto ijk2 = edge_flat_index_unravel_emf<3>(29 + 90, bSize, offsets); + EXPECT_EQ(ijk2[IX] == 1, true); + EXPECT_EQ(ijk2[IY] == 2, true); + EXPECT_EQ(ijk2[IZ] == 1, true); + EXPECT_EQ(ijk2[3] == IY, true); + + // edge_multiindex_t ijk2{ 1, 1, 1, IZ }; + auto ijk3 = edge_flat_index_unravel_emf<3>(29 + 190, bSize, offsets); + EXPECT_EQ(ijk3[IX] == 1, true); + EXPECT_EQ(ijk3[IY] == 1, true); + EXPECT_EQ(ijk3[IZ] == 1, true); + EXPECT_EQ(ijk3[3] == IZ, true); + } +} +TEST(EdgeDataArrayBlock_utils, test_is_edge_at_block_surface) +{ + // 2d + { + block_size_t<2> bSize{ 4, 4 }; + + EXPECT_EQ(is_edge_at_block_surface<2>({ 2, 2, IZ }, bSize, Face::XMIN), false); + EXPECT_EQ(is_edge_at_block_surface<2>({ 0, 2, IZ }, bSize, Face::XMIN), true); + EXPECT_EQ(is_edge_at_block_surface<2>({ 0, 2, IZ }, bSize, Face::XMIN), true); + EXPECT_EQ(is_edge_at_block_surface<2>({ 0, 2, IZ }, bSize, Face::XMAX), false); + EXPECT_EQ(is_edge_at_block_surface<2>({ 3, 3, IZ }, bSize, Face::XMAX), true); + EXPECT_EQ(is_edge_at_block_surface<2>({ 3, 3, IZ }, bSize, Face::YMAX), true); + } + + // 3d + { + block_size_t<3> bSize{ 5, 5, 5 }; + + EXPECT_EQ(is_edge_at_block_surface<3>({ 2, 2, 4, IZ }, bSize, Face::ZMAX), false); + EXPECT_EQ(is_edge_at_block_surface<3>({ 0, 0, 3, IZ }, bSize, Face::ZMAX), false); + EXPECT_EQ(is_edge_at_block_surface<3>({ 0, 0, 3, IZ }, bSize, Face::XMIN), true); + EXPECT_EQ(is_edge_at_block_surface<3>({ 0, 0, 3, IZ }, bSize, Face::YMIN), true); + + EXPECT_EQ(is_edge_at_block_surface<3>({ 0, 0, 3, IX }, bSize, Face::XMIN), false); + EXPECT_EQ(is_edge_at_block_surface<3>({ 0, 0, 3, IX }, bSize, Face::YMIN), true); + + EXPECT_EQ(is_edge_at_block_surface<3>({ 0, 2, 3, IX }, bSize, Face::XMIN), false); + EXPECT_EQ(is_edge_at_block_surface<3>({ 0, 2, 3, IX }, bSize, Face::YMIN), false); + + EXPECT_EQ(is_edge_at_block_surface<3>({ 4, 0, 3, IY }, bSize, Face::XMIN), false); + EXPECT_EQ(is_edge_at_block_surface<3>({ 4, 0, 3, IY }, bSize, Face::YMIN), false); + + EXPECT_EQ(is_edge_at_block_surface<3>({ 2, 4, 1, IX }, bSize, Face::YMAX), true); + EXPECT_EQ(is_edge_at_block_surface<3>({ 2, 4, 1, IY }, bSize, Face::YMAX), false); + EXPECT_EQ(is_edge_at_block_surface<3>({ 2, 4, 1, IZ }, bSize, Face::YMAX), true); + } +} + +TEST(EdgeDataArrayBlock_utils, test_is_edge_at_block_edge) +{ + // 2d + { + block_size_t<2> bSize{ 4, 4 }; + + EXPECT_EQ(is_edge_at_block_edge<2>({ 2, 2, IZ }, bSize, Face::XMIN, Face::YMIN), false); + + EXPECT_EQ(is_edge_at_block_edge<2>({ 0, 0, IZ }, bSize, Face::XMIN, Face::YMIN), true); + + EXPECT_EQ(is_edge_at_block_edge<2>({ 0, 3, IZ }, bSize, Face::XMIN, Face::YMAX), true); + + EXPECT_EQ(is_edge_at_block_edge<2>({ 0, 3, IZ }, bSize, Face::XMAX, Face::YMAX), false); + } + + // 3d + { + block_size_t<3> bSize{ 6, 6, 6 }; + + EXPECT_EQ(is_edge_at_block_edge<3>({ 2, 2, 2, IZ }, bSize, Face::XMIN, Face::YMIN), false); + + EXPECT_EQ(is_edge_at_block_edge<3>({ 0, 0, 0, IZ }, bSize, Face::XMIN, Face::YMIN), true); + + EXPECT_EQ(is_edge_at_block_edge<3>({ 0, 0, 0, IX }, bSize, Face::XMIN, Face::ZMIN), false); + EXPECT_EQ(is_edge_at_block_edge<3>({ 0, 0, 0, IX }, bSize, Face::YMIN, Face::ZMIN), true); + + EXPECT_EQ(is_edge_at_block_edge<3>({ 3, 0, 0, IX }, bSize, Face::YMIN, Face::ZMIN), true); + EXPECT_EQ(is_edge_at_block_edge<3>({ 3, 5, 0, IX }, bSize, Face::YMAX, Face::ZMIN), true); + + EXPECT_EQ(is_edge_at_block_edge<3>({ 5, 5, 0, IY }, bSize, Face::XMAX, Face::ZMIN), true); + EXPECT_EQ(is_edge_at_block_edge<3>({ 5, 5, 5, IY }, bSize, Face::XMAX, Face::ZMIN), false); + } +} + +TEST(EdgeDataArrayBlock_utils, test_get_edge_outside_unit_vector) +{ + + // 2d + { + { + const auto v = get_edge_outside_unit_vector<2>(Face::XMIN, Face::YMAX); + EXPECT_EQ(v[0] == -1, true); + EXPECT_EQ(v[1] == 1, true); + } + + { + const auto v = get_edge_outside_unit_vector<2>(Face::XMAX, Face::YMAX); + EXPECT_EQ(v[0] == 1, true); + EXPECT_EQ(v[1] == 1, true); + } + } + + // 3d + { + { + const auto v = get_edge_outside_unit_vector<3>(Face::XMIN, Face::YMAX); + EXPECT_EQ(v[0] == -1, true); + EXPECT_EQ(v[1] == 1, true); + EXPECT_EQ(v[2] == 0, true); + } + + { + const auto v = get_edge_outside_unit_vector<3>(Face::XMIN, Face::ZMAX); + EXPECT_EQ(v[0] == -1, true); + EXPECT_EQ(v[1] == 0, true); + EXPECT_EQ(v[2] == 1, true); + } + } + +} // EdgeDataArrayBlock_utils, test_get_edge_outside_unit_vector + +} // namespace kalypsso diff --git a/unit_test/core/Kokkos_Array_extensions_test.cpp b/unit_test/core/Kokkos_Array_extensions_test.cpp new file mode 100644 index 0000000..84e8340 --- /dev/null +++ b/unit_test/core/Kokkos_Array_extensions_test.cpp @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// © Commissariat a l'Energie Atomique et aux Energies Alternatives (CEA) +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +#include + +#include "gtest/gtest.h" + +namespace kalypsso +{ + +TEST(kalypsso_shared_test, Kokkos_Array_extensions) +{ + + Kokkos::Array a1{ 12, 13, 14 }; + Kokkos::Array a2{ 12, 13, 14 }; + Kokkos::Array a3{ 42, 10, 20 }; + + Kokkos::Array a13p = a1 + a3; + Kokkos::Array a13m = a1 - a3; + + Kokkos::Array a13p_true{ 54, 23, 34 }; + Kokkos::Array a13m_true{ -30, 3, -6 }; + + Kokkos::Array a1x2{ 24, 26, 28 }; + Kokkos::Array a1x3{ 36, 39, 42 }; + + EXPECT_EQ(a1 == a2, true) << "test a1==a2 failed"; + EXPECT_EQ(a1 == a3, false) << "test a1!=a3 failed"; + + EXPECT_EQ(a13p == a13p_true, true) << "test array addition failed"; + EXPECT_EQ(a13m == a13m_true, true) << "test array subtract failed"; + + EXPECT_EQ(a1 * 2 == a1x2, true) << "test array multiplication by a scalar failed"; + EXPECT_EQ(3 * a1 == a1x3, true) << "test array multiplication by a scalar failed"; + + +} // Kokkos_Array_extensions + +} // namespace kalypsso diff --git a/unit_test/core/MaterialPresence_test.cpp b/unit_test/core/MaterialPresence_test.cpp new file mode 100644 index 0000000..65bc9d2 --- /dev/null +++ b/unit_test/core/MaterialPresence_test.cpp @@ -0,0 +1,442 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include + +#include +#include +#include + +#include "gtest/gtest.h" +#include "../main_kalypsso_unittest.h" + +namespace kalypsso +{ + +// ================================================================================================ +// ================================================================================================ +TEST(kalypsso_shared_test, MaterialPresence) +{ + DataArrayUtils::set_growth_rate(KALYPSSO_NUM(2.5)); + + using MP_t = MaterialPresenceView; + + MP_t mats("mat pres", 120, 3); + + EXPECT_EQ(mats.num_materials(1), 0); + EXPECT_EQ(mats.size(), 3); + EXPECT_EQ(mats.capacity(), 15); + + mats.set(1, 25); + mats.set(1, 44); + mats.set(1, 96); + + EXPECT_TRUE(mats.get(1, 25)); + EXPECT_TRUE(mats.get(1, 44)); + EXPECT_TRUE(mats.get(1, 96)); + + EXPECT_FALSE(mats.get(1, 30)); + EXPECT_FALSE(mats.get(1, 100)); + + EXPECT_EQ(mats.material_num(1, 0), 25); + EXPECT_EQ(mats.material_num(1, 1), 44); + EXPECT_EQ(mats.material_num(1, 2), 96); + + EXPECT_EQ(mats.material_index(1, 25), 0); + EXPECT_EQ(mats.material_index(1, 44), 1); + EXPECT_EQ(mats.material_index(1, 96), 2); + + EXPECT_EQ(mats.num_materials(1), 3); + + mats.set(0, 1); + mats.set(0, 44); + mats.set(0, 100); + + MP_t::copy(mats, 2, mats, 0); + MP_t::update(mats, 2, mats, 1); + EXPECT_TRUE(mats.get(2, 1)); + EXPECT_TRUE(mats.get(2, 25)); + EXPECT_TRUE(mats.get(2, 44)); + EXPECT_TRUE(mats.get(2, 96)); + EXPECT_TRUE(mats.get(2, 100)); + EXPECT_FALSE(mats.get(2, 30)); + + EXPECT_EQ(mats.num_materials(2), 5); + + mats.unset(2, 44); + EXPECT_FALSE(mats.get(2, 44)); + EXPECT_FALSE(mats.get(2, 30)); + EXPECT_EQ(mats.num_materials(2), 4); + + Kokkos::View num_vars("num vars", 3); + mats.compute_num_vars(5, num_vars); + + EXPECT_EQ(num_vars(0), 15); + EXPECT_EQ(num_vars(1), 15); + EXPECT_EQ(num_vars(2), 20); + + mats.resets(); + EXPECT_EQ(mats.num_materials(0), 0); + EXPECT_EQ(mats.num_materials(1), 0); + EXPECT_EQ(mats.num_materials(2), 0); +} + +// ================================================================================================ +// ================================================================================================ +TEST(kalypsso_shared_test, MaterialPresence_fill_ghosts_2d) +{ + static char null_char = '\0'; + static char * empty_string = &null_char; + const ParallelEnv & par_env = **g_par_env_ptr; + ConfigMap config_map(empty_string, 1); + + config_map.setInteger("amr", "level_min", 0); + config_map.setInteger("amr", "level_max", 2); + config_map.setString("amr", "connectivity", "brick"); + + config_map.setInteger("p4est_connectivity", "nbrick_x", 2); + config_map.setInteger("p4est_connectivity", "nbrick_y", 2); + + AMRmesh<2> amr_mesh(par_env, config_map); + MeshMap<2, HostDevice> amr_mesh_map(config_map, par_env); + + auto rebuild_forest = [&]() { + amr_mesh.reset_ghost(); + amr_mesh_map.compute_outside_quad_info(amr_mesh.forest(), amr_mesh.ghost()); + amr_mesh_map.update_hashmap(amr_mesh.forest(), amr_mesh.ghost(), true); + amr_mesh_map.update_mirror_orchard_keys(amr_mesh.ghost()); + amr_mesh_map.update_amr_mesh_info(amr_mesh.forest(), amr_mesh.ghost()); + amr_mesh_map.update_orchard_keys(amr_mesh.forest(), amr_mesh.ghost()); + amr_mesh_map.update_conformal_status(); + }; + + rebuild_forest(); + + { + const auto num_quadrants = amr_mesh.local_num_quadrants(); + AMRContext<2, HostDevice> amr_context(num_quadrants); + const auto flags = amr_context.m_amrflags_h; + + for (uint32_t i_oct = 0; i_oct < num_quadrants; i_oct++) + if (i_oct == 0 || i_oct == 2) + flags(i_oct) = AMRContextBase::KALYPSSO_DO_REFINE; + else + flags(i_oct) = AMRContextBase::KALYPSSO_DO_NOTHING; + + amr_context.adapt_mesh(amr_mesh.forest()); + } + + rebuild_forest(); + + { + const auto num_quadrants = amr_mesh.local_num_quadrants(); + AMRContext<2, HostDevice> amr_context(num_quadrants); + const auto flags = amr_context.m_amrflags_h; + + for (uint32_t i_oct = 0; i_oct < num_quadrants; i_oct++) + if (i_oct == 5) + flags(i_oct) = AMRContextBase::KALYPSSO_DO_REFINE; + else + flags(i_oct) = AMRContextBase::KALYPSSO_DO_NOTHING; + + amr_context.adapt_mesh(amr_mesh.forest()); + } + + rebuild_forest(); + + const auto nb_owned = amr_mesh.local_num_quadrants(); + const auto nb_outside = amr_mesh_map.get_amr_mesh_info().local_num_quadrants_outside(); + + using MatPresView_t = MaterialPresenceView; + + MatPresView_t mat_pres_src("mat pres src", 3, nb_owned + nb_outside); + MatPresView_t mat_pres_dst("mat pres dst", 3, nb_owned); + + { + for (uint32_t i_oct = 0; i_oct < nb_owned; i_oct++) + // clang-format off + switch (i_oct) + { + case 0: case 1: case 2: case 3: case 5: case 6: case 7: case 9: + mat_pres_src.set(i_oct, 2); break; + case 4: case 8: case 10: case 11: + mat_pres_src.set(i_oct, 1); break; + case 12: + mat_pres_src.set(i_oct, 0); break; + } + // clang-format on + }; + + const auto keys = amr_mesh_map.orchard_keys(); + const auto hashmap = amr_mesh_map.hashmap(); + for (uint32_t i_oct = nb_outside; i_oct < nb_owned + nb_outside; i_oct++) + { + const auto key_outside = keys(i_oct); + + // Get the direction towards inside the domain + auto outside_normal = + orchard_key_t<2>::get_outside_normal(key_outside, { 2, 2 }, { true, true }); + outside_normal[IX] *= orchard_key_t<2>::is_touching_face_X(key_outside); + outside_normal[IY] *= orchard_key_t<2>::is_touching_face_Y(key_outside); + + // Get the inside key + auto key_inside = orchard_key_t<2>::get_neighbor_key_same_level( + key_outside, outside_normal, { 2, 2 }, { false, false }); + orchard_key_t<2>::reset_outside_bits(key_inside); + + const auto i_oct_inside = hashmap.value_at(hashmap.find(key_inside)); + MatPresView_t::copy(mat_pres_src, i_oct, mat_pres_src, i_oct_inside); + } + + + MultiMatFillBlockGhostCellsMatPresence<2, HostDevice>::apply(mat_pres_src, + amr_mesh_map.hashmap(), + amr_mesh_map.orchard_keys(), + { 2, 2 }, + { false, false }, + 0, + nb_owned, + mat_pres_dst); + + EXPECT_FALSE(mat_pres_dst.get(0, 0)); + EXPECT_FALSE(mat_pres_dst.get(0, 1)); + EXPECT_TRUE(mat_pres_dst.get(0, 2)); + + EXPECT_FALSE(mat_pres_dst.get(1, 0)); + EXPECT_TRUE(mat_pres_dst.get(1, 1)); + EXPECT_TRUE(mat_pres_dst.get(1, 2)); + + EXPECT_FALSE(mat_pres_dst.get(2, 0)); + EXPECT_FALSE(mat_pres_dst.get(2, 1)); + EXPECT_TRUE(mat_pres_dst.get(2, 2)); + + EXPECT_TRUE(mat_pres_dst.get(3, 0)); + EXPECT_TRUE(mat_pres_dst.get(3, 1)); + EXPECT_TRUE(mat_pres_dst.get(3, 2)); + + EXPECT_TRUE(mat_pres_dst.get(4, 0)); + EXPECT_TRUE(mat_pres_dst.get(4, 1)); + EXPECT_TRUE(mat_pres_dst.get(4, 2)); + + EXPECT_FALSE(mat_pres_dst.get(5, 0)); + EXPECT_TRUE(mat_pres_dst.get(5, 1)); + EXPECT_TRUE(mat_pres_dst.get(5, 2)); + + EXPECT_FALSE(mat_pres_dst.get(6, 0)); + EXPECT_TRUE(mat_pres_dst.get(6, 1)); + EXPECT_TRUE(mat_pres_dst.get(6, 2)); + + EXPECT_FALSE(mat_pres_dst.get(7, 0)); + EXPECT_TRUE(mat_pres_dst.get(7, 1)); + EXPECT_TRUE(mat_pres_dst.get(7, 2)); + + EXPECT_FALSE(mat_pres_dst.get(8, 0)); + EXPECT_TRUE(mat_pres_dst.get(8, 1)); + EXPECT_TRUE(mat_pres_dst.get(8, 2)); + + EXPECT_TRUE(mat_pres_dst.get(9, 0)); + EXPECT_TRUE(mat_pres_dst.get(9, 1)); + EXPECT_TRUE(mat_pres_dst.get(9, 2)); + + EXPECT_FALSE(mat_pres_dst.get(10, 0)); + EXPECT_TRUE(mat_pres_dst.get(10, 1)); + EXPECT_TRUE(mat_pres_dst.get(10, 2)); + + EXPECT_TRUE(mat_pres_dst.get(11, 0)); + EXPECT_TRUE(mat_pres_dst.get(11, 1)); + EXPECT_TRUE(mat_pres_dst.get(11, 2)); + + EXPECT_TRUE(mat_pres_dst.get(12, 0)); + EXPECT_TRUE(mat_pres_dst.get(12, 1)); + EXPECT_TRUE(mat_pres_dst.get(12, 2)); +} + +// ================================================================================================ +// ================================================================================================ +TEST(kalypsso_shared_test, MaterialPresence_amr) +{ + static char null_char = '\0'; + static char * empty_string = &null_char; + const ParallelEnv & par_env = **g_par_env_ptr; + ConfigMap config_map(empty_string, 1); + + config_map.setInteger("amr", "level_min", 0); + config_map.setInteger("amr", "level_max", 1); + config_map.setString("amr", "connectivity", "brick"); + + config_map.setInteger("p4est_connectivity", "nbrick_x", 2); + config_map.setInteger("p4est_connectivity", "nbrick_y", 2); + + AMRmesh<2> amr_mesh(par_env, config_map); + MeshMap<2, HostDevice> amr_mesh_map(config_map, par_env); + + using MatPresView_t = MaterialPresenceView; + + MatPresView_t mat_pres_old("mat pres A", 3, 0); + MatPresView_t mat_pres_new("mat pres B", 3, 0); + + auto rebuild_forest = [&]() { + amr_mesh.reset_ghost(); + amr_mesh_map.compute_outside_quad_info(amr_mesh.forest(), amr_mesh.ghost()); + amr_mesh_map.update_hashmap(amr_mesh.forest(), amr_mesh.ghost(), true); + amr_mesh_map.update_mirror_orchard_keys(amr_mesh.ghost()); + amr_mesh_map.update_amr_mesh_info(amr_mesh.forest(), amr_mesh.ghost()); + amr_mesh_map.update_orchard_keys(amr_mesh.forest(), amr_mesh.ghost()); + amr_mesh_map.update_conformal_status(); + }; + + rebuild_forest(); + + { + const auto num_quadrants = amr_mesh.local_num_quadrants(); + AMRContext<2, HostDevice> amr_context(num_quadrants); + const auto flags = amr_context.m_amrflags_d; + + mat_pres_old.resize(num_quadrants); + mat_pres_old.resets(); + + for (uint32_t i_oct = 0; i_oct < num_quadrants; i_oct++) + switch (i_oct) + { + case 0: + flags(i_oct) = AMRContextBase::KALYPSSO_DO_REFINE; + mat_pres_old.set(i_oct, 0); + break; + case 1: + flags(i_oct) = AMRContextBase::KALYPSSO_DO_NOTHING; + mat_pres_old.set(i_oct, 1); + break; + case 2: + flags(i_oct) = AMRContextBase::KALYPSSO_DO_REFINE; + mat_pres_old.set(i_oct, 1); + break; + case 3: + flags(i_oct) = AMRContextBase::KALYPSSO_DO_NOTHING; + mat_pres_old.set(i_oct, 2); + break; + }; + + + amr_context.adapt_mesh(amr_mesh.forest()); + } + + { + const auto old_hashmap = amr_mesh_map.hashmap_clone(); + const auto old_keys = amr_mesh_map.orchard_keys(); + + rebuild_forest(); + + const auto new_keys = amr_mesh_map.orchard_keys(); + + UserDataRemapper<2, HostDevice> remapper( + old_hashmap, new_keys, old_keys, amr_mesh.local_num_quadrants(), { 2, 2 }, config_map); + mat_pres_new.resize(amr_mesh.local_num_quadrants()); + remapper.remap_material_presence({}, mat_pres_old, mat_pres_new); + my_swap(mat_pres_old, mat_pres_new); + + EXPECT_TRUE(mat_pres_old.get(0, 0)); + EXPECT_FALSE(mat_pres_old.get(0, 1)); + EXPECT_FALSE(mat_pres_old.get(0, 2)); + + EXPECT_TRUE(mat_pres_old.get(1, 0)); + EXPECT_FALSE(mat_pres_old.get(1, 1)); + EXPECT_FALSE(mat_pres_old.get(1, 2)); + + EXPECT_TRUE(mat_pres_old.get(2, 0)); + EXPECT_FALSE(mat_pres_old.get(2, 1)); + EXPECT_FALSE(mat_pres_old.get(2, 2)); + + EXPECT_TRUE(mat_pres_old.get(3, 0)); + EXPECT_FALSE(mat_pres_old.get(3, 1)); + EXPECT_FALSE(mat_pres_old.get(3, 2)); + + EXPECT_FALSE(mat_pres_old.get(4, 0)); + EXPECT_TRUE(mat_pres_old.get(4, 1)); + EXPECT_FALSE(mat_pres_old.get(4, 2)); + + EXPECT_FALSE(mat_pres_old.get(5, 0)); + EXPECT_TRUE(mat_pres_old.get(5, 1)); + EXPECT_FALSE(mat_pres_old.get(5, 2)); + + EXPECT_FALSE(mat_pres_old.get(6, 0)); + EXPECT_TRUE(mat_pres_old.get(6, 1)); + EXPECT_FALSE(mat_pres_old.get(6, 2)); + + EXPECT_FALSE(mat_pres_old.get(7, 0)); + EXPECT_TRUE(mat_pres_old.get(7, 1)); + EXPECT_FALSE(mat_pres_old.get(7, 2)); + + EXPECT_FALSE(mat_pres_old.get(8, 0)); + EXPECT_TRUE(mat_pres_old.get(8, 1)); + EXPECT_FALSE(mat_pres_old.get(8, 2)); + + EXPECT_FALSE(mat_pres_old.get(9, 0)); + EXPECT_FALSE(mat_pres_old.get(9, 1)); + EXPECT_TRUE(mat_pres_old.get(9, 2)); + } + + { + const auto num_quadrants = amr_mesh.local_num_quadrants(); + AMRContext<2, HostDevice> amr_context(num_quadrants); + const auto flags = amr_context.m_amrflags_d; + + mat_pres_old.set(5, 0); + + for (uint32_t i_oct = 0; i_oct < num_quadrants; i_oct++) + if (i_oct >= 5 && i_oct <= 8) + flags(i_oct) = AMRContextBase::KALYPSSO_DO_COARSEN; + else + flags(i_oct) = AMRContextBase::KALYPSSO_DO_NOTHING; + + amr_context.adapt_mesh(amr_mesh.forest()); + } + + { + const auto old_hashmap = amr_mesh_map.hashmap_clone(); + const auto old_keys = amr_mesh_map.orchard_keys(); + + rebuild_forest(); + + const auto new_keys = amr_mesh_map.orchard_keys(); + + UserDataRemapper<2, HostDevice> remapper( + old_hashmap, new_keys, old_keys, amr_mesh.local_num_quadrants(), { 2, 2 }, config_map); + mat_pres_new.resize(amr_mesh.local_num_quadrants()); + remapper.remap_material_presence({}, mat_pres_old, mat_pres_new); + my_swap(mat_pres_old, mat_pres_new); + + EXPECT_TRUE(mat_pres_old.get(0, 0)); + EXPECT_FALSE(mat_pres_old.get(0, 1)); + EXPECT_FALSE(mat_pres_old.get(0, 2)); + + EXPECT_TRUE(mat_pres_old.get(1, 0)); + EXPECT_FALSE(mat_pres_old.get(1, 1)); + EXPECT_FALSE(mat_pres_old.get(1, 2)); + + EXPECT_TRUE(mat_pres_old.get(2, 0)); + EXPECT_FALSE(mat_pres_old.get(2, 1)); + EXPECT_FALSE(mat_pres_old.get(2, 2)); + + EXPECT_TRUE(mat_pres_old.get(3, 0)); + EXPECT_FALSE(mat_pres_old.get(3, 1)); + EXPECT_FALSE(mat_pres_old.get(3, 2)); + + EXPECT_FALSE(mat_pres_old.get(4, 0)); + EXPECT_TRUE(mat_pres_old.get(4, 1)); + EXPECT_FALSE(mat_pres_old.get(4, 2)); + + EXPECT_TRUE(mat_pres_old.get(5, 0)); + EXPECT_TRUE(mat_pres_old.get(5, 1)); + EXPECT_FALSE(mat_pres_old.get(5, 2)); + + EXPECT_FALSE(mat_pres_old.get(6, 0)); + EXPECT_FALSE(mat_pres_old.get(6, 1)); + EXPECT_TRUE(mat_pres_old.get(6, 2)); + } +} + +} // namespace kalypsso diff --git a/unit_test/core/MaterialPresence_test_mpi.cpp b/unit_test/core/MaterialPresence_test_mpi.cpp new file mode 100644 index 0000000..5dd6f44 --- /dev/null +++ b/unit_test/core/MaterialPresence_test_mpi.cpp @@ -0,0 +1,73 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +#include +#include +#include +#include + +#include "gtest/gtest.h" +#include "../main_kalypsso_unittest.h" + +namespace kalypsso +{ + +// ================================================================================================ +// ================================================================================================ +TEST(kalypsso_shared_test, MaterialPresence_exchange) +{ + static char null_char = '\0'; + static char * empty_string = &null_char; + const ParallelEnv & par_env = **g_par_env_ptr; + ConfigMap config_map(empty_string, 1); + + config_map.setInteger("amr", "level_min", 0); + config_map.setInteger("amr", "level_max", 0); + config_map.setString("amr", "connectivity", "brick"); + + config_map.setInteger("p4est_connectivity", "nbrick_x", 2); + config_map.setInteger("p4est_connectivity", "nbrick_y", 2); + + AMRmesh<2> amr_mesh(par_env.mpi_comm(), config_map); + MeshMap<2, HostDevice> amr_mesh_map(config_map, par_env); + MaterialPresenceExchanger<2, HostDevice> mat_pres_exchanger( + config_map, par_env, amr_mesh, amr_mesh_map); + + auto rebuild_forest = [&]() { + amr_mesh.reset_ghost(); + amr_mesh_map.compute_outside_quad_info(amr_mesh.forest(), amr_mesh.ghost()); + amr_mesh_map.update_hashmap(amr_mesh.forest(), amr_mesh.ghost(), true); + amr_mesh_map.update_mirror_orchard_keys(amr_mesh.forest(), amr_mesh.ghost()); + amr_mesh_map.update_amr_mesh_info(amr_mesh.forest(), amr_mesh.ghost()); + amr_mesh_map.update_orchard_keys(amr_mesh.forest(), amr_mesh.ghost()); + amr_mesh_map.update_conformal_status(); + }; + + rebuild_forest(); + + using MatPresView_t = MaterialPresenceView; + MatPresView_t mat_pres("mat pres src", 3, amr_mesh.local_num_quadrants_total()); + mat_pres.resets(); + + { + mat_pres.set(0, par_env.rank() % 3); + mat_pres.set(1, 0); + } + + mat_pres_exchanger.exchange(mat_pres); + + { + EXPECT_EQ(mat_pres.get(2, 0), par_env.rank() == 1); + EXPECT_EQ(mat_pres.get(2, 1), par_env.rank() == 0); + EXPECT_FALSE(mat_pres.get(2, 2)); + + EXPECT_TRUE(mat_pres.get(3, 0)); + EXPECT_FALSE(mat_pres.get(3, 1)); + EXPECT_FALSE(mat_pres.get(3, 2)); + } +} + +} // namespace kalypsso diff --git a/unit_test/core/StencilHelper_test.cpp b/unit_test/core/StencilHelper_test.cpp new file mode 100644 index 0000000..a225616 --- /dev/null +++ b/unit_test/core/StencilHelper_test.cpp @@ -0,0 +1,1353 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/** + * \file StencilHelper_test.cpp + * + * unit test for class StencilHelper. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include + +#include + +#include "../main_kalypsso_unittest.h" + +#include "gtest/gtest.h" + +namespace kalypsso +{ +// ============================================================================= +// ============================================================================= +template +void +run_test(const ParallelEnv & par_env, const ConfigMap & config_map) +{ + using StencilHelper_t = StencilHelper; + using CellLocation_t = CellLocation; + + using Hydro_t = core::models::Hydro; + + InitialAMRSetup initial_amr_setup( + par_env, config_map, InitFuncSineWave{}); + + const auto level_min = config_map.getInteger("amr", "level_min", 2); + const auto level_max = config_map.getInteger("amr", "level_max", 2); + const auto no_refine = (level_min == level_max); + + // setup initial p4est mesh with some test pseudo random refined/coarsen cells pattern + initial_amr_setup.setup_initial_mesh(no_refine); + + auto & amr_mesh = initial_amr_setup.mesh(); + auto mesh_map = initial_amr_setup.mesh_map(); + auto block_sizes = initial_amr_setup.block_sizes(); + auto brick_sizes = initial_amr_setup.brick_sizes(); + auto is_brick_periodic = initial_amr_setup.is_brick_periodic(); + + // retrieve amr keys + mesh_map->update_orchard_keys(amr_mesh.forest(), amr_mesh.ghost()); + auto orchard_keys_host = mesh_map->orchard_keys_host_clone(); + auto orchard_keys_device = mesh_map->orchard_keys_clone(); + + // mirror keys array must be up to date for MeshGhostExchange to be functional + mesh_map->update_mirror_orchard_keys(amr_mesh.ghost()); + + // rebuild the hashmap (must be done after AMR cycle) + constexpr bool on_device = true; + mesh_map->update_hashmap(on_device); + auto amr_hashmap_device = mesh_map->hashmap(); + mesh_map->update_conformal_status(); + auto conformal_status = mesh_map->conformal_status(); + + + auto amr_mesh_info = initial_amr_setup.amr_mesh_info(); + + // for testing DataArrayBlock + auto userdata_block_cell = initial_amr_setup.setup_initial_data_block_new(orchard_keys_device); + + // for testing FaceDataArrayBlock_t + auto userdata_block_face = + initial_amr_setup.setup_initial_data_block_face(orchard_keys_device, false); + + // for testing flux + auto userdata_block_face_x = + initial_amr_setup.setup_initial_data_block_flux(orchard_keys_device, IX); + + // for testing flux + auto userdata_block_face_y = + initial_amr_setup.setup_initial_data_block_flux(orchard_keys_device, IY); + + { + std::string filename = dim == 2 ? "StencilHelper_test_2d" : "StencilHelper_test_3d"; + + // provide mapping between variables Id and variables names + const Hydro_t model(dim); + + DataWriter::save( + filename, userdata_block_cell, config_map, amr_mesh, model); + + DataWriter::save( + filename + "_face", userdata_block_face, config_map, amr_mesh, "facedata"); + } + + auto stencil_helper = StencilHelper_t( + amr_hashmap_device, orchard_keys_device, block_sizes, brick_sizes, is_brick_periodic); + + + if constexpr (dim == 2) + { + { + uint32_t iOct = 1235; + + const auto face_xmin_neighbor_is_coarser = + conformal_face_status_t::face_xmin(conformal_status(iOct)) == + conformal_neighbor_status::NEIGHBOR_IS_COARSER; + + EXPECT_EQ(face_xmin_neighbor_is_coarser, false); + + const auto face_xmax_neighbor_is_coarser = + conformal_face_status_t::face_xmax(conformal_status(iOct)) == + conformal_neighbor_status::NEIGHBOR_IS_COARSER; + + EXPECT_EQ(face_xmax_neighbor_is_coarser, true); + + { + uint32_t cell_index = 15; // i=3, j=3 => cell_index = 3 + 4 * 3 + auto const coords_cell = cellindex_to_coord(cell_index, block_sizes); + + constexpr shift_t<2> shift{ 0, 1 }; + const auto key_cur = orchard_keys_device(iOct); + const CellLocation_t cell_loc{ coords_cell, key_cur, iOct, false }; + const auto cell_loc_neigh = stencil_helper.getNeighLoc(cell_loc, shift); + + EXPECT_EQ(cell_loc_neigh.iOct, 1264); + } + + { + uint32_t cell_index = 7; // i=3, j=1 => cell_index = 3 + 4 * 1 + auto const coords_cell = cellindex_to_coord(cell_index, block_sizes); + + constexpr shift_t<2> shift{ 1, 0 }; + const auto key_cur = orchard_keys_device(iOct); + const CellLocation_t cell_loc{ coords_cell, key_cur, iOct, false }; + const auto cell_loc_neigh = stencil_helper.getNeighLoc(cell_loc, shift); + + EXPECT_EQ(cell_loc_neigh.iOct, 1236); + EXPECT_EQ(cell_loc_neigh.ijk[IX], 0); + EXPECT_EQ(cell_loc_neigh.ijk[IY], 2); + } + } + + { + uint32_t iOct = 1246; + + const auto face_xmin_neighbor_is_coarser = + conformal_face_status_t::face_xmin(conformal_status(iOct)) == + conformal_neighbor_status::NEIGHBOR_IS_COARSER; + + EXPECT_EQ(face_xmin_neighbor_is_coarser, true); + } + + { + uint32_t iOct = 1265; + + const auto face_ymin_neighbor_is_finer = + conformal_face_status_t::face_ymin(conformal_status(iOct)) == + conformal_neighbor_status::NEIGHBOR_IS_FINER; + + EXPECT_EQ(face_ymin_neighbor_is_finer, true); + + const auto face_ymax_neighbor_is_coarser = + conformal_face_status_t::face_ymax(conformal_status(iOct)) == + conformal_neighbor_status::NEIGHBOR_IS_COARSER; + + EXPECT_EQ(face_ymax_neighbor_is_coarser, true); + + { + uint32_t cell_index = 1; // i=1, j=0 => cell_index = 1 + 4 * 0 + auto const coords_cell = cellindex_to_coord(cell_index, block_sizes); + + constexpr shift_t<2> shift{ 0, -1 }; + const auto key_cur = orchard_keys_device(iOct); + const CellLocation_t cell_loc{ coords_cell, key_cur, iOct, false }; + const auto cell_loc_neigh = stencil_helper.getNeighLoc(cell_loc, shift); + + EXPECT_EQ(cell_loc_neigh.iOct, 1262); + EXPECT_EQ(cell_loc_neigh.ijk[IX], 2); + EXPECT_EQ(cell_loc_neigh.ijk[IY], 2); + } + + { + uint32_t cell_index = 2; // i=2, j=0 => cell_index = 2 + 4 * 0 + auto const coords_cell = cellindex_to_coord(cell_index, block_sizes); + + constexpr shift_t<2> shift{ 0, -1 }; + const auto key_cur = orchard_keys_device(iOct); + const CellLocation_t cell_loc{ coords_cell, key_cur, iOct, false }; + const auto cell_loc_neigh = stencil_helper.getNeighLoc(cell_loc, shift); + + EXPECT_EQ(cell_loc_neigh.iOct, 1263); + EXPECT_EQ(cell_loc_neigh.ijk[IX], 0); + EXPECT_EQ(cell_loc_neigh.ijk[IY], 2); + } + } + + // checking compute_siblings_average + { + uint32_t iOct = 1260; + + const uint32_t i = 2; + const uint32_t j = 0; + const uint32_t cell_index = i + 4 * j; + auto const coords_cell = cellindex_to_coord(cell_index, block_sizes); + const auto key_cur = orchard_keys_device(iOct); + const CellLocation_t cell_loc{ coords_cell, key_cur, iOct, false }; + const auto ivar = 0; + + const auto average = + stencil_helper.compute_siblings_average(cell_loc, block_sizes, ivar, userdata_block_cell); + + const auto average_true = (userdata_block_cell(i + 0, j + 0, ivar, iOct) + + userdata_block_cell(i + 1, j + 0, ivar, iOct) + + userdata_block_cell(i + 0, j + 1, ivar, iOct) + + userdata_block_cell(i + 1, j + 1, ivar, iOct)) / + 4; + + EXPECT_NEAR(average, average_true, 1e-14) + << "checking compute_siblings_average failed - iOct=" << iOct; + } + + // checking compute_face_siblings_sum - cell data + { + uint32_t iOct = 1247; + + const uint32_t i = 3; + const uint32_t j = 0; + const uint32_t cell_index = i + 4 * j; + auto const coords_cell = cellindex_to_coord(cell_index, block_sizes); + const auto key_cur = orchard_keys_device(iOct); + const CellLocation_t cell_loc{ coords_cell, key_cur, iOct, false }; + const auto ivar = 0; + + const auto sum = + stencil_helper.compute_face_siblings_sum(cell_loc, ivar, userdata_block_cell, IX); + + const auto sum_true = (userdata_block_cell(i + 0, j + 0, ivar, iOct) + + userdata_block_cell(i + 0, j + 1, ivar, iOct)); + + EXPECT_NEAR(sum, sum_true, 1e-14) + << "checking compute_face_siblings_sum failed - iOct=" << iOct; + } + { + uint32_t iOct = 1260; + + const uint32_t i = 1; + const uint32_t j = 2; + const uint32_t cell_index = i + 4 * j; + auto const coords_cell = cellindex_to_coord(cell_index, block_sizes); + const auto key_cur = orchard_keys_device(iOct); + const CellLocation_t cell_loc{ coords_cell, key_cur, iOct, false }; + const auto ivar = 0; + + const auto sum = + stencil_helper.compute_face_siblings_sum(cell_loc, ivar, userdata_block_cell, IY); + + const auto sum_true = (userdata_block_cell(i + 0, j + 0, ivar, iOct) + + userdata_block_cell(i - 1, j + 0, ivar, iOct)); + + EXPECT_NEAR(sum, sum_true, 1e-14) + << "checking compute_face_siblings_sum failed - iOct=" << iOct; + } + + // checking compute_face_siblings_sum - flux data + { + uint32_t iOct = 1260; + + const uint32_t i = 0; + const uint32_t j = 2; + const uint32_t cell_index = i + 4 * j; + auto const coords_cell = cellindex_to_coord(cell_index, block_sizes); + const auto key_cur = orchard_keys_device(iOct); + const CellLocation_t cell_loc{ coords_cell, key_cur, iOct, false }; + const auto ivar = 0; + + EXPECT_EQ(userdata_block_face_x.is_flux_array(block_sizes, IX), true); + + const bool use_right_face = false; + + const auto sum = stencil_helper.compute_face_siblings_sum( + cell_loc, ivar, userdata_block_face_x, use_right_face); + + auto ijk_face = cell_loc.ijk; + if (use_right_face) + { + ijk_face[IX] += 1; + } + const auto face_multiindex = to_face_multiindex(ijk_face, IX); + + const auto sum_true = (userdata_block_face_x(ijk_face[IX], ijk_face[IY] + 0, ivar, iOct) + + userdata_block_face_x(ijk_face[IX], ijk_face[IY] + 1, ivar, iOct)); + + EXPECT_NEAR(sum, sum_true, 1e-14) << "checking compute_face_siblings_sum failed - left face"; + } + + { + uint32_t iOct = 1261; + + const uint32_t i = 3; + const uint32_t j = 1; + const uint32_t cell_index = i + 4 * j; + auto const coords_cell = cellindex_to_coord(cell_index, block_sizes); + const auto key_cur = orchard_keys_device(iOct); + const CellLocation_t cell_loc{ coords_cell, key_cur, iOct, false }; + const auto ivar = 0; + + EXPECT_EQ(userdata_block_face_x.is_flux_array(block_sizes, IX), true); + + const bool use_right_face = true; + + const auto sum = stencil_helper.compute_face_siblings_sum( + cell_loc, ivar, userdata_block_face_x, use_right_face); + + auto ijk_face = cell_loc.ijk; + { + ijk_face[IX] += 1; + } + + const auto face_multiindex = to_face_multiindex(ijk_face, IX); + + const auto sum_true = (userdata_block_face_x(ijk_face[IX], ijk_face[IY] + 0, ivar, iOct) + + userdata_block_face_x(ijk_face[IX], ijk_face[IY] - 1, ivar, iOct)); + + EXPECT_NEAR(sum, sum_true, 1e-14) << "checking compute_face_siblings_sum failed - right face"; + } + + // testing StencilHelper with FaceDataArrayBlock + { + uint32_t iOct = 1216; + + const uint32_t i = 1; + const uint32_t j = 1; + const uint32_t cell_index = i + 4 * j; + auto const coords_cell = cellindex_to_coord(cell_index, block_sizes); + const auto key_cur = orchard_keys_device(iOct); + const CellLocation_t cell_loc{ coords_cell, key_cur, iOct, false }; + const auto ivar = IX; + const auto ivar_face = face_type_t::LEFT; + + const auto average = stencil_helper.compute_face_siblings_average( + cell_loc, ivar, ivar_face, userdata_block_face); + + auto ijk_face = cell_loc.ijk; + if (ivar_face == face_type_t::RIGHT) + { + ijk_face[ivar] += 1; + } + + const auto face_multiindex = to_face_multiindex(ijk_face, ivar); + + const auto average_true = (userdata_block_face(ijk_face[IX], ijk_face[IY] + 0, ivar, iOct) + + userdata_block_face(ijk_face[IX], ijk_face[IY] - 1, ivar, iOct)) / + 2; + + EXPECT_NEAR(average, average_true, 1e-14) + << "checking compute_face_siblings_average failed - left face"; + } + + { + uint32_t iOct = 1216; + + const uint32_t i = 2; + const uint32_t j = 3; + const uint32_t cell_index = i + 4 * j; + auto const coords_cell = cellindex_to_coord(cell_index, block_sizes); + const auto key_cur = orchard_keys_device(iOct); + const CellLocation_t cell_loc{ coords_cell, key_cur, iOct, false }; + const auto ivar = IY; + const auto ivar_face = face_type_t::RIGHT; + + const auto average = stencil_helper.compute_face_siblings_average( + cell_loc, ivar, ivar_face, userdata_block_face); + + auto ijk_face = cell_loc.ijk; + if (ivar_face == face_type_t::RIGHT) + { + ijk_face[ivar] += 1; + } + + const auto face_multiindex = to_face_multiindex(ijk_face, ivar); + + const auto average_true = (userdata_block_face(ijk_face[IX] + 0, ijk_face[IY], ivar, iOct) + + userdata_block_face(ijk_face[IX] + 1, ijk_face[IY], ivar, iOct)) / + 2; + + EXPECT_NEAR(average, average_true, 1e-14) + << "checking compute_face_siblings_average failed - right face"; + } + + { + uint32_t iOct = 1216; + + const uint32_t i = 1; + const uint32_t j = 1; + const uint32_t cell_index = i + 4 * j; + auto const coords_cell = cellindex_to_coord(cell_index, block_sizes); + const auto key_cur = orchard_keys_device(iOct); + const CellLocation_t cell_loc{ coords_cell, key_cur, iOct, false }; + const auto ivar = IZ; + const auto ivar_face = face_type_t::RIGHT; + + const auto average = stencil_helper.compute_face_siblings_average( + cell_loc, ivar, ivar_face, userdata_block_face); + + auto ijk_face = cell_loc.ijk; + + const auto face_multiindex = to_face_multiindex(ijk_face, ivar); + + const auto average_true = + (userdata_block_face(ijk_face[IX] - 1, ijk_face[IY] - 1, ivar, iOct) + + userdata_block_face(ijk_face[IX] + 0, ijk_face[IY] - 1, ivar, iOct) + + userdata_block_face(ijk_face[IX] - 1, ijk_face[IY] + 0, ivar, iOct) + + userdata_block_face(ijk_face[IX] + 0, ijk_face[IY] + 0, ivar, iOct)) / + 4; + + EXPECT_NEAR(average, average_true, 1e-14) + << "checking compute_face_siblings_average failed - ivar = IZ "; + } + + // =================================================================== + // testing compute_external_face_siblings_average + // =================================================================== + { + uint32_t iOct = 1216; + + // i and j coordinates must be all even (eldest sibling) + const uint32_t i = 2; + const uint32_t j = 2; + const uint32_t cell_index = i + 4 * j; + auto const coords_cell = cellindex_to_coord(cell_index, block_sizes); + const auto key_cur = orchard_keys_device(iOct); + const CellLocation_t cell_loc{ coords_cell, key_cur, iOct, false }; + const auto ivar = IX; + const auto ivar_face = face_type_t::LEFT; + + const auto average = stencil_helper.compute_external_face_siblings_average( + cell_loc, ivar, ivar_face, userdata_block_face); + + // clang-format off + const auto average_true = (userdata_block_face(i, j + 0, ivar, iOct) + + userdata_block_face(i, j + 1, ivar, iOct)) / + 2; + // clang-format on + + EXPECT_NEAR(average, average_true, 1e-14) + << "checking compute_face_siblings_average failed - left face"; + } + + { + uint32_t iOct = 1216; + + // i and j coordinates must be all even (eldest sibling) + const uint32_t i = 2; + const uint32_t j = 0; + const uint32_t cell_index = i + 4 * j; + auto const coords_cell = cellindex_to_coord(cell_index, block_sizes); + const auto key_cur = orchard_keys_device(iOct); + const CellLocation_t cell_loc{ coords_cell, key_cur, iOct, false }; + const auto ivar = IY; + const auto ivar_face = face_type_t::LEFT; + + const auto average = stencil_helper.compute_external_face_siblings_average( + cell_loc, ivar, ivar_face, userdata_block_face); + + // clang-format off + const auto average_true = (userdata_block_face(i + 0, j + 0, ivar, iOct) + + userdata_block_face(i + 1, j + 0, ivar, iOct)) / + 2; + // clang-format on + + EXPECT_NEAR(average, average_true, 1e-14) + << "checking compute_face_siblings_average failed - left face"; + } + + { + uint32_t iOct = 1216; + + // i and j coordinates must be all even (eldest sibling) + const uint32_t i = 0; + const uint32_t j = 2; + const uint32_t cell_index = i + 4 * j; + auto const coords_cell = cellindex_to_coord(cell_index, block_sizes); + const auto key_cur = orchard_keys_device(iOct); + const CellLocation_t cell_loc{ coords_cell, key_cur, iOct, false }; + const auto ivar = IY; + const auto ivar_face = face_type_t::RIGHT; + + const auto average = stencil_helper.compute_external_face_siblings_average( + cell_loc, ivar, ivar_face, userdata_block_face); + + // clang-format off + const auto average_true = (userdata_block_face(i + 0, j + 2, ivar, iOct) + + userdata_block_face(i + 1, j + 2, ivar, iOct)) / + 2; + // clang-format on + + EXPECT_NEAR(average, average_true, 1e-14) + << "checking compute_face_siblings_average failed - left face"; + } + + // testing getBorderEdgeLocSymmetric + { + uint32_t iOct = 2332; + const edge_multiindex_t<2> edge_indexes{ 0, 3, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_neigh = stencil_helper.getBorderEdgeLocSymmetric(edge_loc); + + EXPECT_EQ(edge_loc_neigh.iOct == 2163, true); + EXPECT_EQ(edge_loc_neigh.ijk[IX] == 4, true); + EXPECT_EQ(edge_loc_neigh.ijk[IY] == 2, true); + EXPECT_EQ(edge_loc_neigh.ijk[2] == 2, true); + } + + { + uint32_t iOct = 2332; + const edge_multiindex_t<2> edge_indexes{ 0, 0, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_neigh = stencil_helper.getBorderEdgeLocSymmetric(edge_loc); + + EXPECT_EQ(edge_loc_neigh.iOct == 1655, true); + EXPECT_EQ(edge_loc_neigh.ijk[IX] == 4, true); + EXPECT_EQ(edge_loc_neigh.ijk[IY] == 4, true); + EXPECT_EQ(edge_loc_neigh.ijk[2] == 2, true); + } + + { + uint32_t iOct = 1670; + const edge_multiindex_t<2> edge_indexes{ 0, 4, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_neigh = stencil_helper.getBorderEdgeLocSymmetric(edge_loc); + + EXPECT_EQ(edge_loc_neigh.iOct == 2333, true); + EXPECT_EQ(edge_loc_neigh.ijk[IX] == 4, true); + EXPECT_EQ(edge_loc_neigh.ijk[IY] == 0, true); + EXPECT_EQ(edge_loc_neigh.ijk[2] == 2, true); + } + + { + uint32_t iOct = 1664; + const edge_multiindex_t<2> edge_indexes{ 1, 4, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_neigh = stencil_helper.getBorderEdgeLocSymmetric(edge_loc); + + EXPECT_EQ(edge_loc_neigh.iOct == 1666, true); + EXPECT_EQ(edge_loc_neigh.ijk[IX] == 2, true); + EXPECT_EQ(edge_loc_neigh.ijk[IY] == 0, true); + EXPECT_EQ(edge_loc_neigh.ijk[2] == 2, true); + } + + { + uint32_t iOct = 1666; + const edge_multiindex_t<2> edge_indexes{ 3, 0, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_neigh = stencil_helper.getBorderEdgeLocSymmetric(edge_loc); + + // should return "self" since neighbor is coarser + EXPECT_EQ(edge_loc_neigh.iOct == 1666, true); + EXPECT_EQ(edge_loc_neigh.ijk[IX] == 3, true); + EXPECT_EQ(edge_loc_neigh.ijk[IY] == 0, true); + EXPECT_EQ(edge_loc_neigh.ijk[2] == 2, true); + } + + { + uint32_t iOct = 1665; + const edge_multiindex_t<2> edge_indexes{ 0, 4, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_neigh = stencil_helper.getBorderEdgeLocSymmetric(edge_loc); + + // should return "self" since neighbor is coarser + EXPECT_EQ(edge_loc_neigh.iOct == 1667, true); + EXPECT_EQ(edge_loc_neigh.ijk[IX] == 4, true); + EXPECT_EQ(edge_loc_neigh.ijk[IY] == 0, true); + EXPECT_EQ(edge_loc_neigh.ijk[2] == 2, true); + } + + { + uint32_t iOct = 1664; + const edge_multiindex_t<2> edge_indexes{ 0, 4, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_neigh = + stencil_helper.getBorderEdgeLocSymmetric(edge_loc, EdgeNormalType::DIR1); + + EXPECT_EQ(edge_loc_neigh.iOct == 1647, true); + EXPECT_EQ(edge_loc_neigh.ijk[IX] == 4, true); + EXPECT_EQ(edge_loc_neigh.ijk[IY] == 4, true); + EXPECT_EQ(edge_loc_neigh.ijk[2] == 2, true); + } + + { + uint32_t iOct = 1664; + const edge_multiindex_t<2> edge_indexes{ 0, 4, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_neigh = + stencil_helper.getBorderEdgeLocSymmetric(edge_loc, EdgeNormalType::DIR2); + + EXPECT_EQ(edge_loc_neigh.iOct == 1666, true); + EXPECT_EQ(edge_loc_neigh.ijk[IX] == 0, true); + EXPECT_EQ(edge_loc_neigh.ijk[IY] == 0, true); + EXPECT_EQ(edge_loc_neigh.ijk[2] == 2, true); + } + + { + uint32_t iOct = 1664; + const edge_multiindex_t<2> edge_indexes{ 0, 3, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_neigh = + stencil_helper.getBorderEdgeLocSymmetric(edge_loc, EdgeNormalType::DIR1); + + EXPECT_EQ(edge_loc_neigh.iOct == 1647, true); + EXPECT_EQ(edge_loc_neigh.ijk[IX] == 4, true); + EXPECT_EQ(edge_loc_neigh.ijk[IY] == 2, true); + EXPECT_EQ(edge_loc_neigh.ijk[2] == 2, true); + } + + { + uint32_t iOct = 1664; + const edge_multiindex_t<2> edge_indexes{ 2, 4, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_neigh = + stencil_helper.getBorderEdgeLocSymmetric(edge_loc, EdgeNormalType::DIR1); + + // probably a bit strange, if we shift along the border (stay inside current block), but edge + // index doesn't change + // in normal operation we should not face that case + EXPECT_EQ(edge_loc_neigh.iOct == 1664, true); + EXPECT_EQ(edge_loc_neigh.ijk[IX] == 2, true); + EXPECT_EQ(edge_loc_neigh.ijk[IY] == 4, true); + EXPECT_EQ(edge_loc_neigh.ijk[2] == 2, true); + } + + { + uint32_t iOct = 1279; + const edge_multiindex_t<2> edge_indexes{ 0, 4, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_neigh = + stencil_helper.getBorderEdgeLocSymmetric(edge_loc, EdgeNormalType::DIR1); + + EXPECT_EQ(edge_loc_neigh.iOct == 1278, true); + EXPECT_EQ(edge_loc_neigh.ijk[IX] == 4, true); + EXPECT_EQ(edge_loc_neigh.ijk[IY] == 4, true); + EXPECT_EQ(edge_loc_neigh.ijk[2] == 2, true); + } + + { + uint32_t iOct = 1279; + const edge_multiindex_t<2> edge_indexes{ 1, 4, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_neigh = + stencil_helper.getBorderEdgeLocSymmetric(edge_loc, EdgeNormalType::DIR2); + + EXPECT_EQ(edge_loc_neigh.iOct == 1281, true); + EXPECT_EQ(edge_loc_neigh.ijk[IX] == 2, true); + EXPECT_EQ(edge_loc_neigh.ijk[IY] == 0, true); + EXPECT_EQ(edge_loc_neigh.ijk[2] == 2, true); + } + + { + uint32_t iOct = 1279; + const edge_multiindex_t<2> edge_indexes{ 2, 4, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_neigh = + stencil_helper.getBorderEdgeLocSymmetric(edge_loc, EdgeNormalType::DIAGONAL); + + EXPECT_EQ(edge_loc_neigh.iOct == 1282, true); + EXPECT_EQ(edge_loc_neigh.ijk[IX] == 0, true); + EXPECT_EQ(edge_loc_neigh.ijk[IY] == 0, true); + EXPECT_EQ(edge_loc_neigh.ijk[2] == 2, true); + } + + // =================================================================== + // testing getEdgeSiblingLoc + // =================================================================== + { + uint32_t iOct = 1667; + const edge_multiindex_t<2> edge_indexes{ 4, 0, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 0); + + EXPECT_EQ(get_CellEdgeLocation<2>(edge_loc, block_sizes), EDGE_01); + + EXPECT_EQ(edge_loc_sibling.iOct == 1664, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 4, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 4, true); + EXPECT_EQ(edge_loc_sibling.ijk[2] == 2, true); + } + + { + uint32_t iOct = 1667; + const edge_multiindex_t<2> edge_indexes{ 4, 0, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 1); + + EXPECT_EQ(edge_loc_sibling.iOct == 1665, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 4, true); + EXPECT_EQ(edge_loc_sibling.ijk[2] == 2, true); + } + + { + uint32_t iOct = 1667; + const edge_multiindex_t<2> edge_indexes{ 4, 0, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 2); + + EXPECT_EQ(edge_loc_sibling.iOct == 1670, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[2] == 2, true); + } + + ///////////////// + { + uint32_t iOct = 1664; + const edge_multiindex_t<2> edge_indexes{ 2, 4, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + EXPECT_EQ(get_CellEdgeLocation<2>(edge_loc, block_sizes), EDGE_10); + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 0); + + EXPECT_EQ(edge_loc_sibling.iOct == 1664, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 2, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 4, true); + EXPECT_EQ(edge_loc_sibling.ijk[2] == 2, true); + } + + { + uint32_t iOct = 1664; + const edge_multiindex_t<2> edge_indexes{ 2, 4, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 1); + + EXPECT_EQ(edge_loc_sibling.iOct == 1666, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 4, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[2] == 2, true); + } + + { + uint32_t iOct = 1664; + const edge_multiindex_t<2> edge_indexes{ 2, 4, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 2); + + EXPECT_EQ(edge_loc_sibling.iOct == 1667, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[2] == 2, true); + } + + ///////////////// + { + uint32_t iOct = 1664; + const edge_multiindex_t<2> edge_indexes{ 3, 1, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + EXPECT_EQ(get_CellEdgeLocation<2>(edge_loc, block_sizes), EDGE_00); + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 0); + + EXPECT_EQ(edge_loc_sibling.iOct == 1664, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 3, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 1, true); + EXPECT_EQ(edge_loc_sibling.ijk[2] == 2, true); + } + + { + uint32_t iOct = 1664; + const edge_multiindex_t<2> edge_indexes{ 3, 1, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 1); + + EXPECT_EQ(edge_loc_sibling.iOct == 1664, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 3, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 1, true); + EXPECT_EQ(edge_loc_sibling.ijk[2] == 2, true); + } + + { + uint32_t iOct = 1664; + const edge_multiindex_t<2> edge_indexes{ 3, 1, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 2); + + EXPECT_EQ(edge_loc_sibling.iOct == 1664, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 3, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 1, true); + EXPECT_EQ(edge_loc_sibling.ijk[2] == 2, true); + } + + ///////////////// + { + uint32_t iOct = 1279; + const edge_multiindex_t<2> edge_indexes{ 4, 4, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + EXPECT_EQ(get_CellEdgeLocation<2>(edge_loc, block_sizes), EDGE_11); + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 0); + + EXPECT_EQ(edge_loc_sibling.iOct == 1626, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 4, true); + EXPECT_EQ(edge_loc_sibling.ijk[2] == 2, true); + } + + { + uint32_t iOct = 1279; + const edge_multiindex_t<2> edge_indexes{ 4, 4, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 1); + + EXPECT_EQ(edge_loc_sibling.iOct == 1282, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 4, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[2] == 2, true); + } + + { + uint32_t iOct = 1279; + const edge_multiindex_t<2> edge_indexes{ 4, 4, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 2); + + EXPECT_EQ(edge_loc_sibling.iOct == 1632, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[2] == 2, true); + } + + ///////////////// + { + uint32_t iOct = 1725; + const edge_multiindex_t<2> edge_indexes{ 0, 4, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + EXPECT_EQ(get_CellEdgeLocation<2>(edge_loc, block_sizes), EDGE_10); + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 0); + + EXPECT_EQ(edge_loc_sibling.iOct == 1712, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 4, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 4, true); + EXPECT_EQ(edge_loc_sibling.ijk[2] == 2, true); + } + + { + uint32_t iOct = 1725; + const edge_multiindex_t<2> edge_indexes{ 0, 4, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 1); + + EXPECT_EQ(edge_loc_sibling.iOct == 1714, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 4, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[2] == 2, true); + } + + { + uint32_t iOct = 1725; + const edge_multiindex_t<2> edge_indexes{ 0, 4, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 2); + + EXPECT_EQ(edge_loc_sibling.iOct == 1728, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[2] == 2, true); + } + + ///////////////// + { + uint32_t iOct = 594; + const edge_multiindex_t<2> edge_indexes{ 4, 0, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + EXPECT_EQ(get_CellEdgeLocation<2>(edge_loc, block_sizes), EDGE_01); + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 0); + + EXPECT_EQ(edge_loc_sibling.iOct == 592, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 4, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 4, true); + EXPECT_EQ(edge_loc_sibling.ijk[2] == 2, true); + } + + { + uint32_t iOct = 594; + const edge_multiindex_t<2> edge_indexes{ 4, 0, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 1); + + EXPECT_EQ(edge_loc_sibling.iOct == 1287, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 4, true); + EXPECT_EQ(edge_loc_sibling.ijk[2] == 2, true); + } + + { + uint32_t iOct = 594; + const edge_multiindex_t<2> edge_indexes{ 4, 0, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 2); + + EXPECT_EQ(edge_loc_sibling.iOct == 1293, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[2] == 2, true); + } + + ///////////////// should test invalid edge lcations + { + uint32_t iOct = 1725; + const edge_multiindex_t<2> edge_indexes{ 0, 3, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + EXPECT_EQ(get_CellEdgeLocation<2>(edge_loc, block_sizes), EDGE_00); + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 0); + + EXPECT_EQ(edge_loc_sibling.iOct == 1712, true); + // EXPECT_EQ(edge_loc_sibling.ijk[IX] == 4, true); + // EXPECT_EQ(edge_loc_sibling.ijk[IY] == 4, true); + EXPECT_EQ(edge_loc_sibling.is_valid, false); + EXPECT_EQ(edge_loc_sibling.ijk[2] == 2, true); + } + + { + uint32_t iOct = 1725; + const edge_multiindex_t<2> edge_indexes{ 0, 3, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 1); + + EXPECT_EQ(edge_loc_sibling.iOct == 1725, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 3, true); + EXPECT_EQ(edge_loc_sibling.is_valid, true); + EXPECT_EQ(edge_loc_sibling.ijk[2] == 2, true); + } + + { + uint32_t iOct = 1725; + const edge_multiindex_t<2> edge_indexes{ 0, 3, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 2); + + EXPECT_EQ(edge_loc_sibling.iOct == 1712, true); + // EXPECT_EQ(edge_loc_sibling.ijk[IX] == 0, true); + // EXPECT_EQ(edge_loc_sibling.ijk[IY] == 0, true); + EXPECT_EQ(edge_loc_sibling.is_valid, false); + EXPECT_EQ(edge_loc_sibling.ijk[2] == 2, true); + } + + ///////////////// should test invalid edge lcations + { + uint32_t iOct = 1726; + const edge_multiindex_t<2> edge_indexes{ 3, 4, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + EXPECT_EQ(get_CellEdgeLocation<2>(edge_loc, block_sizes), EDGE_10); + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 0); + + EXPECT_EQ(edge_loc_sibling.iOct == 1726, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 3, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 4, true); + EXPECT_EQ(edge_loc_sibling.is_valid, true); + EXPECT_EQ(edge_loc_sibling.ijk[2] == 2, true); + } + + { + uint32_t iOct = 1726; + const edge_multiindex_t<2> edge_indexes{ 3, 4, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 1); + + EXPECT_EQ(edge_loc_sibling.iOct == 1728, true); + // EXPECT_EQ(edge_loc_sibling.ijk[IX] == 0, true); + // EXPECT_EQ(edge_loc_sibling.ijk[IY] == 3, true); + EXPECT_EQ(edge_loc_sibling.is_valid, false); + EXPECT_EQ(edge_loc_sibling.ijk[2] == 2, true); + } + + { + uint32_t iOct = 1726; + const edge_multiindex_t<2> edge_indexes{ 3, 4, IZ }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<2> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 2); + + EXPECT_EQ(edge_loc_sibling.iOct == 1728, true); + // EXPECT_EQ(edge_loc_sibling.ijk[IX] == 0, true); + // EXPECT_EQ(edge_loc_sibling.ijk[IY] == 0, true); + EXPECT_EQ(edge_loc_sibling.is_valid, false); + EXPECT_EQ(edge_loc_sibling.ijk[2] == 2, true); + } + + } // dim ==2 + else if constexpr (dim == 3) + { + // =================================================================== + // testing getEdgeSiblingLoc + // =================================================================== + { + uint32_t iOct = 29; + const edge_multiindex_t<3> edge_indexes{ 0, 0, 4, IX }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<3> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 0); + + EXPECT_EQ(get_CellEdgeLocation<3>(edge_loc, block_sizes), EDGE_10); + + EXPECT_EQ(edge_loc_sibling.iOct == 3, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 2, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 4, true); + EXPECT_EQ(edge_loc_sibling.ijk[IZ] == 4, true); + EXPECT_EQ(edge_loc_sibling.ijk[dim] == IX, true); + } + + { + uint32_t iOct = 29; + const edge_multiindex_t<3> edge_indexes{ 0, 0, 4, IX }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<3> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 1); + + EXPECT_EQ(edge_loc_sibling.iOct == 7, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 2, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 4, true); + EXPECT_EQ(edge_loc_sibling.ijk[IZ] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[dim] == IX, true); + } + + { + uint32_t iOct = 29; + const edge_multiindex_t<3> edge_indexes{ 0, 0, 4, IX }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<3> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 2); + + EXPECT_EQ(edge_loc_sibling.iOct == 56, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 2, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[IZ] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[dim] == IX, true); + } + + //////////////////// + { + uint32_t iOct = 106; + const edge_multiindex_t<3> edge_indexes{ 2, 2, 0, IX }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<3> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 0); + + EXPECT_EQ(get_CellEdgeLocation<3>(edge_loc, block_sizes), EDGE_00); + + EXPECT_EQ(edge_loc_sibling.iOct == 79, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 4, true); + EXPECT_EQ(edge_loc_sibling.ijk[IZ] == 4, true); + EXPECT_EQ(edge_loc_sibling.ijk[dim] == IX, true); + } + + { + uint32_t iOct = 106; + const edge_multiindex_t<3> edge_indexes{ 2, 2, 0, IX }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<3> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 1); + + EXPECT_EQ(edge_loc_sibling.iOct == 81, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[IZ] == 4, true); + EXPECT_EQ(edge_loc_sibling.ijk[dim] == IX, true); + } + + { + uint32_t iOct = 106; + const edge_multiindex_t<3> edge_indexes{ 2, 2, 0, IX }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<3> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 2); + + EXPECT_EQ(edge_loc_sibling.iOct == 106, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 2, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 2, true); + EXPECT_EQ(edge_loc_sibling.ijk[IZ] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[dim] == IX, true); + } + + //////////////////// + { + uint32_t iOct = 126; + const edge_multiindex_t<3> edge_indexes{ 4, 3, 4, IY }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<3> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 0); + + EXPECT_EQ(get_CellEdgeLocation<3>(edge_loc, block_sizes), EDGE_11); + + EXPECT_EQ(edge_loc_sibling.iOct == 147, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 3, true); + EXPECT_EQ(edge_loc_sibling.ijk[IZ] == 4, true); + EXPECT_EQ(edge_loc_sibling.ijk[dim] == IY, true); + } + + { + uint32_t iOct = 126; + const edge_multiindex_t<3> edge_indexes{ 4, 3, 4, IY }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<3> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 1); + + EXPECT_EQ(edge_loc_sibling.iOct == 144, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 4, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 3, true); + EXPECT_EQ(edge_loc_sibling.ijk[IZ] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[dim] == IY, true); + } + + { + uint32_t iOct = 126; + const edge_multiindex_t<3> edge_indexes{ 4, 3, 4, IY }; + const auto key_cur = orchard_keys_device(iOct); + const EdgeLocation<3> edge_loc{ edge_indexes, key_cur, iOct, false }; + + const auto edge_loc_sibling = stencil_helper.getEdgeSiblingLoc(edge_loc, 2); + + EXPECT_EQ(edge_loc_sibling.iOct == 160, true); + EXPECT_EQ(edge_loc_sibling.ijk[IX] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[IY] == 2, true); + EXPECT_EQ(edge_loc_sibling.ijk[IZ] == 0, true); + EXPECT_EQ(edge_loc_sibling.ijk[dim] == IY, true); + } + + + } // dim == 3 + +} // run_test + +// ==================================================================== +// ==================================================================== +TEST(kalypsso_shared, stencil_helper) +{ + + // initialize mpi, kokkos and p4est, use default MPI communicator : MPI_COMM_WORLD + ParallelEnv & par_env = *(*g_par_env_ptr); + +#ifdef KALYPSSO_CORE_USE_SPDLOG + kalypsso_spdlog_config(*global_argc, *global_argv, par_env.rank(), par_env.size()); +#endif + + { + // run a 2d test + ConfigMap config_map = broadcast_parameters("./StencilHelper_2d.ini"); + kalypsso::run_test<2, kalypsso::HostDevice>(par_env, config_map); + } + + // TODO make 3d mesh smaller (currently much too large for unit testing) + { + // run a 3d test + ConfigMap config_map = broadcast_parameters("./StencilHelper_3d.ini"); + kalypsso::run_test<3, kalypsso::HostDevice>(par_env, config_map); + } + +} // TEST + +// ==================================================================== +// ==================================================================== +TEST(kalypsso_shared, test_edge_utils) +{ + // 2d - testing EdgeUtils + { + const auto shift0 = EdgeUtils::edge_neighbor_shift<2>(EDGE_10, 0, IZ); + EXPECT_EQ(shift0[IX] == -1, true); + EXPECT_EQ(shift0[IY] == 0, true); + + const auto shift1 = EdgeUtils::edge_neighbor_shift<2>(EDGE_11, 2, IZ); + EXPECT_EQ(shift1[IX] == 1, true); + EXPECT_EQ(shift1[IY] == 1, true); + + const auto shift2 = EdgeUtils::edge_neighbor_shift<2>(EDGE_00, 1, IZ); + EXPECT_EQ(shift2[IX] == 0, true); + EXPECT_EQ(shift2[IY] == -1, true); + } + + // 3d - testing EdgeUtils + { + { + const auto shift0 = EdgeUtils::edge_neighbor_shift<3>(EDGE_10, 0, IZ); + EXPECT_EQ(shift0[IX] == -1, true); + EXPECT_EQ(shift0[IY] == 0, true); + EXPECT_EQ(shift0[IZ] == 0, true); + } + + { + const auto shift0 = EdgeUtils::edge_neighbor_shift<3>(EDGE_10, 0, IX); + EXPECT_EQ(shift0[IX] == 0, true); + EXPECT_EQ(shift0[IY] == -1, true); + EXPECT_EQ(shift0[IZ] == 0, true); + } + + { + const auto shift0 = EdgeUtils::edge_neighbor_shift<3>(EDGE_10, 0, IY); + EXPECT_EQ(shift0[IX] == -1, true); + EXPECT_EQ(shift0[IY] == 0, true); + EXPECT_EQ(shift0[IZ] == 0, true); + } + + { + const auto shift0 = EdgeUtils::edge_neighbor_shift<3>(EDGE_01, 2, IZ); + EXPECT_EQ(shift0[IX] == 1, true); + EXPECT_EQ(shift0[IY] == 0, true); + EXPECT_EQ(shift0[IZ] == 0, true); + } + + { + const auto shift0 = EdgeUtils::edge_neighbor_shift<3>(EDGE_01, 2, IX); + EXPECT_EQ(shift0[IX] == 0, true); + EXPECT_EQ(shift0[IY] == 1, true); + EXPECT_EQ(shift0[IZ] == 0, true); + } + + { + const auto shift0 = EdgeUtils::edge_neighbor_shift<3>(EDGE_01, 2, IY); + EXPECT_EQ(shift0[IX] == 1, true); + EXPECT_EQ(shift0[IY] == 0, true); + EXPECT_EQ(shift0[IZ] == 0, true); + } + + { + const auto shift0 = EdgeUtils::edge_neighbor_shift<3>(EDGE_00, 1, IZ); + EXPECT_EQ(shift0[IX] == 0, true); + EXPECT_EQ(shift0[IY] == -1, true); + EXPECT_EQ(shift0[IZ] == 0, true); + } + + { + const auto shift0 = EdgeUtils::edge_neighbor_shift<3>(EDGE_00, 1, IX); + EXPECT_EQ(shift0[IX] == 0, true); + EXPECT_EQ(shift0[IY] == 0, true); + EXPECT_EQ(shift0[IZ] == -1, true); + } + + { + const auto shift0 = EdgeUtils::edge_neighbor_shift<3>(EDGE_00, 0, IY); + EXPECT_EQ(shift0[IX] == -1, true); + EXPECT_EQ(shift0[IY] == 0, true); + EXPECT_EQ(shift0[IZ] == -1, true); + } + + { + const auto shift0 = EdgeUtils::edge_neighbor_shift<3>(EDGE_11, 0, IY); + EXPECT_EQ(shift0[IX] == 1, true); + EXPECT_EQ(shift0[IY] == 0, true); + EXPECT_EQ(shift0[IZ] == 0, true); + } + } +} + +} // namespace kalypsso diff --git a/unit_test/core/geometry_utils_test.cpp b/unit_test/core/geometry_utils_test.cpp new file mode 100644 index 0000000..9d90852 --- /dev/null +++ b/unit_test/core/geometry_utils_test.cpp @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// © Commissariat a l'Energie Atomique et aux Energies Alternatives (CEA) +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +#include + +#include + +namespace kalypsso +{ + +//================================================================= +// +// basic test +// +//================================================================= + +TEST(kalypsso_get_tangent_to_sphere_test, test_2d) +{ + + Kokkos::Array O{ 0.0, 0.0 }; + Kokkos::Array M{ 0.5, 0.5 }; + real_t radius = 1.0; + + Kokkos::Array normal; + real_t alpha; + + get_tangent_to_sphere(O, radius, M, normal, alpha); + + EXPECT_NEAR(normal[IX], sqrt(2.0) / 2, 1e-14); + EXPECT_NEAR(normal[IY], sqrt(2.0) / 2, 1e-14); + EXPECT_NEAR(alpha, 1.0, 1e-14); +} + +TEST(kalypsso_get_tangent_to_sphere_test, test_2d_2) +{ + + Kokkos::Array O{ 0.25, -0.25 }; + Kokkos::Array M{ 0.75, 0.25 }; + real_t radius = 1.0; + + Kokkos::Array normal; + real_t alpha; + + get_tangent_to_sphere(O, radius, M, normal, alpha); + + EXPECT_NEAR(normal[IX], sqrt(2.0) / 2, 1e-14); + EXPECT_NEAR(normal[IY], sqrt(2.0) / 2, 1e-14); + EXPECT_NEAR(alpha, 1.0, 1e-14); +} + +TEST(kalypsso_get_tangent_to_sphere_test, test_2d_3) +{ + + Kokkos::Array O{ 0.0, 0.0 }; + Kokkos::Array M{ 0.5, 0.0 }; + real_t radius = 1.0; + + Kokkos::Array normal; + real_t alpha; + + get_tangent_to_sphere(O, radius, M, normal, alpha); + + EXPECT_NEAR(normal[IX], 1.0, 1e-14); + EXPECT_NEAR(normal[IY], 0.0, 1e-14); + EXPECT_NEAR(alpha, 1.0, 1e-14); +} + +TEST(kalypsso_get_tangent_to_sphere_test, test_3d) +{ + + Kokkos::Array O{ 0.0, 0.0, 0.0 }; + Kokkos::Array M{ 0.5, 0.5, 0.5 }; + real_t radius = 1.0; + + Kokkos::Array normal; + real_t alpha; + + get_tangent_to_sphere(O, radius, M, normal, alpha); + + EXPECT_NEAR(normal[IX], sqrt(3.0) / 3, 1e-14); + EXPECT_NEAR(normal[IY], sqrt(3.0) / 3, 1e-14); + EXPECT_NEAR(normal[IZ], sqrt(3.0) / 3, 1e-14); + EXPECT_NEAR(alpha, 1.0, 1e-14); +} + +} // namespace kalypsso diff --git a/unit_test/core/kalypsso_data_container_test.cpp b/unit_test/core/kalypsso_data_container_test.cpp new file mode 100644 index 0000000..370ac3c --- /dev/null +++ b/unit_test/core/kalypsso_data_container_test.cpp @@ -0,0 +1,847 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// © Commissariat à l'Energie Atomique et aux Energies Alternatives (CEA) +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#define KALYPSSO_CORE_USE_NEW_DATA_ARRAY_GHOSTED_BLOCK_IMPL +#include + +#include + +#include "gtest/gtest.h" + +#include "kalypsso_unittest_utils.h" + +namespace kalypsso +{ + +// ==================================================================== +// ==================================================================== +void +check_DataArray_resize() +{ + { + using device_t = HostDevice; + using exec_space = Kokkos::DefaultHostExecutionSpace; + + using DataArray_t = DataArray; + + DataArrayUtils::set_growth_rate(KALYPSSO_NUM(1.2)); + const size_t num_elts = 18; + auto data = DataArray_t("data", num_elts); + + EXPECT_EQ(data.num_elements(), 18); + EXPECT_EQ(data.allocated_size_in_bytes(), 21 * sizeof(int64_t)); + + data.resize(32); + EXPECT_EQ(data.allocated_size_in_bytes(), 38 * sizeof(int64_t)); + + DataArrayUtils::set_growth_rate(KALYPSSO_NUM(1.0)); + } +} + +// ==================================================================== +// ==================================================================== +void +check_DataArrayBlock_resize_2d() +{ + constexpr int dim = 2; + + // using device_t = DefaultDevice; + // using exec_space = Kokkos::DefaultExecutionSpace; + using device_t = HostDevice; + using exec_space = Kokkos::DefaultHostExecutionSpace; + + using DataArrayBlock_t = DataArrayBlock; + + const auto bSize = block_size_t{ 3, 5 }; + const uint32_t num_vars = 2; + const uint32_t num_octs = 4; + auto data = DataArrayBlock_t("data", bSize, num_vars, num_octs); + + // check that capacity growth rate is equal to default + EXPECT_REAL_EQ(DataArrayUtils::m_capacity_growth_rate, + DataArrayUtils::m_capacity_growth_rate_default); + + const auto num_elements = Kokkos::dim_prod(bSize) * num_vars * num_octs; + uint64_t num_bytes = num_elements * sizeof(real_t); + + // assuming DataArrayUtils::m_capacity_growth_rate_default is 1.0 + EXPECT_EQ(data.allocated_size_in_bytes(), num_bytes); + + { + data.resize(8); + num_bytes = Kokkos::dim_prod(bSize) * num_vars * 8 * sizeof(real_t); + EXPECT_EQ(data.allocated_size_in_bytes(), num_bytes); + } + + DataArrayUtils::set_growth_rate(KALYPSSO_NUM(1.2)); + + { + data.resize(9); + // 3*5*2*9*1.2 = 324 + num_bytes = 324 * sizeof(real_t); + EXPECT_EQ(data.allocated_size_in_bytes(), num_bytes); + } + + { + // capacity should not change because new size is smaller than capacity + data.resize(10); + // 3*5*2*10 = 300 < 324 + num_bytes = 324 * sizeof(real_t); + EXPECT_EQ(data.allocated_size_in_bytes(), num_bytes); + } + + { + // capacity should change because new is larger than capacity + data.resize(13); + // 3*5*2*13*1.2 = 468 > 324 + num_bytes = 468 * sizeof(real_t); + EXPECT_EQ(data.allocated_size_in_bytes(), num_bytes); + } + + DataArrayUtils::set_growth_rate(DataArrayUtils::m_capacity_growth_rate_default); +} + +// ==================================================================== +// ==================================================================== +void +run_kalypsso_data_container_DataArrayBlock_2d() +{ + constexpr int dim = 2; + + // using device_t = DefaultDevice; + // using exec_space = Kokkos::DefaultExecutionSpace; + using device_t = HostDevice; + using exec_space = Kokkos::DefaultHostExecutionSpace; + + using DataArrayBlock_t = DataArrayBlock; + + const auto bSize = block_size_t{ 3, 5 }; + const uint32_t num_vars = 2; + const uint32_t num_octs = 4; + auto data = DataArrayBlock_t("data", bSize, num_vars, num_octs); + + EXPECT_EQ(bSize, data.block_size()); + EXPECT_EQ(data.num_cells(), 15); + + const auto nbCellsTotal = data.num_cells() * data.num_vars() * data.num_quadrants(); + EXPECT_EQ(nbCellsTotal, 120); + + { + // 3 * (3*5*2) + 0 * (3*5) + 1*3 + 2 + EXPECT_EQ(data.flat_index(2, 1, 0, 3), 95); + + // 1 * (3*5*2) + 1 * (3*5) + 1*3 + 1 + EXPECT_EQ(data.flat_index(1, 1, 1, 1), 49); + } + + { + size_t flat_index = 95; + auto mindex = flat_index_unravel(flat_index, bSize, num_vars); + EXPECT_EQ(mindex[dim + 1], 3); + EXPECT_EQ(mindex[dim], 0); + EXPECT_EQ(mindex[IY], 1); + EXPECT_EQ(mindex[IX], 2); + } + + { + size_t flat_index = 49; + auto mindex = flat_index_unravel(flat_index, bSize, num_vars); + EXPECT_EQ(mindex[dim + 1], 1); + EXPECT_EQ(mindex[dim], 1); + EXPECT_EQ(mindex[IY], 1); + EXPECT_EQ(mindex[IX], 1); + } + + // change shape (equal size) + { + const auto new_shape = block_size_t{ 5, 3 }; + data.reshape(new_shape); + + // 3 * (5*3*2) + 0 * (5*3) + 1*5 + 0 + EXPECT_EQ(data.flat_index(0, 1, 0, 3), 95); + + // 1 * (5*3*2) + 1 * (5*3) + 0*5 + 4 + EXPECT_EQ(data.flat_index(4, 0, 1, 1), 49); + } + + // change shape (smaller size) + { + const auto new_shape = block_size_t{ 2, 3 }; + data.reshape(new_shape); + + EXPECT_EQ(data.shape()[IX], 2); + EXPECT_EQ(data.shape()[IY], 3); + + // 7 * (2*3*2) + 1 * (2*3) + 2*2 + 1 + EXPECT_EQ(data.flat_index(1, 2, 1, 7), 95); + } + + // change shape (larger size => should be no-op) + { + const auto old_shape = data.shape(); + const auto new_shape = block_size_t{ 12, 3 }; + data.reshape(new_shape); + + EXPECT_EQ(data.shape(), old_shape); + } + + data.shape_reset(); + auto nbCellsTotal2 = data.num_cells() * data.num_vars() * data.num_quadrants(); + + Kokkos::parallel_for( + "fill", Kokkos::RangePolicy(0, nbCellsTotal2), KOKKOS_LAMBDA(uint32_t flat_index) { + const auto mindex = flat_index_unravel(flat_index, data.block_size(), data.num_vars()); + data(mindex[IX], mindex[IY], mindex[dim], mindex[dim + 1]) = flat_index; + }); + + auto data_h = DataArrayBlock_t::create_host_mirror_view_and_copy(data); + + auto ij = coord_t<2>{ 2, 4 }; + EXPECT_EQ(data_h(14, 0, 0), 14); + EXPECT_EQ(data_h(2, 4, 0, 0), 14); + EXPECT_EQ(data_h(ij, 0, 0), 14); + + ij[IX] = 1; + EXPECT_EQ(data_h(13, 1, 3), 118); + EXPECT_EQ(data_h(1, 4, 1, 3), 118); + EXPECT_EQ(data_h(ij, 1, 3), 118); + + // resize with 5 octants + data.resize_and_reset(5); + auto data_h2 = DataArrayBlock_t::create_host_mirror_view_and_copy(data); + EXPECT_EQ(data_h2(14, 0, 0), 0); + EXPECT_EQ(data_h2(13, 1, 3), 0); + +} // run_kalypsso_data_container_DataArrayBlock_2d + +// ==================================================================== +// ==================================================================== +void +run_kalypsso_data_container_DataArrayBlock_3d() +{ + constexpr int dim = 3; + + // using device_t = DefaultDevice; + // using exec_space = Kokkos::DefaultExecutionSpace; + using device_t = HostDevice; + using exec_space = Kokkos::DefaultHostExecutionSpace; + + using DataArrayBlock_t = DataArrayBlock; + + const auto bSize = block_size_t{ 4, 3, 2 }; + const uint32_t num_vars = 2; + const uint32_t num_octs = 4; + auto data = DataArrayBlock_t("data", bSize, num_vars, num_octs); + + EXPECT_EQ(bSize, data.block_size()); + EXPECT_EQ(data.num_cells(), 24); + + const auto nbCellsTotal = data.num_cells() * data.num_vars() * data.num_quadrants(); + EXPECT_EQ(nbCellsTotal, 192); + + { + // 1 * (4*3*2*2) + 1 * (4*3*2) + 1 * (4*3) + 2 * (4) + 3 + EXPECT_EQ(data.flat_index(3, 2, 1, 1, 1), 95); + + // 1 * (4*3*2*2) + 0 * (4*3*2) + 0 * (4*3) + 0 * (4) + 1 + EXPECT_EQ(data.flat_index(1, 0, 0, 0, 1), 49); + + // 2 * (4*3*2*2) + 1 * (4*3*2) + 0 * (4*3) + 1 * (4) + 3 + EXPECT_EQ(data.flat_index(3, 1, 0, 1, 2), 127); + } + + { + size_t flat_index = 95; + auto mindex = flat_index_unravel(flat_index, bSize, num_vars); + EXPECT_EQ(mindex[dim + 1], 1); + EXPECT_EQ(mindex[dim], 1); + EXPECT_EQ(mindex[IZ], 1); + EXPECT_EQ(mindex[IY], 2); + EXPECT_EQ(mindex[IX], 3); + } + + { + size_t flat_index = 49; + auto mindex = flat_index_unravel(flat_index, bSize, num_vars); + EXPECT_EQ(mindex[dim + 1], 1); + EXPECT_EQ(mindex[dim], 0); + EXPECT_EQ(mindex[IZ], 0); + EXPECT_EQ(mindex[IY], 0); + EXPECT_EQ(mindex[IX], 1); + } + + { + size_t flat_index = 127; + auto mindex = flat_index_unravel(flat_index, bSize, num_vars); + EXPECT_EQ(mindex[dim + 1], 2); + EXPECT_EQ(mindex[dim], 1); + EXPECT_EQ(mindex[IZ], 0); + EXPECT_EQ(mindex[IY], 1); + EXPECT_EQ(mindex[IX], 3); + } + + // change shape (equal size) + { + const auto new_shape = block_size_t{ 2, 3, 4 }; + data.reshape(new_shape); + + // 1 * (2*3*4*2) + 1 * (2*3*4) + 3 * (2*3) + 2 * (2) + 1 + EXPECT_EQ(data.flat_index(1, 2, 3, 1, 1), 95); + + // 1 * (2*3*4*2) + 0 * (2*3*4) + 0 * (2*3) + 1 * (2) + 1 + EXPECT_EQ(data.flat_index(1, 1, 0, 0, 1), 51); + } + + // change shape (smaller size) + { + const auto new_shape = block_size_t{ 2, 3, 1 }; + data.reshape(new_shape); + + EXPECT_EQ(data.shape()[IX], 2); + EXPECT_EQ(data.shape()[IY], 3); + EXPECT_EQ(data.shape()[IZ], 1); + + // 7 * (2*3*1*2) + 1 * (2*3*1) + 0 * (2*3) + 2 * (2) + 1 + EXPECT_EQ(data.flat_index(1, 2, 0, 1, 7), 95); + } + + // change shape (larger size => should be no-op) + { + const auto old_shape = data.shape(); + const auto new_shape = block_size_t{ 12, 13, 14 }; + data.reshape(new_shape); + + EXPECT_EQ(data.shape(), old_shape); + } + + data.shape_reset(); + auto nbCellsTotal2 = data.num_cells() * data.num_vars() * data.num_quadrants(); + + Kokkos::parallel_for( + "fill", Kokkos::RangePolicy(0, nbCellsTotal2), KOKKOS_LAMBDA(uint32_t flat_index) { + const auto mindex = flat_index_unravel(flat_index, data.block_size(), data.num_vars()); + data(mindex[IX], mindex[IY], mindex[IZ], mindex[3], mindex[4]) = flat_index; + }); + + auto data_h = DataArrayBlock_t::create_host_mirror_view_and_copy(data); + + auto ijk = coord_t<3>{ 2, 0, 1 }; + EXPECT_EQ(data_h(14, 0, 0), 14); + EXPECT_EQ(data_h(ijk, 0, 0), 14); + + ijk[IX] = 1; + EXPECT_EQ(data_h(13, 1, 3), 181); + EXPECT_EQ(data_h(ijk, 1, 3), 181); + + // resize with 5 octants + data.resize_and_reset(5); + auto data_h2 = DataArrayBlock_t::create_host_mirror_view_and_copy(data); + EXPECT_EQ(data_h2(14, 0, 0), 0); + EXPECT_EQ(data_h2(13, 1, 3), 0); + +} // run_kalypsso_data_container_DataArrayBlock_3d + +// ==================================================================== +// ==================================================================== +void +run_kalypsso_data_container_FaceDataArrayBlock_2d_noghost() +{ + constexpr int dim = 2; + + // using device_t = DefaultDevice; + // using exec_space = Kokkos::DefaultExecutionSpace; + using device_t = HostDevice; + using exec_space = Kokkos::DefaultHostExecutionSpace; + + using FaceDataArrayBlock_t = FaceDataArrayBlock; + + const auto bSize = block_size_t{ 3, 5 }; + const auto gSize = 0; + const uint32_t num_octs = 4; + auto facedata = FaceDataArrayBlock_t("facedata", bSize, gSize, num_octs); + + EXPECT_EQ(bSize, facedata.cell_block_size_inner()); + EXPECT_EQ(facedata.num_elements_per_octant(), 53); // (3+1)*5 + 3*(5+1) + 3*5 + + EXPECT_EQ(facedata.shift()[IX], 0); + EXPECT_EQ(facedata.shift()[IY], 0); + + EXPECT_EQ(facedata.cell_block_size()[IX], 3); + EXPECT_EQ(facedata.cell_block_size()[IY], 5); + + { + // 0 + 2 + 1*(3+1) + 53*3 + EXPECT_EQ(facedata.flat_index(2, 1, 3), 165); + + // (3+1)*5 + 1 + 1*(3) + 53 + EXPECT_EQ(facedata.flat_index(1, 1, 1), 77); + } + + { + size_t flat_index = 165 - 3 * 53; + auto mindex = + face_flat_index_unravel(flat_index, bSize, facedata.offsets(), facedata.shift()); + EXPECT_EQ(mindex[IX], 2); + EXPECT_EQ(mindex[IY], 1); + EXPECT_EQ(mindex[dim], 0); + } + + { + size_t flat_index = 77 - 53; + auto mindex = + face_flat_index_unravel(flat_index, bSize, facedata.offsets(), facedata.shift()); + EXPECT_EQ(mindex[IX], 1); + EXPECT_EQ(mindex[IY], 1); + EXPECT_EQ(mindex[dim], 1); + } + + const auto nbFacesPerLeaf = facedata.num_elements_per_octant(); + const auto nbFacesTotal = nbFacesPerLeaf * facedata.num_quadrants(); + + Kokkos::parallel_for( + "fill", Kokkos::RangePolicy(0, nbFacesTotal), KOKKOS_LAMBDA(uint32_t global_index) { + const auto iOct = global_index / nbFacesPerLeaf; + const auto face_flat_index = global_index - iOct * nbFacesPerLeaf; + + const auto mindex = face_flat_index_unravel( + face_flat_index, facedata.cell_block_size(), facedata.offsets(), facedata.shift()); + + facedata(mindex[IX], mindex[IY], mindex[dim], iOct) = face_flat_index; + }); + + const auto facedata_x_h = FaceDataArrayBlock_t::to_DataArrayBlockCentered(facedata, IX); + const auto facedata_y_h = FaceDataArrayBlock_t::to_DataArrayBlockCentered(facedata, IY); + + EXPECT_EQ(facedata_x_h(0, 0, 0), 0.5); + EXPECT_EQ(facedata_y_h(0, 0, 0), 21.5); // (20+23)/2 + +} // run_kalypsso_data_container_FaceDataArrayBlock_2d_noghost + +// ==================================================================== +// ==================================================================== +void +run_kalypsso_data_container_FaceDataArrayBlock_2d_ghost() +{ + constexpr int dim = 2; + + // using device_t = DefaultDevice; + // using exec_space = Kokkos::DefaultExecutionSpace; + using device_t = HostDevice; + using exec_space = Kokkos::DefaultHostExecutionSpace; + + using FaceDataArrayBlock_t = FaceDataArrayBlock; + + const auto bSize_inner = block_size_t{ 3, 5 }; + const auto gSize = 2; + const auto bSize = bSize_inner + 2 * gSize; + const uint32_t num_octs = 4; + auto facedata = FaceDataArrayBlock_t("facedata", bSize_inner, gSize, num_octs); + + EXPECT_EQ(bSize_inner[IX], facedata.cell_block_size_inner()[IX]); + EXPECT_EQ(bSize_inner[IY], facedata.cell_block_size_inner()[IY]); + EXPECT_EQ(facedata.num_elements_per_octant(), 205); // (7+1)*9 + 7*(9+1) + 7*9 + + EXPECT_EQ(facedata.shift()[IX], -2); + EXPECT_EQ(facedata.shift()[IY], -2); + + { + // 0 + 2 + 1*(7+1) + 205*3 + EXPECT_EQ(facedata.flat_index(2, 1, 3), 625); + + // (7+1)*9 + 1 + 1*(7) + 205 + EXPECT_EQ(facedata.flat_index(1, 1, 1), 285); + } + + { + size_t flat_index = 625 - 3 * 205; + auto mindex = + face_flat_index_unravel(flat_index, bSize, facedata.offsets(), facedata.shift()); + EXPECT_EQ(mindex[IX] + gSize, 2); + EXPECT_EQ(mindex[IY] + gSize, 1); + EXPECT_EQ(mindex[dim], 0); + } + + { + size_t flat_index = 285 - 205; + auto mindex = + face_flat_index_unravel(flat_index, bSize, facedata.offsets(), facedata.shift()); + EXPECT_EQ(mindex[IX] + gSize, 1); + EXPECT_EQ(mindex[IY] + gSize, 1); + EXPECT_EQ(mindex[dim], 1); + } + + const auto nbFacesPerLeaf = facedata.num_elements_per_octant(); + const auto nbFacesTotal = nbFacesPerLeaf * facedata.num_quadrants(); + + Kokkos::parallel_for( + "fill", Kokkos::RangePolicy(0, nbFacesTotal), KOKKOS_LAMBDA(uint32_t global_index) { + const auto iOct = global_index / nbFacesPerLeaf; + const auto face_flat_index = global_index - iOct * nbFacesPerLeaf; + + const auto mindex = face_flat_index_unravel( + face_flat_index, facedata.cell_block_size(), facedata.offsets(), facedata.shift()); + + facedata(mindex[IX], mindex[IY], mindex[dim], iOct) = face_flat_index; + }); + + const auto facedata_x_h = FaceDataArrayBlock_t::to_DataArrayBlockCentered(facedata, IX); + const auto facedata_y_h = FaceDataArrayBlock_t::to_DataArrayBlockCentered(facedata, IY); + + EXPECT_EQ(facedata_x_h(0, 0, 0), 0.5); + EXPECT_EQ(facedata_y_h(0, 0, 0), 75.5); // (72+79)/2 + +} // run_kalypsso_data_container_FaceDataArrayBlock_2d_ghost + +// ==================================================================== +// ==================================================================== +void +check_FaceDataArrayBlock_resize_2d() +{ + constexpr int dim = 2; + + // using device_t = DefaultDevice; + // using exec_space = Kokkos::DefaultExecutionSpace; + using device_t = HostDevice; + using exec_space = Kokkos::DefaultHostExecutionSpace; + + using FaceDataArrayBlock_t = FaceDataArrayBlock; + + const auto bSize = block_size_t{ 3, 5 }; + const auto gSize = 0; + const uint32_t num_octs = 4; + auto facedata = FaceDataArrayBlock_t("facedata", bSize, gSize, num_octs); + + const auto num_elements = facedata.num_elements_per_octant() * num_octs; + uint64_t num_bytes = num_elements * sizeof(real_t); + + // assuming DataArrayUtils::m_capacity_growth_rate_default is 1.0 + EXPECT_EQ(facedata.allocated_size_in_bytes(), num_bytes); + + { + facedata.resize(8); + num_bytes = ((bSize[0] + 1) * bSize[1] + bSize[0] * (bSize[1] + 1) + bSize[0] * bSize[1]) * 8 * + sizeof(real_t); + EXPECT_EQ(facedata.allocated_size_in_bytes(), num_bytes); + } + + DataArrayUtils::set_growth_rate(KALYPSSO_NUM(1.2)); + + { + facedata.resize(9); + // (4*5+3*6+3*5)*9*1.2 = 572 + num_bytes = 572 * sizeof(real_t); + EXPECT_EQ(facedata.allocated_size_in_bytes(), num_bytes); + } + + { + // capacity should not change because new size is smaller than capacity + facedata.resize(10); + // (4*5+3*6+3*5)*10 = 530 < 572 + num_bytes = 572 * sizeof(real_t); + EXPECT_EQ(facedata.allocated_size_in_bytes(), num_bytes); + } + + { + // capacity should change because new is larger than capacity + facedata.resize(13); + // (4*5+3*6+3*5)*13*1.2 = 826 > 572 + num_bytes = 826 * sizeof(real_t); + EXPECT_EQ(facedata.allocated_size_in_bytes(), num_bytes); + } + + DataArrayUtils::set_growth_rate(DataArrayUtils::m_capacity_growth_rate_default); +} + +// ==================================================================== +// ==================================================================== +void +run_kalypsso_data_container_EdgeDataArrayBlock_2d_noghost() +{ + constexpr int dim = 2; + + // using device_t = DefaultDevice; + // using exec_space = Kokkos::DefaultExecutionSpace; + using device_t = HostDevice; + // using exec_space = Kokkos::DefaultHostExecutionSpace; + + using EdgeDataArrayBlock_t = EdgeDataArrayBlock; + + const auto bSize = block_size_t{ 3, 5 }; + const auto gSize = 0; + const uint32_t num_octs = 4; + auto edgedata = EdgeDataArrayBlock_t("edgedata", bSize, gSize, num_octs); + + EXPECT_EQ(bSize, edgedata.cell_block_size_inner()); + EXPECT_EQ(edgedata.num_elements_per_octant(), 62); // 3*(5+1) + (3+1)*5 + (3+1)*(5+1) + + { + // 0*3*(5+1) + 2 + 1*3 + 62*3 = 191 + EXPECT_EQ(edgedata.flat_index(2, 1, 3), 191); + + // 3*(5+1) + 1 + 1*(3+1) + 62 = 85 + EXPECT_EQ(edgedata.flat_index(1, 1, 1), 85); + } + + { + size_t flat_index = 191 - 3 * 62; + auto mindex = edge_flat_index_unravel(flat_index, bSize, edgedata.offsets()); + EXPECT_EQ(mindex[IX], 2); + EXPECT_EQ(mindex[IY], 1); + EXPECT_EQ(mindex[dim], 0); + } + + { + size_t flat_index = 85 - 62; + auto mindex = edge_flat_index_unravel(flat_index, bSize, edgedata.offsets()); + EXPECT_EQ(mindex[IX], 1); + EXPECT_EQ(mindex[IY], 1); + EXPECT_EQ(mindex[dim], 1); + } + +} // run_kalypsso_data_container_EdgeDataArrayBlock_2d_noghost + +// ==================================================================== +// ==================================================================== +TEST(kalypsso_shared_test, kalypsso_data_container_DataArrayBlock) +{ + run_kalypsso_data_container_DataArrayBlock_2d(); + run_kalypsso_data_container_DataArrayBlock_3d(); + run_kalypsso_data_container_FaceDataArrayBlock_2d_noghost(); + run_kalypsso_data_container_FaceDataArrayBlock_2d_ghost(); + run_kalypsso_data_container_EdgeDataArrayBlock_2d_noghost(); + check_DataArray_resize(); + check_DataArrayBlock_resize_2d(); + check_FaceDataArrayBlock_resize_2d(); + +} // TEST: kalypsso_shared_test/kalypsso_data_container_DataArrayBlock + +#ifdef KALYPSSO_CORE_USE_NEW_DATA_ARRAY_GHOSTED_BLOCK_IMPL +// ==================================================================== +// ==================================================================== +TEST(kalypsso_shared_test, kalypsso_data_container_DataArrayGhostedBlock2d) +{ + using device_t = HostDevice; + + { + auto bSize = block_size_t<2>{ 12, 7 }; + auto gSize = block_size_t<2>{ 16, 13 }; + shift_t<2> shift{ -2, -3 }; + int num_vars = 4; + int num_quadrants = 10; + + auto ghosted_data = DataArrayGhostedBlock<2, real_t, device_t>( + bSize, gSize, shift, "data", num_vars, num_quadrants); + + EXPECT_EQ(ghosted_data.num_vars(), 4) + << "DataArrayGhostedBlock<2, device_t>::num_vars() failed"; + EXPECT_EQ(ghosted_data.ghosted_block_size()[0], 16) + << "DataArrayGhostedBlock<2, device_t> wrong total block size"; + EXPECT_EQ(ghosted_data.ghosted_block_size()[1], 13) + << "DataArrayGhostedBlock<2, device_t> wrong total block size"; + + EXPECT_EQ(ghosted_data.block_size()[0], 12) + << "DataArrayGhostedBlock<2, device_t> wrong block size"; + EXPECT_EQ(ghosted_data.block_size()[1], 7) + << "DataArrayGhostedBlock<2, device_t> wrong block size"; + + EXPECT_EQ(Kokkos::dim_prod(ghosted_data.get_block_size_overlap()), 84) + << "wrong overlap block size"; + + auto block_data = ghosted_data.data(); + + EXPECT_EQ(block_data.num_cells(), 208) << "Wrong view size"; + } + + { + using DirectAccess = DataArrayGhostedBlock<2, real_t, device_t>::DirectAccess; + + auto bSize = block_size_t<2>{ 12, 7 }; + auto gSize = block_size_t<2>{ 14, 7 }; + shift_t<2> shift{ -1, 0 }; + int num_vars = 2; + int num_quadrants = 6; + + auto ghosted_data = DataArrayGhostedBlock<2, real_t, device_t>( + bSize, gSize, shift, "data", num_vars, num_quadrants); + + EXPECT_EQ(ghosted_data.num_cells(), 98) + << "DataArrayGhostedBlock<2, device_t>::num_cells() failed"; + EXPECT_EQ(ghosted_data.num_cells_inner(), 84) + << "DataArrayGhostedBlock<2, device_t>::num_cells() failed"; + + ghosted_data.reshape({ 12, 8 }, { 0, -1 }); + EXPECT_EQ(ghosted_data.num_cells(), 96) + << "DataArrayGhostedBlock<2, device_t>::num_cells() failed"; + + for (int j = -1; j < 7; ++j) + for (int i = 0; i < 12; ++i) + ghosted_data(i, j, 0, 3) = i * i + j; + + EXPECT_NEAR(ghosted_data(0, -1, 0, 3), -1.0, 1e-14); + EXPECT_NEAR(ghosted_data(8, 5, 0, 3), 69.0, 1e-14); + + auto ij = coord_t<2>{ 4, 2 }; + EXPECT_NEAR(ghosted_data(ij, 0, 3), 18.0, 1e-14); + + EXPECT_NEAR(ghosted_data(0, 0, 0, 3, DirectAccess{}), -1.0, 1e-14); + EXPECT_NEAR(ghosted_data(8, 6, 0, 3, DirectAccess{}), 69.0, 1e-14); + EXPECT_NEAR(ghosted_data(ij, 0, 3, DirectAccess{}), 17.0, 1e-14); + + ghosted_data.reshape({ 10, 5 }, { -1, -2 }); + EXPECT_EQ(Kokkos::dim_prod(ghosted_data.get_block_size_overlap()), 27) + << "wrong overlap block size"; + + ghosted_data.reshape({ 10, 5 }, { 0, 3 }); + EXPECT_EQ(Kokkos::dim_prod(ghosted_data.get_block_size_overlap()), 40) + << "wrong overlap block size"; + } + +} // TEST: kalypsso_shared_test/kalypsso_data_container_DataArrayGhostedBlock2d + +// ==================================================================== +// ==================================================================== +TEST(kalypsso_shared_test, kalypsso_data_container_DataArrayGhostedBlock3d) +{ + using device_t = HostDevice; + + { + auto bSize = block_size_t<3>{ 4, 6, 2 }; + auto gSize = block_size_t<3>{ 7, 9, 3 }; + shift_t<3> shift{ -2, 0, 1 }; + int num_vars = 2; + int num_quadrants = 4; + + auto ghosted_data = DataArrayGhostedBlock<3, real_t, device_t>( + bSize, gSize, shift, "data", num_vars, num_quadrants); + + EXPECT_EQ(ghosted_data.num_vars(), 2) + << "DataArrayGhostedBlock<3, device_t>::num_vars() failed"; + EXPECT_EQ(ghosted_data.ghosted_block_size()[0], 7) + << "DataArrayGhostedBlock<3, device_t> wrong total block size"; + EXPECT_EQ(ghosted_data.ghosted_block_size()[1], 9) + << "DataArrayGhostedBlock<3, device_t> wrong total block size"; + EXPECT_EQ(ghosted_data.ghosted_block_size()[2], 3) + << "DataArrayGhostedBlock<3, device_t> wrong total block size"; + + EXPECT_EQ(ghosted_data.block_size()[0], 4) + << "DataArrayGhostedBlock<3, device_t> wrong block size"; + EXPECT_EQ(ghosted_data.block_size()[1], 6) + << "DataArrayGhostedBlock<3, device_t> wrong block size"; + EXPECT_EQ(ghosted_data.block_size()[2], 2) + << "DataArrayGhostedBlock<3, device_t> wrong block size"; + + auto block_data = ghosted_data.data(); + + EXPECT_EQ(block_data.num_cells(), 189) << "Wrong view size"; + } + + { + using DirectAccess = DataArrayGhostedBlock<3, real_t, device_t>::DirectAccess; + + auto bSize = block_size_t<3>{ 4, 6, 8 }; + auto gSize = block_size_t<3>{ 6, 7, 5 }; + shift_t<3> shift{ -1, -1, 0 }; + int num_vars = 2; + int num_quadrants = 6; + + auto ghosted_data = DataArrayGhostedBlock<3, real_t, device_t>( + bSize, gSize, shift, "data", num_vars, num_quadrants); + + EXPECT_EQ(ghosted_data.num_cells(), 210) + << "DataArrayGhostedBlock<3, device_t>::num_cells() failed"; + EXPECT_EQ(ghosted_data.num_cells_inner(), 192) + << "DataArrayGhostedBlock<3, device_t>::num_cells() failed"; + + auto new_shape = block_size_t<3>{ 4, 8, 5 }; + auto new_shift = shift_t<3>{ 0, -1, 1 }; + auto accepted = ghosted_data.reshape(new_shape, new_shift); + EXPECT_EQ(accepted, true); + + EXPECT_EQ(ghosted_data.num_cells(), 160) + << "DataArrayGhostedBlock<3, device_t>::num_cells() failed"; + EXPECT_EQ(ghosted_data.num_cells_inner(), 192) + << "DataArrayGhostedBlock<3, device_t>::num_cells() failed"; + + + for (int k = new_shift[IZ]; k < new_shift[IZ] + static_cast(new_shape[IZ]); ++k) + for (int j = new_shift[IY]; j < new_shift[IY] + static_cast(new_shape[IY]); ++j) + for (int i = new_shift[IX]; i < new_shift[IX] + static_cast(new_shape[IX]); ++i) + { + ghosted_data(i, j, k, 0, 3) = i * i + j - k * k * k; + } + + EXPECT_NEAR(ghosted_data(0, -1, 1, 0, 3), -2.0, 1e-14); + EXPECT_NEAR(ghosted_data(2, 2, 2, 0, 3), -2.0, 1e-14); + + auto ijk = coord_t<3>{ 2, 0, 3 }; + EXPECT_NEAR(ghosted_data(ijk, 0, 3), -23.0, 1e-14); + + EXPECT_NEAR(ghosted_data(0, 0, 0, 0, 3, DirectAccess{}), -2.0, 1e-14); + EXPECT_NEAR(ghosted_data(2, 3, 4, 0, 3, DirectAccess{}), -119.0, 1e-14); + EXPECT_NEAR(ghosted_data(ijk, 0, 3, DirectAccess{}), -61.0, 1e-14); + } + + { + using DirectAccess = DataArrayGhostedBlock<3, real_t, device_t>::DirectAccess; + + auto bSize = block_size_t<3>{ 4, 5, 4 }; + auto gSize = block_size_t<3>{ 4, 9, 4 }; + shift_t<3> shift{ 0, -2, 0 }; + int num_vars = 2; + int num_quadrants = 6; + + auto ghosted_data = DataArrayGhostedBlock<3, real_t, device_t>( + bSize, gSize, shift, "data", num_vars, num_quadrants); + + EXPECT_EQ(ghosted_data.num_cells(), 144) + << "DataArrayGhostedBlock<3, device_t>::num_cells() failed"; + EXPECT_EQ(ghosted_data.num_cells_inner(), 80) + << "DataArrayGhostedBlock<3, device_t>::num_cells() failed"; + + auto new_inner_shape = block_size_t<3>{ 4, 4, 5 }; + auto new_shape = block_size_t<3>{ 4, 4, 9 }; + auto new_shift = shift_t<3>{ 0, 0, -2 }; + auto accepted = ghosted_data.reshape(new_inner_shape, new_shape, new_shift); + EXPECT_EQ(accepted, true); + + EXPECT_EQ(ghosted_data.num_cells(), 144) + << "DataArrayGhostedBlock<3, device_t>::num_cells() failed"; + EXPECT_EQ(ghosted_data.num_cells_inner(), 80) + << "DataArrayGhostedBlock<3, device_t>::num_cells() failed"; + + EXPECT_EQ(ghosted_data.shape()[IX], 4); + EXPECT_EQ(ghosted_data.shape()[IY], 4); + EXPECT_EQ(ghosted_data.shape()[IZ], 9); + } +} // TEST: kalypsso_shared_test/kalypsso_data_container_DataArrayGhostedBlock3d + +#else +// ==================================================================== +// ==================================================================== +TEST(kalypsso_shared_test, kalypsso_data_container_DataArrayGhostedBlock) +{ + // using device_t = DefaultDevice; + using device_t = HostDevice; + auto bSize2 = block_size_t<2>{ 12, 7 }; + auto gw2 = block_size_t<2>{ 2, 3 }; + auto gdata = DataArrayGhostedBlock<2, real_t, device_t>(bSize2, gw2, "data", 4, 10); + + EXPECT_EQ(gdata.num_vars(), 4) << "DataArrayGhostedBlock<2, device_t>::num_vars() failed"; + EXPECT_EQ(gdata.total_block_size()[0], 16) + << "DataArrayGhostedBlock<2, device_t> compute total block size failed"; + EXPECT_EQ(gdata.total_block_size()[1], 13) + << "DataArrayGhostedBlock<2, device_t> compute total block size failed"; + + EXPECT_EQ(gdata.block_size()[0], 12) << "DataArrayGhostedBlock<2, device_t> wrong block size"; + EXPECT_EQ(gdata.block_size()[1], 7) << "DataArrayGhostedBlock<2, device_t> wrong block size"; + + auto block_data = gdata.data(); + + EXPECT_EQ(block_data.num_cells(), 208) << "Wrong view size"; + +} // TEST: kalypsso_shared_test/kalypsso_data_container_DataArrayGhostedBlock +#endif + +} // namespace kalypsso diff --git a/unit_test/core/mesh_utils_test.cpp b/unit_test/core/mesh_utils_test.cpp new file mode 100644 index 0000000..f78b3e6 --- /dev/null +++ b/unit_test/core/mesh_utils_test.cpp @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// © Commissariat a l'Energie Atomique et aux Energies Alternatives (CEA) +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +#include "gtest/gtest.h" + +namespace kalypsso +{ + +TEST(kalypsso_shared_test, mesh_utils_faces) +{ + + // 2d + { + const auto dir0 = IY; + const auto faces = Face::get_pair_of_faces<2>(dir0); + + EXPECT_EQ(faces[0] == Face::XMIN, false); + EXPECT_EQ(faces[0] == Face::YMIN, true); + EXPECT_EQ(faces[1] == Face::YMAX, true); + } + + // 3d + { + const auto dir0 = IZ; + const auto faces = Face::get_pair_of_faces<3>(dir0); + + EXPECT_EQ(faces[0] == Face::XMIN, false); + EXPECT_EQ(faces[0] == Face::ZMIN, true); + EXPECT_EQ(faces[1] == Face::YMAX, false); + } + + +} // kalypsso_shared_test, mesh_utils_faces + +} // namespace kalypsso diff --git a/unit_test/core/morton_utils_test.cpp b/unit_test/core/morton_utils_test.cpp new file mode 100644 index 0000000..e66be58 --- /dev/null +++ b/unit_test/core/morton_utils_test.cpp @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// © Commissariat a l'Energie Atomique et aux Energies Alternatives (CEA) +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "kalypsso/core/morton_utils.h" + +#include + +#include "gtest/gtest.h" + +namespace kalypsso +{ + +TEST(kalypsso_shared_test, morton_utils) +{ + + uint32_t value = 2 + 4; // 0x10 + 0x100 + auto v1 = kalypsso::splitBy3<3>(value); + EXPECT_EQ(v1, 72) << "kalypsso::splitBy3<3> failed"; + + value = 20; + v1 = kalypsso::splitBy3<3>(value); + EXPECT_EQ(v1, 4160) << "kalypsso::splitBy3<3> failed"; + +} // morton_utils + +} // namespace kalypsso diff --git a/unit_test/core/orchard_key_test.cpp b/unit_test/core/orchard_key_test.cpp new file mode 100644 index 0000000..5f50f1e --- /dev/null +++ b/unit_test/core/orchard_key_test.cpp @@ -0,0 +1,334 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// © Commissariat a l'Energie Atomique et aux Energies Alternatives (CEA) +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +#include + +#include + +namespace kalypsso +{ + +//================================================================= +// +// basic test +// +//================================================================= + +TEST(kalypsso_shared_orchard_key_test, base_2d) +{ + + { + auto key = kalypsso::orchard_key_t<2>::encode_orchard(0, { 1 << 13, 1 << 13 }, 5); + + auto oct_coords = kalypsso::orchard_key_t<2>::get_octant_coords(key); + + EXPECT_EQ(oct_coords[IX], 8192) << "kalypsso::orchard_key_t<2>::get_octant_coords"; + EXPECT_EQ(oct_coords[IY], 8192) << "kalypsso::orchard_key_t<2>::get_octant_coords"; + } + + { + auto key = kalypsso::orchard_key_t<2>::encode_orchard(0, { 1 << 21, 1 << 10 }, 5); + + auto oct_coords = kalypsso::orchard_key_t<2>::get_octant_coords(key); + + EXPECT_EQ(oct_coords[IX], 2097152) << "kalypsso::orchard_key_t<2>::get_octant_coords"; + EXPECT_EQ(oct_coords[IY], 1024) << "kalypsso::orchard_key_t<2>::get_octant_coords"; + } + + // this one should "fail" (overflow, because max level is 22), so the extracted coord should be 0 + { + auto key = kalypsso::orchard_key_t<2>::encode_orchard(0, { 1 << 22, 1 << 10 }, 5); + + auto oct_coords = kalypsso::orchard_key_t<2>::get_octant_coords(key); + + EXPECT_EQ(oct_coords[IX], 0) << "kalypsso::orchard_key_t<2>::get_octant_coords"; + EXPECT_EQ(oct_coords[IY], 1024) << "kalypsso::orchard_key_t<2>::get_octant_coords"; + } + + // this one should "fail" (overflow, because max level is 22), so the extracted coord should be 0 + { + auto key = kalypsso::orchard_key_t<2>::encode_orchard(0, { 1 << 23, 1 << 10 }, 5); + + auto oct_coords = kalypsso::orchard_key_t<2>::get_octant_coords(key); + + EXPECT_EQ(oct_coords[IX], 0) << "kalypsso::orchard_key_t<2>::get_octant_coords"; + EXPECT_EQ(oct_coords[IY], 1024) << "kalypsso::orchard_key_t<2>::get_octant_coords"; + } +} + +TEST(kalypsso_shared_orchard_key_test, base_3d) +{ + + { + auto key = kalypsso::orchard_key_t<3>::encode_orchard(0, { 1 << 11, 1 << 11, 1 << 11 }, 5); + + auto oct_coords = kalypsso::orchard_key_t<3>::get_octant_coords(key); + + EXPECT_EQ(oct_coords[IX], 2048) << "kalypsso::orchard_key_t<3>::get_octant_coords"; + EXPECT_EQ(oct_coords[IY], 2048) << "kalypsso::orchard_key_t<3>::get_octant_coords"; + EXPECT_EQ(oct_coords[IZ], 2048) << "kalypsso::orchard_key_t<3>::get_octant_coords"; + } + + { + auto key = kalypsso::orchard_key_t<3>::encode_orchard(0, { 1 << 12, 1 << 11, 1 << 11 }, 5); + + auto oct_coords = kalypsso::orchard_key_t<3>::get_octant_coords(key); + + EXPECT_EQ(oct_coords[IX], 4096) << "kalypsso::orchard_key_t<3>::get_octant_coords"; + EXPECT_EQ(oct_coords[IY], 2048) << "kalypsso::orchard_key_t<3>::get_octant_coords"; + EXPECT_EQ(oct_coords[IZ], 2048) << "kalypsso::orchard_key_t<3>::get_octant_coords"; + } + + // this one should "fail" (overflow, because max level is 14), i.e. 2**13 is max + // so the extracted coord should be 0 + { + auto key = kalypsso::orchard_key_t<3>::encode_orchard(0, { 1 << 14, 1 << 10, 1 << 5 }, 5); + + auto oct_coords = kalypsso::orchard_key_t<3>::get_octant_coords(key); + + EXPECT_EQ(oct_coords[IX], 0) << "kalypsso::orchard_key_t<3>::get_octant_coords"; + EXPECT_EQ(oct_coords[IY], 1024) << "kalypsso::orchard_key_t<3>::get_octant_coords"; + EXPECT_EQ(oct_coords[IZ], 32) << "kalypsso::orchard_key_t<3>::get_octant_coords"; + } + + // this one should "fail" (overflow, because max level is 14), i.e. 2**13 is max + // so the extracted coord should be 0 + { + auto key = kalypsso::orchard_key_t<3>::encode_orchard(0, { 1 << 10, 1 << 14, 1 << 5 }, 3); + + auto oct_coords = kalypsso::orchard_key_t<3>::get_octant_coords(key); + + EXPECT_EQ(oct_coords[IX], 1024) << "kalypsso::orchard_key_t<3>::get_octant_coords"; + EXPECT_EQ(oct_coords[IY], 0) << "kalypsso::orchard_key_t<3>::get_octant_coords"; + EXPECT_EQ(oct_coords[IZ], 32) << "kalypsso::orchard_key_t<3>::get_octant_coords"; + } + + // this on should "fail" (overflow, because max level is 14), i.e. 2**13 is max + // so the extracted coord should be 0 + { + auto key = kalypsso::orchard_key_t<3>::encode_orchard(0, { 1 << 2, 1 << 3, 1 << 14 }, 5); + + auto oct_coords = kalypsso::orchard_key_t<3>::get_octant_coords(key); + + EXPECT_EQ(oct_coords[IX], 4) << "kalypsso::orchard_key_t<3>::get_octant_coords"; + EXPECT_EQ(oct_coords[IY], 8) << "kalypsso::orchard_key_t<3>::get_octant_coords"; + EXPECT_EQ(oct_coords[IZ], 0) << "kalypsso::orchard_key_t<3>::get_octant_coords"; + } +} + +//================================================================= +// +// Father and child +// +//================================================================= + +TEST(kalypsso_shared_orchard_key_test, father_child_2d) +{ + + auto key = kalypsso::orchard_key_t<2>::encode_orchard(0, { 0, 0 }, 5); + + auto length = kalypsso::orchard_key_t<2>::octantLength(key); + + auto father = kalypsso::orchard_key_t<2>::father(key); + + + auto key2 = kalypsso::orchard_key_t<2>::encode_orchard(0, { length, 0 }, 5); + + EXPECT_EQ(kalypsso::orchard_key_t<2>::child_id(key2), 1) + << "kalypsso::orchard_key_t<2>::child_id"; + + key2 = kalypsso::orchard_key_t<2>::encode_orchard(0, { 0, length }, 5); + + EXPECT_EQ(kalypsso::orchard_key_t<2>::child_id(key2), 2) + << "kalypsso::orchard_key_t<2>::child_id"; + + key2 = kalypsso::orchard_key_t<2>::encode_orchard(0, { length, length }, 5); + + EXPECT_EQ(kalypsso::orchard_key_t<2>::child_id(key2), 3) + << "kalypsso::orchard_key_t<2>::child_id"; + + key2 = kalypsso::orchard_key_t<2>::encode_orchard(0, { 2 * length, length }, 5); + + EXPECT_EQ(kalypsso::orchard_key_t<2>::child_id(key2), 2) + << "kalypsso::orchard_key_t<2>::child_id"; + + key2 = kalypsso::orchard_key_t<2>::encode_orchard(0, { 0, length }, 5); + + auto father2 = kalypsso::orchard_key_t<2>::father(key2); + + EXPECT_EQ(father, father2) << "kalypsso::orchard_key_t<2>::father failed"; + + key2 = kalypsso::orchard_key_t<2>::encode_orchard(0, { 0, 2 * length }, 5); + father2 = kalypsso::orchard_key_t<2>::father(key2); + + EXPECT_TRUE(father != father2) << "kalypsso::orchard_key_t<2>::father failed"; +} + +TEST(kalypsso_shared_orchard_key_test, father_child_3d) +{ + + auto key = kalypsso::orchard_key_t<3>::encode_orchard(0, { 0, 0, 0 }, 6); + + auto length = kalypsso::orchard_key_t<3>::octantLength(key); + + // auto father = kalypsso::orchard_key_t<3>::father(key); + + + auto key2 = kalypsso::orchard_key_t<3>::encode_orchard(0, { 0, length, 0 }, 5); + + EXPECT_EQ(kalypsso::orchard_key_t<3>::child_id(key2), 2) + << "kalypsso::orchard_key_t<3>::child_id"; + + key2 = kalypsso::orchard_key_t<3>::encode_orchard(0, { 0, length, length }, 6); + + EXPECT_EQ(kalypsso::orchard_key_t<3>::child_id(key2), 6) + << "kalypsso::orchard_key_t<3>::child_id"; +} + +//================================================================= +// +// neighbors +// +//================================================================= +TEST(kalypsso_shared_orchard_key_test, neighbors) +{ + // 2D + + EXPECT_EQ(kalypsso::orchard_key_t<2>::get_face_neighbor_smallest_child_id(Face::XMIN), 1); + EXPECT_EQ(kalypsso::orchard_key_t<2>::get_face_neighbor_smallest_child_id(Face::XMAX), 0); + EXPECT_EQ(kalypsso::orchard_key_t<2>::get_face_neighbor_smallest_child_id(Face::YMIN), 2); + EXPECT_EQ(kalypsso::orchard_key_t<2>::get_face_neighbor_smallest_child_id(Face::YMAX), 0); + + EXPECT_EQ(kalypsso::orchard_key_t<2>::get_edge_neighbor_smallest_child_id(Face::XMIN, Face::YMIN), + 3); + EXPECT_EQ(kalypsso::orchard_key_t<2>::get_edge_neighbor_smallest_child_id(Face::XMAX, Face::YMIN), + 2); + EXPECT_EQ(kalypsso::orchard_key_t<2>::get_edge_neighbor_smallest_child_id(Face::XMIN, Face::YMAX), + 1); + EXPECT_EQ(kalypsso::orchard_key_t<2>::get_edge_neighbor_smallest_child_id(Face::XMAX, Face::YMAX), + 0); + + // 3D + + // faces + EXPECT_EQ(kalypsso::orchard_key_t<3>::get_face_neighbor_smallest_child_id(Face::XMIN), 1); + EXPECT_EQ(kalypsso::orchard_key_t<3>::get_face_neighbor_smallest_child_id(Face::XMAX), 0); + EXPECT_EQ(kalypsso::orchard_key_t<3>::get_face_neighbor_smallest_child_id(Face::YMIN), 2); + EXPECT_EQ(kalypsso::orchard_key_t<3>::get_face_neighbor_smallest_child_id(Face::YMAX), 0); + EXPECT_EQ(kalypsso::orchard_key_t<3>::get_face_neighbor_smallest_child_id(Face::ZMIN), 4); + EXPECT_EQ(kalypsso::orchard_key_t<3>::get_face_neighbor_smallest_child_id(Face::ZMAX), 0); + + // edges + EXPECT_EQ(kalypsso::orchard_key_t<3>::get_edge_neighbor_smallest_child_id(Face::XMIN, Face::YMIN), + 3); + EXPECT_EQ(kalypsso::orchard_key_t<3>::get_edge_neighbor_smallest_child_id(Face::XMAX, Face::YMIN), + 2); + EXPECT_EQ(kalypsso::orchard_key_t<3>::get_edge_neighbor_smallest_child_id(Face::XMIN, Face::YMAX), + 1); + EXPECT_EQ(kalypsso::orchard_key_t<3>::get_edge_neighbor_smallest_child_id(Face::XMAX, Face::YMAX), + 0); + + EXPECT_EQ(kalypsso::orchard_key_t<3>::get_edge_neighbor_smallest_child_id(Face::YMIN, Face::ZMIN), + 6); + EXPECT_EQ(kalypsso::orchard_key_t<3>::get_edge_neighbor_smallest_child_id(Face::YMAX, Face::ZMIN), + 4); + EXPECT_EQ(kalypsso::orchard_key_t<3>::get_edge_neighbor_smallest_child_id(Face::YMIN, Face::ZMAX), + 2); + EXPECT_EQ(kalypsso::orchard_key_t<3>::get_edge_neighbor_smallest_child_id(Face::YMAX, Face::ZMAX), + 0); + + EXPECT_EQ(kalypsso::orchard_key_t<3>::get_edge_neighbor_smallest_child_id(Face::XMIN, Face::ZMIN), + 5); + EXPECT_EQ(kalypsso::orchard_key_t<3>::get_edge_neighbor_smallest_child_id(Face::XMAX, Face::ZMIN), + 4); + EXPECT_EQ(kalypsso::orchard_key_t<3>::get_edge_neighbor_smallest_child_id(Face::XMIN, Face::ZMAX), + 1); + EXPECT_EQ(kalypsso::orchard_key_t<3>::get_edge_neighbor_smallest_child_id(Face::XMAX, Face::ZMAX), + 0); + + // corners + EXPECT_EQ(kalypsso::orchard_key_t<3>::get_corner_neighbor_smallest_child_id( + Face::XMIN, Face::YMIN, Face::ZMIN), + 7); + EXPECT_EQ(kalypsso::orchard_key_t<3>::get_corner_neighbor_smallest_child_id( + Face::XMAX, Face::YMIN, Face::ZMIN), + 6); + EXPECT_EQ(kalypsso::orchard_key_t<3>::get_corner_neighbor_smallest_child_id( + Face::XMIN, Face::YMAX, Face::ZMIN), + 5); + EXPECT_EQ(kalypsso::orchard_key_t<3>::get_corner_neighbor_smallest_child_id( + Face::XMAX, Face::YMAX, Face::ZMIN), + 4); + EXPECT_EQ(kalypsso::orchard_key_t<3>::get_corner_neighbor_smallest_child_id( + Face::XMIN, Face::YMIN, Face::ZMAX), + 3); + EXPECT_EQ(kalypsso::orchard_key_t<3>::get_corner_neighbor_smallest_child_id( + Face::XMAX, Face::YMIN, Face::ZMAX), + 2); + EXPECT_EQ(kalypsso::orchard_key_t<3>::get_corner_neighbor_smallest_child_id( + Face::XMIN, Face::YMAX, Face::ZMAX), + 1); + EXPECT_EQ(kalypsso::orchard_key_t<3>::get_corner_neighbor_smallest_child_id( + Face::XMAX, Face::YMAX, Face::ZMAX), + 0); +} + +//================================================================= +// +// encode / decode +// +//================================================================= + +TEST(kalypsso_shared_orchard_key_test, encode_decode_2d) +{ + + auto key = kalypsso::orchard_key_t<2>::encode_orchard({ 5, 6 }, { 7777, 3333 }, 8); + kalypsso::orchard_key_t<2>::set_is_touching_face_X(key, true); + + EXPECT_EQ(key, 256705187183600417) << "kalypsso::orchard_key_t<2>::encode_orchard error"; + + EXPECT_EQ(orchard_key_t<2>::template get_tree_coord(key), 5) + << "kalypsso::orchard_key_t<2>::decode_orchard error"; + EXPECT_EQ(orchard_key_t<2>::template get_tree_coord(key), 6) + << "kalypsso::orchard_key_t<2>::decode_orchard error"; + + EXPECT_EQ(orchard_key_t<2>::template get_octant_coord(key), 7777) + << "kalypsso::orchard_key_t<2>::decode_orchard error"; + EXPECT_EQ(orchard_key_t<2>::template get_octant_coord(key), 3333) + << "kalypsso::orchard_key_t<2>::decode_orchard error"; + + EXPECT_EQ(orchard_key_t<2>::level(key), 8) + << "kalypsso::orchard_key_t<2>::decode_orchard error decoding level"; +} + +TEST(kalypsso_shared_orchard_key_test, encode_decode_3d) +{ + + auto key = kalypsso::orchard_key_t<3>::encode_orchard({ 2, 3, 4 }, { 2048, 16383, 4096 }, 5); + kalypsso::orchard_key_t<3>::set_is_touching_face_Y(key, true); + + EXPECT_EQ(key, 158949013592361258) << "kalypsso::orchard_key_t<3>::encode_orchard error"; + + EXPECT_EQ(orchard_key_t<3>::template get_tree_coord(key), 2) + << "kalypsso::orchard_key_t<3>::decode_orchard error"; + EXPECT_EQ(orchard_key_t<3>::template get_tree_coord(key), 3) + << "kalypsso::orchard_key_t<3>::decode_orchard error"; + EXPECT_EQ(orchard_key_t<3>::template get_tree_coord(key), 4) + << "kalypsso::orchard_key_t<3>::decode_orchard error"; + + EXPECT_EQ(orchard_key_t<3>::template get_octant_coord(key), 2048) + << "kalypsso::orchard_key_t<3>::decode_orchard error"; + EXPECT_EQ(orchard_key_t<3>::template get_octant_coord(key), 16383) + << "kalypsso::orchard_key_t<3>::decode_orchard error"; + EXPECT_EQ(orchard_key_t<3>::template get_octant_coord(key), 4096) + << "kalypsso::orchard_key_t<3>::decode_orchard error"; + + EXPECT_EQ(orchard_key_t<3>::level(key), 5) + << "kalypsso::orchard_key_t<3>::decode_orchard error decoding level"; +} + +} // namespace kalypsso diff --git a/unit_test/core/vof/interface_tracking_utils_test.cpp b/unit_test/core/vof/interface_tracking_utils_test.cpp new file mode 100644 index 0000000..6ccaf05 --- /dev/null +++ b/unit_test/core/vof/interface_tracking_utils_test.cpp @@ -0,0 +1,412 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include + +#include "gtest/gtest.h" + +namespace kalypsso +{ + +TEST(kalypsso_get_area_below_plane, volume_fraction_2d) +{ + + Kokkos::Array O{ 0.0, 0.0 }; + Kokkos::Array M{ 0.99, 0.99 }; + real_t radius = sqrt(2); + + Kokkos::Array normal{ 0.0, 0.0 }; + real_t alpha = 0.0; + + { + get_tangent_to_sphere(O, radius, M, normal, alpha); + + const real_t delta_x = 0.04; + + // change of frame so that cell of size delta_x about M becomes a unit cube + alpha = (alpha - normal[IX] * M[IX] - normal[IY] * M[IY]) / delta_x; + + const auto vol_frac = vof::compute_volume_fraction_of_unit_cube_below_plane(normal, alpha); + + EXPECT_NEAR(vol_frac, 0.875, 1e-14); + } +} + +TEST(kalypsso_get_area_below_plane, volume_fraction_2d_2) +{ + + Kokkos::Array O{ 0.0, 0.0 }; + Kokkos::Array M{ 1.01, 1.01 }; + real_t radius = sqrt(2); + + Kokkos::Array normal{ 0.0, 0.0 }; + real_t alpha = 0.0; + + { + get_tangent_to_sphere(O, radius, M, normal, alpha); + + const real_t delta_x = 0.04; + + // change of frame so that cell of size delta_x about M becomes a unit cube + alpha = (alpha - normal[IX] * M[IX] - normal[IY] * M[IY]) / delta_x; + + const auto vol_frac = vof::compute_volume_fraction_of_unit_cube_below_plane(normal, alpha); + + EXPECT_NEAR(vol_frac, 0.125, 1e-14); + } +} + +TEST(kalypsso_get_area_below_plane, volume_fraction_2d_3) +{ + + Kokkos::Array O{ 0.0, 0.0 }; + Kokkos::Array M{ 1.01, 0.0 }; + real_t radius = 1.0; + + Kokkos::Array normal{ 0.0, 0.0 }; + real_t alpha = 0.0; + + { + get_tangent_to_sphere(O, radius, M, normal, alpha); + + const real_t delta_x = 0.04; + + // change of frame so that cell of size delta_x about M becomes a unit cube + alpha = (alpha - normal[IX] * M[IX] - normal[IY] * M[IY]) / delta_x; + + const auto vol_frac = vof::compute_volume_fraction_of_unit_cube_below_plane(normal, alpha); + + EXPECT_NEAR(vol_frac, 1.0 / 4, 1e-14); + } +} + +TEST(kalypsso_get_area_below_plane, volume_fraction_2d_4) +{ + + Kokkos::Array O{ 0.0, 0.0 }; + Kokkos::Array M{ 1.01, 0.0 }; + real_t radius = 1.01; + + Kokkos::Array normal{ 0.0, 0.0 }; + real_t alpha = 0.0; + + { + get_tangent_to_sphere(O, radius, M, normal, alpha); + + const real_t delta_x = 0.04; + + // change of frame so that cell of size delta_x about M becomes a unit cube + alpha = (alpha - normal[IX] * M[IX] - normal[IY] * M[IY]) / delta_x; + + const auto vol_frac = vof::compute_volume_fraction_of_unit_cube_below_plane(normal, alpha); + + EXPECT_NEAR(vol_frac, 0.5, 1e-14); + } +} + +TEST(kalypsso_get_area_below_plane, volume_fraction_2d_5) +{ + + Kokkos::Array O{ 0.0, 0.0 }; + Kokkos::Array M{ 0.99, 0.99 }; + real_t radius = sqrt(2); + + Kokkos::Array normal{ 0.0, 0.0 }; + real_t alpha = 0.0; + + { + get_tangent_to_sphere(O, radius, M, normal, alpha); + + const real_t delta_x = 0.08; + + // change of frame so that cell of size delta_x about M becomes a unit cube + alpha = (alpha - normal[IX] * M[IX] - normal[IY] * M[IY]) / delta_x; + + const auto vol_frac = vof::compute_volume_fraction_of_unit_cube_below_plane(normal, alpha); + + EXPECT_NEAR(vol_frac, 46.0 / 64, 1e-14); + } +} + +TEST(kalypsso_get_area_below_plane, volume_fraction_3d_1) +{ + // case 1 + Kokkos::Array normal{ 1.0 / sqrt(3.0), 1.0 / sqrt(3.0), 1.0 / sqrt(3.0) }; + real_t alpha = -sqrt(3.0) / 2.0 + 1 * sqrt(3.0) / 4; + + const auto vol_frac = vof::compute_volume_fraction_of_unit_cube_below_plane(normal, alpha); + + EXPECT_NEAR(vol_frac, 0.75 * 0.75 * 0.75 / 6, 1e-14); +} + +TEST(kalypsso_get_area_below_plane, volume_fraction_3d_2) +{ + + // case 2 + Kokkos::Array normal{ -1.0 / sqrt(2.0), 1.0 / sqrt(2.0), 0.0 }; + real_t alpha = -0.0000001; + + const auto vol_frac = vof::compute_volume_fraction_of_unit_cube_below_plane(normal, alpha); + + EXPECT_NEAR(vol_frac, 0.5, 1e-6); +} + +TEST(kalypsso_get_area_below_plane, volume_fraction_3d_2_2) +{ + // case 2 + Kokkos::Array normal{ 0.0, 1.0 / sqrt(2.0), 1.0 / sqrt(2.0) }; + real_t alpha = -sqrt(2.0) / 2.0 + sqrt(2.0) / 4; + + const auto vol_frac = vof::compute_volume_fraction_of_unit_cube_below_plane(normal, alpha); + + EXPECT_NEAR(vol_frac, 0.125, 1e-14); +} + +TEST(kalypsso_get_area_below_plane, volume_fraction_3d_2_3) +{ + // case 2 + Kokkos::Array normal{ 0.0, 1.0 / sqrt(2.0), 1.0 / sqrt(2.0) }; + real_t alpha = -sqrt(2.0) / 2.0 + 3 * sqrt(2.0) / 4; + + const auto vol_frac = vof::compute_volume_fraction_of_unit_cube_below_plane(normal, alpha); + + EXPECT_NEAR(vol_frac, 1 - 0.125, 1e-14); +} + +TEST(kalypsso_get_area_below_plane, volume_fraction_3d_4) +{ + // case 4 + Kokkos::Array O{ 0.0, 0.0, 0.0 }; + Kokkos::Array M{ 1.01, 0.0, 0.0 }; + real_t radius = 1.01; + + Kokkos::Array normal{ 0.0, 0.0, 0.0 }; + real_t alpha = 0.0; + + { + get_tangent_to_sphere(O, radius, M, normal, alpha); + + const real_t delta_x = 0.04; + + // change of frame so that cell of size delta_x about M becomes a unit cube + alpha = (alpha - normal[IX] * M[IX] - normal[IY] * M[IY]) / delta_x; + + const auto vol_frac = vof::compute_volume_fraction_of_unit_cube_below_plane(normal, alpha); + + EXPECT_NEAR(vol_frac, 0.5, 1e-14); + } +} + +TEST(kalypsso_get_area_below_plane, volume_fraction_3d_4_2) +{ + // case 4 + Kokkos::Array O{ 0.0, 0.0, 0.0 }; + Kokkos::Array M{ 1.01, 0.0, 0.0 }; + real_t radius = 1.0; + + Kokkos::Array normal{ 0.0, 0.0, 0.0 }; + real_t alpha = 0.0; + + { + get_tangent_to_sphere(O, radius, M, normal, alpha); + + const real_t delta_x = 0.04; + + // change of frame so that cell of size delta_x about M becomes a unit cube + alpha = (alpha - normal[IX] * M[IX] - normal[IY] * M[IY]) / delta_x; + + const auto vol_frac = vof::compute_volume_fraction_of_unit_cube_below_plane(normal, alpha); + + EXPECT_NEAR(vol_frac, 0.25, 1e-14); + } +} + +TEST(kalypsso_get_area_below_plane, volume_fraction_3d_5) +{ + + // case 5 + Kokkos::Array normal{ -1.0 / sqrt(2.0), 1.0 / sqrt(2.0), 0.0 }; + real_t alpha = 0.0; + + const auto vol_frac = vof::compute_volume_fraction_of_unit_cube_below_plane(normal, alpha); + + EXPECT_NEAR(vol_frac, 0.5, 1e-14); +} + + +TEST(kalypsso_get_area_below_plane, volume_fraction_3d_last) +{ + // case last + Kokkos::Array normal{ 1.0, 1.0, 1.0 }; + real_t alpha = 0.0; + + const auto vol_frac = vof::compute_volume_fraction_of_unit_cube_below_plane(normal, alpha); + + EXPECT_NEAR(vol_frac, 0.5, 1e-14); +} + +TEST(kalypsso_shared_vof_test, compute_plane_rhs_2d) +{ + Kokkos::Array normal = {}; + + { // No normal check + normal = { 0., 0. }; + const auto C = vof::compute_plane_rhs(0.5, normal); + EXPECT_DOUBLE_EQ(C, 0); + } + + { // Positive well-ordered normal + normal = { 1., 2. }; + const auto Ca = vof::compute_plane_rhs(0.25, normal); + const auto Cb = vof::compute_plane_rhs(0.4, normal); + const auto Cc = vof::compute_plane_rhs(0.75, normal); + EXPECT_DOUBLE_EQ(Ca, 1.); + EXPECT_DOUBLE_EQ(Cb, 1.3); + EXPECT_DOUBLE_EQ(Cc, 2.); + } + + { // Negative unordered normal + normal = { -2., 1. }; + const auto Ca = vof::compute_plane_rhs(0.25, normal); + const auto Cb = vof::compute_plane_rhs(0.4, normal); + const auto Cc = vof::compute_plane_rhs(0.75, normal); + EXPECT_DOUBLE_EQ(Ca, -1.); + EXPECT_DOUBLE_EQ(Cb, -0.7); + EXPECT_DOUBLE_EQ(Cc, 0.); + } +} + +TEST(kalypsso_shared_vof_test, compute_plane_rhs_3d) +{ + Kokkos::Array normal = {}; + // Using FUZZYCOMPARE on pent ang quad test cases because of newton's approx + // Solutions found using WolframAlpha + + { // No normal check + normal = { 0., 0., 0. }; + const auto C = vof::compute_plane_rhs(0.5, normal); + EXPECT_DOUBLE_EQ(C, 0); + } + + { // Flat check + normal = { 0., 0., 2. }; + const auto C = vof::compute_plane_rhs(0.5, normal); + EXPECT_DOUBLE_EQ(C, 1.); + } + + { // 2D Positive well-ordered normal + normal = { 0., 1., 2. }; + const auto Ca = vof::compute_plane_rhs(0.25, normal); + const auto Cb = vof::compute_plane_rhs(0.4, normal); + const auto Cc = vof::compute_plane_rhs(0.75, normal); + EXPECT_DOUBLE_EQ(Ca, 1.); + EXPECT_DOUBLE_EQ(Cb, 1.3); + EXPECT_DOUBLE_EQ(Cc, 2.); + } + + { // 2D Negative unordered normal + normal = { 0., -2., 1. }; + const auto Ca = vof::compute_plane_rhs(0.25, normal); + const auto Cb = vof::compute_plane_rhs(0.4, normal); + const auto Cc = vof::compute_plane_rhs(0.75, normal); + EXPECT_DOUBLE_EQ(Ca, -1.); + EXPECT_DOUBLE_EQ(Cb, -0.7); + EXPECT_DOUBLE_EQ(Cc, 0.); + } + + { // Positive well-ordered normal and nx + ny < nz + normal = { 1., 2., 3.5 }; + real_t C; + + C = vof::compute_plane_rhs(0.01, normal); // tri + EXPECT_DOUBLE_EQ(C, 0.7488872387218507); + + C = vof::compute_plane_rhs(0.1, normal); // quad A + EXPECT_DOUBLE_EQ(C, 1.6474609652039003); + + C = vof::compute_plane_rhs(0.2, normal); // pent + EXPECT_TRUE(FUZZYCOMPARE(C, 2.1485629016669618)); + + C = vof::compute_plane_rhs(0.45, normal); // quad B + EXPECT_DOUBLE_EQ(C, 3.075); + + C = vof::compute_plane_rhs(0.55, normal); // opp quad B + EXPECT_DOUBLE_EQ(C, 3.425); + + C = vof::compute_plane_rhs(0.8, normal); // opp pent + EXPECT_TRUE(FUZZYCOMPARE(C, 4.3514370983330382)); + + C = vof::compute_plane_rhs(0.9, normal); // opp quad A + EXPECT_DOUBLE_EQ(C, 4.8525390347960997); + + C = vof::compute_plane_rhs(0.99, normal); // opp tri + EXPECT_DOUBLE_EQ(C, 5.7511127612781493); + } + + { // Positive well-ordered normal and nx + ny > nz + normal = { 1., 3., 3.5 }; + real_t C; + + C = vof::compute_plane_rhs(0.01, normal); // tri + EXPECT_DOUBLE_EQ(C, 0.8572618882313395); + + C = vof::compute_plane_rhs(0.1, normal); // quad A + EXPECT_DOUBLE_EQ(C, 1.9200938936093861); + + C = vof::compute_plane_rhs(0.35, normal); // pent + EXPECT_TRUE(FUZZYCOMPARE(C, 3.1961420535807157)); + + C = vof::compute_plane_rhs(0.45, normal); // hexa + EXPECT_TRUE(FUZZYCOMPARE(C, 3.5706216414310085)); + + C = vof::compute_plane_rhs(0.55, normal); // opp hexa + EXPECT_TRUE(FUZZYCOMPARE(C, 3.9293783585689915)); + + C = vof::compute_plane_rhs(0.65, normal); // opp pent + EXPECT_TRUE(FUZZYCOMPARE(C, 4.3038579464192843)); + + C = vof::compute_plane_rhs(0.9, normal); // opp quad A + EXPECT_DOUBLE_EQ(C, 5.5799061063906139); + + C = vof::compute_plane_rhs(0.99, normal); // opp tri + EXPECT_DOUBLE_EQ(C, 6.6427381117686605); + } + + { // Negative unordered normal and nx + ny < nz + normal = { -3.5, 1., 2. }; + real_t C; + + C = vof::compute_plane_rhs(0.01, normal); // tri + EXPECT_DOUBLE_EQ(C, -2.7511127612781493); + + C = vof::compute_plane_rhs(0.1, normal); // quad A + EXPECT_DOUBLE_EQ(C, -1.8525390347960997); + + C = vof::compute_plane_rhs(0.2, normal); // pent + EXPECT_TRUE(FUZZYCOMPARE(C, -1.3514370983330382)); + + C = vof::compute_plane_rhs(0.45, normal); // quad B + EXPECT_DOUBLE_EQ(C, -0.425); + } + + { // Negative unordered normal and nx + ny > nz + normal = { -3.5, 1., 3. }; + real_t C; + + C = vof::compute_plane_rhs(0.01, normal); // tri + EXPECT_DOUBLE_EQ(C, -2.6427381117686605); + + C = vof::compute_plane_rhs(0.1, normal); // quad A + EXPECT_DOUBLE_EQ(C, -1.5799061063906139); + + C = vof::compute_plane_rhs(0.35, normal); // pent + EXPECT_TRUE(FUZZYCOMPARE(C, -0.3038579464192843)); + + C = vof::compute_plane_rhs(0.45, normal); // hexa + EXPECT_TRUE(FUZZYCOMPARE(C, 0.07062164143100854)); + } +} + +} // namespace kalypsso diff --git a/unit_test/core/vof/youngs_test.cpp b/unit_test/core/vof/youngs_test.cpp new file mode 100644 index 0000000..664e8d3 --- /dev/null +++ b/unit_test/core/vof/youngs_test.cpp @@ -0,0 +1,266 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +#include "gtest/gtest.h" + +namespace kalypsso +{ + +TEST(kalypsso_shared_vof_test, youngs_normal_2d) +{ + const Kokkos::Array d = { 2., 3. }; + vof::NormalNeighbors<2> f; + + { // Only ones on east side + f = {}; + f[2] = f[5] = f[8] = 1.; + const auto normal = vof::youngs_normal(f, d); + EXPECT_DOUBLE_EQ(normal[0], -1.); + EXPECT_DOUBLE_EQ(normal[1], 0); + } + + { // Only ones on west side + f = {}; + f[0] = f[3] = f[6] = 1.; + const auto normal = vof::youngs_normal(f, d); + EXPECT_DOUBLE_EQ(normal[0], 1.); + EXPECT_DOUBLE_EQ(normal[1], 0); + } + + { // Diagonal, ones on the south-east + f = {}; + f[1] = f[2] = f[5] = 1.; + f[0] = f[4] = f[8] = 0.5; + const auto normal = vof::youngs_normal(f, d); + EXPECT_DOUBLE_EQ(normal[0], -0.8320502943378436); + EXPECT_DOUBLE_EQ(normal[1], 0.5547001962252291); + } + + { // Ring of ones + f = {}; + f[0] = f[1] = f[2] = f[3] = f[5] = f[6] = f[7] = f[8] = 1.; + const auto normal = vof::youngs_normal(f, d); + EXPECT_DOUBLE_EQ(normal[0], 0); + EXPECT_DOUBLE_EQ(normal[1], 0); + } +} + +TEST(kalypsso_shared_vof_test, youngs_normal_3d) +{ + const Kokkos::Array d = { 2., 3., 4. }; + vof::NormalNeighbors<3> f; + + { // Only ones on east side + f = {}; + f[2] = f[5] = f[8] = f[11] = f[14] = f[17] = f[20] = f[23] = f[26] = 1.; + const auto normal = vof::youngs_normal(f, d); + EXPECT_DOUBLE_EQ(normal[0], -1.); + EXPECT_DOUBLE_EQ(normal[1], 0); + EXPECT_DOUBLE_EQ(normal[2], 0); + } + + { // Only ones on west side + f = {}; + f[0] = f[3] = f[6] = f[9] = f[12] = f[15] = f[18] = f[21] = f[24] = 1.; + const auto normal = vof::youngs_normal(f, d); + EXPECT_DOUBLE_EQ(normal[0], 1.); + EXPECT_DOUBLE_EQ(normal[1], 0); + EXPECT_DOUBLE_EQ(normal[2], 0); + } + + { // Diagonal, ones on the top-south-east + f = {}; + f[11] = f[19] = f[20] = f[23] = 1.; + f[2] = f[10] = f[14] = f[18] = f[22] = f[26] = 0.5; + const auto normal = vof::youngs_normal(f, d); + EXPECT_DOUBLE_EQ(normal[0], -0.7682212795973758); + EXPECT_DOUBLE_EQ(normal[1], 0.5121475197315838); + EXPECT_DOUBLE_EQ(normal[2], -0.3841106397986879); + } + + { // Ring of ones + f = {}; + f[0] = f[1] = f[2] = f[3] = f[4] = f[5] = f[6] = f[7] = f[8] = 1.; + f[9] = f[10] = f[11] = f[12] = f[14] = f[15] = f[16] = f[17] = 1.; + f[18] = f[19] = f[20] = f[21] = f[22] = f[23] = f[24] = f[25] = f[26] = 1.; + const auto normal = vof::youngs_normal(f, d); + EXPECT_DOUBLE_EQ(normal[0], 0); + EXPECT_DOUBLE_EQ(normal[1], 0); + EXPECT_DOUBLE_EQ(normal[2], 0); + } +} + +TEST(kalypsso_shared_vof_test, youngs_advect_2d) +{ + Kokkos::Array normal; + real_t C; + Kokkos::Array d = { 1., 2. }; + + { // normal = 0 + normal = { 0., 0. }; + C = 0.; + real_t volume; + + volume = vof::youngs_advect(0.5, C, normal, d, IX, false, 0.1); + EXPECT_DOUBLE_EQ(volume, 0); + + volume = vof::youngs_advect(0.5, C, normal, d, IY, true, 0.1); + EXPECT_DOUBLE_EQ(volume, 0); + + volume = vof::youngs_advect(0.5, C, normal, d, IX, false, 0.3); + EXPECT_DOUBLE_EQ(volume, 0.3619288125423016); + + volume = vof::youngs_advect(0.5, C, normal, d, IY, true, 0.3); + EXPECT_DOUBLE_EQ(volume, 0.3619288125423016); + + volume = vof::youngs_advect(0.5, C, normal, d, IX, false, 0.9); + EXPECT_DOUBLE_EQ(volume, 0.5555555555555555); + + volume = vof::youngs_advect(0.5, C, normal, d, IY, true, 0.9); + EXPECT_DOUBLE_EQ(volume, 0.5555555555555555); + } + + { // any normal, essentially a wrapper around compute_volume_fraction_of_unit_cube_below_plane, so + // no need for extensive testing + normal = { -1., 2. }; + real_t volumeX, volumeY; + + C = -2.; + volumeX = vof::youngs_advect(0.5, C, normal, d, IX, false, 0.25); + volumeY = vof::youngs_advect(0.5, C, normal, d, IY, true, 0.25); + EXPECT_DOUBLE_EQ(volumeX, 0); + EXPECT_DOUBLE_EQ(volumeY, 0); + + C = 0.; + volumeX = vof::youngs_advect(0.5, C, normal, d, IX, false, 0.25); + volumeY = vof::youngs_advect(0.5, C, normal, d, IY, true, 0.25); + EXPECT_DOUBLE_EQ(volumeX, 0.03125); + EXPECT_DOUBLE_EQ(volumeY, 0); + + C = 2.; + volumeX = vof::youngs_advect(0.5, C, normal, d, IX, false, 0.25); + volumeY = vof::youngs_advect(0.5, C, normal, d, IY, true, 0.25); + EXPECT_DOUBLE_EQ(volumeX, 0.53125); + EXPECT_DOUBLE_EQ(volumeY, 0); + + C = 3.; + volumeX = vof::youngs_advect(0.5, C, normal, d, IX, false, 0.25); + volumeY = vof::youngs_advect(0.5, C, normal, d, IY, true, 0.25); + EXPECT_DOUBLE_EQ(volumeX, 0.78125); + EXPECT_DOUBLE_EQ(volumeY, 0.5); + + C = 4.; + volumeX = vof::youngs_advect(0.5, C, normal, d, IX, false, 0.25); + volumeY = vof::youngs_advect(0.5, C, normal, d, IY, true, 0.25); + EXPECT_DOUBLE_EQ(volumeX, 1.); + EXPECT_DOUBLE_EQ(volumeY, 1.); + } +} + +TEST(kalypsso_shared_vof_test, youngs_advect_3d) +{ + Kokkos::Array normal; + real_t C; + Kokkos::Array d = { 1., 2., 3. }; + + { // normal = 0 + normal = { 0., 0., 0. }; + C = 0.; + real_t volume; + + volume = vof::youngs_advect(0.5, C, normal, d, IX, false, 0.1); + EXPECT_DOUBLE_EQ(volume, 0); + + volume = vof::youngs_advect(0.5, C, normal, d, IZ, true, 0.1); + EXPECT_DOUBLE_EQ(volume, 0); + + volume = vof::youngs_advect(0.5, C, normal, d, IX, false, 0.3); + EXPECT_DOUBLE_EQ(volume, 0.4133596500350424); + + volume = vof::youngs_advect(0.5, C, normal, d, IZ, true, 0.3); + EXPECT_DOUBLE_EQ(volume, 0.4133596500350424); + + volume = vof::youngs_advect(0.5, C, normal, d, IX, false, 0.9); + EXPECT_DOUBLE_EQ(volume, 0.5555555555555555); + + volume = vof::youngs_advect(0.5, C, normal, d, IZ, true, 0.9); + EXPECT_DOUBLE_EQ(volume, 0.5555555555555555); + } + + { // any normal, essentially a wrapper around compute_volume_fraction_of_unit_cube_below_plane, so + // no need for extensive testing + normal = { -1., 2., 0. }; + real_t volumeX, volumeY; + + C = -2.; + volumeX = vof::youngs_advect(0.5, C, normal, d, IX, false, 0.25); + volumeY = vof::youngs_advect(0.5, C, normal, d, IY, true, 0.25); + EXPECT_DOUBLE_EQ(volumeX, 0); + EXPECT_DOUBLE_EQ(volumeY, 0); + + C = 0.; + volumeX = vof::youngs_advect(0.5, C, normal, d, IX, false, 0.25); + volumeY = vof::youngs_advect(0.5, C, normal, d, IY, true, 0.25); + EXPECT_DOUBLE_EQ(volumeX, 0.03125); + EXPECT_DOUBLE_EQ(volumeY, 0); + + C = 2.; + volumeX = vof::youngs_advect(0.5, C, normal, d, IX, false, 0.25); + volumeY = vof::youngs_advect(0.5, C, normal, d, IY, true, 0.25); + EXPECT_DOUBLE_EQ(volumeX, 0.53125); + EXPECT_DOUBLE_EQ(volumeY, 0); + + C = 3.; + volumeX = vof::youngs_advect(0.5, C, normal, d, IX, false, 0.25); + volumeY = vof::youngs_advect(0.5, C, normal, d, IY, true, 0.25); + EXPECT_DOUBLE_EQ(volumeX, 0.78125); + EXPECT_DOUBLE_EQ(volumeY, 0.5); + + C = 4.; + volumeX = vof::youngs_advect(0.5, C, normal, d, IX, false, 0.25); + volumeY = vof::youngs_advect(0.5, C, normal, d, IY, true, 0.25); + EXPECT_DOUBLE_EQ(volumeX, 1.); + EXPECT_DOUBLE_EQ(volumeY, 1.); + } + + { // any normal, essentially a wrapper around compute_volume_fraction_of_unit_cube_below_plane, so + // no need for extensive testing + normal = { -1., 2., 1. }; + real_t volumeX, volumeZ; + + C = -2.; + volumeX = vof::youngs_advect(0.5, C, normal, d, IX, false, 0.25); + volumeZ = vof::youngs_advect(0.5, C, normal, d, IZ, true, 0.25); + EXPECT_DOUBLE_EQ(volumeX, 0); + EXPECT_DOUBLE_EQ(volumeZ, 0); + + C = 0.; + volumeX = vof::youngs_advect(0.5, C, normal, d, IX, false, 0.25); + volumeZ = vof::youngs_advect(0.5, C, normal, d, IZ, true, 0.25); + EXPECT_DOUBLE_EQ(volumeX, 8.6805555555555555e-4); + EXPECT_DOUBLE_EQ(volumeZ, 0); + + C = 2.; + volumeX = vof::youngs_advect(0.5, C, normal, d, IX, false, 0.25); + volumeZ = vof::youngs_advect(0.5, C, normal, d, IZ, true, 0.25); + EXPECT_DOUBLE_EQ(volumeX, 0.1883680555555555); + EXPECT_DOUBLE_EQ(volumeZ, 0.0234375); + + C = 3.; + volumeX = vof::youngs_advect(0.5, C, normal, d, IX, false, 0.25); + volumeZ = vof::youngs_advect(0.5, C, normal, d, IZ, true, 0.25); + EXPECT_DOUBLE_EQ(volumeX, 0.40625); + EXPECT_DOUBLE_EQ(volumeZ, 0.21875); + + C = 4.; + volumeX = vof::youngs_advect(0.5, C, normal, d, IX, false, 0.25); + volumeZ = vof::youngs_advect(0.5, C, normal, d, IZ, true, 0.25); + EXPECT_DOUBLE_EQ(volumeX, 0.6553819444444444); + EXPECT_DOUBLE_EQ(volumeZ, 0.46875); + } +} + +} // namespace kalypsso diff --git a/unit_test/kalypsso_unittest_utils.h b/unit_test/kalypsso_unittest_utils.h new file mode 100644 index 0000000..d01aeaa --- /dev/null +++ b/unit_test/kalypsso_unittest_utils.h @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef KALYPSSO_UNIT_TEST_UTILS_H_ +#define KALYPSSO_UNIT_TEST_UTILS_H_ + +#include "gtest/gtest.h" + +#define EXPECT_REAL_EQ(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, val1, val2) + +#endif // KALYPSSO_UNIT_TEST_UTILS_H_ diff --git a/unit_test/main.cpp b/unit_test/main.cpp new file mode 100644 index 0000000..2c747a2 --- /dev/null +++ b/unit_test/main.cpp @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// © Commissariat a l'Energie Atomique et aux Energies Alternatives (CEA) +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +#include "gtest/gtest.h" + +#include + +#include "main_kalypsso_unittest.h" + +namespace kalypsso +{ +kalypsso::ParallelEnv ** g_par_env_ptr; + +int * global_argc = nullptr; +char *** global_argv = nullptr; + +} // namespace kalypsso + +// entry point +int +main(int argc, char * argv[]) +{ + kalypsso::global_argc = &argc; + kalypsso::global_argv = &argv; + kalypsso::g_par_env_ptr = nullptr; + + ::testing::InitGoogleTest(&argc, argv); + ::testing::AddGlobalTestEnvironment(new GtestParEnv()); + + return RUN_ALL_TESTS(); +} diff --git a/unit_test/main_kalypsso_unittest.h b/unit_test/main_kalypsso_unittest.h new file mode 100644 index 0000000..613274b --- /dev/null +++ b/unit_test/main_kalypsso_unittest.h @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef KALYPSSO_UNIT_TEST_MAIN_H_ +#define KALYPSSO_UNIT_TEST_MAIN_H_ + +#include "gtest/gtest.h" + +#include +#include "kalypsso/utils/mpi/ParallelEnv.h" + +namespace kalypsso +{ +extern ParallelEnv ** g_par_env_ptr; + +extern int * global_argc; +extern char *** global_argv; +} // namespace kalypsso + +namespace +{ +class GtestParEnv : public ::testing::Environment +{ +public: + void + SetUp() override + { + if (m_par_env_ptr == nullptr) + { + // handle both MPI and Kokkos resources initialization + m_par_env_ptr = new kalypsso::ParallelEnv(*kalypsso::global_argc, *kalypsso::global_argv); + kalypsso::g_par_env_ptr = &m_par_env_ptr; + } + } + void + TearDown() override + { + if (m_par_env_ptr != nullptr) + { + delete m_par_env_ptr; + kalypsso::g_par_env_ptr = nullptr; + } + } + kalypsso::ParallelEnv * m_par_env_ptr; + +}; // class GtestParEnv + +} // namespace + +#endif // KALYPSSO_UNIT_TEST_MAIN_H_ diff --git a/unit_test/utils/config/ConfigMap_test.cpp b/unit_test/utils/config/ConfigMap_test.cpp new file mode 100644 index 0000000..d31a92d --- /dev/null +++ b/unit_test/utils/config/ConfigMap_test.cpp @@ -0,0 +1,152 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// © Commissariat a l'Energie Atomique et aux Energies Alternatives (CEA) +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +#include + +#include + +namespace kalypsso +{ + +TEST(kalypsso_shared_ConfigMap_test, config_map_test) +{ + // make test.ini file + std::stringstream ini_data; + ini_data << "# Test config for unit testing " << std::endl; + + ini_data << "[run] " << std::endl; + ini_data << "solver_name=godunov ; solver name" << std::endl; + ini_data << "materials=air,water r22, copper ; solver material name" << std::endl; + ini_data << "some_ints= 34 , 2,-2 9,0 ; some integers" << std::endl; + ini_data << "some_floats= -1.0f 6.5 3.2e5 1.34e-4 ; some floats" << std::endl; + ini_data << "some_bools= yes no, 1, 0 true, false ; some boolean values" << std::endl; + ini_data << "other_ints= 34 , 2, -2 ; some integers" << std::endl; + ini_data << "drinks=water,milk,chocolate " << std::endl; + ini_data << " ,coffee,tea" << std::endl; + + + char * buffer = strdup(ini_data.str().c_str()); + size_t buf_size = ini_data.str().size(); + + // create a config_map + ConfigMap config_map(buffer, buf_size); + + // test string + EXPECT_EQ(config_map.getString("run", "solver_name", "unknown"), "godunov"); + EXPECT_EQ(config_map.getString("run", "solver_name2", "unknown"), "unknown"); + + { + // test read vector of string + auto materials = config_map.getStringVector("run", "materials", std::vector{ "" }); + + EXPECT_EQ(materials[0], "air"); + EXPECT_EQ(materials[1], "water"); + EXPECT_EQ(materials[2], "r22"); + EXPECT_EQ(materials[3], "copper"); + + auto materials2 = + config_map.getStringVector("run", "materials2", std::vector{ "" }); + EXPECT_EQ(materials2.size(), 1); + EXPECT_EQ(materials2[0], ""); + + auto materials3 = + config_map.getStringVector("run", "materials3", std::vector{ "gold", "silver" }); + EXPECT_EQ(materials3.size(), 2); + EXPECT_EQ(materials3[0], "gold"); + } + + { + // test write vector of string + config_map.setStringVector( + "run", "materials4", std::vector{ "wood ", " iron", " water " }); + auto materials4 = + config_map.getStringVector("run", "materials4", std::vector{ "" }); + EXPECT_EQ(materials4.size(), 3); + EXPECT_EQ(materials4[0], "wood"); + EXPECT_EQ(materials4[1], "iron"); + EXPECT_EQ(materials4[2], "water"); + } + + auto some_ints = config_map.getIntegerVector("run", "some_ints", std::vector{ 8, 8 }); + EXPECT_EQ(some_ints[0], 34); + EXPECT_EQ(some_ints[1], 2); + EXPECT_EQ(some_ints[2], -2); + EXPECT_EQ(some_ints[3], 9); + EXPECT_EQ(some_ints[4], 0); + + // vector of 64 bit integers + { + config_map.setInteger64Vector("run", "int64", std::vector{ 34000000000, 68000000000 }); + auto int64 = config_map.getInteger64Vector("run", "int64", std::vector{ 0 }); + EXPECT_EQ(int64.size(), 2); + EXPECT_EQ(int64[0], 34000000000); + EXPECT_EQ(int64[1], 68000000000); + + auto int32 = config_map.getIntegerVector("run", "int64", std::vector{ 0 }); + EXPECT_EQ(int32.size(), 2); + EXPECT_NE(int32[0], 34000000000); + EXPECT_NE(int32[1], 68000000000); + } + + // vector of floats + { + config_map.setFloatVector("run", "floats", std::vector{ 1.0f, 1.34e-4f }); + auto floats = config_map.getFloatVector("run", "floats", std::vector{ 0 }); + EXPECT_EQ(floats.size(), 2); + EXPECT_EQ(floats[0], 1.0f); + EXPECT_EQ(floats[1], 1.34e-4f); + + auto floats2 = config_map.getFloatVector("run", "some_floats", std::vector{}); + EXPECT_EQ(floats2.size(), 4); + EXPECT_EQ(floats2[0], -1.0f); + EXPECT_EQ(floats2[1], 6.5); + EXPECT_EQ(floats2[2], 3.2e5); + EXPECT_EQ(floats2[3], 1.34e-4f); + } + + // vector of bools + { + auto bools = config_map.getBoolVector("run", "some_bools", std::vector{ false }); + EXPECT_EQ(bools.size(), 6); + EXPECT_EQ(bools[0], true); + EXPECT_EQ(bools[1], false); + EXPECT_EQ(bools[2], true); + EXPECT_EQ(bools[3], false); + EXPECT_EQ(bools[4], true); + EXPECT_EQ(bools[5], false); + } + + // vector of ints as a Kokkos::Array + { + auto vec_ints = + config_map.getIntegerVector<3>("run", "other_ints", Kokkos::Array{ 0, 1, 2 }); + + EXPECT_EQ(vec_ints[0], 34); + EXPECT_EQ(vec_ints[1], 2); + EXPECT_EQ(vec_ints[2], -2); + } + + { + auto vec_ints = + config_map.getIntegerVector<2>("run", "other_ints", Kokkos::Array{ 0, 1 }); + + EXPECT_EQ(vec_ints[0], 34); + EXPECT_EQ(vec_ints[1], 2); + } + + // a multiline list + { + auto drinks = config_map.getStringVector("run", "drinks", std::vector{}); + EXPECT_EQ(drinks.size(), 5); + EXPECT_EQ(drinks[0], "water"); + EXPECT_EQ(drinks[4], "tea"); + } + + free(buffer); +} + +} // namespace kalypsso diff --git a/unit_test/utils/monitoring/HostTimer_test.cpp b/unit_test/utils/monitoring/HostTimer_test.cpp new file mode 100644 index 0000000..bc0bcfb --- /dev/null +++ b/unit_test/utils/monitoring/HostTimer_test.cpp @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// © Commissariat a l'Energie Atomique et aux Energies Alternatives (CEA) +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +#include +#include + +#include + +namespace kalypsso +{ + +TEST(kalypsso_shared_HostTimer_test, timer_test) +{ + using std::chrono::operator"" ms; + + HostTimer timer; + + timer.start(); + const auto start = std::chrono::high_resolution_clock::now(); + std::this_thread::sleep_for(1500ms); + const auto end = std::chrono::high_resolution_clock::now(); + timer.stop(); + + const double elapsed1 = + std::chrono::duration_cast(end - start).count() * 1e-9; + const auto elapsed2 = timer.elapsed(); + + EXPECT_NEAR(elapsed1, elapsed2, 0.01) << "timer failed"; +} + +} // namespace kalypsso diff --git a/unit_test/utils/mpi/ParallelEnv_test.cpp b/unit_test/utils/mpi/ParallelEnv_test.cpp new file mode 100644 index 0000000..b5f796a --- /dev/null +++ b/unit_test/utils/mpi/ParallelEnv_test.cpp @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2025 kalypsso-core authors +// © Commissariat a l'Energie Atomique et aux Energies Alternatives (CEA) +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +#include + +#include "../../main_kalypsso_unittest.h" + +namespace kalypsso +{ + +TEST(kalypsso_utils_mpi_ParallelEnv_test, num_ranks) +{ + ParallelEnv * par_env_ptr = *g_par_env_ptr; + +#ifdef KALYPSSO_CORE_USE_MPI + // here we assume this test was run with 2 MPI ranks + EXPECT_EQ(par_env_ptr->nRanks(), 2) << "ParallelEnv rank failed"; +#else + EXPECT_EQ(par_env_ptr->nRanks(), 1) << "ParallelEnv rank failed"; +#endif +} + +} // namespace kalypsso From e84a579c39f552b548ce63af6781d3f9ad4db0e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 15:14:18 +0000 Subject: [PATCH 2/2] Bump actions/setup-python from 5 to 6 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/pre-commit.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index b253cc1..60a9be1 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v6 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.12' - uses: pre-commit/action@v3.0.1