From a826fee16b0746dac024ef42e08bbcf4a68bfbff Mon Sep 17 00:00:00 2001 From: Tor Harald Sandve Date: Wed, 8 Apr 2026 12:50:15 +0200 Subject: [PATCH 1/4] Add support for loading unstructured grids from external files Implement optional external unstructured grid loading via UnstructuredGridFileName parameter. When set, the grid is loaded from the specified file instead of being constructed from the Eclipse deck. - Add UnstructuredGridFileName parameter with validation - Update grid vanguard to load grid from file when parameter is set - Skip grid recreation in output writer when using external file - Add parallel MPI support for unstructured grid loading - Include cell count validation between external file and Eclipse grid - Add test coverage for new functionality Default behavior remains unchanged when parameter is empty. --- CMakeLists.txt | 1 + CMakeLists_files.cmake | 1 + opm/simulators/flow/CpGridVanguard.hpp | 23 +- opm/simulators/flow/EclGenericWriter_impl.hpp | 23 +- opm/simulators/flow/FlowGenericVanguard.cpp | 3 +- opm/simulators/flow/FlowGenericVanguard.hpp | 2 +- opm/simulators/flow/GenericCpGridVanguard.cpp | 127 +++++-- .../flow/PolyhedralGridVanguard.hpp | 63 +++- opm/simulators/flow/Transmissibility_impl.hpp | 7 +- tests/SimulatorFixture.hpp | 24 +- tests/test_grid_from_file.cpp | 342 ++++++++++++++++++ 11 files changed, 558 insertions(+), 58 deletions(-) create mode 100644 tests/test_grid_from_file.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index cfca587b79c..5943f42df05 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -685,6 +685,7 @@ macro(opm-simulators_targets_hook) target_sources(test_outputdir PRIVATE $) target_sources(test_equil PRIVATE $) + target_sources(test_grid_from_file PRIVATE $) target_sources(test_group_higher_constraints PRIVATE $) target_sources(test_injection_topup_phase_validation PRIVATE $) target_sources(test_RestartSerialization PRIVATE $) diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index 8ff3122806d..b160f88a97b 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -507,6 +507,7 @@ list (APPEND TEST_SOURCE_FILES tests/test_OilSatfuncConsistencyChecks.cpp tests/test_outputdir.cpp tests/test_parametersystem.cpp + tests/test_grid_from_file.cpp tests/test_parallel_wbp_sourcevalues.cpp tests/test_parallelwellinfo.cpp tests/test_partitionCells.cpp diff --git a/opm/simulators/flow/CpGridVanguard.hpp b/opm/simulators/flow/CpGridVanguard.hpp index b67bd51d293..9357a61815f 100644 --- a/opm/simulators/flow/CpGridVanguard.hpp +++ b/opm/simulators/flow/CpGridVanguard.hpp @@ -308,7 +308,20 @@ class CpGridVanguard : public FlowBaseVanguard std::function(int)> cellCentroids() const { - return this->cellCentroids_(this->cartesianIndexMapper(), true); + if (!cellCentroidsFromGrid_) { + return this->cellCentroids_(this->cartesianIndexMapper(), true); + } else { + // Use centroids from the grid file if available + auto centroidIter = this->grid().beginCellCentroids(); + int num_cells = this->gridView().size(0); + return [centroidIter, num_cells](int i) -> std::array { + if (i < 0 || i >= num_cells) return std::array{}; + auto it = centroidIter; + std::advance(it, i); + const auto& centroid = *it; + return {centroid[0], centroid[1], centroid[2]}; + }; + } } const std::vector& globalCell() @@ -319,6 +332,11 @@ class CpGridVanguard : public FlowBaseVanguard protected: void createGrids_() { + // Check if grid will be loaded from file + const auto gridFileName = Parameters::Get(); + if (!gridFileName.empty()) { + cellCentroidsFromGrid_ = true; + } this->doCreateGrids_(this->edgeConformal(), this->eclState()); } @@ -364,6 +382,9 @@ class CpGridVanguard : public FlowBaseVanguard // diffusivity_ abd dispersivity_. The main reason is to reduce the memory usage for rank 0 // during parallel running. std::unique_ptr globalTrans_; + + // Flag to indicate if cell centroids should be read from the grid file + bool cellCentroidsFromGrid_ = false; }; } // namespace Opm diff --git a/opm/simulators/flow/EclGenericWriter_impl.hpp b/opm/simulators/flow/EclGenericWriter_impl.hpp index dc178d77acb..170cce09aec 100644 --- a/opm/simulators/flow/EclGenericWriter_impl.hpp +++ b/opm/simulators/flow/EclGenericWriter_impl.hpp @@ -49,7 +49,10 @@ #include #include +#include + #include +#include #if HAVE_MPI #include @@ -246,10 +249,22 @@ EclGenericWriter(const Schedule& schedule, outputNnc_.resize(1); if (this->collectOnIORank_.isIORank()) { - this->eclIO_ = std::make_unique - (this->eclState_, - UgGridHelpers::createEclipseGrid(*equilGrid, eclState_.getInputGrid()), - this->schedule_, summaryConfig, "", enableEsmry); + // If an external unstructured grid is provided, use input grid as-is. + // Otherwise, build an EclipseGrid that reflects the simulation grid. + const auto gridFileName = Parameters::Get(); + if (!gridFileName.empty()) { + this->eclIO_ = std::make_unique + (this->eclState_, + eclState_.getInputGrid(), + this->schedule_, summaryConfig, "", enableEsmry); + } + else { + assert(equilGrid != nullptr); + this->eclIO_ = std::make_unique + (this->eclState_, + UgGridHelpers::createEclipseGrid(*equilGrid, eclState_.getInputGrid()), + this->schedule_, summaryConfig, "", enableEsmry); + } } // create output thread if enabled and rank is I/O rank diff --git a/opm/simulators/flow/FlowGenericVanguard.cpp b/opm/simulators/flow/FlowGenericVanguard.cpp index 3b65d7e8974..2bc43668f91 100644 --- a/opm/simulators/flow/FlowGenericVanguard.cpp +++ b/opm/simulators/flow/FlowGenericVanguard.cpp @@ -544,8 +544,9 @@ void FlowGenericVanguard::registerParameters_() // register here for the use in the tests without BlackoilModelParameters Parameters::Register ("Use the well model for multi-segment wells instead of the one for single-segment wells"); + Parameters::Register + ("Filename for unstructured grid input. If empty, the grid will be constructed from the ECL deck."); } - template void FlowGenericVanguard::registerParameters_(); #if FLOW_INSTANTIATE_FLOAT diff --git a/opm/simulators/flow/FlowGenericVanguard.hpp b/opm/simulators/flow/FlowGenericVanguard.hpp index 3c13587e6ce..b071b3c5a02 100644 --- a/opm/simulators/flow/FlowGenericVanguard.hpp +++ b/opm/simulators/flow/FlowGenericVanguard.hpp @@ -88,7 +88,7 @@ struct ZoltanImbalanceTol { static constexpr Scalar value = 1.1; }; struct ZoltanPhgEdgeSizeThreshold { static constexpr auto value = 0.35; }; struct ZoltanParams { static constexpr auto value = "graph"; }; - +struct UnstructuredGridFileName { static constexpr auto* value = ""; }; } // namespace Opm::Parameters namespace Opm { diff --git a/opm/simulators/flow/GenericCpGridVanguard.cpp b/opm/simulators/flow/GenericCpGridVanguard.cpp index a6c3589c981..521e2f76ec0 100644 --- a/opm/simulators/flow/GenericCpGridVanguard.cpp +++ b/opm/simulators/flow/GenericCpGridVanguard.cpp @@ -40,6 +40,8 @@ #include #include +#include + #include #include #include @@ -465,46 +467,107 @@ doCreateGrids_(const bool edge_conformal, EclipseState& eclState) OpmLog::info("\nProcessing grid"); } + const auto gridFileName = Parameters::Get(); + // --- Create grid --- OPM_TIMEBLOCK(createGrids); + std::vector removed_cells; + if (gridFileName.empty()) { #if HAVE_MPI - this->grid_ = std::make_unique(FlowGenericVanguard::comm()); + this->grid_ = std::make_unique(FlowGenericVanguard::comm()); #else - this->grid_ = std::make_unique(); + this->grid_ = std::make_unique(); #endif - // Note: removed_cells is guaranteed to be empty on ranks other than 0. - auto removed_cells = this->grid_ - ->processEclipseFormat(input_grid, - &eclState, - /* isPeriodic = */ false, - /* flipNormals = */ false, - /* clipZ = */ false, - edge_conformal); + // Note: removed_cells is guaranteed to be empty on ranks other than 0. + removed_cells = this->grid_ + ->processEclipseFormat(input_grid, + &eclState, + /* isPeriodic = */ false, + /* flipNormals = */ false, + /* clipZ = */ false, + edge_conformal); + + if (isRoot) { + const auto& active_porv = eclState.fieldProps().porv(false); + const auto& unit_system = eclState.getUnits(); + const auto& volume_unit = unit_system.name(UnitSystem::measure::volume); + double total_pore_volume = unit_system.from_si(UnitSystem::measure::volume, + std::accumulate(active_porv.begin(), active_porv.end(), 0.0)); + OpmLog::info(fmt::format("Total number of active cells: {} / total pore volume: {:0.0f} {}", + grid_->numCells(), total_pore_volume , volume_unit)); + + double removed_pore_volume = + std::accumulate(removed_cells.begin(), removed_cells.end(), 0.0, + [&active_porv, &eclState](const auto acc, const auto global_index) + { return acc + active_porv[eclState.getInputGrid().activeIndex(global_index)]; }); + + if (removed_pore_volume > 0) { + removed_pore_volume = unit_system.from_si(UnitSystem::measure::volume, removed_pore_volume); + OpmLog::info(fmt::format("Removed {} cells with a pore volume of {:0.0f} " + "{} ({:5.3f} %) due to MINPV/MINPVV", + removed_cells.size(), + removed_pore_volume, + volume_unit, + 100 * removed_pore_volume / total_pore_volume)); + } + } + } + else { +#if HAVE_MPI + if (FlowGenericVanguard::comm().size() > 1) { + OpmLog::info(fmt::format("Using unstructured grid from file (MPI): {}", gridFileName)); + this->grid_ = std::make_unique(FlowGenericVanguard::comm()); + this->grid_->readUnstructuredGridFile(gridFileName); + + if (isRoot) { + if (!input_grid->allActive()) { + OPM_THROW(std::runtime_error, + fmt::format("Cannot use unstructured grid file '{}': the Eclipse input grid " + "contains inactive cells ({} active out of {} total). " + "Unstructured grid files require all cells to be active.", + gridFileName, + input_grid->getNumActive(), + input_grid->getCartesianSize())); + } - if (isRoot) { - const auto& active_porv = eclState.fieldProps().porv(false); - const auto& unit_system = eclState.getUnits(); - const auto& volume_unit = unit_system.name(UnitSystem::measure::volume); - double total_pore_volume = unit_system.from_si(UnitSystem::measure::volume, - std::accumulate(active_porv.begin(), active_porv.end(), 0.0)); - OpmLog::info(fmt::format("Total number of active cells: {} / total pore volume: {:0.0f} {}", - grid_->numCells(), total_pore_volume , volume_unit)); - - double removed_pore_volume = - std::accumulate(removed_cells.begin(), removed_cells.end(), 0.0, - [&active_porv, &eclState](const auto acc, const auto global_index) - { return acc + active_porv[eclState.getInputGrid().activeIndex(global_index)]; }); - - if (removed_pore_volume > 0) { - removed_pore_volume = unit_system.from_si(UnitSystem::measure::volume, removed_pore_volume); - OpmLog::info(fmt::format("Removed {} cells with a pore volume of {:0.0f} " - "{} ({:5.3f} %) due to MINPV/MINPVV", - removed_cells.size(), - removed_pore_volume, - volume_unit, - 100 * removed_pore_volume / total_pore_volume)); + const int numCellsFile = this->grid_->numCells(); + const int numCellsEcl = static_cast(input_grid->getNumActive()); + if (numCellsFile != numCellsEcl) { + OPM_THROW(std::runtime_error, + fmt::format("Cell count mismatch: unstructured grid file '{}' has {} cells, " + "but the Eclipse input grid has {} active cells.", + gridFileName, numCellsFile, numCellsEcl)); + } + } + } + else +#endif + { + OpmLog::info(fmt::format("Using unstructured grid from file: {}", gridFileName)); + this->grid_ = std::make_unique(gridFileName); + + if (isRoot) { + if (!input_grid->allActive()) { + OPM_THROW(std::runtime_error, + fmt::format("Cannot use unstructured grid file '{}': the Eclipse input grid " + "contains inactive cells ({} active out of {} total). " + "Unstructured grid files require all cells to be active.", + gridFileName, + input_grid->getNumActive(), + input_grid->getCartesianSize())); + } + + const int numCellsFile = this->grid_->numCells(); + const int numCellsEcl = static_cast(input_grid->getNumActive()); + if (numCellsFile != numCellsEcl) { + OPM_THROW(std::runtime_error, + fmt::format("Cell count mismatch: unstructured grid file '{}' has {} cells, " + "but the Eclipse input grid has {} active cells.", + gridFileName, numCellsFile, numCellsEcl)); + } + } } } diff --git a/opm/simulators/flow/PolyhedralGridVanguard.hpp b/opm/simulators/flow/PolyhedralGridVanguard.hpp index e2390e76999..ffe23d5a508 100644 --- a/opm/simulators/flow/PolyhedralGridVanguard.hpp +++ b/opm/simulators/flow/PolyhedralGridVanguard.hpp @@ -35,6 +35,10 @@ #include #include +#include + +#include + #include #include #include @@ -113,11 +117,17 @@ class PolyhedralGridVanguard : public FlowBaseVanguard { this->callImplementationInit(); // add a copy in standard vector format to fullfill new interface - const int* globalcellorg = this->grid().globalCell(); + const int* globalcellorg = this->grid().globalCellPtr(); int num_cells = this->gridView().size(0); globalcell_.resize(num_cells); for(int i=0; i < num_cells; ++i){ - globalcell_[i] = globalcellorg[i]; + // For grids without global cell numbering, globalcellorg is nullptr + // and we just use the local cell index as global cell index. + if (globalcellorg) { + globalcell_[i] = globalcellorg[i]; + } else { + globalcell_[i] = i; + } } } @@ -232,7 +242,18 @@ class PolyhedralGridVanguard : public FlowBaseVanguard std::function::dimensionworld>(int)> cellCentroids() const { - return this->cellCentroids_(this->cartesianIndexMapper(), false); + if (!cellCentroidsFromGrid_) { + return this->cellCentroids_(this->cartesianIndexMapper(), false); + } else { + using UnstructuredGridType = Grid::UnstructuredGridType; + UnstructuredGridType ugPtr( *this->grid_ ); + double* centroids = ugPtr.cell_centroids; + int num_cells = this->grid_->size(0); + return [centroids, num_cells](int i) -> std::array::dimensionworld> { + if (i < 0 || i >= num_cells) return std::array::dimensionworld>{}; + return {centroids[i * 3], centroids[i * 3 + 1], centroids[i * 3 + 2]}; + }; + } } std::vector cellPartition() const @@ -244,11 +265,36 @@ class PolyhedralGridVanguard : public FlowBaseVanguard protected: void createGrids_() { - this->grid_ = std::make_unique - (this->eclState().getInputGrid(), - this->eclState().fieldProps().porv(true), - this->edgeConformal()); - + // Read the grid from file if specified, otherwise use the grid from the ECL file. + std::string gridFileName = Parameters::Get(); + if (gridFileName.empty()) { + this->grid_ = std::make_unique + (this->eclState().getInputGrid(), + this->eclState().fieldProps().porv(true), + this->edgeConformal()); + } else { + const auto& input_grid = this->eclState().getInputGrid(); + if (!input_grid.allActive()) { + OPM_THROW(std::runtime_error, + fmt::format("Cannot use unstructured grid file '{}': the Eclipse input grid " + "contains inactive cells ({} active out of {} total). " + "Unstructured grid files require all cells to be active.", + gridFileName, + input_grid.getNumActive(), + input_grid.getCartesianSize())); + } + std::cout << "Using unstructured grid from file: " << gridFileName << std::endl; + this->grid_ = std::make_unique(gridFileName.empty() ? "test.txt" : gridFileName); + cellCentroidsFromGrid_ = true; + const int numCellsFile = static_cast(this->grid_->size(0)); + const int numCellsEcl = static_cast(input_grid.getNumActive()); + if (numCellsFile != numCellsEcl) { + OPM_THROW(std::runtime_error, + fmt::format("Cell count mismatch: unstructured grid file '{}' has {} cells, " + "but the Eclipse input grid has {} active cells.", + gridFileName, numCellsFile, numCellsEcl)); + } + } this->cartesianIndexMapper_ = std::make_unique(*this->grid_); @@ -270,6 +316,7 @@ class PolyhedralGridVanguard : public FlowBaseVanguard std::unordered_set defunctWellNames_; std::vector globalcell_; + bool cellCentroidsFromGrid_ = false; }; } // namespace Opm diff --git a/opm/simulators/flow/Transmissibility_impl.hpp b/opm/simulators/flow/Transmissibility_impl.hpp index c268d2eed91..4c88b5824c1 100644 --- a/opm/simulators/flow/Transmissibility_impl.hpp +++ b/opm/simulators/flow/Transmissibility_impl.hpp @@ -458,8 +458,11 @@ update(bool global, const TransUpdateQuantities update_quantities, Scalar trans = computeHalfMean(computeHalfTrans_, permeability_); // apply the full face transmissibility multipliers - // for the inside ... - if (!pinchActive) { + // for the inside + // Assumes cartesian grid with possible LGRs, but no unstructured grids. + // i.e. check if grid is read from file (unstructured) + const bool gridFromFile = !std::string(Parameters::Get()).empty(); + if (!gridFromFile && !pinchActive) { if (inside.faceIdx > 3) { // top or bottom auto find_layer = [&cartDims](std::size_t cell) { cell /= cartDims[0]; diff --git a/tests/SimulatorFixture.hpp b/tests/SimulatorFixture.hpp index 54d60bc5a5b..20275b646d9 100644 --- a/tests/SimulatorFixture.hpp +++ b/tests/SimulatorFixture.hpp @@ -54,6 +54,7 @@ #include #include +#include namespace Opm { @@ -90,17 +91,22 @@ template std::unique_ptr> initSimulator(const char* filename, const char* test_name = "test_simulator", - int threads_per_process = 1) + int threads_per_process = 1, + const std::vector& extra_args = {}) { using Simulator = GetPropType; - std::string filename_arg = "--ecl-deck-file-name="; - filename_arg += filename; + std::vector argv_storage; + argv_storage.reserve(extra_args.size() + 2); + argv_storage.emplace_back(test_name); + argv_storage.emplace_back(std::string{"--ecl-deck-file-name="} + filename); + argv_storage.insert(argv_storage.end(), extra_args.begin(), extra_args.end()); - const char* argv[] = { - test_name, - filename_arg.c_str() - }; + std::vector argv; + argv.reserve(argv_storage.size()); + for (const auto& arg : argv_storage) { + argv.push_back(arg.c_str()); + } Parameters::reset(); registerAllParameters_(false); @@ -109,8 +115,8 @@ initSimulator(const char* filename, Parameters::Register("Do *NOT* use!"); Parameters::SetDefault(threads_per_process); Parameters::endRegistration(); - setupParameters_(/*argc=*/sizeof(argv) / sizeof(argv[0]), - argv, + setupParameters_(/*argc=*/static_cast(argv.size()), + argv.data(), /*registerParams=*/false, /*allowUnused=*/false, /*handleHelp=*/true, diff --git a/tests/test_grid_from_file.cpp b/tests/test_grid_from_file.cpp new file mode 100644 index 00000000000..ca7231bc2e8 --- /dev/null +++ b/tests/test_grid_from_file.cpp @@ -0,0 +1,342 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: + +#include "config.h" + +#define BOOST_TEST_MODULE GridFromFileSimulator + +#include + +#include "SimulatorFixture.hpp" + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +// These templates are not explicitly instantiated in the library for polyhedral grids. +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace Opm::Properties { + +namespace TTag { + +struct TestPolyhedralTypeTag { + using InheritsFrom = std::tuple; +}; + +} // namespace TTag + +template +struct Linearizer +{ + using type = TpfaLinearizer; +}; + +template +struct LocalResidual +{ + using type = BlackOilLocalResidualTPFA; +}; + +template +struct EnableDiffusion +{ + static constexpr bool value = false; +}; + +template +struct Grid +{ + using type = Dune::PolyhedralGrid<3, 3>; +}; + +template +struct EquilGrid +{ + using type = GetPropType; +}; + +template +struct Vanguard +{ + using type = Opm::PolyhedralGridVanguard; +}; + +} // namespace Opm::Properties + +namespace Opm { + +template<> +class SupportsFaceTag> + : public std::bool_constant +{}; + +} // namespace Opm + +namespace { + +class ScopedFile +{ +public: + explicit ScopedFile(std::string path) + : path_(std::move(path)) + {} + + ~ScopedFile() + { + std::remove(path_.c_str()); + } + + const std::string& path() const + { + return path_; + } + +private: + std::string path_; +}; + +template +void writeValues(std::ostream& os, const T* values, std::size_t count) +{ + for (std::size_t i = 0; i < count; ++i) { + if (i > 0) { + os << ' '; + } + os << values[i]; + } + os << '\n'; +} + +void writeTrivialDeckFile(const std::string& deckFileName, int numCells) +{ + std::ofstream deck(deckFileName); + BOOST_REQUIRE(deck); + + deck << "RUNSPEC\n\n" + << "WATER\n" + << "GAS\n" + << "OIL\n\n" + << "METRIC\n\n" + << "DIMENS\n" + << " " << numCells << " 1 1 /\n\n" + << "GRID\n\n" + << "DX\n" + << " " << numCells << "*1 /\n" + << "DY\n" + << " " << numCells << "*1 /\n" + << "DZ\n" + << " " << numCells << "*1 /\n\n" + << "TOPS\n" + << " " << numCells << "*0 /\n\n" + << "PORO\n" + << " " << numCells << "*0.3 /\n\n" + << "PERMX\n" + << " " << numCells << "*500 /\n\n" + << "PROPS\n\n" + << "PVTW\n" + << " 4017.55 1.038 3.22E-6 0.318 0.0 /\n\n" + << "ROCK\n" + << " 14.7 3E-6 /\n\n" + << "SWOF\n" + << "0.12 0 1 0\n" + << "0.30 0.1 0.9 0\n" + << "1.00 1 0 0 /\n\n" + << "SGOF\n" + << "0.00 0 1 0\n" + << "0.30 0.1 0.9 0\n" + << "0.88 1 0 0 /\n\n" + << "DENSITY\n" + << " 53.66 64.49 0.0533 /\n\n" + << "PVDG\n" + << "14.700 166.666 0.008000\n" + << "9014.7 0.38600 0.047000 /\n\n" + << "PVTO\n" + << "0.0010 14.7 1.0620 1.0400 /\n" + << "1.2700 4014.7 1.6950 0.5100\n" + << " 9014.7 1.5790 0.7400 /\n" + << "/\n\n" + << "SOLUTION\n\n" + << "SWAT\n" + << " " << numCells << "*0.12 /\n\n" + << "SGAS\n" + << " " << numCells << "*0 /\n\n" + << "PRESSURE\n" + << " " << numCells << "*300 /\n\n" + << "SUMMARY\n\n" + << "SCHEDULE\n\n" + << "TSTEP\n" + << "1 /\n"; +} + +void writeCartesianUnstructuredGridFile(const std::string& gridFileName, int numCells) +{ + std::unique_ptr ugPtr(create_grid_cart3d(numCells, 1, 1), &destroy_grid); + BOOST_REQUIRE(ugPtr); + + const auto& grid = *ugPtr; + + std::ofstream gridFile(gridFileName); + BOOST_REQUIRE(gridFile); + + const auto numFaceNodes = static_cast(grid.face_nodepos[grid.number_of_faces]); + const auto numCellFaces = static_cast(grid.cell_facepos[grid.number_of_cells]); + const bool hasFaceTags = grid.cell_facetag != nullptr; + const bool hasIndexMap = grid.global_cell != nullptr; + + gridFile << grid.dimensions << ' ' + << grid.number_of_cells << ' ' + << grid.number_of_faces << ' ' + << grid.number_of_nodes << ' ' + << numFaceNodes << ' ' + << numCellFaces << ' ' + << hasFaceTags << ' ' << hasIndexMap << '\n'; + + writeValues(gridFile, grid.cartdims, static_cast(grid.dimensions)); + writeValues(gridFile, grid.node_coordinates, static_cast(grid.dimensions * grid.number_of_nodes)); + writeValues(gridFile, grid.face_nodepos, static_cast(grid.number_of_faces + 1)); + writeValues(gridFile, grid.face_nodes, numFaceNodes); + writeValues(gridFile, grid.face_cells, static_cast(2 * grid.number_of_faces)); + writeValues(gridFile, grid.face_areas, static_cast(grid.number_of_faces)); + writeValues(gridFile, grid.face_centroids, static_cast(grid.dimensions * grid.number_of_faces)); + writeValues(gridFile, grid.face_normals, static_cast(grid.dimensions * grid.number_of_faces)); + writeValues(gridFile, grid.cell_facepos, static_cast(grid.number_of_cells + 1)); + + if (hasFaceTags) { + for (std::size_t i = 0; i < numCellFaces; ++i) { + if (i > 0) { + gridFile << ' '; + } + gridFile << grid.cell_faces[i] << ' ' << grid.cell_facetag[i]; + } + gridFile << '\n'; + } + else { + writeValues(gridFile, grid.cell_faces, numCellFaces); + } + + if (hasIndexMap) { + writeValues(gridFile, grid.global_cell, static_cast(grid.number_of_cells)); + } + + writeValues(gridFile, grid.cell_volumes, static_cast(grid.number_of_cells)); + writeValues(gridFile, grid.cell_centroids, static_cast(grid.dimensions * grid.number_of_cells)); +} + +} // namespace + +using SimulatorFixture = Opm::SimulatorFixture; +BOOST_GLOBAL_FIXTURE(SimulatorFixture); + +BOOST_AUTO_TEST_CASE(init_polyhedral_simulator_from_unstructured_grid_file) +{ + using TypeTag = Opm::Properties::TTag::TestPolyhedralTypeTag; + + constexpr int numCells = 3; + const ScopedFile deckFile{"test_polyhedral_unstructured.DATA"}; + const ScopedFile gridFile{"test_polyhedral_unstructured.grid"}; + + writeTrivialDeckFile(deckFile.path(), numCells); + writeCartesianUnstructuredGridFile(gridFile.path(), numCells); + + auto simulator = Opm::initSimulator(deckFile.path().c_str(), + "test_polyhedral_unstructured", + /*threads_per_process=*/1, + {std::string{"--unstructured-grid-file-name="} + gridFile.path()}); + BOOST_REQUIRE(simulator); + + const auto& grid = simulator->vanguard().grid(); + const auto& eclGrid = simulator->vanguard().eclState().getInputGrid(); + + BOOST_CHECK_EQUAL(eclGrid.getNumActive(), numCells); + BOOST_CHECK_EQUAL(static_cast(grid.size(0)), eclGrid.getNumActive()); + BOOST_CHECK_EQUAL(simulator->vanguard().globalCell().size(), grid.size(0)); +} + +BOOST_AUTO_TEST_CASE(init_cpgrid_simulator_from_unstructured_grid_file) +{ + using TypeTag = Opm::Properties::TTag::TestTypeTag; + + constexpr int numCells = 3; + const ScopedFile deckFile{"test_cpgrid_unstructured.DATA"}; + const ScopedFile gridFile{"test_cpgrid_unstructured.grid"}; + + writeTrivialDeckFile(deckFile.path(), numCells); + writeCartesianUnstructuredGridFile(gridFile.path(), numCells); + + auto simulator = Opm::initSimulator(deckFile.path().c_str(), + "test_cpgrid_unstructured", + /*threads_per_process=*/1, + {std::string{"--unstructured-grid-file-name="} + gridFile.path()}); + BOOST_REQUIRE(simulator); + + const auto& grid = simulator->vanguard().grid(); + const auto& gridView = simulator->vanguard().gridView(); + const auto& eclGrid = simulator->vanguard().eclState().getInputGrid(); + + BOOST_CHECK_EQUAL(eclGrid.getNumActive(), numCells); + BOOST_CHECK_EQUAL(grid.numCells(), numCells); + BOOST_CHECK_EQUAL(static_cast(grid.numCells()), gridView.size(0)); + BOOST_CHECK_EQUAL(grid.globalCell().size(), static_cast(grid.numCells())); +} + +BOOST_AUTO_TEST_CASE(init_polyhedral_simulator_from_deck) +{ + using TypeTag = Opm::Properties::TTag::TestPolyhedralTypeTag; + using Grid = Opm::GetPropType; + using Vanguard = Opm::GetPropType; + + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + + auto simulator = Opm::initSimulator("equil_base.DATA", "test_polyhedral"); + BOOST_REQUIRE(simulator); + + const auto& grid = simulator->vanguard().grid(); + const auto& gridView = simulator->vanguard().gridView(); + const auto& eclGrid = simulator->vanguard().eclState().getInputGrid(); + + BOOST_CHECK_EQUAL(static_cast(grid.size(0)), eclGrid.getNumActive()); + BOOST_CHECK_GT(gridView.size(0), 0u); + BOOST_CHECK_GT(gridView.size(1), 0u); + BOOST_CHECK_GT(gridView.size(3), 0u); + BOOST_CHECK_EQUAL(simulator->vanguard().globalCell().size(), gridView.size(0)); +} + +BOOST_AUTO_TEST_CASE(init_cpgrid_simulator_from_deck) +{ + using TypeTag = Opm::Properties::TTag::TestTypeTag; + + auto simulator = Opm::initSimulator("equil_base.DATA", "test_cpgrid"); + BOOST_REQUIRE(simulator); + + const auto& grid = simulator->vanguard().grid(); + const auto& gridView = simulator->vanguard().gridView(); + const auto& eclGrid = simulator->vanguard().eclState().getInputGrid(); + + BOOST_CHECK_EQUAL(static_cast(grid.numCells()), eclGrid.getNumActive()); + BOOST_CHECK_GT(gridView.size(0), 0u); + BOOST_CHECK_EQUAL(grid.globalCell().size(), gridView.size(0)); +} From af1dfe3d1028fd42413ff6d10e2339ce7f0c6a65 Mon Sep 17 00:00:00 2001 From: Tor Harald Sandve Date: Fri, 19 Jun 2026 13:06:16 +0200 Subject: [PATCH 2/4] add regression test --- regressionTests.cmake | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/regressionTests.cmake b/regressionTests.cmake index c64549533e7..7ccad9f911b 100644 --- a/regressionTests.cmake +++ b/regressionTests.cmake @@ -788,6 +788,25 @@ add_test_compareECLFiles( model1 ) +add_test_compareECLFiles( + CASENAME + base_model_1_gridfile + FILENAME + BASE_MODEL_1_GRIDFILE + SIMULATOR + flow + DEV_SIMULATOR + flow_blackoil + ABS_TOL + ${abs_tol} + REL_TOL + ${rel_tol} + DIR + model1 + TEST_ARGS + --unstructured-grid-file-name=${OPM_TESTS_ROOT}/model1/base.grid +) + add_test_compareECLFiles( CASENAME faults_model_1 From 3a0865834c7d0629f5aac05a54ef67484feb8872 Mon Sep 17 00:00:00 2001 From: Tor Harald Sandve Date: Fri, 19 Jun 2026 13:12:17 +0200 Subject: [PATCH 3/4] include needed files explicitly --- opm/simulators/flow/Transmissibility_impl.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/opm/simulators/flow/Transmissibility_impl.hpp b/opm/simulators/flow/Transmissibility_impl.hpp index 4c88b5824c1..f7976433581 100644 --- a/opm/simulators/flow/Transmissibility_impl.hpp +++ b/opm/simulators/flow/Transmissibility_impl.hpp @@ -43,8 +43,11 @@ #include #include +#include #include +#include + #include #include #include @@ -55,6 +58,7 @@ #include #include #include +#include #include #include #include From e9b42e7d64e5960b827398431482f323e7aa4653 Mon Sep 17 00:00:00 2001 From: Tor Harald Sandve Date: Fri, 19 Jun 2026 15:15:58 +0200 Subject: [PATCH 4/4] fix build --- opm/simulators/flow/CpGridVanguard.hpp | 8 +++++++- opm/simulators/flow/FlowGenericVanguard.hpp | 5 +++++ opm/simulators/flow/FlowProblem.hpp | 3 ++- opm/simulators/flow/PolyhedralGridVanguard.hpp | 5 +++++ opm/simulators/flow/Transmissibility.hpp | 4 +++- opm/simulators/flow/Transmissibility_impl.hpp | 14 ++++---------- 6 files changed, 26 insertions(+), 13 deletions(-) diff --git a/opm/simulators/flow/CpGridVanguard.hpp b/opm/simulators/flow/CpGridVanguard.hpp index 9357a61815f..637c5206fae 100644 --- a/opm/simulators/flow/CpGridVanguard.hpp +++ b/opm/simulators/flow/CpGridVanguard.hpp @@ -324,6 +324,11 @@ class CpGridVanguard : public FlowBaseVanguard } } + bool gridFromFile() const + { + return cellCentroidsFromGrid_; + } + const std::vector& globalCell() { return this->grid().globalCell(); @@ -351,7 +356,8 @@ class CpGridVanguard : public FlowBaseVanguard getPropValue() == EnergyModules::FullyImplicitThermal || getPropValue() == EnergyModules::SequentialImplicitThermal, getPropValue(), - getPropValue())); + getPropValue(), + cellCentroidsFromGrid_)); globalTrans_->update(false, TransmissibilityType::TransUpdateQuantities::Trans); } diff --git a/opm/simulators/flow/FlowGenericVanguard.hpp b/opm/simulators/flow/FlowGenericVanguard.hpp index b071b3c5a02..1f5dc3cd505 100644 --- a/opm/simulators/flow/FlowGenericVanguard.hpp +++ b/opm/simulators/flow/FlowGenericVanguard.hpp @@ -145,6 +145,11 @@ class FlowGenericVanguard { */ static std::string canonicalDeckPath(const std::string& caseName); + bool gridFromFile() const + { + return false; + } + /*! * \brief Returns the wall time required to set up the simulator before it was born. */ diff --git a/opm/simulators/flow/FlowProblem.hpp b/opm/simulators/flow/FlowProblem.hpp index 4ba0a4e662c..900d7e2e843 100644 --- a/opm/simulators/flow/FlowProblem.hpp +++ b/opm/simulators/flow/FlowProblem.hpp @@ -231,7 +231,8 @@ class FlowProblem : public GetPropType (energyModuleType == EnergyModules::FullyImplicitThermal || energyModuleType == EnergyModules::SequentialImplicitThermal), enableDiffusion, - enableDispersion) + enableDispersion, + simulator.vanguard().gridFromFile()) , wellModel_(simulator, this->iterationContext()) , aquiferModel_(simulator) , pffDofData_(simulator.gridView(), this->elementMapper()) diff --git a/opm/simulators/flow/PolyhedralGridVanguard.hpp b/opm/simulators/flow/PolyhedralGridVanguard.hpp index ffe23d5a508..bfc57419ea8 100644 --- a/opm/simulators/flow/PolyhedralGridVanguard.hpp +++ b/opm/simulators/flow/PolyhedralGridVanguard.hpp @@ -207,6 +207,11 @@ class PolyhedralGridVanguard : public FlowBaseVanguard return globalcell_; } + bool gridFromFile() const + { + return cellCentroidsFromGrid_; + } + unsigned int gridEquilIdxToGridIdx(unsigned int elemIndex) const { return elemIndex; } diff --git a/opm/simulators/flow/Transmissibility.hpp b/opm/simulators/flow/Transmissibility.hpp index 9bc3d6c62bc..9df3c348351 100644 --- a/opm/simulators/flow/Transmissibility.hpp +++ b/opm/simulators/flow/Transmissibility.hpp @@ -66,7 +66,8 @@ class Transmissibility { std::function(int)> centroids, bool enableEnergy, bool enableDiffusivity, - bool enableDispersivity); + bool enableDispersivity, + bool gridFromFile = false); /*! * \brief Return the permeability for an element. @@ -294,6 +295,7 @@ class Transmissibility { bool enableEnergy_; bool enableDiffusivity_; bool enableDispersivity_; + bool gridFromFile_; bool warnEditNNC_ = true; std::unordered_map thermalHalfTrans_; //NB this is based on direction map size is ca 2*trans_ (diffusivity_) std::unordered_map diffusivity_; diff --git a/opm/simulators/flow/Transmissibility_impl.hpp b/opm/simulators/flow/Transmissibility_impl.hpp index f7976433581..4fd18a3a295 100644 --- a/opm/simulators/flow/Transmissibility_impl.hpp +++ b/opm/simulators/flow/Transmissibility_impl.hpp @@ -43,11 +43,8 @@ #include #include -#include #include -#include - #include #include #include @@ -105,7 +102,8 @@ Transmissibility(const EclipseState& eclState, std::function(int)> centroids, bool enableEnergy, bool enableDiffusivity, - bool enableDispersivity) + bool enableDispersivity, + bool gridFromFile) : eclState_(eclState) , gridView_(gridView) , cartMapper_(cartMapper) @@ -114,6 +112,7 @@ Transmissibility(const EclipseState& eclState, , enableEnergy_(enableEnergy) , enableDiffusivity_(enableDiffusivity) , enableDispersivity_(enableDispersivity) + , gridFromFile_(gridFromFile) , lookUpData_(gridView) , lookUpCartesianData_(gridView, cartMapper) { @@ -461,12 +460,7 @@ update(bool global, const TransUpdateQuantities update_quantities, Scalar trans = computeHalfMean(computeHalfTrans_, permeability_); - // apply the full face transmissibility multipliers - // for the inside - // Assumes cartesian grid with possible LGRs, but no unstructured grids. - // i.e. check if grid is read from file (unstructured) - const bool gridFromFile = !std::string(Parameters::Get()).empty(); - if (!gridFromFile && !pinchActive) { + if (!gridFromFile_ && !pinchActive) { if (inside.faceIdx > 3) { // top or bottom auto find_layer = [&cartDims](std::size_t cell) { cell /= cartDims[0];