#11689 Well Target Candidates: generate on well target clusters for single case.

This commit is contained in:
Kristian Bendiksen
2024-09-11 14:22:35 +02:00
parent 13cf450831
commit b760fcebe2
14 changed files with 1265 additions and 8 deletions

View File

@@ -96,6 +96,7 @@ set(SOURCE_GROUP_HEADER_FILES
${CMAKE_CURRENT_LIST_DIR}/RicImportWellLogCsvFileFeature.h ${CMAKE_CURRENT_LIST_DIR}/RicImportWellLogCsvFileFeature.h
${CMAKE_CURRENT_LIST_DIR}/RicNewViewForGridEnsembleFeature.h ${CMAKE_CURRENT_LIST_DIR}/RicNewViewForGridEnsembleFeature.h
${CMAKE_CURRENT_LIST_DIR}/RicNewCustomVfpPlotFeature.h ${CMAKE_CURRENT_LIST_DIR}/RicNewCustomVfpPlotFeature.h
${CMAKE_CURRENT_LIST_DIR}/RicNewWellTargetCandidatesGeneratorFeature.h
) )
set(SOURCE_GROUP_SOURCE_FILES set(SOURCE_GROUP_SOURCE_FILES
@@ -196,6 +197,7 @@ set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RicImportWellLogOsduFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicImportWellLogOsduFeature.cpp
${CMAKE_CURRENT_LIST_DIR}/RicNewViewForGridEnsembleFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicNewViewForGridEnsembleFeature.cpp
${CMAKE_CURRENT_LIST_DIR}/RicNewCustomVfpPlotFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicNewCustomVfpPlotFeature.cpp
${CMAKE_CURRENT_LIST_DIR}/RicNewWellTargetCandidatesGeneratorFeature.cpp
) )
if(RESINSIGHT_USE_QT_CHARTS) if(RESINSIGHT_USE_QT_CHARTS)

View File

@@ -0,0 +1,49 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2024 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 "RicNewWellTargetCandidatesGeneratorFeature.h"
#include "RimEclipseCaseEnsemble.h"
#include "RimWellTargetCandidatesGenerator.h"
#include "cafSelectionManagerTools.h"
#include <QAction>
CAF_CMD_SOURCE_INIT( RicNewWellTargetCandidatesGeneratorFeature, "RicNewWellTargetCandidatesGeneratorFeature" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicNewWellTargetCandidatesGeneratorFeature::onActionTriggered( bool isChecked )
{
auto ensembles = caf::selectedObjectsByTypeStrict<RimEclipseCaseEnsemble*>();
if ( ensembles.empty() ) return;
auto ensemble = ensembles.front();
ensemble->addWellTargetsGenerator( new RimWellTargetCandidatesGenerator() );
ensemble->updateConnectedEditors();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicNewWellTargetCandidatesGeneratorFeature::setupActionLook( QAction* actionToSetup )
{
actionToSetup->setText( "Create Well Target Candidates Generator" );
}

View File

@@ -0,0 +1,33 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2024 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.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "cafCmdFeature.h"
//==================================================================================================
///
//==================================================================================================
class RicNewWellTargetCandidatesGeneratorFeature : public caf::CmdFeature
{
CAF_CMD_HEADER_INIT;
private:
void onActionTriggered( bool isChecked ) override;
void setupActionLook( QAction* actionToSetup ) override;
};

View File

@@ -134,6 +134,7 @@ set(SOURCE_GROUP_HEADER_FILES
${CMAKE_CURRENT_LIST_DIR}/RimEclipseViewCollection.h ${CMAKE_CURRENT_LIST_DIR}/RimEclipseViewCollection.h
${CMAKE_CURRENT_LIST_DIR}/RimEclipseCaseEnsemble.h ${CMAKE_CURRENT_LIST_DIR}/RimEclipseCaseEnsemble.h
${CMAKE_CURRENT_LIST_DIR}/RimCameraPosition.h ${CMAKE_CURRENT_LIST_DIR}/RimCameraPosition.h
${CMAKE_CURRENT_LIST_DIR}/RimWellTargetCandidatesGenerator.h
) )
set(SOURCE_GROUP_SOURCE_FILES set(SOURCE_GROUP_SOURCE_FILES
@@ -268,6 +269,7 @@ set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RimEclipseViewCollection.cpp ${CMAKE_CURRENT_LIST_DIR}/RimEclipseViewCollection.cpp
${CMAKE_CURRENT_LIST_DIR}/RimEclipseCaseEnsemble.cpp ${CMAKE_CURRENT_LIST_DIR}/RimEclipseCaseEnsemble.cpp
${CMAKE_CURRENT_LIST_DIR}/RimCameraPosition.cpp ${CMAKE_CURRENT_LIST_DIR}/RimCameraPosition.cpp
${CMAKE_CURRENT_LIST_DIR}/RimWellTargetCandidatesGenerator.cpp
) )
if(RESINSIGHT_USE_QT_CHARTS) if(RESINSIGHT_USE_QT_CHARTS)

View File

@@ -22,6 +22,7 @@
#include "RimEclipseCase.h" #include "RimEclipseCase.h"
#include "RimEclipseView.h" #include "RimEclipseView.h"
#include "RimEclipseViewCollection.h" #include "RimEclipseViewCollection.h"
#include "RimWellTargetCandidatesGenerator.h"
#include "cafCmdFeatureMenuBuilder.h" #include "cafCmdFeatureMenuBuilder.h"
#include "cafPdmFieldScriptingCapability.h" #include "cafPdmFieldScriptingCapability.h"
@@ -50,6 +51,8 @@ RimEclipseCaseEnsemble::RimEclipseCaseEnsemble()
CAF_PDM_InitFieldNoDefault( &m_viewCollection, "ViewCollection", "Views" ); CAF_PDM_InitFieldNoDefault( &m_viewCollection, "ViewCollection", "Views" );
m_viewCollection = new RimEclipseViewCollection; m_viewCollection = new RimEclipseViewCollection;
CAF_PDM_InitFieldNoDefault( &m_wellTargetGenerators, "WellTargetGenerators", "Well Target Candidates Generators" );
setDeletable( true ); setDeletable( true );
} }
@@ -169,6 +172,7 @@ void RimEclipseCaseEnsemble::fieldChangedByUi( const caf::PdmFieldHandle* change
void RimEclipseCaseEnsemble::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const void RimEclipseCaseEnsemble::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const
{ {
menuBuilder << "RicNewViewForGridEnsembleFeature"; menuBuilder << "RicNewViewForGridEnsembleFeature";
menuBuilder << "RicNewWellTargetCandidatesGeneratorFeature";
} }
//-------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------
@@ -178,3 +182,11 @@ RimEclipseViewCollection* RimEclipseCaseEnsemble::viewCollection() const
{ {
return m_viewCollection; return m_viewCollection;
} }
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimEclipseCaseEnsemble::addWellTargetsGenerator( RimWellTargetCandidatesGenerator* generator )
{
m_wellTargetGenerators.push_back( generator );
}

View File

@@ -29,6 +29,7 @@ class RimCaseCollection;
class RimEclipseCase; class RimEclipseCase;
class RimEclipseView; class RimEclipseView;
class RimEclipseViewCollection; class RimEclipseViewCollection;
class RimWellTargetCandidatesGenerator;
//================================================================================================== //==================================================================================================
// //
@@ -54,14 +55,17 @@ public:
RimEclipseViewCollection* viewCollection() const; RimEclipseViewCollection* viewCollection() const;
void addWellTargetsGenerator( RimWellTargetCandidatesGenerator* generator );
protected: protected:
QList<caf::PdmOptionItemInfo> calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) override; QList<caf::PdmOptionItemInfo> calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) override;
void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override; void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override;
void appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const override; void appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const override;
private: private:
caf::PdmField<int> m_groupId; caf::PdmField<int> m_groupId;
caf::PdmChildField<RimCaseCollection*> m_caseCollection; caf::PdmChildField<RimCaseCollection*> m_caseCollection;
caf::PdmChildField<RimEclipseViewCollection*> m_viewCollection; caf::PdmChildField<RimEclipseViewCollection*> m_viewCollection;
caf::PdmPtrField<RimEclipseCase*> m_selectedCase; caf::PdmChildArrayField<RimWellTargetCandidatesGenerator*> m_wellTargetGenerators;
caf::PdmPtrField<RimEclipseCase*> m_selectedCase;
}; };

