diff --git a/CMakeLists.txt b/CMakeLists.txt index d70b0a602..9060c6ed4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -133,6 +133,7 @@ if(MPI_FOUND) logicalCartesianSize_and_refinement_test mapLevelIndicesToCartesianOutputOrder_test test_communication_utils + id_entity_entityrep_test ) if(Boost_VERSION_STRING VERSION_GREATER 1.53) diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index 17beb2325..aef0bfb89 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -92,6 +92,7 @@ list(APPEND TEST_SOURCE_FILES tests/cpgrid/lgr/distribute_level_zero_from_grid_with_lgrs_test.cpp tests/cpgrid/lgr/global_refine_test.cpp tests/cpgrid/lgr/grid_global_id_set_test.cpp + tests/cpgrid/lgr/id_entity_entityrep_test.cpp tests/cpgrid/lgr/level_and_grid_cartesianIndexMappers_test.cpp tests/cpgrid/lgr/lgr_cell_id_sync_test.cpp tests/cpgrid/lgr/lgrs_sharing_faces_test.cpp diff --git a/tests/cpgrid/lgr/id_entity_entityrep_test.cpp b/tests/cpgrid/lgr/id_entity_entityrep_test.cpp new file mode 100644 index 000000000..c10933238 --- /dev/null +++ b/tests/cpgrid/lgr/id_entity_entityrep_test.cpp @@ -0,0 +1,414 @@ +//=========================================================================== +/* + Copyright 2025 Equinor ASA. + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ +#include "config.h" + +#define BOOST_TEST_MODULE EntityAndEntityRepIdComparisonTests +#include + +#include + +#include + +// This file attempts to verify that the ids and indices returned by Entity and EntityRep are the same +// where they are supposed to be the same, and different where they are supposed to be different. +// This is a test to make sure that modofications to the overloads of id() and index() functions +// in CpGridData and IndexSet does not break the current behaviour. +// The main motivation is to ensure that Entity is not being treated as EntityRep somewhere in the +// codebase (e.g. due to an implicit conversion), which would lead to wrong ids being returned in certain cases. + +struct Fixture +{ + Fixture() + { + int m_argc = boost::unit_test::framework::master_test_suite().argc; + char** m_argv = boost::unit_test::framework::master_test_suite().argv; + Dune::MPIHelper::instance(m_argc, m_argv); + Opm::OpmLog::setupSimpleDefaultLogging(); + } +}; + +BOOST_GLOBAL_FIXTURE(Fixture); + +template +bool checkEntityIndex(const GridView& gridView, EntityToIndex&& entityToIndex, EntityRepToIndex&& entityRepToIndex, bool coincide) +{ + std::size_t comp_count = 0; + std::size_t ref_count = 0; + // Compile-time loop over codimensions + Dune::Hybrid::forEach(std::make_index_sequence{}, [&](auto codim){ + // Only check codimensions that exist in the grid, i.e, CPGrid only supports codim 0 and 3 at the moment. + if constexpr (Dune::Capabilities::hasEntity::v) + for (const Dune::cpgrid::Entity& entity : entities(gridView, Dune::Codim())) { + const auto& entityRep = static_cast&>(entity); + auto entityIndex = entityToIndex(entity); + auto entityRepIndex = entityRepToIndex(entityRep); + comp_count += (entityIndex == entityRepIndex); + ref_count++; + if (coincide) + BOOST_WARN_EQUAL(entityIndex, entityRepIndex); + else + BOOST_WARN_NE(entityIndex, entityRepIndex); + } + }); + BOOST_TEST_MESSAGE(" └─ [" << gridView.comm().rank() + << "]: Compared " + std::to_string(ref_count) + " entities, " + + (coincide ? std::to_string(comp_count) + " matched." + : std::to_string(ref_count - comp_count) + " differed.")); + comp_count = gridView.comm().sum(comp_count); + ref_count = gridView.comm().sum(ref_count); + bool succed = coincide ? (comp_count == ref_count) : (comp_count < ref_count); + + return succed; +} + +std::string coincideString(bool coincide) +{ + return coincide ? "coincide (all ids/indices are equal)" : "differ (at least one id/index is different)"; +} + +template +auto idRep(const IdSet& idSet, const EntityRep& entityRep) +{ + return idSet.id(entityRep); +} + +void entityRepIndexAndEntityIndexCoincide(const Dune::CpGrid& grid) +{ + if (grid.comm().rank() == 0) + BOOST_TEST_MESSAGE(" └─ Comparing Entity vs EntityRep indices - lvl leaf | Expected to " + coincideString(true)); + const auto& leafIndexSet = grid.leafIndexSet(); + const auto leafView = grid.leafGridView(); + BOOST_CHECK( + checkEntityIndex(leafView, + [&](const auto& e){ return leafIndexSet.index(e); }, + [&](const auto& er){ return leafIndexSet.index(er); }, + true /* mustCoincide */ + ) || grid.comm().rank() != 0 // only rank 0 reports test failures + ); + + for (int level = 0; level <= grid.maxLevel(); ++level) { + if (grid.comm().rank() == 0) + BOOST_TEST_MESSAGE(" └─ Comparing Entity vs EntityRep indices - lvl " + std::to_string(level) + " | Expected to " + coincideString(true)); + const auto& levelIndexSet = grid.levelIndexSet(level); + const auto levelView = grid.levelGridView(level); + BOOST_CHECK( + checkEntityIndex(levelView, + [&](const auto& e){ return levelIndexSet.index(e); }, + [&](const auto& er){ return levelIndexSet.index(er); }, + true /* mustCoincide */ + ) || grid.comm().rank() != 0 // only rank 0 reports test failures + ); + } +} + +void globalIdsEntityRepAndEntityInLevelZeroGrid(const Dune::CpGrid& grid, bool coincide) +{ + if (grid.comm().rank() == 0) + BOOST_TEST_MESSAGE(" └─ Comparing Entity vs EntityRep global ids - lvl 0 | Expected to " + coincideString(coincide)); + const auto levelZeroView = grid.levelGridView(0); + const auto& levelZeroGlobalIdSet = grid.currentData()[0]->globalIdSet(); + // TODO(SoilRos): + // At the moment, grid.globalIdSet() returns an id set that does map entites on all levels as the interface requires. + // Thus, we use the level specific global id set here for now. + // const auto& globalIdSet = grid.globalIdSet(); + const auto& globalIdSet = levelZeroGlobalIdSet; + BOOST_CHECK( + checkEntityIndex(levelZeroView, + [&](const auto& e){ return globalIdSet.template id::codimension>(e); }, + [&](const auto& er){ return idRep(levelZeroGlobalIdSet, er); }, + coincide /* mustCoincide */ + ) || grid.comm().rank() != 0 // only rank 0 reports test failures + ); +} + +void localIdsEntityRepAndEntityInLevelZeroGrid(const Dune::CpGrid& grid, bool coincide) +{ + if (grid.comm().rank() == 0) + BOOST_TEST_MESSAGE(" └─ Comparing Entity vs EntityRep local ids - lvl 0 | Expected to " + coincideString(coincide)); + const auto levelZeroView = grid.levelGridView(0); + const auto& levelZeroLocalIdSet = grid.currentData()[0]->localIdSet(); + // TODO(SoilRos): + // At the moment, grid.localIdSet() returns an id set that does map entites on all levels as the interface requires. + // Thus, we use the level specific global id set here for now. + // const auto& localIdSet = grid.localIdSet(); + const auto& localIdSet = levelZeroLocalIdSet; + BOOST_CHECK( + checkEntityIndex(levelZeroView, + [&](const auto& e){ return localIdSet.id(e); }, + [&](const auto& er){ return idRep(levelZeroLocalIdSet, er); }, + coincide /* mustCoincide */ + ) || grid.comm().rank() != 0 // only rank 0 reports test failures + ); +} + +void localIdsEntityRepAndEntityInRefinedLevelGrids(const Dune::CpGrid& grid, bool coincide) +{ + for (int level = 1; level <= grid.maxLevel(); ++level) { + if (grid.comm().rank() == 0) + BOOST_TEST_MESSAGE(" └─ Comparing Entity vs EntityRep local ids - lvl " + std::to_string(level) + " | Expected to " + coincideString(coincide)); + const auto levelView = grid.levelGridView(level); + const auto& levelLocalIdSet = grid.currentData()[level]->localIdSet(); + // TODO(SoilRos): + // At the moment, grid.localIdSet() returns an id set that does map entites on all levels as the interface requires. + // Thus, we use the level specific global id set here for now. + // const auto& localIdSet = grid.localIdSet(); + const auto& localIdSet = levelLocalIdSet; + BOOST_CHECK( + checkEntityIndex(levelView, + [&](const auto& e){ return localIdSet.id(e); }, + [&](const auto& er){ return idRep(levelLocalIdSet, er); }, + coincide /* mustCoincide */ + ) || grid.comm().rank() != 0 // only rank 0 reports test failures + ); + } +} + +void localIdsEntityRepAndEntityInLeafGrid(const Dune::CpGrid& grid, bool coincide) +{ + if (grid.comm().rank() == 0) + BOOST_TEST_MESSAGE(" └─ Comparing Entity vs EntityRep local ids - lvl leaf | Expected to " + coincideString(coincide)); + const auto leafView = grid.leafGridView(); + const auto& leafLocalIdSet = grid.currentData().back()->localIdSet(); + // TODO(SoilRos): + // At the moment, grid.localIdSet() returns an id set that does map entites on all levels as the interface requires. + // Thus, we use the level specific global id set here for now. + // const auto& localIdSet = grid.localIdSet(); + const auto& localIdSet = leafLocalIdSet; + BOOST_CHECK( + checkEntityIndex(leafView, + [&](const auto& e){ return localIdSet.id(e); }, + [&](const auto& er){ return idRep(leafLocalIdSet, er); }, + coincide /* mustCoincide */ + ) || grid.comm().rank() != 0 // only rank 0 reports test failures + ); +} + +void globalIdsEntityRepAndEntityInRefinedLevelGrids(const Dune::CpGrid& grid, bool coincide) +{ + for (int level = 1; level <= grid.maxLevel(); ++level) { + if (grid.comm().rank() == 0) + BOOST_TEST_MESSAGE(" └─ Comparing Entity vs EntityRep global ids - lvl " + std::to_string(level) + " | Expected to " + coincideString(coincide)); + const auto levelView = grid.levelGridView(level); + const auto& levelGlobalIdSet = grid.currentData()[level]->globalIdSet(); + // TODO(SoilRos): + // At the moment, grid.globalIdSet() returns an id set that does map entites on all levels as the interface requires. + // Thus, we use the level specific global id set here for now. + // const auto& globalIdSet = grid.globalIdSet(); + const auto& globalIdSet = grid.currentData()[level]->globalIdSet(); + BOOST_CHECK( + checkEntityIndex(levelView, + [&](const auto& e){ return globalIdSet.id(e); }, + [&](const auto& er){ return idRep(levelGlobalIdSet, er); }, + coincide /* mustCoincide */ + ) || grid.comm().rank() != 0 // only rank 0 reports test failures + ); + } +} + +void globalIdsEntityRepAndEntityInLeafGrid(const Dune::CpGrid& grid, bool coincide) +{ + if (grid.comm().rank() == 0) + BOOST_TEST_MESSAGE(" └─ Comparing Entity vs EntityRep global ids - lvl leaf | Expected to " + coincideString(coincide)); + const auto leafView = grid.leafGridView(); + const auto& leafGlobalIdSet = grid.currentData().back()->globalIdSet(); + // TODO(SoilRos): + // At the moment, grid.globalIdSet() returns an id set that does map entites on all levels as the interface requires. + // Thus, we use the level specific global id set here for now. + // const auto& globalIdSet = grid.globalIdSet(); + const auto& globalIdSet = leafGlobalIdSet; + BOOST_CHECK( + checkEntityIndex(leafView, + [&](const auto& e){ return globalIdSet.id(e); }, + [&](const auto& er){ return idRep(leafGlobalIdSet, er); }, + coincide /* mustCoincide */ + ) || grid.comm().rank() != 0 // only rank 0 reports test failures + ); +} + +void checkEntityIds(const Dune::CpGrid& grid) +{ + bool grid_is_distributed = grid.size(0) != grid.comm().sum(grid.size(0)); + bool grid_is_refined = grid.maxLevel() > 0; + std::string grid_type = grid_is_distributed ? "distributed" : "not distributed"; + grid_type += grid_is_refined ? " refined" : " unrefined"; + if (grid.comm().rank() == 0) + BOOST_TEST_MESSAGE(" └─ Checking ids of Entity and EntityRep in " + grid_type + " grid"); + + // Indices ALWAYS coincide + entityRepIndexAndEntityIndexCoincide(grid); + + // All test below compare ids between Entity and EntityRep. + // EntityRep ids represent the level zero grid ids + + // local ids on level zero grid view ALWAYS coincide + localIdsEntityRepAndEntityInLevelZeroGrid(grid, true); + + // local ids on refined grid view NEVER coincide + localIdsEntityRepAndEntityInRefinedLevelGrids(grid, false); + + // local ids on leaf grid view coincide ONLY if the grid is NOT refined + localIdsEntityRepAndEntityInLeafGrid(grid, !grid_is_refined); + + // global ids on level zero grid view ALWAYS coincide + globalIdsEntityRepAndEntityInLevelZeroGrid(grid, true); + + // global ids on refined level grids view NEVER coincide + if (grid.comm().size() == 0) // TODO(SoilRos): this test currently fails in parallel + globalIdsEntityRepAndEntityInRefinedLevelGrids(grid, false); + + // global ids on leaf grid view coincide ONLY if the grid is NOT distributed and NOT refined + if (grid.comm().size() == 0) // TODO(SoilRos): this test currently fails in parallel + globalIdsEntityRepAndEntityInLeafGrid(grid, !grid_is_distributed and !grid_is_refined); +} + +BOOST_AUTO_TEST_CASE(idChecks_viaAddLgrs) +{ + Dune::CpGrid grid; + grid.createCartesian(/* grid_dim = */ {4,3,3}, /* cell_sizes = */ {1.0, 1.0, 1.0}); + + if (grid.comm().rank() == 0) + BOOST_TEST_MESSAGE("└─ Checking grid refinement via addLgrsUpdateLeafView"); + + checkEntityIds(grid); + + if (grid.comm().size()>1) { + grid.loadBalance(); + checkEntityIds(grid); + } + + grid.addLgrsUpdateLeafView( /* cells_per_dim = */ {{2,2,2}}, + /* startIJK = */ {{1,1,1}}, + /* endIJK = */ {{3,2,2}}, // block cell indices = {17, 18} + /* lgr_name = */ {"LGR1"}); + + if (grid.comm().size() == 1) { // Serial + checkEntityIds(grid); + } + else { // Parallel + // BEFORE applying the same refinement to the global view. + checkEntityIds(grid); + + grid.switchToGlobalView(); + // Synchronizing cell ids requires that both the global + // and distributed views undergo the same refinement process. + grid.addLgrsUpdateLeafView( /* cells_per_dim = */ {{2,2,2}}, + /* startIJK = */ {{1,1,1}}, + /* endIJK = */ {{3,2,2}}, // block cell global ids = {17, 18} + /* lgr_name = */ {"LGR1"}); + grid.switchToDistributedView(); + + grid.syncDistributedGlobalCellIds(); + + // AFTER synchronizing cell ids + checkEntityIds(grid); + } +} + +BOOST_AUTO_TEST_CASE(idChecks_viaAdapt) +{ + Dune::CpGrid grid; + grid.createCartesian(/* grid_dim = */ {4,3,3}, /* cell_sizes = */ {1.0, 1.0, 1.0}); + + if (grid.comm().rank() == 0) + BOOST_TEST_MESSAGE("└─ Checking grid refinement via adapt"); + + checkEntityIds(grid); + + if (grid.comm().size()>1) { + grid.loadBalance(); + checkEntityIds(grid); + } + + std::unordered_set markedCells = {17,18}; // parent cell global ids + // Mark selected elements for refinement. In this moment, level zero and leaf grids coincide. + for (const auto& element : elements(grid.leafGridView())) { + const auto& id = grid.globalIdSet().id(element); + if (markedCells.count(id) > 0) { + grid.mark(1, element); + } + } + grid.preAdapt(); + grid.adapt(); // Default subdivisions per cell 2x2x2 in x-,y-, and z-direction. + grid.postAdapt(); + + if (grid.comm().size() == 1) { // Serial + checkEntityIds(grid); + } + else { // Parallel + // BEFORE applying the same refinement to the global view + checkEntityIds(grid); + + grid.switchToGlobalView(); + // Synchronizing cell ids requires that both the global + // and distributed views undergo the same refinement process. + for (const auto& element : elements(grid.levelGridView(0))) { + const auto& id = grid.globalIdSet().id(element); + if (markedCells.count(id) > 0) { + grid.mark(1, element); + } + } + grid.preAdapt(); + grid.adapt(); // Default subdivisions per cell 2x2x2 in x-,y-, and z-direction. + grid.postAdapt(); + grid.switchToDistributedView(); + + grid.syncDistributedGlobalCellIds(); + + // AFTER synchronozing cell ids + checkEntityIds(grid); + } +} + +BOOST_AUTO_TEST_CASE(idChecks_viaGlobalRefine) +{ + Dune::CpGrid grid; + grid.createCartesian(/* grid_dim = */ {4,3,3}, /* cell_sizes = */ {1.0, 1.0, 1.0}); + + if (grid.comm().rank() == 0) + BOOST_TEST_MESSAGE("└─ Checking grid refinement via globalRefine"); + + checkEntityIds(grid); + + if (grid.comm().size()>1) { + grid.loadBalance(); + checkEntityIds(grid); + } + + grid.globalRefine(1); + + if (grid.comm().size() == 1) { // Serial + checkEntityIds(grid); + } + else { // Parallel + // BEFORE applying the same refinement to the global view. + checkEntityIds(grid); + + grid.switchToGlobalView(); + // Synchronizing cell ids requires that both the global + // and distributed views undergo the same refinement process. + grid.globalRefine(1); + grid.switchToDistributedView(); + + grid.syncDistributedGlobalCellIds(); + + // AFTER synchronizing cell ids + checkEntityIds(grid); + } +}