Add support for COMPLUMP keyword (#13273)

* Add support for COMPLUMP keyword, both in python interface, file export and opm flow jobs
* Add support for WPIMULT kw in opm flow jobs
* Always provide per connection dFactors in COMPDAT kw (negative value)
* Include LGR support in COMPLUMP file export
This commit is contained in:
JJ authored and GitHub committed 2025-12-04 19:25:41 +01:00
1 parent 70f76aefb0
commit 9f17dd81ff
12 files changed
+299 -22

No files matched your search

@@ -260,6 +260,12 @@ void RicWellPathExportCompletionDataFeatureImpl::exportCompletions( const std::v
}
}
// make sure we use per-connection D-factors, must be done after combining completions
for ( auto& completion : completions )
{
completion.setPerConnectionDfactor();
}
const QString eclipseCaseName = exportSettings.caseToApply->caseUserDescription();
if ( exportSettings.fileSplit == RicExportCompletionDataSettingsUi::ExportSplit::UNIFIED_FILE )
@@ -413,8 +419,9 @@ RigCompletionData RicWellPathExportCompletionDataFeatureImpl::combineEclipseCell
auto isValidTransmissibility = []( double transmissibility )
{ return RiaStatisticsTools::isValidNumber<double>( transmissibility ) && transmissibility >= 0.0; };
auto startMD = completions[0].startMD();
auto endMD = completions[0].endMD();
auto startMD = completions[0].startMD();
auto endMD = completions[0].endMD();
auto completionNumber = completions[0].completionNumber();
for ( const RigCompletionData& completion : completions )
{
@@ -442,6 +449,14 @@ RigCompletionData RicWellPathExportCompletionDataFeatureImpl::combineEclipseCell
}
}
if ( !completionNumber.has_value() )
{
if ( completion.completionNumber().has_value() )
{
completionNumber = completion.completionNumber();
}
}
double transmissibility = completion.transmissibility();
if ( !isValidTransmissibility( transmissibility ) )
@@ -468,6 +483,7 @@ RigCompletionData RicWellPathExportCompletionDataFeatureImpl::combineEclipseCell
{
resultCompletion.setDepthRange( startMD.value(), endMD.value() );
}
if ( completionNumber.has_value() ) resultCompletion.setCompletionNumber( completionNumber.value() );
double combinedDiameter = diameterCalculator.weightedMean();
double combinedSkinFactor = skinFactorCalculator.weightedMean();
@@ -898,6 +914,15 @@ void RicWellPathExportCompletionDataFeatureImpl::exportCompdatAndWpimultTables(
{
exportWpimultTableUsingFormatter( formatter, gridName, completions );
}
for ( auto& completion : completions )
{
if ( completion.completionNumber().has_value() )
{
exportComplumpTableUsingFormatter( formatter, gridName, completions );
break;
}
}
}
RiaLogging::info( QString( "Successfully exported completion data to %1" ).arg( exportFile->fileName() ) );
@@ -1000,10 +1025,7 @@ void RicWellPathExportCompletionDataFeatureImpl::exportCompdatTableUsingFormatte
formatter.addValueOrDefaultMarker( data.diameter(), RigCompletionData::defaultValue() );
formatter.addValueOrDefaultMarker( data.kh(), RigCompletionData::defaultValue() );
formatter.addValueOrDefaultMarker( data.skinFactor(), RigCompletionData::defaultValue() );
if ( RigCompletionData::isDefaultValue( data.dFactor() ) )
formatter.add( "1*" );
else
formatter.add( -data.dFactor() );
formatter.addValueOrDefaultMarker( data.dFactor(), RigCompletionData::defaultValue() );
switch ( data.direction() )
{
@@ -1024,6 +1046,65 @@ void RicWellPathExportCompletionDataFeatureImpl::exportCompdatTableUsingFormatte
formatter.tableCompleted();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicWellPathExportCompletionDataFeatureImpl::exportComplumpTableUsingFormatter( RifTextDataTableFormatter& formatter,
const QString& gridName,
const std::vector<RigCompletionData>& completionData )
{
if ( gridName.isEmpty() )
{
std::vector<RifTextDataTableColumn> header = { RifTextDataTableColumn( "WELL", "NAME" ),
RifTextDataTableColumn( "", "I" ),
RifTextDataTableColumn( "", "J" ),
RifTextDataTableColumn( "", "K1" ),
RifTextDataTableColumn( "", "K2" ),
RifTextDataTableColumn( "COMP", "NUM" ) };
formatter.header( header );
formatter.keyword( "COMPLUMP" );
}
else
{
std::vector<RifTextDataTableColumn> header = { RifTextDataTableColumn( "WELL", "NAME" ),
RifTextDataTableColumn( "LGR", "NAME" ),
RifTextDataTableColumn( "", "I" ),
RifTextDataTableColumn( "", "J" ),
RifTextDataTableColumn( "", "K1" ),
RifTextDataTableColumn( "", "K2" ),
RifTextDataTableColumn( "COMP", "NUM" ) };
formatter.header( header );
formatter.keyword( "COMPLMPL" );
}
for ( const RigCompletionData& data : completionData )
{
if ( !data.completionNumber().has_value() || data.transmissibility() == 0.0 || data.wpimult() == 0.0 )
{
// Don't export completions without transmissibility or completion number set
continue;
}
formatter.add( data.wellName() );
if ( !gridName.isEmpty() )
{
formatter.add( gridName );
}
formatter.add( data.completionDataGridCell().localCellIndexI() + 1 )
.add( data.completionDataGridCell().localCellIndexJ() + 1 )
.add( data.completionDataGridCell().localCellIndexK() + 1 )
.add( data.completionDataGridCell().localCellIndexK() + 1 )
.add( data.completionNumber().value() );
formatter.rowCompleted();
}
formatter.tableCompleted();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -1172,6 +1253,7 @@ std::vector<RigCompletionData>
kh,
direction );
completion.setDepthRange( cell.startMD, cell.endMD );
if ( interval->completionNumber() > 0 ) completion.setCompletionNumber( interval->completionNumber() );
completion.addMetadata( "Perforation Completion",
QString( "MD In: %1 - MD Out: %2" ).arg( cell.startMD ).arg( cell.endMD ) +
@@ -1727,5 +1809,12 @@ std::vector<RigCompletionData> RicWellPathExportCompletionDataFeatureImpl::compl
std::sort( completions.begin(), completions.end() );
// make sure we set per-connection D-factors, must be done after combining completions
for ( auto& completion : completions )
{
completion.setPerConnectionDfactor(); // we calculate per connection D-factors, which are negative per definition (ref. OPM Flow
// manual)
}
return completions;
}
@@ -179,6 +179,10 @@ private:
const QString& gridName,
const std::vector<RigCompletionData>& completionData );
static void exportComplumpTableUsingFormatter( RifTextDataTableFormatter& formatter,
const QString& gridName,
const std::vector<RigCompletionData>& completionData );
static void exportWpimultTableUsingFormatter( RifTextDataTableFormatter& formatter,
const QString& gridName,
const std::vector<RigCompletionData>& completionData );
@@ -50,6 +50,7 @@ RimPerforationInterval::RimPerforationInterval()
CAF_PDM_InitScriptableField( &m_endMD, "EndMeasuredDepth", 0.0, "End MD" );
CAF_PDM_InitScriptableField( &m_diameter, "Diameter", 0.216, "Diameter" );
CAF_PDM_InitScriptableField( &m_skinFactor, "SkinFactor", 0.0, "Skin Factor" );
CAF_PDM_InitScriptableField( &m_completionNumber, "CompletionNumber", 0, "Completion Number" );
CAF_PDM_InitField( &m_startOfHistory_OBSOLETE, "StartOfHistory", true, "All Timesteps" );
m_startOfHistory_OBSOLETE.xmlCapability()->setIOWritable( false );
@@ -158,6 +159,22 @@ double RimPerforationInterval::skinFactor() const
return m_skinFactor();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimPerforationInterval::setCompletionNumber( int number )
{
m_completionNumber = number;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
int RimPerforationInterval::completionNumber() const
{
return m_completionNumber();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -363,13 +380,15 @@ void RimPerforationInterval::defineUiOrdering( QString uiConfigName, caf::PdmUiO
uiOrdering.add( &m_endMD );
uiOrdering.add( &m_diameter );
uiOrdering.add( &m_skinFactor );
uiOrdering.add( &m_completionNumber );
uiOrdering.add( &m_useCustomStartDate );
uiOrdering.add( &m_startDate );
auto dateGrp = uiOrdering.addNewGroup( "Date Settings" );
dateGrp->setCollapsedByDefault();
dateGrp->add( &m_useCustomStartDate );
dateGrp->add( &m_startDate );
m_startDate.uiCapability()->setUiReadOnly( !m_useCustomStartDate );
uiOrdering.add( &m_useCustomEndDate );
uiOrdering.add( &m_endDate );
dateGrp->add( &m_useCustomEndDate );
dateGrp->add( &m_endDate );
m_endDate.uiCapability()->setUiReadOnly( !m_useCustomEndDate );
uiOrdering.skipRemainingFields();
@@ -53,8 +53,10 @@ public:
void setDiameter( double diameter );
void setSkinFactor( double skinFactor );
void setCompletionNumber( int number );
double diameter( RiaDefines::EclipseUnitSystem unitSystem ) const;
double skinFactor() const;
int completionNumber() const;
bool isActiveOnDate( const QDateTime& date ) const;
@@ -88,6 +90,7 @@ private:
caf::PdmField<double> m_endMD;
caf::PdmField<double> m_diameter;
caf::PdmField<double> m_skinFactor;
caf::PdmField<int> m_completionNumber;
caf::PdmField<bool> m_useCustomStartDate;
caf::PdmField<QDateTime> m_startDate;
@@ -97,21 +97,17 @@ Opm::DeckKeyword welspecsKeyword( const std::string wellGrpName, RimEclipseCase*
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
Opm::DeckKeyword compdatKeyword( RimEclipseCase* eCase, RimWellPath* wellPath )
Opm::DeckKeyword compdatKeyword( const std::vector<RigCompletionData>& compdata, const std::string wellName )
{
if ( eCase == nullptr || wellPath == nullptr || wellPath->completionSettings() == nullptr || eCase->eclipseCaseData() == nullptr )
if ( compdata.empty() )
{
return Opm::DeckKeyword();
}
auto compdata = RicWellPathExportCompletionDataFeatureImpl::completionDataForWellPath( wellPath, eCase );
using C = Opm::ParserKeywords::COMPDAT;
Opm::DeckKeyword kw( ( Opm::ParserKeywords::COMPDAT() ) );
auto wellName = wellPath->completionSettings()->wellNameForExport().toStdString();
for ( auto& cd : compdata )
{
std::vector<Opm::DeckItem> items;
@@ -153,6 +149,70 @@ Opm::DeckKeyword compdatKeyword( RimEclipseCase* eCase, RimWellPath* wellPath )
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;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -24,6 +24,7 @@
#include <string>
#include <vector>
class RigCompletionData;
class RigMainGrid;
class RigMswTableData;
class RimEclipseCase;
@@ -48,7 +49,9 @@ namespace RimKeywordFactory
{
Opm::DeckKeyword welspecsKeyword( const std::string wellGrpName, RimEclipseCase* eCase, RimWellPath* wellPath );
Opm::DeckKeyword compdatKeyword( RimEclipseCase* eCase, RimWellPath* wellPath );
Opm::DeckKeyword compdatKeyword( const std::vector<RigCompletionData>& compdata, const std::string wellName );
Opm::DeckKeyword wpimultKeyword( const std::vector<RigCompletionData>& compdata, const std::string wellName );
Opm::DeckKeyword complumpKeyword( const std::vector<RigCompletionData>& compdata, const std::string wellName );
Opm::DeckKeyword welsegsKeyword( const RigMswTableData& mswData, int& maxSegments, int& maxBranches );
Opm::DeckKeyword compsegsKeyword( const RigMswTableData& mswData );
@@ -1030,9 +1030,21 @@ int RimOpmFlowJob::mergeBasicWellSettings()
{
const int failure = -1;
if ( m_eclipseCase() == nullptr || m_wellPath() == nullptr || m_wellPath()->completionSettings() == nullptr ||
m_eclipseCase()->eclipseCaseData() == nullptr )
{
RiaLogging::error( "Cannot create COMPDAT keyword - check case, wellpath and completion settings data." );
return failure;
}
auto compdata = RicWellPathExportCompletionDataFeatureImpl::completionDataForWellPath( m_wellPath(), m_eclipseCase() );
auto wellName = m_wellPath()->completionSettings()->wellNameForExport().toStdString();
int mergePosition = m_openWellDeckPosition();
auto compdatKw = RimKeywordFactory::compdatKeyword( m_eclipseCase(), m_wellPath() );
auto compdatKw = RimKeywordFactory::compdatKeyword( compdata, wellName );
auto wpimultKw = RimKeywordFactory::wpimultKeyword( compdata, wellName );
auto complumpKw = RimKeywordFactory::complumpKeyword( compdata, wellName );
auto welspecsKw = RimKeywordFactory::welspecsKeyword( m_wellGroupName().toStdString(), m_eclipseCase(), m_wellPath() );
if ( welspecsKw.empty() || compdatKw.empty() )
@@ -1044,6 +1056,14 @@ int RimOpmFlowJob::mergeBasicWellSettings()
if ( m_wellOpenType == WellOpenType::OPEN_AT_DATE )
{
// reverse order for correct insertion order
if ( !complumpKw.empty() )
{
if ( !m_deckFile->mergeKeywordAtTimeStep( m_openTimeStep(), complumpKw ) ) return failure;
}
if ( !wpimultKw.empty() )
{
if ( !m_deckFile->mergeKeywordAtTimeStep( m_openTimeStep(), wpimultKw ) ) return failure;
}
if ( !m_deckFile->mergeKeywordAtTimeStep( m_openTimeStep(), compdatKw ) ) return failure;
if ( !m_deckFile->mergeKeywordAtTimeStep( m_openTimeStep(), welspecsKw ) ) return failure;
mergePosition = 0;
@@ -1054,6 +1074,16 @@ int RimOpmFlowJob::mergeBasicWellSettings()
if ( mergePosition < 0 ) return failure;
mergePosition = m_deckFile->mergeKeywordAtPosition( mergePosition, compdatKw );
if ( mergePosition < 0 ) return failure;
if ( !wpimultKw.empty() )
{
mergePosition = m_deckFile->mergeKeywordAtPosition( mergePosition, wpimultKw );
if ( mergePosition < 0 ) return failure;
}
if ( !complumpKw.empty() )
{
mergePosition = m_deckFile->mergeKeywordAtPosition( mergePosition, complumpKw );
if ( mergePosition < 0 ) return failure;
}
}
// increase wells and connections in welldims to make sure they are big enough
@@ -24,6 +24,7 @@
#include <QString>
#include <cmath>
#include <limits>
//==================================================================================================
@@ -46,6 +47,7 @@ RigCompletionData::RigCompletionData( const QString& wellName, const RigCompleti
, m_secondOrderingValue( std::numeric_limits<double>::infinity() )
, m_startMD( std::nullopt )
, m_endMD( std::nullopt )
, m_completionNumber( std::nullopt )
{
}
@@ -261,6 +263,17 @@ void RigCompletionData::setDFactor( double dFactor )
m_dFactor = dFactor;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigCompletionData::setPerConnectionDfactor()
{
if ( isDefaultValue( m_dFactor ) ) return;
// We use per connection dfactors, they have negative values, ref OPM Flow manual - COMPDAT kw
m_dFactor = std::abs( m_dFactor ) * -1.0;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -381,6 +394,14 @@ std::optional<double> RigCompletionData::endMD() const
return m_endMD;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::optional<int> RigCompletionData::completionNumber() const
{
return m_completionNumber;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -517,6 +538,7 @@ void RigCompletionData::copy( RigCompletionData& target, const RigCompletionData
target.m_firstOrderingValue = from.m_firstOrderingValue;
target.m_secondOrderingValue = from.m_secondOrderingValue;
target.m_sourcePdmObject = from.m_sourcePdmObject;
target.m_completionNumber = from.m_completionNumber;
}
//--------------------------------------------------------------------------------------------------
@@ -527,3 +549,11 @@ void RigCompletionData::setDepthRange( double startMD, double endMD )
m_startMD = startMD;
m_endMD = endMD;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigCompletionData::setCompletionNumber( int completionNumber )
{
m_completionNumber = completionNumber;
}
@@ -86,6 +86,7 @@ public:
void setDiameter( double diameter );
void setTransmissibility( double transmissibility );
void setDepthRange( double startMD, double endMD );
void setCompletionNumber( int completionNumber );
void setTransAndWPImultBackgroundDataFromFishbone( double transmissibility,
double skinFactor,
@@ -120,6 +121,7 @@ public:
bool isNonDarcyFlow() const;
void setDFactor( double dFactor );
void setKh( double kh );
void setPerConnectionDfactor();
void addMetadata( const QString& name, const QString& comment );
@@ -140,6 +142,7 @@ public:
bool isMainBore() const;
std::optional<double> startMD() const;
std::optional<double> endMD() const;
std::optional<int> completionNumber() const;
QString directionStringIJK() const;
QString directionStringXYZ() const;
QString metaDataString() const;
@@ -166,6 +169,8 @@ private:
std::optional<double> m_startMD; // start MD in completion cell
std::optional<double> m_endMD; // end MD in completion cell
std::optional<int> m_completionNumber; // for complump kword
bool m_isMainBore; // to use mainbore for Eclipse calculation
double m_wpimult;
@@ -39,6 +39,7 @@ message SimulatorCompdatEntry {
optional double end_md = 15;
optional string comment = 16;
optional string grid_name = 17;
optional int32 completion_number = 18;
}
message SimulatorWelspecsEntry {
@@ -53,6 +53,7 @@ for well in wells:
txt += fieldValueOrDefaultText(line, "pvt_num") + " "
txt += fieldValueOrDefaultText(line, "hydrostatic_density_calc") + " "
txt += fieldValueOrDefaultText(line, "fip_region") + " "
txt += "/"
print(txt)
@@ -60,14 +61,17 @@ for well in wells:
compdat = completion_data.compdat
complump_entries = []
print("COMPDAT")
for line in compdat:
txt = ""
complump = ""
if line.HasField("start_md"):
txt += "-- Perforation MD In " + str(line.start_md)
txt += ", MD Out " + str(line.end_md) + "\n"
txt += ", MD Out " + str(line.end_md) + "--\n"
txt += " "
txt += line.well_name + " "
@@ -83,13 +87,36 @@ for well in wells:
txt += fieldValueOrDefaultText(line, "skin_factor") + " "
txt += fieldValueOrDefaultText(line, "d_factor") + " "
txt += "'%s'" % line.direction
txt += " /"
if (line.HasField("completion_number")) and (line.completion_number > 0):
complump += " "
complump += line.well_name + " "
complump += str(line.grid_i) + " "
complump += str(line.grid_j) + " "
complump += str(line.upper_k) + " "
complump += str(line.lower_k) + " "
complump += str(line.completion_number) + " "
complump += " /"
complump_entries.append(complump)
print(txt)
print("/\n")
if len(complump_entries) > 0:
print("COMPLUMP")
for complump_entry in complump_entries:
print(complump_entry)
print("/\n")
else:
print("-- No COMPLUMP entries --\n")
print("WELSEGS")
welsegs = completion_data.welsegs
if (welsegs is None) or (len(welsegs) == 0):
print(" -- No WELSEGS data --")
print(" -- No WELSEGS data --\n")
else:
for welsegs_entry in welsegs:
# Print WELSEGS header
@@ -102,6 +129,7 @@ for well in wells:
txt += header.info_type + " "
txt += fieldValueOrDefaultText(header, "pressure_components") + " "
txt += fieldValueOrDefaultText(header, "flow_model")
txt += " /"
print(txt)
# Print WELSEGS segment rows
@@ -115,7 +143,7 @@ for well in wells:
txt += str(row.depth) + " "
txt += fieldValueOrDefaultText(row, "diameter") + " "
txt += fieldValueOrDefaultText(row, "roughness") + " "
# txt += fieldValueOrDefaultText(row, "description")
txt += " /"
print(txt)
print("/")
@@ -135,6 +163,7 @@ for well in wells:
txt += str(compseg.distance_start) + " "
txt += str(compseg.distance_end) + " "
txt += fieldValueOrDefaultText(compseg, "grid_name")
txt += " /"
print(txt)
print("/\n")
@@ -72,6 +72,10 @@ void RiaWellPathDataToGrpcConverter::copyCompdatToGrpc( const RigCompletionData&
compDat->set_start_md( inputData.startMD().value() );
compDat->set_end_md( inputData.endMD().value() );
}
if ( inputData.completionNumber().has_value() )
{
compDat->set_completion_number( inputData.completionNumber().value() );
}
// Set LGR grid name, empty string for main grid
compDat->set_grid_name( inputData.completionDataGridCell().lgrName().toStdString() );