View File

@@ -0,0 +1,296 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2024- 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 "RimWellTargetCandidatesGenerator.h"
#include "RiaLogging.h"
#include "RiaPorosityModel.h"
#include "RiaResultNames.h"
#include "RigCaseCellResultsData.h"
#include "RigEclipseResultAddress.h"
#include "RigWellTargetCandidatesGenerator.h"
#include "RimEclipseCase.h"
#include "RimEclipseCaseEnsemble.h"
#include "RimEclipseView.h"
#include "RimProject.h"
#include "RimTools.h"
#include "cafPdmUiDoubleSliderEditor.h"
#include "cafPdmUiSliderTools.h"
#include "cvfMath.h"
#include <cmath>
#include <limits>
CAF_PDM_SOURCE_INIT( RimWellTargetCandidatesGenerator, "RimWellTargetCandidatesGenerator" );
namespace caf
{
template <>
void caf::AppEnum<RigWellTargetCandidatesGenerator::VolumeType>::setUp()
{
addItem( RigWellTargetCandidatesGenerator::VolumeType::OIL, "OIL", "Oil" );
addItem( RigWellTargetCandidatesGenerator::VolumeType::GAS, "GAS", "Gas" );
addItem( RigWellTargetCandidatesGenerator::VolumeType::HYDROCARBON, "HYDROCARBON", "Hydrocarbon" );
setDefault( RigWellTargetCandidatesGenerator::VolumeType::OIL );
}
template <>
void caf::AppEnum<RigWellTargetCandidatesGenerator::VolumeResultType>::setUp()
{
addItem( RigWellTargetCandidatesGenerator::VolumeResultType::MOBILE, "MOBILE", "Mobile" );
addItem( RigWellTargetCandidatesGenerator::VolumeResultType::TOTAL, "TOTAL", "Total" );
setDefault( RigWellTargetCandidatesGenerator::VolumeResultType::TOTAL );
}
template <>
void caf::AppEnum<RigWellTargetCandidatesGenerator::VolumesType>::setUp()
{
addItem( RigWellTargetCandidatesGenerator::VolumesType::RESERVOIR_VOLUMES, "RESERVOIR", "Reservoir Volumes (RFIPOIL, RFIPGAS)" );
addItem( RigWellTargetCandidatesGenerator::VolumesType::SURFACE_VOLUMES, "SURFACE", "Surface Volumes (SFIPOIL, SFIPGAS)" );
addItem( RigWellTargetCandidatesGenerator::VolumesType::COMPUTED_VOLUMES, "COMPUTED", "Computed Volumes (PORV*SOIL, PORV*SGAS)" );
setDefault( RigWellTargetCandidatesGenerator::VolumesType::COMPUTED_VOLUMES );
}
} // End namespace caf
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimWellTargetCandidatesGenerator::RimWellTargetCandidatesGenerator()
{
CAF_PDM_InitObject( "Well Target Candidates Generator" );
CAF_PDM_InitField( &m_timeStep, "TimeStep", 0, "Time Step" );
CAF_PDM_InitFieldNoDefault( &m_volumeType, "VolumeType", "Volume" );
CAF_PDM_InitFieldNoDefault( &m_volumeResultType, "VolumeResultType", "Result" );
CAF_PDM_InitFieldNoDefault( &m_volumesType, "VolumesType", "" );
CAF_PDM_InitField( &m_volume, "Volume", 0.0, "Volume" );
m_volume.uiCapability()->setUiEditorTypeName( caf::PdmUiDoubleSliderEditor::uiEditorTypeName() );
CAF_PDM_InitField( &m_pressure, "Pressure", 0.0, "Pressure" );
m_pressure.uiCapability()->setUiEditorTypeName( caf::PdmUiDoubleSliderEditor::uiEditorTypeName() );
CAF_PDM_InitField( &m_permeability, "Permeability", 0.0, "Permeability" );
m_permeability.uiCapability()->setUiEditorTypeName( caf::PdmUiDoubleSliderEditor::uiEditorTypeName() );
CAF_PDM_InitField( &m_transmissibility, "Transmissibility", 0.0, "Transmissibility" );
m_transmissibility.uiCapability()->setUiEditorTypeName( caf::PdmUiDoubleSliderEditor::uiEditorTypeName() );
CAF_PDM_InitField( &m_maxIterations, "Iterations", 10000, "Max Iterations" );
CAF_PDM_InitField( &m_maxClusters, "MaxClusters", 5, "Max Clusters" );
m_minimumVolume = cvf::UNDEFINED_DOUBLE;
m_maximumVolume = cvf::UNDEFINED_DOUBLE;
m_minimumPressure = cvf::UNDEFINED_DOUBLE;
m_maximumPressure = cvf::UNDEFINED_DOUBLE;
m_minimumPermeability = cvf::UNDEFINED_DOUBLE;
m_maximumPermeability = cvf::UNDEFINED_DOUBLE;
m_minimumTransmissibility = cvf::UNDEFINED_DOUBLE;
m_maximumTransmissibility = cvf::UNDEFINED_DOUBLE;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimWellTargetCandidatesGenerator::~RimWellTargetCandidatesGenerator()
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellTargetCandidatesGenerator::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue )
{
updateAllBoundaries();
generateCandidates();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QList<caf::PdmOptionItemInfo> RimWellTargetCandidatesGenerator::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions )
{
QList<caf::PdmOptionItemInfo> options;
if ( fieldNeedingOptions == &m_timeStep )
{
auto ensemble = firstAncestorOrThisOfType<RimEclipseCaseEnsemble>();
if ( ensemble && !ensemble->cases().empty() )
{
RimEclipseCase* eclipseCase = ensemble->cases().front();
RimTools::timeStepsForCase( eclipseCase, &options );
}
}
return options;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellTargetCandidatesGenerator::updateAllBoundaries()
{
auto ensemble = firstAncestorOrThisOfType<RimEclipseCaseEnsemble>();
if ( !ensemble ) return;
if ( ensemble->cases().empty() ) return;
RimEclipseCase* eclipseCase = ensemble->cases().front();
int timeStepIdx = m_timeStep();
auto updateBoundaryValues =
[]( auto resultsData, const std::vector<RigEclipseResultAddress>& addresses, size_t timeStepIdx ) -> std::pair<double, double>
{
double globalMin = std::numeric_limits<double>::max();
double globalMax = -std::numeric_limits<double>::max();
for ( auto address : addresses )
{
double currentMinimum;
double currentMaximum;
resultsData->ensureKnownResultLoaded( address );
resultsData->minMaxCellScalarValues( address, timeStepIdx, currentMinimum, currentMaximum );
globalMin = std::min( globalMin, currentMinimum );
globalMax = std::max( globalMax, currentMaximum );
}
return { globalMin, globalMax };
};
auto resultsData = eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL );
std::tie( m_minimumPressure, m_maximumPressure ) =
updateBoundaryValues( resultsData, { RigEclipseResultAddress( RiaDefines::ResultCatType::DYNAMIC_NATIVE, "PRESSURE" ) }, timeStepIdx );
std::vector<double> volume =
RigWellTargetCandidatesGenerator::getVolumeVector( *resultsData, m_volumeType(), m_volumesType(), m_volumeResultType(), timeStepIdx );
if ( !volume.empty() )
{
const auto [min, max] = std::minmax_element( volume.begin(), volume.end() );
m_minimumVolume = *min;
m_maximumVolume = *max;
}
std::tie( m_minimumPermeability, m_maximumPermeability ) =
updateBoundaryValues( resultsData,
{ RigEclipseResultAddress( RiaDefines::ResultCatType::STATIC_NATIVE, "PERMX" ),
RigEclipseResultAddress( RiaDefines::ResultCatType::STATIC_NATIVE, "PERMY" ),
RigEclipseResultAddress( RiaDefines::ResultCatType::STATIC_NATIVE, "PERMZ" ) },
0 );
std::tie( m_minimumTransmissibility, m_maximumTransmissibility ) =
updateBoundaryValues( resultsData,
{ RigEclipseResultAddress( RiaDefines::ResultCatType::STATIC_NATIVE, "TRANX" ),
RigEclipseResultAddress( RiaDefines::ResultCatType::STATIC_NATIVE, "TRANY" ),
RigEclipseResultAddress( RiaDefines::ResultCatType::STATIC_NATIVE, "TRANZ" ) },
0 );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellTargetCandidatesGenerator::defineEditorAttribute( const caf::PdmFieldHandle* field,
QString uiConfigName,
caf::PdmUiEditorAttribute* attribute )
{
if ( field == &m_volume && m_minimumVolume != cvf::UNDEFINED_DOUBLE && m_maximumVolume != cvf::UNDEFINED_DOUBLE )
{
if ( auto doubleAttributes = dynamic_cast<caf::PdmUiDoubleSliderEditorAttribute*>( attribute ) )
{
doubleAttributes->m_minimum = m_minimumVolume;
doubleAttributes->m_maximum = m_maximumVolume;
doubleAttributes->m_decimals = 3;
}
}
if ( field == &m_pressure && m_minimumPressure != cvf::UNDEFINED_DOUBLE && m_maximumPressure != cvf::UNDEFINED_DOUBLE )
{
if ( auto doubleAttributes = dynamic_cast<caf::PdmUiDoubleSliderEditorAttribute*>( attribute ) )
{
doubleAttributes->m_minimum = m_minimumPressure;
doubleAttributes->m_maximum = m_maximumPressure;
doubleAttributes->m_decimals = 3;
}
}
if ( field == &m_permeability && m_minimumPermeability != cvf::UNDEFINED_DOUBLE && m_maximumPermeability != cvf::UNDEFINED_DOUBLE )
{
if ( auto doubleAttributes = dynamic_cast<caf::PdmUiDoubleSliderEditorAttribute*>( attribute ) )
{
doubleAttributes->m_minimum = m_minimumPermeability;
doubleAttributes->m_maximum = m_maximumPermeability;
doubleAttributes->m_decimals = 3;
}
}
if ( field == &m_transmissibility && m_minimumTransmissibility != cvf::UNDEFINED_DOUBLE && m_maximumTransmissibility != cvf::UNDEFINED_DOUBLE )
{
if ( auto doubleAttributes = dynamic_cast<caf::PdmUiDoubleSliderEditorAttribute*>( attribute ) )
{
doubleAttributes->m_minimum = m_minimumTransmissibility;
doubleAttributes->m_maximum = m_maximumTransmissibility;
doubleAttributes->m_decimals = 3;
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellTargetCandidatesGenerator::generateCandidates()
{
auto ensemble = firstAncestorOrThisOfType<RimEclipseCaseEnsemble>();
if ( !ensemble ) return;
if ( ensemble->cases().empty() ) return;
RimEclipseCase* eclipseCase = ensemble->cases().front();
RigWellTargetCandidatesGenerator::ClusteringLimits limits;
limits.volume = m_volume;
limits.permeability = m_permeability;
limits.pressure = m_pressure;
limits.transmissibility = m_transmissibility;
limits.maxClusters = m_maxClusters;
limits.maxIterations = m_maxIterations;
RigWellTargetCandidatesGenerator::generateCandidates( eclipseCase, m_timeStep(), m_volumeType(), m_volumesType(), m_volumeResultType(), limits );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellTargetCandidatesGenerator::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
{
PdmObject::defineUiOrdering( uiConfigName, uiOrdering );
if ( m_minimumVolume == cvf::UNDEFINED_DOUBLE || m_maximumVolume == cvf::UNDEFINED_DOUBLE )
{
updateAllBoundaries();
}
}

View File

@@ -0,0 +1,74 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2024- 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.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "cafAppEnum.h"
#include "cafPdmField.h"
#include "cafPdmObject.h"
#include "RigWellTargetCandidatesGenerator.h"
//==================================================================================================
///
///
//==================================================================================================
class RimWellTargetCandidatesGenerator : public caf::PdmObject
{
CAF_PDM_HEADER_INIT;
public:
RimWellTargetCandidatesGenerator();
~RimWellTargetCandidatesGenerator() override;
protected:
void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override;
void defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute ) override;
QList<caf::PdmOptionItemInfo> calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) override;
void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override;
private:
void generateCandidates();
void updateAllBoundaries();
caf::PdmField<int> m_timeStep;
caf::PdmField<caf::AppEnum<RigWellTargetCandidatesGenerator::VolumeType>> m_volumeType;
caf::PdmField<caf::AppEnum<RigWellTargetCandidatesGenerator::VolumeResultType>> m_volumeResultType;
caf::PdmField<caf::AppEnum<RigWellTargetCandidatesGenerator::VolumesType>> m_volumesType;
caf::PdmField<double> m_volume;
caf::PdmField<double> m_pressure;
caf::PdmField<double> m_permeability;
caf::PdmField<double> m_transmissibility;
caf::PdmField<int> m_maxIterations;
caf::PdmField<int> m_maxClusters;
double m_minimumVolume;
double m_maximumVolume;
double m_minimumPressure;
double m_maximumPressure;
double m_minimumPermeability;
double m_maximumPermeability;
double m_minimumTransmissibility;
double m_maximumTransmissibility;
};

View File

@@ -101,6 +101,7 @@ set(SOURCE_GROUP_HEADER_FILES
${CMAKE_CURRENT_LIST_DIR}/RigReservoirBuilder.h ${CMAKE_CURRENT_LIST_DIR}/RigReservoirBuilder.h
${CMAKE_CURRENT_LIST_DIR}/RigVfpTables.h ${CMAKE_CURRENT_LIST_DIR}/RigVfpTables.h
${CMAKE_CURRENT_LIST_DIR}/RigOsduWellLogData.h ${CMAKE_CURRENT_LIST_DIR}/RigOsduWellLogData.h
${CMAKE_CURRENT_LIST_DIR}/RigWellTargetCandidatesGenerator.h
) )
set(SOURCE_GROUP_SOURCE_FILES set(SOURCE_GROUP_SOURCE_FILES
@@ -200,6 +201,7 @@ set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RigReservoirBuilder.cpp ${CMAKE_CURRENT_LIST_DIR}/RigReservoirBuilder.cpp
${CMAKE_CURRENT_LIST_DIR}/RigVfpTables.cpp ${CMAKE_CURRENT_LIST_DIR}/RigVfpTables.cpp
${CMAKE_CURRENT_LIST_DIR}/RigOsduWellLogData.cpp ${CMAKE_CURRENT_LIST_DIR}/RigOsduWellLogData.cpp
${CMAKE_CURRENT_LIST_DIR}/RigWellTargetCandidatesGenerator.cpp
) )
list(APPEND CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES}) list(APPEND CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES})

