mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-26 07:00:17 -05:00
#14127 Add static Completion Type result for grid-only cases
The Completion Type result was registered only as a dynamic result, which requires simulation time steps. Loading only grid geometry (e.g. a ROFF grid) therefore made the result unavailable, since there are no time steps to compute it for. Register and compute a STATIC_NATIVE variant of Completion Type in addition to the existing DYNAMIC_NATIVE one, computed as a single time-step-independent frame. The shared per-frame computation is factored out of computeCompletionTypeForTimeStep so both variants reuse it. The virtual perforation transmissibilities already handle the no-restart case by clamping to a single frame, so the static result can be computed at calc time step 0. The legend/category predicates and the completion-type result clearing are broadened to recognise the static variant so it renders with the same discrete named-category legend as the dynamic one.
This commit is contained in:
1 parent
48de332bfb
commit
5e2156fca1
6 files changed
+165
-14
No files matched your search
@@ -110,6 +110,10 @@ void RiaCompletionTypeCalculationScheduler::clearCompletionTypeResults( const st
|
||||
->results( RiaDefines::PorosityModelType::MATRIX_MODEL )
|
||||
->clearScalarResult( RiaDefines::ResultCatType::DYNAMIC_NATIVE, RiaResultNames::completionTypeResultName() );
|
||||
|
||||
eclipseCase->eclipseCaseData()
|
||||
->results( RiaDefines::PorosityModelType::MATRIX_MODEL )
|
||||
->clearScalarResult( RiaDefines::ResultCatType::STATIC_NATIVE, RiaResultNames::completionTypeResultName() );
|
||||
|
||||
// Delete virtual perforation transmissibilities, as these are the basis for the computation of completion type
|
||||
eclipseCase->eclipseCaseData()->setVirtualPerforationTransmissibilities( nullptr );
|
||||
}
|
||||
|
||||
@@ -1427,7 +1427,8 @@ bool RimEclipseResultDefinition::isTernarySaturationSelected() const
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
bool RimEclipseResultDefinition::isCompletionTypeSelected() const
|
||||
{
|
||||
return ( m_resultType() == RiaDefines::ResultCatType::DYNAMIC_NATIVE && m_resultVariable() == RiaResultNames::completionTypeResultName() );
|
||||
return ( ( m_resultType() == RiaDefines::ResultCatType::DYNAMIC_NATIVE || m_resultType() == RiaDefines::ResultCatType::STATIC_NATIVE ) &&
|
||||
m_resultVariable() == RiaResultNames::completionTypeResultName() );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -1456,8 +1457,7 @@ bool RimEclipseResultDefinition::hasCategoryResult() const
|
||||
!m_eclipseCase->eclipseCaseData()->formationNames().empty() )
|
||||
return true;
|
||||
|
||||
if ( m_resultType() == RiaDefines::ResultCatType::DYNAMIC_NATIVE && resultVariable() == RiaResultNames::completionTypeResultName() )
|
||||
return true;
|
||||
if ( isCompletionTypeSelected() ) return true;
|
||||
|
||||
if ( m_resultType() == RiaDefines::ResultCatType::FLOW_DIAGNOSTICS &&
|
||||
m_resultVariable() == RigFlowDiagDefines::maxFractionTracerResultName() )
|
||||
|
||||
@@ -605,8 +605,7 @@ void RimEclipseResultDefinitionTools::updateCellResultLegend( const RimEclipseRe
|
||||
legendConfigToUpdate->setCategoryItems( categories );
|
||||
}
|
||||
}
|
||||
else if ( resultDefinition->resultType() == RiaDefines::ResultCatType::DYNAMIC_NATIVE &&
|
||||
resultDefinition->resultVariable() == RiaResultNames::completionTypeResultName() )
|
||||
else if ( resultDefinition->isCompletionTypeSelected() )
|
||||
{
|
||||
const std::vector<int>& visibleCategories = cellResultsData->uniqueCellScalarValues( resultDefinition->eclipseResultAddress() );
|
||||
|
||||
@@ -754,7 +753,9 @@ QList<caf::PdmOptionItemInfo> RimEclipseResultDefinitionTools::calcOptionsForVar
|
||||
{
|
||||
if ( s == RiaResultNames::completionTypeResultName() )
|
||||
{
|
||||
if ( results->timeStepDates().empty() ) continue;
|
||||
// The dynamic completion type requires time steps, so hide it when there are none. The static
|
||||
// variant can be computed from grid geometry alone, so it stays available (see issue #14127).
|
||||
if ( resultCatType == RiaDefines::ResultCatType::DYNAMIC_NATIVE && results->timeStepDates().empty() ) continue;
|
||||
}
|
||||
|
||||
if ( RiaResultNames::isPerCellFaceResult( s ) )
|
||||
|
||||
@@ -1031,6 +1031,12 @@ void RigCaseCellResultsData::createPlaceholderResultEntries()
|
||||
findOrCreateScalarResultIndex( RigEclipseResultAddress( RiaDefines::ResultCatType::DYNAMIC_NATIVE,
|
||||
RiaResultNames::completionTypeResultName() ),
|
||||
needsToBeStored );
|
||||
|
||||
// Also register a static variant so completion type can be computed/viewed when only grid
|
||||
// geometry is loaded (no restart / no simulation time steps). See issue #14127.
|
||||
findOrCreateScalarResultIndex( RigEclipseResultAddress( RiaDefines::ResultCatType::STATIC_NATIVE,
|
||||
RiaResultNames::completionTypeResultName() ),
|
||||
needsToBeStored );
|
||||
}
|
||||
|
||||
// Fault results
|
||||
@@ -1517,6 +1523,15 @@ size_t RigCaseCellResultsData::findOrLoadKnownScalarResult( const RigEclipseResu
|
||||
}
|
||||
else if ( resultName == RiaResultNames::completionTypeResultName() )
|
||||
{
|
||||
if ( type == RiaDefines::ResultCatType::STATIC_NATIVE )
|
||||
{
|
||||
// Single time-step-independent frame, computed even when no restart data is loaded.
|
||||
caf::ProgressInfo progressInfo( 1, "Calculate Completion Type Results" );
|
||||
computeStaticCompletionType();
|
||||
progressInfo.incrementProgress();
|
||||
return scalarResultIndex;
|
||||
}
|
||||
|
||||
caf::ProgressInfo progressInfo( maxTimeStepCount(), "Calculate Completion Type Results" );
|
||||
m_cellScalarResults[scalarResultIndex].resize( maxTimeStepCount() );
|
||||
for ( size_t timeStepIdx = 0; timeStepIdx < maxTimeStepCount(); ++timeStepIdx )
|
||||
@@ -1710,6 +1725,12 @@ size_t RigCaseCellResultsData::findOrLoadKnownScalarResultForTimeStep( const Rig
|
||||
computeCompletionTypeForTimeStep( timeStepIndex );
|
||||
return completionTypeScalarResultIndex;
|
||||
}
|
||||
else if ( type == RiaDefines::ResultCatType::STATIC_NATIVE && resultName == RiaResultNames::completionTypeResultName() )
|
||||
{
|
||||
size_t completionTypeScalarResultIndex = findScalarResultIndexFromAddress( resVarAddr );
|
||||
computeStaticCompletionType();
|
||||
return completionTypeScalarResultIndex;
|
||||
}
|
||||
|
||||
size_t scalarResultIndex = findScalarResultIndexFromAddress( resVarAddr );
|
||||
if ( scalarResultIndex == cvf::UNDEFINED_SIZE_T ) return cvf::UNDEFINED_SIZE_T;
|
||||
@@ -2764,24 +2785,62 @@ void RigCaseCellResultsData::computeCompletionTypeForTimeStep( size_t timeStep )
|
||||
m_cellScalarResults[completionTypeResultIndex].resize( maxTimeStepCount() );
|
||||
}
|
||||
|
||||
std::vector<double>& completionTypeResult = m_cellScalarResults[completionTypeResultIndex][timeStep];
|
||||
computeCompletionTypeForFrame( m_cellScalarResults[completionTypeResultIndex][timeStep], timeStep );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Static completion type: a single frame, computed independent of simulation time steps. Used when
|
||||
/// only grid geometry is loaded (no restart). See issue #14127.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RigCaseCellResultsData::computeStaticCompletionType()
|
||||
{
|
||||
size_t completionTypeResultIndex = findScalarResultIndexFromAddress(
|
||||
RigEclipseResultAddress( RiaDefines::ResultCatType::STATIC_NATIVE, RiaResultNames::completionTypeResultName() ) );
|
||||
|
||||
if ( completionTypeResultIndex == cvf::UNDEFINED_SIZE_T ) return;
|
||||
|
||||
if ( m_cellScalarResults[completionTypeResultIndex].empty() )
|
||||
{
|
||||
m_cellScalarResults[completionTypeResultIndex].resize( 1 );
|
||||
}
|
||||
|
||||
// calcTimeStep 0: RigVirtualPerforationTransmissibilities::multipleCompletionsPerEclipseCell clamps to the single
|
||||
// available frame, and RimEclipseCase::computeAndGetVirtualPerforationTransmissibilities builds exactly one frame
|
||||
// when timeStepDates() is empty.
|
||||
computeCompletionTypeForFrame( m_cellScalarResults[completionTypeResultIndex][0], 0 );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Core computation populating a single completion-type frame. calcTimeStep is the time step passed to
|
||||
/// calculateCompletionTypeResult; it is clamped to a valid virtual-perforation frame downstream.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RigCaseCellResultsData::computeCompletionTypeForFrame( std::vector<double>& frameValues, size_t calcTimeStep )
|
||||
{
|
||||
size_t resultValues = m_ownerMainGrid->totalCellCount();
|
||||
|
||||
if ( completionTypeResult.size() == resultValues )
|
||||
if ( frameValues.size() == resultValues )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
completionTypeResult.resize( resultValues );
|
||||
std::fill( completionTypeResult.begin(), completionTypeResult.end(), HUGE_VAL );
|
||||
frameValues.resize( resultValues );
|
||||
std::fill( frameValues.begin(), frameValues.end(), HUGE_VAL );
|
||||
|
||||
RimEclipseCase* eclipseCase = m_ownerCaseData->ownerCase();
|
||||
|
||||
if ( !eclipseCase ) return;
|
||||
|
||||
// If permeabilities are generated by calculations, make sure that generated data is calculated
|
||||
// See RicExportFractureCompletionsImpl::generateCompdatValues()
|
||||
triggerGeneratedPermeabilityCalculations( eclipseCase );
|
||||
|
||||
RimCompletionCellIntersectionCalc::calculateCompletionTypeResult( eclipseCase, frameValues, calcTimeStep );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// If permeabilities are generated by calculations, make sure that generated data is calculated.
|
||||
/// See RicExportFractureCompletionsImpl::generateCompdatValues()
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RigCaseCellResultsData::triggerGeneratedPermeabilityCalculations( RimEclipseCase* eclipseCase )
|
||||
{
|
||||
for ( const QString propertyName : { "PERMX", "PERMY", "PERMZ" } )
|
||||
{
|
||||
for ( auto userCalculation : RimProject::current()->gridCalculationCollection()->calculations() )
|
||||
@@ -2799,8 +2858,6 @@ void RigCaseCellResultsData::computeCompletionTypeForTimeStep( size_t timeStep )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RimCompletionCellIntersectionCalc::calculateCompletionTypeResult( eclipseCase, completionTypeResult, timeStep );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -201,6 +201,9 @@ private:
|
||||
void computeNncCombRiTRANSbyArea();
|
||||
|
||||
void computeCompletionTypeForTimeStep( size_t timeStep );
|
||||
void computeStaticCompletionType();
|
||||
void computeCompletionTypeForFrame( std::vector<double>& frameValues, size_t calcTimeStep );
|
||||
void triggerGeneratedPermeabilityCalculations( RimEclipseCase* eclipseCase );
|
||||
double darchysValue();
|
||||
|
||||
void computeOilVolumes();
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import sys
|
||||
import os
|
||||
import math
|
||||
|
||||
sys.path.insert(1, os.path.join(sys.path[0], "../../"))
|
||||
import rips
|
||||
|
||||
import dataroot
|
||||
|
||||
|
||||
def test_static_completion_type_on_roff_grid(rips_instance, initialize_test):
|
||||
# Load a ROFF grid only - no restart and therefore no simulation time steps.
|
||||
# The "Completion Type" result used to be available only as a DYNAMIC result,
|
||||
# which requires time steps, so it could not be computed for a grid-only case.
|
||||
# This test verifies the STATIC_NATIVE variant added for issue #14127.
|
||||
#
|
||||
# Note: loading this case logs an error about missing PERMX/PERMY/PERMZ. That
|
||||
# comes from the (unrelated) fracture completion path and is harmless here - the
|
||||
# grid has no permeability, and the well has no fractures. Perforation and well
|
||||
# path intersections do not depend on it.
|
||||
case_path = dataroot.PATH + "/reek/reek_box_grid_w_props.roff"
|
||||
case = rips_instance.project.load_case(path=case_path)
|
||||
|
||||
# This is the scenario from #14127: a grid with no time steps.
|
||||
assert len(case.time_steps()) == 0
|
||||
|
||||
grid = case.grid(index=0)
|
||||
dims = grid.dimensions()
|
||||
total_cell_count = dims.i * dims.j * dims.k
|
||||
|
||||
centers = grid.cell_centers()
|
||||
assert len(centers) == total_cell_count
|
||||
|
||||
# Build a vertical well straight down a central column using real cell geometry,
|
||||
# so the well path is guaranteed to intersect cells in this case.
|
||||
i0 = dims.i // 2
|
||||
j0 = dims.j // 2
|
||||
|
||||
well_path_coll = rips_instance.project.well_path_collection()
|
||||
well_path = well_path_coll.add_new_object(rips.ModeledWellPath)
|
||||
well_path.name = "static_completion_well"
|
||||
well_path.update()
|
||||
|
||||
geometry = well_path.well_path_geometry()
|
||||
for k in range(1, dims.k + 1): # IJK is 1-based
|
||||
index = grid.property_data_index_from_ijk(i0, j0, k)
|
||||
center = centers[index]
|
||||
# cell_centers() returns positive depth; append_well_target negates z internally.
|
||||
geometry.append_well_target([center.x, center.y, center.z], absolute=True)
|
||||
geometry.update()
|
||||
|
||||
# Perforate the whole trajectory so every intersected cell is covered.
|
||||
trajectory = well_path.trajectory_properties(resampling_interval=5.0)
|
||||
measured_depths = trajectory["measured_depth"]
|
||||
assert len(measured_depths) > 2
|
||||
well_path.append_perforation_interval(
|
||||
start_md=measured_depths[0],
|
||||
end_md=measured_depths[-1],
|
||||
diameter=0.2,
|
||||
skin_factor=0.1,
|
||||
)
|
||||
|
||||
# Extract the STATIC completion type result. This must succeed even though the
|
||||
# case has no time steps.
|
||||
completion_type = case.grid_property("STATIC_NATIVE", "Completion Type", 0)
|
||||
assert len(completion_type) == total_cell_count
|
||||
|
||||
# Cells not touched by the well are undefined (HUGE_VAL -> inf), so only the
|
||||
# handful of cells along the well are expected to carry a value.
|
||||
defined_cells = [v for v in completion_type if math.isfinite(v)]
|
||||
assert len(defined_cells) > 0, (
|
||||
"Static completion type produced no intersected cells"
|
||||
)
|
||||
|
||||
# RiaDefines::WellPathComponentType codes: WELL_PATH = 0, PERFORATION_INTERVAL = 1,
|
||||
# FISHBONES = 2, FRACTURE = 3. Every defined cell must be a valid code.
|
||||
valid_codes = {0.0, 1.0, 2.0, 3.0}
|
||||
assert all(v in valid_codes for v in defined_cells)
|
||||
|
||||
# The full-length perforation must override the well path cells, so every defined
|
||||
# cell should be reported as a perforation.
|
||||
perforation_code = 1.0
|
||||
perforated_cells = [v for v in defined_cells if v == perforation_code]
|
||||
assert len(perforated_cells) == len(defined_cells), (
|
||||
"Expected all intersected cells to be reported as perforations"
|
||||
)
|
||||
Reference in new issue
Block a user