diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6ecbeb2adc28e64b6b14dde7ff4ae6010c86ee4e..c267b3fcc3df8b83b85d45c9fbd11b82185b1b32 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -118,11 +118,12 @@ full-dune-latest-release-clang: full-dune-master-gcc-cpp-20: extends: .base-trigger variables: - IMAGE: $IMAGE_REGISTRY_URL/full:dune-master-gcc-ubuntu-22.04 + IMAGE: $IMAGE_REGISTRY_URL/full:dune-master-gcc-12-ubuntu-22.04 CXX_MAX_STANDARD: "20" DUMUX_ENABLE_CPPCHECK: "true" DUMUX_ENABLE_DOXYGEN_BUILD: "true" DUMUX_CHECK_EXAMPLE_DOCS: "true" + GIT_SUBMODULE_STRATEGY: recursive ################################## # additional scheduled pipelines # diff --git a/.gitlab-ci/default.yml b/.gitlab-ci/default.yml index ac0380e44765e578fd03f4f3d92798f1bd177c46..c09c78c31b12f8b456bc9138a457209052bb6a5a 100644 --- a/.gitlab-ci/default.yml +++ b/.gitlab-ci/default.yml @@ -33,6 +33,11 @@ configure: stage: configure script: - | + # remove submodules (may be present due to caching done by gitlab, although this pipeline may not support the submodule) + rm -rf deps/* + if [ "${GIT_SUBMODULE_STRATEGY}" == "recursive" ]; then + git submodule update --init --recursive + fi echo "source ${DUNE_OPTS_FILE}" > opts_file.opts echo "CMAKE_FLAGS=\"\${CMAKE_FLAGS} -DCXX_MAX_STANDARD=${CXX_MAX_STANDARD} -DCMAKE_EXPORT_COMPILE_COMMANDS=ON\"" >> opts_file.opts - dunecontrol --opts=opts_file.opts --current configure @@ -63,7 +68,7 @@ linters: # codespell - echo "Running codespell" - codespell --version - - codespell --skip="*format/fmt/*,*io/expression/*,*build-cmake*,*.png,*.svg,*.eps,*.bib,*.tex,patches,.git,*staggered_grid.pdf,*dumux/common/dumuxmessage.hh" --ignore-words-list="ges" + - codespell --skip="./deps/**/*,*format/fmt/*,*io/expression/*,*build-cmake*,*.png,*.svg,*.eps,*.bib,*.tex,patches,.git,*staggered_grid.pdf,*dumux/common/dumuxmessage.hh" --ignore-words-list="ges" # check examples - | if [ "${DUMUX_CHECK_EXAMPLE_DOCS}" == "true" ]; then diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000000000000000000000000000000..176b9f14a17bd5fac5e51b984d3e5d6d98eaf407 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightInfo: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +# SPDX-License-Identifier: CC0-1.0 + +[submodule "deps/gridformat"] + path = deps/gridformat + url = https://github.com/dglaeser/gridformat.git diff --git a/CMakeLists.txt b/CMakeLists.txt index ad1c345552201bf93c2672c70a3fde7fd3bf9d14..f1475080ee2d65314f0bd8d62fdca2db0f374e79 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,10 @@ endif() # enforce C++-17 target_compile_features(dumux PUBLIC cxx_std_17) +if (DUMUX_HAVE_GRIDFORMAT) + dune_register_package_flags(LIBRARIES gridformat::gridformat) +endif() + add_subdirectory(cmake/modules) add_subdirectory(doc) add_subdirectory(dumux) diff --git a/cmake/modules/DumuxMacros.cmake b/cmake/modules/DumuxMacros.cmake index 01f384076c4666af68929bb4531beebbc9baef62..d1994ed8c1b8f362863e25a0b179dab77e0f9368 100644 --- a/cmake/modules/DumuxMacros.cmake +++ b/cmake/modules/DumuxMacros.cmake @@ -95,3 +95,15 @@ else() endif() message(STATUS "Dumux multithreading backend: ${DUMUX_MULTITHREADING_BACKEND}") + +# check for optional 3rd-party libraries as submodules +set(DUMUX_HAVE_GRIDFORMAT false) +set(DUMUX_HAVE_VTK_HDF false) +if (EXISTS ${CMAKE_SOURCE_DIR}/deps/gridformat/CMakeLists.txt) + message(STATUS "Including gridformat in the source tree") + add_subdirectory(${CMAKE_SOURCE_DIR}/deps/gridformat) + + include(${CMAKE_SOURCE_DIR}/deps/gridformat/cmake/modules/GridFormatHaveFeature.cmake) + set(DUMUX_HAVE_GRIDFORMAT true) + gridformat_have_feature(VTK_HDF DUMUX_HAVE_VTK_HDF) +endif () diff --git a/cmake/modules/DumuxTestMacros.cmake b/cmake/modules/DumuxTestMacros.cmake index e62bb416fc3725edc5f4530ab80489ca0438c8de..e37bd35b09f6b9548c0d747e730d084ce9934af5 100644 --- a/cmake/modules/DumuxTestMacros.cmake +++ b/cmake/modules/DumuxTestMacros.cmake @@ -260,9 +260,23 @@ function(dumux_add_test) set(ADDTEST_TARGET ${ADDTEST_NAME}) endif() - file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/TestMetaData") - file(WRITE "${CMAKE_BINARY_DIR}/TestMetaData/${ADDTEST_NAME}.json" - "{\n \"name\": \"${ADDTEST_NAME}\",\n \"target\": \"${ADDTEST_TARGET}\",\n \"source_dir\": \"${CMAKE_CURRENT_SOURCE_DIR}\"\n}\n") + if(NOT ADDTEST_MPI_RANKS) + set(ADDTEST_MPI_RANKS 1) + endif() + + foreach(procnum ${ADDTEST_MPI_RANKS}) + if((NOT "${procnum}" GREATER "${DUNE_MAX_TEST_CORES}") AND (NOT ADDTEST_COMPILE_ONLY)) + set(ACTUAL_NAME ${ADDTEST_NAME}) + # add suffix + if(NOT ${procnum} STREQUAL "1") + set(ACTUAL_NAME "${ACTUAL_NAME}-mpi-${procnum}") + endif() + + file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/TestMetaData") + file(WRITE "${CMAKE_BINARY_DIR}/TestMetaData/${ACTUAL_NAME}.json" + "{\n \"name\": \"${ACTUAL_NAME}\",\n \"target\": \"${ADDTEST_TARGET}\",\n \"source_dir\": \"${CMAKE_CURRENT_SOURCE_DIR}\"\n}\n") + endif() + endforeach() endfunction() # Evaluate test guards like dune_add_test internally does diff --git a/config.h.cmake b/config.h.cmake index 71bfe1eea6e5a287e9be0dbb32a017810625c417..6345d845efba35b602c0557de8808e3f7e1e0405 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -92,6 +92,9 @@ /* Set DUMUX_HAVE_CPP_PARALLEL_ALGORITHMS if available */ #cmakedefine DUMUX_HAVE_CPP_PARALLEL_ALGORITHMS 1 +/* Set DUMUX_HAVE_GRIDFORMAT if available */ +#cmakedefine DUMUX_HAVE_GRIDFORMAT 1 + /* end dumux Everything below here will be overwritten */ diff --git a/deps/gridformat b/deps/gridformat new file mode 160000 index 0000000000000000000000000000000000000000..fa73387ccbd562034c99fdcf3238ba6a4195d9cf --- /dev/null +++ b/deps/gridformat @@ -0,0 +1 @@ +Subproject commit fa73387ccbd562034c99fdcf3238ba6a4195d9cf diff --git a/dumux/io/gridwriter.hh b/dumux/io/gridwriter.hh new file mode 100644 index 0000000000000000000000000000000000000000..13f479183879e6e0561b8c3613aa7edabd779740 --- /dev/null +++ b/dumux/io/gridwriter.hh @@ -0,0 +1,235 @@ +// -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- +// vi: set et ts=4 sw=2 sts=2: +// +// SPDX-FileCopyrightInfo: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup InputOutput + * \brief Generic writer for a variety of grid file formats. + */ +#ifndef DUMUX_IO_GRID_WRITER_HH +#define DUMUX_IO_GRID_WRITER_HH + +#include <config.h> + +#if DUMUX_HAVE_GRIDFORMAT + +#include <type_traits> + +#include <gridformat/gridformat.hpp> +#include <gridformat/traits/dune.hpp> + +namespace Dumux::IO { + + +#ifndef DOXYGEN +namespace Detail { + +template<typename Grid, typename Format, typename Comm, typename... Args> +auto makeParallelWriter(const Grid& grid, const Format& fmt, const Dune::Communication<Comm>& comm, Args&&... args) +{ + return comm.size() > 1 + ? GridFormat::Writer<Grid>{fmt, grid, static_cast<Comm>(comm), std::forward<Args>(args)...} + : GridFormat::Writer<Grid>{fmt, grid, std::forward<Args>(args)...}; +} + +template<typename Grid, typename Format, typename... Args> +auto makeParallelWriter(const Grid& grid, const Format& fmt, const Dune::Communication<Dune::No_Comm>&, Args&&... args) +{ return GridFormat::Writer<Grid>{fmt, grid, std::forward<Args>(args)...}; } + +template<typename Grid, typename Format, typename... Args> +auto makeWriter(const Grid& grid, const Format& fmt, Args&&... args) +{ + const auto& comm = GridFormat::Dune::Traits::GridView<Grid>::get(grid).comm(); + return makeParallelWriter(grid, fmt, comm, std::forward<Args>(args)...); +} + +} // namespace Detail +#endif // DOXYGEN + + +namespace VTK { using namespace GridFormat::VTK; } +namespace Format { using namespace GridFormat::Formats; } +namespace Encoding { using namespace GridFormat::Encoding; } +namespace Compression { using namespace GridFormat::Compression; using GridFormat::none; } +namespace Precision { + using GridFormat::float32; + using GridFormat::float64; + + using GridFormat::uint64; + using GridFormat::uint32; + using GridFormat::uint16; + using GridFormat::uint8; + + using GridFormat::int64; + using GridFormat::int32; + using GridFormat::int16; + using GridFormat::int8; +} // namespace Precision + + +/*! + * \ingroup InputOutput + * \brief Represents the interpolation order with which fields are written out. + */ +template<int order> +struct Order { static_assert(order > 0, "order must be > 0"); }; + +template<int o> +inline constexpr auto order = Order<o>{}; + +/*! + * \ingroup InputOutput + * \brief Generic writer for a variety of grid file formats. + * Supports higher-order output of fields provided as Dune::Function objects. + * To create a writer for higher-order output, you may write + * \code + * GridWriter writer{gridView, order<2>}; + * \endcode + */ +template<GridFormat::Concepts::Grid GridView, int order = 1> +class GridWriter +{ + using Grid = std::conditional_t< + (order > 1), + GridFormat::Dune::LagrangePolynomialGrid<GridView>, + GridView + >; + using Cell = GridFormat::Cell<Grid>; + using Vertex = typename GridView::template Codim<GridView::dimension>::Entity; + using Element = typename GridView::template Codim<0>::Entity; + using Coordinate = typename Element::Geometry::GlobalCoordinate; + using Writer = GridFormat::Writer<Grid>; + + public: + /*! + * \brief Constructor for non-transient file formats. + * \note This does not compile if the chosen format is a transient file format. + */ + template<typename Format> + explicit GridWriter(const Format& fmt, + const GridView& gridView, + const Order<order>& = {}) + : gridView_{gridView} + , grid_{makeGrid_(gridView)} + , writer_{Detail::makeWriter(grid_, fmt)} + { writer_.set_meta_data("rank", gridView_.comm().rank()); } + + /*! + * \brief Constructor for transient file formats, i.e. time series. + * \note This does not compile if the chosen format is not a transient file format. + */ + template<typename Format> + explicit GridWriter(const Format& fmt, + const GridView& gridView, + const std::string& filename, + const Order<order>& = {}) + : gridView_{gridView} + , grid_{makeGrid_(gridView)} + , writer_{Detail::makeWriter(grid_, fmt, filename)} + { writer_.set_meta_data("rank", gridView_.comm().rank()); } + + /*! + * \ingroup InputOutput + * \brief Write the registered fields into the file with the given name. + * \note This function throws if the writer was constructed for time series output. + */ + std::string write(const std::string& name) const + { return writer_.write(name); } + + /*! + * \ingroup InputOutput + * \brief Write a step in a time series. + * \note This function throws if the writer was not constructed for time series output. + */ + template<std::floating_point T> + std::string write(T time) const + { return writer_.write(time); } + + //! Set a cell field via a lambda invoked with grid elements + template<GridFormat::Concepts::CellFunction<GridView> F, + GridFormat::Concepts::Scalar T = GridFormat::FieldScalar<std::invoke_result_t<F, Element>>> + void setCellField(const std::string& name, F&& f, const GridFormat::Precision<T>& prec = {}) + { writer_.set_cell_field(name, std::move(f), prec); } + + //! Set a Dune::Function as cell field + template<GridFormat::Dune::Concepts::Function<GridView> F> + void setCellField(const std::string& name, F&& f) + { GridFormat::Dune::set_cell_function(std::forward<F>(f), writer_, name); } + + //! Set a Dune::Function as cell field with custom precision + template<typename F, GridFormat::Concepts::Scalar T> + void setCellField(const std::string& name, F&& f, const GridFormat::Precision<T>& prec) + { GridFormat::Dune::set_cell_function(std::forward<F>(f), writer_, name, prec); } + + //! Set a point field via a lambda invoked with grid vertices + template<GridFormat::Concepts::PointFunction<GridView> F, + GridFormat::Concepts::Scalar T = GridFormat::FieldScalar<std::invoke_result_t<F, Vertex>>> + void setPointField(const std::string& name, F&& f, const GridFormat::Precision<T>& prec = {}) + { + static_assert(order == 1, "Point lambdas can only be used for order == 1. Use Dune::Functions instead."); + writer_.set_point_field(name, std::move(f), prec); + } + + //! Set a Dune::Function as point field + template<GridFormat::Dune::Concepts::Function<GridView> F> + void setPointField(const std::string& name, F&& f) + { GridFormat::Dune::set_point_function(std::forward<F>(f), writer_, name); } + + //! Set a Dune::Function as point data with custom precision + template<GridFormat::Dune::Concepts::Function<GridView> F, GridFormat::Concepts::Scalar T> + void setPointField(const std::string& name, F&& f, const GridFormat::Precision<T>& prec) + { GridFormat::Dune::set_point_function(std::forward<F>(f), writer_, name, prec); } + + //! Clear all data + void clear() + { writer_.clear(); } + + //! Update (must be called when the grid has changed) + void update() + { + if constexpr (order > 1) + grid_.update(gridView_); + } + + private: + Grid makeGrid_(const GridView& gv) const + { + if constexpr (order > 1) + return Grid{gv, order}; + else + return gv; + } + + GridView gridView_; + Grid grid_; + Writer writer_; +}; + +} // namespace Dumux::IO + +#else // DUMUX_HAVE_GRIDFORMAT + +namespace Dumux::IO { + +template<class... Args> +class GridWriter +{ +public: + template<class... _Args> + GridWriter(_Args&&...) + { + static_assert( + false, + "GridWriter only available when the GridFormat library is available. " + "Use `git submodule init && git submodule update` to pull it." + ); + } +}; + +} // namespace Dumux::IO + +#endif // DUMUX_HAVE_GRIDFORMAT +#endif // DUMUX_IO_GRID_HH diff --git a/test/io/CMakeLists.txt b/test/io/CMakeLists.txt index 394a45a794f68f1318aef2572e468e8ff5418d7b..4aa65aa7350f5d760ef2db954ac49cf62896191f 100644 --- a/test/io/CMakeLists.txt +++ b/test/io/CMakeLists.txt @@ -9,3 +9,17 @@ add_subdirectory(gridmanager) add_subdirectory(inputdata) add_subdirectory(rasterimagereader) add_subdirectory(vtk) + +dumux_add_test( + NAME test_grid_writer + SOURCES test_grid_writer.cc + CMAKE_GUARD DUMUX_HAVE_GRIDFORMAT +) + +dumux_add_test( + NAME test_grid_writer_parallel + SOURCES test_grid_writer_parallel.cc + CMAKE_GUARD DUMUX_HAVE_GRIDFORMAT + MPI_RANKS 1 2 + TIMEOUT 100 +) diff --git a/test/io/test_grid_writer.cc b/test/io/test_grid_writer.cc new file mode 100644 index 0000000000000000000000000000000000000000..ee746d015b83cd9f8934cd3e4e9f81c6b003fb04 --- /dev/null +++ b/test/io/test_grid_writer.cc @@ -0,0 +1,80 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightInfo: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \brief Test for reading/writing from/to different formats with the generic grid writers & readers. + */ +#include <config.h> + +#include <dune/grid/yaspgrid.hh> +#if HAVE_DUNE_FUNCTIONS +#include <dune/functions/gridfunctions/analyticgridviewfunction.hh> +#endif + +#include <dumux/common/initialize.hh> +#include <dumux/io/gridwriter.hh> + +using namespace Dumux::IO; + +template<class Writer> +void setFields(Writer& writer) +{ + writer.setPointField("pcoords", [] (const auto& p) { + return p.geometry().center(); + }); + writer.setCellField("ccoords", [] (const auto& e) { + return e.geometry().center(); + }); +} + +template<typename GridView> +auto writeFirstOrder(const GridView& gridView) +{ + GridWriter writer{Format::vtu, gridView, order<1>}; + setFields(writer); + std::cout << "Wrote " << writer.write("test_grid_writer_first_order") << std::endl; +} + +template<typename GridView> +auto writeSecondOrder(const GridView& gridView) +{ +#if HAVE_DUNE_FUNCTIONS + GridWriter writer{Format::vtu, gridView, order<2>}; + auto f = Dune::Functions::makeAnalyticGridViewFunction([&] (const auto& x) { + return x[0]*x[1]; + }, gridView); + writer.setPointField("point_function", f); + writer.setCellField("cell_function", f); + std::cout << "Wrote " << writer.write("test_grid_writer_second_order") << std::endl; +#endif +} + +template<typename GridView> +auto writeSpecifiedFormat(const GridView& gridView) +{ + GridWriter writer{Format::vtu.with({.encoder = Encoding::raw}), gridView}; + setFields(writer); + std::cout << "Wrote " << writer.write("test_grid_writer_format_selection") << std::endl;; +} + +int main(int argc, char** argv) +{ + Dumux::initialize(argc, argv); + + Dune::YaspGrid<2> grid{ + {1.0, 1.0}, + {20, 20}, + std::bitset<2>(), // periodicity + 0 // overlap + }; + + writeFirstOrder(grid.leafGridView()); + writeSecondOrder(grid.leafGridView()); + writeSpecifiedFormat(grid.leafGridView()); + + return 0; +} diff --git a/test/io/test_grid_writer_parallel.cc b/test/io/test_grid_writer_parallel.cc new file mode 100644 index 0000000000000000000000000000000000000000..fac3c2aa1c3ff99b12c62146e974d7585ee8ffb5 --- /dev/null +++ b/test/io/test_grid_writer_parallel.cc @@ -0,0 +1,91 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightInfo: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \brief Test for writing parallel files with the generic grid file format writer. + */ +#include <config.h> + +#include <dune/grid/yaspgrid.hh> +#if HAVE_DUNE_FUNCTIONS +#include <dune/functions/gridfunctions/analyticgridviewfunction.hh> +#endif + +#include <dumux/common/initialize.hh> +#include <dumux/io/gridwriter.hh> + +// The rank is added as cell field, although the writer +// writes it out as meta data per default (which is much +// more space-efficient). However, VTK seems to have issues +// displaying field data in parallel, and I could not find +// any helpful info in the code or documentation. + +using namespace Dumux::IO; + +template<class GlobalPosition> +double testFunction(const GlobalPosition& x) +{ return x[0]*x[1]; } + +template<class Writer> +void addFields(Writer& writer, int rank) +{ + writer.setPointField("pfield", [=] (const auto& p) { + return testFunction(p.geometry().center()); + }); + writer.setCellField("cfield", [=] (const auto& e) { + return testFunction(e.geometry().center()); + }); + writer.setCellField("rank", [=] (const auto& e) { + return rank; + }); +} + +template<class GridView> +void testFirstOrder(const GridView& gridView) +{ + GridWriter writer{Format::vti, gridView}; + addFields(writer, gridView.comm().rank()); + std::cout << "Wrote " << writer.write("test_grid_writer_parallel") << std::endl; +} + +template<class GridView> +void testSecondOrder(const GridView& gridView) +{ +#if HAVE_DUNE_FUNCTIONS + GridWriter writer{Format::vtu, gridView, order<2>}; + auto f = Dune::Functions::makeAnalyticGridViewFunction([] (const auto& x) { + return testFunction(x); + }, gridView); + auto rank = Dune::Functions::makeAnalyticGridViewFunction([rank=gridView.comm().rank()] (const auto& x) { + return rank; + }, gridView); + writer.setPointField("pfunc", f); + writer.setCellField("cfunc", f); + writer.setCellField("rank", rank); + std::cout << "Wrote " << writer.write("test_grid_writer_parallel_second_order") << std::endl; +#endif +} + +int main(int argc, char** argv) +{ + Dumux::initialize(argc, argv); + + // write a parallel vti file + Dune::YaspGrid<2> grid{ + {1.0, 1.0}, + {20, 20}, + std::bitset<2>(), // periodicity + 0 // overlap + }; + grid.loadBalance(); + const auto& gridView = grid.leafGridView(); + + testFirstOrder(gridView); + testSecondOrder(gridView); + + return 0; +}