diff --git a/ApplicationExeCode/Resources/Play.svg b/ApplicationExeCode/Resources/Play.svg new file mode 100644 index 00000000000..1f701af18d4 --- /dev/null +++ b/ApplicationExeCode/Resources/Play.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/ApplicationExeCode/Resources/ResInsight.qrc b/ApplicationExeCode/Resources/ResInsight.qrc index 7458e4a2eb5..5a827a61d2d 100644 --- a/ApplicationExeCode/Resources/ResInsight.qrc +++ b/ApplicationExeCode/Resources/ResInsight.qrc @@ -295,7 +295,9 @@ arrow-swap.svg inspect.svg pin.svg - pinned.svg + Play.svg + gear_icon_16x16.png + pinned.svg pinned-remove.svg Select.svg NavigationProperty.svg diff --git a/ApplicationExeCode/Resources/gear_icon_16x16.png b/ApplicationExeCode/Resources/gear_icon_16x16.png new file mode 100644 index 00000000000..a1cf07e94b0 Binary files /dev/null and b/ApplicationExeCode/Resources/gear_icon_16x16.png differ diff --git a/ApplicationLibCode/Application/Tools/CMakeLists_files.cmake b/ApplicationLibCode/Application/Tools/CMakeLists_files.cmake index e9eac644664..c01b30167e2 100644 --- a/ApplicationLibCode/Application/Tools/CMakeLists_files.cmake +++ b/ApplicationLibCode/Application/Tools/CMakeLists_files.cmake @@ -29,7 +29,6 @@ set(SOURCE_GROUP_HEADER_FILES ${CMAKE_CURRENT_LIST_DIR}/RiaCurveDataTools.h ${CMAKE_CURRENT_LIST_DIR}/RiaWellLogCurveMerger.h ${CMAKE_CURRENT_LIST_DIR}/RiaTimeHistoryCurveResampler.h - ${CMAKE_CURRENT_LIST_DIR}/RiaStatisticsTools.h ${CMAKE_CURRENT_LIST_DIR}/RiaOffshoreSphericalCoords.h ${CMAKE_CURRENT_LIST_DIR}/RiaWeightedMeanCalculator.h ${CMAKE_CURRENT_LIST_DIR}/RiaMedianCalculator.h @@ -91,7 +90,6 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RiaCurveDataTools.cpp ${CMAKE_CURRENT_LIST_DIR}/RiaWellLogCurveMerger.cpp ${CMAKE_CURRENT_LIST_DIR}/RiaTimeHistoryCurveResampler.cpp - ${CMAKE_CURRENT_LIST_DIR}/RiaStatisticsTools.cpp ${CMAKE_CURRENT_LIST_DIR}/RiaWeightedGeometricMeanCalculator.cpp ${CMAKE_CURRENT_LIST_DIR}/RiaWeightedHarmonicMeanCalculator.cpp ${CMAKE_CURRENT_LIST_DIR}/RiaOptionItemFactory.cpp diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicMswValveAccumulators.cpp b/ApplicationLibCode/Commands/CompletionExportCommands/RicMswValveAccumulators.cpp index e34ced036e5..01fc03130ed 100644 --- a/ApplicationLibCode/Commands/CompletionExportCommands/RicMswValveAccumulators.cpp +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicMswValveAccumulators.cpp @@ -17,7 +17,7 @@ ///////////////////////////////////////////////////////////////////////////////// #include "RicMswValveAccumulators.h" -#include "RiaStatisticsTools.h" +#include "RigStatisticsTools.h" #include "RicMswCompletions.h" @@ -110,7 +110,7 @@ bool RicMswAICDAccumulator::accumulateValveParameters( const RimWellPathValve* w std::array values = params->doubleValues(); for ( size_t i = 0; i < (size_t)AICD_NUM_PARAMS; ++i ) { - if ( RiaStatisticsTools::isValidNumber( values[i] ) ) + if ( RigStatisticsTools::isValidNumber( values[i] ) ) { m_meanCalculators[i].addValueAndWeight( values[i], overlapLength ); } diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.cpp b/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.cpp index 9781bea6f7d..b95e4eeb28f 100644 --- a/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.cpp +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.cpp @@ -23,8 +23,8 @@ #include "RiaFractureDefines.h" #include "RiaLogging.h" #include "RiaPreferencesSystem.h" -#include "RiaStatisticsTools.h" #include "RiaWeightedMeanCalculator.h" +#include "RigStatisticsTools.h" #include "ExportCommands/RicExportLgrFeature.h" #include "RicExportCompletionDataSettingsUi.h" @@ -493,7 +493,7 @@ RigCompletionData RicWellPathExportCompletionDataFeatureImpl::combineEclipseCell RiaWeightedMeanCalculator skinFactorCalculator; auto isValidTransmissibility = []( double transmissibility ) - { return RiaStatisticsTools::isValidNumber( transmissibility ) && transmissibility >= 0.0; }; + { return RigStatisticsTools::isValidNumber( transmissibility ) && transmissibility >= 0.0; }; auto startMD = completions[0].startMD(); auto endMD = completions[0].endMD(); diff --git a/ApplicationLibCode/Commands/ExportCommands/RicExportEclipseSectorModelFeature.cpp b/ApplicationLibCode/Commands/ExportCommands/RicExportEclipseSectorModelFeature.cpp index 40b96f56727..39122635a03 100644 --- a/ApplicationLibCode/Commands/ExportCommands/RicExportEclipseSectorModelFeature.cpp +++ b/ApplicationLibCode/Commands/ExportCommands/RicExportEclipseSectorModelFeature.cpp @@ -357,9 +357,9 @@ cvf::ref cvf::ref visibility = new cvf::UByteArray( totalCellCount ); visibility->setAll( false ); - for ( size_t activeCellIdx : activeReservoirCells ) + for ( auto activeCellIdx : activeReservoirCells ) { - visibility->set( activeCellIdx, true ); + visibility->set( activeCellIdx.value(), true ); } return visibility; } diff --git a/ApplicationLibCode/Commands/JobCommands/CMakeLists_files.cmake b/ApplicationLibCode/Commands/JobCommands/CMakeLists_files.cmake index bc24e0c7e5e..69f434e612d 100644 --- a/ApplicationLibCode/Commands/JobCommands/CMakeLists_files.cmake +++ b/ApplicationLibCode/Commands/JobCommands/CMakeLists_files.cmake @@ -1,11 +1,13 @@ set(SOURCE_GROUP_HEADER_FILES ${CMAKE_CURRENT_LIST_DIR}/RicRunJobFeature.h ${CMAKE_CURRENT_LIST_DIR}/RicNewOpmFlowJobFeature.h + ${CMAKE_CURRENT_LIST_DIR}/RicDuplicateJobFeature.h ) set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RicRunJobFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicNewOpmFlowJobFeature.cpp + ${CMAKE_CURRENT_LIST_DIR}/RicDuplicateJobFeature.cpp ) list(APPEND COMMAND_CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES}) diff --git a/ApplicationLibCode/Commands/JobCommands/RicDuplicateJobFeature.cpp b/ApplicationLibCode/Commands/JobCommands/RicDuplicateJobFeature.cpp new file mode 100644 index 00000000000..c75f2e004b1 --- /dev/null +++ b/ApplicationLibCode/Commands/JobCommands/RicDuplicateJobFeature.cpp @@ -0,0 +1,73 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2025 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 "RicDuplicateJobFeature.h" + +#include "RicNewOpmFlowJobFeature.h" + +#include "Jobs/RimJobCollection.h" +#include "Jobs/RimOpmFlowJob.h" +#include "RimTools.h" + +#include "Riu3DMainWindowTools.h" + +#include "cafSelectionManager.h" + +#include +#include +#include + +CAF_CMD_SOURCE_INIT( RicDuplicateJobFeature, "RicDuplicateJobFeature" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RicDuplicateJobFeature::onActionTriggered( bool isChecked ) +{ + if ( auto job = dynamic_cast( caf::SelectionManager::instance()->selectedItem() ) ) + { + QString defaultDir = job->mainWorkingDirectory(); + QFileInfo fi( defaultDir ); + defaultDir = fi.dir().absolutePath(); + + QString workDir = RicNewOpmFlowJobFeature::workingFolder( defaultDir ); + if ( workDir.isEmpty() ) return; + + if ( auto copiedJob = job->copyObject() ) + { + copiedJob->setWorkingDirectory( workDir ); + copiedJob->setName( job->name() + " (copy)" ); + + auto jobColl = RimTools::jobCollection(); + jobColl->addNewJob( copiedJob ); + + copiedJob->resolveReferencesRecursively(); + + Riu3DMainWindowTools::selectAsCurrentItem( copiedJob ); + } + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RicDuplicateJobFeature::setupActionLook( QAction* actionToSetup ) +{ + actionToSetup->setIcon( QIcon( ":/Copy.svg" ) ); + actionToSetup->setText( "Duplicate..." ); +} diff --git a/ApplicationLibCode/Commands/JobCommands/RicDuplicateJobFeature.h b/ApplicationLibCode/Commands/JobCommands/RicDuplicateJobFeature.h new file mode 100644 index 00000000000..7701d71a04c --- /dev/null +++ b/ApplicationLibCode/Commands/JobCommands/RicDuplicateJobFeature.h @@ -0,0 +1,33 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2025 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 RicDuplicateJobFeature : public caf::CmdFeature +{ + CAF_CMD_HEADER_INIT; + +protected: + void onActionTriggered( bool isChecked ) override; + void setupActionLook( QAction* actionToSetup ) override; +}; diff --git a/ApplicationLibCode/Commands/JobCommands/RicNewOpmFlowJobFeature.cpp b/ApplicationLibCode/Commands/JobCommands/RicNewOpmFlowJobFeature.cpp index fc4cf17e24c..a012f603168 100644 --- a/ApplicationLibCode/Commands/JobCommands/RicNewOpmFlowJobFeature.cpp +++ b/ApplicationLibCode/Commands/JobCommands/RicNewOpmFlowJobFeature.cpp @@ -96,12 +96,13 @@ void RicNewOpmFlowJobFeature::setupActionLook( QAction* actionToSetup ) //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -QString RicNewOpmFlowJobFeature::workingFolder() +QString RicNewOpmFlowJobFeature::workingFolder( QString defaultDir ) { // get base directory for our work, should be a new, empty folder somewhere const QString defaultDirName = "OPM_FLOW_MODELING"; - QString defaultDir = RiaApplication::instance()->lastUsedDialogDirectoryWithFallbackToProjectFolder( defaultDirName ); - QString baseDir = + if ( defaultDir.isEmpty() ) + defaultDir = RiaApplication::instance()->lastUsedDialogDirectoryWithFallbackToProjectFolder( defaultDirName ); + QString baseDir = RiuFileDialogTools::getExistingDirectory( Riu3DMainWindowTools::mainWindowWidget(), "Select Simulation Output Directory", defaultDir ); if ( baseDir.isNull() || baseDir.isEmpty() ) return ""; RiaApplication::instance()->setLastUsedDialogDirectory( defaultDirName, baseDir ); diff --git a/ApplicationLibCode/Commands/JobCommands/RicNewOpmFlowJobFeature.h b/ApplicationLibCode/Commands/JobCommands/RicNewOpmFlowJobFeature.h index d9a02166647..44962bb6f29 100644 --- a/ApplicationLibCode/Commands/JobCommands/RicNewOpmFlowJobFeature.h +++ b/ApplicationLibCode/Commands/JobCommands/RicNewOpmFlowJobFeature.h @@ -27,12 +27,12 @@ class RicNewOpmFlowJobFeature : public caf::CmdFeature { CAF_CMD_HEADER_INIT; +public: + static QString workingFolder( QString defaultDir = "" ); + static QString inputDataFile(); + protected: bool isCommandEnabled() const override; void onActionTriggered( bool isChecked ) override; void setupActionLook( QAction* actionToSetup ) override; - -private: - static QString workingFolder(); - static QString inputDataFile(); }; diff --git a/ApplicationLibCode/Commands/JobCommands/RicRunJobFeature.cpp b/ApplicationLibCode/Commands/JobCommands/RicRunJobFeature.cpp index 578a416a9d7..77f47227bba 100644 --- a/ApplicationLibCode/Commands/JobCommands/RicRunJobFeature.cpp +++ b/ApplicationLibCode/Commands/JobCommands/RicRunJobFeature.cpp @@ -39,8 +39,8 @@ void RicRunJobFeature::onActionTriggered( bool isChecked ) //-------------------------------------------------------------------------------------------------- void RicRunJobFeature::setupActionLook( QAction* actionToSetup ) { - actionToSetup->setIcon( QIcon( ":/gear.png" ) ); - actionToSetup->setText( "Run Job..." ); + actionToSetup->setIcon( QIcon( ":/Play.svg" ) ); + actionToSetup->setText( "Run..." ); } //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/FileInterface/RifOpmFlowDeckFile.cpp b/ApplicationLibCode/FileInterface/RifOpmFlowDeckFile.cpp index c4b83ee9eb4..3f860363695 100644 --- a/ApplicationLibCode/FileInterface/RifOpmFlowDeckFile.cpp +++ b/ApplicationLibCode/FileInterface/RifOpmFlowDeckFile.cpp @@ -20,6 +20,8 @@ #include "RifOpmDeckTools.h" +#include "RigEclipseResultTools.h" + #include "opm/common/utility/TimeService.hpp" #include "opm/input/eclipse/Deck/Deck.hpp" #include "opm/input/eclipse/Deck/FileDeck.hpp" @@ -27,6 +29,7 @@ #include "opm/input/eclipse/Parser/InputErrorAction.hpp" #include "opm/input/eclipse/Parser/ParseContext.hpp" #include "opm/input/eclipse/Parser/Parser.hpp" +#include "opm/input/eclipse/Parser/ParserKeywords/B.hpp" #include "opm/input/eclipse/Parser/ParserKeywords/C.hpp" #include "opm/input/eclipse/Parser/ParserKeywords/D.hpp" #include "opm/input/eclipse/Parser/ParserKeywords/E.hpp" @@ -37,6 +40,8 @@ #include "opm/input/eclipse/Parser/ParserKeywords/S.hpp" #include "opm/input/eclipse/Parser/ParserKeywords/W.hpp" +#include "cvfStructGrid.h" + #include #include #include @@ -629,6 +634,7 @@ bool RifOpmFlowDeckFile::appendDateKeywords( const std::vector& dat newRec.addItem( RifOpmDeckTools::item( Opm::ParserKeywords::DATES::DAY::itemName, lt->tm_mday ) ); newRec.addItem( RifOpmDeckTools::item( Opm::ParserKeywords::DATES::MONTH::itemName, month ) ); newRec.addItem( RifOpmDeckTools::item( Opm::ParserKeywords::DATES::YEAR::itemName, lt->tm_year + 1900 ) ); + newRec.addItem( RifOpmDeckTools::defaultItem( Opm::ParserKeywords::DATES::TIME::itemName ) ); newKw.addRecord( std::move( newRec ) ); @@ -861,6 +867,132 @@ bool RifOpmFlowDeckFile::addOperaterKeyword( std::string section, return true; } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RifOpmFlowDeckFile::addBcconKeyword( std::string section, const std::vector& borderCellFaces ) +{ + if ( m_fileDeck.get() == nullptr ) return false; + + if ( borderCellFaces.empty() ) return true; // Nothing to add + + // Find insertion point within the section + auto insertPos = internal::findSectionInsertionPoint( m_fileDeck, section ); + if ( !insertPos.has_value() ) + { + return false; // Section not found + } + + // Helper lambda to convert FaceType to string + auto faceTypeToString = []( cvf::StructGridInterface::FaceType faceType ) -> std::string + { + switch ( faceType ) + { + case cvf::StructGridInterface::POS_I: + return "X"; + case cvf::StructGridInterface::NEG_I: + return "X-"; + case cvf::StructGridInterface::POS_J: + return "Y"; + case cvf::StructGridInterface::NEG_J: + return "Y-"; + case cvf::StructGridInterface::POS_K: + return "Z"; + case cvf::StructGridInterface::NEG_K: + return "Z-"; + default: + return ""; + } + }; + + // Create the BCCON keyword using OPM's BCCON parser keyword + using B = Opm::ParserKeywords::BCCON; + + Opm::DeckKeyword bcconKw( ( Opm::ParserKeywords::BCCON() ) ); + + for ( const auto& borderFace : borderCellFaces ) + { + // Convert from 0-based to 1-based Eclipse indexing + int i1 = static_cast( borderFace.ijk[0] ) + 1; + int j1 = static_cast( borderFace.ijk[1] ) + 1; + int k1 = static_cast( borderFace.ijk[2] ) + 1; + + std::string faceStr = faceTypeToString( borderFace.faceType ); + + // Create items for the record using proper BCCON item names + std::vector recordItems; + + recordItems.push_back( RifOpmDeckTools::item( B::INDEX::itemName, borderFace.boundaryCondition ) ); + recordItems.push_back( RifOpmDeckTools::item( B::I1::itemName, i1 ) ); + recordItems.push_back( RifOpmDeckTools::item( B::I2::itemName, i1 ) ); // Same as i1 for single cell + recordItems.push_back( RifOpmDeckTools::item( B::J1::itemName, j1 ) ); + recordItems.push_back( RifOpmDeckTools::item( B::J2::itemName, j1 ) ); // Same as j1 for single cell + recordItems.push_back( RifOpmDeckTools::item( B::K1::itemName, k1 ) ); + recordItems.push_back( RifOpmDeckTools::item( B::K2::itemName, k1 ) ); // Same as k1 for single cell + recordItems.push_back( RifOpmDeckTools::item( B::DIRECTION::itemName, faceStr ) ); + + bcconKw.addRecord( Opm::DeckRecord{ std::move( recordItems ) } ); + } + + m_fileDeck->insert( insertPos.value(), bcconKw ); + return true; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RifOpmFlowDeckFile::addBcpropKeyword( std::string section, + const std::vector& boundaryConditions, + const std::vector& boundaryConditionProperties ) +{ + if ( m_fileDeck.get() == nullptr ) return false; + + if ( boundaryConditions.empty() ) return true; // Nothing to add + + // Find insertion point within the section + auto insertPos = internal::findSectionInsertionPoint( m_fileDeck, section ); + if ( !insertPos.has_value() ) + { + return false; // Section not found + } + + // Create the BCPROP keyword using OPM's BCPROP parser keyword + using B = Opm::ParserKeywords::BCPROP; + + Opm::DeckKeyword bcpropKw( ( Opm::ParserKeywords::BCPROP() ) ); + + // Add one entry per boundary condition + for ( const auto& bc : boundaryConditions ) + { + if ( bc.boundaryCondition <= 0 ) continue; // Skip entries without a valid boundary condition + + // Find the corresponding property record + // The properties vector should be indexed by boundaryCondition - 1 + size_t propIndex = static_cast( bc.boundaryCondition - 1 ); + if ( propIndex < boundaryConditionProperties.size() ) + { + const auto& propRecord = boundaryConditionProperties[propIndex]; + + // Create a new record with the boundary condition INDEX + std::vector recordItems; + + // Add INDEX field + recordItems.push_back( RifOpmDeckTools::item( B::INDEX::itemName, bc.boundaryCondition ) ); + + // Copy all items from the property record (which doesn't include INDEX) + for ( size_t i = 0; i < propRecord.size(); ++i ) + { + recordItems.push_back( propRecord.getItem( i ) ); + } + + bcpropKw.addRecord( Opm::DeckRecord{ std::move( recordItems ) } ); + } + } + + m_fileDeck->insert( insertPos.value(), bcpropKw ); + return true; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/FileInterface/RifOpmFlowDeckFile.h b/ApplicationLibCode/FileInterface/RifOpmFlowDeckFile.h index 29630a5f498..262edd44451 100644 --- a/ApplicationLibCode/FileInterface/RifOpmFlowDeckFile.h +++ b/ApplicationLibCode/FileInterface/RifOpmFlowDeckFile.h @@ -29,9 +29,16 @@ namespace Opm { class FileDeck; class DeckKeyword; +class DeckItem; +class DeckRecord; class ParseContext; } // namespace Opm +namespace RigEclipseResultTools +{ +struct BorderCellFace; +} + //================================================================================================== /// /// @@ -81,6 +88,11 @@ class RifOpmFlowDeckFile std::optional alpha, std::optional beta ); + bool addBcconKeyword( std::string section, const std::vector& borderCellFaces ); + bool addBcpropKeyword( std::string section, + const std::vector& boundaryConditions, + const std::vector& boundaryConditionProperties ); + private: void splitDatesIfNecessary(); diff --git a/ApplicationLibCode/GeoMech/GeoMechDataModel/CMakeLists.txt b/ApplicationLibCode/GeoMech/GeoMechDataModel/CMakeLists.txt index aac48d14e59..b265192075f 100644 --- a/ApplicationLibCode/GeoMech/GeoMechDataModel/CMakeLists.txt +++ b/ApplicationLibCode/GeoMech/GeoMechDataModel/CMakeLists.txt @@ -113,7 +113,13 @@ add_library( target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries( - RigGeoMechDataModel PUBLIC LibCore cafPdmCvf cafTensor cafUserInterface - CommonCode ResultStatisticsCache + RigGeoMechDataModel + PUBLIC LibCore + cafPdmCvf + cafTensor + cafUserInterface + CommonCode + ResultStatisticsCache + ApplicationLibCode ) target_link_libraries(RigGeoMechDataModel PRIVATE ResInsightCommonSettings) diff --git a/ApplicationLibCode/ProjectDataModel/Completions/RimFishbones.cpp b/ApplicationLibCode/ProjectDataModel/Completions/RimFishbones.cpp index 240a0611dde..b85fa32b9ec 100644 --- a/ApplicationLibCode/ProjectDataModel/Completions/RimFishbones.cpp +++ b/ApplicationLibCode/ProjectDataModel/Completions/RimFishbones.cpp @@ -576,6 +576,8 @@ void RimFishbones::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& u m_lateralOpenHoleRoghnessFactor.uiCapability()->setUiName( "Open Hole Roughness Factor [m]" ); m_lateralTubingRoghnessFactor.uiCapability()->setUiName( "Tubing Roughness Factor [m]" ); + m_lateralDiameter.uiCapability()->setUiName( "Lateral Diameter [mm]" ); + m_icdOrificeDiameter.uiCapability()->setUiName( "ICD Orifice Diameter [mm]" ); } else if ( wellPath->unitSystem() == RiaDefines::EclipseUnitSystem::UNITS_FIELD ) @@ -586,6 +588,8 @@ void RimFishbones::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& u m_lateralOpenHoleRoghnessFactor.uiCapability()->setUiName( "Open Hole Roughness Factor [ft]" ); m_lateralTubingRoghnessFactor.uiCapability()->setUiName( "Tubing Roughness Factor [ft]" ); + m_lateralDiameter.uiCapability()->setUiName( "Lateral Diameter [in]" ); + m_icdOrificeDiameter.uiCapability()->setUiName( "ICD Orifice Diameter [in]" ); } } diff --git a/ApplicationLibCode/ProjectDataModel/Completions/RimFishbonesCollection.cpp b/ApplicationLibCode/ProjectDataModel/Completions/RimFishbonesCollection.cpp index b57c96f2826..7247d9e0e90 100644 --- a/ApplicationLibCode/ProjectDataModel/Completions/RimFishbonesCollection.cpp +++ b/ApplicationLibCode/ProjectDataModel/Completions/RimFishbonesCollection.cpp @@ -160,8 +160,9 @@ RimFishbones* RimFishbonesCollection::appendFishbonesSubsAtLocations( const std: auto* obj = new RimFishbones; obj->setValveLocations( subLocations ); - RimFishbonesDefines::RicFishbonesSystemParameters customParameters = RimFishbonesDefines::drillingStandardParameters(); + appendFishbonesSubs( obj ); + RimFishbonesDefines::RicFishbonesSystemParameters customParameters = RimFishbonesDefines::drillingStandardParameters(); if ( drillingType == RimFishbonesDefines::DrillingType::EXTENDED ) { customParameters = RimFishbonesDefines::drillingExtendedParameters(); @@ -171,14 +172,13 @@ RimFishbones* RimFishbonesCollection::appendFishbonesSubsAtLocations( const std: customParameters = RimFishbonesDefines::acidJettingParameters(); } + // NB: Setting the system parameters must be done after appendFishbonesSubs, as the latter sets some default values obj->setSystemParameters( customParameters.lateralsPerSub, customParameters.lateralLength, customParameters.holeDiameter, customParameters.buildAngle, customParameters.icdsPerSub ); - appendFishbonesSubs( obj ); - return obj; } diff --git a/ApplicationLibCode/ProjectDataModel/ContourMap/RimStatisticsContourMap.cpp b/ApplicationLibCode/ProjectDataModel/ContourMap/RimStatisticsContourMap.cpp index 1f34cd75504..1b486a44188 100644 --- a/ApplicationLibCode/ProjectDataModel/ContourMap/RimStatisticsContourMap.cpp +++ b/ApplicationLibCode/ProjectDataModel/ContourMap/RimStatisticsContourMap.cpp @@ -20,7 +20,7 @@ #include "RiaLogging.h" #include "RiaPreferencesGrid.h" -#include "RiaStatisticsTools.h" +#include "RigStatisticsTools.h" #include "RicNewStatisticsContourMapViewFeature.h" @@ -469,16 +469,16 @@ void RimStatisticsContourMap::doStatisticsCalculation( std::map::max() ) minResults[i] = minValue; + double minValue = RigStatisticsTools::minimumValue( samples ); + if ( RigStatisticsTools::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; + double maxValue = RigStatisticsTools::maximumValue( samples ); + if ( RigStatisticsTools::isValidNumber( maxValue ) && maxValue > -std::numeric_limits::max() ) maxResults[i] = maxValue; } m_timeResults[timeStep][StatisticsType::P10] = p10Results; diff --git a/ApplicationLibCode/ProjectDataModel/CorrelationPlots/RimCorrelationMatrixPlot.cpp b/ApplicationLibCode/ProjectDataModel/CorrelationPlots/RimCorrelationMatrixPlot.cpp index d072df50027..7fd2c6b0895 100644 --- a/ApplicationLibCode/ProjectDataModel/CorrelationPlots/RimCorrelationMatrixPlot.cpp +++ b/ApplicationLibCode/ProjectDataModel/CorrelationPlots/RimCorrelationMatrixPlot.cpp @@ -21,7 +21,7 @@ #include "RiaColorTools.h" #include "RiaPreferences.h" #include "RiaQDateTimeTools.h" -#include "RiaStatisticsTools.h" +#include "RigStatisticsTools.h" #include "Summary/RiaSummaryCurveDefinition.h" #include "RifCsvDataTableFormatter.h" @@ -560,7 +560,7 @@ void RimCorrelationMatrixPlot::createMatrix() if ( parameterValues.empty() ) continue; - correlation = RiaStatisticsTools::pearsonCorrelation( parameterValues, caseValuesAtTimestep ); + correlation = RigStatisticsTools::pearsonCorrelation( parameterValues, caseValuesAtTimestep ); bool validResult = RiaCurveDataTools::isValidValue( correlation, false ); if ( validResult ) diff --git a/ApplicationLibCode/ProjectDataModel/Jobs/RimGenericJob.cpp b/ApplicationLibCode/ProjectDataModel/Jobs/RimGenericJob.cpp index 58451f68be4..62188afb890 100644 --- a/ApplicationLibCode/ProjectDataModel/Jobs/RimGenericJob.cpp +++ b/ApplicationLibCode/ProjectDataModel/Jobs/RimGenericJob.cpp @@ -48,6 +48,7 @@ RimGenericJob::~RimGenericJob() void RimGenericJob::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const { menuBuilder << "RicRunJobFeature"; + menuBuilder << "RicDuplicateJobFeature"; } //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/Jobs/RimJobCollection.cpp b/ApplicationLibCode/ProjectDataModel/Jobs/RimJobCollection.cpp index 9b01a6c4821..9208d9dd42f 100644 --- a/ApplicationLibCode/ProjectDataModel/Jobs/RimJobCollection.cpp +++ b/ApplicationLibCode/ProjectDataModel/Jobs/RimJobCollection.cpp @@ -34,7 +34,7 @@ CAF_PDM_SOURCE_INIT( RimJobCollection, "JobCollection" ); //-------------------------------------------------------------------------------------------------- RimJobCollection::RimJobCollection() { - CAF_PDM_InitObject( "Jobs" + RiaDefines::betaFeaturePostfix(), ":/gear.svg" ); + CAF_PDM_InitObject( "Jobs" + RiaDefines::betaFeaturePostfix(), ":/gear_icon_16x16.png" ); CAF_PDM_InitFieldNoDefault( &m_jobs, "Jobs", "Jobs" ); diff --git a/ApplicationLibCode/ProjectDataModel/Jobs/RimOpmFlowJob.cpp b/ApplicationLibCode/ProjectDataModel/Jobs/RimOpmFlowJob.cpp index c626959017c..9a56129473d 100644 --- a/ApplicationLibCode/ProjectDataModel/Jobs/RimOpmFlowJob.cpp +++ b/ApplicationLibCode/ProjectDataModel/Jobs/RimOpmFlowJob.cpp @@ -482,6 +482,14 @@ void RimOpmFlowJob::setWorkingDirectory( QString workDir ) m_workDir = workDir; } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RimOpmFlowJob::mainWorkingDirectory() const +{ + return m_workDir().path(); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/Jobs/RimOpmFlowJob.h b/ApplicationLibCode/ProjectDataModel/Jobs/RimOpmFlowJob.h index 27f05ccb8ac..4905fe85730 100644 --- a/ApplicationLibCode/ProjectDataModel/Jobs/RimOpmFlowJob.h +++ b/ApplicationLibCode/ProjectDataModel/Jobs/RimOpmFlowJob.h @@ -62,6 +62,7 @@ class RimOpmFlowJob : public RimGenericJob void setInputDataFile( QString filename ); QString deckName(); + QString mainWorkingDirectory() const; protected: void defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute ) override; diff --git a/ApplicationLibCode/ProjectDataModel/RimGridCalculation.cpp b/ApplicationLibCode/ProjectDataModel/RimGridCalculation.cpp index ea91d24e0c9..1570b40b815 100644 --- a/ApplicationLibCode/ProjectDataModel/RimGridCalculation.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimGridCalculation.cpp @@ -665,7 +665,7 @@ std::vector RimGridCalculation::getActiveCellValues( const QString& #pragma omp parallel for for ( int i = 0; i < static_cast( activeReservoirCells.size() ); i++ ) { - values[i] = resultAccessor->cellScalarGlobIdx( activeReservoirCells[i] ); + values[i] = resultAccessor->cellScalarGlobIdx( activeReservoirCells[i].value() ); } if ( m_releaseMemoryAfterDataIsExtracted ) @@ -698,7 +698,7 @@ void RimGridCalculation::replaceFilteredValuesWithVector( const std::vectorval( reservoirCellIndex ) ) + if ( !visibility->val( reservoirCellIndex.value() ) ) { resultValues[i] = inputValues[i]; } @@ -723,7 +723,7 @@ void RimGridCalculation::replaceFilteredValuesWithDefaultValue( double for ( int i = 0; i < numActiveCells; i++ ) { const auto reservoirCellIndex = activeReservoirCellIndices[i]; - if ( !visibility->val( reservoirCellIndex ) ) + if ( !visibility->val( reservoirCellIndex.value() ) ) { resultValues[i] = defaultValue; } diff --git a/ApplicationLibCode/ProjectDataModel/RimWellTargetMapping.cpp b/ApplicationLibCode/ProjectDataModel/RimWellTargetMapping.cpp index 69b4d90b403..b8d5364a47a 100644 --- a/ApplicationLibCode/ProjectDataModel/RimWellTargetMapping.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimWellTargetMapping.cpp @@ -588,7 +588,7 @@ std::vector RimWellTargetMapping::getVisibilityFilter() const for ( int i = 0; i < numActiveCells; i++ ) { const auto reservoirCellIndex = activeReservoirCellIndices[i]; - filter[i] = visibility->val( reservoirCellIndex ) ? 1.0 : 0.0; + filter[i] = visibility->val( reservoirCellIndex.value() ) ? 1.0 : 0.0; } } } diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimFileSummaryCase.cpp b/ApplicationLibCode/ProjectDataModel/Summary/RimFileSummaryCase.cpp index e8b71f907db..a55fe7945f8 100644 --- a/ApplicationLibCode/ProjectDataModel/Summary/RimFileSummaryCase.cpp +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimFileSummaryCase.cpp @@ -172,29 +172,41 @@ std::unique_ptr RimFileSummaryCase::findRelatedFilesA auto startTime = RiaLogging::currentTime(); std::vector warnings; - std::vector restartFileNames; - if ( RiaPreferencesSummary::current()->summaryDataReader() == RiaPreferencesSummary::SummaryReaderMode::OPM_COMMON ) + + auto findFileCandidates = [&ensembleImportState, &headerFileName, &warnings] -> std::vector { - if ( ensembleImportState.useConfigValues() ) + if ( RiaPreferencesSummary::current()->summaryDataReader() == RiaPreferencesSummary::SummaryReaderMode::OPM_COMMON ) { - auto realizationNumber = RifOpmSummaryTools::extractRealizationNumber( headerFileName ); - if ( !realizationNumber.has_value() ) + if ( ensembleImportState.useConfigValues() ) { - RiaLogging::error( realizationNumber.error() ); - return nullptr; + auto realizationNumber = RifOpmSummaryTools::extractRealizationNumber( headerFileName ); + if ( !realizationNumber.has_value() ) + { + RiaLogging::error( realizationNumber.error() ); + return {}; + } + return ensembleImportState.restartFilesForRealization( realizationNumber.value() ); + } + else + { + // If the restart file names are not provided, we search for them + return RifEclipseSummaryTools::getRestartFileNamesOpm( headerFileName, warnings ); } - - restartFileNames = ensembleImportState.restartFilesForRealization( realizationNumber.value() ); } else { - // If the restart file names are not provided, we search for them - restartFileNames = RifEclipseSummaryTools::getRestartFileNamesOpm( headerFileName, warnings ); + return RifEclipseSummaryTools::getRestartFileNames( headerFileName, warnings ); } - } - else + }; + + std::vector restartFileNames; + for ( const auto& fileName : findFileCandidates() ) { - restartFileNames = RifEclipseSummaryTools::getRestartFileNames( headerFileName, warnings ); + QFileInfo fi( fileName ); + if ( fi.exists() ) + { + restartFileNames.push_back( fileName ); + } } bool isLoggingEnabled = RiaPreferencesSystem::current()->isLoggingActivatedForKeyword( "OpmSummaryImport" ); diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryEnsemble.cpp b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryEnsemble.cpp index ad6ec4f7b3f..92561b41962 100644 --- a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryEnsemble.cpp +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryEnsemble.cpp @@ -22,9 +22,9 @@ #include "RiaFieldHandleTools.h" #include "RiaFilePathTools.h" #include "RiaLogging.h" -#include "RiaStatisticsTools.h" #include "RiaStdStringTools.h" #include "RiaTextStringTools.h" +#include "RigStatisticsTools.h" #include "Summary/Ensemble/RimSummaryEnsembleParameterCollection.h" #include "Summary/RiaSummaryAddressAnalyzer.h" #include "Summary/RiaSummaryTools.h" @@ -571,7 +571,7 @@ std::vector> for ( auto parameterValuesPair : parameterValues ) { double correlation = 0.0; - double pearson = RiaStatisticsTools::pearsonCorrelation( parameterValuesPair.second, caseValuesAtTimestep ); + double pearson = RigStatisticsTools::pearsonCorrelation( parameterValuesPair.second, caseValuesAtTimestep ); if ( pearson != std::numeric_limits::infinity() ) correlation = pearson; correlationResults.push_back( std::make_pair( parameterValuesPair.first, correlation ) ); } diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryRegressionAnalysisCurve.cpp b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryRegressionAnalysisCurve.cpp index e4b03743552..b70f1e3280b 100644 --- a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryRegressionAnalysisCurve.cpp +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryRegressionAnalysisCurve.cpp @@ -21,8 +21,8 @@ #include "RiaLogging.h" #include "RiaQDateTimeTools.h" #include "RiaRegressionTextTools.h" -#include "RiaStatisticsTools.h" #include "RiaTimeTTools.h" +#include "RigStatisticsTools.h" #include "RimEnsembleCurveSet.h" #include "RimEnsembleCurveSetCollection.h" @@ -203,7 +203,7 @@ void RimSummaryRegressionAnalysisCurve::onLoadDataAndUpdate( bool updateParentPl { for ( size_t i = 0; i < xValues.size(); i++ ) { - if ( xValues[i] < m_valueRangeX().first || xValues[i] > m_valueRangeX().second || !RiaStatisticsTools::isValidNumber( xValues[i] ) ) + if ( xValues[i] < m_valueRangeX().first || xValues[i] > m_valueRangeX().second || !RigStatisticsTools::isValidNumber( xValues[i] ) ) { indicesToRemove.push_back( i ); } @@ -211,7 +211,7 @@ void RimSummaryRegressionAnalysisCurve::onLoadDataAndUpdate( bool updateParentPl for ( size_t i = 0; i < yValues.size(); i++ ) { - if ( yValues[i] < m_valueRangeY().first || yValues[i] > m_valueRangeY().second || !RiaStatisticsTools::isValidNumber( yValues[i] ) ) + if ( yValues[i] < m_valueRangeY().first || yValues[i] > m_valueRangeY().second || !RigStatisticsTools::isValidNumber( yValues[i] ) ) { indicesToRemove.push_back( i ); } diff --git a/ApplicationLibCode/ProjectDataModel/WellLog/RimWellLogRftCurve.cpp b/ApplicationLibCode/ProjectDataModel/WellLog/RimWellLogRftCurve.cpp index d8eda4ef483..163ab1a78cc 100644 --- a/ApplicationLibCode/ProjectDataModel/WellLog/RimWellLogRftCurve.cpp +++ b/ApplicationLibCode/ProjectDataModel/WellLog/RimWellLogRftCurve.cpp @@ -27,8 +27,8 @@ #include "RiaResultNames.h" #include "RiaRftDefines.h" #include "RiaSimWellBranchTools.h" -#include "RiaStatisticsTools.h" #include "RiaTextStringTools.h" +#include "RigStatisticsTools.h" #include "Summary/RiaSummaryTools.h" #include "RifEclipseRftAddress.h" @@ -670,7 +670,7 @@ void RimWellLogRftCurve::onLoadDataAndUpdate( bool updateParentPlot ) { for ( const auto& v : values ) { - if ( RiaStatisticsTools::isValidNumber( v ) ) return true; + if ( RigStatisticsTools::isValidNumber( v ) ) return true; } return false; }; diff --git a/ApplicationLibCode/ProjectDataModelCommands/CMakeLists_files.cmake b/ApplicationLibCode/ProjectDataModelCommands/CMakeLists_files.cmake index 0e1c8389c54..20b230684dc 100644 --- a/ApplicationLibCode/ProjectDataModelCommands/CMakeLists_files.cmake +++ b/ApplicationLibCode/ProjectDataModelCommands/CMakeLists_files.cmake @@ -26,6 +26,7 @@ set(SOURCE_GROUP_HEADER_FILES ${CMAKE_CURRENT_LIST_DIR}/RimcIntersection.h ${CMAKE_CURRENT_LIST_DIR}/RimcEclipseCase.h ${CMAKE_CURRENT_LIST_DIR}/RimcEclipseStatisticsCase.h + ${CMAKE_CURRENT_LIST_DIR}/RimcGridView.h ${CMAKE_CURRENT_LIST_DIR}/RimcIdenticalGridCaseGroup.h ${CMAKE_CURRENT_LIST_DIR}/RimcPressureTable.h ${CMAKE_CURRENT_LIST_DIR}/RimcFishbonesCollection.h @@ -64,6 +65,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RimcIntersection.cpp ${CMAKE_CURRENT_LIST_DIR}/RimcEclipseCase.cpp ${CMAKE_CURRENT_LIST_DIR}/RimcEclipseStatisticsCase.cpp + ${CMAKE_CURRENT_LIST_DIR}/RimcGridView.cpp ${CMAKE_CURRENT_LIST_DIR}/RimcIdenticalGridCaseGroup.cpp ${CMAKE_CURRENT_LIST_DIR}/RimcPressureTable.cpp ${CMAKE_CURRENT_LIST_DIR}/RimcFishbonesCollection.cpp diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcGridView.cpp b/ApplicationLibCode/ProjectDataModelCommands/RimcGridView.cpp new file mode 100644 index 00000000000..e42ddad5e66 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcGridView.cpp @@ -0,0 +1,82 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2025 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 "RimcGridView.h" + +#include "RiaApplication.h" +#include "RiaKeyValueStoreUtil.h" + +#include "RimEclipseView.h" +#include "RimGridView.h" + +#include "cafPdmFieldScriptingCapability.h" + +#include "cvfArray.h" + +CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimEclipseView, RimcGridView_visibleCellsInternal, "visible_cells_internal" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimcGridView_visibleCellsInternal::RimcGridView_visibleCellsInternal( caf::PdmObjectHandle* self ) + : caf::PdmVoidObjectMethod( self ) +{ + CAF_PDM_InitObject( "Visible Cells Internal", "", "", "Visible Cells Internal" ); + + CAF_PDM_InitScriptableFieldNoDefault( &m_visibilityKey, "VisibilityKey", "" ); + CAF_PDM_InitScriptableFieldNoDefault( &m_timeStep, "TimeStep", "" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::expected RimcGridView_visibleCellsInternal::execute() +{ + auto gridView = self(); + if ( !gridView ) + { + return std::unexpected( QString( "Grid view is null." ) ); + } + + if ( m_visibilityKey().isEmpty() ) + { + return std::unexpected( QString( "Visibility key is empty." ) ); + } + + // Get the cell visibility array + cvf::ref visibleCells = gridView->currentTotalCellVisibility(); + + if ( visibleCells.isNull() ) + { + return std::unexpected( QString( "Failed to get cell visibility data." ) ); + } + + // Convert cvf::UByteArray to std::vector where 1.0 = visible, 0.0 = invisible + std::vector visibilityValues; + visibilityValues.reserve( visibleCells->size() ); + for ( size_t i = 0; i < visibleCells->size(); ++i ) + { + visibilityValues.push_back( ( *visibleCells )[i] ? 1.0f : 0.0f ); + } + + // Store the visibility data in the key-value store + auto keyValueStore = RiaApplication::instance()->keyValueStore(); + keyValueStore->set( m_visibilityKey().toStdString(), RiaKeyValueStoreUtil::convertToByteVector( visibilityValues ) ); + + return nullptr; +} diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcGridView.h b/ApplicationLibCode/ProjectDataModelCommands/RimcGridView.h new file mode 100644 index 00000000000..82c6d4e2e34 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcGridView.h @@ -0,0 +1,42 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2025 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 "cafPdmField.h" +#include "cafPdmObjectHandle.h" +#include "cafPdmObjectMethod.h" + +#include + +//================================================================================================== +/// +//================================================================================================== +class RimcGridView_visibleCellsInternal : public caf::PdmVoidObjectMethod +{ + CAF_PDM_HEADER_INIT; + +public: + RimcGridView_visibleCellsInternal( caf::PdmObjectHandle* self ); + + std::expected execute() override; + +private: + caf::PdmField m_visibilityKey; + caf::PdmField m_timeStep; +}; diff --git a/ApplicationLibCode/ReservoirDataModel/CMakeLists_files.cmake b/ApplicationLibCode/ReservoirDataModel/CMakeLists_files.cmake index 091a9d30b3a..5f0e5f46c63 100644 --- a/ApplicationLibCode/ReservoirDataModel/CMakeLists_files.cmake +++ b/ApplicationLibCode/ReservoirDataModel/CMakeLists_files.cmake @@ -4,6 +4,7 @@ set(SOURCE_GROUP_HEADER_FILES ${CMAKE_CURRENT_LIST_DIR}/RigActiveCellGrid.h ${CMAKE_CURRENT_LIST_DIR}/RigActiveCellInfo.h ${CMAKE_CURRENT_LIST_DIR}/RigActiveCellLocalGrid.h + ${CMAKE_CURRENT_LIST_DIR}/RigTypeSafeIndex.h ${CMAKE_CURRENT_LIST_DIR}/RigAllanDiagramData.h ${CMAKE_CURRENT_LIST_DIR}/RigBasicPlane.h ${CMAKE_CURRENT_LIST_DIR}/RigCaseCellResultCalculator.h diff --git a/ApplicationLibCode/ReservoirDataModel/ContourMap/RigContourMapProjection.cpp b/ApplicationLibCode/ReservoirDataModel/ContourMap/RigContourMapProjection.cpp index af6c4a0b685..6f5dde8e00c 100644 --- a/ApplicationLibCode/ReservoirDataModel/ContourMap/RigContourMapProjection.cpp +++ b/ApplicationLibCode/ReservoirDataModel/ContourMap/RigContourMapProjection.cpp @@ -18,8 +18,8 @@ #include "RigContourMapProjection.h" -#include "RiaStatisticsTools.h" #include "RiaWeightedMeanCalculator.h" +#include "RigStatisticsTools.h" #include "RigContourMapCalculator.h" #include "RigContourMapGrid.h" @@ -231,7 +231,7 @@ double RigContourMapProjection::calculateValueInMapCell( unsigned int //-------------------------------------------------------------------------------------------------- double RigContourMapProjection::maxValue( const std::vector& aggregatedResults ) { - double maxV = RiaStatisticsTools::maximumValue( aggregatedResults ); + double maxV = RigStatisticsTools::maximumValue( aggregatedResults ); return maxV != -std::numeric_limits::max() ? maxV : -std::numeric_limits::infinity(); } @@ -240,7 +240,7 @@ double RigContourMapProjection::maxValue( const std::vector& aggregatedR //-------------------------------------------------------------------------------------------------- double RigContourMapProjection::minValue( const std::vector& aggregatedResults ) { - double minV = RiaStatisticsTools::minimumValue( aggregatedResults ); + double minV = RigStatisticsTools::minimumValue( aggregatedResults ); return minV != std::numeric_limits::max() ? minV : std::numeric_limits::infinity(); } diff --git a/ApplicationLibCode/ReservoirDataModel/ResultAccessors/RigResultAccessorFactory.cpp b/ApplicationLibCode/ReservoirDataModel/ResultAccessors/RigResultAccessorFactory.cpp index c945ad63953..ea5e04f0eb5 100644 --- a/ApplicationLibCode/ReservoirDataModel/ResultAccessors/RigResultAccessorFactory.cpp +++ b/ApplicationLibCode/ReservoirDataModel/ResultAccessors/RigResultAccessorFactory.cpp @@ -371,9 +371,26 @@ cvf::ref RigResultAccessorFactory::createNativeFromResultAddr bool useGlobalActiveIndex = eclipseCase->results( porosityModel )->isUsingGlobalActiveIndex( resultAddress ); if ( eclipseCase->mainGrid()->gridCount() > 1 ) { - // Always use active cell indexing for multi-grid cases. If all cells in the main grid is active, and a local grid is created, this - // option must be set to true - useGlobalActiveIndex = true; + bool isRadialTempGrid = [&eclipseCase]() -> bool + { + for ( size_t gIdx = 0; gIdx < eclipseCase->gridCount(); gIdx++ ) + { + auto currentGrid = eclipseCase->grid( gIdx ); + if ( currentGrid && currentGrid->isRadial() && currentGrid->isTempGrid() ) + { + return true; + } + } + + return false; + }(); + + if ( isRadialTempGrid ) + { + // Always use active cell indexing for temporary LGRs of radial multi-grids. If all cells in the main grid is active, and a + // local grid is created, this option must be set to true + useGlobalActiveIndex = true; + } } if ( useGlobalActiveIndex ) { diff --git a/ApplicationLibCode/ReservoirDataModel/ResultCalculators/RigFaultDistanceResultCalculator.cpp b/ApplicationLibCode/ReservoirDataModel/ResultCalculators/RigFaultDistanceResultCalculator.cpp index 603e4de4c86..fbceff65910 100644 --- a/ApplicationLibCode/ReservoirDataModel/ResultCalculators/RigFaultDistanceResultCalculator.cpp +++ b/ApplicationLibCode/ReservoirDataModel/ResultCalculators/RigFaultDistanceResultCalculator.cpp @@ -87,11 +87,11 @@ void RigFaultDistanceResultCalculator::calculate( const RigEclipseResultAddress& const auto activeCells = m_resultsData->activeCellInfo()->activeReservoirCellIndices(); for ( auto cellIdx : activeCells ) { - const RigCell& cell = mainGrid->cell( cellIdx ); + const RigCell& cell = mainGrid->cell( cellIdx.value() ); if ( cell.isInvalid() ) continue; for ( auto faceType : faceTypes ) { - if ( m_resultsData->m_ownerMainGrid->findFaultFromCellIndexAndCellFace( cellIdx, faceType ) ) + if ( m_resultsData->m_ownerMainGrid->findFaultFromCellIndexAndCellFace( cellIdx.value(), faceType ) ) faultFaceCenters.push_back( cell.faceCenter( faceType ) ); } } @@ -122,9 +122,9 @@ void RigFaultDistanceResultCalculator::calculate( const RigEclipseResultAddress& for ( int activeIndex = 0; activeIndex < static_cast( activeCells.size() ); activeIndex++ ) { auto cellIdx = activeCells[activeIndex]; - if ( cellIdx == cvf::UNDEFINED_SIZE_T ) continue; + if ( cellIdx.value() == cvf::UNDEFINED_SIZE_T ) continue; - const RigCell& cell = mainGrid->cell( cellIdx ); + const RigCell& cell = mainGrid->cell( cellIdx.value() ); if ( cell.isInvalid() ) continue; std::vector candidateFaceIndices; diff --git a/ApplicationLibCode/ReservoirDataModel/ResultCalculators/RigIndexIjkResultCalculator.cpp b/ApplicationLibCode/ReservoirDataModel/ResultCalculators/RigIndexIjkResultCalculator.cpp index 55a7d0d8dc8..46bce40eacb 100644 --- a/ApplicationLibCode/ReservoirDataModel/ResultCalculators/RigIndexIjkResultCalculator.cpp +++ b/ApplicationLibCode/ReservoirDataModel/ResultCalculators/RigIndexIjkResultCalculator.cpp @@ -106,9 +106,9 @@ void RigIndexIjkResultCalculator::calculate( const RigEclipseResultAddress& resV for ( int activeIndex = 0; activeIndex < static_cast( activeReservoirCellIndices.size() ); activeIndex++ ) { auto cellIdx = activeReservoirCellIndices[activeIndex]; - if ( cellIdx == cvf::UNDEFINED_SIZE_T ) continue; + if ( cellIdx.value() == cvf::UNDEFINED_SIZE_T ) continue; - const RigCell& cell = mainGrid->cell( cellIdx ); + const RigCell& cell = mainGrid->cell( cellIdx.value() ); if ( cell.isInvalid() ) continue; bool isTemporaryGrid = cell.hostGrid()->isTempGrid(); diff --git a/ApplicationLibCode/ReservoirDataModel/RigActiveCellInfo.cpp b/ApplicationLibCode/ReservoirDataModel/RigActiveCellInfo.cpp index 92fd87fe975..015324df2da 100644 --- a/ApplicationLibCode/ReservoirDataModel/RigActiveCellInfo.cpp +++ b/ApplicationLibCode/ReservoirDataModel/RigActiveCellInfo.cpp @@ -84,7 +84,7 @@ void RigActiveCellInfo::setCellResultIndex( size_t reservoirCellIndex, size_t re //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -std::vector RigActiveCellInfo::activeReservoirCellIndices() const +std::vector RigActiveCellInfo::activeReservoirCellIndices() const { return m_activeCellIndices; } @@ -123,7 +123,7 @@ void RigActiveCellInfo::computeDerivedData() { if ( m_cellIndexToResultIndex[i] != cvf::UNDEFINED_SIZE_T ) { - m_activeCellIndices.push_back( i ); + m_activeCellIndices.push_back( ReservoirCellIndex( i ) ); } } } diff --git a/ApplicationLibCode/ReservoirDataModel/RigActiveCellInfo.h b/ApplicationLibCode/ReservoirDataModel/RigActiveCellInfo.h index 2ebf272b989..b9de8f7509b 100644 --- a/ApplicationLibCode/ReservoirDataModel/RigActiveCellInfo.h +++ b/ApplicationLibCode/ReservoirDataModel/RigActiveCellInfo.h @@ -20,6 +20,8 @@ #pragma once +#include "RigTypeSafeIndex.h" + #include "cvfBoundingBox.h" #include "cvfObject.h" #include "cvfVector3.h" @@ -35,10 +37,10 @@ class RigActiveCellInfo : public cvf::Object size_t reservoirCellCount() const; size_t reservoirActiveCellCount() const; - bool isActive( size_t reservoirCellIndex ) const; - size_t cellResultIndex( size_t reservoirCellIndex ) const; - void setCellResultIndex( size_t reservoirCellIndex, size_t globalResultCellIndex ); - std::vector activeReservoirCellIndices() const; + bool isActive( size_t reservoirCellIndex ) const; + size_t cellResultIndex( size_t reservoirCellIndex ) const; + void setCellResultIndex( size_t reservoirCellIndex, size_t globalResultCellIndex ); + std::vector activeReservoirCellIndices() const; void setGridCount( size_t gridCount ); void setGridActiveCellCounts( size_t gridIndex, size_t activeCellCount ); @@ -71,8 +73,8 @@ class RigActiveCellInfo : public cvf::Object private: std::vector m_perGridActiveCellInfo; - std::vector m_cellIndexToResultIndex; - std::vector m_activeCellIndices; + std::vector m_cellIndexToResultIndex; + std::vector m_activeCellIndices; size_t m_reservoirActiveCellCount; diff --git a/ApplicationLibCode/ReservoirDataModel/RigEclipseResultTools.cpp b/ApplicationLibCode/ReservoirDataModel/RigEclipseResultTools.cpp index 2656f6f0a37..a4ba3f7b3c2 100644 --- a/ApplicationLibCode/ReservoirDataModel/RigEclipseResultTools.cpp +++ b/ApplicationLibCode/ReservoirDataModel/RigEclipseResultTools.cpp @@ -28,6 +28,7 @@ #include "RigEclipseResultAddress.h" #include "RigMainGrid.h" +#include "RigTypeSafeIndex.h" #include "RimEclipseCase.h" #include "RimEclipseResultCase.h" #include "RimEclipseView.h" @@ -67,8 +68,9 @@ void generateBorderResult( RimEclipseCase* eclipseCase, cvf::refeclipseCaseData()->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL )->activeReservoirCellIndices(); int numActiveCells = static_cast( activeReservoirCellIdxs.size() ); - std::vector result; - result.resize( numActiveCells, BorderType::INVISIBLE_CELL ); + size_t reservoirCellCount = + eclipseCase->eclipseCaseData()->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL )->reservoirCellCount(); + std::vector result( reservoirCellCount, BorderType::INVISIBLE_CELL ); auto grid = eclipseCase->eclipseCaseData()->mainGrid(); @@ -77,9 +79,9 @@ void generateBorderResult( RimEclipseCase* eclipseCase, cvf::refval( cellIdx ) ) + if ( customVisibility->val( cellIdx.value() ) ) { - auto neighbors = grid->neighborCells( cellIdx, true /*ignore invalid k layers*/ ); + auto neighbors = grid->neighborCells( cellIdx.value(), true /*ignore invalid k layers*/ ); int nVisibleNeighbors = 0; for ( auto nIdx : neighbors ) @@ -89,11 +91,11 @@ void generateBorderResult( RimEclipseCase* eclipseCase, cvf::refeclipseCaseData()->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL )->activeReservoirCellIndices(); - int numActiveCells = static_cast( activeReservoirCellIdxs.size() ); - - std::vector result; - result.resize( numActiveCells, 0 ); // Check if OPERNUM already exists - if so, keep existing values. RigEclipseResultAddress operNumAddrNative( RiaDefines::ResultCatType::STATIC_NATIVE, @@ -147,16 +145,17 @@ void generateOperNumResult( RimEclipseCase* eclipseCase, int borderCellValue ) hasExistingOperNum = true; } + std::vector result; + if ( hasExistingOperNum ) { resultsData->ensureKnownResultLoaded( operNumAddr ); - auto existingValues = resultsData->cellScalarResults( operNumAddr ); - if ( !existingValues.empty() ) + auto existingValues = resultsData->cellScalarResults( operNumAddr, 0 ); + result.resize( existingValues.size() ); + + for ( size_t i = 0; i < existingValues.size(); i++ ) { - for ( int i = 0; i < numActiveCells; i++ ) - { - result[i] = static_cast( existingValues[0][i] ); - } + result[i] = static_cast( existingValues[i] ); } } @@ -165,15 +164,16 @@ void generateOperNumResult( RimEclipseCase* eclipseCase, int borderCellValue ) if ( resultsData->hasResultEntry( bordNumAddr ) ) { resultsData->ensureKnownResultLoaded( bordNumAddr ); - auto bordNumValues = resultsData->cellScalarResults( bordNumAddr ); + auto bordNumValues = resultsData->cellScalarResults( bordNumAddr, 0 ); if ( !bordNumValues.empty() ) { - for ( int i = 0; i < numActiveCells; i++ ) + if ( result.empty() ) result.resize( bordNumValues.size(), 0 ); + for ( auto activeCellIdx : activeReservoirCellIdxs ) { // If BORDNUM = 1 (BORDER_CELL), assign the border cell value - if ( static_cast( bordNumValues[0][i] ) == BorderType::BORDER_CELL ) + if ( static_cast( bordNumValues[activeCellIdx.value()] ) == BorderType::BORDER_CELL ) { - result[i] = borderCellValue; + result[activeCellIdx.value()] = borderCellValue; } } } @@ -219,18 +219,98 @@ int findMaxOperNumValue( RimEclipseCase* eclipseCase ) if ( !hasOperNum ) return 0; resultsData->ensureKnownResultLoaded( operNumAddr ); - auto operNumValues = resultsData->cellScalarResults( operNumAddr ); + auto operNumValues = resultsData->cellScalarResults( operNumAddr, 0 ); if ( operNumValues.empty() ) return 0; // Find maximum value int maxValue = 0; - for ( double value : operNumValues[0] ) + for ( double value : operNumValues ) { - int intValue = static_cast( value ); - maxValue = std::max( maxValue, intValue ); + maxValue = std::max( maxValue, static_cast( value ) ); } return maxValue; } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector generateBorderCellFaces( RimEclipseCase* eclipseCase ) +{ + if ( eclipseCase == nullptr ) return {}; + + auto resultsData = eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL ); + if ( !resultsData ) return {}; + + auto grid = eclipseCase->eclipseCaseData()->mainGrid(); + if ( !grid ) return {}; + + // Check if BORDNUM result exists + RigEclipseResultAddress bordNumAddr( RiaDefines::ResultCatType::GENERATED, RiaDefines::ResultDataType::INTEGER, RiaResultNames::bordnum() ); + if ( !resultsData->hasResultEntry( bordNumAddr ) ) return {}; // BORDNUM not generated yet + + resultsData->ensureKnownResultLoaded( bordNumAddr ); + auto bordNumValues = resultsData->cellScalarResults( bordNumAddr, 0 ); + if ( bordNumValues.empty() ) return {}; + + // Check if BCCON result exists to get boundary condition values + RigEclipseResultAddress bcconAddr( RiaDefines::ResultCatType::GENERATED, RiaDefines::ResultDataType::INTEGER, "BCCON" ); + if ( !resultsData->hasResultEntry( bcconAddr ) ) return {}; + + resultsData->ensureKnownResultLoaded( bcconAddr ); + std::vector bcconValues = resultsData->cellScalarResults( bcconAddr, 0 ); + + auto activeReservoirCellIdxs = + eclipseCase->eclipseCaseData()->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL )->activeReservoirCellIndices(); + + std::vector borderCellFaces; + + // Iterate through all active cells + for ( auto activeCellIdx : activeReservoirCellIdxs ) + { + // Check if this cell is a border cell + int borderValue = static_cast( bordNumValues[activeCellIdx.value()] ); + if ( borderValue != BorderType::BORDER_CELL ) continue; + + // Get IJK indices for this cell + size_t i, j, k; + if ( !grid->ijkFromCellIndex( activeCellIdx.value(), &i, &j, &k ) ) continue; + + // Check all 6 faces + std::vector faces = cvf::StructGridInterface::validFaceTypes(); + + for ( auto faceType : faces ) + { + // Get neighbor cell IJK + size_t ni, nj, nk; + cvf::StructGridInterface::neighborIJKAtCellFace( i, j, k, faceType, &ni, &nj, &nk ); + + // Check if neighbor is within bounds + if ( ni >= grid->cellCountI() || nj >= grid->cellCountJ() || nk >= grid->cellCountK() ) continue; + + // Get neighbor reservoir cell index + size_t neighborReservoirIdx = grid->cellIndexFromIJK( ni, nj, nk ); + + // Find active cell index for neighbor + auto it = std::find( activeReservoirCellIdxs.begin(), activeReservoirCellIdxs.end(), ReservoirCellIndex( neighborReservoirIdx ) ); + if ( it == activeReservoirCellIdxs.end() ) continue; // Neighbor not active + + // Check if neighbor is an interior cell + int neighborBorderValue = static_cast( bordNumValues[neighborReservoirIdx] ); + if ( neighborBorderValue == BorderType::INTERIOR_CELL ) + { + // Get boundary condition value from BCCON grid property + int boundaryCondition = static_cast( bcconValues[activeCellIdx.value()] ); + if ( boundaryCondition > 0 ) + { + // Add this face to the result + borderCellFaces.push_back( { cvf::Vec3st( i, j, k ), faceType, boundaryCondition } ); + } + } + } + } + + return borderCellFaces; +} + } // namespace RigEclipseResultTools diff --git a/ApplicationLibCode/ReservoirDataModel/RigEclipseResultTools.h b/ApplicationLibCode/ReservoirDataModel/RigEclipseResultTools.h index c87f621a9fd..ae2b549feea 100644 --- a/ApplicationLibCode/ReservoirDataModel/RigEclipseResultTools.h +++ b/ApplicationLibCode/ReservoirDataModel/RigEclipseResultTools.h @@ -22,6 +22,7 @@ #include #include "cvfArray.h" +#include "cvfStructGrid.h" class RimEclipseCase; class RimEclipseView; @@ -35,6 +36,13 @@ enum BorderType : int INTERIOR_CELL = 2 }; +struct BorderCellFace +{ + cvf::Vec3st ijk; // Cell indices (0-based) + cvf::StructGridInterface::FaceType faceType; + int boundaryCondition; // BCCON grid value +}; + void createResultVector( RimEclipseCase& eclipseCase, const QString& resultName, const std::vector& intValues ); void generateBorderResult( RimEclipseCase* eclipseCase, cvf::ref customVisibility, const QString& resultName = "BORDER" ); @@ -43,4 +51,6 @@ void generateOperNumResult( RimEclipseCase* eclipseCase, int borderCellValue = - int findMaxOperNumValue( RimEclipseCase* eclipseCase ); +std::vector generateBorderCellFaces( RimEclipseCase* eclipseCase ); + } // namespace RigEclipseResultTools diff --git a/ApplicationLibCode/ReservoirDataModel/RigTypeSafeIndex.h b/ApplicationLibCode/ReservoirDataModel/RigTypeSafeIndex.h new file mode 100644 index 00000000000..5ca92026dd2 --- /dev/null +++ b/ApplicationLibCode/ReservoirDataModel/RigTypeSafeIndex.h @@ -0,0 +1,76 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2025 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 +#include + +template +class TypeSafeIndex +{ +public: + // Default constructor + constexpr TypeSafeIndex() noexcept + : m_value( 0 ) + { + } + + // Explicit constructor from size_t (prevents implicit conversion) + explicit constexpr TypeSafeIndex( size_t value ) noexcept + : m_value( value ) + { + } + + // Explicit conversion to size_t + explicit constexpr operator size_t() const noexcept { return m_value; } + + // Getter for when you need the value + constexpr size_t value() const noexcept { return m_value; } + + // Comparison operators + constexpr bool operator==( const TypeSafeIndex& other ) const noexcept = default; + constexpr auto operator<=>( const TypeSafeIndex& other ) const noexcept = default; + + // Arithmetic operators (if needed) + constexpr TypeSafeIndex& operator++() noexcept + { + ++m_value; + return *this; + } + +private: + size_t m_value; +}; + +// Define specific types using tag structs +struct ReservoirCellIndexTag +{ +}; + +using ReservoirCellIndex = TypeSafeIndex; + +// Hash support for using in std::unordered_map, etc. +namespace std +{ +template +struct hash> +{ + size_t operator()( const TypeSafeIndex& idx ) const noexcept { return hash{}( idx.value() ); } +}; +} // namespace std diff --git a/ApplicationLibCode/ReservoirDataModel/Well/RigWellTargetMapping.cpp b/ApplicationLibCode/ReservoirDataModel/Well/RigWellTargetMapping.cpp index 9faec09ce9c..72ec48b5d30 100644 --- a/ApplicationLibCode/ReservoirDataModel/Well/RigWellTargetMapping.cpp +++ b/ApplicationLibCode/ReservoirDataModel/Well/RigWellTargetMapping.cpp @@ -1027,16 +1027,16 @@ void RigWellTargetMapping::computeStatisticsAndCreateVectors( RimEclipseCase& 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; + if ( RigStatisticsTools::isValidNumber( p10 ) ) p10Results[i] = p10; + if ( RigStatisticsTools::isValidNumber( p50 ) ) p50Results[i] = p50; + if ( RigStatisticsTools::isValidNumber( p90 ) ) p90Results[i] = p90; + if ( RigStatisticsTools::isValidNumber( mean ) ) meanResults[i] = mean; - double minValue = RiaStatisticsTools::minimumValue( samples ); - if ( RiaStatisticsTools::isValidNumber( minValue ) && minValue < std::numeric_limits::max() ) minResults[i] = minValue; + double minValue = RigStatisticsTools::minimumValue( samples ); + if ( RigStatisticsTools::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; + double maxValue = RigStatisticsTools::maximumValue( samples ); + if ( RigStatisticsTools::isValidNumber( maxValue ) && maxValue > -std::numeric_limits::max() ) maxResults[i] = maxValue; } createResultVectorIfDefined( targetCase, resultName + "_P10", p10Results ); diff --git a/ApplicationLibCode/ResultStatisticsCache/CMakeLists.txt b/ApplicationLibCode/ResultStatisticsCache/CMakeLists.txt index 121aae877b4..1d362a26a6c 100644 --- a/ApplicationLibCode/ResultStatisticsCache/CMakeLists.txt +++ b/ApplicationLibCode/ResultStatisticsCache/CMakeLists.txt @@ -2,11 +2,16 @@ project(ResultStatisticsCache) add_library( ${PROJECT_NAME} - RigStatisticsCalculator.h RigStatisticsCalculator.cpp - RigStatisticsDataCache.h RigStatisticsDataCache.cpp RigStatisticsMath.h + RigStatisticsCalculator.h + RigStatisticsCalculator.cpp + RigStatisticsDataCache.h + RigStatisticsDataCache.cpp + RigStatisticsMath.h RigStatisticsMath.cpp + RigStatisticsTools.h + RigStatisticsTools.cpp ) target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(${PROJECT_NAME} LibCore ApplicationLibCode) +target_link_libraries(${PROJECT_NAME} LibCore) diff --git a/ApplicationLibCode/ResultStatisticsCache/RigStatisticsMath.cpp b/ApplicationLibCode/ResultStatisticsCache/RigStatisticsMath.cpp index a61b4a4b9c4..090ffab6f70 100644 --- a/ApplicationLibCode/ResultStatisticsCache/RigStatisticsMath.cpp +++ b/ApplicationLibCode/ResultStatisticsCache/RigStatisticsMath.cpp @@ -50,7 +50,7 @@ void RigStatisticsMath::calculateBasicStatistics( const std::vector& val for ( size_t i = 0; i < values.size(); i++ ) { double val = values[i]; - if ( RiaStatisticsTools::isInvalidNumber( val ) ) continue; + if ( RigStatisticsTools::isInvalidNumber( val ) ) continue; validValueCount++; @@ -110,7 +110,7 @@ void RigStatisticsMath::calculateStatisticsCurves( const std::vector& va sortedValues.erase( std::remove_if( sortedValues.begin(), sortedValues.end(), - []( double x ) { return !RiaStatisticsTools::isValidNumber( x ); } ), + []( double x ) { return !RigStatisticsTools::isValidNumber( x ); } ), sortedValues.end() ); std::sort( sortedValues.begin(), sortedValues.end() ); @@ -174,7 +174,7 @@ std::vector RigStatisticsMath::calculateNearestRankPercentiles( const st for ( size_t i = 0; i < inputValues.size(); ++i ) { - if ( RiaStatisticsTools::isValidNumber( inputValues[i] ) ) + if ( RigStatisticsTools::isValidNumber( inputValues[i] ) ) { sortedValues.push_back( inputValues[i] ); } @@ -218,7 +218,7 @@ std::vector RigStatisticsMath::calculateInterpolatedPercentiles( const s for ( size_t i = 0; i < inputValues.size(); ++i ) { - if ( RiaStatisticsTools::isValidNumber( inputValues[i] ) ) + if ( RigStatisticsTools::isValidNumber( inputValues[i] ) ) { sortedValues.push_back( inputValues[i] ); } @@ -290,7 +290,7 @@ RigHistogramCalculator::RigHistogramCalculator( double min, double max, size_t n //-------------------------------------------------------------------------------------------------- void RigHistogramCalculator::addValue( double value ) { - if ( RiaStatisticsTools::isInvalidNumber( value ) ) return; + if ( RigStatisticsTools::isInvalidNumber( value ) ) return; size_t index = 0; diff --git a/ApplicationLibCode/ResultStatisticsCache/RigStatisticsMath.h b/ApplicationLibCode/ResultStatisticsCache/RigStatisticsMath.h index f8e9355bbed..47714a5bdf3 100644 --- a/ApplicationLibCode/ResultStatisticsCache/RigStatisticsMath.h +++ b/ApplicationLibCode/ResultStatisticsCache/RigStatisticsMath.h @@ -17,7 +17,7 @@ ///////////////////////////////////////////////////////////////////////////////// #pragma once -#include "RiaStatisticsTools.h" +#include "RigStatisticsTools.h" #include #include @@ -107,7 +107,7 @@ class MinMaxAccumulator void addValue( double value ) { - if ( RiaStatisticsTools::isValidNumber( value ) ) + if ( RigStatisticsTools::isValidNumber( value ) ) { if ( value < min ) { @@ -186,7 +186,7 @@ class PosNegAccumulator void addValue( double value ) { - if ( RiaStatisticsTools::isValidNumber( value ) ) + if ( RigStatisticsTools::isValidNumber( value ) ) { if ( value < pos && value > 0 ) { @@ -231,7 +231,7 @@ class SumCountAccumulator void addValue( double value ) { - if ( RiaStatisticsTools::isValidNumber( value ) ) + if ( RigStatisticsTools::isValidNumber( value ) ) { valueSum += value; ++sampleCount; @@ -265,7 +265,7 @@ class UniqueValueAccumulator void addValue( double value ) { - if ( RiaStatisticsTools::isValidNumber( value ) ) + if ( RigStatisticsTools::isValidNumber( value ) ) { uniqueValues.insert( static_cast( value ) ); } diff --git a/ApplicationLibCode/Application/Tools/RiaStatisticsTools.cpp b/ApplicationLibCode/ResultStatisticsCache/RigStatisticsTools.cpp similarity index 96% rename from ApplicationLibCode/Application/Tools/RiaStatisticsTools.cpp rename to ApplicationLibCode/ResultStatisticsCache/RigStatisticsTools.cpp index 5605065f65b..07b4899741c 100644 --- a/ApplicationLibCode/Application/Tools/RiaStatisticsTools.cpp +++ b/ApplicationLibCode/ResultStatisticsCache/RigStatisticsTools.cpp @@ -18,14 +18,14 @@ // ///////////////////////////////////////////////////////////////////////////////// -#include "RiaStatisticsTools.h" +#include "RigStatisticsTools.h" #include "RigStatisticsMath.h" //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -double RiaStatisticsTools::pearsonCorrelation( const std::vector& xValues, const std::vector& yValues ) +double RigStatisticsTools::pearsonCorrelation( const std::vector& xValues, const std::vector& yValues ) { const double eps = 1.0e-8; double rangeX = 0.0, rangeY = 0.0; diff --git a/ApplicationLibCode/Application/Tools/RiaStatisticsTools.h b/ApplicationLibCode/ResultStatisticsCache/RigStatisticsTools.h similarity index 93% rename from ApplicationLibCode/Application/Tools/RiaStatisticsTools.h rename to ApplicationLibCode/ResultStatisticsCache/RigStatisticsTools.h index 65d83623d68..20897e416b6 100644 --- a/ApplicationLibCode/Application/Tools/RiaStatisticsTools.h +++ b/ApplicationLibCode/ResultStatisticsCache/RigStatisticsTools.h @@ -29,7 +29,7 @@ // // //================================================================================================== -class RiaStatisticsTools +class RigStatisticsTools { public: template @@ -50,7 +50,7 @@ class RiaStatisticsTools NumberType minValue = std::numeric_limits::max(); for ( NumberType value : values ) { - if ( RiaStatisticsTools::isValidNumber( value ) ) + if ( RigStatisticsTools::isValidNumber( value ) ) { minValue = std::min( minValue, value ); } @@ -65,7 +65,7 @@ class RiaStatisticsTools NumberType maxValue = -std::numeric_limits::max(); for ( NumberType value : values ) { - if ( RiaStatisticsTools::isValidNumber( value ) ) + if ( RigStatisticsTools::isValidNumber( value ) ) { maxValue = std::max( maxValue, value ); } diff --git a/ApplicationLibCode/UnitTests/CMakeLists.txt b/ApplicationLibCode/UnitTests/CMakeLists.txt index 5f48e491b16..a2f778ce7a2 100644 --- a/ApplicationLibCode/UnitTests/CMakeLists.txt +++ b/ApplicationLibCode/UnitTests/CMakeLists.txt @@ -15,6 +15,7 @@ set(SOURCE_UNITTEST_FILES ${CMAKE_CURRENT_LIST_DIR}/RifReaderEclipseSummary-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RigActiveCellInfo-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RigEclipseCaseDataTools-Test.cpp + ${CMAKE_CURRENT_LIST_DIR}/RigEclipseResultTools-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RigReservoir-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RigResdataGridConverter-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RigGridExportAdapter-Test.cpp @@ -74,7 +75,7 @@ set(SOURCE_UNITTEST_FILES ${CMAKE_CURRENT_LIST_DIR}/RifColorLegendData-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RifRoffReader-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RifElasticPropertiesReader-Test.cpp - ${CMAKE_CURRENT_LIST_DIR}/RiaStatisticsTools-Test.cpp + ${CMAKE_CURRENT_LIST_DIR}/RigStatisticsTools-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RifStimPlanXmlReader-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RifThermalFractureReader-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RigWellPathGeometryExporter-Test.cpp diff --git a/ApplicationLibCode/UnitTests/RifOpmFlowDeckFile-Test.cpp b/ApplicationLibCode/UnitTests/RifOpmFlowDeckFile-Test.cpp index c8ec84c9d13..6115330eada 100644 --- a/ApplicationLibCode/UnitTests/RifOpmFlowDeckFile-Test.cpp +++ b/ApplicationLibCode/UnitTests/RifOpmFlowDeckFile-Test.cpp @@ -1,8 +1,18 @@ #include "gtest/gtest.h" #include "RiaTestDataDirectory.h" + +#include "RifOpmDeckTools.h" #include "RifOpmFlowDeckFile.h" +#include "RigEclipseResultTools.h" + +#include "cvfStructGrid.h" + +#include "opm/input/eclipse/Deck/DeckItem.hpp" +#include "opm/input/eclipse/Deck/DeckRecord.hpp" +#include "opm/input/eclipse/Parser/ParserKeywords/B.hpp" + #include #include #include @@ -322,3 +332,111 @@ TEST( RifOpmFlowDeckFileTest, AddOperaterSaveAndReload ) // The test validates that our addOperaterKeyword functionality works correctly // by checking that the OPERATER statement is properly saved to the file. } + +//-------------------------------------------------------------------------------------------------- +/// Test BCPROP keyword generation +//-------------------------------------------------------------------------------------------------- +TEST( RifOpmFlowDeckFileTest, BcpropKeyword ) +{ + QTemporaryDir tempDir; + ASSERT_TRUE( tempDir.isValid() ) << "Failed to create temporary directory"; + + // Load the deck file + static const QString testDataFolder = QString( "%1/RifOpmFlowDeckFile/" ).arg( TEST_DATA_DIR ); + QString fileName = testDataFolder + "SIMPLE_NO_REGDIMS.DATA"; + + RifOpmFlowDeckFile deckFile; + bool loadSuccess = deckFile.loadDeck( fileName.toStdString() ); + + ASSERT_TRUE( loadSuccess ) << "Failed to load deck file"; + + // Create boundary conditions with different indices + std::vector boundaryConditions; + boundaryConditions.push_back( { cvf::Vec3st( 5, 5, 2 ), cvf::StructGridInterface::POS_I, 1 } ); + boundaryConditions.push_back( { cvf::Vec3st( 5, 6, 2 ), cvf::StructGridInterface::POS_J, 1 } ); + boundaryConditions.push_back( { cvf::Vec3st( 6, 5, 2 ), cvf::StructGridInterface::NEG_I, 2 } ); + boundaryConditions.push_back( { cvf::Vec3st( 7, 5, 2 ), cvf::StructGridInterface::POS_K, 2 } ); + + // Create boundary condition properties + // BC 1: Free flow boundary with specified pressure + // BC 2: Fixed pressure boundary with temperature + std::vector bcProperties; + + using B = Opm::ParserKeywords::BCPROP; + + // Property for BC 1 (index will be added by addBcpropKeyword) + { + std::vector items; + items.push_back( RifOpmDeckTools::item( B::TYPE::itemName, std::string( "FREE" ) ) ); + items.push_back( RifOpmDeckTools::item( B::COMPONENT::itemName, std::string( "NONE" ) ) ); + items.push_back( RifOpmDeckTools::item( B::RATE::itemName, 0.0 ) ); + items.push_back( RifOpmDeckTools::item( B::PRESSURE::itemName, 200.0 ) ); // 200 bar + items.push_back( RifOpmDeckTools::item( B::TEMPERATURE::itemName, 80.0 ) ); // 80 C + items.push_back( RifOpmDeckTools::item( B::MECHTYPE::itemName, std::string( "NONE" ) ) ); + items.push_back( RifOpmDeckTools::item( B::FIXEDX::itemName, 1 ) ); + items.push_back( RifOpmDeckTools::item( B::FIXEDY::itemName, 1 ) ); + items.push_back( RifOpmDeckTools::item( B::FIXEDZ::itemName, 1 ) ); + items.push_back( RifOpmDeckTools::item( B::STRESSXX::itemName, 0.0 ) ); + items.push_back( RifOpmDeckTools::item( B::STRESSYY::itemName, 0.0 ) ); + items.push_back( RifOpmDeckTools::item( B::STRESSZZ::itemName, 0.0 ) ); + items.push_back( RifOpmDeckTools::item( B::DISPX::itemName, 0.0 ) ); + items.push_back( RifOpmDeckTools::item( B::DISPY::itemName, 0.0 ) ); + items.push_back( RifOpmDeckTools::item( B::DISPZ::itemName, 0.0 ) ); + bcProperties.push_back( Opm::DeckRecord{ std::move( items ) } ); + } + + // Property for BC 2 + { + std::vector items; + items.push_back( RifOpmDeckTools::item( B::TYPE::itemName, std::string( "DIRICH" ) ) ); + items.push_back( RifOpmDeckTools::item( B::COMPONENT::itemName, std::string( "WATER" ) ) ); + items.push_back( RifOpmDeckTools::item( B::RATE::itemName, 0.0 ) ); + items.push_back( RifOpmDeckTools::item( B::PRESSURE::itemName, 250.0 ) ); // 250 bar + items.push_back( RifOpmDeckTools::item( B::TEMPERATURE::itemName, 90.0 ) ); // 90 C + items.push_back( RifOpmDeckTools::item( B::MECHTYPE::itemName, std::string( "NONE" ) ) ); + items.push_back( RifOpmDeckTools::item( B::FIXEDX::itemName, 1 ) ); + items.push_back( RifOpmDeckTools::item( B::FIXEDY::itemName, 1 ) ); + items.push_back( RifOpmDeckTools::item( B::FIXEDZ::itemName, 1 ) ); + items.push_back( RifOpmDeckTools::item( B::STRESSXX::itemName, 0.0 ) ); + items.push_back( RifOpmDeckTools::item( B::STRESSYY::itemName, 0.0 ) ); + items.push_back( RifOpmDeckTools::item( B::STRESSZZ::itemName, 0.0 ) ); + items.push_back( RifOpmDeckTools::item( B::DISPX::itemName, 0.0 ) ); + items.push_back( RifOpmDeckTools::item( B::DISPY::itemName, 0.0 ) ); + items.push_back( RifOpmDeckTools::item( B::DISPZ::itemName, 0.0 ) ); + bcProperties.push_back( Opm::DeckRecord{ std::move( items ) } ); + } + + // Add BCPROP keyword + bool bcpropAdded = deckFile.addBcpropKeyword( "GRID", boundaryConditions, bcProperties ); + ASSERT_TRUE( bcpropAdded ) << "Failed to add BCPROP keyword"; + + // Save deck and verify format + QString outputDeckPath = tempDir.filePath( "output_bcprop.DATA" ); + bool deckSaved = deckFile.saveDeck( tempDir.path().toStdString(), "output_bcprop.DATA" ); + ASSERT_TRUE( deckSaved ) << "Failed to save deck file"; + ASSERT_TRUE( QFile::exists( outputDeckPath ) ) << "Output deck file not created"; + + // Read and verify BCPROP content + QFile outputFile( outputDeckPath ); + ASSERT_TRUE( outputFile.open( QIODevice::ReadOnly | QIODevice::Text ) ); + QString content = QTextStream( &outputFile ).readAll(); + outputFile.close(); + + // Verify BCPROP keyword is present + EXPECT_TRUE( content.contains( "BCPROP" ) ) << "BCPROP keyword not found in output"; + + // Verify boundary condition types are present + EXPECT_TRUE( content.contains( "FREE" ) ) << "FREE boundary condition type not found"; + EXPECT_TRUE( content.contains( "DIRICH" ) ) << "DIRICH boundary condition type not found"; + + // Verify pressure values + EXPECT_TRUE( content.contains( "200" ) ) << "Pressure 200 bar not found"; + EXPECT_TRUE( content.contains( "250" ) ) << "Pressure 250 bar not found"; + + // Verify temperature values + EXPECT_TRUE( content.contains( "80" ) ) << "Temperature 80 C not found"; + EXPECT_TRUE( content.contains( "90" ) ) << "Temperature 90 C not found"; + + // Verify component + EXPECT_TRUE( content.contains( "WATER" ) ) << "Component WATER not found"; +} diff --git a/ApplicationLibCode/UnitTests/RigEclipseCaseDataTools-Test.cpp b/ApplicationLibCode/UnitTests/RigEclipseCaseDataTools-Test.cpp index 483c867c696..ed85ffa619e 100644 --- a/ApplicationLibCode/UnitTests/RigEclipseCaseDataTools-Test.cpp +++ b/ApplicationLibCode/UnitTests/RigEclipseCaseDataTools-Test.cpp @@ -18,6 +18,7 @@ #include "gtest/gtest.h" +#include "RiaPorosityModel.h" #include "RiaResultNames.h" #include "RiaTestDataDirectory.h" @@ -592,7 +593,8 @@ TEST( RigEclipseCaseDataToolsTest, GenerateOperNumResultFromBorderResult ) int numActiveCells = static_cast( activeReservoirCellIdxs.size() ); std::vector customOperValues; - customOperValues.resize( numActiveCells, 5 ); // Set all to 5 initially + customOperValues.resize( eclipseCase->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL )->reservoirCellCount(), + 5 ); // Set all to 5 initially // Add some higher values to test max detection if ( numActiveCells > 10 ) { diff --git a/ApplicationLibCode/UnitTests/RigEclipseResultTools-Test.cpp b/ApplicationLibCode/UnitTests/RigEclipseResultTools-Test.cpp new file mode 100644 index 00000000000..f37a0fa50e6 --- /dev/null +++ b/ApplicationLibCode/UnitTests/RigEclipseResultTools-Test.cpp @@ -0,0 +1,203 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2025- 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 "gtest/gtest.h" + +#include "RiaDefines.h" +#include "RiaTestDataDirectory.h" +#include "RifEclipseInputFileTools.h" +#include "RifOpmFlowDeckFile.h" +#include "RifReaderEclipseOutput.h" +#include "RigEclipseResultTools.h" + +#include "RigActiveCellInfo.h" +#include "RigCaseCellResultsData.h" +#include "RigEclipseCaseData.h" +#include "RigEclipseResultAddress.h" +#include "RigMainGrid.h" +#include "RimEclipseResultCase.h" + +#include +#include +#include +#include + +#include + +//-------------------------------------------------------------------------------------------------- +/// Test BCCON generation from border cells +/// +/// This test verifies that: +/// 1. We can generate BORDNUM result identifying border and interior cells +/// 2. We can extract border cell faces that connect to interior cells +/// 3. We can write BCCON keyword to an OPM deck file +/// +/// Test process: +/// 1. Load reek test model +/// 2. Create custom visibility for a subset of cells (a box region) +/// 3. Generate BORDNUM result +/// 4. Generate border cell faces +/// 5. Verify border cells and faces are correctly identified +/// 6. Create OPM deck and add BCCON keyword +/// 7. Save and verify deck file format +//-------------------------------------------------------------------------------------------------- +TEST( RigEclipseResultToolsTest, BorderCellBcconGeneration ) +{ + // Setup test data directory - use BRUGGE model like other tests + QDir baseFolder( TEST_MODEL_DIR ); + bool subFolderExists = baseFolder.cd( "Case_with_10_timesteps/Real0" ); + ASSERT_TRUE( subFolderExists ) << "Test model directory not found"; + + QString inputFilename( "BRUGGE_0000.EGRID" ); + QString inputFilePath = baseFolder.absoluteFilePath( inputFilename ); + ASSERT_TRUE( QFile::exists( inputFilePath ) ) << "Test model file not found: " << inputFilePath.toStdString(); + + // Step 1: Load original grid using RifReaderEclipseOutput (properly initializes everything) + std::unique_ptr testCase( new RimEclipseResultCase ); + cvf::ref caseData = new RigEclipseCaseData( testCase.get() ); + + cvf::ref readerInterfaceEcl = new RifReaderEclipseOutput; + bool success = readerInterfaceEcl->open( inputFilePath, caseData.p() ); + ASSERT_TRUE( success ) << "Failed to load grid"; + + testCase->setReservoirData( caseData.p() ); + + const RigMainGrid* grid = caseData->mainGrid(); + ASSERT_NE( grid, nullptr ) << "Grid is null"; + + // Step 2: Create custom visibility for a subset of cells (e.g., middle 50% of grid) + size_t cellCount = grid->cellCount(); + + cvf::ref customVisibility = new cvf::UByteArray( cellCount ); + customVisibility->setAll( 0 ); // Start with all invisible + + std::vector bcconValues( cellCount, 0 ); + + // Make a box in the middle visible + size_t startI = grid->cellCountI() / 4; + size_t endI = 3 * grid->cellCountI() / 4; + size_t startJ = grid->cellCountJ() / 4; + size_t endJ = 3 * grid->cellCountJ() / 4; + size_t startK = grid->cellCountK() / 4; + size_t endK = 3 * grid->cellCountK() / 4; + + for ( size_t i = startI; i < endI; ++i ) + { + for ( size_t j = startJ; j < endJ; ++j ) + { + for ( size_t k = startK; k < endK; ++k ) + { + size_t cellIndex = grid->cellIndexFromIJK( i, j, k ); + ( *customVisibility )[cellIndex] = 1; + + // Set bccon==1 for half of the cells + if ( i > grid->cellCountI() / 2 ) + { + bcconValues[cellIndex] = 1; + } + } + } + } + + // Step 3: Generate BORDNUM result + RigEclipseResultTools::generateBorderResult( testCase.get(), customVisibility, "BORDNUM" ); + + // Verify BORDNUM was created + auto resultsData = testCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL ); + ASSERT_NE( resultsData, nullptr ); + + RigEclipseResultAddress bordNumAddr( RiaDefines::ResultCatType::GENERATED, RiaDefines::ResultDataType::INTEGER, "BORDNUM" ); + ASSERT_TRUE( resultsData->hasResultEntry( bordNumAddr ) ) << "BORDNUM result not created"; + + RigEclipseResultTools::createResultVector( *testCase, "BCCON", bcconValues ); + + // Step 4: Generate border cell faces + auto borderCellFaces = RigEclipseResultTools::generateBorderCellFaces( testCase.get() ); + + // Step 5: Verify results + EXPECT_GT( borderCellFaces.size(), 0 ) << "No border cell faces generated"; + + // Verify that all faces are within the visible region boundaries + for ( const auto& face : borderCellFaces ) + { + EXPECT_GE( face.ijk[0], startI ); + EXPECT_LT( face.ijk[0], endI ); + EXPECT_GE( face.ijk[1], startJ ); + EXPECT_LT( face.ijk[1], endJ ); + EXPECT_GE( face.ijk[2], startK ); + EXPECT_LT( face.ijk[2], endK ); + + // Verify face type is valid + EXPECT_GE( static_cast( face.faceType ), static_cast( cvf::StructGridInterface::POS_I ) ); + EXPECT_LT( static_cast( face.faceType ), static_cast( cvf::StructGridInterface::NO_FACE ) ); + + // Verify boundary condition (should be 1 if no BCCON grid property exists) + EXPECT_EQ( face.boundaryCondition, 1 ); + } + + // Step 6: Create OPM deck file and add BCCON keyword + QTemporaryDir tempDir; + ASSERT_TRUE( tempDir.isValid() ) << "Failed to create temporary directory"; + + // Create a minimal deck file with GRID section + QString deckFilePath = tempDir.filePath( "test_bccon.DATA" ); + { + QFile deckFile( deckFilePath ); + ASSERT_TRUE( deckFile.open( QIODevice::WriteOnly | QIODevice::Text ) ); + QTextStream out( &deckFile ); + out << "RUNSPEC\n\n"; + out << "DIMENS\n"; + out << grid->cellCountI() << " " << grid->cellCountJ() << " " << grid->cellCountK() << " /\n\n"; + out << "GRID\n\n"; + out << "-- BCCON will be added here\n\n"; + out << "PROPS\n\n"; + out << "SCHEDULE\n\n"; + deckFile.close(); + } + + // Load the deck file + RifOpmFlowDeckFile deckFile; + bool deckLoaded = deckFile.loadDeck( deckFilePath.toStdString() ); + ASSERT_TRUE( deckLoaded ) << "Failed to load deck file"; + + // Add BCCON keyword + bool bcconAdded = deckFile.addBcconKeyword( "GRID", borderCellFaces ); + ASSERT_TRUE( bcconAdded ) << "Failed to add BCCON keyword"; + + // Step 7: Save deck and verify format + QString outputDeckPath = tempDir.filePath( "output_bccon.DATA" ); + bool deckSaved = deckFile.saveDeck( tempDir.path().toStdString(), "output_bccon.DATA" ); + ASSERT_TRUE( deckSaved ) << "Failed to save deck file"; + ASSERT_TRUE( QFile::exists( outputDeckPath ) ) << "Output deck file not created"; + + // Read and verify BCCON content + QFile outputFile( outputDeckPath ); + ASSERT_TRUE( outputFile.open( QIODevice::ReadOnly | QIODevice::Text ) ); + QString content = QTextStream( &outputFile ).readAll(); + outputFile.close(); + + // Verify BCCON keyword is present + EXPECT_TRUE( content.contains( "BCCON" ) ) << "BCCON keyword not found in output"; + + // Verify at least one entry exists + // BCCON format: counter i1 i2 j1 j2 k1 k2 face + // Should have face directions like X, X-, Y, Y-, Z, Z- + bool hasFaceDirection = content.contains( "X-" ) || content.contains( "Y-" ) || content.contains( "Z-" ) || content.contains( " X " ) || + content.contains( " Y " ) || content.contains( " Z " ); + EXPECT_TRUE( hasFaceDirection ) << "BCCON entries don't contain face directions"; +} diff --git a/ApplicationLibCode/UnitTests/RiaStatisticsTools-Test.cpp b/ApplicationLibCode/UnitTests/RigStatisticsTools-Test.cpp similarity index 90% rename from ApplicationLibCode/UnitTests/RiaStatisticsTools-Test.cpp rename to ApplicationLibCode/UnitTests/RigStatisticsTools-Test.cpp index 6dd5002ee7a..36577b539de 100644 --- a/ApplicationLibCode/UnitTests/RiaStatisticsTools-Test.cpp +++ b/ApplicationLibCode/UnitTests/RigStatisticsTools-Test.cpp @@ -18,7 +18,7 @@ #include "gtest/gtest.h" -#include "RiaStatisticsTools.h" +#include "RigStatisticsTools.h" #include //-------------------------------------------------------------------------------------------------- @@ -38,7 +38,7 @@ TEST( RiaStatisticsTools, NoCorrelation ) { b.push_back( (double)std::rand() ); } - double correlation = RiaStatisticsTools::pearsonCorrelation( a, b ); + double correlation = RigStatisticsTools::pearsonCorrelation( a, b ); EXPECT_LE( correlation, 0.25 ); } @@ -59,7 +59,7 @@ TEST( RiaStatisticsTools, FullCorrelation ) { b.push_back( i * 2.0 + 1.0 ); } - double correlation = RiaStatisticsTools::pearsonCorrelation( a, b ); + double correlation = RigStatisticsTools::pearsonCorrelation( a, b ); EXPECT_NEAR( correlation, 1.0, 1.0e-2 ); } @@ -80,7 +80,7 @@ TEST( RiaStatisticsTools, NegativeCorrelation ) { b.push_back( i * -2.0 + 1.0 ); } - double correlation = RiaStatisticsTools::pearsonCorrelation( a, b ); + double correlation = RigStatisticsTools::pearsonCorrelation( a, b ); EXPECT_NEAR( correlation, -1.0, 1.0e-2 ); } @@ -95,7 +95,7 @@ TEST( RiaStatisticsTools, MinValue ) { a.push_back( static_cast( i ) ); } - double minValue = RiaStatisticsTools::minimumValue( a ); + double minValue = RigStatisticsTools::minimumValue( a ); EXPECT_EQ( minValue, 10.0 ); } @@ -110,6 +110,6 @@ TEST( RiaStatisticsTools, MaxValue ) { a.push_back( static_cast( i ) ); } - double maxValue = RiaStatisticsTools::maximumValue( a ); + double maxValue = RigStatisticsTools::maximumValue( a ); EXPECT_EQ( maxValue, 999.0 ); } diff --git a/ApplicationLibCode/UserInterface/RiuViewer.cpp b/ApplicationLibCode/UserInterface/RiuViewer.cpp index 22f94b57729..1ba6c9489a9 100644 --- a/ApplicationLibCode/UserInterface/RiuViewer.cpp +++ b/ApplicationLibCode/UserInterface/RiuViewer.cpp @@ -605,7 +605,7 @@ void RiuViewer::showZScaleLabel( bool enable ) //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -void RiuViewer::setZScale( int scale ) +void RiuViewer::setZScale( double scale ) { bool isScaleChanged = m_zScale != scale; m_zScale = scale; diff --git a/ApplicationLibCode/UserInterface/RiuViewer.h b/ApplicationLibCode/UserInterface/RiuViewer.h index 3989bd9ebf3..440f5ceabb4 100644 --- a/ApplicationLibCode/UserInterface/RiuViewer.h +++ b/ApplicationLibCode/UserInterface/RiuViewer.h @@ -87,7 +87,7 @@ class RiuViewer : public caf::Viewer, public RiuInterfaceToViewWindow void hideZScaleCheckbox( bool hide ); void showZScaleLabel( bool enable ); - void setZScale( int scale ); + void setZScale( double scale ); void showHistogram( bool enable ); void setHistogram( double min, double max, const std::vector& histogram ); diff --git a/Fwk/AppFwk/cafTests/cafTestApplication/TamComboBox.cpp b/Fwk/AppFwk/cafTests/cafTestApplication/TamComboBox.cpp index 8a5c064c58c..1a4f7e1f69e 100644 --- a/Fwk/AppFwk/cafTests/cafTestApplication/TamComboBox.cpp +++ b/Fwk/AppFwk/cafTests/cafTestApplication/TamComboBox.cpp @@ -68,6 +68,9 @@ void TamComboBox::defineEditorAttribute( const caf::PdmFieldHandle* field, auto attr = dynamic_cast( attribute ); if ( attr ) { - attr->enableEditableContent = true; + attr->enableEditableContent = true; + attr->enableAutoComplete = false; + attr->adjustWidthToContents = true; + attr->notifyWhenTextIsEdited = false; } } diff --git a/Fwk/AppFwk/cafUserInterface/cafPdmUiComboBoxEditor.cpp b/Fwk/AppFwk/cafUserInterface/cafPdmUiComboBoxEditor.cpp index 11d504fdfcd..ddf49a464ba 100644 --- a/Fwk/AppFwk/cafUserInterface/cafPdmUiComboBoxEditor.cpp +++ b/Fwk/AppFwk/cafUserInterface/cafPdmUiComboBoxEditor.cpp @@ -311,57 +311,6 @@ QMargins PdmUiComboBoxEditor::calculateLabelContentMargins() const return contentMargins; } -//-------------------------------------------------------------------------------------------------- -// Special class used to prevent a combo box to steal focus when scrolling -// the QScrollArea using the mouse wheel -// -// Based on -// http://stackoverflow.com/questions/5821802/qspinbox-inside-a-qscrollarea-how-to-prevent-spin-box-from-stealing-focus-when -//-------------------------------------------------------------------------------------------------- -class CustomQComboBox : public QComboBox -{ -public: - explicit CustomQComboBox( QWidget* parent = nullptr ) - : QComboBox( parent ) - { - } - - //-------------------------------------------------------------------------------------------------- - /// - //-------------------------------------------------------------------------------------------------- - void wheelEvent( QWheelEvent* e ) override - { - if ( hasFocus() ) - { - QComboBox::wheelEvent( e ); - } - else - { - // Ignore the event to make sure event is handled by another widget - e->ignore(); - } - } - -protected: - //-------------------------------------------------------------------------------------------------- - /// - //-------------------------------------------------------------------------------------------------- - void focusInEvent( QFocusEvent* e ) override - { - setFocusPolicy( Qt::WheelFocus ); - QComboBox::focusInEvent( e ); - } - - //-------------------------------------------------------------------------------------------------- - /// - //-------------------------------------------------------------------------------------------------- - void focusOutEvent( QFocusEvent* e ) override - { - setFocusPolicy( Qt::StrongFocus ); - QComboBox::focusOutEvent( e ); - } -}; - //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -386,6 +335,7 @@ QWidget* PdmUiComboBoxEditor::createEditorWidget( QWidget* parent ) m_layout->addWidget( m_comboBox ); connect( m_comboBox, SIGNAL( activated( int ) ), this, SLOT( slotIndexActivated( int ) ) ); + connect( m_comboBox, SIGNAL( signalFocusOutEvent() ), this, SLOT( slotSetCurrentTextToField() ) ); m_autoValueToolButton = new QToolButton( m_placeholder ); m_autoValueToolButton->setCheckable( true ); @@ -445,6 +395,20 @@ void PdmUiComboBoxEditor::slotEditTextChanged( const QString& text ) this->setValueToField( text ); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void PdmUiComboBoxEditor::slotSetCurrentTextToField() +{ + if ( m_attributes.enableEditableContent ) + { + // Use the text directly, as the item text could be entered directly by the user + + auto text = m_comboBox->currentText(); + this->setValueToField( text ); + } +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -483,4 +447,48 @@ void PdmUiComboBoxEditor::slotApplyAutoValue() configureAndUpdateUi( "" ); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +CustomQComboBox::CustomQComboBox( QWidget* parent /*= nullptr */ ) + : QComboBox( parent ) +{ +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void CustomQComboBox::wheelEvent( QWheelEvent* e ) +{ + if ( hasFocus() ) + { + QComboBox::wheelEvent( e ); + } + else + { + // Ignore the event to make sure event is handled by another widget + e->ignore(); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void CustomQComboBox::focusInEvent( QFocusEvent* e ) +{ + setFocusPolicy( Qt::WheelFocus ); + QComboBox::focusInEvent( e ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void CustomQComboBox::focusOutEvent( QFocusEvent* e ) +{ + setFocusPolicy( Qt::StrongFocus ); + QComboBox::focusOutEvent( e ); + + emit signalFocusOutEvent(); +} + } // end namespace caf diff --git a/Fwk/AppFwk/cafUserInterface/cafPdmUiComboBoxEditor.h b/Fwk/AppFwk/cafUserInterface/cafPdmUiComboBoxEditor.h index adac7873cee..9642f00bff1 100644 --- a/Fwk/AppFwk/cafUserInterface/cafPdmUiComboBoxEditor.h +++ b/Fwk/AppFwk/cafUserInterface/cafPdmUiComboBoxEditor.h @@ -108,6 +108,7 @@ protected slots: void slotIndexActivated( int index ); void slotEditTextChanged( const QString& ); + void slotSetCurrentTextToField(); void slotNextButtonPressed(); void slotPreviousButtonPressed(); void slotApplyAutoValue(); @@ -127,4 +128,27 @@ protected slots: int m_interactiveEditCursorPosition; }; +//-------------------------------------------------------------------------------------------------- +// Special class used to prevent a combo box to steal focus when scrolling +// the QScrollArea using the mouse wheel +// +// Based on +// http://stackoverflow.com/questions/5821802/qspinbox-inside-a-qscrollarea-how-to-prevent-spin-box-from-stealing-focus-when +//-------------------------------------------------------------------------------------------------- +class CustomQComboBox : public QComboBox +{ + Q_OBJECT +public: + explicit CustomQComboBox( QWidget* parent = nullptr ); + + void wheelEvent( QWheelEvent* e ) override; + +protected: + void focusInEvent( QFocusEvent* e ) override; + void focusOutEvent( QFocusEvent* e ) override; + +signals: + void signalFocusOutEvent(); +}; + } // end namespace caf diff --git a/GrpcInterface/CMakeLists.txt b/GrpcInterface/CMakeLists.txt index 7bcb2c5713a..c3c1eb034e6 100644 --- a/GrpcInterface/CMakeLists.txt +++ b/GrpcInterface/CMakeLists.txt @@ -217,7 +217,7 @@ list( list(APPEND GRPC_PYTHON_SOURCES ${GRPC_PYTHON_GENERATED_SOURCES}) add_library( - ${PROJECT_NAME} OBJECT + ${PROJECT_NAME} STATIC ${SOURCE_GROUP_HEADER_FILES} ${SOURCE_GROUP_SOURCE_FILES} ${GRPC_HEADER_FILES} ${GRPC_CPP_SOURCES}) diff --git a/GrpcInterface/Python/rips/tests/test_view.py b/GrpcInterface/Python/rips/tests/test_view.py new file mode 100644 index 00000000000..fe11e6441de --- /dev/null +++ b/GrpcInterface/Python/rips/tests/test_view.py @@ -0,0 +1,64 @@ +import sys +import os + +sys.path.insert(1, os.path.join(sys.path[0], "../../")) + +import dataroot + + +def test_visible_cells(rips_instance, initialize_test): + """Test the visible_cells() method on a view""" + case_root_path = dataroot.PATH + "/TEST10K_FLT_LGR_NNC" + case_path = case_root_path + "/TEST10K_FLT_LGR_NNC.EGRID" + case = rips_instance.project.load_case(path=case_path) + + # Get the first view or create one + views = case.views() + if views: + view = views[0] + else: + view = case.create_view() + + # Test visible_cells at time step 0 + visibility = view.visible_cells(time_step=0) + + # Verify we got a list + assert isinstance(visibility, list) + + # Verify we have data + assert len(visibility) > 0 + + # Verify all values are either 0 or 1 + for v in visibility: + assert v in [0, 1] + + # Count visible and invisible cells + visible_count = sum(visibility) + invisible_count = len(visibility) - visible_count + + # Total cells should match grid size + assert len(visibility) == visible_count + invisible_count + + # The visibility array should have the correct size (can be all visible, all invisible, or mixed) + # This depends on the view settings + assert visible_count + invisible_count > 0 + + +def test_visible_cells_different_timesteps(rips_instance, initialize_test): + """Test visible_cells() at different time steps""" + case_root_path = dataroot.PATH + "/TEST10K_FLT_LGR_NNC" + case_path = case_root_path + "/TEST10K_FLT_LGR_NNC.EGRID" + case = rips_instance.project.load_case(path=case_path) + + view = case.views()[0] if case.views() else case.create_view() + + # Test at time step 0 + visibility_ts0 = view.visible_cells(time_step=0) + assert len(visibility_ts0) > 0 + + # Test at time step 1 (if it exists) + visibility_ts1 = view.visible_cells(time_step=1) + assert len(visibility_ts1) > 0 + + # Both should have the same number of cells + assert len(visibility_ts0) == len(visibility_ts1) diff --git a/GrpcInterface/Python/rips/view.py b/GrpcInterface/Python/rips/view.py index 3b9294007e3..f95dcbbf6b3 100644 --- a/GrpcInterface/Python/rips/view.py +++ b/GrpcInterface/Python/rips/view.py @@ -4,6 +4,9 @@ import Commands_pb2 as Cmd +import uuid +import rips.project + import rips.case # Circular import of Case, which already imports View. Use full name. from .pdmobject import add_method from .resinsight_classes import ( @@ -211,6 +214,37 @@ def case(self): return None +@add_method(View) +def visible_cells(self, time_step=0): + """Get the visibility status of all cells in the view + + Arguments: + time_step (int): The time step to get visibility for. Defaults to 0. + + Returns: + List[int]: A list where 1 represents a visible cell and 0 represents an invisible cell + """ + + # Generate temporary key for key-value store + visibility_key = f"{uuid.uuid4()}_visibility" + + # Get the project + project = self.ancestor(rips.project.Project) + try: + # Call internal method to store visibility data + self.visible_cells_internal(visibility_key=visibility_key, time_step=time_step) + + # Retrieve visibility data from key-value store + visibility_values = project.key_values(visibility_key) + + # Convert floats to integers (1 for visible, 0 for invisible) + return [int(v) for v in visibility_values] + + finally: + # Clean up temporary key from key-value store + project.remove_key_values(visibility_key) + + @add_method(ViewWindow) def export_snapshot(self, prefix="", export_folder=""): """Export snapshot for the current view diff --git a/ResInsightVersion.cmake b/ResInsightVersion.cmake index 787d82d6680..80d3bf4f74a 100644 --- a/ResInsightVersion.cmake +++ b/ResInsightVersion.cmake @@ -1,7 +1,7 @@ set(RESINSIGHT_MAJOR_VERSION 2025) set(RESINSIGHT_MINOR_VERSION 09) -set(RESINSIGHT_PATCH_VERSION 2) +set(RESINSIGHT_PATCH_VERSION 3) # Opional text with no restrictions set(RESINSIGHT_VERSION_TEXT "-dev") @@ -11,7 +11,7 @@ set(RESINSIGHT_VERSION_TEXT "-dev") # Must be unique and increasing within one combination of major/minor/patch version # The uniqueness of this text is independent of RESINSIGHT_VERSION_TEXT # Format of text must be ".xx" -set(RESINSIGHT_DEV_VERSION ".04") +set(RESINSIGHT_DEV_VERSION ".01") set(STRPRODUCTVER ${RESINSIGHT_MAJOR_VERSION}.${RESINSIGHT_MINOR_VERSION}.${RESINSIGHT_PATCH_VERSION}${RESINSIGHT_VERSION_TEXT}${RESINSIGHT_DEV_VERSION})