mirror of
https://github.com/OPM/ResInsight.git
synced 2026-08-27 05:37:21 -05:00
Migrate all assert macros in ApplicationLibCode to CAF_ASSERT and remove every use of cvfAssert.h. CVF_ASSERT is replaced one to one. CVF_TIGHT_ASSERT is also replaced by CAF_ASSERT, which is semantically exact: CVF_ENABLE_TIGHT_ASSERTS is 1 only under _DEBUG, and that is what CAF_ASSERT now does. The two CVF_FAIL_MSG sites become CAF_ASSERT( false && "message" ), preserving the message with the idiom already used elsewhere in the code base. Counts before and after: CVF_ASSERT 1044 to 0, CVF_TIGHT_ASSERT 66 to 0, CVF_FAIL_MSG 2 to 0, cvfAssert.h references 154 to 0. Include handling: files that included cvfAssert.h directly now include cafAssert.h instead, includes left dead by the migration are removed, and files that were relying on cvfAssert.h transitively get an explicit cafAssert.h. Files that reach cafAssert.h through another caf header are left unchanged; a missing include here is a compile error, not a silently disabled assert. ResultStatisticsCache links only LibCore and therefore had no path to cafAssert.h. Add the cafPdmCore directory as a private include path rather than linking the library, since cafAssert.h is header only. Note that this stops these asserts from firing in Release and RelWithDebInfo, where CVF_ASSERT was previously active.
857 lines
34 KiB
C++
857 lines
34 KiB
C++
/////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright (C) 2022- Equinor ASA
|
|
//
|
|
// ResInsight is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
// FITNESS FOR A PARTICULAR PURPOSE.
|
|
//
|
|
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
|
|
// for more details.
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "RifRoffFileTools.h"
|
|
|
|
#include "RiaApplication.h"
|
|
#include "RiaFractureDefines.h"
|
|
#include "RiaLogging.h"
|
|
#include "RiaQStringFormatter.h"
|
|
#include "RiaResultNames.h"
|
|
|
|
#include "RicFaciesPropertiesImportTools.h"
|
|
|
|
#include "RigActiveCellInfo.h"
|
|
#include "RigCaseCellResultsData.h"
|
|
#include "RigEclipseCaseData.h"
|
|
#include "RigEclipseResultAddress.h"
|
|
#include "RigMainGrid.h"
|
|
|
|
#include "RimColorLegendCollection.h"
|
|
#include "RimEclipseCase.h"
|
|
#include "RimProject.h"
|
|
|
|
#include "cafProgressInfo.h"
|
|
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <vector>
|
|
|
|
#include "Reader.hpp"
|
|
|
|
#ifdef USE_OPENMP
|
|
#include <omp.h>
|
|
#endif
|
|
|
|
using namespace std::chrono;
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
/// NOTE: Moved from header file due to compile header when using RifRoffFileTools from
|
|
/// ApplicationLibCode/Commands
|
|
//--------------------------------------------------------------------------------------------------
|
|
template <typename IN, typename OUT>
|
|
static void convertToReservoirIndexOrder( int nx, int ny, int nz, const std::vector<IN>& in, std::vector<OUT>& out )
|
|
{
|
|
CAF_ASSERT( static_cast<size_t>( nx * ny * nz ) == in.size() );
|
|
|
|
out.resize( in.size(), -1 );
|
|
|
|
int outIdx = 0;
|
|
for ( int k = 0; k < nz; k++ )
|
|
{
|
|
for ( int j = 0; j < ny; j++ )
|
|
{
|
|
for ( int i = 0; i < nx; i++ )
|
|
{
|
|
int inIdx = i * ny * nz + j * nz + ( nz - k - 1 );
|
|
out[outIdx] = static_cast<OUT>( in[inIdx] );
|
|
outIdx++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
/// Constructor
|
|
//--------------------------------------------------------------------------------------------------
|
|
RifRoffFileTools::RifRoffFileTools()
|
|
{
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
/// Destructor
|
|
//--------------------------------------------------------------------------------------------------
|
|
RifRoffFileTools::~RifRoffFileTools()
|
|
{
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifRoffFileTools::openGridFile( const QString& fileName, RigEclipseCaseData* eclipseCase, QString* errorMessages )
|
|
{
|
|
RiaLogging::info( std::format( "Opening roff file: {}", fileName ) );
|
|
|
|
std::string filename = fileName.toStdString();
|
|
|
|
std::ifstream stream( filename, std::ios::binary );
|
|
if ( !stream.good() )
|
|
{
|
|
if ( errorMessages ) *errorMessages = QString( "Unable to open roff file" );
|
|
return false;
|
|
}
|
|
|
|
auto getInt = []( auto values, const std::string& name )
|
|
{
|
|
auto v = std::find_if( values.begin(), values.end(), [&name]( const auto& arg ) { return arg.first == name; } );
|
|
if ( v != values.end() )
|
|
return std::get<int>( v->second );
|
|
else
|
|
throw std::runtime_error( "Missing parameter (integer): " + name );
|
|
};
|
|
|
|
auto getFloat = []( auto values, const std::string& name )
|
|
{
|
|
auto v = std::find_if( values.begin(), values.end(), [&name]( const auto& arg ) { return arg.first == name; } );
|
|
if ( v != values.end() )
|
|
return std::get<float>( v->second );
|
|
else
|
|
throw std::runtime_error( "Missing parameter (float): " + name );
|
|
};
|
|
|
|
try
|
|
{
|
|
const auto totalStart = high_resolution_clock::now();
|
|
|
|
roff::Reader reader( stream );
|
|
reader.parse();
|
|
|
|
const auto tokenizeDone = high_resolution_clock::now();
|
|
|
|
std::vector<std::pair<std::string, roff::RoffScalar>> values = reader.scalarNamedValues();
|
|
std::vector<std::pair<std::string, roff::Token::Kind>> arrayTypes = reader.getNamedArrayTypes();
|
|
|
|
int nx = getInt( values, "dimensions.nX" );
|
|
int ny = getInt( values, "dimensions.nY" );
|
|
int nz = getInt( values, "dimensions.nZ" );
|
|
|
|
float xOffset = getFloat( values, "translate.xoffset" );
|
|
float yOffset = getFloat( values, "translate.yoffset" );
|
|
float zOffset = getFloat( values, "translate.zoffset" );
|
|
|
|
float xScale = getFloat( values, "scale.xscale" );
|
|
float yScale = getFloat( values, "scale.yscale" );
|
|
float zScale = getFloat( values, "scale.zscale" );
|
|
|
|
RiaLogging::debug( std::format( "Grid dimensions: {} {} {}", nx, ny, nz ) );
|
|
RiaLogging::debug( std::format( "Offset: {} {} {}", xOffset, yOffset, zOffset ) );
|
|
RiaLogging::debug( std::format( "Scale: {} {} {}", xScale, yScale, zScale ) );
|
|
|
|
std::vector<float> cornerLines = reader.getFloatArray( "cornerLines" + roff::Parser::postFixData() );
|
|
std::vector<float> zValues = reader.getFloatArray( "zvalues" + roff::Parser::postFixData() );
|
|
std::vector<char> splitEnz = reader.getByteArray( "zvalues.splitEnz" );
|
|
std::vector<char> active = reader.getByteArray( "active" + roff::Parser::postFixData() );
|
|
|
|
const auto parsingDone = high_resolution_clock::now();
|
|
|
|
unsigned int zCornerSize = static_cast<unsigned int>( ( nx + 1 ) * ( ny + 1 ) * ( nz + 1 ) * 4u );
|
|
std::vector<float> zCorners( zCornerSize, 0.0 );
|
|
|
|
interpretSplitenzData( static_cast<int>( nz ) + 1, zOffset, zScale, splitEnz, zValues, zCorners );
|
|
|
|
RigActiveCellInfo* activeCellInfo = eclipseCase->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL );
|
|
CAF_ASSERT( activeCellInfo );
|
|
|
|
RigActiveCellInfo* fractureActiveCellInfo = eclipseCase->activeCellInfo( RiaDefines::PorosityModelType::FRACTURE_MODEL );
|
|
CAF_ASSERT( fractureActiveCellInfo );
|
|
|
|
RigMainGrid* mainGrid = eclipseCase->mainGrid();
|
|
CAF_ASSERT( mainGrid );
|
|
|
|
mainGrid->setCellCounts( cvf::Vec3st( nx, ny, nz ) );
|
|
mainGrid->setGridName( "Main grid" );
|
|
|
|
size_t totalCellCount = nx * ny * nz;
|
|
|
|
activeCellInfo->setGridCount( 1 );
|
|
fractureActiveCellInfo->setGridCount( 1 );
|
|
|
|
activeCellInfo->setReservoirCellCount( totalCellCount );
|
|
fractureActiveCellInfo->setReservoirCellCount( totalCellCount );
|
|
|
|
// Reserve room for the cells and nodes and fill them with data
|
|
mainGrid->reservoirCells().reserve( totalCellCount );
|
|
mainGrid->nodes().reserve( 8 * totalCellCount );
|
|
|
|
int progTicks = 100;
|
|
caf::ProgressInfo progInfo( progTicks, "" );
|
|
|
|
int cellCount = static_cast<int>( totalCellCount );
|
|
size_t cellStartIndex = mainGrid->reservoirCells().size();
|
|
size_t nodeStartIndex = mainGrid->nodes().size();
|
|
|
|
RigCell defaultCell;
|
|
defaultCell.setHostGrid( mainGrid );
|
|
mainGrid->reservoirCells().resize( cellStartIndex + cellCount, defaultCell );
|
|
|
|
mainGrid->nodes().resize( nodeStartIndex + static_cast<size_t>( cellCount ) * 8, cvf::Vec3d( 0, 0, 0 ) );
|
|
|
|
// Swap i and j to get correct faces
|
|
const size_t cellMappingECLRi[8] = { 2, 3, 1, 0, 6, 7, 5, 4 };
|
|
|
|
cvf::Vec3d offset( xOffset, yOffset, zOffset );
|
|
cvf::Vec3d scale( xScale, yScale, zScale );
|
|
|
|
std::vector<int> activeCells;
|
|
convertToReservoirIndexOrder<char, int>( nx, ny, nz, active, activeCells );
|
|
|
|
// Precompute the active cell matrix index
|
|
size_t numActiveCells = computeActiveCellMatrixIndex( activeCells );
|
|
|
|
// Loop over cells and fill them with data
|
|
#pragma omp for
|
|
for ( int gridLocalCellIndex = 0; gridLocalCellIndex < cellCount; ++gridLocalCellIndex )
|
|
{
|
|
RigCell& cell = mainGrid->cell( cellStartIndex + gridLocalCellIndex );
|
|
|
|
cell.setGridLocalCellIndex( gridLocalCellIndex );
|
|
|
|
// Active cell index
|
|
int matrixActiveIndex = activeCells[gridLocalCellIndex];
|
|
if ( matrixActiveIndex != -1 )
|
|
{
|
|
activeCellInfo->setCellResultIndex( ReservoirCellIndex( cellStartIndex + gridLocalCellIndex ),
|
|
ActiveCellIndex( matrixActiveIndex ) );
|
|
}
|
|
|
|
cell.setParentCellIndex( cvf::UNDEFINED_SIZE_T );
|
|
|
|
// Corner coordinates
|
|
for ( int cIdx = 0; cIdx < 8; ++cIdx )
|
|
{
|
|
double* point = mainGrid->nodes()[nodeStartIndex + (size_t)gridLocalCellIndex * 8 + cellMappingECLRi[cIdx]].ptr();
|
|
auto corner = getCorner( *mainGrid, cornerLines, zCorners, gridLocalCellIndex, cIdx, offset, scale );
|
|
|
|
point[0] = corner.x();
|
|
point[1] = corner.y();
|
|
point[2] = corner.z();
|
|
|
|
cell.cornerIndices()[cIdx] = nodeStartIndex + (size_t)gridLocalCellIndex * 8 + cIdx;
|
|
}
|
|
|
|
// Mark inactive long pyramid looking cells as invalid
|
|
cell.setInvalid( cell.isLongPyramidCell() );
|
|
}
|
|
|
|
activeCellInfo->setGridActiveCellCounts( 0, numActiveCells );
|
|
fractureActiveCellInfo->setGridActiveCellCounts( 0, 0 );
|
|
|
|
mainGrid->initAllSubGridsParentGridPointer();
|
|
activeCellInfo->computeDerivedData();
|
|
fractureActiveCellInfo->computeDerivedData();
|
|
|
|
auto gridConstructionDone = high_resolution_clock::now();
|
|
|
|
auto tokenizeDuration = duration_cast<milliseconds>( tokenizeDone - totalStart );
|
|
RiaLogging::debug( std::format( "Tokenizing: {} ms", tokenizeDuration.count() ) );
|
|
|
|
auto parsingDuration = duration_cast<milliseconds>( parsingDone - tokenizeDone );
|
|
RiaLogging::debug( std::format( "Parsing: {} ms", parsingDuration.count() ) );
|
|
|
|
auto gridConstructionDuration = duration_cast<milliseconds>( gridConstructionDone - parsingDone );
|
|
RiaLogging::debug( std::format( "Grid Construction: {} ms", gridConstructionDuration.count() ) );
|
|
|
|
auto totalDuration = duration_cast<milliseconds>( gridConstructionDone - totalStart );
|
|
RiaLogging::debug( std::format( "Total: {} ms", totalDuration.count() ) );
|
|
}
|
|
catch ( std::runtime_error& err )
|
|
{
|
|
RiaLogging::error( std::format( "Roff file import failed: {}", err.what() ) );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// The indexing conventions for vertices in Roff file
|
|
//
|
|
// 1-------------0
|
|
// /| /|
|
|
// / | / | /j
|
|
// / | / | /
|
|
// 3-------------2 | *---i
|
|
// | | | | |
|
|
// | 5---------|---4 |
|
|
// | / | / |k
|
|
// | / | /
|
|
// |/ |/
|
|
// 7-------------6
|
|
// vertex indices
|
|
//
|
|
|
|
//
|
|
// The indexing conventions for vertices in ResInsight
|
|
//
|
|
// 7-------------6 |k
|
|
// /| /| | /j
|
|
// / | / | |/
|
|
// / | / | *---i
|
|
// 4-------------5 |
|
|
// | | | |
|
|
// | 3---------|---2
|
|
// | / | /
|
|
// | / | /
|
|
// |/ |/
|
|
// 0-------------1
|
|
// vertex indices
|
|
//
|
|
//--------------------------------------------------------------------------------------------------
|
|
cvf::Vec3d RifRoffFileTools::getCorner( const RigMainGrid& grid,
|
|
const std::vector<float>& cornerLines,
|
|
const std::vector<float>& zcorn,
|
|
size_t cellIdx,
|
|
int cornerIdx,
|
|
const cvf::Vec3d& offset,
|
|
const cvf::Vec3d& scale )
|
|
{
|
|
size_t iOffset = 0;
|
|
if ( cornerIdx != 1 && cornerIdx != 3 && cornerIdx != 5 && cornerIdx != 7 )
|
|
{
|
|
iOffset = 1;
|
|
}
|
|
|
|
size_t jOffset = 0;
|
|
if ( cornerIdx != 2 && cornerIdx != 3 && cornerIdx != 6 && cornerIdx != 7 )
|
|
{
|
|
jOffset = 1;
|
|
}
|
|
|
|
size_t i;
|
|
size_t j;
|
|
size_t k;
|
|
grid.ijkFromCellIndex( cellIdx, &i, &j, &k );
|
|
|
|
size_t ny = grid.cellCountJ();
|
|
size_t nz = grid.cellCountK();
|
|
|
|
size_t cOffset = ( ( i + iOffset ) * ( ny + 1 ) + ( j + jOffset ) ) * 6;
|
|
|
|
cvf::Vec3d top( cornerLines[cOffset], cornerLines[cOffset + 1], cornerLines[cOffset + 2] );
|
|
cvf::Vec3d bottom( cornerLines[cOffset + 3], cornerLines[cOffset + 4], cornerLines[cOffset + 5] );
|
|
|
|
int adjustedCornerIdx = cornerIdx;
|
|
if ( cornerIdx == 4 ) adjustedCornerIdx = 0;
|
|
if ( cornerIdx == 5 ) adjustedCornerIdx = 1;
|
|
if ( cornerIdx == 6 ) adjustedCornerIdx = 2;
|
|
if ( cornerIdx == 7 ) adjustedCornerIdx = 3;
|
|
|
|
// Find the correct offset k direction
|
|
size_t kOffset = 0;
|
|
if ( cornerIdx > 3 )
|
|
{
|
|
kOffset = 1;
|
|
}
|
|
|
|
size_t zOffset = ( ( i + iOffset ) * ( ny + 1 ) * ( nz + 1 ) + ( j + jOffset ) * ( nz + 1 ) + ( k + kOffset ) ) * 4 + adjustedCornerIdx;
|
|
|
|
double z = -zcorn[zOffset];
|
|
double x = interpolate( top, bottom, z, 0 ) + offset.x();
|
|
double y = interpolate( top, bottom, z, 1 ) + offset.y();
|
|
|
|
cvf::Vec3d result = cvf::Vec3d( x, y, z );
|
|
return result;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
/// Adapted from xtgeo
|
|
//--------------------------------------------------------------------------------------------------
|
|
double RifRoffFileTools::interpolate( const cvf::Vec3d& top, const cvf::Vec3d& bottom, double z, int idx )
|
|
{
|
|
if ( fabs( bottom[idx] - top[idx] ) > 0.01 )
|
|
{
|
|
return top[idx] - ( z - top.z() ) * ( top[idx] - bottom[idx] ) / ( bottom.z() - top.z() );
|
|
}
|
|
else
|
|
{
|
|
return top[idx];
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
/// Adapted from xtgeo: https://github.com/equinor/xtgeo/blob/master/src/clib/xtg/grd3d_roff2xtgeo_splitenz.c
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RifRoffFileTools::interpretSplitenzData( int nz,
|
|
float zoffset,
|
|
float zscale,
|
|
const std::vector<char>& splitenz,
|
|
const std::vector<float>& zdata,
|
|
std::vector<float>& zcornsv )
|
|
|
|
{
|
|
// We Read one corner line (pillar) from zdata at a time (one for each
|
|
// i,j), transform it according to zoffset and zscale, and put it in to
|
|
// zcornsv in reverse order.
|
|
|
|
// As i and j order and size is the same for both zcornsv and zdata, we can
|
|
// ignore it here and place one pillar at the time regardless of how many
|
|
// pillars there are.
|
|
|
|
size_t nzcorn = zcornsv.size();
|
|
size_t nsplitenz = splitenz.size();
|
|
size_t nzdata = zdata.size();
|
|
|
|
size_t num_row = 4 * static_cast<size_t>( nz );
|
|
if ( nzcorn % num_row != 0 ) throw std::runtime_error( "Incorrect size of zcorn." );
|
|
if ( nsplitenz != nzcorn / 4 ) throw std::runtime_error( "Incorrect size of splitenz." );
|
|
|
|
std::vector<float> pillar( num_row, 0.0 );
|
|
size_t it_zdata = 0, it_splitenz = 0, it_zcorn = 0;
|
|
while ( it_zcorn < nzcorn )
|
|
{
|
|
for ( size_t it_pillar = 0; it_pillar < num_row; )
|
|
{
|
|
char split = splitenz[it_splitenz++];
|
|
if ( split == 1 )
|
|
{
|
|
// There is one value for this corner which
|
|
// we must duplicate 4 times in zcornsv
|
|
if ( it_zdata >= nzdata ) throw std::runtime_error( "Incorrect size of zdata" );
|
|
float val = ( zdata[it_zdata++] + zoffset ) * zscale;
|
|
for ( int n = 0; n < 4; n++ )
|
|
{
|
|
pillar[it_pillar++] = val;
|
|
}
|
|
}
|
|
else if ( split == 4 )
|
|
{
|
|
// There are four value for this corner which
|
|
// we must duplicate 4 times in zcornsv
|
|
if ( it_zdata + 3 >= nzdata ) throw std::runtime_error( "Incorrect size of zdata" );
|
|
// As we place the pillar in reverse order into zcornsv,
|
|
// we must put zdata in reverse order into pillar to
|
|
// preserve n,s,w,e directions.
|
|
pillar[it_pillar + 3] = ( zdata[it_zdata++] + zoffset ) * zscale;
|
|
pillar[it_pillar + 2] = ( zdata[it_zdata++] + zoffset ) * zscale;
|
|
pillar[it_pillar + 1] = ( zdata[it_zdata++] + zoffset ) * zscale;
|
|
pillar[it_pillar] = ( zdata[it_zdata++] + zoffset ) * zscale;
|
|
it_pillar += 4;
|
|
}
|
|
else
|
|
{
|
|
throw std::runtime_error( "Unsupported split type" );
|
|
}
|
|
}
|
|
// Put the pillar into zcornsv in reverse order
|
|
for ( size_t it_pillar = num_row; it_pillar >= 1; )
|
|
{
|
|
zcornsv[it_zcorn++] = pillar[--it_pillar];
|
|
}
|
|
}
|
|
|
|
if ( it_splitenz != nsplitenz ) throw std::runtime_error( "Incorrect size of splitenz." );
|
|
if ( it_zdata != nzdata ) throw std::runtime_error( "Incorrect size of zdata" );
|
|
if ( it_zcorn != nzcorn ) throw std::runtime_error( "Incorrect size of zcorn." );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
size_t RifRoffFileTools::computeActiveCellMatrixIndex( std::vector<int>& activeCells )
|
|
{
|
|
int activeMatrixIndex = 0;
|
|
int cellCount = static_cast<int>( activeCells.size() );
|
|
for ( int gridLocalCellIndex = 0; gridLocalCellIndex < cellCount; gridLocalCellIndex++ )
|
|
{
|
|
if ( activeCells[gridLocalCellIndex] != 0 )
|
|
{
|
|
activeCells[gridLocalCellIndex] = activeMatrixIndex;
|
|
activeMatrixIndex++;
|
|
}
|
|
else
|
|
{
|
|
activeCells[gridLocalCellIndex] = -1;
|
|
}
|
|
}
|
|
|
|
return activeMatrixIndex;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::pair<bool, std::map<QString, QString>> RifRoffFileTools::createInputProperties( const QString& fileName,
|
|
RigEclipseCaseData* eclipseCaseData )
|
|
{
|
|
RiaLogging::info( std::format( "Reading properties from roff file: {}", fileName ) );
|
|
|
|
std::string filename = fileName.toStdString();
|
|
|
|
std::map<QString, QString> keywordMapping;
|
|
|
|
std::ifstream stream( filename, std::ios::binary );
|
|
if ( !stream.good() )
|
|
{
|
|
RiaLogging::error( "Unable to open roff file" );
|
|
return std::make_pair( false, keywordMapping );
|
|
}
|
|
|
|
auto codeNamesAndValuesForKeyword = []( const std::string& keyword, roff::Reader& reader ) -> std::map<int, QString>
|
|
{
|
|
const std::string codeNamesKeyword = keyword + roff::Parser::postFixCodeNames();
|
|
const std::string codeValuesKeyword = keyword + roff::Parser::postFixCodeValues();
|
|
|
|
if ( reader.getArrayLength( codeNamesKeyword ) > 0 &&
|
|
reader.getArrayLength( codeNamesKeyword ) == reader.getArrayLength( codeValuesKeyword ) )
|
|
{
|
|
const auto fileCodeNames = reader.getStringArray( codeNamesKeyword );
|
|
const auto fileCodeValues = reader.getIntArray( codeValuesKeyword );
|
|
|
|
std::map<int, QString> codeNamesAndValues;
|
|
for ( size_t i = 0; i < fileCodeNames.size(); i++ )
|
|
{
|
|
codeNamesAndValues[fileCodeValues[i]] = QString::fromStdString( fileCodeNames[i] ).trimmed();
|
|
}
|
|
|
|
return codeNamesAndValues;
|
|
};
|
|
|
|
return {};
|
|
};
|
|
|
|
try
|
|
{
|
|
roff::Reader reader( stream );
|
|
reader.parse();
|
|
|
|
std::vector<std::pair<std::string, roff::Token::Kind>> arrayTypes = reader.getNamedArrayTypes();
|
|
|
|
for ( auto [keyword, kind] : arrayTypes )
|
|
{
|
|
size_t keywordLength = reader.getArrayLength( keyword );
|
|
RiaLogging::debug(
|
|
std::format( "Array found: '{}'. Type: {} with size: {}.", keyword, roff::Token::kindToString( kind ), keywordLength ) );
|
|
|
|
QString keywordUpperCase = QString::fromStdString( keyword ).toUpper();
|
|
|
|
if ( keyword == "subgrids.nLayers" )
|
|
{
|
|
if ( !appendZoneIndexPropertyFromSubgrids( eclipseCaseData, reader, keywordMapping ) )
|
|
{
|
|
RiaLogging::warning( std::format( "Unable to import ROFF subgrids zonation from {}", fileName ) );
|
|
}
|
|
}
|
|
else if ( eclipseCaseData->mainGrid()->cellCount() == keywordLength )
|
|
{
|
|
QString newResultName = eclipseCaseData->results( RiaDefines::PorosityModelType::MATRIX_MODEL )
|
|
->makeResultNameUnique( QString::fromStdString( keyword ) );
|
|
// Special handling for active.data ==> ACTNUM
|
|
if ( newResultName == QString( "active" ) + QString::fromStdString( roff::Parser::postFixData() ) )
|
|
{
|
|
newResultName = "ACTNUM";
|
|
}
|
|
|
|
if ( !appendNewInputPropertyResult( eclipseCaseData, newResultName, keyword, kind, reader ) )
|
|
{
|
|
RiaLogging::error( std::format( "Unable to import result '{}' from {}", keyword, fileName ) );
|
|
return std::make_pair( false, keywordMapping );
|
|
}
|
|
|
|
const auto codeNames = codeNamesAndValuesForKeyword( keyword, reader );
|
|
bool anyValidCategoryName = false;
|
|
for ( const auto& codeName : codeNames )
|
|
{
|
|
if ( !codeName.second.isEmpty() )
|
|
{
|
|
anyValidCategoryName = true;
|
|
}
|
|
}
|
|
|
|
if ( anyValidCategoryName )
|
|
{
|
|
RimColorLegendCollection* colorLegendCollection = RimProject::current()->colorLegendCollection;
|
|
|
|
auto rimCase = eclipseCaseData->ownerCase();
|
|
|
|
// Update existing color legend in place, or create one, with values from file
|
|
if ( keywordUpperCase == RiaResultNames::facies() )
|
|
{
|
|
const auto colors = RicFaciesPropertiesImportTools::matchDefaultRockColors( codeNames );
|
|
colorLegendCollection->updateColorLegend( rimCase, newResultName, RiaDefines::faciesColorLegendName(), codeNames, colors );
|
|
}
|
|
else
|
|
{
|
|
colorLegendCollection->updateColorLegend( rimCase, newResultName, newResultName, codeNames );
|
|
}
|
|
|
|
colorLegendCollection->updateAllRequiredEditors();
|
|
}
|
|
|
|
keywordMapping[QString::fromStdString( keyword )] = newResultName;
|
|
}
|
|
else if ( keywordUpperCase == RiaResultNames::facies() )
|
|
{
|
|
// We have the definition of facies name and value, but we do not have values for cells. Create color legend and get values
|
|
// from other sources, i.e. Eclipse result files
|
|
|
|
const auto codeNames = codeNamesAndValuesForKeyword( keyword, reader );
|
|
RicFaciesPropertiesImportTools::createColorLegendMatchDefaultRockColors( codeNames );
|
|
}
|
|
}
|
|
}
|
|
catch ( std::runtime_error& err )
|
|
{
|
|
RiaLogging::error( std::format( "Roff property file import failed: {}", err.what() ) );
|
|
return std::make_pair( false, keywordMapping );
|
|
}
|
|
|
|
return std::make_pair( true, keywordMapping );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifRoffFileTools::hasGridData( const QString& filename )
|
|
{
|
|
// Check if arrayTypes contains grid info
|
|
// - Alt 1. Look for tag "cornerLines" and its data with keyword "cornerLines.data" - actual representation of grid data
|
|
// - Alt 2. Look for tag "filedata" and its filetype with keyword "filedata.filetype" - should be equal "grid" (not as robust, rather
|
|
// look for "cornerLines" which is grid representation)
|
|
|
|
std::ifstream stream( filename.toStdString(), std::ios::binary );
|
|
if ( !stream.good() )
|
|
{
|
|
RiaLogging::error( "Unable to open roff file" );
|
|
return false;
|
|
}
|
|
|
|
roff::Reader reader( stream );
|
|
reader.parse();
|
|
|
|
const std::vector<std::pair<std::string, roff::Token::Kind>> arrayTypes = reader.getNamedArrayTypes();
|
|
|
|
const std::string cornerLinesDataKeyword = "cornerLines" + roff::Parser::postFixData();
|
|
auto cornerLinesDataItr = std::find_if( arrayTypes.begin(),
|
|
arrayTypes.end(),
|
|
[&cornerLinesDataKeyword]( const auto& arrayType )
|
|
{ return arrayType.first == cornerLinesDataKeyword; } );
|
|
|
|
return cornerLinesDataItr != arrayTypes.end();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::vector<double>
|
|
RifRoffFileTools::readAndConvertToDouble( int nx, int ny, int nz, const std::string& keyword, roff::Token::Kind kind, roff::Reader& reader )
|
|
{
|
|
std::vector<double> doubleVals;
|
|
|
|
if ( kind == roff::Token::Kind::FLOAT )
|
|
{
|
|
std::vector<float> values = reader.getFloatArray( keyword );
|
|
convertToReservoirIndexOrder<float, double>( nx, ny, nz, values, doubleVals );
|
|
}
|
|
else if ( kind == roff::Token::Kind::BOOL || kind == roff::Token::Kind::BYTE )
|
|
{
|
|
std::vector<char> values = reader.getByteArray( keyword );
|
|
convertToReservoirIndexOrder<char, double>( nx, ny, nz, values, doubleVals );
|
|
}
|
|
else if ( kind == roff::Token::Kind::INT )
|
|
{
|
|
std::vector<int> values = reader.getIntArray( keyword );
|
|
convertToReservoirIndexOrder<int, double>( nx, ny, nz, values, doubleVals );
|
|
}
|
|
else
|
|
{
|
|
RiaLogging::error( std::format( "Unsupported property type '{}' for keyword '{}'.", roff::Token::kindToString( kind ), keyword ) );
|
|
}
|
|
|
|
return doubleVals;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifRoffFileTools::appendNewInputPropertyResult( RigEclipseCaseData* caseData,
|
|
const QString& resultName,
|
|
const std::string& keyword,
|
|
roff::Token::Kind kind,
|
|
roff::Reader& reader )
|
|
{
|
|
CAF_ASSERT( caseData );
|
|
|
|
int nx = static_cast<int>( caseData->mainGrid()->cellCountI() );
|
|
int ny = static_cast<int>( caseData->mainGrid()->cellCountJ() );
|
|
int nz = static_cast<int>( caseData->mainGrid()->cellCountK() );
|
|
std::vector<double> values = readAndConvertToDouble( nx, ny, nz, keyword, kind, reader );
|
|
|
|
auto mainGrid = caseData->mainGrid();
|
|
if ( values.size() != mainGrid->cellCount() ) return false;
|
|
|
|
// Set better invalid value for inactive cells: roff file has -999
|
|
auto activeCellInfo = caseData->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL );
|
|
size_t cellCount = mainGrid->cellCount();
|
|
for ( size_t i = 0; i < cellCount; i++ )
|
|
{
|
|
if ( !activeCellInfo->isActive( ReservoirCellIndex( mainGrid->reservoirCellIndex( i ) ) ) )
|
|
{
|
|
values[i] = HUGE_VAL;
|
|
}
|
|
}
|
|
|
|
RigEclipseResultAddress resAddr( RiaDefines::ResultCatType::INPUT_PROPERTY, RifRoffFileTools::mapFromType( kind ), resultName );
|
|
caseData->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->createResultEntry( resAddr, false );
|
|
|
|
auto newPropertyData = caseData->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->modifiableCellScalarResultTimesteps( resAddr );
|
|
|
|
newPropertyData->push_back( values );
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::vector<double> RifRoffFileTools::computeZoneValuesFromSubgrids( const std::vector<int>& nLayers, size_t nx, size_t ny, size_t nz )
|
|
{
|
|
if ( nLayers.empty() ) return {};
|
|
|
|
size_t layerSum = 0;
|
|
for ( int n : nLayers )
|
|
{
|
|
if ( n <= 0 ) return {};
|
|
layerSum += static_cast<size_t>( n );
|
|
}
|
|
if ( layerSum != nz ) return {};
|
|
|
|
// K→zone lookup in ROFF k-order (zone numbers are 1-based).
|
|
std::vector<int> roffKToZone( nz );
|
|
size_t kRoff = 0;
|
|
for ( size_t z = 0; z < nLayers.size(); ++z )
|
|
{
|
|
const int zoneValue = static_cast<int>( z ) + 1;
|
|
for ( int i = 0; i < nLayers[z]; ++i )
|
|
{
|
|
roffKToZone[kRoff++] = zoneValue;
|
|
}
|
|
}
|
|
|
|
// Per-cell values in reservoir index order; apply the same K-flip as convertToReservoirIndexOrder.
|
|
std::vector<double> values( nx * ny * nz );
|
|
size_t outIdx = 0;
|
|
for ( size_t k = 0; k < nz; ++k )
|
|
{
|
|
const int zone = roffKToZone[nz - k - 1];
|
|
for ( size_t j = 0; j < ny; ++j )
|
|
{
|
|
for ( size_t i = 0; i < nx; ++i )
|
|
{
|
|
values[outIdx++] = static_cast<double>( zone );
|
|
}
|
|
}
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifRoffFileTools::appendZoneIndexPropertyFromSubgrids( RigEclipseCaseData* caseData,
|
|
roff::Reader& reader,
|
|
std::map<QString, QString>& keywordMapping )
|
|
{
|
|
CAF_ASSERT( caseData );
|
|
|
|
auto* mainGrid = caseData->mainGrid();
|
|
if ( !mainGrid ) return false;
|
|
|
|
const size_t nx = mainGrid->cellCountI();
|
|
const size_t ny = mainGrid->cellCountJ();
|
|
const size_t nz = mainGrid->cellCountK();
|
|
|
|
const std::string nLayersKeyword = "subgrids.nLayers";
|
|
if ( reader.getArrayLength( nLayersKeyword ) == 0 ) return false;
|
|
|
|
const std::vector<int> nLayers = reader.getIntArray( nLayersKeyword );
|
|
|
|
std::vector<double> values = computeZoneValuesFromSubgrids( nLayers, nx, ny, nz );
|
|
if ( values.empty() )
|
|
{
|
|
RiaLogging::warning(
|
|
std::format( "ROFF subgrids.nLayers could not be expanded to grid K dimension ({}). Skipping zonation import.", nz ) );
|
|
return false;
|
|
}
|
|
|
|
auto* activeCellInfo = caseData->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL );
|
|
const size_t cellCount = mainGrid->cellCount();
|
|
for ( size_t cellIdx = 0; cellIdx < cellCount; ++cellIdx )
|
|
{
|
|
if ( !activeCellInfo->isActive( ReservoirCellIndex( mainGrid->reservoirCellIndex( cellIdx ) ) ) )
|
|
{
|
|
values[cellIdx] = HUGE_VAL;
|
|
}
|
|
}
|
|
|
|
const QString resultName = caseData->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->makeResultNameUnique( QString( "SUBGRIDS" ) );
|
|
|
|
RigEclipseResultAddress resAddr( RiaDefines::ResultCatType::INPUT_PROPERTY, RiaDefines::ResultDataType::INTEGER, resultName );
|
|
caseData->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->createResultEntry( resAddr, false );
|
|
|
|
auto newPropertyData = caseData->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->modifiableCellScalarResultTimesteps( resAddr );
|
|
newPropertyData->push_back( values );
|
|
|
|
keywordMapping[QString::fromStdString( nLayersKeyword )] = resultName;
|
|
|
|
// Attach zone names as category code-names when the file provides a companion names array.
|
|
const std::string namesKeyword = "subgrids.names";
|
|
if ( reader.getArrayLength( namesKeyword ) == nLayers.size() )
|
|
{
|
|
// Skip legend creation when the application/project is not initialized (e.g. early-init or non-UI contexts).
|
|
auto project = RiaApplication::instance() ? RimProject::current() : nullptr;
|
|
auto colorLegendCollection = project ? project->colorLegendCollection() : nullptr;
|
|
if ( colorLegendCollection )
|
|
{
|
|
const auto zoneNames = reader.getStringArray( namesKeyword );
|
|
std::map<int, QString> codeNames;
|
|
for ( size_t z = 0; z < zoneNames.size(); ++z )
|
|
{
|
|
codeNames[static_cast<int>( z ) + 1] = QString::fromStdString( zoneNames[z] ).trimmed();
|
|
}
|
|
|
|
auto rimCase = caseData->ownerCase();
|
|
|
|
colorLegendCollection->updateColorLegend( rimCase, resultName, resultName, codeNames );
|
|
colorLegendCollection->updateAllRequiredEditors();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
RiaDefines::ResultDataType RifRoffFileTools::mapFromType( roff::Token::Kind kind )
|
|
{
|
|
switch ( kind )
|
|
{
|
|
case roff::Token::Kind::BOOL:
|
|
return RiaDefines::ResultDataType::INTEGER;
|
|
case roff::Token::Kind::BYTE:
|
|
return RiaDefines::ResultDataType::INTEGER;
|
|
case roff::Token::Kind::INT:
|
|
return RiaDefines::ResultDataType::INTEGER;
|
|
}
|
|
|
|
return RiaDefines::ResultDataType::FLOAT;
|
|
}
|