Skip to content

Commit 2f4eb83

Browse files
author
Tor Harald Sandve
committed
Add CpGrid support for loading unstructured grids from external files
Enable loading Sintef legacy unstructured-grid files into CpGrid via: - CpGrid(filename) constructor for direct file loading - readUnstructuredGridFile() method for MPI-aware loading - CpGridData::processUnstructuredGrid() for format conversion Add export utility (export_grid) to convert CpGrid back to unstructured format. Support includes: - MPI-parallel file reading (rank 0 reads and broadcasts) - Full grid topology conversion (cells, faces, vertices, adjacency) - Face axis tagging for K-direction identification - Validation of 3D grids, face tags, and cell structure - Comprehensive test coverage for both CpGrid and PolyhedralGrid New files: - examples/export_grid.cpp: CpGrid → unstructured format converter - Complete test cases for unstructured grid operations
1 parent 7bdf0f1 commit 2f4eb83

10 files changed

Lines changed: 862 additions & 0 deletions

File tree

CMakeLists_files.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ list(APPEND TEST_DATA_FILES
170170
# originally generated with the command:
171171
# find tutorials examples -name '*.c*' -printf '\t%p\n' | sort
172172
list(APPEND EXAMPLE_SOURCE_FILES
173+
examples/export_grid.cpp
173174
examples/finitevolume/finitevolume.cc
174175
examples/griditer.cpp
175176
)

examples/export_grid.cpp

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
#include <config.h>
2+
3+
#include <opm/grid/CpGrid.hpp>
4+
#include <opm/grid/cpgrid/GridHelpers.hpp>
5+
6+
#if HAVE_OPM_COMMON
7+
#include <opm/input/eclipse/Deck/Deck.hpp>
8+
#include <opm/input/eclipse/EclipseState/EclipseState.hpp>
9+
#include <opm/input/eclipse/EclipseState/Grid/EclipseGrid.hpp>
10+
#include <opm/input/eclipse/Parser/InputErrorAction.hpp>
11+
#include <opm/input/eclipse/Parser/ParseContext.hpp>
12+
#include <opm/input/eclipse/Parser/Parser.hpp>
13+
#endif
14+
15+
#include <fstream>
16+
#include <filesystem>
17+
#include <iomanip>
18+
#include <iostream>
19+
#include <stdexcept>
20+
#include <string>
21+
#include <vector>
22+
23+
namespace {
24+
25+
#if HAVE_OPM_COMMON
26+
Opm::ParseContext makeFlowLikeParseContext()
27+
{
28+
return Opm::ParseContext({
29+
{Opm::ParseContext::PARSE_RANDOM_SLASH, Opm::InputErrorAction::IGNORE},
30+
{Opm::ParseContext::PARSE_MISSING_DIMS_KEYWORD, Opm::InputErrorAction::WARN},
31+
{Opm::ParseContext::SUMMARY_UNKNOWN_WELL, Opm::InputErrorAction::WARN},
32+
{Opm::ParseContext::SUMMARY_UNKNOWN_GROUP, Opm::InputErrorAction::WARN},
33+
});
34+
}
35+
#endif
36+
37+
void writeGridFromCpGrid(const Dune::CpGrid& grid, const std::string& output_file)
38+
{
39+
const int ndims = 3;
40+
const int ncells = grid.numCells();
41+
const int nfaces = grid.numFaces();
42+
const int nnodes = grid.numVertices();
43+
const int ncellfaces = grid.numCellFaces();
44+
45+
int nfacenodes = 0;
46+
std::vector<int> face_nodepos;
47+
std::vector<int> face_nodes;
48+
face_nodepos.reserve(nfaces + 1);
49+
face_nodes.reserve(4 * nfaces);
50+
face_nodepos.push_back(0);
51+
for (int f = 0; f < nfaces; ++f) {
52+
const int nv = grid.numFaceVertices(f);
53+
for (int lv = 0; lv < nv; ++lv) {
54+
face_nodes.push_back(grid.faceVertex(f, lv));
55+
}
56+
nfacenodes += nv;
57+
face_nodepos.push_back(nfacenodes);
58+
}
59+
60+
std::vector<int> face_cells;
61+
face_cells.reserve(2 * nfaces);
62+
for (int f = 0; f < nfaces; ++f) {
63+
face_cells.push_back(grid.faceCell(f, 0));
64+
face_cells.push_back(grid.faceCell(f, 1));
65+
}
66+
67+
std::vector<int> cell_facepos;
68+
std::vector<int> cell_faces;
69+
std::vector<int> cell_facetag;
70+
cell_facepos.reserve(ncells + 1);
71+
cell_faces.reserve(ncellfaces);
72+
cell_facetag.reserve(ncellfaces);
73+
cell_facepos.push_back(0);
74+
const auto c2f = Opm::UgGridHelpers::cell2Faces(grid);
75+
for (int c = 0; c < ncells; ++c) {
76+
const auto row = c2f[c];
77+
for (auto it = row.begin(); it != row.end(); ++it) {
78+
const int face = *it;
79+
cell_faces.push_back(face);
80+
81+
// Export the exact CpGrid face tag used by transmissibility logic
82+
const int tag = Opm::UgGridHelpers::faceTag(grid, it);
83+
cell_facetag.push_back(tag);
84+
}
85+
cell_facepos.push_back(static_cast<int>(cell_faces.size()));
86+
}
87+
88+
if (static_cast<int>(cell_faces.size()) != ncellfaces) {
89+
throw std::runtime_error("Internal inconsistency: numCellFaces() total mismatch");
90+
}
91+
92+
std::ofstream out(output_file);
93+
if (!out) {
94+
throw std::runtime_error("Failed to open output file: " + output_file);
95+
}
96+
97+
out << std::setprecision(17) << std::fixed;
98+
99+
const int has_tag = 1;
100+
const int has_indexmap = 1;
101+
out << ndims << ' ' << ncells << ' ' << nfaces << ' '
102+
<< nnodes << ' ' << nfacenodes << ' ' << ncellfaces << ' '
103+
<< has_tag << ' ' << has_indexmap << '\n';
104+
105+
const auto cartdims = grid.logicalCartesianSize();
106+
out << cartdims[0] << ' ' << cartdims[1] << ' ' << cartdims[2] << '\n';
107+
108+
for (int v = 0; v < nnodes; ++v) {
109+
const auto& p = grid.vertexPosition(v);
110+
out << p[0] << ' ' << p[1] << ' ' << p[2] << ' ';
111+
}
112+
out << '\n';
113+
114+
for (const int value : face_nodepos) {
115+
out << value << ' ';
116+
}
117+
out << '\n';
118+
119+
for (const int value : face_nodes) {
120+
out << value << ' ';
121+
}
122+
out << '\n';
123+
124+
for (const int value : face_cells) {
125+
out << value << ' ';
126+
}
127+
out << '\n';
128+
129+
for (int f = 0; f < nfaces; ++f) {
130+
out << grid.faceArea(f) << ' ';
131+
}
132+
out << '\n';
133+
134+
for (int f = 0; f < nfaces; ++f) {
135+
const auto& c = grid.faceCentroid(f);
136+
out << c[0] << ' ' << c[1] << ' ' << c[2] << ' ';
137+
}
138+
out << '\n';
139+
140+
for (int f = 0; f < nfaces; ++f) {
141+
const auto& n = grid.faceNormal(f);
142+
out << n[0] << ' ' << n[1] << ' ' << n[2] << ' ';
143+
}
144+
out << '\n';
145+
146+
for (const int value : cell_facepos) {
147+
out << value << ' ';
148+
}
149+
out << '\n';
150+
151+
for (std::size_t i = 0; i < cell_faces.size(); ++i) {
152+
out << cell_faces[i] << ' ' << cell_facetag[i] << ' ';
153+
}
154+
out << '\n';
155+
156+
const auto& global_cell = grid.globalCell();
157+
for (const int value : global_cell) {
158+
out << value << ' ';
159+
}
160+
out << '\n';
161+
162+
for (int c = 0; c < ncells; ++c) {
163+
out << grid.cellVolume(c) << ' ';
164+
}
165+
out << '\n';
166+
167+
for (int c = 0; c < ncells; ++c) {
168+
const auto& cc = grid.cellCentroid(c);
169+
out << cc[0] << ' ' << cc[1] << ' ' << cc[2] << ' ';
170+
}
171+
out << '\n';
172+
}
173+
174+
} // namespace
175+
176+
int main(int argc, char** argv)
177+
{
178+
Dune::MPIHelper::instance(argc, argv);
179+
180+
#if !HAVE_OPM_COMMON
181+
std::cerr << "export_grid requires HAVE_OPM_COMMON to parse .DATA files." << std::endl;
182+
return 2;
183+
#else
184+
if (argc != 3) {
185+
std::cerr << "Usage: " << argv[0] << " <case.DATA> <output.grid>" << std::endl;
186+
return 1;
187+
}
188+
189+
const std::string data_file = argv[1];
190+
const std::string output_file = argv[2];
191+
192+
try {
193+
Dune::CpGrid cpgrid;
194+
bool built_from_data = false;
195+
196+
try {
197+
Opm::Parser parser;
198+
const auto deck = parser.parseFile(data_file, makeFlowLikeParseContext());
199+
200+
Opm::EclipseGrid eclipse_grid(deck);
201+
Opm::EclipseState ecl_state(deck);
202+
203+
cpgrid.processEclipseFormat(&eclipse_grid, &ecl_state, false);
204+
built_from_data = true;
205+
} catch (const std::exception& e) {
206+
const std::filesystem::path data_path(data_file);
207+
const std::filesystem::path egrid_path =
208+
data_path.parent_path() / (data_path.stem().string() + ".EGRID");
209+
if (!std::filesystem::exists(egrid_path)) {
210+
throw std::runtime_error(
211+
std::string("Failed to parse DATA and no EGRID fallback found. DATA error: ") + e.what());
212+
}
213+
214+
std::cerr << "Warning: failed to build grid directly from DATA (" << e.what() << ")\n"
215+
<< "Falling back to EGRID: " << egrid_path << "\n";
216+
Opm::EclipseGrid eclipse_grid(egrid_path.string());
217+
cpgrid.processEclipseFormat(&eclipse_grid, nullptr, false);
218+
}
219+
220+
writeGridFromCpGrid(cpgrid, output_file);
221+
222+
std::cout << "Wrote grid: " << output_file << "\n"
223+
<< " source : " << (built_from_data ? "DATA" : "EGRID fallback") << "\n"
224+
<< " cells/faces/vertices/cellFaces = "
225+
<< cpgrid.numCells() << " / "
226+
<< cpgrid.numFaces() << " / "
227+
<< cpgrid.numVertices() << " / "
228+
<< cpgrid.numCellFaces() << "\n";
229+
} catch (const std::exception& e) {
230+
std::cerr << "Failed to export grid from DATA: " << e.what() << std::endl;
231+
return 3;
232+
}
233+
234+
return 0;
235+
#endif
236+
}

opm/grid/CpGrid.hpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@
5858

5959
#include <set>
6060

61+
struct UnstructuredGrid;
62+
6163
namespace Opm
6264
{
6365
struct NNCdata;
@@ -226,6 +228,20 @@ namespace Dune
226228

227229
explicit CpGrid(MPIHelper::MPICommunicator comm);
228230

231+
/// Construct grid from serialized UnstructuredGrid file.
232+
explicit CpGrid(const std::string& filename);
233+
234+
/// Read a serialized UnstructuredGrid file into an already-constructed grid.
235+
///
236+
/// In an MPI run this method must be called on all ranks. Only rank 0
237+
/// actually reads the file and calls processUnstructuredGrid(); the other
238+
/// ranks keep an empty global-view grid ready for scatterGrid / loadBalance.
239+
/// After this call the logical_cartesian_size is broadcast to all ranks so
240+
/// every rank knows the total cell count.
241+
///
242+
/// \param filename Path to the Sintef legacy unstructured-grid file.
243+
void readUnstructuredGridFile(const std::string& filename);
244+
229245
#if HAVE_OPM_COMMON
230246
/// Read the Eclipse grid format ('grdecl').
231247
///

opm/grid/cpgrid/CpGrid.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
#include <opm/input/eclipse/EclipseState/Grid/EclipseGrid.hpp>
4949
#endif
5050

51+
#include <opm/grid/UnstructuredGrid.h>
52+
5153
#include "../CpGrid.hpp"
5254
#include "LgrHelpers.hpp"
5355
#include "ParentToChildrenCellGlobalIdHandle.hpp"
@@ -185,6 +187,48 @@ CpGrid::CpGrid(MPIHelper::MPICommunicator comm)
185187
global_id_set_ptr_ = std::make_shared<cpgrid::GlobalIdSet>(*(current_data_->back()));
186188
}
187189

190+
CpGrid::CpGrid(const std::string& filename)
191+
: distributed_data_(),
192+
cell_scatter_gather_interfaces_(new InterfaceMap, FreeInterfaces{}),
193+
point_scatter_gather_interfaces_(new InterfaceMap, FreeInterfaces{}),
194+
global_id_set_ptr_()
195+
{
196+
data_.push_back(std::make_shared<cpgrid::CpGridData>(data_));
197+
current_data_ = &data_;
198+
global_id_set_ptr_ = std::make_shared<cpgrid::GlobalIdSet>(*(current_data_->back()));
199+
200+
using GridPtr = std::unique_ptr<UnstructuredGrid, decltype(&destroy_grid)>;
201+
GridPtr input_grid(read_grid(filename.c_str()), &destroy_grid);
202+
if (!input_grid) {
203+
OPM_THROW(std::runtime_error,
204+
"Failed to read UnstructuredGrid from file: " + filename);
205+
}
206+
207+
current_data_->back()->processUnstructuredGrid(*input_grid);
208+
}
209+
210+
void CpGrid::readUnstructuredGridFile(const std::string& filename)
211+
{
212+
// Only the root rank reads and processes the file; all other ranks keep
213+
// an empty global-view grid, which is correct for a subsequent
214+
// scatterGrid / loadBalance call.
215+
if (current_data_->back()->ccobj_.rank() == 0) {
216+
using GridPtr = std::unique_ptr<UnstructuredGrid, decltype(&destroy_grid)>;
217+
GridPtr input_grid(read_grid(filename.c_str()), &destroy_grid);
218+
if (!input_grid) {
219+
OPM_THROW(std::runtime_error,
220+
"Failed to read UnstructuredGrid from file: " + filename);
221+
}
222+
current_data_->back()->processUnstructuredGrid(*input_grid);
223+
}
224+
225+
// Broadcast the logical Cartesian size so every rank knows the total
226+
// cell count (mirrors what processEclipseFormat does).
227+
current_data_->back()->ccobj_.broadcast(
228+
current_data_->back()->logical_cartesian_size_.data(),
229+
current_data_->back()->logical_cartesian_size_.size(), 0);
230+
}
231+
188232
std::vector<int>
189233
CpGrid::zoltanPartitionWithoutScatter([[maybe_unused]] const std::vector<cpgrid::OpmWellType>* wells,
190234
[[maybe_unused]] const std::unordered_map<std::string, std::set<int>>& possibleFutureConnections,

0 commit comments

Comments
 (0)