diff --git a/ApplicationLibCode/Commands/CMakeLists_files.cmake b/ApplicationLibCode/Commands/CMakeLists_files.cmake index 3adb164c67..d96866c0a6 100644 --- a/ApplicationLibCode/Commands/CMakeLists_files.cmake +++ b/ApplicationLibCode/Commands/CMakeLists_files.cmake @@ -96,6 +96,7 @@ set(SOURCE_GROUP_HEADER_FILES ${CMAKE_CURRENT_LIST_DIR}/RicImportWellLogCsvFileFeature.h ${CMAKE_CURRENT_LIST_DIR}/RicNewViewForGridEnsembleFeature.h ${CMAKE_CURRENT_LIST_DIR}/RicNewCustomVfpPlotFeature.h + ${CMAKE_CURRENT_LIST_DIR}/RicNewWellTargetCandidatesGeneratorFeature.h ) set(SOURCE_GROUP_SOURCE_FILES @@ -196,6 +197,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RicImportWellLogOsduFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicNewViewForGridEnsembleFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicNewCustomVfpPlotFeature.cpp + ${CMAKE_CURRENT_LIST_DIR}/RicNewWellTargetCandidatesGeneratorFeature.cpp ) if(RESINSIGHT_USE_QT_CHARTS) diff --git a/ApplicationLibCode/Commands/RicNewWellTargetCandidatesGeneratorFeature.cpp b/ApplicationLibCode/Commands/RicNewWellTargetCandidatesGeneratorFeature.cpp new file mode 100644 index 0000000000..77d0875b5f --- /dev/null +++ b/ApplicationLibCode/Commands/RicNewWellTargetCandidatesGeneratorFeature.cpp @@ -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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RicNewWellTargetCandidatesGeneratorFeature.h" + +#include "RimEclipseCaseEnsemble.h" +#include "RimWellTargetCandidatesGenerator.h" + +#include "cafSelectionManagerTools.h" + +#include + +CAF_CMD_SOURCE_INIT( RicNewWellTargetCandidatesGeneratorFeature, "RicNewWellTargetCandidatesGeneratorFeature" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RicNewWellTargetCandidatesGeneratorFeature::onActionTriggered( bool isChecked ) +{ + auto ensembles = caf::selectedObjectsByTypeStrict(); + 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" ); +} diff --git a/ApplicationLibCode/Commands/RicNewWellTargetCandidatesGeneratorFeature.h b/ApplicationLibCode/Commands/RicNewWellTargetCandidatesGeneratorFeature.h new file mode 100644 index 0000000000..77430ae978 --- /dev/null +++ b/ApplicationLibCode/Commands/RicNewWellTargetCandidatesGeneratorFeature.h @@ -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 +// 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; +}; diff --git a/ApplicationLibCode/ProjectDataModel/CMakeLists_files.cmake b/ApplicationLibCode/ProjectDataModel/CMakeLists_files.cmake index 67bccb2642..617e3b95fb 100644 --- a/ApplicationLibCode/ProjectDataModel/CMakeLists_files.cmake +++ b/ApplicationLibCode/ProjectDataModel/CMakeLists_files.cmake @@ -134,6 +134,7 @@ set(SOURCE_GROUP_HEADER_FILES ${CMAKE_CURRENT_LIST_DIR}/RimEclipseViewCollection.h ${CMAKE_CURRENT_LIST_DIR}/RimEclipseCaseEnsemble.h ${CMAKE_CURRENT_LIST_DIR}/RimCameraPosition.h + ${CMAKE_CURRENT_LIST_DIR}/RimWellTargetCandidatesGenerator.h ) set(SOURCE_GROUP_SOURCE_FILES @@ -268,6 +269,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RimEclipseViewCollection.cpp ${CMAKE_CURRENT_LIST_DIR}/RimEclipseCaseEnsemble.cpp ${CMAKE_CURRENT_LIST_DIR}/RimCameraPosition.cpp + ${CMAKE_CURRENT_LIST_DIR}/RimWellTargetCandidatesGenerator.cpp ) if(RESINSIGHT_USE_QT_CHARTS) diff --git a/ApplicationLibCode/ProjectDataModel/RimEclipseCaseEnsemble.cpp b/ApplicationLibCode/ProjectDataModel/RimEclipseCaseEnsemble.cpp index f00e47e3d8..2fc769f359 100644 --- a/ApplicationLibCode/ProjectDataModel/RimEclipseCaseEnsemble.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimEclipseCaseEnsemble.cpp @@ -22,6 +22,7 @@ #include "RimEclipseCase.h" #include "RimEclipseView.h" #include "RimEclipseViewCollection.h" +#include "RimWellTargetCandidatesGenerator.h" #include "cafCmdFeatureMenuBuilder.h" #include "cafPdmFieldScriptingCapability.h" @@ -50,6 +51,8 @@ RimEclipseCaseEnsemble::RimEclipseCaseEnsemble() CAF_PDM_InitFieldNoDefault( &m_viewCollection, "ViewCollection", "Views" ); m_viewCollection = new RimEclipseViewCollection; + CAF_PDM_InitFieldNoDefault( &m_wellTargetGenerators, "WellTargetGenerators", "Well Target Candidates Generators" ); + setDeletable( true ); } @@ -169,6 +172,7 @@ void RimEclipseCaseEnsemble::fieldChangedByUi( const caf::PdmFieldHandle* change void RimEclipseCaseEnsemble::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const { menuBuilder << "RicNewViewForGridEnsembleFeature"; + menuBuilder << "RicNewWellTargetCandidatesGeneratorFeature"; } //-------------------------------------------------------------------------------------------------- @@ -178,3 +182,11 @@ RimEclipseViewCollection* RimEclipseCaseEnsemble::viewCollection() const { return m_viewCollection; } + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimEclipseCaseEnsemble::addWellTargetsGenerator( RimWellTargetCandidatesGenerator* generator ) +{ + m_wellTargetGenerators.push_back( generator ); +} diff --git a/ApplicationLibCode/ProjectDataModel/RimEclipseCaseEnsemble.h b/ApplicationLibCode/ProjectDataModel/RimEclipseCaseEnsemble.h index 2e723505e8..a66065d046 100644 --- a/ApplicationLibCode/ProjectDataModel/RimEclipseCaseEnsemble.h +++ b/ApplicationLibCode/ProjectDataModel/RimEclipseCaseEnsemble.h @@ -29,6 +29,7 @@ class RimCaseCollection; class RimEclipseCase; class RimEclipseView; class RimEclipseViewCollection; +class RimWellTargetCandidatesGenerator; //================================================================================================== // @@ -54,14 +55,17 @@ class RimEclipseCaseEnsemble : public RimNamedObject RimEclipseViewCollection* viewCollection() const; + void addWellTargetsGenerator( RimWellTargetCandidatesGenerator* generator ); + protected: QList calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) override; void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override; void appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const override; private: - caf::PdmField m_groupId; - caf::PdmChildField m_caseCollection; - caf::PdmChildField m_viewCollection; - caf::PdmPtrField m_selectedCase; + caf::PdmField m_groupId; + caf::PdmChildField m_caseCollection; + caf::PdmChildField m_viewCollection; + caf::PdmChildArrayField m_wellTargetGenerators; + caf::PdmPtrField m_selectedCase; }; diff --git a/ApplicationLibCode/ProjectDataModel/RimWellTargetCandidatesGenerator.cpp b/ApplicationLibCode/ProjectDataModel/RimWellTargetCandidatesGenerator.cpp new file mode 100644 index 0000000000..abc6543295 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/RimWellTargetCandidatesGenerator.cpp @@ -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 +// 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 +#include + +CAF_PDM_SOURCE_INIT( RimWellTargetCandidatesGenerator, "RimWellTargetCandidatesGenerator" ); + +namespace caf +{ +template <> +void caf::AppEnum::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::setUp() +{ + addItem( RigWellTargetCandidatesGenerator::VolumeResultType::MOBILE, "MOBILE", "Mobile" ); + addItem( RigWellTargetCandidatesGenerator::VolumeResultType::TOTAL, "TOTAL", "Total" ); + setDefault( RigWellTargetCandidatesGenerator::VolumeResultType::TOTAL ); +} + +template <> +void caf::AppEnum::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 RimWellTargetCandidatesGenerator::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) +{ + QList options; + + if ( fieldNeedingOptions == &m_timeStep ) + { + auto ensemble = firstAncestorOrThisOfType(); + if ( ensemble && !ensemble->cases().empty() ) + { + RimEclipseCase* eclipseCase = ensemble->cases().front(); + + RimTools::timeStepsForCase( eclipseCase, &options ); + } + } + + return options; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellTargetCandidatesGenerator::updateAllBoundaries() +{ + auto ensemble = firstAncestorOrThisOfType(); + if ( !ensemble ) return; + + if ( ensemble->cases().empty() ) return; + + RimEclipseCase* eclipseCase = ensemble->cases().front(); + + int timeStepIdx = m_timeStep(); + + auto updateBoundaryValues = + []( auto resultsData, const std::vector& addresses, size_t timeStepIdx ) -> std::pair + { + double globalMin = std::numeric_limits::max(); + double globalMax = -std::numeric_limits::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 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( 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( 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( 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( attribute ) ) + { + doubleAttributes->m_minimum = m_minimumTransmissibility; + doubleAttributes->m_maximum = m_maximumTransmissibility; + doubleAttributes->m_decimals = 3; + } + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellTargetCandidatesGenerator::generateCandidates() +{ + auto ensemble = firstAncestorOrThisOfType(); + 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(); + } +} diff --git a/ApplicationLibCode/ProjectDataModel/RimWellTargetCandidatesGenerator.h b/ApplicationLibCode/ProjectDataModel/RimWellTargetCandidatesGenerator.h new file mode 100644 index 0000000000..cab46dff41 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/RimWellTargetCandidatesGenerator.h @@ -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 +// 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 calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) override; + void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override; + +private: + void generateCandidates(); + void updateAllBoundaries(); + + caf::PdmField m_timeStep; + + caf::PdmField> m_volumeType; + caf::PdmField> m_volumeResultType; + caf::PdmField> m_volumesType; + + caf::PdmField m_volume; + caf::PdmField m_pressure; + caf::PdmField m_permeability; + caf::PdmField m_transmissibility; + + caf::PdmField m_maxIterations; + caf::PdmField 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; +}; diff --git a/ApplicationLibCode/ReservoirDataModel/CMakeLists_files.cmake b/ApplicationLibCode/ReservoirDataModel/CMakeLists_files.cmake index c6f90aafa8..574795281c 100644 --- a/ApplicationLibCode/ReservoirDataModel/CMakeLists_files.cmake +++ b/ApplicationLibCode/ReservoirDataModel/CMakeLists_files.cmake @@ -101,6 +101,7 @@ set(SOURCE_GROUP_HEADER_FILES ${CMAKE_CURRENT_LIST_DIR}/RigReservoirBuilder.h ${CMAKE_CURRENT_LIST_DIR}/RigVfpTables.h ${CMAKE_CURRENT_LIST_DIR}/RigOsduWellLogData.h + ${CMAKE_CURRENT_LIST_DIR}/RigWellTargetCandidatesGenerator.h ) set(SOURCE_GROUP_SOURCE_FILES @@ -200,6 +201,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RigReservoirBuilder.cpp ${CMAKE_CURRENT_LIST_DIR}/RigVfpTables.cpp ${CMAKE_CURRENT_LIST_DIR}/RigOsduWellLogData.cpp + ${CMAKE_CURRENT_LIST_DIR}/RigWellTargetCandidatesGenerator.cpp ) list(APPEND CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES}) diff --git a/ApplicationLibCode/ReservoirDataModel/ResultCalculators/RigPorvSoilSgasResultCalculator.cpp b/ApplicationLibCode/ReservoirDataModel/ResultCalculators/RigPorvSoilSgasResultCalculator.cpp index 09710e0d13..579f4814d6 100644 --- a/ApplicationLibCode/ReservoirDataModel/ResultCalculators/RigPorvSoilSgasResultCalculator.cpp +++ b/ApplicationLibCode/ReservoirDataModel/ResultCalculators/RigPorvSoilSgasResultCalculator.cpp @@ -101,7 +101,7 @@ void RigPorvSoilSgasResultCalculator::calculateSum( const RigEclipseResultAddres void RigPorvSoilSgasResultCalculator::calculate( const RigEclipseResultAddress& in1Addr, const RigEclipseResultAddress& in2Addr, const RigEclipseResultAddress& outAddr, - std::function op ) + std::function operation ) { size_t in1Idx = m_resultsData->findOrLoadKnownScalarResult( in1Addr ); size_t in2Idx = m_resultsData->findOrLoadKnownScalarResult( in2Addr ); @@ -132,7 +132,7 @@ void RigPorvSoilSgasResultCalculator::calculate( const RigEclipseResultAddress& { size_t idx1 = res1ActiveOnly ? resultIndex : nativeResvCellIndex; size_t idx2 = res2ActiveOnly ? resultIndex : nativeResvCellIndex; - outResults[resultIndex] = op( in1Results[idx1], in2Results[idx2] ); + outResults[resultIndex] = operation( in1Results[idx1], in2Results[idx2] ); } } } diff --git a/ApplicationLibCode/ReservoirDataModel/ResultCalculators/RigPorvSoilSgasResultCalculator.h b/ApplicationLibCode/ReservoirDataModel/ResultCalculators/RigPorvSoilSgasResultCalculator.h index 134e44a652..7be14ad53c 100644 --- a/ApplicationLibCode/ReservoirDataModel/ResultCalculators/RigPorvSoilSgasResultCalculator.h +++ b/ApplicationLibCode/ReservoirDataModel/ResultCalculators/RigPorvSoilSgasResultCalculator.h @@ -45,5 +45,5 @@ class RigPorvSoilSgasResultCalculator : public RigEclipseResultCalculator void calculate( const RigEclipseResultAddress& in1Addr, const RigEclipseResultAddress& in2Addr, const RigEclipseResultAddress& outAddr, - std::function op ); + std::function operation ); }; diff --git a/ApplicationLibCode/ReservoirDataModel/RigCaseCellResultsData.cpp b/ApplicationLibCode/ReservoirDataModel/RigCaseCellResultsData.cpp index 379983c996..699df7819c 100644 --- a/ApplicationLibCode/ReservoirDataModel/RigCaseCellResultsData.cpp +++ b/ApplicationLibCode/ReservoirDataModel/RigCaseCellResultsData.cpp @@ -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& resultCategorySearchOrder ) { std::set otherResultTypesToSearch = { RiaDefines::ResultCatType::STATIC_NATIVE, diff --git a/ApplicationLibCode/ReservoirDataModel/RigWellTargetCandidatesGenerator.cpp b/ApplicationLibCode/ReservoirDataModel/RigWellTargetCandidatesGenerator.cpp new file mode 100644 index 0000000000..24baaeaaa5 --- /dev/null +++ b/ApplicationLibCode/ReservoirDataModel/RigWellTargetCandidatesGenerator.cpp @@ -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 +// 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 +#include + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +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 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& pressure = resultsData->cellScalarResults( pressureAddress, timeStepIdx ); + + RigEclipseResultAddress permeabilityXAddress( RiaDefines::ResultCatType::STATIC_NATIVE, "PERMX" ); + resultsData->ensureKnownResultLoaded( permeabilityXAddress ); + const std::vector& permeabilityX = resultsData->cellScalarResults( permeabilityXAddress, 0 ); + + RigEclipseResultAddress permeabilityYAddress( RiaDefines::ResultCatType::STATIC_NATIVE, "PERMY" ); + resultsData->ensureKnownResultLoaded( permeabilityYAddress ); + const std::vector& permeabilityY = resultsData->cellScalarResults( permeabilityYAddress, 0 ); + + RigEclipseResultAddress permeabilityZAddress( RiaDefines::ResultCatType::STATIC_NATIVE, "PERMZ" ); + resultsData->ensureKnownResultLoaded( permeabilityZAddress ); + const std::vector& permeabilityZ = resultsData->cellScalarResults( permeabilityZAddress, 0 ); + + RigEclipseResultAddress transmissibilityXAddress( RiaDefines::ResultCatType::STATIC_NATIVE, "TRANX" ); + resultsData->ensureKnownResultLoaded( transmissibilityXAddress ); + const std::vector& transmissibilityX = resultsData->cellScalarResults( transmissibilityXAddress, 0 ); + + RigEclipseResultAddress transmissibilityYAddress( RiaDefines::ResultCatType::STATIC_NATIVE, "TRANY" ); + resultsData->ensureKnownResultLoaded( transmissibilityYAddress ); + const std::vector& transmissibilityY = resultsData->cellScalarResults( transmissibilityYAddress, 0 ); + + RigEclipseResultAddress transmissibilityZAddress( RiaDefines::ResultCatType::STATIC_NATIVE, "TRANZ" ); + resultsData->ensureKnownResultLoaded( transmissibilityZAddress ); + const std::vector& transmissibilityZ = resultsData->cellScalarResults( transmissibilityZAddress, 0 ); + + std::vector 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 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( 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( view ) ) + { + eclipseView->scheduleReservoirGridGeometryRegen(); + eclipseView->propertyFilterCollection()->updateConnectedEditors(); + } + } + + std::vector 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 RigWellTargetCandidatesGenerator::findStartCell( RimEclipseCase* eclipseCase, + size_t timeStepIdx, + const ClusteringLimits& limits, + const std::vector& volume, + const std::vector& pressure, + const std::vector& permeabilityX, + const std::vector& permeabilityY, + const std::vector& permeabilityZ, + const std::vector& transmissibilityX, + const std::vector& transmissibilityY, + const std::vector& transmissibilityZ, + const std::vector& 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::max(); + double maxVolume = -std::numeric_limits::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::max() ) return {}; + + return eclipseCase->mainGrid()->ijkFromCellIndex( startCell ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RigWellTargetCandidatesGenerator::growCluster( RimEclipseCase* eclipseCase, + const caf::VecIjk& startCell, + const ClusteringLimits& limits, + const std::vector& volume, + const std::vector& pressure, + const std::vector& permeabilityX, + const std::vector& permeabilityY, + const std::vector& permeabilityZ, + const std::vector& transmissibilityX, + const std::vector& transmissibilityY, + const std::vector& transmissibilityZ, + std::vector& 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 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 RigWellTargetCandidatesGenerator::findCandidates( const RimEclipseCase& eclipseCase, + const std::vector& previousCells, + const ClusteringLimits& limits, + const std::vector& volume, + const std::vector& pressure, + const std::vector& permeabilityX, + const std::vector& permeabilityY, + const std::vector& permeabilityZ, + const std::vector& transmissibilityX, + const std::vector& transmissibilityY, + const std::vector& transmissibilityZ, + std::vector& clusters ) +{ + std::vector candidates; + auto resultsData = eclipseCase.results( RiaDefines::PorosityModelType::MATRIX_MODEL ); + + for ( size_t cellIdx : previousCells ) + { + std::vector 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& cells, + std::vector& 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& 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* resultVector = resultsData->modifiableCellScalarResult( resultAddress, 0 ); + resultVector->resize( clusterIds.size(), std::numeric_limits::infinity() ); + + std::fill( resultVector->begin(), resultVector->end(), std::numeric_limits::infinity() ); + + for ( size_t idx = 0; idx < clusterIds.size(); idx++ ) + { + if ( clusterIds[idx] > 0 ) + { + resultVector->at( idx ) = clusterIds[idx]; + } + } + + resultsData->recalculateStatistics( resultAddress ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::optional 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& x, + const std::vector& y, + const std::vector& 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::infinity(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +double RigWellTargetCandidatesGenerator::getTransmissibilityValueForFace( const std::vector& x, + const std::vector& y, + const std::vector& 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 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 + { + 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 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 oilVolume = loadVectorByName( resultsData, getOilVectorName( volumesType ), timeStepIdx ); + std::vector gasVolume = loadVectorByName( resultsData, getGasVectorName( volumesType ), timeStepIdx ); + if ( oilVolume.empty() || gasVolume.empty() || oilVolume.size() != gasVolume.size() ) return volume; + + volume.resize( oilVolume.size(), std::numeric_limits::infinity() ); + for ( size_t i = 0; i < oilVolume.size(); i++ ) + { + volume[i] = oilVolume[i] + gasVolume[i]; + } + } + + return volume; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector + RigWellTargetCandidatesGenerator::generateStatistics( RimEclipseCase* eclipseCase, + const std::vector& pressure, + const std::vector& permeabilityX, + const std::vector& permeabilityY, + const std::vector& permeabilityZ, + int numClustersFound, + size_t timeStepIdx, + const QString& clusterResultName ) +{ + std::vector 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& porv = resultsData->cellScalarResults( porvAddress, 0 ); + + RigEclipseResultAddress porvSoilAddress( RiaDefines::ResultCatType::DYNAMIC_NATIVE, RiaResultNames::riPorvSoil() ); + resultsData->ensureKnownResultLoaded( porvSoilAddress ); + const std::vector& porvSoil = resultsData->cellScalarResults( porvSoilAddress, timeStepIdx ); + + RigEclipseResultAddress porvSgasAddress( RiaDefines::ResultCatType::DYNAMIC_NATIVE, RiaResultNames::riPorvSgas() ); + resultsData->ensureKnownResultLoaded( porvSgasAddress ); + const std::vector& porvSgas = resultsData->cellScalarResults( porvSgasAddress, timeStepIdx ); + + RigEclipseResultAddress porvSoilAndSgasAddress( RiaDefines::ResultCatType::DYNAMIC_NATIVE, RiaResultNames::riPorvSoilSgas() ); + resultsData->ensureKnownResultLoaded( porvSoilAndSgasAddress ); + const std::vector& porvSoilAndSgas = resultsData->cellScalarResults( porvSoilAndSgasAddress, timeStepIdx ); + + RigEclipseResultAddress fipOilAddress( RiaDefines::ResultCatType::DYNAMIC_NATIVE, "FIPOIL" ); + resultsData->ensureKnownResultLoaded( fipOilAddress ); + const std::vector& fipOil = resultsData->cellScalarResults( fipOilAddress, timeStepIdx ); + + RigEclipseResultAddress fipGasAddress( RiaDefines::ResultCatType::DYNAMIC_NATIVE, "FIPGAS" ); + resultsData->ensureKnownResultLoaded( fipGasAddress ); + const std::vector& fipGas = resultsData->cellScalarResults( fipGasAddress, timeStepIdx ); + + RigEclipseResultAddress clusterAddress( RiaDefines::ResultCatType::GENERATED, clusterResultName ); + resultsData->ensureKnownResultLoaded( clusterAddress ); + const std::vector& clusterIds = resultsData->cellScalarResults( clusterAddress, 0 ); + + std::vector> permeabilityCalculators( numClustersFound ); + std::vector> pressureCalculators( numClustersFound ); + + for ( size_t idx = 0; idx < clusterIds.size(); idx++ ) + { + if ( !std::isinf( clusterIds[idx] ) && static_cast( clusterIds[idx] ) > 0 ) + { + size_t i = clusterIds[idx] - 1; + if ( i < static_cast( 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; +} diff --git a/ApplicationLibCode/ReservoirDataModel/RigWellTargetCandidatesGenerator.h b/ApplicationLibCode/ReservoirDataModel/RigWellTargetCandidatesGenerator.h new file mode 100644 index 0000000000..0f93e9b875 --- /dev/null +++ b/ApplicationLibCode/ReservoirDataModel/RigWellTargetCandidatesGenerator.h @@ -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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "cafVecIjk.h" + +#include "cvfStructGrid.h" + +#include + +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 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 findStartCell( RimEclipseCase* eclipseCase, + size_t timeStepIdx, + const ClusteringLimits& limits, + const std::vector& volume, + const std::vector& pressure, + const std::vector& permeabilityX, + const std::vector& permeabilityY, + const std::vector& permeabilityZ, + const std::vector& transmissibilityX, + const std::vector& transmissibilityY, + const std::vector& transmissibilityZ, + const std::vector& clusters ); + + static void growCluster( RimEclipseCase* eclipseCase, + const caf::VecIjk& startCell, + const ClusteringLimits& limits, + const std::vector& volume, + const std::vector& pressure, + const std::vector& permeabilityX, + const std::vector& permeabilityY, + const std::vector& permeabilityZ, + const std::vector& transmissibilityX, + const std::vector& transmissibilityY, + const std::vector& transmissibilityZ, + std::vector& clusters, + int clusterId, + size_t timeStepIdx, + int maxIterations ); + + static std::vector findCandidates( const RimEclipseCase& eclipseCase, + const std::vector& previousCells, + const ClusteringLimits& limits, + const std::vector& volume, + const std::vector& pressure, + const std::vector& permeabilityX, + const std::vector& permeabilityY, + const std::vector& permeabilityZ, + const std::vector& transmissibilityX, + const std::vector& transmissibilityY, + const std::vector& transmissibilityZ, + std::vector& clusters ); + + static void assignClusterIdToCells( const RigActiveCellInfo& activeCellInfo, + const std::vector& cells, + std::vector& clusters, + int clusterId ); + + static std::optional getActiveCellCount( RimEclipseCase* eclipseCase ); + + static void createResultVector( RimEclipseCase& eclipseCase, const QString& resultName, const std::vector& clusterIds ); + + static double getValueForFace( const std::vector& x, + const std::vector& y, + const std::vector& z, + cvf::StructGridInterface::FaceType face, + size_t resultIndex ); + + static double getTransmissibilityValueForFace( const std::vector& x, + const std::vector& y, + const std::vector& z, + cvf::StructGridInterface::FaceType face, + size_t resultIndex, + size_t neighborResultIndex ); + + static std::vector generateStatistics( RimEclipseCase* eclipseCase, + const std::vector& pressure, + const std::vector& permeabilityX, + const std::vector& permeabilityY, + const std::vector& permeabilityZ, + int numClustersFound, + size_t timeStepIdx, + const QString& clusterResultName ); +};