///////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2026- 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 "RimEclipseResultCase.h" #include "RimEclipseResultDefinition.h" #include "RigActiveCellInfo.h" #include "RigCaseCellResultsData.h" #include "RigEclipseCaseData.h" #include "RigEclipseCrossPlotDataExtractor.h" #include "RigEclipseResultAddress.h" #include "RigMainGrid.h" #include "RigReservoirBuilder.h" #include "cvfVector3.h" #include #include namespace { struct CrossPlotMockCase { std::unique_ptr resultCase; cvf::ref eclipseCase; }; //-------------------------------------------------------------------------------------------------- /// Build a regular ni x nj x nk box grid in memory (no file, no view) //-------------------------------------------------------------------------------------------------- CrossPlotMockCase buildCrossPlotBoxGridCase( int ni, int nj, int nk ) { RigReservoirBuilder builder; builder.setIJKCount( cvf::Vec3st( ni, nj, nk ) ); builder.setWorldCoordinates( cvf::Vec3d( 0.0, 0.0, 0.0 ), cvf::Vec3d( ni, nj, -nk ) ); CrossPlotMockCase mockCase; mockCase.resultCase.reset( new RimEclipseResultCase ); mockCase.eclipseCase = new RigEclipseCaseData( mockCase.resultCase.get() ); builder.createGridsAndCells( mockCase.eclipseCase.p() ); mockCase.eclipseCase->mainGrid()->computeCachedData(); mockCase.resultCase->setReservoirData( mockCase.eclipseCase.p() ); return mockCase; } void fillStaticResult( RigCaseCellResultsData* resultsData, const QString& resultName, size_t valueCount, double value ) { resultsData->addStaticScalarResult( RiaDefines::ResultCatType::STATIC_NATIVE, resultName, false, valueCount ); const RigEclipseResultAddress resultAddress( RiaDefines::ResultCatType::STATIC_NATIVE, resultName ); std::vector* resultVector = resultsData->modifiableCellScalarResult( resultAddress, 0 ); ASSERT_NE( resultVector, nullptr ); ASSERT_EQ( resultVector->size(), valueCount ); std::fill( resultVector->begin(), resultVector->end(), value ); } void setupResultDefinition( RimEclipseResultDefinition* resultDefinition, RimEclipseResultCase* resultCase, const QString& resultName ) { resultDefinition->setEclipseCase( resultCase ); resultDefinition->setPorosityModel( RiaDefines::PorosityModelType::MATRIX_MODEL ); resultDefinition->setResultType( RiaDefines::ResultCatType::STATIC_NATIVE ); resultDefinition->setResultVariable( resultName ); } } // namespace //-------------------------------------------------------------------------------------------------- /// A cell visibility array not matching the reservoir cell count must be ignored. /// /// RimEclipseView::calculateCurrentTotalCellVisibility() returns without resizing when the view has /// no main grid, leaving an empty array in the visibility map. Indexing that array crashed. //-------------------------------------------------------------------------------------------------- TEST( RigEclipseCrossPlotDataExtractorTest, ExtractIgnoresEmptyCellVisibility ) { CrossPlotMockCase mockCase = buildCrossPlotBoxGridCase( 10, 10, 5 ); RigCaseCellResultsData* resultsData = mockCase.eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL ); ASSERT_NE( resultsData, nullptr ); ASSERT_NE( resultsData->activeCellInfo(), nullptr ); const size_t activeCellCount = resultsData->activeCellInfo()->reservoirActiveCellCount(); ASSERT_EQ( activeCellCount, 500u ); fillStaticResult( resultsData, "XVALUES", activeCellCount, 1.0 ); fillStaticResult( resultsData, "YVALUES", activeCellCount, 2.0 ); RimEclipseResultDefinition xAddress; RimEclipseResultDefinition yAddress; RimEclipseResultDefinition groupAddress; setupResultDefinition( &xAddress, mockCase.resultCase.get(), "XVALUES" ); setupResultDefinition( &yAddress, mockCase.resultCase.get(), "YVALUES" ); // An empty visibility array is what a view without a main grid leaves behind. std::map timeStepCellVisibilityMap; timeStepCellVisibilityMap[0] = cvf::UByteArray(); RigEclipseCrossPlotResult result = RigEclipseCrossPlotDataExtractor::extract( mockCase.eclipseCase.p(), 0, xAddress, yAddress, RigGridCrossPlotCurveGrouping::NO_GROUPING, groupAddress, timeStepCellVisibilityMap ); // The filter must be ignored, so all active cells are included. EXPECT_EQ( result.xValues.size(), activeCellCount ); EXPECT_EQ( result.yValues.size(), activeCellCount ); } //-------------------------------------------------------------------------------------------------- /// A cell visibility array from a smaller grid must be ignored instead of read out of bounds. //-------------------------------------------------------------------------------------------------- TEST( RigEclipseCrossPlotDataExtractorTest, ExtractIgnoresTooShortCellVisibility ) { CrossPlotMockCase mockCase = buildCrossPlotBoxGridCase( 10, 10, 5 ); RigCaseCellResultsData* resultsData = mockCase.eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL ); ASSERT_NE( resultsData, nullptr ); ASSERT_NE( resultsData->activeCellInfo(), nullptr ); const size_t activeCellCount = resultsData->activeCellInfo()->reservoirActiveCellCount(); const size_t reservoirCellCount = resultsData->activeCellInfo()->reservoirCellCount(); ASSERT_EQ( activeCellCount, 500u ); fillStaticResult( resultsData, "XVALUES", activeCellCount, 3.0 ); fillStaticResult( resultsData, "YVALUES", activeCellCount, 4.0 ); RimEclipseResultDefinition xAddress; RimEclipseResultDefinition yAddress; RimEclipseResultDefinition groupAddress; setupResultDefinition( &xAddress, mockCase.resultCase.get(), "XVALUES" ); setupResultDefinition( &yAddress, mockCase.resultCase.get(), "YVALUES" ); // Visibility computed for a smaller grid, as when the cell filter view refers to another case. std::map timeStepCellVisibilityMap; cvf::UByteArray& cellVisibility = timeStepCellVisibilityMap[0]; cellVisibility.resize( reservoirCellCount / 2 ); cellVisibility.setAll( 1 ); RigEclipseCrossPlotResult result = RigEclipseCrossPlotDataExtractor::extract( mockCase.eclipseCase.p(), 0, xAddress, yAddress, RigGridCrossPlotCurveGrouping::NO_GROUPING, groupAddress, timeStepCellVisibilityMap ); EXPECT_EQ( result.xValues.size(), activeCellCount ); EXPECT_EQ( result.yValues.size(), activeCellCount ); }