diff --git a/.github/workflows/idefix-ci-jobs.yml b/.github/workflows/idefix-ci-jobs.yml index 4c3b54715..73882f191 100644 --- a/.github/workflows/idefix-ci-jobs.yml +++ b/.github/workflows/idefix-ci-jobs.yml @@ -24,6 +24,17 @@ env: permissions: {} jobs: + UnitTests: + runs-on: self-hosted + steps: + - name: Check out repo + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + submodules: recursive + persist-credentials: false + - name: Configure, Build Idefix and Run Unit Tests + run: scripts/ci/run-unit-tests $TESTME_OPTIONS + ShocksHydro: runs-on: self-hosted steps: diff --git a/CMakeLists.txt b/CMakeLists.txt index e0206f4d7..3d75f891b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,7 @@ option(Idefix_MHD "enable MHD" OFF) option(Idefix_MPI "enable Message Passing Interface parallelisation" OFF) option(Idefix_HIGH_ORDER_FARGO "Force Fargo to use a PPM reconstruction scheme" OFF) option(Idefix_DEBUG "Enable Idefix debug features (makes the code very slow)" OFF) +option(Idefix_GTEST "Enable building unittests with GoogleTest framework)" OFF) option(Idefix_RUNTIME_CHECKS "Enable runtime sanity checks" OFF) option(Idefix_WERROR "Treat compiler warnings as errors" OFF) option(Idefix_PYTHON "Enable python bindings (requires pybind11)" OFF) @@ -70,6 +71,16 @@ include(SetIdefixProperty) include(SetRequiredBuildSettingsForGCC8) include(CheckHdf5ParallelSupport) include(SuppressFMA) +include(HandleDeps) + +# enable tests +if (Idefix_GTEST) + # enable supports of "make test" + enable_testing() + + # it not, download it internally to the project + GoogleTestDownloadAndLink() +endif() #Idefix requires Cuda Lambdas (experimental) if(Kokkos_ENABLE_CUDA) @@ -98,18 +109,32 @@ if(Idefix_WERROR) endif() endif() +# add tests +if (Idefix_GTEST) + add_executable(unittests) + target_link_libraries(unittests GTest::gtest GTest::gmock) + add_test(unittests unittests) +endif() + # sub directories should be called after add_executable # because the binary target "idefix" should be defined # before adding source files via "target_sources" # create target binary "idefix" add_executable(idefix) +add_library(idefix-core STATIC) add_subdirectory(src build) +# link idefix-core where it should +target_link_libraries(idefix idefix-core) +if (Idefix_GTEST) + target_link_libraries(unittests idefix-core) +endif() + # make absolute get_filename_component(Idefix_PROBLEM_DIR_ABS ${Idefix_PROBLEM_DIR} ABSOLUTE BASE_DIR ${PROJECT_BINARY_DIR}) if(EXISTS ${Idefix_PROBLEM_DIR_ABS}/setup.cpp) - target_sources(idefix PUBLIC ${Idefix_PROBLEM_DIR_ABS}/setup.cpp) + target_sources(idefix PRIVATE ${Idefix_PROBLEM_DIR_ABS}/setup.cpp) else() message(WARNING "No specific setup.cpp found in the problem directory (this message can be ignored if using python to define your problem)") endif() @@ -130,7 +155,7 @@ endif() if(Idefix_MPI) add_compile_definitions("WITH_MPI") find_package(MPI REQUIRED) - target_link_libraries(idefix MPI::MPI_CXX) + target_link_libraries(idefix-core PUBLIC MPI::MPI_CXX) add_subdirectory(src/mpi) endif() @@ -143,9 +168,9 @@ if(Idefix_HDF5) if(Idefix_MPI) set(HDF5_PREFER_PARALLEL TRUE) endif() - target_sources(idefix - PUBLIC src/output/xdmf.cpp - PUBLIC src/output/xdmf.hpp + target_sources(idefix-core + PRIVATE src/output/xdmf.cpp + PRIVATE src/output/xdmf.hpp ) # Prefer imported targets (matches older working behavior), then fall back. find_package(HDF5 QUIET COMPONENTS C) @@ -160,9 +185,9 @@ if(Idefix_HDF5) set(_idefix_hdf5_link_items HDF5::HDF5) endif() - target_link_libraries(idefix ${_idefix_hdf5_link_items}) + target_link_libraries(idefix-core PUBLIC ${_idefix_hdf5_link_items}) message(STATUS "Found HDF5 include directories: ${HDF5_INCLUDE_DIRS}") - target_include_directories(idefix PUBLIC "${HDF5_INCLUDE_DIRS}") + target_include_directories(idefix-core PUBLIC "${HDF5_INCLUDE_DIRS}") if(Idefix_MPI) CheckHdf5ParallelSupport("${_idefix_hdf5_link_items}") if(NOT IDEFIX_HDF5_IS_PARALLEL) @@ -191,8 +216,8 @@ if(Idefix_PYTHON) endif () set(PYBIND11_FINDPYTHON ON CACHE BOOL "Idefix requires python" FORCE) find_package(pybind11 REQUIRED) - target_link_libraries(idefix pybind11::embed) - target_sources(idefix + target_link_libraries(idefix-core PUBLIC pybind11::embed) + target_sources(idefix-core PUBLIC src/pydefix.cpp PUBLIC src/pydefix.hpp ) @@ -288,10 +313,10 @@ if (${Idefix_MPI_GPU_FORCE_COPY}) add_compile_definitions("WITH_GPU_FORCE_COPY") endif() -target_include_directories(idefix PUBLIC +target_include_directories(idefix-core PUBLIC "${Idefix_PROBLEM_DIR_ABS}" ) -target_include_directories(idefix PUBLIC +target_include_directories(idefix-core PUBLIC src/kokkos/core/src src/dataBlock src/dataBlock/planetarySystem @@ -314,7 +339,7 @@ target_include_directories(idefix PUBLIC src ) -target_link_libraries(idefix Kokkos::kokkos) +target_link_libraries(idefix-core PUBLIC Kokkos::kokkos) # Generate header with compiler information (name, flags, path) configure_file( @@ -323,12 +348,19 @@ configure_file( @ONLY ) # Make sure the generated header is on the include path -target_include_directories(idefix PRIVATE ${CMAKE_BINARY_DIR}/build/generated) +target_include_directories(idefix-core PRIVATE ${CMAKE_BINARY_DIR}/build/generated) +if (Idefix_GTEST) + target_include_directories(unittests PRIVATE ${CMAKE_BINARY_DIR}/build/generated) +endif() # disable FMA if needed if(Idefix_SUPPRESS_FMA) message(STATUS "FMA (fused multiply-add) contraction/codegen is disabled") target_suppress_fma(idefix) + target_suppress_fma(idefix-core) + if (Idefix_GTEST) + target_suppress_fma(unittests) + endif() endif() message(STATUS "Idefix final configuration") diff --git a/cmake/AddIdefixSource.cmake b/cmake/AddIdefixSource.cmake index 328ddcb0c..0b4d04946 100644 --- a/cmake/AddIdefixSource.cmake +++ b/cmake/AddIdefixSource.cmake @@ -14,5 +14,5 @@ function(add_idefix_source _new_source) message(STATUS " Adding problem-specific source file ${_new_source}") - target_sources(idefix PUBLIC ${_new_source}) + target_sources(idefix-core PRIVATE ${_new_source}) endfunction() diff --git a/cmake/HandleDeps.cmake b/cmake/HandleDeps.cmake new file mode 100644 index 000000000..cebcd7b73 --- /dev/null +++ b/cmake/HandleDeps.cmake @@ -0,0 +1,30 @@ +###################################################################################### +# Idefix MHD astrophysical code +# +# Source file cmake/HandleDeps.cmake +# +# Last modified : 10/2026 +# +# Copyright(C) by : +# - Sébastien Valat (IPAG/UGA/CNRS - 2026) +# and other code contributors +# +# Licensed under CeCILL 2.1 License, see COPYING for more information +###################################################################################### + +function(GoogleTestDownloadAndLink) + # CAUTION: if you change this verison, you should also check if you need to adapt + # the fix in gtest-idfx and re-import the definition of GTEST_TEST_(). + set(gtest_version "1.18.0") + + # it not, download it internally to the project + cmake_policy(SET CMP0135 NEW) + include(FetchContent) + FetchContent_Declare( + googletest + URL https://github.com/google/googletest/releases/download/v${gtest_version}/googletest-${gtest_version}.tar.gz + ) + # For Windows: Prevent overriding the parent project's compiler/linker settings + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + FetchContent_MakeAvailable(googletest) +endfunction() diff --git a/cmake/ReplaceIdefixSource.cmake b/cmake/ReplaceIdefixSource.cmake index d48f4cbab..1032d035a 100644 --- a/cmake/ReplaceIdefixSource.cmake +++ b/cmake/ReplaceIdefixSource.cmake @@ -32,5 +32,5 @@ function(replace_idefix_source _old_source _new_source) # replace the source with our new source list set_property(TARGET idefix PROPERTY SOURCES ${mylist}) # add the new source file - target_sources(idefix PUBLIC ${_new_source}) + target_sources(idefix-core PRIVATE ${_new_source}) endfunction() diff --git a/scripts/ci/run-unit-tests b/scripts/ci/run-unit-tests new file mode 100755 index 000000000..91f8387ed --- /dev/null +++ b/scripts/ci/run-unit-tests @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +###################################################################################### +# Idefix MHD astrophysical code +# +# Source file scripts/ci/run-unit-tests +# +# Last modified : 10/2026 +# +# Copyright(C) by : +# - Sébastien Valat (IPAG/UGA/CNRS - 2026) +# and other code contributors +# +# Licensed under CeCILL 2.1 License, see COPYING for more information +###################################################################################### + +function gen_cmake_options() +{ + # scan args + while [ $# != 0 ]; do + case "${1}" in + '-Werror') + echo "-DIdefix_WERROR=ON" + shift 1 + ;; + '-ccache') + echo "-DCMAKE_CXX_COMPILER_LAUNCHER=ccache" + shift 1 + ;; + '-intel') + echo "-DCMAKE_CXX_COMPILER=icpx" + echo "-DCMAKE_C_COMPILER=icx" + shift 1 + ;; + '-cuda') + echo "-DKokkos_ENABLE_CUDA=ON" + # disable Async cuda malloc for tests performed on old UCX implementations + echo "-DKokkos_ENABLE_IMPL_CUDA_MALLOC_ASYNC=OFF" + shift 1 + ;; + '-hip') + echo "-DKokkos_ENABLE_HIP=ON" + shift 1 + ;; + *) + echo "Invalid option : ${1}" 1>&2 + return 1 + esac + done +} + +function display_and_run() +{ + echo "" + echo "======================================================================================" + echo ">> $@" + echo "" + "$@" || return 1 +} + +# load ICC compiler env +if [ "$IDEFIX_COMPILER" == icc ]; then + source /opt/intel/oneapi/setvars.sh +fi + +# enable bash features +set -ue + +# load python venv if present +if [ -d .venv ]; then + source .venv/bin/activate +fi + +# to get access to idefix ./pytools/ module in python code +if [[ -v PYTHONPATH ]]; then + export PYTHONPATH="${PWD}:${PYTHONPATH}" +else + export PYTHONPATH="${PWD}" +fi + +# check gen options +display_and_run gen_cmake_options "$@" + +# create build dir +display_and_run mkdir -p build-unittests + +# configure via cmake +display_and_run cmake . -B ./build-unittests -DIdefix_GTEST=ON -DIdefix_PROBLEM_DIR=$PWD/test/MHD/AxisFluxTube/ $(gen_cmake_options "$@") --fresh + +# compile +display_and_run make -C build-unittests -j8 + +# jump in dir to run in +display_and_run cd build-unittests + +# run the unit tests +display_and_run ./unittests + +# END +echo "" +echo "======================================================================================" +echo "Finished with SUCCESS" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d0b714368..c51fbe49f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -21,33 +21,43 @@ add_subdirectory(gravity) add_subdirectory(utils) target_sources(idefix - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/arrays.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/error.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/error.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/global.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/global.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/grid.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/grid.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/gridHost.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/gridHost.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/idefix.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/input.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/input.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/loop.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/macros.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/main.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/profiler.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/profiler.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/real_types.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/reduce.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/setup.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/setup.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/units.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/units.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/timeIntegrator.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/timeIntegrator.cpp - ) + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/main.cpp +) + +target_sources(idefix-core + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/arrays.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/error.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/error.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/global.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/global.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/grid.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/grid.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/gridHost.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/gridHost.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/idefix.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/input.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/input.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/loop.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/macros.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/profiler.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/profiler.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/real_types.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/reduce.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/setup.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/setup.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/units.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/units.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/timeIntegrator.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/timeIntegrator.cpp +) # NOTE: mpi file are added by the root CMakeLists along with dependencies to the MPI library +if (Idefix_GTEST) + target_sources(unittests + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/tests/testKokkos.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/tests/testArrays.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/gtest-idfx.cpp + ) +endif() #add_subdirectory("${PROJECT_BINARY_DIR}" "${PROJECT_BINARY_DIR}/local") diff --git a/src/dataBlock/CMakeLists.txt b/src/dataBlock/CMakeLists.txt index 3ddf4016d..72dcedebf 100644 --- a/src/dataBlock/CMakeLists.txt +++ b/src/dataBlock/CMakeLists.txt @@ -16,19 +16,19 @@ add_subdirectory(planetarySystem) -target_sources(idefix - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/coarsen.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/dataBlock.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/dataBlock.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/dataBlockHost.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/dataBlockHost.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/dumpToFile.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/evolveStage.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/fargo.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/fargo.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/makeGeometry.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/stateContainer.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/stateContainer.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/validation.cpp +target_sources(idefix-core + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/coarsen.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/dataBlock.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/dataBlock.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/dataBlockHost.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/dataBlockHost.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/dumpToFile.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/evolveStage.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/fargo.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/fargo.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/makeGeometry.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/stateContainer.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/stateContainer.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/validation.cpp ) diff --git a/src/dataBlock/planetarySystem/CMakeLists.txt b/src/dataBlock/planetarySystem/CMakeLists.txt index 8be62f30a..f89a7615e 100644 --- a/src/dataBlock/planetarySystem/CMakeLists.txt +++ b/src/dataBlock/planetarySystem/CMakeLists.txt @@ -12,11 +12,11 @@ # Licensed under CeCILL 2.1 License, see COPYING for more information ###################################################################################### -target_sources(idefix - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/planet.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/planet.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/planetarySystem.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/planetarySystem.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/planetStructs.hpp +target_sources(idefix-core + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/planet.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/planet.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/planetarySystem.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/planetarySystem.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/planetStructs.hpp ) diff --git a/src/fluid/CMakeLists.txt b/src/fluid/CMakeLists.txt index b5d70be2c..084c0beab 100644 --- a/src/fluid/CMakeLists.txt +++ b/src/fluid/CMakeLists.txt @@ -20,24 +20,24 @@ add_subdirectory(RiemannSolver) add_subdirectory(tracer) -target_sources(idefix - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/addNonIdealMHDFlux.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/addSourceTerms.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/calcCurrent.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/calcParabolicFlux.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/calcRightHandSide.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/checkNan.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/checkDivB.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/coarsenFlow.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/convertConsToPrim.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/drag.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/drag.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/evolveStage.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/fluid_defs.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/enroll.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/fluid.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/viscosity.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/viscosity.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/thermalDiffusion.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/thermalDiffusion.cpp +target_sources(idefix-core + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/addNonIdealMHDFlux.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/addSourceTerms.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/calcCurrent.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/calcParabolicFlux.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/calcRightHandSide.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/checkNan.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/checkDivB.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/coarsenFlow.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/convertConsToPrim.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/drag.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/drag.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/evolveStage.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/fluid_defs.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/enroll.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/fluid.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/viscosity.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/viscosity.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/thermalDiffusion.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/thermalDiffusion.cpp ) diff --git a/src/fluid/RiemannSolver/CMakeLists.txt b/src/fluid/RiemannSolver/CMakeLists.txt index 9099fea25..d4863fda0 100644 --- a/src/fluid/RiemannSolver/CMakeLists.txt +++ b/src/fluid/RiemannSolver/CMakeLists.txt @@ -17,11 +17,11 @@ add_subdirectory(HDsolvers) add_subdirectory(Dustsolvers) -target_sources(idefix - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/calcFlux.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/extrapolateToFaces.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/flux.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/riemannSolver.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/shockFlattening.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/slopeLimiter.hpp +target_sources(idefix-core + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/calcFlux.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/extrapolateToFaces.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/flux.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/riemannSolver.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/shockFlattening.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/slopeLimiter.hpp ) diff --git a/src/fluid/RiemannSolver/Dustsolvers/CMakeLists.txt b/src/fluid/RiemannSolver/Dustsolvers/CMakeLists.txt index 6f5a7bb38..0dea6346e 100644 --- a/src/fluid/RiemannSolver/Dustsolvers/CMakeLists.txt +++ b/src/fluid/RiemannSolver/Dustsolvers/CMakeLists.txt @@ -12,6 +12,6 @@ # Licensed under CeCILL 2.1 License, see COPYING for more information ###################################################################################### -target_sources(idefix - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/hllDust.hpp +target_sources(idefix-core + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/hllDust.hpp ) diff --git a/src/fluid/RiemannSolver/HDsolvers/CMakeLists.txt b/src/fluid/RiemannSolver/HDsolvers/CMakeLists.txt index 5d684e731..9fccb4bec 100644 --- a/src/fluid/RiemannSolver/HDsolvers/CMakeLists.txt +++ b/src/fluid/RiemannSolver/HDsolvers/CMakeLists.txt @@ -12,9 +12,9 @@ # Licensed under CeCILL 2.1 License, see COPYING for more information ###################################################################################### -target_sources(idefix - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/hllcHD.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/hllHD.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/roeHD.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/tvdlfHD.hpp +target_sources(idefix-core + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/hllcHD.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/hllHD.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/roeHD.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/tvdlfHD.hpp ) diff --git a/src/fluid/RiemannSolver/MHDsolvers/CMakeLists.txt b/src/fluid/RiemannSolver/MHDsolvers/CMakeLists.txt index 268318f84..6a32c7b9c 100644 --- a/src/fluid/RiemannSolver/MHDsolvers/CMakeLists.txt +++ b/src/fluid/RiemannSolver/MHDsolvers/CMakeLists.txt @@ -12,9 +12,9 @@ # Licensed under CeCILL 2.1 License, see COPYING for more information ###################################################################################### -target_sources(idefix - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/hlldMHD.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/hllMHD.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/roeMHD.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/tvdlfMHD.hpp +target_sources(idefix-core + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/hlldMHD.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/hllMHD.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/roeMHD.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/tvdlfMHD.hpp ) diff --git a/src/fluid/boundary/CMakeLists.txt b/src/fluid/boundary/CMakeLists.txt index 8c04f2b3c..280e53232 100644 --- a/src/fluid/boundary/CMakeLists.txt +++ b/src/fluid/boundary/CMakeLists.txt @@ -12,8 +12,8 @@ # Licensed under CeCILL 2.1 License, see COPYING for more information ###################################################################################### -target_sources(idefix - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/axis.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/axis.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/boundary.hpp +target_sources(idefix-core + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/axis.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/axis.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/boundary.hpp ) diff --git a/src/fluid/braginskii/CMakeLists.txt b/src/fluid/braginskii/CMakeLists.txt index 8b6414014..eabd0ec1d 100644 --- a/src/fluid/braginskii/CMakeLists.txt +++ b/src/fluid/braginskii/CMakeLists.txt @@ -12,9 +12,9 @@ # Licensed under CeCILL 2.1 License, see COPYING for more information ###################################################################################### -target_sources(idefix - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/bragThermalDiffusion.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/bragThermalDiffusion.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/bragViscosity.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/bragViscosity.cpp +target_sources(idefix-core + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/bragThermalDiffusion.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/bragThermalDiffusion.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/bragViscosity.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/bragViscosity.cpp ) diff --git a/src/fluid/constrainedTransport/CMakeLists.txt b/src/fluid/constrainedTransport/CMakeLists.txt index d9ce0c658..20cac360b 100644 --- a/src/fluid/constrainedTransport/CMakeLists.txt +++ b/src/fluid/constrainedTransport/CMakeLists.txt @@ -12,14 +12,14 @@ # Licensed under CeCILL 2.1 License, see COPYING for more information ###################################################################################### -target_sources(idefix - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/calcCornerEmf.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/calcNonidealEMF.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/calcRiemannEmf.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/constrainedTransport.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/EMFexchange.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/enforceEMFBoundary.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/enforceVectorPotentialBoundary.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/evolveMagField.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/evolveVectorPotential.hpp +target_sources(idefix-core + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/calcCornerEmf.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/calcNonidealEMF.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/calcRiemannEmf.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/constrainedTransport.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/EMFexchange.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/enforceEMFBoundary.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/enforceVectorPotentialBoundary.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/evolveMagField.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/evolveVectorPotential.hpp ) diff --git a/src/fluid/eos/CMakeLists.txt b/src/fluid/eos/CMakeLists.txt index 14a2c484d..04f6be9a9 100644 --- a/src/fluid/eos/CMakeLists.txt +++ b/src/fluid/eos/CMakeLists.txt @@ -12,8 +12,8 @@ # Licensed under CeCILL 2.1 License, see COPYING for more information ###################################################################################### -target_sources(idefix - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/eos_adiabatic.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/eos_isothermal.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/eos.hpp +target_sources(idefix-core + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/eos_adiabatic.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/eos_isothermal.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/eos.hpp ) diff --git a/src/fluid/tracer/CMakeLists.txt b/src/fluid/tracer/CMakeLists.txt index 282ec7ddb..1f8a3d55a 100644 --- a/src/fluid/tracer/CMakeLists.txt +++ b/src/fluid/tracer/CMakeLists.txt @@ -12,7 +12,7 @@ # Licensed under CeCILL 2.1 License, see COPYING for more information ###################################################################################### -target_sources(idefix - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/tracer.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/tracer.hpp +target_sources(idefix-core + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/tracer.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/tracer.hpp ) diff --git a/src/gravity/CMakeLists.txt b/src/gravity/CMakeLists.txt index 1a643fa23..9721992b0 100644 --- a/src/gravity/CMakeLists.txt +++ b/src/gravity/CMakeLists.txt @@ -12,11 +12,11 @@ # Licensed under CeCILL 2.1 License, see COPYING for more information ###################################################################################### -target_sources(idefix - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/gravity.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/gravity.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/laplacian.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/laplacian.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/selfGravity.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/selfGravity.cpp +target_sources(idefix-core + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/gravity.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/gravity.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/laplacian.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/laplacian.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/selfGravity.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/selfGravity.cpp ) diff --git a/src/gtest-idfx.cpp b/src/gtest-idfx.cpp new file mode 100644 index 000000000..2390b0276 --- /dev/null +++ b/src/gtest-idfx.cpp @@ -0,0 +1,143 @@ +// *********************************************************************************** +// Idefix MHD astrophysical code +// +// Source file src/utils/gtest-idfx.hpp +// +// Last modified : 10/2026 +// +// Copyright(C) by : +// - Sébastien Valat (IPAG/UGA/CNRS - 2026) +// and other code contributors +// +// Licensed under CeCILL 2.1 License, see COPYING for more information +// *********************************************************************************** + +// includes +#include +#if __has_include() + #include // NOLINT [build/c++17] + namespace fs = std::filesystem; +#elif __has_include() + #include + namespace fs = std::experimental::filesystem; +#else + error "Missing the header." +#endif +#include +#include +#ifdef WITH_MPI + #include +#endif //WITH_MPI +#include "idefix.hpp" +#include "gtest-idfx.hpp" + +/** + * Get the full path to get access to the data/ directory on same location than the sourceFile. + * @param sourceFile Absolute path to the source file to consider. + * @param fname Filename to append to SRC_DIR/data/. +*/ +std::string idfx_get_data_file_path( + const std::string & sourceFile, const std::string & fname) { + fs::path p(sourceFile); + return p.parent_path() / "data" / fname; +} + +/** + * Configure MPI environnement to setup/tear down +**/ +class MPIEnvironment : public ::testing::Environment { + public: + MPIEnvironment(int * argc, char *** argv): argc(argc), argv(argv) {} + ~MPIEnvironment() override {} + + // Override this to define how to set up the environment. + void SetUp() override { + #ifdef WITH_MPI + int mpiError = MPI_Init(argc, argv); + ASSERT_EQ(mpiError, 0); + #endif + } + + // Override this to define how to tear down the environment. + void TearDown() override { + #ifdef WITH_MPI + int mpiError = MPI_Finalize(); + ASSERT_EQ(mpiError, 0); + #endif + } + + private: + int * argc{nullptr}; + char *** argv{nullptr}; +}; + +/** + * configure MPI environnement to setup/tear down +*/ +class KokkosEnvironment : public ::testing::Environment { + public: + KokkosEnvironment(int argc, char ** argv): argc(argc), argv(argv) {} + ~KokkosEnvironment() override {} + + // Override this to define how to set up the environment. + void SetUp() override { + Kokkos::initialize(this->argc, this->argv); + } + + // Override this to define how to tear down the environment. + void TearDown() override { + Kokkos::finalize(); + } + + private: + int argc{0}; + char ** argv{nullptr}; +}; + +/** + * configure MPI environnement to setup/tear down +*/ +class IdefixEnvironment : public ::testing::Environment { + public: + ~IdefixEnvironment() override {} + + // Override this to define how to set up the environment. + void SetUp() override { + int status = idfx::initialize(); + ASSERT_EQ(status, 0); + } + + // Override this to define how to tear down the environment. + void TearDown() override { + } +}; + +// setup advanded main for tests using MPI / Kokkos +int main(int argc, char* argv[]) { + bool initKokkosBeforeMPI = false; + // When running on GPUS with Omnipath network, + // Kokkos needs to be initialised *before* the MPI layer + #ifdef KOKKOS_ENABLE_CUDA + if(std::getenv("PSM2_CUDA") != NULL) { + initKokkosBeforeMPI = true; + } + #endif + + //ini gtest + ::testing::InitGoogleTest(&argc, argv); + + //init MPI & Kokkos with right order + if (initKokkosBeforeMPI) { + ::testing::AddGlobalTestEnvironment(new MPIEnvironment(&argc, &argv)); + ::testing::AddGlobalTestEnvironment(new KokkosEnvironment(argc, argv)); + } else { + ::testing::AddGlobalTestEnvironment(new KokkosEnvironment(argc, argv)); + ::testing::AddGlobalTestEnvironment(new MPIEnvironment(&argc, &argv)); + } + + //init idefix + ::testing::AddGlobalTestEnvironment(new IdefixEnvironment); + + //run tests + return RUN_ALL_TESTS(); +} diff --git a/src/gtest-idfx.hpp b/src/gtest-idfx.hpp new file mode 100644 index 000000000..cd8c012dd --- /dev/null +++ b/src/gtest-idfx.hpp @@ -0,0 +1,432 @@ +// *********************************************************************************** +// Idefix MHD astrophysical code +// +// Source file src/utils/gtest-idfx.hpp +// +// Last modified : 10/2026 +// +// Copyright(C) by : +// - Sébastien Valat (IPAG/UGA/CNRS - 2026) +// and other code contributors +// +// Licensed under CeCILL 2.1 License, see COPYING for more information +// *********************************************************************************** + +#ifndef GTEST_IDFX_HPP_ +#define GTEST_IDFX_HPP_ + +// includes +#include +#include +#include +#include "idefix.hpp" + +std::string idfx_get_data_file_path(const std::string & sourceFile, const std::string & fname); + +/** + * A macro to load a datafile present at data/{fname}, data/ being in the same dir than the sources. + * @param fname The file name to append to the SRC_DIR/data path. +*/ +#define DATA_FILE(fname) (idfx_get_data_file_path(__FILE__, fname)) + +/** + * Compare two C arrays of given size. + * @param arr1 First C pointer. + * @param arr2 Second C pointer. + * @param cnt Number of elements to compare. + * @param msg Debugging message to print on error. +**/ +#define ASSERT_C_ARRAY_EQ(arr1, arr2, cnt, msg) \ + do { \ + for (size_t __i__ = 0 ; __i__ < cnt ; __i__++) \ + ASSERT_EQ(arr1[__i__], arr2[__i__]) << "index=" << __i__ << "\n" << msg; \ + } while(0) + +/** + * Apply a google mock patcher matcher to each element of an array + * @param array Name of the Kokkos::view to validate. + * @param matcher Name of the google mock matcher to apply. + * @param ... Optional arguments to pass to the matcher constructor. + * + * For usage example, please see ./src/tests/testKokkos.cpp + * + * For what can be used as matcher name, see : + * https://google.github.io/googletest/reference/matchers.html#generic-comparison + */ +#define ASSERT_THAT_1D_ARRAY(array, matcher, ...) \ + do { \ + Kokkos::fence(); \ + auto array##_host = Kokkos::create_mirror_view(array); \ + Kokkos::deep_copy(array##_host, array); \ + Kokkos::fence(); \ + for (size_t i = 0 ; i < array##_host.extent(0) ; i++) { \ + ASSERT_THAT(array##_host(i), matcher(__VA_ARGS__)) << " at: (" << i << ")"; \ + } \ + } while(0) + +/** + * Apply a google mock patcher matcher to each element of two array applying + * the given matcher to each element. + * @param array Name of the Kokkos::view to validate. + * @param matcher Name of the google mock matcher to apply. It will recieve the array + * value as first parameter. + * + * For usage example, please see ./src/tests/testKokkos.cpp + * + * For what can be used as matcher name, see : + * https://google.github.io/googletest/reference/matchers.html#generic-comparison + */ +#define ASSERT_THAT_1D_ARRAYS(array, matcher, array2) \ + do { \ + Kokkos::fence(); \ + auto array##_host = Kokkos::create_mirror_view(array); \ + auto array2##_host = Kokkos::create_mirror_view(array2); \ + Kokkos::deep_copy(array##_host, array); \ + Kokkos::deep_copy(array2##_host, array2); \ + Kokkos::fence(); \ + for (size_t i = 0 ; i < array##_host.extent(0) ; i++) { \ + ASSERT_THAT(array##_host(i), matcher(array2##_host(i))) << " at: (" << i << ")"; \ + } \ + } while(0) + +/** + * Apply a google mock patcher matcher to each element of two array applying + * the given matcher to each element. + * @param array Name of the Kokkos::view to validate. + * @param matcher Name of the google mock matcher to apply. It will recieve the array + * value as first parameter. + * @param ... Optional extra arguments to pass to the matcher constructor. + * + * For usage example, please see ./src/tests/testKokkos.cpp + * + * For what can be used as matcher name, see : + * https://google.github.io/googletest/reference/matchers.html#generic-comparison + */ +#define ASSERT_THAT_1D_ARRAYS_ARGS(array, that, array2, ...) \ + do { \ + Kokkos::fence(); \ + auto array##_host = Kokkos::create_mirror_view(array); \ + auto array2##_host = Kokkos::create_mirror_view(array2); \ + Kokkos::deep_copy(array##_host, array); \ + Kokkos::deep_copy(array2##_host, array2); \ + Kokkos::fence(); \ + for (size_t i = 0 ; i < array##_host.extent(0) ; i++) { \ + ASSERT_THAT(array##_host(i), that(array2##_host(i), __VA_ARGS__)) \ + << " at: (" << i << ")"; \ + } \ + } while(0) + +/** + * Apply a google mock patcher matcher to each element of an array + * @param array Name of the Kokkos::view to validate. + * @param matcher Name of the google mock matcher to apply. + * @param ... Optional arguments to pass to the matcher constructor. + * + * For usage example, please see ./src/tests/testKokkos.cpp + * + * For what can be used as matcher name, see : + * https://google.github.io/googletest/reference/matchers.html#generic-comparison + */ +#define ASSERT_THAT_2D_ARRAY(array, matcher, ...) \ + do { \ + Kokkos::fence(); \ + auto array##_host = Kokkos::create_mirror_view(array); \ + Kokkos::deep_copy(array##_host, array); \ + Kokkos::fence(); \ + for (size_t i = 0 ; i < array##_host.extent(0) ; i++) { \ + for (size_t j = 0 ; j < array##_host.extent(1) ; j++) { \ + ASSERT_THAT(array##_host(i, j), matcher(__VA_ARGS__)) \ + << " at: (" << i << " , " << j << ")"; \ + } \ + } \ + } while(0) + +/** + * Apply a google mock patcher matcher to each element of two array applying + * the given matcher to each element. + * @param array Name of the Kokkos::view to validate. + * @param matcher Name of the google mock matcher to apply. It will recieve the array + * value as first parameter. + * + * For usage example, please see ./src/tests/testKokkos.cpp + * + * For what can be used as matcher name, see : + * https://google.github.io/googletest/reference/matchers.html#generic-comparison + */ +#define ASSERT_THAT_2D_ARRAYS(array, matcher, array2) \ + do { \ + Kokkos::fence(); \ + auto array##_host = Kokkos::create_mirror_view(array); \ + auto array2##_host = Kokkos::create_mirror_view(array2); \ + Kokkos::deep_copy(array##_host, array); \ + Kokkos::deep_copy(array2##_host, array2); \ + Kokkos::fence(); \ + for (size_t i = 0 ; i < array##_host.extent(0) ; i++) { \ + for (size_t j = 0 ; j < array##_host.extent(1) ; j++) { \ + ASSERT_THAT(array##_host(i, j), matcher(array2##_host(i, j))) \ + << " at: (" << i << " , " << j << ")"; \ + } \ + } \ + } while(0) + +/** + * Apply a google mock patcher matcher to each element of two array applying + * the given matcher to each element. + * @param array Name of the Kokkos::view to validate. + * @param matcher Name of the google mock matcher to apply. It will recieve the array + * value as first parameter. + * @param ... Optional extra arguments to pass to the matcher constructor. + * + * For usage example, please see ./src/tests/testKokkos.cpp + * + * For what can be used as matcher name, see : + * https://google.github.io/googletest/reference/matchers.html#generic-comparison + */ +#define ASSERT_THAT_2D_ARRAYS_ARGS(array, matcher, array2, ...) \ + do { \ + Kokkos::fence(); \ + auto array##_host = Kokkos::create_mirror_view(array); \ + auto array2##_host = Kokkos::create_mirror_view(array2); \ + Kokkos::deep_copy(array##_host, array); \ + Kokkos::deep_copy(array2##_host, array2); \ + Kokkos::fence(); \ + for (size_t i = 0 ; i < array##_host.extent(0) ; i++) { \ + for (size_t j = 0 ; j < array##_host.extent(1) ; j++) { \ + ASSERT_THAT(array##_host(i, j), matcher(array2##_host(i, j), __VA_ARGS__)) \ + << " at: (" << i << " , " << j << ")"; \ + } \ + } \ + } while(0) + +/** + * Apply a google mock patcher matcher to each element of an array + * @param array Name of the Kokkos::view to validate. + * @param matcher Name of the google mock matcher to apply. + * @param ... Optional arguments to pass to the matcher constructor. + * + * For usage example, please see ./src/tests/testKokkos.cpp + * + * For what can be used as matcher name, see : + * https://google.github.io/googletest/reference/matchers.html#generic-comparison + */ +#define ASSERT_THAT_3D_ARRAY(array, matcher, ...) \ + do { \ + Kokkos::fence(); \ + auto array##_host = Kokkos::create_mirror_view(array); \ + Kokkos::deep_copy(array##_host, array); \ + Kokkos::fence(); \ + for (size_t i = 0 ; i < array##_host.extent(0) ; i++) { \ + for (size_t j = 0 ; j < array##_host.extent(1) ; j++) { \ + for (size_t k = 0 ; k < array##_host.extent(2) ; k++) { \ + ASSERT_THAT(array##_host(i, j, k), matcher(__VA_ARGS__)) \ + << " at: (" << i << " , " << j << " , " << k << ")"; \ + } \ + } \ + } \ + } while(0) + +/** + * Apply a google mock patcher matcher to each element of two array applying + * the given matcher to each element. + * @param array Name of the Kokkos::view to validate. + * @param matcher Name of the google mock matcher to apply. It will recieve the array + * value as first parameter. + * + * For usage example, please see ./src/tests/testKokkos.cpp + * + * For what can be used as matcher name, see : + * https://google.github.io/googletest/reference/matchers.html#generic-comparison + */ +#define ASSERT_THAT_3D_ARRAYS(array, matcher, array2) \ + do { \ + Kokkos::fence(); \ + auto array##_host = Kokkos::create_mirror_view(array); \ + auto array2##_host = Kokkos::create_mirror_view(array2); \ + Kokkos::deep_copy(array##_host, array); \ + Kokkos::deep_copy(array2##_host, array2); \ + Kokkos::fence(); \ + for (size_t i = 0 ; i < array##_host.extent(0) ; i++) { \ + for (size_t j = 0 ; j < array##_host.extent(1) ; j++) { \ + for (size_t k = 0 ; k < array##_host.extent(2) ; k++) { \ + ASSERT_THAT(array##_host(i, j, k), matcher(array2##_host(i, j, k))) \ + << " at: (" << i << " , " << j << " , " << k << ")"; \ + } \ + } \ + } \ + } while(0) + +/** + * Apply a google mock patcher matcher to each element of two array applying + * the given matcher to each element. + * @param array Name of the Kokkos::view to validate. + * @param matcher Name of the google mock matcher to apply. It will recieve the array + * value as first parameter. + * @param ... Optional extra arguments to pass to the matcher constructor. + * + * For usage example, please see ./src/tests/testKokkos.cpp + * + * For what can be used as matcher name, see : + * https://google.github.io/googletest/reference/matchers.html#generic-comparison + */ +#define ASSERT_THAT_3D_ARRAYS_ARGS(array, matcher, array2, ...) \ + do { \ + Kokkos::fence(); \ + auto array##_host = Kokkos::create_mirror_view(array); \ + auto array2##_host = Kokkos::create_mirror_view(array2); \ + Kokkos::deep_copy(array##_host, array); \ + Kokkos::deep_copy(array2##_host, array2); \ + Kokkos::fence(); \ + for (size_t i = 0 ; i < array##_host.extent(0) ; i++) { \ + for (size_t j = 0 ; j < array##_host.extent(1) ; j++) { \ + for (size_t k = 0 ; k < array##_host.extent(2) ; k++) { \ + ASSERT_THAT(array##_host(i, j, k), matcher(array2##_host(i, j, k), __VA_ARGS__)) \ + << " at: (" << i << " , " << j << " , " << k << ")"; \ + } \ + } \ + } \ + } while(0) + +/** + * Apply a google mock patcher matcher to each element of an array + * @param array Name of the Kokkos::view to validate. + * @param matcher Name of the google mock matcher to apply. + * @param ... Optional arguments to pass to the matcher constructor. + * + * For usage example, please see ./src/tests/testKokkos.cpp + * + * For what can be used as matcher name, see : + * https://google.github.io/googletest/reference/matchers.html#generic-comparison + */ +#define ASSERT_THAT_4D_ARRAY(array, matcher, ...) \ + do { \ + Kokkos::fence(); \ + auto array##_host = Kokkos::create_mirror_view(array); \ + Kokkos::deep_copy(array##_host, array); \ + Kokkos::fence(); \ + for (size_t i = 0 ; i < array##_host.extent(0) ; i++) { \ + for (size_t j = 0 ; j < array##_host.extent(1) ; j++) { \ + for (size_t k = 0 ; k < array##_host.extent(2) ; k++) { \ + for (size_t l = 0 ; l < array##_host.extent(3) ; l++) { \ + ASSERT_THAT(array##_host(i, j, k, l), matcher(__VA_ARGS__)) \ + << " at: (" << i << " , " << j << " , " << k << " , " << l << ")"; \ + } \ + } \ + } \ + } \ + } while(0) + +/** + * Apply a google mock patcher matcher to each element of two array applying + * the given matcher to each element. + * @param array Name of the Kokkos::view to validate. + * @param matcher Name of the google mock matcher to apply. It will recieve the array + * value as first parameter. + * + * For usage example, please see ./src/tests/testKokkos.cpp + * + * For what can be used as matcher name, see : + * https://google.github.io/googletest/reference/matchers.html#generic-comparison + */ +#define ASSERT_THAT_4D_ARRAYS(array, matcher, array2) \ + do { \ + Kokkos::fence(); \ + auto array##_host = Kokkos::create_mirror_view(array); \ + auto array2##_host = Kokkos::create_mirror_view(array2); \ + Kokkos::deep_copy(array##_host, array); \ + Kokkos::deep_copy(array2##_host, array2); \ + Kokkos::fence(); \ + for (size_t i = 0 ; i < array##_host.extent(0) ; i++) { \ + for (size_t j = 0 ; j < array##_host.extent(1) ; j++) { \ + for (size_t k = 0 ; k < array##_host.extent(2) ; k++) { \ + for (size_t l = 0 ; l < array##_host.extent(3) ; l++) { \ + ASSERT_THAT(array##_host(i, j, k, l), matcher(array2##_host(i, j, k, l))) \ + << " at: (" << i << " , " << j << " , " << k << " , " << l << ")"; \ + } \ + } \ + } \ + } \ + } while(0) + +/** + * Apply a google mock patcher matcher to each element of two array applying + * the given matcher to each element. + * @param array Name of the Kokkos::view to validate. + * @param matcher Name of the google mock matcher to apply. It will recieve the array + * value as first parameter. + * @param ... Optional extra arguments to pass to the matcher constructor. + * + * For usage example, please see ./src/tests/testKokkos.cpp + * + * For what can be used as matcher name, see : + * https://google.github.io/googletest/reference/matchers.html#generic-comparison + */ +#define ASSERT_THAT_4D_ARRAYS_ARGS(array, matcher, array2, ...) \ + do { \ + Kokkos::fence(); \ + auto array##_host = Kokkos::create_mirror_view(array); \ + auto array2##_host = Kokkos::create_mirror_view(array2); \ + Kokkos::deep_copy(array##_host, array); \ + Kokkos::deep_copy(array2##_host, array2); \ + Kokkos::fence(); \ + for (size_t i = 0 ; i < array##_host.extent(0) ; i++) { \ + for (size_t j = 0 ; j < array##_host.extent(1) ; j++) { \ + for (size_t k = 0 ; k < array##_host.extent(2) ; k++) { \ + for (size_t l = 0 ; l < array##_host.extent(3) ; l++) { \ + ASSERT_THAT(array##_host(i, j, k, l), matcher(array2##_host(i, j, k, l), __VA_ARGS__)) \ + << " at: (" << i << " , " << j << " , " << k << " , " << l << ")"; \ + } \ + } \ + } \ + } \ + } while(0) + +//Note from Sébastien: I don't like too much that approach but it looks the only easy one. +//Patch GTEST_TEST_() so it is supported by NVCC. We fix here the usage of "private" +//keyword for TestBody function which prevent using KOKKOS_LAMBDA. +//Fix is reported coming from : https://github.com/google/googletest/issues/4104 +//We re-write the GTEST_TEST_ with that feature. +//Here it if the GEST_TEST_() imported from googletest/include/gtest/internal/gtest-internal.h +//version 1.18.0 +#define GTEST_TESTBODY_VISIBILITY public +#undef GTEST_TEST_ +#define GTEST_TEST_(test_suite_name, test_name, parent_class, parent_id) \ + static_assert(sizeof(GTEST_STRINGIFY_(test_suite_name)) > 1, \ + "test_suite_name must not be empty"); \ + static_assert(sizeof(GTEST_STRINGIFY_(test_name)) > 1, \ + "test_name must not be empty"); \ + class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \ + : public parent_class { \ + public: \ + GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() = default; \ + ~GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() override = default; \ + GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \ + (const GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &) = delete; \ + GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) & operator=( \ + const GTEST_TEST_CLASS_NAME_(test_suite_name, \ + test_name) &) = delete; /* NOLINT */ \ + GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \ + (GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &&) noexcept = delete; \ + GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) & operator=( \ + GTEST_TEST_CLASS_NAME_(test_suite_name, \ + test_name) &&) noexcept = delete; /* NOLINT */ \ + \ + GTEST_TESTBODY_VISIBILITY: \ + void TestBody() override; \ + private: \ + [[maybe_unused]] static ::testing::TestInfo* const test_info_; \ + }; \ + \ + ::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_suite_name, \ + test_name)::test_info_ = \ + ::testing::internal::MakeAndRegisterTestInfo( \ + #test_suite_name, #test_name, nullptr, nullptr, \ + ::testing::internal::CodeLocation(__FILE__, __LINE__), (parent_id), \ + ::testing::internal::SuiteApiResolver< \ + parent_class>::GetSetUpCaseOrSuite(__FILE__, __LINE__), \ + ::testing::internal::SuiteApiResolver< \ + parent_class>::GetTearDownCaseOrSuite(__FILE__, __LINE__), \ + new ::testing::internal::TestFactoryImpl); \ + void GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::TestBody() + +#endif //GTEST_IDFX_HPP_ diff --git a/src/mpi/CMakeLists.txt b/src/mpi/CMakeLists.txt index 8a9eef4d9..bc57f0313 100644 --- a/src/mpi/CMakeLists.txt +++ b/src/mpi/CMakeLists.txt @@ -12,10 +12,10 @@ # Licensed under CeCILL 2.1 License, see COPYING for more information ###################################################################################### -target_sources(idefix - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/buffer.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/exchanger.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/exchanger.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/mpi.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/mpi.cpp +target_sources(idefix-core + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/buffer.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/exchanger.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/exchanger.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/mpi.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/mpi.cpp ) diff --git a/src/output/CMakeLists.txt b/src/output/CMakeLists.txt index 17d19b549..263076579 100644 --- a/src/output/CMakeLists.txt +++ b/src/output/CMakeLists.txt @@ -13,14 +13,14 @@ # Licensed under CeCILL 2.1 License, see COPYING for more information ###################################################################################### -target_sources(idefix - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/slice.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/slice.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/dump.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/dump.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/output.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/output.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/scalarField.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/vtk.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/vtk.hpp +target_sources(idefix-core + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/slice.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/slice.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/dump.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/dump.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/output.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/output.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/scalarField.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/vtk.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/vtk.hpp ) diff --git a/src/rkl/CMakeLists.txt b/src/rkl/CMakeLists.txt index 865410265..74d979e72 100644 --- a/src/rkl/CMakeLists.txt +++ b/src/rkl/CMakeLists.txt @@ -12,6 +12,6 @@ # Licensed under CeCILL 2.1 License, see COPYING for more information ###################################################################################### -target_sources(idefix - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/rkl.hpp +target_sources(idefix-core + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/rkl.hpp ) diff --git a/src/tests/testArrays.cpp b/src/tests/testArrays.cpp new file mode 100644 index 000000000..177553555 --- /dev/null +++ b/src/tests/testArrays.cpp @@ -0,0 +1,125 @@ +// *********************************************************************************** +// Idefix MHD astrophysical code +// +// Source file src/tests/testArrays.hpp +// +// Last modified : 10/2026 +// +// Copyright(C) by : +// - Sébastien Valat (IPAG/UGA/CNRS - 2026) +// and other code contributors +// +// Licensed under CeCILL 2.1 License, see COPYING for more information +// *********************************************************************************** + +// headers +#include +#include +#include "idefix.hpp" +#include "arrays.hpp" +#include "gtest-idfx.hpp" + +TEST(Array, assert_1d) { + //data array + IdefixArray1D a("a", 10); + + //ref array + IdefixArray1D b("b", 10); + + //make both 5 + idefix_for("Loop1", 0, 10, KOKKOS_LAMBDA (const int i) { + a(i) = b(i) = 5; + }); + + //check all 5 static + ASSERT_THAT_1D_ARRAY(a, testing::Eq, 5); + + //compare both array + ASSERT_THAT_1D_ARRAYS(a, testing::Eq, b); + + //compare with near + ASSERT_THAT_1D_ARRAYS_ARGS(a, testing::DoubleNear, b, 0.001); +} + +TEST(Array, assert_1d_is_nan) { + //data array + IdefixArray1D a("a", 10); + + //ref array + IdefixArray1D b("b", 10); + + //make both 5 + idefix_for("Loop1", 0, 10, KOKKOS_LAMBDA (const int i) { + a(i) = b(i) = NAN; + }); + + //check all 5 static + ASSERT_THAT_1D_ARRAY(a, testing::IsNan); +} + +TEST(Array, assert_2d) { + //data array + IdefixArray2D a("a", 10, 20); + + //ref array + IdefixArray2D b("b", 10, 20); + + //make both 5 + idefix_for("Loop1", 0, 10, 0, 20, KOKKOS_LAMBDA (const int j, const int i) { + a(j, i) = b(j, i) = 5; + }); + + //check all 5 static + ASSERT_THAT_2D_ARRAY(a, testing::Eq, 5); + + //compare both array + ASSERT_THAT_2D_ARRAYS(a, testing::Eq, b); + + //compare with near + ASSERT_THAT_2D_ARRAYS_ARGS(a, testing::DoubleNear, b, 0.001); +} + +TEST(Array, assert_3d) { + //data array + IdefixArray3D a("a", 10, 20, 30); + + //ref array + IdefixArray3D b("b", 10, 20, 30); + + //make both 5 + idefix_for("Loop1", 0, 10, 0, 20, 0, 30, KOKKOS_LAMBDA (const int k, const int j, const int i) { + a(k, j, i) = b(k, j, i) = 5; + }); + + //check all 5 static + ASSERT_THAT_3D_ARRAY(a, testing::Eq, 5); + + //compare both array + ASSERT_THAT_3D_ARRAYS(a, testing::Eq, b); + + //compare with near + ASSERT_THAT_3D_ARRAYS_ARGS(a, testing::DoubleNear, b, 0.001); +} + +TEST(Array, assert_4d) { + //data array + IdefixArray4D a("a", 10, 20, 30, 40); + + //ref array + IdefixArray4D b("b", 10, 20, 30, 40); + + //make both 5 + idefix_for("Loop1", 0, 10, 0, 20, 0, 30, 0, 40, + KOKKOS_LAMBDA (const int l, const int k, const int j, const int i) { + a(l, k, j, i) = b(l, k, j, i) = 5; + }); + + //check all 5 static + ASSERT_THAT_4D_ARRAY(a, testing::Eq, 5); + + //compare both array + ASSERT_THAT_4D_ARRAYS(a, testing::Eq, b); + + //compare with near + ASSERT_THAT_4D_ARRAYS_ARGS(a, testing::DoubleNear, b, 0.001); +} diff --git a/src/tests/testKokkos.cpp b/src/tests/testKokkos.cpp new file mode 100644 index 000000000..fed7ff25f --- /dev/null +++ b/src/tests/testKokkos.cpp @@ -0,0 +1,57 @@ +// *********************************************************************************** +// Idefix MHD astrophysical code +// +// Source file src/tests/testKokkos.hpp +// +// Last modified : 10/2026 +// +// Copyright(C) by : +// - Sébastien Valat (IPAG/UGA/CNRS - 2026) +// and other code contributors +// +// Licensed under CeCILL 2.1 License, see COPYING for more information +// *********************************************************************************** + +// headers +#include +#include +#include +#include "gtest-idfx.hpp" + +TEST(Kokkos, assert_view_1d) { + //data array + Kokkos::View a("a", 10); + + //ref array + Kokkos::View b("a", 10); + + //make both 5 + Kokkos::parallel_for("Loop1", 10, KOKKOS_LAMBDA (const int i) { + a(i) = b(i) = 5; + }); + + //check all 5 static + ASSERT_THAT_1D_ARRAY(a, testing::Eq, 5); + + //compare both array + ASSERT_THAT_1D_ARRAYS(a, testing::Eq, b); + + //compare with near + ASSERT_THAT_1D_ARRAYS_ARGS(a, testing::DoubleNear, b, 0.001); +} + +TEST(Kokkos, assert_view_1d_is_nan) { + //data array + Kokkos::View a("a", 10); + + //ref array + Kokkos::View b("a", 10); + + //make both 5 + Kokkos::parallel_for("Loop1", 10, KOKKOS_LAMBDA (const int i) { + a(i) = b(i) = NAN; + }); + + //check all 5 static + ASSERT_THAT_1D_ARRAY(a, testing::IsNan); +} diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt index ef71c20c7..f5276557d 100644 --- a/src/utils/CMakeLists.txt +++ b/src/utils/CMakeLists.txt @@ -14,11 +14,17 @@ add_subdirectory(iterativesolver) -target_sources(idefix - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/bigEndian.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/dumpImage.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/dumpImage.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/lookupTable.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/column.cpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/column.hpp +target_sources(idefix-core + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/bigEndian.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/dumpImage.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/dumpImage.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/lookupTable.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/column.cpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/column.hpp ) + +if (Idefix_GTEST) + target_sources(unittests + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/tests/testLookupTable.cpp + ) +endif() diff --git a/src/utils/iterativesolver/CMakeLists.txt b/src/utils/iterativesolver/CMakeLists.txt index f9cd84c6e..fa745a069 100644 --- a/src/utils/iterativesolver/CMakeLists.txt +++ b/src/utils/iterativesolver/CMakeLists.txt @@ -12,9 +12,9 @@ # Licensed under CeCILL 2.1 License, see COPYING for more information ###################################################################################### -target_sources(idefix - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/cg.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/minres.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/bicgstab.hpp - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/jacobi.hpp +target_sources(idefix-core + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/cg.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/minres.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/bicgstab.hpp + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/jacobi.hpp ) diff --git a/src/utils/tests/data/data.npy b/src/utils/tests/data/data.npy new file mode 100644 index 000000000..8287fc9e3 Binary files /dev/null and b/src/utils/tests/data/data.npy differ diff --git a/src/utils/tests/data/gendata.py b/src/utils/tests/data/gendata.py new file mode 100644 index 000000000..52fb36434 --- /dev/null +++ b/src/utils/tests/data/gendata.py @@ -0,0 +1,42 @@ +###################################################################################### +# Idefix MHD astrophysical code +# +# Source file test/utils/lookupTable/testmelib.py +# +# Last modified : 08/2026 +# +# Copyright(C) by : +# - Sébastien Valat (IPAG/UGA/CNRS - 2026) +# - Alankar Dutta (MPA Garching - 2026) +# and other code contributors +# +# Licensed under CeCILL 2.1 License, see COPYING for more information +###################################################################################### + +import numpy as np +from scipy.interpolate import RegularGridInterpolator + + +def MakeNumpyFile(): + x = 2 ** np.arange(np.log2(1), np.log2(10), 0.2) + y = 2 ** np.arange(np.log2(5), np.log2(10), 0.2) + z = 2 ** np.arange(np.log2(2), np.log2(5), 0.2) + + print(x) + print(y) + print(z) + xp, yp, zp = np.meshgrid(x, y, z, indexing="ij") + + data = xp + 2 * yp - zp + + np.save("x.npy", x) + np.save("y.npy", y) + np.save("z.npy", z) + np.save("data.npy", data) + f = RegularGridInterpolator((x, y, z), data) + return f([2.7, 7.4, 3.9]) + + +if __name__ == "__main__": + f = MakeNumpyFile() + print("expected result:%f", f) diff --git a/src/utils/tests/data/toto.csv b/src/utils/tests/data/toto.csv new file mode 100644 index 000000000..07c9eb20c --- /dev/null +++ b/src/utils/tests/data/toto.csv @@ -0,0 +1,6 @@ +# test csv file + + 2.0, 3.0 +2.0, 4.0, 5.0 +3.0, 5.0, 6.0 +4.0, 6.0, 7.0 diff --git a/src/utils/tests/data/toto1D.csv b/src/utils/tests/data/toto1D.csv new file mode 100644 index 000000000..971a524f5 --- /dev/null +++ b/src/utils/tests/data/toto1D.csv @@ -0,0 +1,5 @@ +# test csv file + +1.0, 2.0, 3.0 +2.0, 4.0, 6.0 +mlskj diff --git a/src/utils/tests/data/toto1Dcolumn.csv b/src/utils/tests/data/toto1Dcolumn.csv new file mode 100644 index 000000000..291aa31cb --- /dev/null +++ b/src/utils/tests/data/toto1Dcolumn.csv @@ -0,0 +1,5 @@ +# test csv file, same 1D table as toto1D.csv, but stored as columns + +1.0, 2.0 +2.0, 4.0 +3.0, 6.0 diff --git a/src/utils/tests/data/toto1Dlog.csv b/src/utils/tests/data/toto1Dlog.csv new file mode 100644 index 000000000..2cb5d5d51 --- /dev/null +++ b/src/utils/tests/data/toto1Dlog.csv @@ -0,0 +1,6 @@ +# test csv file, power law data = x^2 stored as columns +# used to test the interpolation in function space + +1.0, 1.0 +2.0, 4.0 +4.0, 16.0 diff --git a/src/utils/tests/data/x.npy b/src/utils/tests/data/x.npy new file mode 100644 index 000000000..cb32b9911 Binary files /dev/null and b/src/utils/tests/data/x.npy differ diff --git a/src/utils/tests/data/y.npy b/src/utils/tests/data/y.npy new file mode 100644 index 000000000..fd9f75643 Binary files /dev/null and b/src/utils/tests/data/y.npy differ diff --git a/src/utils/tests/data/z.npy b/src/utils/tests/data/z.npy new file mode 100644 index 000000000..297b01a6f Binary files /dev/null and b/src/utils/tests/data/z.npy differ diff --git a/src/utils/tests/testLookupTable.cpp b/src/utils/tests/testLookupTable.cpp new file mode 100644 index 000000000..aa1be55b4 --- /dev/null +++ b/src/utils/tests/testLookupTable.cpp @@ -0,0 +1,571 @@ +// *********************************************************************************** +// Idefix MHD astrophysical code +// +// Source file src/utils/testLookupTable.hpp +// +// Last modified : 10/2026 +// +// Copyright(C) by : +// - Sébastien Valat (IPAG/UGA/CNRS - 2026) +// and other code contributors +// +// Licensed under CeCILL 2.1 License, see COPYING for more information +// *********************************************************************************** + +// headers +#include +#include +#include "gtest-idfx.hpp" +#include "lookupTable.hpp" + +// Custom transformation (and its inverse) to check that the functions used for the interpolation +// in function space can be changed when the lookup table is created +struct MyLog10 { + KOKKOS_INLINE_FUNCTION real operator() (const real x) const { + return(log10(x)); + } +}; + +struct MyPow10 { + KOKKOS_INLINE_FUNCTION real operator() (const real x) const { + return(pow(10.0,x)); + } +}; + +// Expected bracketing neighbours of the 2D CSV table (toto.csv) around (x=2.1, y=3.5): x brackets +// [2,3], y brackets [3,4], and the data on the four surrounding vertices is 5,6,6,7. This is +// checked from several angles across the file below (Get vs GetNeighbours vs GetNeighboursIndx, +// host vs device, cached vs uncached search), so it is defined once here instead of being +// retyped as magic numbers in every block. +const real kXN2D[4] = {2.0, 3.0, 3.0, 4.0}; +const real kDataN2D[4] = {5.0, 6.0, 6.0, 7.0}; +const int kIdx2D[2] = {0, 1}; +const int kDataIdx2D[4] = {1, 4, 2, 5}; +const real kValue2D = 5.6; // csv.Get({2.1, 3.5}) + +//Testing 2D CSV file on device +TEST(lookupTable, 2d_csv_file_on_device) { + IdefixArray1D arr = IdefixArray1D("Test",1); + IdefixArray1D::host_mirror_type arrHost = Kokkos::create_mirror_view(arr); + + LookupTable<2> csv(DATA_FILE("toto.csv"),','); + + idefix_for("loop",0, 1, KOKKOS_LAMBDA (int i) { + real x[2]; + x[0] = 2.1; + x[1] = 3.5; + arr(i) = csv.Get(x); + }); + + Kokkos::deep_copy(arrHost , arr); + + ASSERT_NEAR(arrHost(0), kValue2D, 1e-13) << "2D CSV, device"; +} + +//Testing 2D CSV file on Host +TEST(lookupTable, 2d_csv_file_on_host) { + LookupTable<2> csv(DATA_FILE("toto.csv"),','); + real x[2]; + x[0] = 2.1; + x[1] = 3.5; + real result = csv.GetHost(x); + ASSERT_NEAR(result, kValue2D, 1e-13) << "2D CSV, host"; +} + +//Testing 1D CSV file on device. +TEST(lookupTable, 1d_csv_file_on_device) { + IdefixArray1D arr = IdefixArray1D("Test",1); + IdefixArray1D::host_mirror_type arrHost = Kokkos::create_mirror_view(arr); + + // Read 1D CSV File + LookupTable<1> csv1D(DATA_FILE("toto1D.csv"),','); + + idefix_for("loop",0, 1, KOKKOS_LAMBDA (int i) { + real x[2]; + x[0] = 2.1; + x[1] = 3.5; + arr(i) = csv1D.Get(x); + }); + + Kokkos::deep_copy(arrHost , arr); + ASSERT_EQ(arrHost(0), real(4.2)) << "1D CSV, device"; +} + +//Testing 1D CSV file on host. +TEST(lookupTable, 1d_csv_file_on_host) { + LookupTable<1> csv1D(DATA_FILE("toto1D.csv"),','); + real x[2]; + x[0] = 2.1; + x[1] = 3.5; + real result = csv1D.GetHost(x); + ASSERT_EQ(result, real(4.2)) << "1D CSV, host"; +} + +//Testing 1D CSV file read as columns on device. +TEST(LookupTable, 1d_csv_read_as_col_on_device) { + // Read the same 1D table, but stored as columns of the CSV file + LookupTable<1> csv1Dcolumn(DATA_FILE("toto1Dcolumn.csv"),',', true, true); + IdefixArray1D arr = IdefixArray1D("Test",1); + IdefixArray1D::host_mirror_type arrHost = Kokkos::create_mirror_view(arr); + + idefix_for("loop",0, 1, KOKKOS_LAMBDA (int i) { + real x[1]; + x[0] = 2.1; + arr(i) = csv1Dcolumn.Get(x); + }); + + Kokkos::deep_copy(arrHost , arr); + ASSERT_EQ(arrHost(0), real(4.2)) << "1D CSV read as columns, device"; +} + +//Testing 1D CSV file read as columns on Host. +TEST(LookupTable, 1d_csv_read_as_col_on_host) { + // Read the same 1D table, but stored as columns of the CSV file + LookupTable<1> csv1Dcolumn(DATA_FILE("toto1Dcolumn.csv"),',', true, true); + + real x[1]; + x[0] = 2.1; + real result = csv1Dcolumn.GetHost(x); + ASSERT_EQ(result, real(4.2)) << "1D CSV read as columns, host"; +} + +//Testing interpolation in function space on device. +TEST(LookupTable, interpol_func_space_on_device) { + IdefixArray1D arr = IdefixArray1D("Test",1); + IdefixArray1D::host_mirror_type arrHost = Kokkos::create_mirror_view(arr); + + // data = x^2 sampled in x=1,2,4. A linear interpolation in x=3 gives 10, while an + // interpolation performed on log(x) and log(data) gives the exact result 9 + LookupTable<1> csv1Dlin(DATA_FILE("toto1Dlog.csv"),',', true, true); + LookupTable<1> csv1Dlog(DATA_FILE("toto1Dlog.csv"),',', true, true, true); + // the transformation and its inverse can also be chosen when the table is created + LookupTable<1, MyLog10, MyPow10> csv1Dlog10(DATA_FILE("toto1Dlog.csv"),',', true, true, true); + + idefix_for("loop",0, 1, KOKKOS_LAMBDA (int i) { + real x[1]; + x[0] = 3.0; + arr(i) = csv1Dlog.Get(x); + }); + Kokkos::deep_copy(arrHost , arr); + ASSERT_NEAR(arrHost(0), 9.0, 1e-13) << "function-space interpolation (default log), device"; + + idefix_for("loop",0, 1, KOKKOS_LAMBDA (int i) { + real x[1]; + x[0] = 3.0; + arr(i) = csv1Dlog10.Get(x); + }); + Kokkos::deep_copy(arrHost , arr); + ASSERT_NEAR(arrHost(0), 9.0, 1e-13) << "function-space interpolation (custom log10), device"; +} + +//Testing interpolation in function space on Host. +TEST(LookupTable, interpol_func_space_on_host) { + // data = x^2 sampled in x=1,2,4. A linear interpolation in x=3 gives 10, while an + // interpolation performed on log(x) and log(data) gives the exact result 9 + LookupTable<1> csv1Dlin(DATA_FILE("toto1Dlog.csv"),',', true, true); + LookupTable<1> csv1Dlog(DATA_FILE("toto1Dlog.csv"),',', true, true, true); + // the transformation and its inverse can also be chosen when the table is created + LookupTable<1, MyLog10, MyPow10> csv1Dlog10(DATA_FILE("toto1Dlog.csv"),',', true, true, true); + + real x[1]; + x[0] = 3.0; + real result = csv1Dlog.GetHost(x); + ASSERT_NEAR(result, 9.0, 1e-13) << "function-space interpolation (default log), host"; + + result = csv1Dlog10.GetHost(x); + ASSERT_NEAR(result, 9.0, 1e-13) << "function-space interpolation (custom log10), host"; + + // the same table, interpolated linearly (default behaviour) + result = csv1Dlin.GetHost(x); + ASSERT_NEAR(result, 10.0, 1e-13) << "plain linear interpolation, host"; +} + +//Testing the neighbours used for the interpolation on Host. +TEST(LookupTable, neighbours_used_for_interpol_on_host) { + // data = x^2 sampled in x=1,2,4. A linear interpolation in x=3 gives 10, while an + // interpolation performed on log(x) and log(data) gives the exact result 9 + LookupTable<1> csv1Dlog(DATA_FILE("toto1Dlog.csv"),',', true, true, true); + LookupTable<1> csv1D(DATA_FILE("toto1D.csv"),','); + LookupTable<2> csv(DATA_FILE("toto.csv"),','); + + // 1D table (x=1,2,3 and data=2,4,6) interpolated in x=2.1 + real xq[1]; + xq[0] = 2.1; + real xN[2]; + real dataN[2]; + int idx[1]; + int dataIdx[2]; + csv1D.GetNeighboursHost(xq, xN, dataN); + csv1D.GetNeighboursIndxHost(xq, idx, dataIdx); + //idfx::cout << "1D: x=" << xN[0] << "," << xN[1] << " data=" << dataN[0] << "," << dataN[1] + // << " idx=" << idx[0] << " dataIdx=" << dataIdx[0] << "," << dataIdx[1] + // << std::endl; + const real expectedXN[2] = {2.0, 3.0}; + const real expectedDataN[2] = {4.0, 6.0}; + const int expectedDataIdx[2] = {1, 2}; + ASSERT_C_ARRAY_EQ(xN, expectedXN, 2, "1D neighbours, coordinates"); + ASSERT_C_ARRAY_EQ(dataN, expectedDataN, 2, "1D neighbours, data"); + ASSERT_EQ(idx[0], 1) << "1D neighbours, idx"; + ASSERT_C_ARRAY_EQ(dataIdx, expectedDataIdx, 2, "1D neighbours, dataIdx"); + + // 2D table interpolated in (2.1,3.5). The vertices are ordered so that the bit n of the + // vertex index tells whether we are on the right (1) or on the left (0) of the dimension n + real xq2[2]; + xq2[0] = 2.1; + xq2[1] = 3.5; + real xN2[4]; + real dataN2[4]; + int idx2[2]; + int dataIdx2[4]; + csv.GetNeighboursHost(xq2, xN2, dataN2); + csv.GetNeighboursIndxHost(xq2, idx2, dataIdx2); + //idfx::cout << "2D: x=" << xN2[0] << "," << xN2[1] << " y=" << xN2[2] << "," << xN2[3] + // << " data=" << dataN2[0] << "," << dataN2[1] << "," << dataN2[2] << "," + // << dataN2[3] << " idx=" << idx2[0] << "," << idx2[1] << std::endl; + ASSERT_C_ARRAY_EQ(xN2, kXN2D, 4, "2D neighbours, coordinates"); + ASSERT_C_ARRAY_EQ(dataN2, kDataN2D, 4, "2D neighbours, data"); + ASSERT_C_ARRAY_EQ(idx2, kIdx2D, 2, "2D neighbours, idx"); + ASSERT_C_ARRAY_EQ(dataIdx2, kDataIdx2D, 4, "2D neighbours, dataIdx"); + + // The neighbours should reproduce the value returned by GetHost + real dx = (xq2[0]-xN2[0])/(xN2[1]-xN2[0]); + real dy = (xq2[1]-xN2[2])/(xN2[3]-xN2[2]); + real interpolated = (1-dx)*(1-dy)*dataN2[0] + dx*(1-dy)*dataN2[1] + + (1-dx)*dy*dataN2[2] + dx*dy*dataN2[3]; + //idfx::cout << "2D: interpolation from the neighbours=" << interpolated << std::endl; + ASSERT_NEAR(interpolated, csv.GetHost(xq2), 1e-13) + << "2D neighbours, manual interpolation vs GetHost"; + + // When the table is interpolated in function space, the neighbours are returned in the + // original space of the table (x=1,2,4 and data=1,4,16 here) + real xq3[1]; + xq3[0] = 3.0; + real xN3[2]; + real dataN3[2]; + int idx3[1]; + int dataIdx3[2]; + csv1Dlog.GetNeighboursHost(xq3, xN3, dataN3); + csv1Dlog.GetNeighboursIndxHost(xq3, idx3, dataIdx3); + //idfx::cout << "1D (function space): x=" << xN3[0] << "," << xN3[1] + // << " data=" << dataN3[0] << "," << dataN3[1] << " idx=" << idx3[0] << std::endl; + ASSERT_NEAR(xN3[0], 2.0, 1e-13) << "1D function-space neighbours, x lower bound"; + ASSERT_NEAR(xN3[1], 4.0, 1e-13) << "1D function-space neighbours, x upper bound"; + ASSERT_NEAR(dataN3[0], 4.0, 1e-13) << "1D function-space neighbours, data lower bound"; + ASSERT_NEAR(dataN3[1], 16.0, 1e-13) << "1D function-space neighbours, data upper bound"; + ASSERT_EQ(idx3[0], 1) << "1D function-space neighbours, idx"; + const int expectedDataIdx3[2] = {1, 2}; + ASSERT_C_ARRAY_EQ(dataIdx3, expectedDataIdx3, 2, "1D function-space neighbours, dataIdx"); +} + +//Testing the neighbours used for the interpolation on device."); +TEST(LookupTable, neighbours_used_for_interpol_on_device) { + LookupTable<2> csv(DATA_FILE("toto.csv"),','); + + // data = x^2 sampled in x=1,2,4. A linear interpolation in x=3 gives 10, while an + // interpolation performed on log(x) and log(data) gives the exact result 9 + LookupTable<1> csv1Dlin(DATA_FILE("toto1Dlog.csv"),',', true, true); + LookupTable<1> csv1Dlog(DATA_FILE("toto1Dlog.csv"),',', true, true, true); + + IdefixArray1D xNdev = IdefixArray1D("xN",4); + IdefixArray1D dataNdev = IdefixArray1D("dataN",4); + IdefixArray1D idxDev = IdefixArray1D("idx",2); + IdefixArray1D dataIdxDev = IdefixArray1D("dataIdx",4); + + idefix_for("neighbours",0, 1, KOKKOS_LAMBDA (int i) { + real xq[2]; + xq[0] = 2.1; + xq[1] = 3.5; + real xN[4]; + real dataN[4]; + int idx[2]; + int dataIdx[4]; + csv.GetNeighbours(xq, xN, dataN); + csv.GetNeighboursIndx(xq, idx, dataIdx); + for(int n = 0 ; n < 4 ; n++) { + xNdev(n) = xN[n]; + dataNdev(n) = dataN[n]; + dataIdxDev(n) = dataIdx[n]; + } + idxDev(0) = idx[0]; + idxDev(1) = idx[1]; + }); + + auto xNHost = Kokkos::create_mirror_view(xNdev); + auto dataNHost = Kokkos::create_mirror_view(dataNdev); + auto idxHost = Kokkos::create_mirror_view(idxDev); + auto dataIdxHost = Kokkos::create_mirror_view(dataIdxDev); + Kokkos::deep_copy(xNHost, xNdev); + Kokkos::deep_copy(dataNHost, dataNdev); + Kokkos::deep_copy(idxHost, idxDev); + Kokkos::deep_copy(dataIdxHost, dataIdxDev); + + //idfx::cout << "2D: x=" << xNHost(0) << "," << xNHost(1) << " y=" << xNHost(2) << "," + // << xNHost(3) << " data=" << dataNHost(0) << "," << dataNHost(1) << "," + // << dataNHost(2) << "," << dataNHost(3) << " idx=" << idxHost(0) << "," + // << idxHost(1) << std::endl; + real xN[4], dataN[4]; + int idx[2], dataIdx[4]; + for(int n = 0 ; n < 4 ; n++) { xN[n] = xNHost(n); dataN[n] = dataNHost(n); } + idx[0] = idxHost(0); idx[1] = idxHost(1); + for(int n = 0 ; n < 4 ; n++) dataIdx[n] = dataIdxHost(n); + ASSERT_C_ARRAY_EQ(xN, kXN2D, 4, "2D neighbours (device), coordinates"); + ASSERT_C_ARRAY_EQ(dataN, kDataN2D, 4, "2D neighbours (device), data"); + ASSERT_C_ARRAY_EQ(idx, kIdx2D, 2, "2D neighbours (device), idx"); + ASSERT_C_ARRAY_EQ(dataIdx, kDataIdx2D, 4, "2D neighbours (device), dataIdx"); +} + +//Testing the 1D neighbours on the edges of the table. +TEST(LookupTable, neighbours_on_edges_1d) { + // Read 1D CSV File + LookupTable<1> csv1D(DATA_FILE("toto1D.csv"),','); + + // toto1D.csv holds x=1,2,3 and data=2,4,6. Whatever the requested value, we expect the two + // neighbours bracketing it, i.e. the last two nodes when we sit on the upper edge + real xq[1]; + real xN[2]; + real dataN[2]; + real expected[3][2] = {{1.0,2.0}, {2.0,3.0}, {2.0,3.0}}; + real xRequest[3] = {1.0, 2.0, 3.0}; + for(int n = 0 ; n < 3 ; n++) { + xq[0] = xRequest[n]; + csv1D.GetNeighboursHost(xq, xN, dataN); + //idfx::cout << "x=" << xq[0] << " -> neighbours " << xN[0] << "," << xN[1] + // << " (data " << dataN[0] << "," << dataN[1] << ")" << std::endl; + ASSERT_C_ARRAY_EQ(xN, expected[n], 2, "1D edge neighbours, coordinates"); + EXPECT_LE(xN[0], xq[0]) << "1D edge neighbours do not bracket the requested value"; + EXPECT_GE(xN[1], xq[0]) << "1D edge neighbours do not bracket the requested value"; + } +} + +//Testing the reuse of the search between Get and GetNeighbours on Host. +TEST(LookupTable, reuse_search_between_get_and_get_neighbours_on_host) { + LookupTable<2> csv(DATA_FILE("toto.csv"),','); + + real xq[2]; + xq[0] = 2.1; + xq[1] = 3.5; + // The neighbours found by Get are stored in nb... + LookupTableSearchCache<2> nb; + real result = csv.GetHost(xq, nb); + ASSERT_NEAR(result, kValue2D, 1e-13) << "Get-then-GetNeighbours (host), value"; + ASSERT_TRUE(nb.valid) << "Get-then-GetNeighbours (host), search validity"; + ASSERT_C_ARRAY_EQ(nb.idx, kIdx2D, 2, "Get-then-GetNeighbours (host), cached idx"); + + // ... and are reused (not computed again) by the getters below + real xN[4]; + real dataN[4]; + int idx[2]; + int dataIdx[4]; + csv.GetNeighboursHost(xq, nb, xN, dataN); + csv.GetNeighboursIndxHost(xq, nb, idx, dataIdx); + ASSERT_C_ARRAY_EQ(xN, kXN2D, 4, "Get-then-GetNeighbours (host), coordinates"); + ASSERT_C_ARRAY_EQ(dataN, kDataN2D, 4, "Get-then-GetNeighbours (host), data"); + ASSERT_C_ARRAY_EQ(idx, kIdx2D, 2, "Get-then-GetNeighbours (host), idx"); + ASSERT_C_ARRAY_EQ(dataIdx, kDataIdx2D, 4, "Get-then-GetNeighbours (host), dataIdx"); + + // Check that the table is really not searched again for the same coordinates: we corrupt + // the stored search, and check that the corrupted result is the one which is used + nb.idx[0] = 1; + csv.GetNeighboursIndxHost(xq, nb, idx, dataIdx); + ASSERT_EQ(idx[0], 1) << "Get-then-GetNeighbours (host): the stored search was not reused"; + + // ... while different coordinates trigger a new search, as usual + real xq2[2]; + xq2[0] = 2.9; + xq2[1] = 2.5; + csv.GetNeighboursIndxHost(xq2, nb, idx, dataIdx); + ASSERT_EQ(idx[0], 0) + << "Get-then-GetNeighbours (host): different coordinates did not trigger a new search"; + ASSERT_EQ(idx[1], 0) + << "Get-then-GetNeighbours (host): different coordinates did not trigger a new search"; +} + +//Testing the search performed by GetNeighbours first, and reused by Get, on Host. +TEST(LookupTable, get_and_reuse_on_host) { + LookupTable<2> csv(DATA_FILE("toto.csv"),','); + LookupTable<1> csv1D(DATA_FILE("toto1D.csv"),','); + real xq[2]; + xq[0] = 2.1; + xq[1] = 3.5; + + // No call to Get yet: GetNeighbours searches the table as usual, and stores the result + LookupTableSearchCache<2> nb; + real xN[4]; + real dataN[4]; + csv.GetNeighboursHost(xq, nb, xN, dataN); + ASSERT_TRUE(nb.valid) << "GetNeighbours-then-Get (host), search validity"; + ASSERT_C_ARRAY_EQ(nb.idx, kIdx2D, 2, "GetNeighbours-then-Get (host), cached idx"); + ASSERT_C_ARRAY_EQ(xN, kXN2D, 4, "GetNeighbours-then-Get (host), coordinates"); + ASSERT_C_ARRAY_EQ(dataN, kDataN2D, 4, "GetNeighbours-then-Get (host), data"); + + // Get now reuses that search for the same coordinates + real result = csv.GetHost(xq, nb); + ASSERT_NEAR(result, kValue2D, 1e-13) << "GetNeighbours-then-Get (host), value"; + + // Same check as before, the other way around: we corrupt the ratio stored by + // GetNeighbours, and check that Get uses it instead of computing it again. + // With delta[0]=0.5 instead of 0.1, the interpolation gives (5+6+6+7)/4=6 + nb.delta[0] = 0.5; + result = csv.GetHost(xq, nb); + ASSERT_NEAR(result, 6.0, 1e-13) + << "GetNeighbours-then-Get (host): the stored search was not reused by Get"; + + // ... while different coordinates make Get search the table again + real xq2[2]; + xq2[0] = 2.9; + xq2[1] = 2.5; + result = csv.GetHost(xq2, nb); + ASSERT_NEAR(result, csv.GetHost(xq2), 1e-13) + << "GetNeighbours-then-Get (host): different coordinates did not trigger a new search"; + ASSERT_EQ(nb.idx[1], 0) + << "GetNeighbours-then-Get (host): different coordinates did not trigger a new search"; + + // The same holds when the search comes from GetNeighboursIndx, here on the 1D table + LookupTableSearchCache<1> nb1D; + real xq1[1]; + xq1[0] = 2.1; + int idx1[1]; + int dataIdx1[2]; + csv1D.GetNeighboursIndxHost(xq1, nb1D, idx1, dataIdx1); + ASSERT_TRUE(nb1D.valid) << "GetNeighboursIndx-then-Get (host, 1D), search validity"; + ASSERT_EQ(idx1[0], 1) << "GetNeighboursIndx-then-Get (host, 1D), idx"; + nb1D.delta[0] = 0.0; // x is now on the left neighbour, so we expect its data + result = csv1D.GetHost(xq1, nb1D); + ASSERT_NEAR(result, 4.0, 1e-13) + << "GetNeighboursIndx-then-Get (host, 1D): the stored search was not reused by Get"; +} + +//Testing the reuse of the search between Get and GetNeighbours on device. +TEST(LookupTable, get_and_reuse_on_device) { + IdefixArray1D arr = IdefixArray1D("Test",1); + IdefixArray1D::host_mirror_type arrHost = Kokkos::create_mirror_view(arr); + + LookupTable<2> csv(DATA_FILE("toto.csv"),','); + LookupTable<1> csv1D(DATA_FILE("toto1D.csv"),','); + + IdefixArray1D xNdev = IdefixArray1D("xN",4); + IdefixArray1D dataNdev = IdefixArray1D("dataN",4); + IdefixArray1D idxDev = IdefixArray1D("idx",2); + IdefixArray1D dataIdxDev = IdefixArray1D("dataIdx",4); + + idefix_for("neighbours",0, 1, KOKKOS_LAMBDA (int i) { + real xq[2]; + xq[0] = 2.1; + xq[1] = 3.5; + // the structure is local to the loop, and is therefore private to each thread + LookupTableSearchCache<2> nb; + real xN[4]; + real dataN[4]; + int idx[2]; + int dataIdx[4]; + arr(i) = csv.Get(xq, nb); + csv.GetNeighbours(xq, nb, xN, dataN); + csv.GetNeighboursIndx(xq, nb, idx, dataIdx); + for(int n = 0 ; n < 4 ; n++) { + xNdev(n) = xN[n]; + dataNdev(n) = dataN[n]; + dataIdxDev(n) = dataIdx[n]; + } + idxDev(0) = idx[0]; + idxDev(1) = idx[1]; + }); + + Kokkos::deep_copy(arrHost , arr); + auto xNHost = Kokkos::create_mirror_view(xNdev); + auto dataNHost = Kokkos::create_mirror_view(dataNdev); + auto idxHost = Kokkos::create_mirror_view(idxDev); + auto dataIdxHost = Kokkos::create_mirror_view(dataIdxDev); + Kokkos::deep_copy(xNHost, xNdev); + Kokkos::deep_copy(dataNHost, dataNdev); + Kokkos::deep_copy(idxHost, idxDev); + Kokkos::deep_copy(dataIdxHost, dataIdxDev); + + // Same test on device, but with GetNeighbours called first and Get reusing its search. + // The ratio stored by GetNeighbours is corrupted in between, so that a value of 6 (instead + // of 5.6) proves that Get did not search the table again + IdefixArray1D valuesDev = IdefixArray1D("values",2); + idefix_for("neighboursFirst",0, 1, KOKKOS_LAMBDA (int i) { + real xq[2]; + xq[0] = 2.1; + xq[1] = 3.5; + LookupTableSearchCache<2> nb; + real xN[4]; + real dataN[4]; + // no call to Get yet: the search is performed here for the first time + csv.GetNeighbours(xq, nb, xN, dataN); + valuesDev(0) = csv.Get(xq, nb); + nb.delta[0] = 0.5; + valuesDev(1) = csv.Get(xq, nb); + }); + auto valuesHost = Kokkos::create_mirror_view(valuesDev); + Kokkos::deep_copy(valuesHost, valuesDev); + //idfx::cout << "neighbours first: value=" << valuesHost(0) + // << " (with a corrupted stored search)=" << valuesHost(1) << std::endl; + ASSERT_NEAR(valuesHost(0), kValue2D, 1e-13) + << "GetNeighbours-then-Get (device): the stored search was not reused by Get"; + ASSERT_NEAR(valuesHost(1), 6.0, 1e-13) + << "GetNeighbours-then-Get (device): the stored search was not reused by Get"; + + //idfx::cout << "value=" << arrHost(0) << " x=" << xNHost(0) << "," << xNHost(1) + // << " y=" << xNHost(2) << "," << xNHost(3) + // << " data=" << dataNHost(0) << "," << dataNHost(1) << "," << dataNHost(2) + // << "," << dataNHost(3) << " idx=" << idxHost(0) << "," << idxHost(1) << std::endl; + ASSERT_NEAR(arrHost(0), kValue2D, 1e-13) << "Get-then-GetNeighbours (device), value"; + real xN[4], dataN[4]; + int idx[2], dataIdx[4]; + for(int n = 0 ; n < 4 ; n++) { xN[n] = xNHost(n); dataN[n] = dataNHost(n); } + idx[0] = idxHost(0); idx[1] = idxHost(1); + for(int n = 0 ; n < 4 ; n++) dataIdx[n] = dataIdxHost(n); + ASSERT_C_ARRAY_EQ(xN, kXN2D, 4, "Get-then-GetNeighbours (device), coordinates"); + ASSERT_C_ARRAY_EQ(dataN, kDataN2D, 4, "Get-then-GetNeighbours (device), data"); + ASSERT_C_ARRAY_EQ(idx, kIdx2D, 2, "Get-then-GetNeighbours (device), idx"); + ASSERT_C_ARRAY_EQ(dataIdx, kDataIdx2D, 4, "Get-then-GetNeighbours (device), dataIdx"); +} + +//Testing 3D npy file on device. +TEST(LookupTable, numpy_file_3d_on_device) { + IdefixArray1D arr = IdefixArray1D("Test",1); + IdefixArray1D::host_mirror_type arrHost = Kokkos::create_mirror_view(arr); + + // Read npy File + std::vector coords({ + DATA_FILE("x.npy"), + DATA_FILE("y.npy"), + DATA_FILE("z.npy") + }); + LookupTable<3> csvnpy(coords, std::string(DATA_FILE("data.npy"))); + + idefix_for("loop",0, 1, KOKKOS_LAMBDA (int i) { + real x[3]; + x[0] = 2.7; + x[1] = 7.4; + x[2] = 3.9; + arr(i) = csvnpy.Get(x); + }); + + Kokkos::deep_copy(arrHost , arr); + //idfx::cout << "result="< coords({ + DATA_FILE("x.npy"), + DATA_FILE("y.npy"), + DATA_FILE("z.npy") + }); + LookupTable<3> csvnpy(coords, + std::string(DATA_FILE("data.npy")) + ); + + real y[3]; + y[0] = 2.7; + y[1] = 7.4; + y[2] = 3.9; + real result = csvnpy.GetHost(y); + + //idfx::cout << "result="<< result << std::endl; + ASSERT_NEAR(result, 13.6, 1e-13) << "3D npy, host"; +}