mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-24 22:27:55 -05:00
Sector export: Correct connection factors for refinement perpendicular to wells
Lateral refinement reduces the Peaceman pressure equivalent radius r0 of the completed center sub-cell. Explicit CF is scaled by (ln(r0_old/rw)+S)/(ln(r0_new/rw)+S), and an explicit PR item is updated to r0_new. r0_old is taken from PR, or computed from the original cell size and permeability.
This commit is contained in:
1 parent
9a01e09ecd
commit
b84eea5cb1
4 files changed
+326
-20
No files matched your search
@@ -46,12 +46,10 @@ public:
|
||||
|
||||
static double permeability( const double conductivity, const double width );
|
||||
|
||||
private:
|
||||
// If using wellBoreTransmissibilityComponent to calculate Tx (transmissibility in x direction),
|
||||
// perforationVectorComponent is the x component (in the cell local coordinate system) of the perforation vector
|
||||
// permeability and cell size for Z and Y are to be specified as "normal directions" 1 and 2
|
||||
// but normal directions 1 and 2 are interchangeable (so Z=1, Y=2 and Z=2, Y=1 gives same result)
|
||||
|
||||
static double peacemanRadius( double permeabilityNormalDirection1,
|
||||
double permeabilityNormalDirection2,
|
||||
double cellSizeNormalDirection1,
|
||||
|
||||
@@ -29,8 +29,11 @@
|
||||
#include "ProjectDataModel/Jobs/RimKeywordFactory.h"
|
||||
#include "RimEclipseCase.h"
|
||||
|
||||
#include "RigActiveCellInfo.h"
|
||||
#include "RigBoundingBoxIjk.h"
|
||||
#include "RigCaseCellResultsData.h"
|
||||
#include "RigEclipseCaseData.h"
|
||||
#include "RigEclipseResultAddress.h"
|
||||
#include "RigEclipseResultTools.h"
|
||||
#include "RigGridExportAdapter.h"
|
||||
#include "RigMainGrid.h"
|
||||
@@ -39,6 +42,7 @@
|
||||
#include "RigRefinement.h"
|
||||
#include "RigResdataGridConverter.h"
|
||||
#include "RigSimulationInputSettings.h"
|
||||
#include "RigTransmissibilityEquations.h"
|
||||
#include "Well/RigSimWellData.h"
|
||||
#include "Well/RigWellResultFrame.h"
|
||||
#include "Well/RigWellResultPoint.h"
|
||||
@@ -60,6 +64,8 @@
|
||||
|
||||
#include <QFileInfo>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <set>
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -154,7 +160,7 @@ std::expected<void, QString> RigSimulationInputTool::exportSimulationInput( RimE
|
||||
|
||||
auto validWellNames = wellNamesToInclude( &eclipseCase, settings );
|
||||
|
||||
if ( auto result = filterAndUpdateWellKeywords( validWellNames, settings, deckFile ); !result )
|
||||
if ( auto result = filterAndUpdateWellKeywords( &eclipseCase, validWellNames, settings, deckFile ); !result )
|
||||
{
|
||||
return result;
|
||||
}
|
||||
@@ -1331,6 +1337,177 @@ static bool hasExplicitDoubleValue( const Opm::DeckRecord& record, const std::st
|
||||
return item.hasValue( 0 ) && !item.defaultApplied( 0 );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Length fraction of the center sub-cell of an original (sector-relative) cell index
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
static double centerSubcellFraction( const RigRefinement& refinement, RigRefinement::Dimension dim, size_t sectorIndex )
|
||||
{
|
||||
const auto& cumulative = refinement.cumulativeFractions( dim, sectorIndex );
|
||||
const size_t center = refinement.subcellCount( dim, sectorIndex ) / 2;
|
||||
if ( center >= cumulative.size() ) return 1.0;
|
||||
return cumulative[center] - ( center > 0 ? cumulative[center - 1] : 0.0 );
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
struct LateralRefinementCorrection
|
||||
{
|
||||
double connectionFactorMultiplier = 1.0;
|
||||
double pressureEquivalentRadius = 0.0;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Correction of an explicit connection factor when the cell is refined perpendicular to the well.
|
||||
/// Peaceman: CF = c * theta * K * h / ( ln( r0 / rw ) + S ). Lateral refinement keeps K * h, but reduces
|
||||
/// the pressure equivalent radius r0, so CF_new = CF_old * ( ln( r0_old / rw ) + S ) / ( ln( r0_new / rw ) + S ).
|
||||
/// r0_old is taken from the PR item if given, otherwise computed from the original cell.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
static std::optional<LateralRefinementCorrection>
|
||||
lateralRefinementCorrection( const Opm::DeckRecord& record,
|
||||
const std::string& wellName,
|
||||
RigRefinement::Dimension penetrationDim,
|
||||
double fraction1,
|
||||
double fraction2,
|
||||
const std::optional<RigSimulationInputTool::ConnectionCellProperties>& cellProperties )
|
||||
{
|
||||
using C = Opm::ParserKeywords::COMPDAT;
|
||||
|
||||
if ( fraction1 >= 1.0 && fraction2 >= 1.0 ) return std::nullopt;
|
||||
if ( !hasExplicitDoubleValue( record, C::CONNECTION_TRANSMISSIBILITY_FACTOR::itemName ) ) return std::nullopt;
|
||||
|
||||
auto warning = [&]( const std::string& reason )
|
||||
{
|
||||
RiaLogging::warning(
|
||||
std::format( "COMPDAT for well {}: connection factor not corrected for refinement perpendicular to the well ({})", wellName, reason ) );
|
||||
};
|
||||
|
||||
if ( !hasExplicitDoubleValue( record, C::DIAMETER::itemName ) )
|
||||
{
|
||||
warning( "no well diameter" );
|
||||
return std::nullopt;
|
||||
}
|
||||
const double wellRadius = 0.5 * record.getItem( C::DIAMETER::itemName ).get<double>( 0 );
|
||||
|
||||
double skin = 0.0;
|
||||
if ( record.hasItem( C::SKIN::itemName ) && record.getItem( C::SKIN::itemName ).hasValue( 0 ) )
|
||||
{
|
||||
skin = record.getItem( C::SKIN::itemName ).get<double>( 0 );
|
||||
}
|
||||
|
||||
// Peaceman radius of the original and refined cell, using the cell sizes and permeabilities perpendicular to the well
|
||||
std::optional<std::pair<double, double>> computedRadii;
|
||||
if ( cellProperties )
|
||||
{
|
||||
const int d1 = ( penetrationDim == RigRefinement::DimI ) ? 1 : 0;
|
||||
const int d2 = ( penetrationDim == RigRefinement::DimK ) ? 1 : 2;
|
||||
|
||||
const double size1 = cellProperties->cellSize[d1];
|
||||
const double size2 = cellProperties->cellSize[d2];
|
||||
double perm1 = cellProperties->permeability[d1];
|
||||
double perm2 = cellProperties->permeability[d2];
|
||||
if ( !( perm1 > 0.0 && perm2 > 0.0 ) )
|
||||
{
|
||||
perm1 = 1.0;
|
||||
perm2 = 1.0;
|
||||
}
|
||||
|
||||
if ( size1 > 0.0 && size2 > 0.0 )
|
||||
{
|
||||
const double coarse = RigTransmissibilityEquations::peacemanRadius( perm1, perm2, size1, size2 );
|
||||
const double fine = RigTransmissibilityEquations::peacemanRadius( perm1, perm2, fraction1 * size1, fraction2 * size2 );
|
||||
computedRadii = std::make_pair( coarse, fine );
|
||||
}
|
||||
}
|
||||
|
||||
double originalRadius = 0.0;
|
||||
if ( hasExplicitDoubleValue( record, C::PR::itemName ) )
|
||||
{
|
||||
originalRadius = record.getItem( C::PR::itemName ).get<double>( 0 );
|
||||
}
|
||||
else if ( computedRadii )
|
||||
{
|
||||
originalRadius = computedRadii->first;
|
||||
}
|
||||
else
|
||||
{
|
||||
warning( "no pressure equivalent radius or cell geometry" );
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Peaceman radius is homogeneous in the cell sizes, so the ratio is exact when both directions are refined equally
|
||||
const double radiusRatio = computedRadii ? computedRadii->second / computedRadii->first
|
||||
: std::sqrt( 0.5 * ( fraction1 * fraction1 + fraction2 * fraction2 ) );
|
||||
const double refinedRadius = originalRadius * radiusRatio;
|
||||
|
||||
const double originalDenominator = std::log( originalRadius / wellRadius ) + skin;
|
||||
const double refinedDenominator = std::log( refinedRadius / wellRadius ) + skin;
|
||||
if ( !( wellRadius > 0.0 && originalDenominator > 0.0 && refinedDenominator > 0.0 ) )
|
||||
{
|
||||
warning(
|
||||
std::format( "refined pressure equivalent radius {:.4g} is too small compared to well radius {:.4g}", refinedRadius, wellRadius ) );
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return LateralRefinementCorrection{ originalDenominator / refinedDenominator, refinedRadius };
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RigSimulationInputTool::ConnectionCellPropertiesProvider RigSimulationInputTool::connectionCellPropertiesProvider( RimEclipseCase* eclipseCase )
|
||||
{
|
||||
if ( !eclipseCase || !eclipseCase->eclipseCaseData() || !eclipseCase->eclipseCaseData()->mainGrid() ) return {};
|
||||
|
||||
auto* caseData = eclipseCase->eclipseCaseData();
|
||||
auto* results = caseData->results( RiaDefines::PorosityModelType::MATRIX_MODEL );
|
||||
auto* activeCellInfo = caseData->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL );
|
||||
|
||||
std::array<const std::vector<double>*, 3> permeabilities = { nullptr, nullptr, nullptr };
|
||||
if ( results && activeCellInfo )
|
||||
{
|
||||
const std::array<QString, 3> permNames = { "PERMX", "PERMY", "PERMZ" };
|
||||
for ( size_t i = 0; i < 3; ++i )
|
||||
{
|
||||
RigEclipseResultAddress address( RiaDefines::ResultCatType::STATIC_NATIVE, permNames[i] );
|
||||
if ( results->ensureKnownResultLoaded( address ) && !results->cellScalarResults( address ).empty() )
|
||||
{
|
||||
permeabilities[i] = &results->cellScalarResults( address, 0 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const RigMainGrid* mainGrid = caseData->mainGrid();
|
||||
|
||||
return [mainGrid, activeCellInfo, permeabilities]( const caf::VecIjk0& ijk ) -> std::optional<ConnectionCellProperties>
|
||||
{
|
||||
if ( !mainGrid->isCellValid( ijk.i(), ijk.j(), ijk.k() ) ) return std::nullopt;
|
||||
|
||||
const size_t cellIndex = mainGrid->cellIndexFromIJK( ijk.i(), ijk.j(), ijk.k() );
|
||||
const RigCell& cell = mainGrid->cell( cellIndex );
|
||||
if ( cell.isInvalid() ) return std::nullopt;
|
||||
|
||||
using F = cvf::StructGridInterface;
|
||||
ConnectionCellProperties properties;
|
||||
properties.cellSize = cvf::Vec3d( ( cell.faceCenter( F::NEG_I ) - cell.faceCenter( F::POS_I ) ).length(),
|
||||
( cell.faceCenter( F::NEG_J ) - cell.faceCenter( F::POS_J ) ).length(),
|
||||
( cell.faceCenter( F::NEG_K ) - cell.faceCenter( F::POS_K ) ).length() );
|
||||
|
||||
properties.permeability = cvf::Vec3d( 1.0, 1.0, 1.0 );
|
||||
if ( activeCellInfo && permeabilities[0] && permeabilities[1] && permeabilities[2] )
|
||||
{
|
||||
const size_t resultIndex = activeCellInfo->cellResultIndex( ReservoirCellIndex( cellIndex ) ).value();
|
||||
if ( resultIndex < permeabilities[0]->size() && resultIndex < permeabilities[1]->size() && resultIndex < permeabilities[2]->size() )
|
||||
{
|
||||
properties.permeability =
|
||||
cvf::Vec3d( ( *permeabilities[0] )[resultIndex], ( *permeabilities[1] )[resultIndex], ( *permeabilities[2] )[resultIndex] );
|
||||
}
|
||||
}
|
||||
|
||||
return properties;
|
||||
};
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -1369,9 +1546,11 @@ RigSimulationInputTool::ConnectionDirectionMap RigSimulationInputTool::connectio
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::expected<std::vector<Opm::DeckRecord>, QString> RigSimulationInputTool::processCompdatRecord( const Opm::DeckRecord& record,
|
||||
const std::string& wellName,
|
||||
const RigSimulationInputSettings& settings )
|
||||
std::expected<std::vector<Opm::DeckRecord>, QString>
|
||||
RigSimulationInputTool::processCompdatRecord( const Opm::DeckRecord& record,
|
||||
const std::string& wellName,
|
||||
const RigSimulationInputSettings& settings,
|
||||
const ConnectionCellPropertiesProvider& cellProperties )
|
||||
{
|
||||
// COMPDAT format: WELL I J K1 K2 STATE ...
|
||||
// Items: 0=WELL, 1=I, 2=J, 3=K1, 4=K2
|
||||
@@ -1439,6 +1618,7 @@ std::expected<std::vector<Opm::DeckRecord>, QString> RigSimulationInputTool::pro
|
||||
size_t k1 = 0;
|
||||
size_t k2 = 0;
|
||||
double fraction = 1.0;
|
||||
size_t sectorK = 0; // Original (sector-relative) K, used for the lateral refinement correction
|
||||
};
|
||||
|
||||
std::vector<KSpan> kSpans;
|
||||
@@ -1446,14 +1626,16 @@ std::expected<std::vector<Opm::DeckRecord>, QString> RigSimulationInputTool::pro
|
||||
{
|
||||
kSpans.push_back( { refinement.cumulativeOffset( RigRefinement::DimK, sectorK1 ),
|
||||
refinement.cumulativeOffset( RigRefinement::DimK, sectorK2 ),
|
||||
1.0 } );
|
||||
1.0,
|
||||
sectorK1 } );
|
||||
}
|
||||
else if ( dim == RigRefinement::DimK && !hasExplicitFactors )
|
||||
{
|
||||
// The simulator computes the connection factors, so the full refined range can be given in one record
|
||||
kSpans.push_back( { refinement.cumulativeOffset( RigRefinement::DimK, sectorK1 ),
|
||||
refinement.cumulativeOffset( RigRefinement::DimK, sectorK2 + 1 ) - 1,
|
||||
1.0 } );
|
||||
1.0,
|
||||
sectorK1 } );
|
||||
}
|
||||
else if ( dim == RigRefinement::DimK )
|
||||
{
|
||||
@@ -1461,7 +1643,7 @@ std::expected<std::vector<Opm::DeckRecord>, QString> RigSimulationInputTool::pro
|
||||
{
|
||||
for ( const auto& subcell : allSubcells( refinement, RigRefinement::DimK, k ) )
|
||||
{
|
||||
kSpans.push_back( { subcell.index, subcell.index, subcell.fraction } );
|
||||
kSpans.push_back( { subcell.index, subcell.index, subcell.fraction, k } );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1470,7 +1652,7 @@ std::expected<std::vector<Opm::DeckRecord>, QString> RigSimulationInputTool::pro
|
||||
for ( size_t k = sectorK1; k <= sectorK2; ++k )
|
||||
{
|
||||
const size_t centerK = centerSubcellIndex( refinement, RigRefinement::DimK, k );
|
||||
kSpans.push_back( { centerK, centerK, 1.0 } );
|
||||
kSpans.push_back( { centerK, centerK, 1.0, k } );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1483,7 +1665,34 @@ std::expected<std::vector<Opm::DeckRecord>, QString> RigSimulationInputTool::pro
|
||||
? allSubcells( refinement, RigRefinement::DimJ, sectorJ )
|
||||
: std::vector<RefinedSubcell>{ { centerSubcellIndex( refinement, RigRefinement::DimJ, sectorJ ), 1.0 } };
|
||||
|
||||
auto createRecord = [&]( size_t i, size_t j, size_t k1, size_t k2, double fraction )
|
||||
// Refinement perpendicular to the well, applies to the center sub-cell
|
||||
auto lateralCorrection = [&]( size_t sectorK ) -> std::optional<LateralRefinementCorrection>
|
||||
{
|
||||
double fraction1 = 1.0;
|
||||
double fraction2 = 1.0;
|
||||
if ( dim == RigRefinement::DimK )
|
||||
{
|
||||
fraction1 = centerSubcellFraction( refinement, RigRefinement::DimI, sectorI );
|
||||
fraction2 = centerSubcellFraction( refinement, RigRefinement::DimJ, sectorJ );
|
||||
}
|
||||
else
|
||||
{
|
||||
fraction1 = ( dim == RigRefinement::DimI ) ? centerSubcellFraction( refinement, RigRefinement::DimJ, sectorJ )
|
||||
: centerSubcellFraction( refinement, RigRefinement::DimI, sectorI );
|
||||
fraction2 = centerSubcellFraction( refinement, RigRefinement::DimK, sectorK );
|
||||
}
|
||||
if ( fraction1 >= 1.0 && fraction2 >= 1.0 ) return std::nullopt;
|
||||
|
||||
std::optional<ConnectionCellProperties> properties;
|
||||
if ( cellProperties )
|
||||
{
|
||||
properties = cellProperties( caf::VecIjk0( origI, origJ, sectorK + settings.min().z() ) );
|
||||
}
|
||||
return lateralRefinementCorrection( record, wellName, dim, fraction1, fraction2, properties );
|
||||
};
|
||||
|
||||
auto createRecord =
|
||||
[&]( size_t i, size_t j, size_t k1, size_t k2, double fraction, const std::optional<LateralRefinementCorrection>& correction )
|
||||
{
|
||||
std::vector<Opm::DeckItem> items;
|
||||
items.push_back( record.getItem( 0 ) );
|
||||
@@ -1495,8 +1704,19 @@ std::expected<std::vector<Opm::DeckRecord>, QString> RigSimulationInputTool::pro
|
||||
for ( size_t idx = 5; idx < record.size(); ++idx )
|
||||
{
|
||||
const auto& item = record.getItem( idx );
|
||||
const bool isScalable = item.name() == C::CONNECTION_TRANSMISSIBILITY_FACTOR::itemName || item.name() == C::Kh::itemName;
|
||||
if ( fraction != 1.0 && isScalable && hasExplicitDoubleValue( record, item.name() ) )
|
||||
const bool isCF = item.name() == C::CONNECTION_TRANSMISSIBILITY_FACTOR::itemName;
|
||||
const bool isScalable = isCF || item.name() == C::Kh::itemName;
|
||||
const bool isExplicit = hasExplicitDoubleValue( record, item.name() );
|
||||
if ( isCF && isExplicit && correction )
|
||||
{
|
||||
items.push_back(
|
||||
RifOpmDeckTools::item( item.name(), item.get<double>( 0 ) * fraction * correction->connectionFactorMultiplier ) );
|
||||
}
|
||||
else if ( item.name() == C::PR::itemName && isExplicit && correction )
|
||||
{
|
||||
items.push_back( RifOpmDeckTools::item( item.name(), correction->pressureEquivalentRadius ) );
|
||||
}
|
||||
else if ( fraction != 1.0 && isScalable && isExplicit )
|
||||
{
|
||||
items.push_back( RifOpmDeckTools::item( item.name(), item.get<double>( 0 ) * fraction ) );
|
||||
}
|
||||
@@ -1512,12 +1732,17 @@ std::expected<std::vector<Opm::DeckRecord>, QString> RigSimulationInputTool::pro
|
||||
std::vector<Opm::DeckRecord> records;
|
||||
for ( const auto& kSpan : kSpans )
|
||||
{
|
||||
const auto correction = lateralCorrection( kSpan.sectorK );
|
||||
for ( const auto& iSubcell : iSubcells )
|
||||
{
|
||||
for ( const auto& jSubcell : jSubcells )
|
||||
{
|
||||
records.push_back(
|
||||
createRecord( iSubcell.index, jSubcell.index, kSpan.k1, kSpan.k2, kSpan.fraction * iSubcell.fraction * jSubcell.fraction ) );
|
||||
records.push_back( createRecord( iSubcell.index,
|
||||
jSubcell.index,
|
||||
kSpan.k1,
|
||||
kSpan.k2,
|
||||
kSpan.fraction * iSubcell.fraction * jSubcell.fraction,
|
||||
correction ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2350,7 +2575,8 @@ std::expected<void, QString> RigSimulationInputTool::updateWellListKeywords( std
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::expected<void, QString> RigSimulationInputTool::filterAndUpdateWellKeywords( std::set<std::string>& validWellNames,
|
||||
std::expected<void, QString> RigSimulationInputTool::filterAndUpdateWellKeywords( RimEclipseCase* eclipseCase,
|
||||
std::set<std::string>& validWellNames,
|
||||
const RigSimulationInputSettings& settings,
|
||||
RifOpmFlowDeckFile& deckFile )
|
||||
{
|
||||
@@ -2364,6 +2590,8 @@ std::expected<void, QString> RigSimulationInputTool::filterAndUpdateWellKeywords
|
||||
// Collect connection directions from the original COMPDAT before it is transformed, used to split COMPSEGS
|
||||
const auto directions = connectionDirections( deckFile.findAllKeywords( "COMPDAT" ) );
|
||||
|
||||
const auto cellProperties = connectionCellPropertiesProvider( eclipseCase );
|
||||
|
||||
// Process each type of well keyword
|
||||
// Use findAllKeywordsWithIndices to get all occurrences with their positions
|
||||
for ( const auto& keywordName : wellKeywords )
|
||||
@@ -2441,7 +2669,7 @@ std::expected<void, QString> RigSimulationInputTool::filterAndUpdateWellKeywords
|
||||
}
|
||||
else if ( keywordName == "COMPDAT" )
|
||||
{
|
||||
auto result = processCompdatRecord( record, wellName, settings );
|
||||
auto result = processCompdatRecord( record, wellName, settings, cellProperties );
|
||||
if ( result )
|
||||
{
|
||||
for ( auto& rec : result.value() )
|
||||
|
||||
@@ -22,12 +22,14 @@
|
||||
|
||||
#include "cafVecIjk.h"
|
||||
#include "cvfArray.h"
|
||||
#include "cvfVector3.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include <expected>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
@@ -108,10 +110,24 @@ public:
|
||||
|
||||
static ConnectionDirectionMap connectionDirections( const std::vector<Opm::DeckKeyword>& compdatKeywords );
|
||||
|
||||
// Size and permeability of an original grid cell, ordered I/X, J/Y, K/Z
|
||||
struct ConnectionCellProperties
|
||||
{
|
||||
cvf::Vec3d cellSize;
|
||||
cvf::Vec3d permeability;
|
||||
};
|
||||
using ConnectionCellPropertiesProvider = std::function<std::optional<ConnectionCellProperties>( const caf::VecIjk0& originalIjk )>;
|
||||
|
||||
static ConnectionCellPropertiesProvider connectionCellPropertiesProvider( RimEclipseCase* eclipseCase );
|
||||
|
||||
// Refined connections along the penetration direction are split into one record per sub-cell with
|
||||
// explicit CF and KH scaled by the sub-cell length fraction. Perpendicular directions use the center sub-cell.
|
||||
// explicit CF and KH scaled by the sub-cell length fraction. Perpendicular directions use the center sub-cell,
|
||||
// and explicit CF is corrected for the smaller Peaceman pressure equivalent radius of the refined cell.
|
||||
static std::expected<std::vector<Opm::DeckRecord>, QString>
|
||||
processCompdatRecord( const Opm::DeckRecord& record, const std::string& wellName, const RigSimulationInputSettings& settings );
|
||||
processCompdatRecord( const Opm::DeckRecord& record,
|
||||
const std::string& wellName,
|
||||
const RigSimulationInputSettings& settings,
|
||||
const ConnectionCellPropertiesProvider& cellProperties = {} );
|
||||
|
||||
// Segment records are split into one record per sub-cell along the connection penetration direction,
|
||||
// with the MD interval divided by the sub-cell length fractions
|
||||
@@ -183,7 +199,8 @@ private:
|
||||
static std::expected<void, QString>
|
||||
addFaultsToDeckFile( RimEclipseCase* eclipseCase, const RigSimulationInputSettings& settings, RifOpmFlowDeckFile& deckFile );
|
||||
|
||||
static std::expected<void, QString> filterAndUpdateWellKeywords( std::set<std::string>& validWellNames,
|
||||
static std::expected<void, QString> filterAndUpdateWellKeywords( RimEclipseCase* eclipseCase,
|
||||
std::set<std::string>& validWellNames,
|
||||
const RigSimulationInputSettings& settings,
|
||||
RifOpmFlowDeckFile& deckFile );
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "RigNonUniformRefinement.h"
|
||||
#include "RigSimulationInputSettings.h"
|
||||
#include "RigSimulationInputTool.h"
|
||||
#include "RigTransmissibilityEquations.h"
|
||||
#include "RigUniformRefinement.h"
|
||||
#include "RimEclipseResultCase.h"
|
||||
|
||||
@@ -46,6 +47,7 @@
|
||||
#include <QString>
|
||||
#include <QTemporaryDir>
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
@@ -3220,3 +3222,64 @@ TEST( RigSimulationInputTool, ProcessCompsegsRecord_SplitAcrossKSubcells )
|
||||
EXPECT_DOUBLE_EQ( 110.0, result->at( 1 ).getItem( 4 ).get<double>( 0 ) );
|
||||
EXPECT_DOUBLE_EQ( 120.0, result->at( 1 ).getItem( 5 ).get<double>( 0 ) );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Refinement perpendicular to a Z well reduces the Peaceman radius given by PR, so CF increases
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RigSimulationInputTool, ProcessCompdatRecord_LateralRefinementCorrectsCFUsingPR )
|
||||
{
|
||||
auto settings = createSectorSettings( cvf::Vec3st( 10, 10, 1 ) );
|
||||
auto records = parseKeywordRecords( "COMPDAT\n 'W' 3 4 2 2 'OPEN' 1* 10.0 0.216 100.0 1.0 1* 'Z' 20.0 /\n/\n", "COMPDAT" );
|
||||
|
||||
auto result = RigSimulationInputTool::processCompdatRecord( records[0], "W", settings );
|
||||
ASSERT_TRUE( result.has_value() ) << result.error().toStdString();
|
||||
ASSERT_EQ( 1u, result->size() );
|
||||
|
||||
const auto& rec = result->at( 0 );
|
||||
EXPECT_EQ( 26, rec.getItem( "I" ).get<int>( 0 ) );
|
||||
EXPECT_EQ( 36, rec.getItem( "J" ).get<int>( 0 ) );
|
||||
|
||||
const double rw = 0.108;
|
||||
const double expected = 10.0 * ( std::log( 20.0 / rw ) + 1.0 ) / ( std::log( 2.0 / rw ) + 1.0 );
|
||||
EXPECT_NEAR( expected, rec.getItem( "CONNECTION_TRANSMISSIBILITY_FACTOR" ).get<double>( 0 ), 1e-9 );
|
||||
EXPECT_NEAR( 2.0, rec.getItem( "PR" ).get<double>( 0 ), 1e-9 );
|
||||
EXPECT_DOUBLE_EQ( 100.0, rec.getItem( "Kh" ).get<double>( 0 ) );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Without PR the Peaceman radii are computed from the cell sizes and permeabilities, also for
|
||||
/// refinement in only one of the perpendicular directions
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RigSimulationInputTool, ProcessCompdatRecord_LateralRefinementCorrectsCFUsingCellProperties )
|
||||
{
|
||||
auto settings = createSectorSettings( cvf::Vec3st( 4, 1, 1 ) );
|
||||
auto records = parseKeywordRecords( "COMPDAT\n 'W' 3 4 2 2 'OPEN' 1* 10.0 0.216 /\n/\n", "COMPDAT" );
|
||||
|
||||
RigSimulationInputTool::ConnectionCellProperties properties{ cvf::Vec3d( 100.0, 50.0, 5.0 ), cvf::Vec3d( 200.0, 50.0, 10.0 ) };
|
||||
auto provider = [&]( const caf::VecIjk0& ) -> std::optional<RigSimulationInputTool::ConnectionCellProperties> { return properties; };
|
||||
|
||||
auto result = RigSimulationInputTool::processCompdatRecord( records[0], "W", settings, provider );
|
||||
ASSERT_TRUE( result.has_value() ) << result.error().toStdString();
|
||||
ASSERT_EQ( 1u, result->size() );
|
||||
|
||||
const double rw = 0.108;
|
||||
const double r0Coarse = RigTransmissibilityEquations::peacemanRadius( 200.0, 50.0, 100.0, 50.0 );
|
||||
const double r0Fine = RigTransmissibilityEquations::peacemanRadius( 200.0, 50.0, 25.0, 50.0 );
|
||||
const double expected = 10.0 * std::log( r0Coarse / rw ) / std::log( r0Fine / rw );
|
||||
EXPECT_NEAR( expected, result->at( 0 ).getItem( "CONNECTION_TRANSMISSIBILITY_FACTOR" ).get<double>( 0 ), 1e-9 );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// The correction is skipped when the refined Peaceman radius gets smaller than the well radius
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RigSimulationInputTool, ProcessCompdatRecord_LateralRefinementTooSmallRadiusKeepsCF )
|
||||
{
|
||||
auto settings = createSectorSettings( cvf::Vec3st( 10, 10, 1 ) );
|
||||
auto records = parseKeywordRecords( "COMPDAT\n 'W' 3 4 2 2 'OPEN' 1* 10.0 0.216 4* 0.5 /\n/\n", "COMPDAT" );
|
||||
|
||||
auto result = RigSimulationInputTool::processCompdatRecord( records[0], "W", settings );
|
||||
ASSERT_TRUE( result.has_value() ) << result.error().toStdString();
|
||||
ASSERT_EQ( 1u, result->size() );
|
||||
EXPECT_DOUBLE_EQ( 10.0, result->at( 0 ).getItem( "CONNECTION_TRANSMISSIBILITY_FACTOR" ).get<double>( 0 ) );
|
||||
EXPECT_DOUBLE_EQ( 0.5, result->at( 0 ).getItem( "PR" ).get<double>( 0 ) );
|
||||
}
|
||||
Reference in new issue
Block a user