mirror of
https://github.com/OPM/ResInsight.git
synced 2026-08-27 05:37:21 -05:00
1035 lines
46 KiB
C++
1035 lines
46 KiB
C++
/////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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 <http://www.gnu.org/licenses/gpl.html>
|
|
// for more details.
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "RimKeywordFactory.h"
|
|
|
|
#include "Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.h"
|
|
|
|
#include "RiaResultNames.h"
|
|
|
|
#include "RifEclipseInputFileTools.h"
|
|
#include "RifOpmDeckTools.h"
|
|
|
|
#include "CompletionsMsw/RigMswTableData.h"
|
|
#include "RigEclipseResultTools.h"
|
|
#include "RigFault.h"
|
|
#include "RigMainGrid.h"
|
|
#include "RigSimulationInputTool.h"
|
|
|
|
#include "RimEclipseCase.h"
|
|
#include "RimWellEventWellSpec.h"
|
|
#include "RimWellPath.h"
|
|
#include "RimWellPathCompletionSettings.h"
|
|
|
|
#include "cvfStructGrid.h"
|
|
|
|
#include "opm/input/eclipse/Deck/Deck.hpp"
|
|
#include "opm/input/eclipse/Deck/DeckItem.hpp"
|
|
#include "opm/input/eclipse/Deck/DeckKeyword.hpp"
|
|
#include "opm/input/eclipse/Deck/DeckOutput.hpp"
|
|
#include "opm/input/eclipse/Deck/DeckRecord.hpp"
|
|
#include "opm/input/eclipse/Parser/Parser.hpp"
|
|
#include "opm/input/eclipse/Parser/ParserKeyword.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"
|
|
#include "opm/input/eclipse/Parser/ParserKeywords/F.hpp"
|
|
#include "opm/input/eclipse/Parser/ParserKeywords/N.hpp"
|
|
#include "opm/input/eclipse/Parser/ParserKeywords/O.hpp"
|
|
#include "opm/input/eclipse/Parser/ParserKeywords/W.hpp"
|
|
|
|
#include <QLocale>
|
|
|
|
#include <algorithm>
|
|
#include <sstream>
|
|
|
|
//==================================================================================================
|
|
///
|
|
///
|
|
//==================================================================================================
|
|
namespace RimKeywordFactory
|
|
{
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
Opm::DeckKeyword
|
|
welspecsKeyword( const std::string wellGrpName, RimEclipseCase* eCase, RimWellPath* wellPath, const RimWellSpecData* wellSpecData )
|
|
{
|
|
if ( eCase == nullptr || wellPath == nullptr || wellPath->completionSettings() == nullptr || eCase->eclipseCaseData() == nullptr )
|
|
{
|
|
return Opm::DeckKeyword();
|
|
}
|
|
|
|
auto ijPos = RicWellPathExportCompletionDataFeatureImpl::wellPathUpperGridIntersectionIJ( eCase, wellPath );
|
|
auto compSettings = wellPath->completionSettings();
|
|
|
|
auto wellName = compSettings->wellNameForExport().toStdString();
|
|
|
|
using W = Opm::ParserKeywords::WELSPECS;
|
|
|
|
std::vector<Opm::DeckItem> items;
|
|
|
|
items.push_back( RifOpmDeckTools::item( W::WELL::itemName, wellName ) );
|
|
const std::string groupName = wellSpecData ? ( wellSpecData->groupName.isEmpty() ? "1*" : wellSpecData->groupName.toStdString() )
|
|
: wellGrpName;
|
|
const auto referenceDepth = wellSpecData ? wellSpecData->referenceDepth : compSettings->referenceDepth();
|
|
const std::string phase = wellSpecData
|
|
? caf::AppEnum<RimWellPathCompletionSettings::WellType>( wellSpecData->wellType ).text().toStdString()
|
|
: compSettings->wellTypeNameForExport().toStdString();
|
|
|
|
items.push_back( RifOpmDeckTools::item( W::GROUP::itemName, groupName ) );
|
|
items.push_back( RifOpmDeckTools::item( W::HEAD_I::itemName, ijPos.second.x() + 1 ) );
|
|
items.push_back( RifOpmDeckTools::item( W::HEAD_J::itemName, ijPos.second.y() + 1 ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::REF_DEPTH::itemName, referenceDepth ) );
|
|
items.push_back( RifOpmDeckTools::item( W::PHASE::itemName, phase ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::D_RADIUS::itemName, compSettings->drainageRadius() ) );
|
|
items.push_back( RifOpmDeckTools::item( W::INFLOW_EQ::itemName, compSettings->gasInflowEquationForExport().toStdString() ) );
|
|
items.push_back( RifOpmDeckTools::item( W::AUTO_SHUTIN::itemName, compSettings->automaticWellShutInForExport().toStdString() ) );
|
|
const std::string crossFlow = wellSpecData ? ( wellSpecData->allowCrossFlow ? "YES" : "NO" )
|
|
: compSettings->allowWellCrossFlowForExport().toStdString();
|
|
items.push_back( RifOpmDeckTools::item( W::CROSSFLOW::itemName, crossFlow ) );
|
|
items.push_back( RifOpmDeckTools::item( W::P_TABLE::itemName, compSettings->wellBoreFluidPVT() ) );
|
|
items.push_back( RifOpmDeckTools::item( W::DENSITY_CALC::itemName, compSettings->hydrostaticDensityForExport().toStdString() ) );
|
|
items.push_back( RifOpmDeckTools::item( W::FIP_REGION::itemName, compSettings->fluidInPlaceRegion() ) );
|
|
|
|
Opm::DeckKeyword kw( ( Opm::ParserKeywords::WELSPECS() ) );
|
|
kw.addRecord( Opm::DeckRecord{ std::move( items ) } );
|
|
|
|
return kw;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
Opm::DeckKeyword compordKeyword( const std::string& wellName )
|
|
{
|
|
using C = Opm::ParserKeywords::COMPORD;
|
|
|
|
std::vector<Opm::DeckItem> items;
|
|
items.push_back( RifOpmDeckTools::item( C::WELL::itemName, wellName ) );
|
|
items.push_back( RifOpmDeckTools::item( C::ORDER_TYPE::itemName, "INPUT" ) );
|
|
|
|
Opm::DeckKeyword kw( ( C() ) );
|
|
kw.addRecord( Opm::DeckRecord{ std::move( items ) } );
|
|
return kw;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
Opm::DeckKeyword compdatKeyword( const std::vector<RigCompletionData>& compdata, const std::string wellName )
|
|
{
|
|
if ( compdata.empty() )
|
|
{
|
|
return Opm::DeckKeyword();
|
|
}
|
|
|
|
using C = Opm::ParserKeywords::COMPDAT;
|
|
|
|
Opm::DeckKeyword kw( ( Opm::ParserKeywords::COMPDAT() ) );
|
|
|
|
for ( auto& cd : compdata )
|
|
{
|
|
std::vector<Opm::DeckItem> items;
|
|
|
|
items.push_back( RifOpmDeckTools::item( C::WELL::itemName, wellName ) );
|
|
items.push_back( RifOpmDeckTools::item( C::I::itemName, cd.completionDataGridCell().localCellIndexI() + 1 ) );
|
|
items.push_back( RifOpmDeckTools::item( C::J::itemName, cd.completionDataGridCell().localCellIndexJ() + 1 ) );
|
|
items.push_back( RifOpmDeckTools::item( C::K1::itemName, cd.completionDataGridCell().localCellIndexK() + 1 ) );
|
|
items.push_back( RifOpmDeckTools::item( C::K2::itemName, cd.completionDataGridCell().localCellIndexK() + 1 ) );
|
|
items.push_back( RifOpmDeckTools::item( C::STATE::itemName, "OPEN" ) );
|
|
|
|
auto satTable = cd.saturation();
|
|
items.push_back( satTable != RigCompletionData::defaultValue() ? RifOpmDeckTools::item( C::SAT_TABLE::itemName, satTable )
|
|
: RifOpmDeckTools::defaultItem( C::SAT_TABLE::itemName ) );
|
|
|
|
auto transmissibility = cd.transmissibility();
|
|
items.push_back( transmissibility != RigCompletionData::defaultValue()
|
|
? RifOpmDeckTools::item( C::CONNECTION_TRANSMISSIBILITY_FACTOR::itemName, transmissibility )
|
|
: RifOpmDeckTools::defaultItem( C::CONNECTION_TRANSMISSIBILITY_FACTOR::itemName ) );
|
|
|
|
auto diameter = cd.diameter();
|
|
items.push_back( diameter != RigCompletionData::defaultValue() ? RifOpmDeckTools::item( C::DIAMETER::itemName, diameter )
|
|
: RifOpmDeckTools::defaultItem( C::DIAMETER::itemName ) );
|
|
auto kh = cd.kh();
|
|
items.push_back( kh != RigCompletionData::defaultValue() ? RifOpmDeckTools::item( C::Kh::itemName, kh )
|
|
: RifOpmDeckTools::defaultItem( C::Kh::itemName ) );
|
|
auto skinFactor = cd.skinFactor();
|
|
items.push_back( skinFactor != RigCompletionData::defaultValue() ? RifOpmDeckTools::item( C::SKIN::itemName, skinFactor )
|
|
: RifOpmDeckTools::defaultItem( C::SKIN::itemName ) );
|
|
auto dFactor = cd.dFactor();
|
|
items.push_back( dFactor != RigCompletionData::defaultValue() ? RifOpmDeckTools::item( C::D_FACTOR::itemName, dFactor )
|
|
: RifOpmDeckTools::defaultItem( C::D_FACTOR::itemName ) );
|
|
|
|
items.push_back( RifOpmDeckTools::item( C::DIR::itemName, cd.directionStringXYZ().toStdString() ) );
|
|
|
|
kw.addRecord( Opm::DeckRecord{ std::move( items ) } );
|
|
}
|
|
|
|
return kw;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
Opm::DeckKeyword wpimultKeyword( const std::vector<RigCompletionData>& compdata, const std::string wellName )
|
|
{
|
|
if ( compdata.empty() )
|
|
{
|
|
return Opm::DeckKeyword();
|
|
}
|
|
|
|
using W = Opm::ParserKeywords::WPIMULT;
|
|
|
|
Opm::DeckKeyword kw( ( Opm::ParserKeywords::WPIMULT() ) );
|
|
|
|
for ( auto& cd : compdata )
|
|
{
|
|
if ( cd.wpimult() == RigCompletionData::defaultValue() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
std::vector<Opm::DeckItem> items;
|
|
|
|
items.push_back( RifOpmDeckTools::item( W::WELL::itemName, wellName ) );
|
|
items.push_back( RifOpmDeckTools::item( W::WELLPI::itemName, cd.wpimult() ) );
|
|
items.push_back( RifOpmDeckTools::item( W::I::itemName, cd.completionDataGridCell().localCellIndexI() + 1 ) );
|
|
items.push_back( RifOpmDeckTools::item( W::J::itemName, cd.completionDataGridCell().localCellIndexJ() + 1 ) );
|
|
items.push_back( RifOpmDeckTools::item( W::K::itemName, cd.completionDataGridCell().localCellIndexK() + 1 ) );
|
|
|
|
kw.addRecord( Opm::DeckRecord{ std::move( items ) } );
|
|
}
|
|
|
|
return kw;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
Opm::DeckKeyword complumpKeyword( const std::vector<RigCompletionData>& compdata, const std::string wellName )
|
|
{
|
|
if ( compdata.empty() )
|
|
{
|
|
return Opm::DeckKeyword();
|
|
}
|
|
using C = Opm::ParserKeywords::COMPLUMP;
|
|
Opm::DeckKeyword kw( ( Opm::ParserKeywords::COMPLUMP() ) );
|
|
for ( auto& cd : compdata )
|
|
{
|
|
if ( !cd.completionNumber().has_value() )
|
|
{
|
|
continue;
|
|
}
|
|
std::vector<Opm::DeckItem> items;
|
|
items.push_back( RifOpmDeckTools::item( C::WELL::itemName, wellName ) );
|
|
items.push_back( RifOpmDeckTools::item( C::I::itemName, cd.completionDataGridCell().localCellIndexI() + 1 ) );
|
|
items.push_back( RifOpmDeckTools::item( C::J::itemName, cd.completionDataGridCell().localCellIndexJ() + 1 ) );
|
|
items.push_back( RifOpmDeckTools::item( C::K1::itemName, cd.completionDataGridCell().localCellIndexK() + 1 ) );
|
|
items.push_back( RifOpmDeckTools::item( C::K2::itemName, cd.completionDataGridCell().localCellIndexK() + 1 ) );
|
|
items.push_back( RifOpmDeckTools::item( C::N::itemName, cd.completionNumber().value() ) );
|
|
kw.addRecord( Opm::DeckRecord{ std::move( items ) } );
|
|
}
|
|
return kw;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
Opm::DeckKeyword welsegsKeyword( const RigMswTableData& mswData, int& maxSegments, int& maxBranches )
|
|
{
|
|
maxSegments = 0;
|
|
maxBranches = 0;
|
|
|
|
if ( !mswData.hasWelsegsData() ) return Opm::DeckKeyword();
|
|
|
|
using W = Opm::ParserKeywords::WELSEGS;
|
|
|
|
Opm::DeckKeyword newKw( ( W() ) );
|
|
|
|
// welsegs header row
|
|
auto& header = mswData.welsegsHeader();
|
|
std::vector<Opm::DeckItem> headerItems;
|
|
headerItems.push_back( RifOpmDeckTools::item( W::WELL::itemName, header.well ) );
|
|
headerItems.push_back( RifOpmDeckTools::item( W::TOP_DEPTH::itemName, header.topDepth ) );
|
|
headerItems.push_back( RifOpmDeckTools::item( W::TOP_LENGTH::itemName, header.topLength ) );
|
|
headerItems.push_back( RifOpmDeckTools::optionalItem( W::WELLBORE_VOLUME::itemName, header.wellboreVolume ) );
|
|
headerItems.push_back( RifOpmDeckTools::item( W::INFO_TYPE::itemName, header.infoType ) );
|
|
headerItems.push_back( RifOpmDeckTools::optionalItem( W::PRESSURE_COMPONENTS::itemName, header.pressureComponents ) );
|
|
headerItems.push_back( RifOpmDeckTools::optionalItem( W::FLOW_MODEL::itemName, header.flowModel ) );
|
|
|
|
newKw.addRecord( Opm::DeckRecord{ std::move( headerItems ) } );
|
|
|
|
// welsegs data rows
|
|
for ( auto& wsRow : mswData.welsegsData() )
|
|
{
|
|
maxSegments = std::max( maxSegments, wsRow.segment1 );
|
|
maxSegments = std::max( maxSegments, wsRow.segment2 );
|
|
maxBranches = std::max( maxBranches, wsRow.branch );
|
|
|
|
std::vector<Opm::DeckItem> items;
|
|
items.push_back( RifOpmDeckTools::item( Opm::ParserKeywords::WELSEGS::SEGMENT1::itemName, wsRow.segment1 ) );
|
|
items.push_back( RifOpmDeckTools::item( Opm::ParserKeywords::WELSEGS::SEGMENT2::itemName, wsRow.segment2 ) );
|
|
items.push_back( RifOpmDeckTools::item( Opm::ParserKeywords::WELSEGS::BRANCH::itemName, wsRow.branch ) );
|
|
items.push_back( RifOpmDeckTools::item( Opm::ParserKeywords::WELSEGS::JOIN_SEGMENT::itemName, wsRow.joinSegment ) );
|
|
items.push_back( RifOpmDeckTools::item( Opm::ParserKeywords::WELSEGS::LENGTH::itemName, wsRow.length ) );
|
|
items.push_back( RifOpmDeckTools::item( Opm::ParserKeywords::WELSEGS::DEPTH::itemName, wsRow.depth ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( Opm::ParserKeywords::WELSEGS::DIAMETER::itemName, wsRow.diameter ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( Opm::ParserKeywords::WELSEGS::ROUGHNESS::itemName, wsRow.roughness ) );
|
|
|
|
newKw.addRecord( Opm::DeckRecord{ std::move( items ) } );
|
|
}
|
|
|
|
return newKw;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
Opm::DeckKeyword compsegsKeyword( const RigMswTableData& mswData )
|
|
{
|
|
if ( !mswData.hasCompsegsData() )
|
|
{
|
|
return Opm::DeckKeyword();
|
|
}
|
|
|
|
Opm::DeckKeyword newKw( ( Opm::ParserKeywords::COMPSEGS() ) );
|
|
|
|
// header row
|
|
std::vector<Opm::DeckItem> headerItems;
|
|
headerItems.push_back( RifOpmDeckTools::item( Opm::ParserKeywords::COMPSEGS::WELL::itemName, mswData.wellName() ) );
|
|
newKw.addRecord( Opm::DeckRecord{ std::move( headerItems ) } );
|
|
|
|
// data rows
|
|
for ( auto& csRow : mswData.compsegsData() )
|
|
{
|
|
std::vector<Opm::DeckItem> items;
|
|
items.push_back( RifOpmDeckTools::item( Opm::ParserKeywords::COMPSEGS::I::itemName, csRow.i ) );
|
|
items.push_back( RifOpmDeckTools::item( Opm::ParserKeywords::COMPSEGS::J::itemName, csRow.j ) );
|
|
items.push_back( RifOpmDeckTools::item( Opm::ParserKeywords::COMPSEGS::K::itemName, csRow.k ) );
|
|
items.push_back( RifOpmDeckTools::item( Opm::ParserKeywords::COMPSEGS::BRANCH::itemName, csRow.branch ) );
|
|
items.push_back( RifOpmDeckTools::item( Opm::ParserKeywords::COMPSEGS::DISTANCE_START::itemName, csRow.distanceStart ) );
|
|
items.push_back( RifOpmDeckTools::item( Opm::ParserKeywords::COMPSEGS::DISTANCE_END::itemName, csRow.distanceEnd ) );
|
|
|
|
newKw.addRecord( Opm::DeckRecord{ std::move( items ) } );
|
|
}
|
|
|
|
return newKw;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
Opm::DeckKeyword wsegvalvKeyword( const RigMswTableData& mswData )
|
|
{
|
|
if ( !mswData.hasWsegvalvData() )
|
|
{
|
|
return Opm::DeckKeyword();
|
|
}
|
|
|
|
using W = Opm::ParserKeywords::WSEGVALV;
|
|
|
|
Opm::DeckKeyword newKw( ( W() ) );
|
|
|
|
for ( auto& wvRow : mswData.wsegvalvData() )
|
|
{
|
|
std::vector<Opm::DeckItem> items;
|
|
|
|
items.push_back( RifOpmDeckTools::item( W::WELL::itemName, wvRow.well ) );
|
|
items.push_back( RifOpmDeckTools::item( W::SEGMENT_NUMBER::itemName, wvRow.segmentNumber ) );
|
|
items.push_back( RifOpmDeckTools::item( W::CV::itemName, wvRow.cv ) );
|
|
items.push_back( RifOpmDeckTools::item( W::AREA::itemName, wvRow.area ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::EXTRA_LENGTH::itemName, wvRow.extraLength ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::PIPE_D::itemName, wvRow.pipeD ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::ROUGHNESS::itemName, wvRow.roughness ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::PIPE_A::itemName, wvRow.pipeA ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::STATUS::itemName, wvRow.status ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::MAX_A::itemName, wvRow.maxA ) );
|
|
|
|
newKw.addRecord( Opm::DeckRecord{ std::move( items ) } );
|
|
}
|
|
|
|
return newKw;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
Opm::DeckKeyword wsegaicdKeyword( const RigMswTableData& mswData )
|
|
{
|
|
if ( !mswData.hasWsegaicdData() )
|
|
{
|
|
return Opm::DeckKeyword();
|
|
}
|
|
|
|
using W = Opm::ParserKeywords::WSEGAICD;
|
|
|
|
Opm::DeckKeyword newKw( ( W() ) );
|
|
|
|
for ( auto& waRow : mswData.wsegaicdData() )
|
|
{
|
|
std::vector<Opm::DeckItem> items;
|
|
|
|
items.push_back( RifOpmDeckTools::item( W::WELL::itemName, waRow.well ) );
|
|
items.push_back( RifOpmDeckTools::item( W::SEGMENT1::itemName, waRow.segment1 ) );
|
|
items.push_back( RifOpmDeckTools::item( W::SEGMENT2::itemName, waRow.segment2 ) );
|
|
items.push_back( RifOpmDeckTools::item( W::STRENGTH::itemName, waRow.strength ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::LENGTH::itemName, waRow.length ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::DENSITY_CALI::itemName, waRow.densityCali ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::VISCOSITY_CALI::itemName, waRow.viscosityCali ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::CRITICAL_VALUE::itemName, waRow.criticalValue ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::WIDTH_TRANS::itemName, waRow.widthTrans ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::MAX_VISC_RATIO::itemName, waRow.maxViscRatio ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::METHOD_SCALING_FACTOR::itemName, waRow.methodScalingFactor ) );
|
|
items.push_back( RifOpmDeckTools::item( W::MAX_ABS_RATE::itemName, waRow.maxAbsRate ) );
|
|
items.push_back( RifOpmDeckTools::item( W::FLOW_RATE_EXPONENT::itemName, waRow.flowRateExponent ) );
|
|
items.push_back( RifOpmDeckTools::item( W::VISC_EXPONENT::itemName, waRow.viscExponent ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::STATUS::itemName, waRow.status ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::OIL_FLOW_FRACTION::itemName, waRow.oilFlowFraction ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::WATER_FLOW_FRACTION::itemName, waRow.waterFlowFraction ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::GAS_FLOW_FRACTION::itemName, waRow.gasFlowFraction ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::OIL_VISC_FRACTION::itemName, waRow.oilViscFraction ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::WATER_VISC_FRACTION::itemName, waRow.waterViscFraction ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::GAS_VISC_FRACTION::itemName, waRow.gasViscFraction ) );
|
|
|
|
newKw.addRecord( Opm::DeckRecord{ std::move( items ) } );
|
|
}
|
|
|
|
return newKw;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
Opm::DeckKeyword wsegsicdKeyword( const RigMswTableData& mswData )
|
|
{
|
|
if ( !mswData.hasWsegsicdData() )
|
|
{
|
|
return Opm::DeckKeyword();
|
|
}
|
|
|
|
using W = Opm::ParserKeywords::WSEGSICD;
|
|
|
|
Opm::DeckKeyword newKw( ( W() ) );
|
|
|
|
for ( auto& waRow : mswData.wsegsicdData() )
|
|
{
|
|
std::vector<Opm::DeckItem> items;
|
|
|
|
items.push_back( RifOpmDeckTools::item( W::WELL::itemName, waRow.well ) );
|
|
items.push_back( RifOpmDeckTools::item( W::SEGMENT1::itemName, waRow.segment1 ) );
|
|
items.push_back( RifOpmDeckTools::item( W::SEGMENT2::itemName, waRow.segment2 ) );
|
|
items.push_back( RifOpmDeckTools::item( W::STRENGTH::itemName, waRow.strength ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::LENGTH::itemName, waRow.length ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::DENSITY_CALI::itemName, waRow.densityCali ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::VISCOSITY_CALI::itemName, waRow.viscosityCali ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::CRITICAL_VALUE::itemName, waRow.criticalValue ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::WIDTH_TRANS::itemName, waRow.widthTrans ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::MAX_VISC_RATIO::itemName, waRow.maxViscRatio ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::METHOD_SCALING_FACTOR::itemName, waRow.methodScalingFactor ) );
|
|
items.push_back( RifOpmDeckTools::item( W::MAX_ABS_RATE::itemName, waRow.maxAbsRate ) );
|
|
items.push_back( RifOpmDeckTools::optionalItem( W::STATUS::itemName, waRow.status ) );
|
|
|
|
newKw.addRecord( Opm::DeckRecord{ std::move( items ) } );
|
|
}
|
|
|
|
return newKw;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
Opm::DeckKeyword faultsKeyword( const RigMainGrid* mainGrid, const cvf::Vec3st& min, const cvf::Vec3st& max, const RigRefinement& refinement )
|
|
{
|
|
if ( mainGrid == nullptr )
|
|
{
|
|
return Opm::DeckKeyword();
|
|
}
|
|
|
|
// Helper lambda to convert FaceType to Eclipse face string
|
|
auto faceTypeToString = []( cvf::StructGridInterface::FaceType faceType ) -> std::string
|
|
{
|
|
switch ( faceType )
|
|
{
|
|
case cvf::StructGridInterface::POS_I:
|
|
return "I";
|
|
case cvf::StructGridInterface::NEG_I:
|
|
return "I-";
|
|
case cvf::StructGridInterface::POS_J:
|
|
return "J";
|
|
case cvf::StructGridInterface::NEG_J:
|
|
return "J-";
|
|
case cvf::StructGridInterface::POS_K:
|
|
return "K";
|
|
case cvf::StructGridInterface::NEG_K:
|
|
return "K-";
|
|
default:
|
|
return "";
|
|
}
|
|
};
|
|
|
|
using F = Opm::ParserKeywords::FAULTS;
|
|
|
|
Opm::DeckKeyword kw{ Opm::ParserKeywords::FAULTS() };
|
|
|
|
// Process all faults in the grid
|
|
const cvf::Collection<RigFault>& faults = mainGrid->faults();
|
|
for ( const auto& fault : faults )
|
|
{
|
|
// Skip undefined faults
|
|
if ( fault->name() == RiaResultNames::undefinedGridFaultName() || fault->name() == RiaResultNames::undefinedGridFaultWithInactiveName() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Extract fault cell and face data for this fault
|
|
std::vector<RigFault::CellAndFace> faultCellAndFaces =
|
|
RifEclipseInputFileTools::extractFaults( mainGrid, fault->faultFaces(), min, max, refinement );
|
|
|
|
// Group consecutive cells in K direction and create fault records
|
|
size_t lastI = std::numeric_limits<size_t>::max();
|
|
size_t lastJ = std::numeric_limits<size_t>::max();
|
|
size_t lastK = std::numeric_limits<size_t>::max();
|
|
size_t startK = std::numeric_limits<size_t>::max();
|
|
cvf::StructGridInterface::FaceType lastFaceType = cvf::StructGridInterface::FaceType::NO_FACE;
|
|
|
|
for ( const RigFault::CellAndFace& faultCellAndFace : faultCellAndFaces )
|
|
{
|
|
size_t i, j, k;
|
|
cvf::StructGridInterface::FaceType faceType;
|
|
std::tie( i, j, k, faceType ) = faultCellAndFace;
|
|
|
|
// Check if we need to write out the previous range
|
|
if ( i != lastI || j != lastJ || lastFaceType != faceType || k != lastK + 1 )
|
|
{
|
|
// Write out previous fault line if valid
|
|
if ( lastFaceType != cvf::StructGridInterface::FaceType::NO_FACE )
|
|
{
|
|
// Convert from 0-based to 1-based Eclipse indexing
|
|
int i1 = static_cast<int>( lastI ) + 1;
|
|
int j1 = static_cast<int>( lastJ ) + 1;
|
|
int k1 = static_cast<int>( startK ) + 1;
|
|
int k2 = static_cast<int>( lastK ) + 1;
|
|
|
|
std::string faceStr = faceTypeToString( lastFaceType );
|
|
|
|
// Create a record for this fault line
|
|
std::vector<Opm::DeckItem> items;
|
|
items.push_back( RifOpmDeckTools::item( F::NAME::itemName, fault->name().toStdString() ) );
|
|
items.push_back( RifOpmDeckTools::item( F::IX1::itemName, i1 ) );
|
|
items.push_back( RifOpmDeckTools::item( F::IX2::itemName, i1 ) );
|
|
items.push_back( RifOpmDeckTools::item( F::IY1::itemName, j1 ) );
|
|
items.push_back( RifOpmDeckTools::item( F::IY2::itemName, j1 ) );
|
|
items.push_back( RifOpmDeckTools::item( F::IZ1::itemName, k1 ) );
|
|
items.push_back( RifOpmDeckTools::item( F::IZ2::itemName, k2 ) );
|
|
items.push_back( RifOpmDeckTools::item( F::FACE::itemName, faceStr ) );
|
|
|
|
kw.addRecord( Opm::DeckRecord{ std::move( items ) } );
|
|
}
|
|
|
|
// Start new range
|
|
lastI = i;
|
|
lastJ = j;
|
|
lastK = k;
|
|
lastFaceType = faceType;
|
|
startK = k;
|
|
}
|
|
else
|
|
{
|
|
// Continue current range
|
|
lastK = k;
|
|
}
|
|
}
|
|
|
|
// Write out final fault line for this fault if valid
|
|
if ( lastFaceType != cvf::StructGridInterface::FaceType::NO_FACE )
|
|
{
|
|
// Convert from 0-based to 1-based Eclipse indexing
|
|
int i1 = static_cast<int>( lastI ) + 1;
|
|
int j1 = static_cast<int>( lastJ ) + 1;
|
|
int k1 = static_cast<int>( startK ) + 1;
|
|
int k2 = static_cast<int>( lastK ) + 1;
|
|
|
|
std::string faceStr = faceTypeToString( lastFaceType );
|
|
|
|
// Create a record for this fault line
|
|
std::vector<Opm::DeckItem> items;
|
|
items.push_back( RifOpmDeckTools::item( F::NAME::itemName, fault->name().toStdString() ) );
|
|
items.push_back( RifOpmDeckTools::item( F::IX1::itemName, i1 ) );
|
|
items.push_back( RifOpmDeckTools::item( F::IX2::itemName, i1 ) );
|
|
items.push_back( RifOpmDeckTools::item( F::IY1::itemName, j1 ) );
|
|
items.push_back( RifOpmDeckTools::item( F::IY2::itemName, j1 ) );
|
|
items.push_back( RifOpmDeckTools::item( F::IZ1::itemName, k1 ) );
|
|
items.push_back( RifOpmDeckTools::item( F::IZ2::itemName, k2 ) );
|
|
items.push_back( RifOpmDeckTools::item( F::FACE::itemName, faceStr ) );
|
|
|
|
kw.addRecord( Opm::DeckRecord{ std::move( items ) } );
|
|
}
|
|
}
|
|
|
|
return kw;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
Opm::DeckKeyword bcconKeyword( const std::vector<RigEclipseResultTools::BorderCellFace>& borderCellFaces )
|
|
{
|
|
if ( borderCellFaces.empty() )
|
|
{
|
|
return Opm::DeckKeyword();
|
|
}
|
|
|
|
// Helper lambda to convert FaceType to Eclipse BCCON DIRECTION string.
|
|
// faceType is the direction from the border cell toward its interior neighbour,
|
|
// so the exterior face of the border cell is on the opposite side. The BCCON
|
|
// DIRECTION names the exterior face using the OPM-Flow convention: an unsuffixed
|
|
// axis name is the positive direction, an axis name followed by "-" is the
|
|
// negative direction.
|
|
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 "";
|
|
}
|
|
};
|
|
|
|
using B = Opm::ParserKeywords::BCCON;
|
|
|
|
Opm::DeckKeyword kw{ Opm::ParserKeywords::BCCON() };
|
|
|
|
int bcconIndex = 1;
|
|
for ( const auto& borderFace : borderCellFaces )
|
|
{
|
|
// Convert from 0-based to 1-based Eclipse indexing
|
|
int i1 = static_cast<int>( borderFace.ijk[0] ) + 1;
|
|
int j1 = static_cast<int>( borderFace.ijk[1] ) + 1;
|
|
int k1 = static_cast<int>( borderFace.ijk[2] ) + 1;
|
|
|
|
std::string faceStr = faceTypeToString( borderFace.faceType );
|
|
|
|
// Create items for the record
|
|
std::vector<Opm::DeckItem> items;
|
|
|
|
items.push_back( RifOpmDeckTools::item( B::INDEX::itemName, bcconIndex ) );
|
|
items.push_back( RifOpmDeckTools::item( B::I1::itemName, i1 ) );
|
|
items.push_back( RifOpmDeckTools::item( B::I2::itemName, i1 ) );
|
|
items.push_back( RifOpmDeckTools::item( B::J1::itemName, j1 ) );
|
|
items.push_back( RifOpmDeckTools::item( B::J2::itemName, j1 ) );
|
|
items.push_back( RifOpmDeckTools::item( B::K1::itemName, k1 ) );
|
|
items.push_back( RifOpmDeckTools::item( B::K2::itemName, k1 ) );
|
|
items.push_back( RifOpmDeckTools::item( B::DIRECTION::itemName, faceStr ) );
|
|
|
|
kw.addRecord( Opm::DeckRecord{ std::move( items ) } );
|
|
bcconIndex++;
|
|
}
|
|
|
|
return kw;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
Opm::DeckKeyword bcpropKeyword( const std::vector<RigEclipseResultTools::BorderCellFace>& boundaryConditions,
|
|
const std::vector<Opm::DeckRecord>& boundaryConditionProperties )
|
|
{
|
|
if ( boundaryConditions.empty() )
|
|
{
|
|
return Opm::DeckKeyword();
|
|
}
|
|
|
|
using B = Opm::ParserKeywords::BCPROP;
|
|
|
|
Opm::DeckKeyword kw{ Opm::ParserKeywords::BCPROP() };
|
|
|
|
int bcIndex = 1;
|
|
// Add one entry per boundary condition
|
|
for ( const auto& bc : boundaryConditions )
|
|
{
|
|
// Find the corresponding property record
|
|
// The properties vector should be indexed by boundaryCondition - 1
|
|
int propIndex = bc.boundaryCondition - 1;
|
|
if ( propIndex >= 0 && propIndex < static_cast<int>( boundaryConditionProperties.size() ) )
|
|
{
|
|
const auto& propRecord = boundaryConditionProperties[propIndex];
|
|
|
|
// Create a new record with the boundary condition INDEX
|
|
std::vector<Opm::DeckItem> items;
|
|
|
|
// Add INDEX field
|
|
items.push_back( RifOpmDeckTools::item( B::INDEX::itemName, bcIndex ) );
|
|
|
|
// Copy all items from the property record (which doesn't include INDEX)
|
|
for ( size_t i = 0; i < propRecord.size(); ++i )
|
|
{
|
|
if ( propRecord.getItem( i ).name() != B::INDEX::itemName )
|
|
{
|
|
items.push_back( propRecord.getItem( i ) );
|
|
}
|
|
}
|
|
|
|
kw.addRecord( Opm::DeckRecord{ std::move( items ) } );
|
|
}
|
|
bcIndex++;
|
|
}
|
|
|
|
return kw;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
Opm::DeckKeyword operaterKeyword( std::string targetProperty,
|
|
int regionId,
|
|
std::string equation,
|
|
std::string inputProperty,
|
|
std::optional<float> alpha,
|
|
std::optional<float> beta )
|
|
{
|
|
using O = Opm::ParserKeywords::OPERATER;
|
|
|
|
// Create the OPERATER keyword
|
|
Opm::DeckKeyword operaterKw( ( Opm::ParserKeywords::OPERATER() ) );
|
|
|
|
std::vector<Opm::DeckItem> recordItems;
|
|
recordItems.push_back( RifOpmDeckTools::item( O::TARGET_ARRAY::itemName, targetProperty ) );
|
|
recordItems.push_back( RifOpmDeckTools::item( O::REGION_NUMBER::itemName, regionId ) );
|
|
recordItems.push_back( RifOpmDeckTools::item( O::OPERATION::itemName, equation ) );
|
|
recordItems.push_back( RifOpmDeckTools::item( O::ARRAY_PARAMETER::itemName, inputProperty ) );
|
|
recordItems.push_back( RifOpmDeckTools::optionalItem( O::PARAM1::itemName, alpha ) );
|
|
recordItems.push_back( RifOpmDeckTools::optionalItem( O::PARAM2::itemName, beta ) );
|
|
|
|
// Add final default item
|
|
recordItems.push_back( RifOpmDeckTools::defaultItem( O::REGION_NAME::itemName ) ); // 1* for the last field
|
|
|
|
operaterKw.addRecord( Opm::DeckRecord{ std::move( recordItems ) } );
|
|
return operaterKw;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
Opm::DeckKeyword editnncKeyword( const std::vector<RigSimulationInputTool::TransformedNNCConnection>& connections )
|
|
{
|
|
using E = Opm::ParserKeywords::EDITNNC;
|
|
|
|
Opm::DeckKeyword kw{ Opm::ParserKeywords::EDITNNC{} };
|
|
|
|
for ( const auto& conn : connections )
|
|
{
|
|
std::vector<Opm::DeckItem> items;
|
|
|
|
// Convert from 0-based sector coordinates to 1-based Eclipse convention
|
|
items.push_back( RifOpmDeckTools::item( E::I1::itemName, static_cast<int>( conn.cell1.i() + 1 ) ) );
|
|
items.push_back( RifOpmDeckTools::item( E::J1::itemName, static_cast<int>( conn.cell1.j() + 1 ) ) );
|
|
items.push_back( RifOpmDeckTools::item( E::K1::itemName, static_cast<int>( conn.cell1.k() + 1 ) ) );
|
|
items.push_back( RifOpmDeckTools::item( E::I2::itemName, static_cast<int>( conn.cell2.i() + 1 ) ) );
|
|
items.push_back( RifOpmDeckTools::item( E::J2::itemName, static_cast<int>( conn.cell2.j() + 1 ) ) );
|
|
items.push_back( RifOpmDeckTools::item( E::K2::itemName, static_cast<int>( conn.cell2.k() + 1 ) ) );
|
|
items.push_back( RifOpmDeckTools::item( E::TRAN_MULT::itemName, conn.transmissibility ) );
|
|
|
|
kw.addRecord( Opm::DeckRecord{ std::move( items ) } );
|
|
}
|
|
|
|
return kw;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
Opm::DeckKeyword datesKeyword( const QDateTime& date )
|
|
{
|
|
using D = Opm::ParserKeywords::DATES;
|
|
|
|
QLocale locale( QLocale::English );
|
|
int day = date.date().day();
|
|
QString month = locale.monthName( date.date().month(), QLocale::ShortFormat ).toUpper();
|
|
int year = date.date().year();
|
|
|
|
std::vector<Opm::DeckItem> items;
|
|
items.push_back( RifOpmDeckTools::item( D::DAY::itemName, day ) );
|
|
items.push_back( RifOpmDeckTools::item( D::MONTH::itemName, month.toStdString() ) );
|
|
items.push_back( RifOpmDeckTools::item( D::YEAR::itemName, year ) );
|
|
|
|
// Emit the optional TIME field (HH:MM:SS[.SSS], default 00:00:00) only when the timestamp carries a
|
|
// non-midnight time, so date-only events keep their plain DAY/MONTH/YEAR output. QDateTime resolves
|
|
// to milliseconds, which is the finest precision we can preserve here.
|
|
const QTime time = date.time();
|
|
if ( time.isValid() && ( time.hour() != 0 || time.minute() != 0 || time.second() != 0 || time.msec() != 0 ) )
|
|
{
|
|
const QString timeStr = ( time.msec() != 0 ) ? time.toString( "HH:mm:ss.zzz" ) : time.toString( "HH:mm:ss" );
|
|
items.push_back( RifOpmDeckTools::item( D::TIME::itemName, timeStr.toStdString() ) );
|
|
}
|
|
|
|
Opm::DeckKeyword kw{ D() };
|
|
kw.addRecord( Opm::DeckRecord{ std::move( items ) } );
|
|
return kw;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
QString deckKeywordToString( const Opm::DeckKeyword& keyword )
|
|
{
|
|
std::ostringstream oss;
|
|
Opm::DeckOutput out( oss, 10 );
|
|
keyword.write( out );
|
|
return QString::fromStdString( oss.str() );
|
|
}
|
|
|
|
namespace
|
|
{
|
|
//--------------------------------------------------------------------------------------------------
|
|
/// Render a single DeckItem to text the same way OPM's DeckOutput does (quoted strings, raw strings
|
|
/// unquoted, integers, doubles at precision 10), except defaults always render as "1*" per value
|
|
/// rather than being accumulated into "N*". Multi-value items are space-joined into one column.
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::string renderDeckItemValue( const Opm::DeckItem& item )
|
|
{
|
|
const size_t numValues = item.data_size();
|
|
if ( numValues == 0 ) return "1*";
|
|
|
|
auto renderOne = [&]( size_t i ) -> std::string
|
|
{
|
|
if ( item.defaultApplied( i ) ) return "1*";
|
|
|
|
switch ( item.getType() )
|
|
{
|
|
case Opm::type_tag::integer:
|
|
return std::to_string( item.get<int>( i ) );
|
|
case Opm::type_tag::fdouble:
|
|
{
|
|
std::ostringstream oss;
|
|
oss.precision( 10 );
|
|
oss << item.get<double>( i );
|
|
return oss.str();
|
|
}
|
|
case Opm::type_tag::string:
|
|
return "'" + item.get<std::string>( i ) + "'";
|
|
case Opm::type_tag::raw_string:
|
|
return std::string( item.get<Opm::RawString>( i ) );
|
|
case Opm::type_tag::uda:
|
|
{
|
|
const Opm::UDAValue& uda = item.getData<Opm::UDAValue>()[i];
|
|
if ( uda.is<double>() )
|
|
{
|
|
std::ostringstream oss;
|
|
oss.precision( 10 );
|
|
oss << uda.get<double>();
|
|
return oss.str();
|
|
}
|
|
return "'" + uda.get<std::string>() + "'";
|
|
}
|
|
default:
|
|
return "1*";
|
|
}
|
|
};
|
|
|
|
std::string result;
|
|
for ( size_t i = 0; i < numValues; ++i )
|
|
{
|
|
if ( i > 0 ) result += " ";
|
|
result += renderOne( i );
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
/// Shorten a parser item name to at most maxWidth characters so long names (e.g.
|
|
/// "CONNECTION_TRANSMISSIBILITY_FACTOR") do not widen their column beyond the data. Each
|
|
/// underscore-separated token is truncated to progressively shorter prefixes ("CONN_TRAN_FACT",
|
|
/// "CON_TRA_FAC", ...), falling back to token initials ("CTF") and finally a hard cut.
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::string shortenItemName( const std::string& name, size_t maxWidth )
|
|
{
|
|
if ( name.size() <= maxWidth ) return name;
|
|
|
|
std::vector<std::string> tokens;
|
|
std::stringstream tokenStream( name );
|
|
std::string token;
|
|
while ( std::getline( tokenStream, token, '_' ) )
|
|
{
|
|
if ( !token.empty() ) tokens.push_back( token );
|
|
}
|
|
if ( tokens.empty() ) return name.substr( 0, maxWidth );
|
|
|
|
auto joinPrefixes = [&tokens]( size_t prefixLength )
|
|
{
|
|
std::string joined;
|
|
for ( const auto& t : tokens )
|
|
{
|
|
if ( !joined.empty() ) joined += "_";
|
|
joined += t.substr( 0, prefixLength );
|
|
}
|
|
return joined;
|
|
};
|
|
|
|
size_t longestToken = 0;
|
|
for ( const auto& t : tokens )
|
|
longestToken = std::max( longestToken, t.size() );
|
|
|
|
for ( size_t prefixLength = longestToken - 1; prefixLength >= 2; --prefixLength )
|
|
{
|
|
std::string candidate = joinPrefixes( prefixLength );
|
|
if ( candidate.size() <= maxWidth ) return candidate;
|
|
}
|
|
|
|
std::string initials;
|
|
for ( const auto& t : tokens )
|
|
initials += t.front();
|
|
if ( initials.size() <= maxWidth ) return initials;
|
|
|
|
return name.substr( 0, maxWidth );
|
|
}
|
|
} // namespace
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
QString deckKeywordToAlignedString( const Opm::DeckKeyword& keyword )
|
|
{
|
|
if ( keyword.name().empty() ) return {};
|
|
|
|
std::ostringstream oss;
|
|
oss << keyword.name() << "\n";
|
|
|
|
auto rightAlign = []( const std::string& s, size_t width ) { return std::string( width - s.size(), ' ' ) + s; };
|
|
|
|
// Emit the keyword as one or more groups of consecutive records that share the same ordered list
|
|
// of item names. Each group gets its own aligned column header. Tabular keywords (WCONHIST,
|
|
// COMPDAT, ...) form a single group; heterogeneous keywords (WELSEGS = header + segment records,
|
|
// TUNING = three distinct records) form one group per record shape.
|
|
const size_t numRecords = keyword.size();
|
|
size_t recordIndex = 0;
|
|
while ( recordIndex < numRecords )
|
|
{
|
|
// Group signature = item names of the first record in the group.
|
|
std::vector<std::string> header;
|
|
for ( const auto& item : keyword.getRecord( recordIndex ) )
|
|
{
|
|
header.push_back( item.name() );
|
|
}
|
|
|
|
// Collect every consecutive record matching this signature, rendering its values.
|
|
std::vector<std::vector<std::string>> rows;
|
|
size_t groupEnd = recordIndex;
|
|
for ( ; groupEnd < numRecords; ++groupEnd )
|
|
{
|
|
const Opm::DeckRecord& record = keyword.getRecord( groupEnd );
|
|
if ( record.size() != header.size() ) break;
|
|
|
|
bool sameNames = true;
|
|
size_t col = 0;
|
|
for ( const auto& item : record )
|
|
{
|
|
if ( item.name() != header[col] )
|
|
{
|
|
sameNames = false;
|
|
break;
|
|
}
|
|
++col;
|
|
}
|
|
if ( !sameNames ) break;
|
|
|
|
std::vector<std::string> row;
|
|
row.reserve( record.size() );
|
|
for ( const auto& item : record )
|
|
{
|
|
row.push_back( renderDeckItemValue( item ) );
|
|
}
|
|
rows.push_back( std::move( row ) );
|
|
}
|
|
|
|
const size_t numCols = header.size();
|
|
std::vector<size_t> width( numCols, 0 );
|
|
for ( const auto& row : rows )
|
|
for ( size_t c = 0; c < numCols; ++c )
|
|
width[c] = std::max( width[c], row[c].size() );
|
|
|
|
// Long parser item names (e.g. "CONNECTION_TRANSMISSIBILITY_FACTOR") would otherwise pad
|
|
// every data row to the header width and push lines past the 132-character limit enforced
|
|
// by some simulators. Shorten each header to the width of its data, letting narrow columns
|
|
// grow to a small minimum so the abbreviations stay readable.
|
|
constexpr size_t minHeaderWidth = 8;
|
|
for ( size_t c = 0; c < numCols; ++c )
|
|
{
|
|
header[c] = shortenItemName( header[c], std::max( width[c], minHeaderWidth ) );
|
|
width[c] = std::max( width[c], header[c].size() );
|
|
}
|
|
|
|
// Header comment line: "--" occupies the same two columns as the data-row indent so the
|
|
// header names line up with the values below them.
|
|
if ( numCols > 0 )
|
|
{
|
|
oss << "--";
|
|
for ( size_t c = 0; c < numCols; ++c )
|
|
{
|
|
if ( c > 0 ) oss << " ";
|
|
oss << rightAlign( header[c], width[c] );
|
|
}
|
|
oss << "\n";
|
|
}
|
|
|
|
for ( const auto& row : rows )
|
|
{
|
|
oss << " ";
|
|
for ( size_t c = 0; c < numCols; ++c )
|
|
{
|
|
if ( c > 0 ) oss << " ";
|
|
oss << rightAlign( row[c], width[c] );
|
|
}
|
|
oss << " /\n";
|
|
}
|
|
|
|
recordIndex = groupEnd;
|
|
}
|
|
|
|
// Emit the keyword-terminating slash for variable-size keywords, matching OPM
|
|
// (DeckKeyword::m_slashTerminated == !ParserKeyword::hasFixedSize()).
|
|
bool slashTerminated = true;
|
|
try
|
|
{
|
|
Opm::Parser parser;
|
|
slashTerminated = !parser.getKeyword( keyword.name() ).hasFixedSize();
|
|
}
|
|
catch ( ... )
|
|
{
|
|
slashTerminated = true;
|
|
}
|
|
if ( slashTerminated ) oss << "/\n";
|
|
|
|
return QString::fromStdString( oss.str() );
|
|
}
|
|
|
|
} // namespace RimKeywordFactory
|