Skip to content

Commit 3548253

Browse files
committed
ENH: add a function in Grid that can merge two grids
1 parent 2520061 commit 3548253

File tree

7 files changed

+1477
-0
lines changed

7 files changed

+1477
-0
lines changed

src/lib/include/xtgeo/grid3d.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ init(py::module &m)
207207
.def("extract_onelayer_grid", &Grid::extract_onelayer_grid,
208208
"Get a one-layer grid - returns coordv, zcornsv, and actnumsv arrays ")
209209
.def("get_bounding_box", &Grid::get_bounding_box, "Get bounding box of full grid")
210+
.def("fix_zero_pillars", &Grid::fix_zero_pillars, "Fix zero pillars in the grid.")
210211

211212
// free form functions in C++ to members in Python
212213
.def("get_cell_volumes", &get_cell_volumes, "Compute the bulk volume of cell.")

src/lib/include/xtgeo/types.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,9 @@ class Grid
534534
return { m_min_point, m_max_point };
535535
}
536536

537+
// Fix zero pillars to ensure proper Z separation
538+
void fix_zero_pillars() const;
539+
537540
private:
538541
const size_t m_ncol;
539542
const size_t m_nrow;

src/lib/src/grid3d/grid.cpp

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1404,5 +1404,110 @@ convert_to_hybrid_grid(const Grid &grid_cpp,
14041404
logger.debug("Hybrid grid conversion completed");
14051405
return std::make_tuple(zcorn_hyb, actnum_hyb);
14061406
}
1407+
1408+
// Fix zero in Z value pillars to avoid tolerance violations and assure "verticality".
1409+
void
1410+
Grid::fix_zero_pillars() const
1411+
{
1412+
auto &logger = xtgeo::logging::LoggerManager::get("Grid::fix_zero_pillars");
1413+
logger.debug("Fixing zero pillars for grid ({}, {}, {})", m_ncol, m_nrow, m_nlay);
1414+
1415+
constexpr double MIN_Z_SEPARATION = 1e-5; // Must be > TOLERANCE (1e-6)
1416+
1417+
// Get mutable access to the coordinate arrays
1418+
auto coordsv_mut =
1419+
const_cast<py::array_t<double> &>(m_coordsv).mutable_unchecked<3>();
1420+
auto zcornsv_mut =
1421+
const_cast<py::array_t<float> &>(m_zcornsv).mutable_unchecked<4>();
1422+
1423+
// First pass: count zero pillars and compute average X,Y,Z from non-zero pillars
1424+
size_t zero_count = 0;
1425+
double sum_x = 0.0;
1426+
double sum_y = 0.0;
1427+
double sum_z_top = 0.0;
1428+
double sum_z_bot = 0.0;
1429+
size_t non_zero_count = 0;
1430+
1431+
for (size_t i = 0; i <= m_ncol; i++) {
1432+
for (size_t j = 0; j <= m_nrow; j++) {
1433+
bool is_zero = true;
1434+
for (size_t coord = 0; coord < 6; coord++) {
1435+
if (std::abs(coordsv_mut(i, j, coord)) > numerics::EPSILON) {
1436+
is_zero = false;
1437+
break;
1438+
}
1439+
}
1440+
1441+
if (is_zero) {
1442+
zero_count++;
1443+
} else {
1444+
// Accumulate X,Y,Z from non-zero pillars
1445+
sum_x += coordsv_mut(i, j, 0); // x_top
1446+
sum_y += coordsv_mut(i, j, 1); // y_top
1447+
sum_z_top += coordsv_mut(i, j, 2); // z_top
1448+
sum_z_bot += coordsv_mut(i, j, 5); // z_bot
1449+
non_zero_count++;
1450+
}
1451+
}
1452+
}
1453+
1454+
if (zero_count == 0) {
1455+
return; // No zero pillars to fix
1456+
}
1457+
1458+
// Compute average X,Y,Z from non-zero pillars
1459+
double avg_x = (non_zero_count > 0) ? (sum_x / non_zero_count) : 0.0;
1460+
double avg_y = (non_zero_count > 0) ? (sum_y / non_zero_count) : 0.0;
1461+
double avg_z_top = (non_zero_count > 0) ? (sum_z_top / non_zero_count) : 0.0;
1462+
double avg_z_bot =
1463+
(non_zero_count > 0) ? (sum_z_bot / non_zero_count) : MIN_Z_SEPARATION;
1464+
1465+
// Ensure minimum separation between top and bottom
1466+
if (std::abs(avg_z_bot - avg_z_top) < MIN_Z_SEPARATION) {
1467+
avg_z_bot = avg_z_top + MIN_Z_SEPARATION;
1468+
}
1469+
1470+
logger.debug(
1471+
"Found {} zero pillars, avg coordinates from grid: ({}, {}, z_top={}, z_bot={})",
1472+
zero_count, avg_x, avg_y, avg_z_top, avg_z_bot);
1473+
1474+
// Second pass: fix zero pillars with average X,Y and proper Z separation
1475+
for (size_t i = 0; i <= m_ncol; i++) {
1476+
for (size_t j = 0; j <= m_nrow; j++) {
1477+
// Check if pillar is all zeros
1478+
bool is_zero = true;
1479+
for (size_t coord = 0; coord < 6; coord++) {
1480+
if (std::abs(coordsv_mut(i, j, coord)) > numerics::EPSILON) {
1481+
is_zero = false;
1482+
break;
1483+
}
1484+
}
1485+
1486+
if (is_zero) {
1487+
// Fix coordsv: set average X,Y,Z with proper separation
1488+
coordsv_mut(i, j, 0) = avg_x; // x_top
1489+
coordsv_mut(i, j, 1) = avg_y; // y_top
1490+
coordsv_mut(i, j, 2) = avg_z_top; // z_top
1491+
coordsv_mut(i, j, 3) = avg_x; // x_bot (vertical pillar)
1492+
coordsv_mut(i, j, 4) = avg_y; // y_bot (vertical pillar)
1493+
coordsv_mut(i, j, 5) = avg_z_bot; // z_bot
1494+
1495+
// Fix zcornsv: set progressive depth for each layer
1496+
// Use linear interpolation between avg_z_top and avg_z_bot
1497+
double layer_thickness =
1498+
(avg_z_bot - avg_z_top) / static_cast<double>(m_nlay);
1499+
for (size_t k = 0; k <= m_nlay; k++) {
1500+
float depth = static_cast<float>(avg_z_top + k * layer_thickness);
1501+
for (size_t corner = 0; corner < 4; corner++) {
1502+
zcornsv_mut(i, j, k, corner) = depth;
1503+
}
1504+
}
1505+
}
1506+
}
1507+
}
1508+
1509+
logger.debug("Fixed {} zero pillars with average coordinates", zero_count);
1510+
}
1511+
14071512
//===================
14081513
} // xtgeo::grid3d

src/xtgeo/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ def _xprint(msg):
144144
grid_from_file,
145145
grid_from_roxar,
146146
grid_from_surfaces,
147+
grid_merge,
147148
)
148149
from xtgeo.xyz.polygons import (
149150
polygons_from_file,
@@ -213,6 +214,7 @@ def _xprint(msg):
213214
"grid_from_file",
214215
"grid_from_roxar",
215216
"grid_from_surfaces",
217+
"grid_merge",
216218
"grid_properties",
217219
"grid_properties",
218220
"grid_property",

0 commit comments

Comments
 (0)