diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.cpp b/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.cpp index f729e95351..7c56d55229 100644 --- a/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.cpp +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.cpp @@ -66,6 +66,7 @@ #include #include +#include #include #include #include @@ -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::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::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> 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 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() ); diff --git a/GrpcInterface/Python/rips/tests/test_completion_export.py b/GrpcInterface/Python/rips/tests/test_completion_export.py index ee8b7c1338..c8f1743281 100644 --- a/GrpcInterface/Python/rips/tests/test_completion_export.py +++ b/GrpcInterface/Python/rips/tests/test_completion_export.py @@ -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" diff --git a/TestModels/TEST10K_FLT_LGR_NNC/lgr_twice/wellpath_lgr_twice.dev b/TestModels/TEST10K_FLT_LGR_NNC/lgr_twice/wellpath_lgr_twice.dev new file mode 100644 index 0000000000..9edd9d6a51 --- /dev/null +++ b/TestModels/TEST10K_FLT_LGR_NNC/lgr_twice/wellpath_lgr_twice.dev @@ -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