diff --git a/ApplicationLibCode/FileInterface/CMakeLists_files.cmake b/ApplicationLibCode/FileInterface/CMakeLists_files.cmake index 234e9ec5ab..18dbb395f0 100644 --- a/ApplicationLibCode/FileInterface/CMakeLists_files.cmake +++ b/ApplicationLibCode/FileInterface/CMakeLists_files.cmake @@ -102,6 +102,7 @@ set(SOURCE_GROUP_HEADER_FILES ${CMAKE_CURRENT_LIST_DIR}/RifAsciiDataParseOptions.h ${CMAKE_CURRENT_LIST_DIR}/RifByteArrayArrowRandomAccessFile.h ${CMAKE_CURRENT_LIST_DIR}/RifArrowTools.h + ${CMAKE_CURRENT_LIST_DIR}/RifReaderRegularGridModel.h ) set(SOURCE_GROUP_SOURCE_FILES @@ -201,6 +202,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RifOsduWellLogReader.cpp ${CMAKE_CURRENT_LIST_DIR}/RifByteArrayArrowRandomAccessFile.cpp ${CMAKE_CURRENT_LIST_DIR}/RifArrowTools.cpp + ${CMAKE_CURRENT_LIST_DIR}/RifReaderRegularGridModel.cpp ) list(APPEND CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES}) diff --git a/ApplicationLibCode/FileInterface/RifReaderRegularGridModel.cpp b/ApplicationLibCode/FileInterface/RifReaderRegularGridModel.cpp new file mode 100644 index 0000000000..eed8e8f674 --- /dev/null +++ b/ApplicationLibCode/FileInterface/RifReaderRegularGridModel.cpp @@ -0,0 +1,104 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 "RifReaderRegularGridModel.h" + +#include "RigCaseCellResultsData.h" +#include "RigEclipseCaseData.h" + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RifReaderRegularGridModel::RifReaderRegularGridModel() + : m_reservoir( nullptr ) +{ +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RifReaderRegularGridModel::~RifReaderRegularGridModel() +{ +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RifReaderRegularGridModel::open( const QString& fileName, RigEclipseCaseData* eclipseCase ) +{ + m_reservoirBuilder.createGridsAndCells( eclipseCase ); + m_reservoir = eclipseCase; + return true; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RifReaderRegularGridModel::inputProperty( const QString& propertyName, std::vector* values ) + +{ + CAF_ASSERT( false ); + return true; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RifReaderRegularGridModel::staticResult( const QString& result, RiaDefines::PorosityModelType matrixOrFracture, std::vector* values ) +{ + CAF_ASSERT( false ); + return true; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RifReaderRegularGridModel::dynamicResult( const QString& result, + RiaDefines::PorosityModelType matrixOrFracture, + size_t stepIndex, + std::vector* values ) +{ + CAF_ASSERT( false ); + return true; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RifReaderRegularGridModel::setWorldCoordinates( cvf::Vec3d minWorldCoordinate, cvf::Vec3d maxWorldCoordinate ) +{ + m_reservoirBuilder.setWorldCoordinates( minWorldCoordinate, maxWorldCoordinate ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RifReaderRegularGridModel::setGridPointDimensions( const cvf::Vec3st& gridPointDimensions ) +{ + m_reservoirBuilder.setIJKCount( gridPointDimensions ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RifReaderRegularGridModel::addLocalGridRefinement( const cvf::Vec3st& minCellPosition, + const cvf::Vec3st& maxCellPosition, + const cvf::Vec3st& singleCellRefinementFactors ) +{ + m_reservoirBuilder.addLocalGridRefinement( minCellPosition, maxCellPosition, singleCellRefinementFactors ); +} diff --git a/ApplicationLibCode/FileInterface/RifReaderRegularGridModel.h b/ApplicationLibCode/FileInterface/RifReaderRegularGridModel.h new file mode 100644 index 0000000000..e048885a28 --- /dev/null +++ b/ApplicationLibCode/FileInterface/RifReaderRegularGridModel.h @@ -0,0 +1,47 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 "RifReaderInterface.h" +#include "RigReservoirBuilder.h" + +class RifReaderRegularGridModel : public RifReaderInterface +{ +public: + RifReaderRegularGridModel(); + ~RifReaderRegularGridModel() override; + + void setWorldCoordinates( cvf::Vec3d minWorldCoordinate, cvf::Vec3d maxWorldCoordinate ); + void setGridPointDimensions( const cvf::Vec3st& gridPointDimensions ); + + void addLocalGridRefinement( const cvf::Vec3st& minCellPosition, + const cvf::Vec3st& maxCellPosition, + const cvf::Vec3st& singleCellRefinementFactors ); + + bool open( const QString& fileName, RigEclipseCaseData* eclipseCase ) override; + + bool staticResult( const QString& result, RiaDefines::PorosityModelType matrixOrFracture, std::vector* values ) override; + bool dynamicResult( const QString& result, RiaDefines::PorosityModelType matrixOrFracture, size_t stepIndex, std::vector* values ) override; + +private: + bool inputProperty( const QString& propertyName, std::vector* values ); + + RigReservoirBuilder m_reservoirBuilder; + RigEclipseCaseData* m_reservoir; +}; diff --git a/ApplicationLibCode/ProjectDataModel/CMakeLists_files.cmake b/ApplicationLibCode/ProjectDataModel/CMakeLists_files.cmake index 67290883d1..8eb0e645e5 100644 --- a/ApplicationLibCode/ProjectDataModel/CMakeLists_files.cmake +++ b/ApplicationLibCode/ProjectDataModel/CMakeLists_files.cmake @@ -137,6 +137,7 @@ set(SOURCE_GROUP_HEADER_FILES ${CMAKE_CURRENT_LIST_DIR}/RimStatisticsContourMap.h ${CMAKE_CURRENT_LIST_DIR}/RimStatisticsContourMapProjection.h ${CMAKE_CURRENT_LIST_DIR}/RimStatisticsContourMapView.h + ${CMAKE_CURRENT_LIST_DIR}/RimRegularGridCase.h ) set(SOURCE_GROUP_SOURCE_FILES @@ -274,6 +275,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RimStatisticsContourMap.cpp ${CMAKE_CURRENT_LIST_DIR}/RimStatisticsContourMapProjection.cpp ${CMAKE_CURRENT_LIST_DIR}/RimStatisticsContourMapView.cpp + ${CMAKE_CURRENT_LIST_DIR}/RimRegularGridCase.cpp ) if(RESINSIGHT_USE_QT_CHARTS) diff --git a/ApplicationLibCode/ProjectDataModel/RimRegularGridCase.cpp b/ApplicationLibCode/ProjectDataModel/RimRegularGridCase.cpp new file mode 100644 index 0000000000..821e169a1c --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/RimRegularGridCase.cpp @@ -0,0 +1,87 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 "RimRegularGridCase.h" + +#include "RifReaderRegularGridModel.h" + +#include "RigCaseCellResultsData.h" +#include "RigEclipseCaseData.h" + +CAF_PDM_SOURCE_INIT( RimRegularGridCase, "EclipseBoundingBoxCase" ); +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimRegularGridCase::RimRegularGridCase() +{ + CAF_PDM_InitObject( "Bounding Box Case", ":/Case48x48.png", "", "Bounding Box Case" ); + + CAF_PDM_InitFieldNoDefault( &m_minimum, "Minimum", "Minimum" ); + m_minimum.uiCapability()->setUiReadOnly( true ); + + CAF_PDM_InitFieldNoDefault( &m_maximum, "Maximum", "Maximum" ); + m_maximum.uiCapability()->setUiReadOnly( true ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- + +void RimRegularGridCase::setBoundingBox( const cvf::BoundingBox& boundingBox ) +{ + m_minimum = boundingBox.min(); + m_maximum = boundingBox.max(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +cvf::ref RimRegularGridCase::createModel( QString modelName ) +{ + cvf::ref reader = new RifReaderRegularGridModel; + cvf::ref reservoir = new RigEclipseCaseData( this ); + + reader->setWorldCoordinates( m_minimum, m_maximum ); + + cvf::Vec3st gridPointDimensions( 50, 50, 10 ); + reader->setGridPointDimensions( gridPointDimensions ); + + reader->open( "", reservoir.p() ); + + setReservoirData( reservoir.p() ); + + return reader.p(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RimRegularGridCase::openEclipseGridFile() +{ + if ( eclipseCaseData() ) return true; + + auto readerInterface = createModel( "" ); + + results( RiaDefines::PorosityModelType::MATRIX_MODEL )->setReaderInterface( readerInterface.p() ); + results( RiaDefines::PorosityModelType::FRACTURE_MODEL )->setReaderInterface( readerInterface.p() ); + + computeCachedData(); + results( RiaDefines::PorosityModelType::MATRIX_MODEL )->computeCellVolumes(); + + return true; +} diff --git a/ApplicationLibCode/ProjectDataModel/RimRegularGridCase.h b/ApplicationLibCode/ProjectDataModel/RimRegularGridCase.h new file mode 100644 index 0000000000..898c388bf0 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/RimRegularGridCase.h @@ -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. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "RiaDefines.h" + +#include "RimEclipseResultCase.h" + +#include "cafPdmFieldCvfVec3d.h" +#include "cvfBoundingBox.h" + +//================================================================================================== +// +// +// +//================================================================================================== +class RimRegularGridCase : public RimEclipseResultCase +{ + CAF_PDM_HEADER_INIT; + +public: + RimRegularGridCase(); + + void setBoundingBox( const cvf::BoundingBox& boundingBox ); + + bool openEclipseGridFile() override; + + cvf::ref createModel( QString modelName ); + +private: + caf::PdmField m_minimum; + caf::PdmField m_maximum; +}; diff --git a/ApplicationLibCode/ProjectDataModel/RimWellTargetCandidatesGenerator.cpp b/ApplicationLibCode/ProjectDataModel/RimWellTargetCandidatesGenerator.cpp index fe4cdf04fb..d5b042cac4 100644 --- a/ApplicationLibCode/ProjectDataModel/RimWellTargetCandidatesGenerator.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimWellTargetCandidatesGenerator.cpp @@ -18,21 +18,28 @@ #include "RimWellTargetCandidatesGenerator.h" +#include "RiaGuiApplication.h" #include "RiaLogging.h" #include "RiaPorosityModel.h" #include "RiaResultNames.h" +#include "RiuMainWindow.h" #include "RigCaseCellResultsData.h" #include "RigEclipseResultAddress.h" +#include "RimRegularGridCase.h" #include "Well/RigWellTargetCandidatesGenerator.h" #include "RimEclipseCase.h" +#include "RimEclipseCaseCollection.h" #include "RimEclipseCaseEnsemble.h" +#include "RimEclipseCellColors.h" #include "RimEclipseView.h" +#include "RimOilField.h" #include "RimProject.h" #include "RimTools.h" #include "cafPdmUiDoubleSliderEditor.h" +#include "cafPdmUiPushButtonEditor.h" #include "cafPdmUiSliderTools.h" #include "cvfMath.h" @@ -100,6 +107,9 @@ RimWellTargetCandidatesGenerator::RimWellTargetCandidatesGenerator() CAF_PDM_InitField( &m_maxIterations, "Iterations", 10000, "Max Iterations" ); CAF_PDM_InitField( &m_maxClusters, "MaxClusters", 5, "Max Clusters" ); + CAF_PDM_InitField( &m_generateEnsembleStatistics, "GenerateEnsembleStatistics", true, "Generate Ensemble Statistics" ); + caf::PdmUiPushButtonEditor::configureEditorLabelHidden( &m_generateEnsembleStatistics ); + m_minimumVolume = cvf::UNDEFINED_DOUBLE; m_maximumVolume = cvf::UNDEFINED_DOUBLE; @@ -126,8 +136,12 @@ RimWellTargetCandidatesGenerator::~RimWellTargetCandidatesGenerator() void RimWellTargetCandidatesGenerator::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) { updateAllBoundaries(); + generateCandidates( firstCase() ); - generateCandidates(); + if ( changedField == &m_generateEnsembleStatistics ) + { + generateEnsembleStatistics(); + } } //-------------------------------------------------------------------------------------------------- @@ -139,12 +153,9 @@ QList RimWellTargetCandidatesGenerator::calculateValueOp if ( fieldNeedingOptions == &m_timeStep ) { - auto ensemble = firstAncestorOrThisOfType(); - if ( ensemble && !ensemble->cases().empty() ) + if ( auto fc = firstCase() ) { - RimEclipseCase* eclipseCase = ensemble->cases().front(); - - RimTools::timeStepsForCase( eclipseCase, &options ); + RimTools::timeStepsForCase( fc, &options ); } } @@ -257,29 +268,62 @@ void RimWellTargetCandidatesGenerator::defineEditorAttribute( const caf::PdmFiel doubleAttributes->m_decimals = 3; } } + + if ( field == &m_generateEnsembleStatistics ) + { + caf::PdmUiPushButtonEditorAttribute* attrib = dynamic_cast( attribute ); + if ( attrib ) + { + attrib->m_buttonText = "Create Ensemble Statistics"; + } + } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -void RimWellTargetCandidatesGenerator::generateCandidates() +void RimWellTargetCandidatesGenerator::generateCandidates( RimEclipseCase* eclipseCase ) +{ + RigWellTargetCandidatesGenerator::ClusteringLimits limits = getClusteringLimits(); + RigWellTargetCandidatesGenerator::generateCandidates( eclipseCase, m_timeStep(), m_volumeType(), m_volumesType(), m_volumeResultType(), limits ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellTargetCandidatesGenerator::generateEnsembleStatistics() { auto ensemble = firstAncestorOrThisOfType(); if ( !ensemble ) return; - if ( ensemble->cases().empty() ) return; + RigWellTargetCandidatesGenerator::ClusteringLimits limits = getClusteringLimits(); + RimRegularGridCase* regularGridCase = RigWellTargetCandidatesGenerator::generateEnsembleCandidates( *ensemble, + m_timeStep(), + m_volumeType(), + m_volumesType(), + m_volumeResultType(), + limits ); - RimEclipseCase* eclipseCase = ensemble->cases().front(); + RimProject* project = RimProject::current(); + if ( !project ) return; - 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; + RimEclipseCaseCollection* analysisModels = project->activeOilField() ? project->activeOilField()->analysisModels() : nullptr; + if ( !analysisModels ) return; - RigWellTargetCandidatesGenerator::generateCandidates( eclipseCase, m_timeStep(), m_volumeType(), m_volumesType(), m_volumeResultType(), limits ); + analysisModels->cases.push_back( regularGridCase ); + + auto eclipseView = regularGridCase->createAndAddReservoirView(); + + eclipseView->cellResult()->setResultType( RiaDefines::ResultCatType::GENERATED ); + + if ( RiaGuiApplication::isRunning() || RiuMainWindow::instance() ) + { + RiuMainWindow::instance()->selectAsCurrentItem( eclipseView->cellResult() ); + } + + eclipseView->loadDataAndUpdate(); + + analysisModels->updateConnectedEditors(); } //-------------------------------------------------------------------------------------------------- @@ -294,3 +338,29 @@ void RimWellTargetCandidatesGenerator::defineUiOrdering( QString uiConfigName, c updateAllBoundaries(); } } + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RigWellTargetCandidatesGenerator::ClusteringLimits RimWellTargetCandidatesGenerator::getClusteringLimits() const +{ + 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; + + return limits; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimEclipseCase* RimWellTargetCandidatesGenerator::firstCase() const +{ + auto ensemble = firstAncestorOrThisOfType(); + if ( !ensemble || ensemble->cases().empty() ) return nullptr; + return ensemble->cases()[0]; +} diff --git a/ApplicationLibCode/ProjectDataModel/RimWellTargetCandidatesGenerator.h b/ApplicationLibCode/ProjectDataModel/RimWellTargetCandidatesGenerator.h index b9334226bf..7deb3f50be 100644 --- a/ApplicationLibCode/ProjectDataModel/RimWellTargetCandidatesGenerator.h +++ b/ApplicationLibCode/ProjectDataModel/RimWellTargetCandidatesGenerator.h @@ -21,9 +21,12 @@ #include "cafAppEnum.h" #include "cafPdmField.h" #include "cafPdmObject.h" +#include "cafPdmPtrField.h" #include "Well/RigWellTargetCandidatesGenerator.h" +class RimEclipseCase; + //================================================================================================== /// /// @@ -43,10 +46,16 @@ class RimWellTargetCandidatesGenerator : public caf::PdmObject void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override; private: - void generateCandidates(); + void generateCandidates( RimEclipseCase* eclipseCase ); void updateAllBoundaries(); + void generateEnsembleStatistics(); + + RimEclipseCase* firstCase() const; + + RigWellTargetCandidatesGenerator::ClusteringLimits getClusteringLimits() const; - caf::PdmField m_timeStep; + caf::PdmPtrField m_targetCase; + caf::PdmField m_timeStep; caf::PdmField> m_volumeType; caf::PdmField> m_volumeResultType; @@ -57,8 +66,9 @@ class RimWellTargetCandidatesGenerator : public caf::PdmObject caf::PdmField m_permeability; caf::PdmField m_transmissibility; - caf::PdmField m_maxIterations; - caf::PdmField m_maxClusters; + caf::PdmField m_maxIterations; + caf::PdmField m_maxClusters; + caf::PdmField m_generateEnsembleStatistics; double m_minimumVolume; double m_maximumVolume; diff --git a/ApplicationLibCode/ReservoirDataModel/Well/RigWellTargetCandidatesGenerator.cpp b/ApplicationLibCode/ReservoirDataModel/Well/RigWellTargetCandidatesGenerator.cpp index 718512f32f..717890fe6f 100644 --- a/ApplicationLibCode/ReservoirDataModel/Well/RigWellTargetCandidatesGenerator.cpp +++ b/ApplicationLibCode/ReservoirDataModel/Well/RigWellTargetCandidatesGenerator.cpp @@ -27,16 +27,20 @@ #include "RigCaseCellResultsData.h" #include "RigEclipseResultAddress.h" #include "RigMainGrid.h" +#include "RigStatisticsMath.h" #include "RimEclipseCase.h" #include "RimEclipseCaseEnsemble.h" #include "RimEclipseView.h" #include "RimProject.h" #include "RimPropertyFilterCollection.h" +#include "RimRegularGridCase.h" #include "RimTools.h" +#include "cafProgressInfo.h" #include "cafVecIjk.h" +#include "cvfBoundingBox.h" #include "cvfMath.h" #include "cvfStructGrid.h" @@ -53,6 +57,8 @@ void RigWellTargetCandidatesGenerator::generateCandidates( RimEclipseCase* VolumeResultType volumeResultType, const ClusteringLimits& limits ) { + if ( !eclipseCase->ensureReservoirCaseIsOpen() ) return; + auto activeCellCount = getActiveCellCount( eclipseCase ); if ( !activeCellCount ) { @@ -174,6 +180,22 @@ void RigWellTargetCandidatesGenerator::generateCandidates( RimEclipseCase* std::vector statistics = generateStatistics( eclipseCase, pressure, permeabilityX, permeabilityY, permeabilityZ, numClustersFound, timeStepIdx, resultName ); + std::vector totalPorvSoil( clusters.size(), std::numeric_limits::infinity() ); + std::vector totalPorvSgas( clusters.size(), std::numeric_limits::infinity() ); + std::vector totalPorvSoilAndSgas( clusters.size(), std::numeric_limits::infinity() ); + std::vector totalFipOil( clusters.size(), std::numeric_limits::infinity() ); + std::vector totalFipGas( clusters.size(), std::numeric_limits::infinity() ); + + auto addValuesForClusterId = []( std::vector& values, const std::vector& clusters, int clusterId, double value ) + { +#pragma omp parallel for + for ( int i = 0; i < static_cast( clusters.size() ); i++ ) + { + if ( clusters[i] == clusterId ) values[i] = value; + } + }; + + int clusterId = 1; for ( auto s : statistics ) { RiaLogging::info( QString( "Cluster #%1 Statistics" ).arg( s.id ) ); @@ -185,7 +207,30 @@ void RigWellTargetCandidatesGenerator::generateCandidates( RimEclipseCase* 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 ) ); + + addValuesForClusterId( totalPorvSoil, clusters, clusterId, s.totalPorvSoil ); + addValuesForClusterId( totalPorvSgas, clusters, clusterId, s.totalPorvSgas ); + addValuesForClusterId( totalPorvSoilAndSgas, clusters, clusterId, s.totalPorvSoilAndSgas ); + addValuesForClusterId( totalFipOil, clusters, clusterId, s.totalFipOil ); + addValuesForClusterId( totalFipGas, clusters, clusterId, s.totalFipGas ); + + clusterId++; } + + QString clusterPorvSoil = "TOTAL_PORV_SOIL"; + createResultVector( *eclipseCase, clusterPorvSoil, totalPorvSoil ); + + QString clusterPorvSgas = "TOTAL_PORV_SGAS"; + createResultVector( *eclipseCase, clusterPorvSgas, totalPorvSgas ); + + QString clusterPorvSoilAndSgas = "TOTAL_PORV_SOIL_SGAS"; + createResultVector( *eclipseCase, clusterPorvSoilAndSgas, totalPorvSoilAndSgas ); + + QString clusterFipOil = "TOTAL_FIPOIL"; + createResultVector( *eclipseCase, clusterFipOil, totalFipOil ); + + QString clusterFipGas = "TOTAL_FIPGAS"; + createResultVector( *eclipseCase, clusterFipGas, totalFipGas ); } //-------------------------------------------------------------------------------------------------- @@ -401,6 +446,58 @@ void RigWellTargetCandidatesGenerator::createResultVector( RimEclipseCase& resultsData->recalculateStatistics( resultAddress ); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RigWellTargetCandidatesGenerator::createResultVector( RimEclipseCase& eclipseCase, const QString& resultName, const std::vector& values ) +{ + RigEclipseResultAddress resultAddress( RiaDefines::ResultCatType::GENERATED, resultName ); + + auto resultsData = eclipseCase.results( RiaDefines::PorosityModelType::MATRIX_MODEL ); + + resultsData->addStaticScalarResult( RiaDefines::ResultCatType::GENERATED, resultName, false, values.size() ); + + std::vector* resultVector = resultsData->modifiableCellScalarResult( resultAddress, 0 ); + resultVector->resize( values.size(), std::numeric_limits::infinity() ); + + for ( size_t idx = 0; idx < values.size(); idx++ ) + { + resultVector->at( idx ) = values[idx]; + } + + resultsData->recalculateStatistics( resultAddress ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RigWellTargetCandidatesGenerator::createResultVector( RimEclipseCase& eclipseCase, + const QString& resultName, + const std::vector& clusterIds, + double value ) +{ + 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 ) = value; + } + } + + resultsData->recalculateStatistics( resultAddress ); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -602,3 +699,218 @@ std::vector return statistics; } + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimRegularGridCase* RigWellTargetCandidatesGenerator::generateEnsembleCandidates( RimEclipseCaseEnsemble& ensemble, + size_t timeStepIdx, + VolumeType volumeType, + VolumesType volumesType, + VolumeResultType volumeResultType, + const ClusteringLimits& limits ) +{ + RiaLogging::debug( "Generating ensemble statistics" ); + + caf::ProgressInfo progInfo( ensemble.cases().size() * 2, "Generating ensemble statistics" ); + + for ( auto eclipseCase : ensemble.cases() ) + { + auto task = progInfo.task( "Generating realization statistics.", 1 ); + + generateCandidates( eclipseCase, timeStepIdx, volumeType, volumesType, volumeResultType, limits ); + } + + cvf::BoundingBox boundingBox; + for ( auto eclipseCase : ensemble.cases() ) + { + cvf::BoundingBox bb = computeBoundingBoxForResult( *eclipseCase, "CLUSTERS_NUM", 0 ); + boundingBox.add( bb ); + } + + RiaLogging::debug( + QString( "Clusters bounding box min: [%1 %2 %3]" ).arg( boundingBox.min().x() ).arg( boundingBox.min().y() ).arg( boundingBox.min().z() ) ); + RiaLogging::debug( + QString( "Clusters bounding box max: [%1 %2 %3]" ).arg( boundingBox.max().x() ).arg( boundingBox.max().y() ).arg( boundingBox.max().z() ) ); + + RimRegularGridCase* targetCase = new RimRegularGridCase; + targetCase->setBoundingBox( boundingBox ); + targetCase->createModel( "" ); + + std::vector occupancy; + + std::map>> resultNamesAndSamples; + resultNamesAndSamples["TOTAL_PORV_SOIL"] = {}; + resultNamesAndSamples["TOTAL_PORV_SGAS"] = {}; + resultNamesAndSamples["TOTAL_PORV_SOIL_SGAS"] = {}; + resultNamesAndSamples["TOTAL_FIPOIL"] = {}; + resultNamesAndSamples["TOTAL_FIPGAS"] = {}; + + for ( auto eclipseCase : ensemble.cases() ) + { + auto task = progInfo.task( "Accumulating results.", 1 ); + + accumulateResultsForSingleCase( *eclipseCase, *targetCase, resultNamesAndSamples, occupancy ); + } + + createResultVector( *targetCase, "OCCUPANCY", occupancy ); + + for ( auto [resultName, vec] : resultNamesAndSamples ) + { + computeStatisticsAndCreateVectors( *targetCase, resultName, vec ); + } + + return targetCase; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RigWellTargetCandidatesGenerator::computeStatisticsAndCreateVectors( RimEclipseCase& targetCase, + const QString& resultName, + const std::vector>& vec ) +{ + const RigCaseCellResultsData* targetResultsData = targetCase.results( RiaDefines::PorosityModelType::MATRIX_MODEL ); + + const RigActiveCellInfo* targetActiveCellInfo = targetResultsData->activeCellInfo(); + + const size_t targetNumActiveCells = targetActiveCellInfo->reservoirActiveCellCount(); + + int nCells = static_cast( targetNumActiveCells ); + std::vector p10Results( nCells, std::numeric_limits::infinity() ); + std::vector p50Results( nCells, std::numeric_limits::infinity() ); + std::vector p90Results( nCells, std::numeric_limits::infinity() ); + std::vector meanResults( nCells, std::numeric_limits::infinity() ); + std::vector minResults( nCells, std::numeric_limits::infinity() ); + std::vector maxResults( nCells, std::numeric_limits::infinity() ); + +#pragma omp parallel for + for ( int i = 0; i < nCells; i++ ) + { + size_t numSamples = vec.size(); + std::vector samples( numSamples, 0.0 ); + for ( size_t s = 0; s < numSamples; s++ ) + samples[s] = vec[s][i]; + + double p10, p50, p90, mean; + RigStatisticsMath::calculateStatisticsCurves( samples, &p10, &p50, &p90, &mean, RigStatisticsMath::PercentileStyle::SWITCHED ); + + if ( RiaStatisticsTools::isValidNumber( p10 ) ) p10Results[i] = p10; + if ( RiaStatisticsTools::isValidNumber( p50 ) ) p50Results[i] = p50; + if ( RiaStatisticsTools::isValidNumber( p90 ) ) p90Results[i] = p90; + if ( RiaStatisticsTools::isValidNumber( mean ) ) meanResults[i] = mean; + + double minValue = RiaStatisticsTools::minimumValue( samples ); + if ( RiaStatisticsTools::isValidNumber( minValue ) && minValue < std::numeric_limits::max() ) minResults[i] = minValue; + + double maxValue = RiaStatisticsTools::maximumValue( samples ); + if ( RiaStatisticsTools::isValidNumber( maxValue ) && maxValue > -std::numeric_limits::max() ) maxResults[i] = maxValue; + } + + createResultVector( targetCase, "ENSEMBLE_" + resultName + "_P10", p10Results ); + createResultVector( targetCase, "ENSEMBLE_" + resultName + "_P50", p50Results ); + createResultVector( targetCase, "ENSEMBLE_" + resultName + "_P90", p90Results ); + createResultVector( targetCase, "ENSEMBLE_" + resultName + "_MEAN", meanResults ); + createResultVector( targetCase, "ENSEMBLE_" + resultName + "_MIN", minResults ); + createResultVector( targetCase, "ENSEMBLE_" + resultName + "_MAX", maxResults ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RigWellTargetCandidatesGenerator::accumulateResultsForSingleCase( RimEclipseCase& eclipseCase, + RimEclipseCase& targetCase, + std::map>>& resultNamesAndSamples, + std::vector& occupancy ) +{ + RigCaseCellResultsData* resultsData = eclipseCase.results( RiaDefines::PorosityModelType::MATRIX_MODEL ); + const RigMainGrid* mainGrid = eclipseCase.mainGrid(); + const RigActiveCellInfo* activeCellInfo = resultsData->activeCellInfo(); + + const RigCaseCellResultsData* targetResultsData = targetCase.results( RiaDefines::PorosityModelType::MATRIX_MODEL ); + + const RigActiveCellInfo* targetActiveCellInfo = targetResultsData->activeCellInfo(); + + const size_t targetNumReservoirCells = targetActiveCellInfo->reservoirCellCount(); + const size_t targetNumActiveCells = targetActiveCellInfo->reservoirActiveCellCount(); + + occupancy.resize( targetNumActiveCells, 0 ); + + RigEclipseResultAddress clustersNumAddress( RiaDefines::ResultCatType::GENERATED, "CLUSTERS_NUM" ); + resultsData->ensureKnownResultLoaded( clustersNumAddress ); + const std::vector& clusterNum = resultsData->cellScalarResults( clustersNumAddress, 0 ); + + std::map*> namedInputVector; + + for ( auto [resultName, vec] : resultNamesAndSamples ) + { + RigEclipseResultAddress resultAddress( RiaDefines::ResultCatType::GENERATED, resultName ); + resultsData->ensureKnownResultLoaded( resultAddress ); + const std::vector& resultVector = resultsData->cellScalarResults( resultAddress, 0 ); + namedInputVector[resultName] = &resultVector; + } + + std::map> namedOutputVector; + for ( auto [resultName, vec] : resultNamesAndSamples ) + { + namedOutputVector[resultName] = std::vector( targetNumActiveCells, std::numeric_limits::infinity() ); + } + + for ( size_t targetCellIdx = 0; targetCellIdx < targetNumReservoirCells; targetCellIdx++ ) + { + const RigCell& nativeCell = targetCase.mainGrid()->cell( targetCellIdx ); + cvf::Vec3d cellCenter = nativeCell.center(); + + size_t targetResultIndex = targetActiveCellInfo->cellResultIndex( targetCellIdx ); + + size_t cellIdx = mainGrid->findReservoirCellIndexFromPoint( cellCenter ); + if ( cellIdx != cvf::UNDEFINED_SIZE_T && activeCellInfo->isActive( cellIdx ) && targetResultIndex != cvf::UNDEFINED_SIZE_T ) + { + size_t resultIndex = resultsData->activeCellInfo()->cellResultIndex( cellIdx ); + if ( !std::isinf( clusterNum[resultIndex] ) && clusterNum[resultIndex] > 0 ) + { + occupancy[targetResultIndex]++; + for ( auto [resultName, vec] : resultNamesAndSamples ) + { + namedOutputVector[resultName][targetResultIndex] = namedInputVector[resultName]->at( resultIndex ); + } + } + } + } + + for ( auto [resultName, vec] : resultNamesAndSamples ) + { + resultNamesAndSamples[resultName].push_back( namedOutputVector[resultName] ); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +cvf::BoundingBox RigWellTargetCandidatesGenerator::computeBoundingBoxForResult( RimEclipseCase& eclipseCase, + const QString& resultName, + size_t timeStepIndex ) +{ + RigCaseCellResultsData* resultsData = eclipseCase.results( RiaDefines::PorosityModelType::MATRIX_MODEL ); + const RigMainGrid* mainGrid = eclipseCase.mainGrid(); + const RigActiveCellInfo* activeCellInfo = resultsData->activeCellInfo(); + const size_t numReservoirCells = activeCellInfo->reservoirCellCount(); + + RigEclipseResultAddress clustersNumAddress( RiaDefines::ResultCatType::GENERATED, resultName ); + resultsData->ensureKnownResultLoaded( clustersNumAddress ); + const std::vector& clusterNum = resultsData->cellScalarResults( clustersNumAddress, timeStepIndex ); + + cvf::BoundingBox boundingBox; + for ( size_t reservoirCellIndex = 0; reservoirCellIndex < numReservoirCells; reservoirCellIndex++ ) + { + size_t targetResultIndex = activeCellInfo->cellResultIndex( reservoirCellIndex ); + if ( reservoirCellIndex != cvf::UNDEFINED_SIZE_T && activeCellInfo->isActive( reservoirCellIndex ) && + targetResultIndex != cvf::UNDEFINED_SIZE_T && !std::isinf( clusterNum[targetResultIndex] ) && clusterNum[targetResultIndex] > 0 ) + { + const RigCell& nativeCell = mainGrid->cell( reservoirCellIndex ); + boundingBox.add( nativeCell.boundingBox() ); + } + } + + return boundingBox; +} diff --git a/ApplicationLibCode/ReservoirDataModel/Well/RigWellTargetCandidatesGenerator.h b/ApplicationLibCode/ReservoirDataModel/Well/RigWellTargetCandidatesGenerator.h index 0f93e9b875..09be5daa1c 100644 --- a/ApplicationLibCode/ReservoirDataModel/Well/RigWellTargetCandidatesGenerator.h +++ b/ApplicationLibCode/ReservoirDataModel/Well/RigWellTargetCandidatesGenerator.h @@ -20,13 +20,18 @@ #include "cafVecIjk.h" +#include "cvfBoundingBox.h" #include "cvfStructGrid.h" +#include #include class RigActiveCellInfo; class RigCaseCellResultsData; class RimEclipseCase; +class RimEclipseCaseEnsemble; +class RimRegularGridCase; + //================================================================================================== /// /// @@ -77,6 +82,13 @@ class RigWellTargetCandidatesGenerator VolumeResultType volumeResultType, size_t timeStepIdx ); + static RimRegularGridCase* generateEnsembleCandidates( RimEclipseCaseEnsemble& ensemble, + size_t timeStepIdx, + VolumeType volumeType, + VolumesType volumesType, + VolumeResultType volumeResultType, + const ClusteringLimits& limits ); + class ClusterStatistics { public: @@ -156,6 +168,10 @@ class RigWellTargetCandidatesGenerator static void createResultVector( RimEclipseCase& eclipseCase, const QString& resultName, const std::vector& clusterIds ); + static void createResultVector( RimEclipseCase& eclipseCase, const QString& resultName, const std::vector& values ); + + static void createResultVector( RimEclipseCase& eclipseCase, const QString& resultName, const std::vector& clusterIds, double value ); + static double getValueForFace( const std::vector& x, const std::vector& y, const std::vector& z, @@ -177,4 +193,15 @@ class RigWellTargetCandidatesGenerator int numClustersFound, size_t timeStepIdx, const QString& clusterResultName ); + + static void computeStatisticsAndCreateVectors( RimEclipseCase& targetCase, + const QString& resultName, + const std::vector>& vec ); + + static void accumulateResultsForSingleCase( RimEclipseCase& eclipseCase, + RimEclipseCase& targetCase, + std::map>>& resultNamesAndSamples, + std::vector& occupancy ); + + static cvf::BoundingBox computeBoundingBoxForResult( RimEclipseCase& eclipseCase, const QString& resultName, size_t timeStepIndex ); };