Files
ResInsight/ApplicationLibCode/UnitTests/RigMainGridFaults-Test.cpp
T
Magne Sjaastad 4ab0938c2d #14806 Faults: Compute unnamed fault faces in parallel and only from positive faces (#14836)
* #14806 Faults: Compute unnamed fault faces in parallel and only from positive faces

- A negative I or J face is the positive face of the neighbor cell with a lower
  index, and is already handled when that neighbor is processed. Only visit the
  positive faces.
- Each cell only writes the fault index of its own positive faces and the
  opposite faces of its neighbors, so process the cells in parallel. The
  thread results are appended in thread order, so the fault faces keep the
  same order as before.

* #14806 Faults: Add unit tests for parallel unnamed fault face computation

Build a small in-memory grid (no file, no view) with geometric faults at
every I interface, and verify that calculateFaults() finds all unnamed
fault faces, keeps them ordered by native cell index (the per-thread
results are appended in thread order), and separates faces touching an
inactive cell into the dedicated inactive fault face list.
2026-10-03 09:16:23 +02:00

147 lines
6.5 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026- Equinor ASA
//
// ResInsight 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.
//
// ResInsight 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 at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "gtest/gtest.h"
#include "RiaResultNames.h"
#include "RigActiveCellInfo.h"
#include "RigEclipseCaseData.h"
#include "RigFault.h"
#include "RigMainGrid.h"
#include "RigReservoirBuilder.h"
namespace
{
//--------------------------------------------------------------------------------------------------
/// Build a 1 x 1 x cellCount row of cells along I (no file, no view), and shift every other cell
/// down in Z so that every I interface becomes a geometric (unnamed) fault.
//--------------------------------------------------------------------------------------------------
cvf::ref<RigEclipseCaseData> buildStaggeredRowGridCase( int cellCount )
{
RigReservoirBuilder builder;
builder.setIJKCount( cvf::Vec3st( cellCount, 1, 1 ) );
builder.setWorldCoordinates( cvf::Vec3d( 0.0, 0.0, 0.0 ), cvf::Vec3d( cellCount, 1.0, -1.0 ) );
cvf::ref<RigEclipseCaseData> eclipseCase = new RigEclipseCaseData( nullptr );
builder.createGridsAndCells( eclipseCase.p() );
eclipseCase->mainGrid()->computeCachedData();
std::vector<cvf::Vec3d>& nodes = eclipseCase->mainGrid()->nodes();
for ( int i = 1; i < cellCount; i += 2 )
{
for ( int corner = 0; corner < 8; corner++ )
{
nodes[static_cast<size_t>( i ) * 8 + corner].z() -= 10.0;
}
}
return eclipseCase;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void findUnnamedFaults( RigMainGrid* mainGrid, const RigFault*& unnamedFault, const RigFault*& unnamedFaultInactive )
{
unnamedFault = nullptr;
unnamedFaultInactive = nullptr;
for ( const auto& fault : mainGrid->faults() )
{
if ( fault->name() == RiaResultNames::undefinedGridFaultName() ) unnamedFault = fault.p();
if ( fault->name() == RiaResultNames::undefinedGridFaultWithInactiveName() ) unnamedFaultInactive = fault.p();
}
}
} // namespace
//--------------------------------------------------------------------------------------------------
/// Every I interface in the staggered grid is a geometric fault. The unnamed fault faces are computed
/// in parallel, one cell range per thread, and the per-thread results are appended in thread order.
/// Verify that all interfaces are found, and that the resulting faces stay ordered by cell index.
//--------------------------------------------------------------------------------------------------
TEST( RigMainGridFaults, UnnamedFaultFacesAreFoundInCellOrder )
{
const int cellCount = 10;
cvf::ref<RigEclipseCaseData> eclipseCase = buildStaggeredRowGridCase( cellCount );
RigMainGrid* mainGrid = eclipseCase->mainGrid();
RigActiveCellInfo* activeCellInfo = eclipseCase->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL );
mainGrid->calculateFaults( activeCellInfo );
const RigFault* unnamedFault = nullptr;
const RigFault* unnamedFaultInactive = nullptr;
findUnnamedFaults( mainGrid, unnamedFault, unnamedFaultInactive );
ASSERT_NE( unnamedFault, nullptr );
ASSERT_NE( unnamedFaultInactive, nullptr );
EXPECT_TRUE( unnamedFaultInactive->faultFaces().empty() );
const std::vector<RigFault::FaultFace>& faces = unnamedFault->faultFaces();
ASSERT_EQ( static_cast<size_t>( cellCount - 1 ), faces.size() );
for ( size_t idx = 0; idx < faces.size(); idx++ )
{
EXPECT_EQ( idx, faces[idx].m_nativeReservoirCellIndex ) << "Face " << idx << " is out of cell-index order";
EXPECT_EQ( idx + 1, faces[idx].m_oppositeReservoirCellIndex );
EXPECT_EQ( cvf::StructGridInterface::POS_I, faces[idx].m_nativeFace );
}
}
//--------------------------------------------------------------------------------------------------
/// Fault faces with an inactive native or neighbor cell are reported in a separate fault face list.
//--------------------------------------------------------------------------------------------------
TEST( RigMainGridFaults, UnnamedFaultFacesWithInactiveCellGoToSeparateList )
{
const int cellCount = 4;
cvf::ref<RigEclipseCaseData> eclipseCase = buildStaggeredRowGridCase( cellCount );
RigMainGrid* mainGrid = eclipseCase->mainGrid();
// Build an independent active cell info with cell 2 left inactive (its entry is never assigned, so it stays
// at the default "undefined" value). The faces (1,2) and (2,3) should then be reported as inactive fault faces.
cvf::ref<RigActiveCellInfo> activeCellInfo = new RigActiveCellInfo;
activeCellInfo->setReservoirCellCount( static_cast<size_t>( cellCount ) );
size_t activeIdx = 0;
for ( int i = 0; i < cellCount; i++ )
{
if ( i == 2 ) continue;
activeCellInfo->setCellResultIndex( ReservoirCellIndex( static_cast<size_t>( i ) ), ActiveCellIndex( activeIdx++ ) );
}
mainGrid->calculateFaults( activeCellInfo.p() );
const RigFault* unnamedFault = nullptr;
const RigFault* unnamedFaultInactive = nullptr;
findUnnamedFaults( mainGrid, unnamedFault, unnamedFaultInactive );
ASSERT_NE( unnamedFault, nullptr );
ASSERT_NE( unnamedFaultInactive, nullptr );
ASSERT_EQ( 1u, unnamedFault->faultFaces().size() );
EXPECT_EQ( 0u, unnamedFault->faultFaces()[0].m_nativeReservoirCellIndex );
EXPECT_EQ( 1u, unnamedFault->faultFaces()[0].m_oppositeReservoirCellIndex );
ASSERT_EQ( 2u, unnamedFaultInactive->faultFaces().size() );
EXPECT_EQ( 1u, unnamedFaultInactive->faultFaces()[0].m_nativeReservoirCellIndex );
EXPECT_EQ( 2u, unnamedFaultInactive->faultFaces()[0].m_oppositeReservoirCellIndex );
EXPECT_EQ( 2u, unnamedFaultInactive->faultFaces()[1].m_nativeReservoirCellIndex );
EXPECT_EQ( 3u, unnamedFaultInactive->faultFaces()[1].m_oppositeReservoirCellIndex );
}