Skip to content

Commit 6267dca

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 0ff52dd commit 6267dca

10 files changed

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

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)