View File

@@ -101,7 +101,7 @@ void RigPorvSoilSgasResultCalculator::calculateSum( const RigEclipseResultAddres
void RigPorvSoilSgasResultCalculator::calculate( const RigEclipseResultAddress& in1Addr, void RigPorvSoilSgasResultCalculator::calculate( const RigEclipseResultAddress& in1Addr,
const RigEclipseResultAddress& in2Addr, const RigEclipseResultAddress& in2Addr,
const RigEclipseResultAddress& outAddr, const RigEclipseResultAddress& outAddr,
std::function<double( double, double )> op ) std::function<double( double, double )> operation )
{ {
size_t in1Idx = m_resultsData->findOrLoadKnownScalarResult( in1Addr ); size_t in1Idx = m_resultsData->findOrLoadKnownScalarResult( in1Addr );
size_t in2Idx = m_resultsData->findOrLoadKnownScalarResult( in2Addr ); size_t in2Idx = m_resultsData->findOrLoadKnownScalarResult( in2Addr );
@@ -132,7 +132,7 @@ void RigPorvSoilSgasResultCalculator::calculate( const RigEclipseResultAddress&
{ {
size_t idx1 = res1ActiveOnly ? resultIndex : nativeResvCellIndex; size_t idx1 = res1ActiveOnly ? resultIndex : nativeResvCellIndex;
size_t idx2 = res2ActiveOnly ? resultIndex : nativeResvCellIndex; size_t idx2 = res2ActiveOnly ? resultIndex : nativeResvCellIndex;
outResults[resultIndex] = op( in1Results[idx1], in2Results[idx2] ); outResults[resultIndex] = operation( in1Results[idx1], in2Results[idx2] );
} }
} }
} }

