diff --git a/cmake/CxxThirdParty.cmake b/cmake/CxxThirdParty.cmake index bbd70c8c4..50d339da0 100644 --- a/cmake/CxxThirdParty.cmake +++ b/cmake/CxxThirdParty.cmake @@ -109,28 +109,22 @@ cpmaddpackage( SYSTEM) if(${NEOFOAM_WITH_GINKGO}) - set(GINKGO_BUILD_TESTS - OFF - CACHE INTERNAL "") - set(GINKGO_BUILD_BENCHMARKS - OFF - CACHE INTERNAL "") - set(GINKGO_BUILD_EXAMPLES - OFF - CACHE INTERNAL "") - set(GINKGO_BUILD_MPI - OFF - CACHE INTERNAL "") - set(GINKGO_ENABLE_HALF - OFF - CACHE INTERNAL "") cpmaddpackage( NAME Ginkgo + VERSION + 1.10.0 GITHUB_REPOSITORY ginkgo-project/ginkgo GIT_TAG develop + OPTIONS + "GINKGO_BUILD_TESTS OFF" + "GINKGO_BUILD_BENCHMARKS OFF" + "GINKGO_BUILD_EXAMPLES OFF" + "GINKGO_BUILD_MPI ${NEOFOAM_ENABLE_MPI_SUPPORT}" + "GINKGO_BUILD_CUDA ${Kokkos_ENABLE_CUDA}" + "GINKGO_BUILD_HIP ${Kokkos_ENABLE_HIP}" SYSTEM) endif() diff --git a/include/NeoFOAM/core/dictionary.hpp b/include/NeoFOAM/core/dictionary.hpp index f7230ae73..05c48aa0b 100644 --- a/include/NeoFOAM/core/dictionary.hpp +++ b/include/NeoFOAM/core/dictionary.hpp @@ -95,7 +95,7 @@ class Dictionary catch (const std::bad_any_cast& e) { logBadAnyCast(e, key, data_); - throw e; + throw; } } @@ -117,7 +117,7 @@ class Dictionary catch (const std::bad_any_cast& e) { logBadAnyCast(e, key, data_); - throw e; + throw; } } diff --git a/include/NeoFOAM/dsl/solver.hpp b/include/NeoFOAM/dsl/solver.hpp index c2a392372..d6cd3b166 100644 --- a/include/NeoFOAM/dsl/solver.hpp +++ b/include/NeoFOAM/dsl/solver.hpp @@ -132,8 +132,7 @@ void solve( using ValueType = typename FieldType::ElementType; auto ls = ginkgoMatrix(exp, solution); - - NeoFOAM::la::ginkgo::BiCGStab solver(solution.exec(), fvSolution); + NeoFOAM::la::ginkgo::Solver solver(solution.exec(), fvSolution); solver.solve(ls, solution.internalField()); } } diff --git a/include/NeoFOAM/linearAlgebra/ginkgo.hpp b/include/NeoFOAM/linearAlgebra/ginkgo.hpp index 38bc710ae..8d554ed18 100644 --- a/include/NeoFOAM/linearAlgebra/ginkgo.hpp +++ b/include/NeoFOAM/linearAlgebra/ginkgo.hpp @@ -17,98 +17,42 @@ namespace NeoFOAM::la::ginkgo { +gko::config::pnode parse(const Dictionary& dict); + template -class GkoSolverBase +class Solver { -private: - - std::shared_ptr gkoExec_; - Dictionary solverDict_; - - virtual std::shared_ptr solverGen( - std::shared_ptr exec, - std::shared_ptr mtx, - size_t maxIter, - float relTol - ) = 0; - -protected: +public: - GkoSolverBase(Executor exec, Dictionary solverDict) - : gkoExec_(getGkoExecutor(exec)), solverDict_(solverDict) + Solver(Executor exec, Dictionary solverConfig) + : gkoExec_(getGkoExecutor(exec)), config_(parse(solverConfig)), + factory_( + gko::config::parse( + config_, gko::config::registry(), gko::config::make_type_descriptor() + ) + .on(gkoExec_) + ) {} -public: - - virtual void solve(LinearSystem& sys, Field& x) + void solve(LinearSystem& sys, Field& x) { size_t nrows = sys.rhs().size(); - auto solver = solverGen( - gkoExec_, - detail::createGkoMtx(gkoExec_, sys), - size_t(solverDict_.get("maxIters")), - float(solverDict_.get("relTol")) - ); + auto solver = factory_->generate(detail::createGkoMtx(gkoExec_, sys)); auto rhs = detail::createGkoDense(gkoExec_, sys.rhs().data(), nrows); auto gkoX = detail::createGkoDense(gkoExec_, x.data(), nrows); solver->apply(rhs, gkoX); } -}; - - -template -class CG : public GkoSolverBase -{ - - virtual std::shared_ptr solverGen( - std::shared_ptr exec, - std::shared_ptr mtx, - size_t maxIter, - float relTol - ) override - { - auto fact = - gko::solver::Bicgstab::build() - .with_criteria( - gko::stop::Iteration::build().with_max_iters(maxIter), - gko::stop::ResidualNorm::build().with_reduction_factor(relTol) - ) - .on(exec); - return fact->generate(mtx); - } -public: +private: - CG(Executor exec, Dictionary solverDict) : GkoSolverBase(exec, solverDict) {} + std::shared_ptr gkoExec_; + gko::config::pnode config_; + std::shared_ptr factory_; }; -template -class BiCGStab : public GkoSolverBase -{ - virtual std::shared_ptr solverGen( - std::shared_ptr exec, - std::shared_ptr mtx, - size_t maxIter, - float relTol - ) - { - auto fact = - gko::solver::Bicgstab::build() - .with_criteria( - gko::stop::Iteration::build().with_max_iters(maxIter), - gko::stop::ResidualNorm::build().with_reduction_factor(relTol) - ) - .on(exec); - return fact->generate(mtx); - } - -public: - - BiCGStab(Executor exec, Dictionary solverDict) : GkoSolverBase(exec, solverDict) {} -}; } diff --git a/include/NeoFOAM/linearAlgebra/utilities.hpp b/include/NeoFOAM/linearAlgebra/utilities.hpp index 8ce3ffed0..b72b03d88 100644 --- a/include/NeoFOAM/linearAlgebra/utilities.hpp +++ b/include/NeoFOAM/linearAlgebra/utilities.hpp @@ -18,6 +18,20 @@ std::shared_ptr getGkoExecutor(Executor exec); namespace detail { + +template +gko::array createGkoArray(std::shared_ptr exec, std::span values) +{ + return gko::make_array_view(exec, values.size(), values.data()); +} + +template +gko::detail::const_array_view +createGkoArray(std::shared_ptr exec, std::span values) +{ + return gko::make_const_array_view(exec, values.size(), values.data()); +} + template std::shared_ptr> createGkoMtx(std::shared_ptr exec, LinearSystem& sys) @@ -25,12 +39,12 @@ createGkoMtx(std::shared_ptr exec, LinearSystem::view(exec, mtx.values().size(), mtx.values().data()); - auto colIdxView = gko::array::view(exec, mtx.colIdxs().size(), mtx.colIdxs().data()); - auto rowPtrView = gko::array::view(exec, mtx.rowPtrs().size(), mtx.rowPtrs().data()); - - return gko::share(gko::matrix::Csr::create( - exec, gko::dim<2> {nrows, nrows}, valuesView, colIdxView, rowPtrView + return gko::share(gko::matrix::Csr::create( + exec, + gko::dim<2> {nrows, nrows}, + createGkoArray(exec, mtx.values()), + createGkoArray(exec, mtx.colIdxs()), + createGkoArray(exec, mtx.rowPtrs()) )); } @@ -39,7 +53,7 @@ std::shared_ptr> createGkoDense(std::shared_ptr exec, ValueType* ptr, size_t size) { return gko::share(gko::matrix::Dense::create( - exec, gko::dim<2> {size, 1}, gko::array::view(exec, size, ptr), 1 + exec, gko::dim<2> {size, 1}, createGkoArray(exec, std::span {ptr, size}), 1 )); } } diff --git a/include/NeoFOAM/timeIntegration/backwardEuler.hpp b/include/NeoFOAM/timeIntegration/backwardEuler.hpp index 2eae7604f..f0d0e6395 100644 --- a/include/NeoFOAM/timeIntegration/backwardEuler.hpp +++ b/include/NeoFOAM/timeIntegration/backwardEuler.hpp @@ -57,7 +57,7 @@ class BackwardEuler : auto ginkgoLs = NeoFOAM::dsl::ginkgoMatrix(ls, solutionField); - NeoFOAM::la::ginkgo::BiCGStab solver(solutionField.exec(), this->solutionDict_); + NeoFOAM::la::ginkgo::Solver solver(solutionField.exec(), this->solutionDict_); solver.solve(ginkgoLs, solutionField.internalField()); // check if executor is GPU diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 32930bc76..386f9707c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,8 +5,6 @@ add_library(NeoFOAM ${NeoFOAM_LIB_TYPE}) include(GNUInstallDirs) -target_sources(NeoFOAM PRIVATE ${NeoFOAM_SRCS}) - if(NEOFOAM_ENABLE_CUDA) set_source_files_properties(${NeoFOAM_SRCS} PROPERTIES LANGUAGE CUDA) endif() @@ -28,6 +26,7 @@ target_sources( "executor/CPUExecutor.cpp" "executor/GPUExecutor.cpp" "executor/serialExecutor.cpp" + "linearAlgebra/ginkgo.cpp" "linearAlgebra/utilities.cpp" "mesh/unstructured/boundaryMesh.cpp" "mesh/unstructured/unstructuredMesh.cpp" diff --git a/src/linearAlgebra/ginkgo.cpp b/src/linearAlgebra/ginkgo.cpp new file mode 100644 index 000000000..512b97dc3 --- /dev/null +++ b/src/linearAlgebra/ginkgo.cpp @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2025 NeoFOAM authors +// +// SPDX-License-Identifier: MIT + +#include "NeoFOAM/linearAlgebra/ginkgo.hpp" + +gko::config::pnode NeoFOAM::la::ginkgo::parse(const Dictionary& dict) +{ + auto parseData = [&](auto key) + { + auto parseAny = [&](auto blueprint) + { + using value_type = decltype(blueprint); + if (dict[key].type() == typeid(value_type)) + { + return gko::config::pnode(dict.get(key)); + } + else + { + return gko::config::pnode(); + } + }; + + if (auto node = parseAny(std::string())) + { + return node; + } + if (auto node = parseAny(static_cast(nullptr))) + { + return node; + } + if (auto node = parseAny(int {})) + { + return node; + } + if (auto node = parseAny(static_cast(0))) + { + return node; + } + if (auto node = parseAny(double {})) + { + return node; + } + if (auto node = parseAny(float {})) + { + return node; + } + + NF_THROW("Dictionary key " + key + " has unsupported type: " + dict[key].type().name()); + }; + gko::config::pnode::map_type result; + for (const auto& key : dict.keys()) + { + gko::config::pnode node; + if (dict.isDict(key)) + { + node = parse(dict.subDict(key)); + } + else + { + node = parseData(key); + } + result.emplace(key, node); + } + return gko::config::pnode {result}; +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index cee08ddf6..ffcd00fce 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -22,30 +22,32 @@ function(neofoam_unit_test TEST) set(neofoam_WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/tests) endif() if(NOT DEFINED "neofoam_COMMAND") - set(neofoam_COMMAND ${TEST}) + set(neofoam_COMMAND nf_${TEST}) endif() - add_executable(${TEST} "${TEST}.cpp") - target_link_libraries(${TEST} PRIVATE neofoam_catch_main neofoam_warnings neofoam_options - Kokkos::kokkos NeoFOAM cpptrace::cpptrace) + add_executable(${neofoam_COMMAND} "${TEST}.cpp") + set_target_properties(${neofoam_COMMAND} PROPERTIES OUTPUT_NAME ${TEST}) + target_link_libraries( + ${neofoam_COMMAND} PRIVATE neofoam_catch_main neofoam_warnings neofoam_options Kokkos::kokkos + NeoFOAM cpptrace::cpptrace) if(NEOFOAM_WITH_SUNDIALS) - target_link_libraries(${TEST} PRIVATE SUNDIALS::arkode) + target_link_libraries(${neofoam_COMMAND} PRIVATE SUNDIALS::arkode) endif() if(WIN32) set_target_properties( - ${TEST} + ${neofoam_COMMAND} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${neofoam_WORKING_DIRECTORY}/$<0:> LIBRARY_OUTPUT_DIRECTORY ${neofoam_WORKING_DIRECTORY}/$<0:> ARCHIVE_OUTPUT_DIRECTORY ${neofoam_WORKING_DIRECTORY}/$<0:>) else() - set_target_properties(${TEST} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${neofoam_WORKING_DIRECTORY}) + set_target_properties(${neofoam_COMMAND} PROPERTIES RUNTIME_OUTPUT_DIRECTORY + ${neofoam_WORKING_DIRECTORY}) endif() add_test( NAME ${TEST} COMMAND ${neofoam_COMMAND} WORKING_DIRECTORY ${neofoam_WORKING_DIRECTORY}) - endfunction() function(neofoam_unit_test_mpi TEST) diff --git a/test/linearAlgebra/CMakeLists.txt b/test/linearAlgebra/CMakeLists.txt index 2befdbf63..218c24af8 100644 --- a/test/linearAlgebra/CMakeLists.txt +++ b/test/linearAlgebra/CMakeLists.txt @@ -1,5 +1,6 @@ # SPDX-License-Identifier: Unlicense # SPDX-FileCopyrightText: 2024 NeoFOAM authors +neofoam_unit_test(ginkgo) neofoam_unit_test(linearSystem) neofoam_unit_test(linearAlgebra) diff --git a/test/linearAlgebra/ginkgo.cpp b/test/linearAlgebra/ginkgo.cpp new file mode 100644 index 000000000..5bbd5addc --- /dev/null +++ b/test/linearAlgebra/ginkgo.cpp @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2025 NeoFOAM authors + +#include + +#include "catch2/catch_session.hpp" +#include "catch2/catch_test_macros.hpp" +#include "catch2/generators/catch_generators_all.hpp" +#include + +#if NF_WITH_GINKGO + +bool operator==(const gko::config::pnode& a, const gko::config::pnode& b) +{ + using tag_t = gko::config::pnode::tag_t; + + if (a.get_tag() != b.get_tag()) + { + return false; + } + + if (a.get_tag() == tag_t::array) + { + const auto& aArr = a.get_array(); + const auto& bArr = b.get_array(); + if (aArr.size() != bArr.size()) + { + return false; + } + for (std::size_t i = 0; i < aArr.size(); ++i) + { + if (!(aArr[i] == bArr[i])) + { + return false; + } + } + } + if (a.get_tag() == tag_t::boolean) + { + return a.get_boolean() == b.get_boolean(); + } + if (a.get_tag() == tag_t::integer) + { + return a.get_integer() == b.get_integer(); + } + if (a.get_tag() == tag_t::map) + { + const auto& aMap = a.get_map(); + const auto& bMap = b.get_map(); + if (aMap.size() != bMap.size()) + { + return false; + } + for (const auto& [key, value] : aMap) + { + if (!bMap.contains(key)) + { + return false; + } + if (!(bMap.at(key) == value)) + { + return false; + } + } + } + if (a.get_tag() == tag_t::real) + { + return a.get_real() == b.get_real(); + } + if (a.get_tag() == tag_t::string) + { + return a.get_string() == b.get_string(); + } + + return true; +} + +struct EqualsPnodeMatcher : Catch::Matchers::MatcherGenericBase +{ + EqualsPnodeMatcher(const gko::config::pnode& node) : node_ {node} {} + + bool match(const gko::config::pnode& other) const { return node_ == other; } + + std::string describe() const override { return "Equals: to node"; } + +private: + + const gko::config::pnode& node_; +}; + +TEST_CASE("Dictionary Parsing - Ginkgo") +{ + SECTION("String") + { + NeoFOAM::Dictionary dict {{{"key", std::string("value")}}}; + + auto node = NeoFOAM::la::ginkgo::parse(dict); + + gko::config::pnode expected({{"key", gko::config::pnode {"value"}}}); + CHECK_THAT(node, EqualsPnodeMatcher(expected)); + } + SECTION("Const Char *") + { + NeoFOAM::Dictionary dict {{{"key", "value"}}}; + + auto node = NeoFOAM::la::ginkgo::parse(dict); + + gko::config::pnode expected({{"key", gko::config::pnode {"value"}}}); + CHECK_THAT(node, EqualsPnodeMatcher(expected)); + } + SECTION("Int") + { + NeoFOAM::Dictionary dict {{{"key", 10}}}; + + auto node = NeoFOAM::la::ginkgo::parse(dict); + + gko::config::pnode expected({{"key", gko::config::pnode {10}}}); + CHECK_THAT(node, EqualsPnodeMatcher(expected)); + } + SECTION("Double") + { + NeoFOAM::Dictionary dict {{{"key", 1.0}}}; + + auto node = NeoFOAM::la::ginkgo::parse(dict); + + gko::config::pnode expected({{"key", gko::config::pnode {1.0}}}); + CHECK_THAT(node, EqualsPnodeMatcher(expected)); + } + SECTION("Float") + { + NeoFOAM::Dictionary dict {{{"key", 1.0f}}}; + + auto node = NeoFOAM::la::ginkgo::parse(dict); + + gko::config::pnode expected({{"key", gko::config::pnode {1.0f}}}); + CHECK_THAT(node, EqualsPnodeMatcher(expected)); + } + SECTION("Dict") + { + NeoFOAM::Dictionary dict; + dict.insert("key", NeoFOAM::Dictionary {{"key", "value"}}); + + auto node = NeoFOAM::la::ginkgo::parse(dict); + + gko::config::pnode expected( + {{"key", gko::config::pnode({{"key", gko::config::pnode {"value"}}})}} + ); + CHECK_THAT(node, EqualsPnodeMatcher(expected)); + } + SECTION("Throws") + { + NeoFOAM::Dictionary dict({{"key", std::pair> {}}}); + + REQUIRE_THROWS_AS(NeoFOAM::la::ginkgo::parse(dict), NeoFOAM::NeoFOAMException); + } +} + +#endif diff --git a/test/linearAlgebra/linearAlgebra.cpp b/test/linearAlgebra/linearAlgebra.cpp index d3323c1bd..f0d704854 100644 --- a/test/linearAlgebra/linearAlgebra.cpp +++ b/test/linearAlgebra/linearAlgebra.cpp @@ -12,12 +12,14 @@ #if NF_WITH_GINKGO template -bool isNotKokkosThreads(ExecSpace ex) +bool isNotKokkosThreads([[maybe_unused]] ExecSpace ex) { +#ifdef KOKKOS_ENABLE_THREADS if constexpr (std::is_same_v) { return false; } +#endif return true; } @@ -56,12 +58,14 @@ TEST_CASE("MatrixAssembly - Ginkgo") NeoFOAM::la::LinearSystem linearSystem(csrMatrix, rhs, "custom"); NeoFOAM::Field x(exec, {0.0, 0.0, 0.0}); - NeoFOAM::Dictionary solverDict; - solverDict.insert("maxIters", 100); - solverDict.insert("relTol", float(1e-7)); + NeoFOAM::Dictionary solverDict { + {{"type", "solver::Cg"}, + {"criteria", + NeoFOAM::Dictionary {{{"iteration", 100}, {"relative_residual_norm", 1e-7}}}}} + }; // Create solver - auto solver = NeoFOAM::la::ginkgo::CG(exec, solverDict); + auto solver = NeoFOAM::la::ginkgo::Solver(exec, solverDict); // Solve system solver.solve(linearSystem, x);