#13475: Unified LGR completion export tables and single WELSPECL entry per well

* Export LGR completions in single COMPDATL, WPIMULTL and COMPLMPL tables
* Export only first WELSPECL entry per well
* Move LGR test well path to subfolder and format test
This commit is contained in:
Magne Sjaastad authored and GitHub committed 2026-10-15 15:25:49 +02:00
1 parent 1d7d0eb763
commit c323846ec5
3 files changed
+117 -21

No files matched your search

@@ -66,6 +66,7 @@
#include <QDir>
#include <algorithm>
#include <limits>
#include <map>
#include <optional>
#include <set>
@@ -769,30 +770,47 @@ void RicWellPathExportCompletionDataFeatureImpl::exportWelspeclToFile( RimEclips
// See similar logic in RiaGrpcWellPathService::GetCompletionData()
// A well must be defined only once. Use the LGR the well path enters first.
for ( const auto& [wellPath, gridNames] : wellPathToLgrNameMap )
{
QString firstLgrName;
double firstMeasuredDepth = std::numeric_limits<double>::max();
cvf::Vec2i firstIJ;
bool isFound = false;
for ( const auto& lgrName : gridNames )
{
const auto& [measuredDepth, ijIntersection] = wellPathUpperGridIntersectionIJ( gridCase, wellPath, lgrName );
auto completionSettings = wellPath->completionSettings();
formatter.add( completionSettings->wellNameForExport() )
.add( completionSettings->groupNameForExport() )
.add( lgrName )
.add( ijIntersection.x() + 1 )
.add( ijIntersection.y() + 1 )
.add( completionSettings->referenceDepthForExport() )
.add( completionSettings->wellTypeNameForExport() )
.add( completionSettings->drainageRadiusForExport() )
.add( completionSettings->gasInflowEquationForExport() )
.add( completionSettings->automaticWellShutInForExport() )
.add( completionSettings->allowWellCrossFlowForExport() )
.add( completionSettings->wellBoreFluidPVTForExport() )
.add( completionSettings->hydrostaticDensityForExport() )
.add( completionSettings->fluidInPlaceRegionForExport() )
.rowCompleted();
const bool isValid = measuredDepth != cvf::UNDEFINED_DOUBLE;
if ( !isFound || ( isValid && measuredDepth < firstMeasuredDepth ) )
{
firstLgrName = lgrName;
firstMeasuredDepth = isValid ? measuredDepth : std::numeric_limits<double>::max();
firstIJ = ijIntersection;
isFound = true;
}
}
if ( !isFound ) continue;
auto completionSettings = wellPath->completionSettings();
formatter.add( completionSettings->wellNameForExport() )
.add( completionSettings->groupNameForExport() )
.add( firstLgrName )
.add( firstIJ.x() + 1 )
.add( firstIJ.y() + 1 )
.add( completionSettings->referenceDepthForExport() )
.add( completionSettings->wellTypeNameForExport() )
.add( completionSettings->drainageRadiusForExport() )
.add( completionSettings->gasInflowEquationForExport() )
.add( completionSettings->automaticWellShutInForExport() )
.add( completionSettings->allowWellCrossFlowForExport() )
.add( completionSettings->wellBoreFluidPVTForExport() )
.add( completionSettings->hydrostaticDensityForExport() )
.add( completionSettings->fluidInPlaceRegionForExport() )
.rowCompleted();
}
formatter.tableCompleted();
}
@@ -872,7 +890,15 @@ void RicWellPathExportCompletionDataFeatureImpl::exportCompdatAndWpimultTables(
formatter.setColumnSpacing( 3 );
formatter.setOptionalComment( exportDataSourceAsComment );
for ( const auto& gridCompletions : completionsPerGrid )
// All LGRs share one set of tables (COMPDATL, WPIMULTL, COMPLMPL) where each row carries its own LGR name
std::map<QString, std::vector<RigCompletionData>> completionsPerTable;
for ( const auto& [gridName, gridCompletions] : completionsPerGrid )
{
auto& tableCompletions = completionsPerTable[gridName.isEmpty() ? QString() : QString( "LGR" )];
tableCompletions.insert( tableCompletions.end(), gridCompletions.begin(), gridCompletions.end() );
}
for ( const auto& gridCompletions : completionsPerTable )
{
std::vector<RigCompletionData> completions = gridCompletions.second;
@@ -983,7 +1009,7 @@ void RicWellPathExportCompletionDataFeatureImpl::exportCompdatTableUsingFormatte
if ( !gridName.isEmpty() )
{
formatter.add( gridName );
formatter.add( data.completionDataGridCell().lgrName() );
}
formatter.add( data.completionDataGridCell().localCellIndexI() + 1 )
@@ -1064,7 +1090,7 @@ void RicWellPathExportCompletionDataFeatureImpl::exportComplumpTableUsingFormatt
if ( !gridName.isEmpty() )
{
formatter.add( gridName );
formatter.add( data.completionDataGridCell().lgrName() );
}
formatter.add( data.completionDataGridCell().localCellIndexI() + 1 )
@@ -1123,7 +1149,7 @@ void RicWellPathExportCompletionDataFeatureImpl::exportWpimultTableUsingFormatte
if ( !gridName.isEmpty() )
{
formatter.add( gridName );
formatter.add( completion.completionDataGridCell().lgrName() );
}
formatter.add( completion.wpimult() );
@@ -164,6 +164,70 @@ def test_export_completion_files_split_by_well(rips_instance, initialize_test):
pass # Keep exported files in the output directory
def test_export_completion_well_entering_lgr_twice(rips_instance, initialize_test):
"""A well going into the LGR, out of it, and into it again must give one
WELSPECL and one COMPDATL keyword, with rows for both perforations."""
case_root_path = dataroot.PATH + "/TEST10K_FLT_LGR_NNC"
case = rips_instance.project.load_case(
path=case_root_path + "/TEST10K_FLT_LGR_NNC.EGRID"
)
assert len(case.grids()) == 2
rips_instance.project.import_well_paths(
[case_root_path + "/lgr_twice/wellpath_lgr_twice.dev"]
)
well_path = rips_instance.project.well_path_by_name("LgrWell")
assert well_path is not None
# The well path leaves the LGR between MD 300 and MD 500
well_path.append_perforation_interval(
start_md=100.0, end_md=290.0, diameter=0.2, skin_factor=0.0
)
well_path.append_perforation_interval(
start_md=560.0, end_md=740.0, diameter=0.2, skin_factor=0.0
)
export_folder = os.path.abspath(
case_root_path + "/completion_export_output/lgr_twice"
)
os.makedirs(export_folder, exist_ok=True)
for old_file in os.listdir(export_folder):
os.remove(os.path.join(export_folder, old_file))
rips_instance.set_export_folder(export_type="COMPLETIONS", path=export_folder)
case.export_well_path_completions(
time_step=0,
well_path_names=["LgrWell"],
file_split="UNIFIED_FILE",
include_perforations=True,
include_fishbones=False,
export_welspec=True,
export_comments=False,
)
lgr_files = [
f for f in os.listdir(export_folder) if f.endswith("_LGR") and "_MSW_" not in f
]
assert len(lgr_files) == 1
with open(os.path.join(export_folder, lgr_files[0]), "r") as f:
lines = [line.strip() for line in f.read().split("\n")]
assert lines.count("WELSPECL") == 1
assert lines.count("COMPDATL") == 1
# Both perforations give connections in the LGR, on both sides of the part outside the LGR
compdatl_start = lines.index("COMPDATL")
compdatl_end = lines.index("/", compdatl_start)
rows = [
line
for line in lines[compdatl_start:compdatl_end]
if line.startswith("LgrWell")
]
assert len(rows) > 2
assert all(row.split()[1] == "LGR1" for row in rows)
def test_export_completion_dual_porosity_model(rips_instance, initialize_test):
case_root_path = dataroot.PATH + "/dualporo-testcase"
project_path = case_root_path + "/Well-completions-mail.rsp"
@@ -0,0 +1,6 @@
name LgrWell
5300 6250 4050 0
5300 6250 4200 150
5150 6250 4200 300
5150 6450 4200 500
5400 6450 4200 750