View File

@@ -45,5 +45,5 @@ private:
void calculate( const RigEclipseResultAddress& in1Addr, void calculate( const RigEclipseResultAddress& in1Addr,
const RigEclipseResultAddress& in2Addr, const RigEclipseResultAddress& in2Addr,
const RigEclipseResultAddress& outAddr, const RigEclipseResultAddress& outAddr,
std::function<double( double, double )> op ); std::function<double( double, double )> operation );
}; };

View File

@@ -1565,7 +1565,7 @@ size_t RigCaseCellResultsData::findOrLoadKnownScalarResult( const RigEclipseResu
//-------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------
/// ///
//-------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------
size_t RigCaseCellResultsData::findOrLoadKnownScalarResultByResultTypeOrder( const RigEclipseResultAddress& resVarAddr, size_t RigCaseCellResultsData::findOrLoadKnownScalarResultByResultTypeOrder( const RigEclipseResultAddress& resVarAddr,
const std::vector<RiaDefines::ResultCatType>& resultCategorySearchOrder ) const std::vector<RiaDefines::ResultCatType>& resultCategorySearchOrder )
{ {
std::set<RiaDefines::ResultCatType> otherResultTypesToSearch = { RiaDefines::ResultCatType::STATIC_NATIVE, std::set<RiaDefines::ResultCatType> otherResultTypesToSearch = { RiaDefines::ResultCatType::STATIC_NATIVE,

View File

@@ -0,0 +1,603 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2024- 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 "RigWellTargetCandidatesGenerator.h"
#include "RiaLogging.h"
#include "RiaPorosityModel.h"
#include "RiaResultNames.h"
#include "RiaWeightedMeanCalculator.h"
#include "RigActiveCellInfo.h"
#include "RigCaseCellResultsData.h"
#include "RigEclipseResultAddress.h"
#include "RigMainGrid.h"
#include "RimEclipseCase.h"
#include "RimEclipseCaseEnsemble.h"
#include "RimEclipseView.h"
#include "RimProject.h"
#include "RimPropertyFilterCollection.h"
#include "RimTools.h"
#include "cafVecIjk.h"
#include "cvfMath.h"
#include "cvfStructGrid.h"
#include <cmath>
#include <limits>
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigWellTargetCandidatesGenerator::generateCandidates( RimEclipseCase* eclipseCase,
size_t timeStepIdx,
VolumeType volumeType,
VolumesType volumesType,
VolumeResultType volumeResultType,
const ClusteringLimits& limits )
{
auto activeCellCount = getActiveCellCount( eclipseCase );
if ( !activeCellCount )
{
RiaLogging::error( "No active cells found" );
return;
}
auto resultsData = eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL );
if ( !resultsData ) return;
std::vector<double> volume = getVolumeVector( *resultsData, volumeType, volumesType, volumeResultType, timeStepIdx );
if ( volume.empty() )
{
RiaLogging::error( "Unable to produce volume vector." );
return;
}
RigEclipseResultAddress pressureAddress( RiaDefines::ResultCatType::DYNAMIC_NATIVE, "PRESSURE" );
resultsData->ensureKnownResultLoaded( pressureAddress );
const std::vector<double>& pressure = resultsData->cellScalarResults( pressureAddress, timeStepIdx );
RigEclipseResultAddress permeabilityXAddress( RiaDefines::ResultCatType::STATIC_NATIVE, "PERMX" );
resultsData->ensureKnownResultLoaded( permeabilityXAddress );
const std::vector<double>& permeabilityX = resultsData->cellScalarResults( permeabilityXAddress, 0 );
RigEclipseResultAddress permeabilityYAddress( RiaDefines::ResultCatType::STATIC_NATIVE, "PERMY" );
resultsData->ensureKnownResultLoaded( permeabilityYAddress );
const std::vector<double>& permeabilityY = resultsData->cellScalarResults( permeabilityYAddress, 0 );
RigEclipseResultAddress permeabilityZAddress( RiaDefines::ResultCatType::STATIC_NATIVE, "PERMZ" );
resultsData->ensureKnownResultLoaded( permeabilityZAddress );
const std::vector<double>& permeabilityZ = resultsData->cellScalarResults( permeabilityZAddress, 0 );
RigEclipseResultAddress transmissibilityXAddress( RiaDefines::ResultCatType::STATIC_NATIVE, "TRANX" );
resultsData->ensureKnownResultLoaded( transmissibilityXAddress );
const std::vector<double>& transmissibilityX = resultsData->cellScalarResults( transmissibilityXAddress, 0 );
RigEclipseResultAddress transmissibilityYAddress( RiaDefines::ResultCatType::STATIC_NATIVE, "TRANY" );
resultsData->ensureKnownResultLoaded( transmissibilityYAddress );
const std::vector<double>& transmissibilityY = resultsData->cellScalarResults( transmissibilityYAddress, 0 );
RigEclipseResultAddress transmissibilityZAddress( RiaDefines::ResultCatType::STATIC_NATIVE, "TRANZ" );
resultsData->ensureKnownResultLoaded( transmissibilityZAddress );
const std::vector<double>& transmissibilityZ = resultsData->cellScalarResults( transmissibilityZAddress, 0 );
std::vector<int> clusters( activeCellCount.value(), 0 );
auto start = std::chrono::high_resolution_clock::now();
int numClusters = limits.maxClusters;
int maxIterations = limits.maxIterations;
int numClustersFound = 0;
for ( int clusterId = 1; clusterId <= numClusters; clusterId++ )
{
std::optional<caf::VecIjk> startCell = findStartCell( eclipseCase,
timeStepIdx,
limits,
volume,
pressure,
permeabilityX,
permeabilityY,
permeabilityZ,
transmissibilityX,
transmissibilityY,
transmissibilityZ,
clusters );
if ( startCell.has_value() )
{
RiaLogging::info( QString( "Cluster %1 start cell: [%2 %3 %4] " )
.arg( clusterId )
.arg( startCell->i() + 1 )
.arg( startCell->j() + 1 )
.arg( startCell->k() + 1 ) );
growCluster( eclipseCase,
startCell.value(),
limits,
volume,
pressure,
permeabilityX,
permeabilityY,
permeabilityZ,
transmissibilityX,
transmissibilityY,
transmissibilityZ,
clusters,
clusterId,
timeStepIdx,
maxIterations );
numClustersFound++;
}
else
{
RiaLogging::error( "No suitable starting cell found" );
break;
}
}
RiaLogging::info( QString( "Found %1 clusters." ).arg( numClustersFound ) );
auto finish = std::chrono::high_resolution_clock::now();
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>( finish - start );
RiaLogging::info( QString( "Time spent: %1 ms" ).arg( milliseconds.count() ) );
QString resultName = "CLUSTERS_NUM";
createResultVector( *eclipseCase, resultName, clusters );
// Update views and property filters
RimProject* proj = RimProject::current();
proj->scheduleCreateDisplayModelAndRedrawAllViews();
for ( auto view : eclipseCase->reservoirViews() )
{
if ( auto eclipseView = dynamic_cast<RimEclipseView*>( view ) )
{
eclipseView->scheduleReservoirGridGeometryRegen();
eclipseView->propertyFilterCollection()->updateConnectedEditors();
}
}
std::vector<ClusterStatistics> statistics =
generateStatistics( eclipseCase, pressure, permeabilityX, permeabilityY, permeabilityZ, numClustersFound, timeStepIdx, resultName );
for ( auto s : statistics )
{
RiaLogging::info( QString( "Cluster #%1 Statistics" ).arg( s.id ) );
RiaLogging::info( QString( "Number of cells: %1" ).arg( s.numCells ) );
RiaLogging::info( QString( "Total PORV*SOIL: %1" ).arg( s.totalPorvSoil ) );
RiaLogging::info( QString( "Total PORV*SGAS: %1" ).arg( s.totalPorvSgas ) );
RiaLogging::info( QString( "Total PORV*(SOIL+SGAS): %1" ).arg( s.totalPorvSoilAndSgas ) );
RiaLogging::info( QString( "Total FIPOIL: %1" ).arg( s.totalFipOil ) );
RiaLogging::info( QString( "Total FIPGAS: %1" ).arg( s.totalFipGas ) );
RiaLogging::info( QString( "Average Permeability: %1" ).arg( s.permeability ) );
RiaLogging::info( QString( "Average Pressure: %1" ).arg( s.pressure ) );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::optional<caf::VecIjk> RigWellTargetCandidatesGenerator::findStartCell( RimEclipseCase* eclipseCase,
size_t timeStepIdx,
const ClusteringLimits& limits,
const std::vector<double>& volume,
const std::vector<double>& pressure,
const std::vector<double>& permeabilityX,
const std::vector<double>& permeabilityY,
const std::vector<double>& permeabilityZ,
const std::vector<double>& transmissibilityX,
const std::vector<double>& transmissibilityY,
const std::vector<double>& transmissibilityZ,
const std::vector<int>& clusters )
{
auto resultsData = eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL );
if ( !resultsData )
{
RiaLogging::error( "No results data found for eclipse case" );
return {};
}
size_t startCell = std::numeric_limits<size_t>::max();
double maxVolume = -std::numeric_limits<double>::max();
const size_t numReservoirCells = resultsData->activeCellInfo()->reservoirCellCount();
for ( size_t reservoirCellIdx = 0; reservoirCellIdx < numReservoirCells; reservoirCellIdx++ )
{
size_t resultIndex = resultsData->activeCellInfo()->cellResultIndex( reservoirCellIdx );
if ( resultIndex != cvf::UNDEFINED_SIZE_T && clusters[resultIndex] == 0 )
{
const double cellVolume = volume[resultIndex];
const double cellPressure = pressure[resultIndex];
const double cellPermeabiltyX = permeabilityX[resultIndex];
const double cellPermeabiltyY = permeabilityY[resultIndex];
const double cellPermeabiltyZ = permeabilityZ[resultIndex];
const bool permeabilityValidInAnyDirection = ( cellPermeabiltyX >= limits.permeability || cellPermeabiltyY >= limits.permeability ||
cellPermeabiltyZ >= limits.permeability );
if ( cellVolume > maxVolume && cellVolume >= limits.volume && cellPressure >= limits.pressure && permeabilityValidInAnyDirection )
{
maxVolume = cellVolume;
startCell = reservoirCellIdx;
}
}
}
if ( startCell == std::numeric_limits<size_t>::max() ) return {};
return eclipseCase->mainGrid()->ijkFromCellIndex( startCell );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigWellTargetCandidatesGenerator::growCluster( RimEclipseCase* eclipseCase,
const caf::VecIjk& startCell,
const ClusteringLimits& limits,
const std::vector<double>& volume,
const std::vector<double>& pressure,
const std::vector<double>& permeabilityX,
const std::vector<double>& permeabilityY,
const std::vector<double>& permeabilityZ,
const std::vector<double>& transmissibilityX,
const std::vector<double>& transmissibilityY,
const std::vector<double>& transmissibilityZ,
std::vector<int>& clusters,
int clusterId,
size_t timeStepIdx,
int maxIterations )
{
auto resultsData = eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL );
// Initially only the start cell is found
size_t reservoirCellIdx = eclipseCase->mainGrid()->cellIndexFromIJK( startCell.i(), startCell.j(), startCell.k() );
std::vector<size_t> foundCells = { reservoirCellIdx };
assignClusterIdToCells( *resultsData->activeCellInfo(), foundCells, clusters, clusterId );
for ( int i = 0; i < maxIterations; i++ )
{
foundCells = findCandidates( *eclipseCase,
foundCells,
limits,
volume,
pressure,
permeabilityX,
permeabilityY,
permeabilityZ,
transmissibilityX,
transmissibilityY,
transmissibilityZ,
clusters );
if ( foundCells.empty() ) break;
assignClusterIdToCells( *resultsData->activeCellInfo(), foundCells, clusters, clusterId );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<size_t> RigWellTargetCandidatesGenerator::findCandidates( const RimEclipseCase& eclipseCase,
const std::vector<size_t>& previousCells,
const ClusteringLimits& limits,
const std::vector<double>& volume,
const std::vector<double>& pressure,
const std::vector<double>& permeabilityX,
const std::vector<double>& permeabilityY,
const std::vector<double>& permeabilityZ,
const std::vector<double>& transmissibilityX,
const std::vector<double>& transmissibilityY,
const std::vector<double>& transmissibilityZ,
std::vector<int>& clusters )
{
std::vector<size_t> candidates;
auto resultsData = eclipseCase.results( RiaDefines::PorosityModelType::MATRIX_MODEL );
for ( size_t cellIdx : previousCells )
{
std::vector<cvf::StructGridInterface::FaceType> faces = {
cvf::StructGridInterface::FaceType::POS_I,
cvf::StructGridInterface::FaceType::NEG_I,
cvf::StructGridInterface::FaceType::POS_J,
cvf::StructGridInterface::FaceType::NEG_J,
cvf::StructGridInterface::FaceType::POS_K,
cvf::StructGridInterface::FaceType::NEG_K,
};
size_t resultIndex = resultsData->activeCellInfo()->cellResultIndex( cellIdx );
for ( cvf::StructGridInterface::FaceType face : faces )
{
const RigCell& nativeCell = eclipseCase.mainGrid()->globalCellArray()[cellIdx];
RigGridBase* grid = nativeCell.hostGrid();
size_t gridLocalNativeCellIndex = nativeCell.gridLocalCellIndex();
size_t i, j, k, gridLocalNeighborCellIdx;
grid->ijkFromCellIndex( gridLocalNativeCellIndex, &i, &j, &k );
if ( grid->cellIJKNeighbor( i, j, k, face, &gridLocalNeighborCellIdx ) )
{
size_t neighborResvCellIdx = grid->reservoirCellIndex( gridLocalNeighborCellIdx );
size_t neighborResultIndex = resultsData->activeCellInfo()->cellResultIndex( neighborResvCellIdx );
if ( neighborResultIndex != cvf::UNDEFINED_SIZE_T && clusters[neighborResultIndex] == 0 )
{
double permeability = getValueForFace( permeabilityX, permeabilityY, permeabilityZ, face, neighborResultIndex );
double transmissibility = getTransmissibilityValueForFace( transmissibilityX,
transmissibilityY,
transmissibilityZ,
face,
resultIndex,
neighborResultIndex );
if ( volume[neighborResultIndex] > limits.volume && pressure[neighborResultIndex] > limits.pressure &&
permeability > limits.permeability && transmissibility > limits.transmissibility )
{
candidates.push_back( neighborResvCellIdx );
clusters[neighborResultIndex] = -1;
}
}
}
}
}
return candidates;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigWellTargetCandidatesGenerator::assignClusterIdToCells( const RigActiveCellInfo& activeCellInfo,
const std::vector<size_t>& cells,
std::vector<int>& clusters,
int clusterId )
{
for ( size_t reservoirCellIdx : cells )
{
size_t resultIndex = activeCellInfo.cellResultIndex( reservoirCellIdx );
if ( resultIndex != cvf::UNDEFINED_SIZE_T ) clusters[resultIndex] = clusterId;
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigWellTargetCandidatesGenerator::createResultVector( RimEclipseCase& eclipseCase,
const QString& resultName,
const std::vector<int>& clusterIds )
{
RigEclipseResultAddress resultAddress( RiaDefines::ResultCatType::GENERATED, resultName );
auto resultsData = eclipseCase.results( RiaDefines::PorosityModelType::MATRIX_MODEL );
resultsData->addStaticScalarResult( RiaDefines::ResultCatType::GENERATED, resultName, false, clusterIds.size() );
std::vector<double>* resultVector = resultsData->modifiableCellScalarResult( resultAddress, 0 );
resultVector->resize( clusterIds.size(), std::numeric_limits<double>::infinity() );
std::fill( resultVector->begin(), resultVector->end(), std::numeric_limits<double>::infinity() );
for ( size_t idx = 0; idx < clusterIds.size(); idx++ )
{
if ( clusterIds[idx] > 0 )
{
resultVector->at( idx ) = clusterIds[idx];
}
}
resultsData->recalculateStatistics( resultAddress );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::optional<size_t> RigWellTargetCandidatesGenerator::getActiveCellCount( RimEclipseCase* eclipseCase )
{
auto resultsData = eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL );
if ( !resultsData ) return {};
return resultsData->activeCellInfo()->reservoirActiveCellCount();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RigWellTargetCandidatesGenerator::getValueForFace( const std::vector<double>& x,
const std::vector<double>& y,
const std::vector<double>& z,
cvf::StructGridInterface::FaceType face,
size_t resultIndex )
{
if ( face == cvf::StructGridInterface::FaceType::POS_I || face == cvf::StructGridInterface::FaceType::NEG_I ) return x[resultIndex];
if ( face == cvf::StructGridInterface::FaceType::POS_J || face == cvf::StructGridInterface::FaceType::NEG_J ) return y[resultIndex];
if ( face == cvf::StructGridInterface::FaceType::POS_K || face == cvf::StructGridInterface::FaceType::NEG_K ) return z[resultIndex];
return std::numeric_limits<double>::infinity();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RigWellTargetCandidatesGenerator::getTransmissibilityValueForFace( const std::vector<double>& x,
const std::vector<double>& y,
const std::vector<double>& z,
cvf::StructGridInterface::FaceType face,
size_t resultIndex,
size_t neighborResultIndex )
{
// For negative directions (NEG_I, NEG_J, NEG_K) use the value from the neighbor cell
bool isPos = face == cvf::StructGridInterface::FaceType::POS_I || face == cvf::StructGridInterface::FaceType::POS_J ||
face == cvf::StructGridInterface::FaceType::POS_K;
size_t index = isPos ? resultIndex : neighborResultIndex;
return getValueForFace( x, y, z, face, index );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<double> RigWellTargetCandidatesGenerator::getVolumeVector( RigCaseCellResultsData& resultsData,
VolumeType volumeType,
VolumesType volumesType,
VolumeResultType volumeResultType,
size_t timeStepIdx )
{
auto loadVectorByName = []( RigCaseCellResultsData& resultsData, const QString& resultName, size_t timeStepIdx ) -> std::vector<double>
{
RigEclipseResultAddress address( RiaDefines::ResultCatType::DYNAMIC_NATIVE, resultName );
if ( !resultsData.ensureKnownResultLoaded( address ) ) return {};
return resultsData.cellScalarResults( address, timeStepIdx );
};
auto getOilVectorName = []( VolumesType volumesType ) -> QString
{
switch ( volumesType )
{
case VolumesType::COMPUTED_VOLUMES:
return RiaResultNames::riPorvSoil();
case VolumesType::RESERVOIR_VOLUMES:
return "RFIPOIL";
case VolumesType::SURFACE_VOLUMES:
return "SFIPOIL";
default:
{
CAF_ASSERT( false );
return "";
}
}
};
auto getGasVectorName = []( VolumesType volumesType ) -> QString
{
switch ( volumesType )
{
case VolumesType::COMPUTED_VOLUMES:
return RiaResultNames::riPorvSgas();
case VolumesType::RESERVOIR_VOLUMES:
return "RFIPGAS";
case VolumesType::SURFACE_VOLUMES:
return "SFIPGAS";
default:
{
CAF_ASSERT( false );
return "";
}
}
};
std::vector<double> volume;
if ( volumeType == VolumeType::OIL )
{
volume = loadVectorByName( resultsData, getOilVectorName( volumesType ), timeStepIdx );
}
else if ( volumeType == VolumeType::GAS )
{
volume = loadVectorByName( resultsData, getGasVectorName( volumesType ), timeStepIdx );
}
else if ( volumeType == VolumeType::HYDROCARBON )
{
std::vector<double> oilVolume = loadVectorByName( resultsData, getOilVectorName( volumesType ), timeStepIdx );
std::vector<double> gasVolume = loadVectorByName( resultsData, getGasVectorName( volumesType ), timeStepIdx );
if ( oilVolume.empty() || gasVolume.empty() || oilVolume.size() != gasVolume.size() ) return volume;
volume.resize( oilVolume.size(), std::numeric_limits<double>::infinity() );
for ( size_t i = 0; i < oilVolume.size(); i++ )
{
volume[i] = oilVolume[i] + gasVolume[i];
}
}
return volume;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RigWellTargetCandidatesGenerator::ClusterStatistics>
RigWellTargetCandidatesGenerator::generateStatistics( RimEclipseCase* eclipseCase,
const std::vector<double>& pressure,
const std::vector<double>& permeabilityX,
const std::vector<double>& permeabilityY,
const std::vector<double>& permeabilityZ,
int numClustersFound,
size_t timeStepIdx,
const QString& clusterResultName )
{
std::vector<ClusterStatistics> statistics( numClustersFound );
auto resultsData = eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL );
if ( !resultsData ) return statistics;
RigEclipseResultAddress porvAddress( RiaDefines::ResultCatType::STATIC_NATIVE, "PORV" );
resultsData->ensureKnownResultLoaded( porvAddress );
const std::vector<double>& porv = resultsData->cellScalarResults( porvAddress, 0 );
RigEclipseResultAddress porvSoilAddress( RiaDefines::ResultCatType::DYNAMIC_NATIVE, RiaResultNames::riPorvSoil() );
resultsData->ensureKnownResultLoaded( porvSoilAddress );
const std::vector<double>& porvSoil = resultsData->cellScalarResults( porvSoilAddress, timeStepIdx );
RigEclipseResultAddress porvSgasAddress( RiaDefines::ResultCatType::DYNAMIC_NATIVE, RiaResultNames::riPorvSgas() );
resultsData->ensureKnownResultLoaded( porvSgasAddress );
const std::vector<double>& porvSgas = resultsData->cellScalarResults( porvSgasAddress, timeStepIdx );
RigEclipseResultAddress porvSoilAndSgasAddress( RiaDefines::ResultCatType::DYNAMIC_NATIVE, RiaResultNames::riPorvSoilSgas() );
resultsData->ensureKnownResultLoaded( porvSoilAndSgasAddress );
const std::vector<double>& porvSoilAndSgas = resultsData->cellScalarResults( porvSoilAndSgasAddress, timeStepIdx );
RigEclipseResultAddress fipOilAddress( RiaDefines::ResultCatType::DYNAMIC_NATIVE, "FIPOIL" );
resultsData->ensureKnownResultLoaded( fipOilAddress );
const std::vector<double>& fipOil = resultsData->cellScalarResults( fipOilAddress, timeStepIdx );
RigEclipseResultAddress fipGasAddress( RiaDefines::ResultCatType::DYNAMIC_NATIVE, "FIPGAS" );
resultsData->ensureKnownResultLoaded( fipGasAddress );
const std::vector<double>& fipGas = resultsData->cellScalarResults( fipGasAddress, timeStepIdx );
RigEclipseResultAddress clusterAddress( RiaDefines::ResultCatType::GENERATED, clusterResultName );
resultsData->ensureKnownResultLoaded( clusterAddress );
const std::vector<double>& clusterIds = resultsData->cellScalarResults( clusterAddress, 0 );
std::vector<RiaWeightedMeanCalculator<double>> permeabilityCalculators( numClustersFound );
std::vector<RiaWeightedMeanCalculator<double>> pressureCalculators( numClustersFound );
for ( size_t idx = 0; idx < clusterIds.size(); idx++ )
{
if ( !std::isinf( clusterIds[idx] ) && static_cast<int>( clusterIds[idx] ) > 0 )
{
size_t i = clusterIds[idx] - 1;
if ( i < static_cast<size_t>( numClustersFound ) )
{
statistics[i].id = clusterIds[idx];
statistics[i].numCells++;
statistics[i].totalPorvSoil += porvSoil[idx];
statistics[i].totalPorvSgas += porvSgas[idx];
statistics[i].totalPorvSoilAndSgas += porvSoilAndSgas[idx];
statistics[i].totalFipOil += fipOil[idx];
statistics[i].totalFipGas += fipGas[idx];
double meanPermeability = ( permeabilityX[idx] + permeabilityY[idx] + permeabilityZ[idx] ) / 3.0;
permeabilityCalculators[i].addValueAndWeight( meanPermeability, porv[idx] );
pressureCalculators[i].addValueAndWeight( pressure[idx], porv[idx] );
}
}
}
for ( int i = 0; i < numClustersFound; i++ )
{
statistics[i].permeability = permeabilityCalculators[i].weightedMean();
statistics[i].pressure = pressureCalculators[i].weightedMean();
}
return statistics;
}

View File

@@ -0,0 +1,180 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2024- 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.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "cafVecIjk.h"
#include "cvfStructGrid.h"
#include <optional>
class RigActiveCellInfo;
class RigCaseCellResultsData;
class RimEclipseCase;
//==================================================================================================
///
///
//==================================================================================================
class RigWellTargetCandidatesGenerator
{
public:
enum class VolumeType
{
OIL,
GAS,
HYDROCARBON
};
enum class VolumeResultType
{
MOBILE,
TOTAL
};
enum class VolumesType
{
RESERVOIR_VOLUMES,
SURFACE_VOLUMES,
COMPUTED_VOLUMES
};
struct ClusteringLimits
{
double volume;
double permeability;
double pressure;
double transmissibility;
int maxClusters;
int maxIterations;
};
static void generateCandidates( RimEclipseCase* eclipseCase,
size_t timeStepIdx,
VolumeType volumeType,
VolumesType volumesType,
VolumeResultType volumeResultType,
const ClusteringLimits& limits );
static std::vector<double> getVolumeVector( RigCaseCellResultsData& resultsData,
VolumeType volumeType,
VolumesType volumesType,
VolumeResultType volumeResultType,
size_t timeStepIdx );
class ClusterStatistics
{
public:
ClusterStatistics()
: id( -1 )
, numCells( 0 )
, totalPorvSoil( 0.0 )
, totalPorvSgas( 0.0 )
, totalPorvSoilAndSgas( 0.0 )
, totalFipOil( 0.0 )
, totalFipGas( 0.0 )
, permeability( 0.0 )
, pressure( 0.0 )
{
}
int id;
size_t numCells;
double totalPorvSoil;
double totalPorvSgas;
double totalPorvSoilAndSgas;
double totalFipOil;
double totalFipGas;
double permeability;
double pressure;
};
private:
static std::optional<caf::VecIjk> findStartCell( RimEclipseCase* eclipseCase,
size_t timeStepIdx,
const ClusteringLimits& limits,
const std::vector<double>& volume,
const std::vector<double>& pressure,
const std::vector<double>& permeabilityX,
const std::vector<double>& permeabilityY,
const std::vector<double>& permeabilityZ,
const std::vector<double>& transmissibilityX,
const std::vector<double>& transmissibilityY,
const std::vector<double>& transmissibilityZ,
const std::vector<int>& clusters );
static void growCluster( RimEclipseCase* eclipseCase,
const caf::VecIjk& startCell,
const ClusteringLimits& limits,
const std::vector<double>& volume,
const std::vector<double>& pressure,
const std::vector<double>& permeabilityX,
const std::vector<double>& permeabilityY,
const std::vector<double>& permeabilityZ,
const std::vector<double>& transmissibilityX,
const std::vector<double>& transmissibilityY,
const std::vector<double>& transmissibilityZ,
std::vector<int>& clusters,
int clusterId,
size_t timeStepIdx,
int maxIterations );
static std::vector<size_t> findCandidates( const RimEclipseCase& eclipseCase,
const std::vector<size_t>& previousCells,
const ClusteringLimits& limits,
const std::vector<double>& volume,
const std::vector<double>& pressure,
const std::vector<double>& permeabilityX,
const std::vector<double>& permeabilityY,
const std::vector<double>& permeabilityZ,
const std::vector<double>& transmissibilityX,
const std::vector<double>& transmissibilityY,
const std::vector<double>& transmissibilityZ,
std::vector<int>& clusters );
static void assignClusterIdToCells( const RigActiveCellInfo& activeCellInfo,
const std::vector<size_t>& cells,
std::vector<int>& clusters,
int clusterId );
static std::optional<size_t> getActiveCellCount( RimEclipseCase* eclipseCase );
static void createResultVector( RimEclipseCase& eclipseCase, const QString& resultName, const std::vector<int>& clusterIds );
static double getValueForFace( const std::vector<double>& x,
const std::vector<double>& y,
const std::vector<double>& z,
cvf::StructGridInterface::FaceType face,
size_t resultIndex );
static double getTransmissibilityValueForFace( const std::vector<double>& x,
const std::vector<double>& y,
const std::vector<double>& z,
cvf::StructGridInterface::FaceType face,
size_t resultIndex,
size_t neighborResultIndex );
static std::vector<RigWellTargetCandidatesGenerator::ClusterStatistics> generateStatistics( RimEclipseCase* eclipseCase,
const std::vector<double>& pressure,
const std::vector<double>& permeabilityX,
const std::vector<double>& permeabilityY,
const std::vector<double>& permeabilityZ,
int numClustersFound,
size_t timeStepIdx,
const QString& clusterResultName );
};