diff --git a/ApplicationLibCode/UnitTests/RiaGrpcWellPathService-Test.cpp b/ApplicationLibCode/UnitTests/RiaGrpcWellPathService-Test.cpp index bf2319650b..8c587b8a9f 100644 --- a/ApplicationLibCode/UnitTests/RiaGrpcWellPathService-Test.cpp +++ b/ApplicationLibCode/UnitTests/RiaGrpcWellPathService-Test.cpp @@ -222,11 +222,12 @@ TEST( RiaGrpcWellPathServiceTest, copyWelspecsToGrpcCopiesAllFields ) rips::SimulatorWelspecsEntry grpcData; // Test coordinates - int gridI = 10; - int gridJ = 20; + int gridI = 10; + int gridJ = 20; + std::string gridName = ""; // Call the function under test - RiaWellPathDataToGrpcConverter::copyWelspecsToGrpc( &compSettings, &grpcData, nullptr, gridI, gridJ ); + RiaWellPathDataToGrpcConverter::copyWelspecsToGrpc( &compSettings, &grpcData, gridI, gridJ, gridName ); // Verify basic fields are copied EXPECT_EQ( grpcData.well_name(), "TEST_WELL" ); diff --git a/GrpcInterface/GrpcProtos/SimulatorTables.proto b/GrpcInterface/GrpcProtos/SimulatorTables.proto index 001dd17b67..11cc8874ee 100644 --- a/GrpcInterface/GrpcProtos/SimulatorTables.proto +++ b/GrpcInterface/GrpcProtos/SimulatorTables.proto @@ -38,6 +38,7 @@ message SimulatorCompdatEntry { optional double start_md = 14; optional double end_md = 15; optional string comment = 16; + optional string grid_name = 17; } message SimulatorWelspecsEntry { @@ -54,6 +55,7 @@ message SimulatorWelspecsEntry { optional int32 pvt_num = 11; optional string hydrostatic_density_calc = 12; optional int32 fip_region = 13; + optional string grid_name = 14; } message SimulatorWelsegsHeaderEntry { @@ -131,6 +133,15 @@ message SimulatorWsegaicdEntry { optional string description = 22; } +message SimulatorWpimultEntry { + string well_name = 1; + double pimult = 2; + int32 i = 3; + int32 j = 4; + int32 k = 5; + optional string grid_name = 6; +} + message SimulatorTableData { repeated SimulatorCompdatEntry compdat = 1; repeated SimulatorWelspecsEntry welspecs = 2; @@ -138,5 +149,6 @@ message SimulatorTableData { repeated SimulatorCompsegsEntry compsegs = 4; repeated SimulatorWsegvalvEntry wsegvalv = 5; repeated SimulatorWsegaicdEntry wsegaicd = 6; + repeated SimulatorWpimultEntry wpimult = 7; } diff --git a/GrpcInterface/Python/rips/tests/reference_data/README.md b/GrpcInterface/Python/rips/tests/reference_data/README.md new file mode 100644 index 0000000000..fea2281848 --- /dev/null +++ b/GrpcInterface/Python/rips/tests/reference_data/README.md @@ -0,0 +1,95 @@ +# Reference Data for Completion Tests + +This directory contains reference JSON files used for regression testing of the ResInsight Python gRPC API. + +## completion_data_reference.json + +Reference data for testing the `GetCompletionData` gRPC service method. Contains expected COMPDAT and WELSPECS table entries for a well with fixed completion parameters: + +- **Well**: Imported from `wellpath_a.dev` (TEST10K dataset) +- **Start MD**: 2200.0m +- **End MD**: 2600.0m +- **Interval Length**: 400m +- **Diameter**: 0.25m +- **Skin Factor**: 0.1 +- **Grid**: TEST10K_FLT_LGR_NNC + +### Test Coverage + +The 400m perforation interval generates **6 COMPDAT entries** covering: +- Multiple grid cells: (39,54), (39,55), (39,56) +- Multiple K layers: 15-18 +- Various completion directions: Y, Z +- Different transmissibility and KH values + +### Usage + +The reference file is automatically used by `test_completion_data_regression_reference()` to validate that completion data generation remains consistent across code changes. + +```python +# Test automatically compares current results against reference +pytest test_get_completion_data.py::test_completion_data_regression_reference +``` + +### Updating Reference Data + +If intentional changes are made to completion data generation: + +1. Delete the reference file: `rm completion_data_reference.json` +2. Run the test to regenerate: `pytest test_get_completion_data.py::test_completion_data_regression_reference` +3. Review the new reference data for correctness +4. Commit the new reference file to git + +### Cross-Platform Compatibility + +The tests are designed to produce identical results on Windows and Linux: + +- **File Paths**: Uses `pathlib.Path` for cross-platform file paths +- **JSON Encoding**: UTF-8 encoding with `ensure_ascii=True` for consistent serialization +- **Field Ordering**: Alphabetical field ordering in dictionaries +- **Number Formatting**: Explicit `int()` and `float()` conversion with consistent rounding (6-8 decimal places) +- **JSON Serialization**: Fixed separators `(',', ':')` to eliminate whitespace variations +- **Hash Stability**: SHA256 with UTF-8 encoding for reproducible hashes across platforms +- **Data Types**: Explicit type casting to avoid platform-specific numeric representations + +### Reference Data Structure + +```json +{ + "compdat": [ + { + "comment": "Perforation Completion: MD In: 2274.13 - MD Out: 2290.66 ...", + "diameter": 0.25, + "direction": "Z", + "end_md": 2290.660636, + "grid_i": 39, + "grid_j": 54, + "kh": 8.39888987, + "lower_k": 16, + "open_shut_flag": "OPEN", + "skin_factor": 0.1, + "start_md": 2274.130017, + "transmissibility": 0.09835443, + "upper_k": 16, + "well_name": "ReferenceTestWell" + }, + // ... 5 more COMPDAT entries + ], + "welspecs": [ + { + "well_name": "ReferenceTestWell", + "group_name": "1*", + "grid_i": 29, + "grid_j": 41, + "phase": "OIL", + // ... other WELSPECS fields + } + ] +} +``` + +## Maintenance + +- Reference data should be updated whenever the completion data generation algorithm changes intentionally +- Failed regression tests indicate unexpected changes in completion data generation +- The test includes validation that modified data properly fails comparison (failure simulation) \ No newline at end of file diff --git a/GrpcInterface/Python/rips/tests/reference_data/completion_data_reference.json b/GrpcInterface/Python/rips/tests/reference_data/completion_data_reference.json new file mode 100644 index 0000000000..446c465b57 --- /dev/null +++ b/GrpcInterface/Python/rips/tests/reference_data/completion_data_reference.json @@ -0,0 +1,122 @@ +{ + "compdat": [ + { + "comment": "Perforation Completion: MD In: 2274.13 - MD Out: 2290.66 Transmissibility: 0.0983544", + "diameter": 0.25, + "direction": "Z", + "end_md": 2290.660636, + "grid_i": 39, + "grid_j": 54, + "grid_name": "", + "kh": 8.39888987, + "lower_k": 16, + "open_shut_flag": "OPEN", + "skin_factor": 0.1, + "start_md": 2274.130017, + "transmissibility": 0.09835443, + "upper_k": 16, + "well_name": "ReferenceTestWell" + }, + { + "comment": "Perforation Completion: MD In: 2240.91 - MD Out: 2274.13 Transmissibility: 0.993938", + "diameter": 0.25, + "direction": "Z", + "end_md": 2274.130017, + "grid_i": 39, + "grid_j": 54, + "grid_name": "", + "kh": 84.97793886, + "lower_k": 17, + "open_shut_flag": "OPEN", + "skin_factor": 0.1, + "start_md": 2240.908558, + "transmissibility": 0.9939382, + "upper_k": 17, + "well_name": "ReferenceTestWell" + }, + { + "comment": "Perforation Completion: MD In: 2200 - MD Out: 2240.91 Transmissibility: 0.0863079", + "diameter": 0.25, + "direction": "Z", + "end_md": 2240.908558, + "grid_i": 39, + "grid_j": 54, + "grid_name": "", + "kh": 7.38157699, + "lower_k": 18, + "open_shut_flag": "OPEN", + "skin_factor": 0.1, + "start_md": 2200.0, + "transmissibility": 0.08630795, + "upper_k": 18, + "well_name": "ReferenceTestWell" + }, + { + "comment": "Perforation Completion: MD In: 2341.04 - MD Out: 2391.47 Transmissibility: 0.00885377", + "diameter": 0.25, + "direction": "Z", + "end_md": 2391.469453, + "grid_i": 39, + "grid_j": 55, + "grid_name": "", + "kh": 0.51920334, + "lower_k": 15, + "open_shut_flag": "OPEN", + "skin_factor": 0.1, + "start_md": 2341.037598, + "transmissibility": 0.00885377, + "upper_k": 15, + "well_name": "ReferenceTestWell" + }, + { + "comment": "Perforation Completion: MD In: 2290.66 - MD Out: 2341.04 Transmissibility: 0.104071", + "diameter": 0.25, + "direction": "Z", + "end_md": 2341.037598, + "grid_i": 39, + "grid_j": 55, + "grid_name": "", + "kh": 8.88321679, + "lower_k": 16, + "open_shut_flag": "OPEN", + "skin_factor": 0.1, + "start_md": 2290.660636, + "transmissibility": 0.10407092, + "upper_k": 16, + "well_name": "ReferenceTestWell" + }, + { + "comment": "Perforation Completion: MD In: 2391.47 - MD Out: 2600 Transmissibility: 0.0132193", + "diameter": 0.25, + "direction": "Y", + "end_md": 2600.0, + "grid_i": 39, + "grid_j": 56, + "grid_name": "", + "kh": 0.76175274, + "lower_k": 15, + "open_shut_flag": "OPEN", + "skin_factor": 0.1, + "start_md": 2391.469453, + "transmissibility": 0.01321926, + "upper_k": 15, + "well_name": "ReferenceTestWell" + } + ], + "welspecs": [ + { + "auto_shut_in": "STOP", + "cross_flow": "YES", + "fip_region": 0, + "grid_i": 29, + "grid_j": 41, + "grid_name": "", + "group_name": "1*", + "hydrostatic_density_calc": "SEG", + "inflow_equation": "STD", + "phase": "OIL", + "pvt_num": 0, + "well_name": "ReferenceTestWell" + } + ] +} \ No newline at end of file diff --git a/GrpcInterface/Python/rips/tests/test_get_completion_data.py b/GrpcInterface/Python/rips/tests/test_get_completion_data.py new file mode 100644 index 0000000000..0e17cad8ac --- /dev/null +++ b/GrpcInterface/Python/rips/tests/test_get_completion_data.py @@ -0,0 +1,787 @@ +import sys +import os +import json +import hashlib +from pathlib import Path + +sys.path.insert(1, os.path.join(sys.path[0], "../../")) +import rips + +import dataroot + +import pytest + + +def test_get_completion_data_basic(rips_instance, initialize_test): + """Test GetCompletionData with a well path and completions""" + # Load a grid case and import existing well path + case_root_path = dataroot.PATH + "/TEST10K_FLT_LGR_NNC" + case_path = case_root_path + "/TEST10K_FLT_LGR_NNC.EGRID" + case = rips_instance.project.load_case(path=case_path) + assert len(case.grids()) == 2 + + # Import existing well path that intersects the grid properly + well_path_file = case_root_path + "/wellpath_a.dev" + well_path_names = rips_instance.project.import_well_paths([well_path_file]) + assert len(well_path_names) == 1 + + wells = rips_instance.project.well_paths() + assert len(wells) == 1 + well_path = wells[0] + well_path.name = "TestWell" # Rename for our test + well_path.update() + + # Get the trajectory properties to find valid MD range + result = well_path.trajectory_properties(resampling_interval=10.0) + measured_depths = result["measured_depth"] + + # Use measured depth range from the actual well trajectory + start_md = measured_depths[len(measured_depths) // 2] # Middle of well + end_md = ( + measured_depths[len(measured_depths) // 2 + 10] + if len(measured_depths) > 20 + else measured_depths[-1] + ) + + # Add perforation intervals (completions) + perf_interval = well_path.append_perforation_interval( + start_md=start_md, end_md=end_md, diameter=0.25, skin_factor=0.1 + ) + assert perf_interval is not None + + # Test the completion_data method + completion_data = well_path.completion_data(case.id) + + # Verify basic structure exists + assert completion_data is not None + + # Should have WELSPECS data for the well + assert len(completion_data.welspecs) > 0 + welspec = completion_data.welspecs[0] + assert welspec.well_name == "TestWell" + assert welspec.grid_i > 0 + assert welspec.grid_j > 0 + + # Should have COMPDAT data for the perforation intervals + assert len(completion_data.compdat) > 0 + compdat = completion_data.compdat[0] + assert compdat.well_name == "TestWell" + assert compdat.grid_i > 0 + assert compdat.grid_j > 0 + assert compdat.upper_k > 0 + assert compdat.lower_k > 0 + assert compdat.open_shut_flag in ["OPEN", "SHUT"] + + # Check measured depth information in COMPDAT + assert compdat.HasField("start_md") + assert compdat.HasField("end_md") + assert compdat.start_md >= start_md - 10 # Allow some tolerance + assert compdat.end_md <= end_md + 10 + + +def test_get_completion_data_with_valves(rips_instance, initialize_test): + """Test GetCompletionData with valves/ICDs""" + # Load a grid case and import well path + case_root_path = dataroot.PATH + "/TEST10K_FLT_LGR_NNC" + case_path = case_root_path + "/TEST10K_FLT_LGR_NNC.EGRID" + case = rips_instance.project.load_case(path=case_path) + + # Import existing well path + well_path_file = case_root_path + "/wellpath_b.dev" + well_path_names = rips_instance.project.import_well_paths([well_path_file]) + assert len(well_path_names) == 1 + + wells = rips_instance.project.well_paths() + well_path = wells[0] + well_path.name = "TestWellWithValves" + well_path.update() + + # Get trajectory properties for valid MD range + result = well_path.trajectory_properties(resampling_interval=10.0) + measured_depths = result["measured_depth"] + start_md = measured_depths[len(measured_depths) // 2] + end_md = ( + measured_depths[len(measured_depths) // 2 + 10] + if len(measured_depths) > 20 + else measured_depths[-1] + ) + + # Add perforation interval with valve + perf_interval = well_path.append_perforation_interval( + start_md=start_md, end_md=end_md, diameter=0.25, skin_factor=0.1 + ) + + # Add a valve to the perforation interval + valve_templates = rips_instance.project.valve_templates() + valve_defs = valve_templates.valve_definitions() + assert len(valve_defs) >= 1 + + valve_start_md = start_md + (end_md - start_md) * 0.2 + valve_end_md = start_md + (end_md - start_md) * 0.8 + valve = perf_interval.add_valve( + template=valve_defs[0], + start_md=valve_start_md, + end_md=valve_end_md, + valve_count=2, + ) + assert valve is not None + + # Test completion data + completion_data = well_path.completion_data(case.id) + assert completion_data is not None + + # Should have WELSPECS + assert len(completion_data.welspecs) > 0 + welspec = completion_data.welspecs[0] + assert welspec.well_name == "TestWellWithValves" + + # Should have COMPDAT + assert len(completion_data.compdat) > 0 + + +def test_get_completion_data_multiple_intervals(rips_instance, initialize_test): + """Test GetCompletionData with multiple perforation intervals""" + # Load a grid case and import well path + case_root_path = dataroot.PATH + "/TEST10K_FLT_LGR_NNC" + case_path = case_root_path + "/TEST10K_FLT_LGR_NNC.EGRID" + case = rips_instance.project.load_case(path=case_path) + + # Import existing well path + well_path_file = case_root_path + "/wellpath_a.dev" + well_path_names = rips_instance.project.import_well_paths([well_path_file]) + wells = rips_instance.project.well_paths() + well_path = wells[0] + well_path.name = "TestMultiInterval" + well_path.update() + + # Get trajectory properties for valid MD ranges + result = well_path.trajectory_properties(resampling_interval=10.0) + measured_depths = result["measured_depth"] + + # Create three intervals along the well path + total_length = len(measured_depths) + if total_length >= 30: + # Interval 1: Early part of well + start_md1 = measured_depths[total_length // 4] + end_md1 = measured_depths[total_length // 4 + 5] + + # Interval 2: Middle part of well + start_md2 = measured_depths[total_length // 2] + end_md2 = measured_depths[total_length // 2 + 5] + + # Interval 3: Later part of well + start_md3 = measured_depths[3 * total_length // 4] + end_md3 = measured_depths[3 * total_length // 4 + 5] + + # Add multiple perforation intervals + interval1 = well_path.append_perforation_interval( + start_md=start_md1, end_md=end_md1, diameter=0.2, skin_factor=0.0 + ) + interval2 = well_path.append_perforation_interval( + start_md=start_md2, end_md=end_md2, diameter=0.25, skin_factor=0.1 + ) + interval3 = well_path.append_perforation_interval( + start_md=start_md3, end_md=end_md3, diameter=0.3, skin_factor=0.2 + ) + + assert interval1 is not None + assert interval2 is not None + assert interval3 is not None + + # Test completion data + completion_data = well_path.completion_data(case.id) + assert completion_data is not None + + # Should have WELSPECS + assert len(completion_data.welspecs) > 0 + welspec = completion_data.welspecs[0] + assert welspec.well_name == "TestMultiInterval" + + # Should have multiple COMPDAT entries for different intervals + assert len(completion_data.compdat) >= 1 # At least some COMPDAT data + + # Check that we have MD information in COMPDAT entries + has_md_data = False + for compdat in completion_data.compdat: + if compdat.HasField("start_md") and compdat.HasField("end_md"): + has_md_data = True + assert compdat.start_md >= 0 + assert compdat.end_md >= compdat.start_md + + assert has_md_data, "Should have measured depth data in COMPDAT" + else: + # Skip test if well is too short for multiple intervals + assert True # Test passes if well is too short + + +def test_get_completion_data_field_validation(rips_instance, initialize_test): + """Test optional field handling in completion data""" + # Load a grid case and import well path + case_root_path = dataroot.PATH + "/TEST10K_FLT_LGR_NNC" + case_path = case_root_path + "/TEST10K_FLT_LGR_NNC.EGRID" + case = rips_instance.project.load_case(path=case_path) + + # Import existing well path + well_path_file = case_root_path + "/wellpath_a.dev" + well_path_names = rips_instance.project.import_well_paths([well_path_file]) + wells = rips_instance.project.well_paths() + well_path = wells[0] + well_path.name = "FieldValidationTest" + well_path.update() + + # Get valid MD range + result = well_path.trajectory_properties(resampling_interval=10.0) + measured_depths = result["measured_depth"] + start_md = measured_depths[len(measured_depths) // 2] + end_md = ( + measured_depths[len(measured_depths) // 2 + 5] + if len(measured_depths) > 10 + else measured_depths[-1] + ) + + perf_interval = well_path.append_perforation_interval(start_md, end_md, 0.25, 0.1) + assert perf_interval is not None + + # Get completion data + completion_data = well_path.completion_data(case.id) + assert completion_data is not None + + # Validate COMPDAT optional fields + if len(completion_data.compdat) > 0: + comp = completion_data.compdat[0] + + # Test required fields + assert comp.well_name == "FieldValidationTest" + assert comp.grid_i > 0 + assert comp.grid_j > 0 + assert comp.upper_k > 0 + assert comp.lower_k > 0 + assert comp.open_shut_flag in ["OPEN", "SHUT"] + + # Test optional double fields - verify type and range when present + if comp.HasField("transmissibility"): + assert comp.transmissibility >= 0.0 + if comp.HasField("diameter"): + assert comp.diameter > 0.0 + if comp.HasField("skin_factor"): + assert isinstance(comp.skin_factor, float) + if comp.HasField("start_md") and comp.HasField("end_md"): + assert comp.end_md >= comp.start_md + + # Validate WELSPECS required and optional fields + if len(completion_data.welspecs) > 0: + welspec = completion_data.welspecs[0] + + # Required fields + assert welspec.well_name == "FieldValidationTest" + assert welspec.group_name + assert welspec.grid_i > 0 + assert welspec.grid_j > 0 + assert welspec.phase in ["OIL", "GAS", "WATER", "LIQ"] + + # Optional fields + if welspec.HasField("bhp_depth"): + assert welspec.bhp_depth >= 0.0 + if welspec.HasField("drainage_radius"): + assert welspec.drainage_radius > 0.0 + + +def test_get_completion_data_invalid_case_id(rips_instance, initialize_test): + """Test error handling for invalid case ID""" + # Load a grid case + case_root_path = dataroot.PATH + "/TEST10K_FLT_LGR_NNC" + case_path = case_root_path + "/TEST10K_FLT_LGR_NNC.EGRID" + rips_instance.project.load_case(path=case_path) + + # Create well path + well_path_coll = rips_instance.project.well_path_collection() + well_path = well_path_coll.add_new_object(rips.ModeledWellPath) + well_path.name = "ErrorTestWell" + well_path.update() + + geometry = well_path.well_path_geometry() + surface_coord = [460000.0, 5930000.0, 1600.0] + bottom_coord = [461000.0, 5931000.0, 1900.0] + + geometry.append_well_target(surface_coord) + geometry.append_well_target(bottom_coord) + well_path.update() + + perf_interval = well_path.append_perforation_interval(1000.0, 1200.0, 0.25, 0.1) + assert perf_interval is not None + + # Test with invalid case ID + try: + # Using a negative case ID that shouldn't exist + completion_data = well_path.completion_data(-1) + # Should return empty data or handle gracefully + assert completion_data is not None + # With invalid case, completion data should be empty or minimal + except Exception as e: + # Acceptable if it throws an error for invalid case ID + assert ( + "case" in str(e).lower() + or "invalid" in str(e).lower() + or "id" in str(e).lower() + ) + + +def test_get_completion_data_empty_well(rips_instance, initialize_test): + """Test GetCompletionData with a well that has no completions""" + # Load a grid case + case_root_path = dataroot.PATH + "/TEST10K_FLT_LGR_NNC" + case_path = case_root_path + "/TEST10K_FLT_LGR_NNC.EGRID" + case = rips_instance.project.load_case(path=case_path) + + # Create well path without completions + well_path_coll = rips_instance.project.well_path_collection() + well_path = well_path_coll.add_new_object(rips.ModeledWellPath) + well_path.name = "EmptyWell" + well_path.update() + + geometry = well_path.well_path_geometry() + surface_coord = [460000.0, 5930000.0, 1600.0] + bottom_coord = [461000.0, 5931000.0, 1900.0] + + geometry.append_well_target(surface_coord) + geometry.append_well_target(bottom_coord) + well_path.update() + + # Don't add any perforation intervals + + # Test completion data - should return valid response but with empty/minimal data + completion_data = well_path.completion_data(case.id) + assert completion_data is not None + + # May or may not have WELSPECS depending on implementation + # But should not have COMPDAT entries without perforations + assert len(completion_data.compdat) == 0 + + +def test_get_completion_data_stability_comprehensive(rips_instance, initialize_test): + """Comprehensive stability test for GetCompletionData - ensures deterministic results""" + + # 1. Use deterministic test setup + case_root_path = dataroot.PATH + "/TEST10K_FLT_LGR_NNC" + case_path = case_root_path + "/TEST10K_FLT_LGR_NNC.EGRID" + case = rips_instance.project.load_case(path=case_path) + + well_path_file = case_root_path + "/wellpath_a.dev" + rips_instance.project.import_well_paths([well_path_file]) + well_path = rips_instance.project.well_paths()[0] + well_path.name = "StabilityTestWell" + well_path.update() + + # 2. Fixed completion parameters (no dynamic MD calculation) - 400m interval + perf_interval = well_path.append_perforation_interval( + start_md=2200.0, end_md=2600.0, diameter=0.25, skin_factor=0.1 + ) + assert perf_interval is not None + + # 3. Get completion data multiple times + data_run1 = well_path.completion_data(case.id) + data_run2 = well_path.completion_data(case.id) + + # 4. Serialize and compare + compdat1 = serialize_compdat_for_comparison(data_run1.compdat) + compdat2 = serialize_compdat_for_comparison(data_run2.compdat) + + # 5. Verify deterministic behavior + assert compdat1 == compdat2, "COMPDAT data should be identical between runs" + assert len(compdat1) > 0, "Should have COMPDAT entries" + + # 6. Validate expected properties + validate_compdat_properties( + data_run1.compdat, expected_diameter=0.25, expected_skin=0.1 + ) + + # 7. Validate WELSPECS stability + welspecs1 = serialize_welspecs_for_comparison(data_run1.welspecs) + welspecs2 = serialize_welspecs_for_comparison(data_run2.welspecs) + assert welspecs1 == welspecs2, "WELSPECS data should be identical between runs" + + # 8. Cross-validate with perforation interval properties + for compdat in data_run1.compdat: + assert compdat.well_name == "StabilityTestWell" + if compdat.HasField("diameter"): + assert abs(compdat.diameter - 0.25) < 0.001, "Diameter should match input" + if compdat.HasField("skin_factor"): + assert ( + abs(compdat.skin_factor - 0.1) < 0.001 + ), "Skin factor should match input" + if compdat.HasField("start_md") and compdat.HasField("end_md"): + assert ( + 2190.0 <= compdat.start_md <= 2610.0 + ), "Start MD should be in expected range" + assert ( + 2190.0 <= compdat.end_md <= 2610.0 + ), "End MD should be in expected range" + assert compdat.end_md >= compdat.start_md, "End MD should be >= start MD" + + +def test_completion_data_regression_reference(rips_instance, initialize_test): + """Regression test against stored reference JSON file""" + + # Generate completion data with fixed parameters + completion_data = generate_reference_completion_data(rips_instance) + + # Serialize current results + current_results = { + "compdat": serialize_compdat_for_comparison(completion_data.compdat), + "welspecs": serialize_welspecs_for_comparison(completion_data.welspecs), + } + + # Path to reference data (cross-platform) + test_dir = Path(__file__).parent + reference_dir = test_dir / "reference_data" + reference_file = reference_dir / "completion_data_reference.json" + + # Ensure reference directory exists + reference_dir.mkdir(exist_ok=True) + + if reference_file.exists(): + # Load reference and compare + with open(reference_file, "r", encoding="utf-8") as f: + reference_results = json.load(f) + + # Compare COMPDAT data + assert current_results["compdat"] == reference_results["compdat"], ( + "COMPDAT data differs from reference. " + "If this is expected, delete the reference file and run again to regenerate." + ) + + # Compare WELSPECS data + assert current_results["welspecs"] == reference_results["welspecs"], ( + "WELSPECS data differs from reference. " + "If this is expected, delete the reference file and run again to regenerate." + ) + + print("Completion data matches reference file successfully") + + else: + # Generate reference file (run once to establish baseline) + with open(reference_file, "w", encoding="utf-8") as f: + json.dump(current_results, f, indent=2, sort_keys=True) + + print(f"Generated reference data at: {reference_file}") + print("Re-run the test to validate against the reference") + + # Validate the generated data has expected structure + assert ( + len(current_results["compdat"]) > 0 + ), "Should have COMPDAT entries in reference" + assert ( + len(current_results["welspecs"]) > 0 + ), "Should have WELSPECS entries in reference" + + +def generate_reference_completion_data(rips_instance): + """Generate completion data with fixed, deterministic parameters for reference testing""" + + # Load case + case_root_path = dataroot.PATH + "/TEST10K_FLT_LGR_NNC" + case_path = case_root_path + "/TEST10K_FLT_LGR_NNC.EGRID" + case = rips_instance.project.load_case(path=case_path) + + # Import well path + well_path_file = case_root_path + "/wellpath_a.dev" + rips_instance.project.import_well_paths([well_path_file]) + well_path = rips_instance.project.well_paths()[0] + well_path.name = "ReferenceTestWell" + well_path.update() + + # Create completion with fixed parameters - 400m interval to cover multiple cells + perf_interval = well_path.append_perforation_interval( + start_md=2200.0, end_md=2600.0, diameter=0.25, skin_factor=0.1 + ) + assert perf_interval is not None + + # Return completion data + return well_path.completion_data(case.id) + + +def serialize_compdat_for_comparison(compdat_entries): + """Convert COMPDAT to stable comparison format - cross-platform consistent""" + entries = [] + for comp in compdat_entries: + # Create ordered dict to ensure consistent field ordering + entry = {} + + # Add fields in alphabetical order for consistency + if comp.HasField("comment"): + entry["comment"] = comp.comment + if comp.HasField("d_factor"): + entry["d_factor"] = round(float(comp.d_factor), 8) + if comp.HasField("diameter"): + entry["diameter"] = round(float(comp.diameter), 8) + if comp.HasField("direction"): + entry["direction"] = comp.direction + if comp.HasField("end_md"): + entry["end_md"] = round(float(comp.end_md), 6) + entry["grid_i"] = int(comp.grid_i) + entry["grid_j"] = int(comp.grid_j) + if comp.HasField("grid_name"): + entry["grid_name"] = comp.grid_name + if comp.HasField("kh"): + entry["kh"] = round(float(comp.kh), 8) + entry["lower_k"] = int(comp.lower_k) + entry["open_shut_flag"] = comp.open_shut_flag + if comp.HasField("saturation"): + entry["saturation"] = round(float(comp.saturation), 8) + if comp.HasField("skin_factor"): + entry["skin_factor"] = round(float(comp.skin_factor), 8) + if comp.HasField("start_md"): + entry["start_md"] = round(float(comp.start_md), 6) + if comp.HasField("transmissibility"): + entry["transmissibility"] = round(float(comp.transmissibility), 8) + entry["upper_k"] = int(comp.upper_k) + entry["well_name"] = comp.well_name + + entries.append(entry) + + # Sort for consistent ordering across platforms + entries.sort(key=lambda x: (x["grid_i"], x["grid_j"], x["upper_k"], x["lower_k"])) + return entries + + +def serialize_welspecs_for_comparison(welspecs_entries): + """Convert WELSPECS to stable comparison format - cross-platform consistent""" + entries = [] + for welspec in welspecs_entries: + # Create ordered dict with alphabetical field ordering + entry = {} + + if welspec.HasField("auto_shut_in"): + entry["auto_shut_in"] = welspec.auto_shut_in + if welspec.HasField("bhp_depth"): + entry["bhp_depth"] = round(float(welspec.bhp_depth), 6) + if welspec.HasField("cross_flow"): + entry["cross_flow"] = welspec.cross_flow + if welspec.HasField("drainage_radius"): + entry["drainage_radius"] = round(float(welspec.drainage_radius), 6) + if welspec.HasField("fip_region"): + entry["fip_region"] = int(welspec.fip_region) + entry["grid_i"] = int(welspec.grid_i) + entry["grid_j"] = int(welspec.grid_j) + if welspec.HasField("grid_name"): + entry["grid_name"] = welspec.grid_name + entry["group_name"] = welspec.group_name + if welspec.HasField("hydrostatic_density_calc"): + entry["hydrostatic_density_calc"] = welspec.hydrostatic_density_calc + if welspec.HasField("inflow_equation"): + entry["inflow_equation"] = welspec.inflow_equation + entry["phase"] = welspec.phase + if welspec.HasField("pvt_num"): + entry["pvt_num"] = int(welspec.pvt_num) + entry["well_name"] = welspec.well_name + + entries.append(entry) + + # Sort for consistent ordering across platforms + entries.sort(key=lambda x: (x["well_name"], x["grid_i"], x["grid_j"])) + return entries + + +def validate_compdat_properties( + compdat_entries, expected_diameter=None, expected_skin=None +): + """Validate that COMPDAT entries have consistent and expected properties""" + assert len(compdat_entries) > 0, "Should have at least one COMPDAT entry" + + for i, compdat in enumerate(compdat_entries): + # Validate required fields + assert compdat.well_name, f"Entry {i}: Well name should not be empty" + assert compdat.grid_i > 0, f"Entry {i}: Grid I should be positive" + assert compdat.grid_j > 0, f"Entry {i}: Grid J should be positive" + assert compdat.upper_k > 0, f"Entry {i}: Upper K should be positive" + assert ( + compdat.lower_k >= compdat.upper_k + ), f"Entry {i}: Lower K should be >= upper K" + assert compdat.open_shut_flag in [ + "OPEN", + "SHUT", + ], f"Entry {i}: Invalid open/shut flag" + + # Validate optional fields when present + if compdat.HasField("transmissibility"): + assert ( + compdat.transmissibility >= 0 + ), f"Entry {i}: Transmissibility should be non-negative" + + if compdat.HasField("diameter") and expected_diameter is not None: + assert ( + abs(compdat.diameter - expected_diameter) < 0.001 + ), f"Entry {i}: Diameter mismatch" + + if compdat.HasField("skin_factor") and expected_skin is not None: + assert ( + abs(compdat.skin_factor - expected_skin) < 0.001 + ), f"Entry {i}: Skin factor mismatch" + + if compdat.HasField("start_md") and compdat.HasField("end_md"): + assert ( + compdat.end_md >= compdat.start_md + ), f"Entry {i}: End MD should be >= start MD" + assert compdat.start_md >= 0, f"Entry {i}: Start MD should be non-negative" + + if compdat.HasField("kh"): + assert compdat.kh >= 0, f"Entry {i}: KH should be non-negative" + + +def generate_compdat_hash(compdat_data): + """Generate hash for regression detection - cross-platform stable""" + # Ensure consistent JSON serialization across platforms + data_str = json.dumps( + compdat_data, + sort_keys=True, + separators=(",", ":"), # Remove whitespace variations + ensure_ascii=True, # Avoid unicode encoding differences + ) + # Use UTF-8 encoding explicitly for cross-platform consistency + return hashlib.sha256(data_str.encode("utf-8")).hexdigest() + + +def test_completion_data_hash_cross_platform_stability(rips_instance, initialize_test): + """Test that hash generation is stable across different platforms""" + + # Generate test data + completion_data = generate_reference_completion_data(rips_instance) + + # Serialize the same data multiple times + serialized_data1 = { + "compdat": serialize_compdat_for_comparison(completion_data.compdat), + "welspecs": serialize_welspecs_for_comparison(completion_data.welspecs), + } + + serialized_data2 = { + "compdat": serialize_compdat_for_comparison(completion_data.compdat), + "welspecs": serialize_welspecs_for_comparison(completion_data.welspecs), + } + + # Generate hashes + hash1 = generate_compdat_hash(serialized_data1) + hash2 = generate_compdat_hash(serialized_data2) + + # Should be identical + assert hash1 == hash2, "Hash should be deterministic for same data" + + # Test specific cross-platform concerns + test_data = [ + {"float_val": 0.12345678901234567, "int_val": 42, "str_val": "test"}, + {"float_val": 1.0000000000000001, "int_val": 0, "str_val": ""}, + ] + + # Generate hash with explicit settings + json_str1 = json.dumps( + test_data, sort_keys=True, separators=(",", ":"), ensure_ascii=True + ) + json_str2 = json.dumps( + test_data, sort_keys=True, separators=(",", ":"), ensure_ascii=True + ) + + hash_test1 = hashlib.sha256(json_str1.encode("utf-8")).hexdigest() + hash_test2 = hashlib.sha256(json_str2.encode("utf-8")).hexdigest() + + assert hash_test1 == hash_test2, "JSON serialization should be deterministic" + + print(f"Cross-platform stable hash: {hash1[:16]}...") + + +def test_completion_data_reference_failure_simulation(rips_instance, initialize_test): + """Test that the reference test properly detects changes (simulate failure scenario)""" + # This test validates that our reference test would catch real changes + # by temporarily creating modified data and verifying it fails comparison + + case_root_path = dataroot.PATH + "/TEST10K_FLT_LGR_NNC" + case_path = case_root_path + "/TEST10K_FLT_LGR_NNC.EGRID" + case = rips_instance.project.load_case(path=case_path) + + well_path_file = case_root_path + "/wellpath_a.dev" + rips_instance.project.import_well_paths([well_path_file]) + well_path = rips_instance.project.well_paths()[0] + well_path.name = "ModifiedTestWell" # Different well name + well_path.update() + + # Same completion parameters as reference + perf_interval = well_path.append_perforation_interval( + start_md=2200.0, end_md=2600.0, diameter=0.25, skin_factor=0.1 + ) + + completion_data = well_path.completion_data(case.id) + modified_results = { + "compdat": serialize_compdat_for_comparison(completion_data.compdat), + "welspecs": serialize_welspecs_for_comparison(completion_data.welspecs), + } + + # Load reference data + test_dir = Path(__file__).parent + reference_file = test_dir / "reference_data" / "completion_data_reference.json" + + with open(reference_file, "r", encoding="utf-8") as f: + reference_results = json.load(f) + + # This should fail because well names are different + assert ( + modified_results["compdat"] != reference_results["compdat"] + ), "Modified data should differ from reference (well name changed)" + assert ( + modified_results["welspecs"] != reference_results["welspecs"] + ), "Modified WELSPECS should differ from reference (well name changed)" + + print("Reference test correctly detects data changes") + + +def test_completion_data_cross_validation(rips_instance, initialize_test): + """Cross-validate completion data with perforation interval properties""" + case_root_path = dataroot.PATH + "/TEST10K_FLT_LGR_NNC" + case_path = case_root_path + "/TEST10K_FLT_LGR_NNC.EGRID" + case = rips_instance.project.load_case(path=case_path) + + well_path_file = case_root_path + "/wellpath_a.dev" + rips_instance.project.import_well_paths([well_path_file]) + well_path = rips_instance.project.well_paths()[0] + well_path.name = "CrossValidationWell" + well_path.update() + + # Create perforation with known parameters + diameter = 0.3 + skin_factor = 0.05 + start_md = 2400.0 + end_md = 2420.0 + + perf_interval = well_path.append_perforation_interval( + start_md, end_md, diameter, skin_factor + ) + + # Get data via gRPC API + completion_data = well_path.completion_data(case.id) + + # Cross-validate with perforation interval properties + assert len(completion_data.compdat) > 0, "Should have COMPDAT entries" + + # Validate against original interval properties + found_matching_entry = False + for compdat in completion_data.compdat: + assert compdat.well_name == "CrossValidationWell", "Well name should match" + + # Check diameter matches + if compdat.HasField("diameter"): + if abs(compdat.diameter - diameter) < 0.001: + found_matching_entry = True + + # Also check skin factor for this entry + if compdat.HasField("skin_factor"): + assert ( + abs(compdat.skin_factor - skin_factor) < 0.001 + ), "Skin factor should match" + + # Check MD range + if compdat.HasField("start_md") and compdat.HasField("end_md"): + assert ( + start_md <= compdat.start_md <= end_md + 5 + ), "Start MD should be in range" + assert ( + start_md <= compdat.end_md <= end_md + 5 + ), "End MD should be in range" + + assert found_matching_entry, f"Should find COMPDAT entry with diameter {diameter}" diff --git a/GrpcInterface/RiaGrpcWellPathService.cpp b/GrpcInterface/RiaGrpcWellPathService.cpp index b953692087..52a107f245 100644 --- a/GrpcInterface/RiaGrpcWellPathService.cpp +++ b/GrpcInterface/RiaGrpcWellPathService.cpp @@ -38,48 +38,63 @@ using namespace rips; +namespace internal +{ //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -grpc::Status RiaGrpcWellPathService::GetCompletionData( grpc::ServerContext* context, - const rips::SimulatorTableRequest* request, - rips::SimulatorTableData* reply ) - +void populateReply( RimEclipseCase* eclipseCase, RimWellPath* wellPath, ::rips::SimulatorTableData* reply ) { - RimEclipseCase* eclipseCase = dynamic_cast( RiaGrpcHelper::findCase( request->case_id() ) ); - if ( !eclipseCase ) - { - return grpc::Status( grpc::NOT_FOUND, "Case not found" ); - } - - RimWellPath* wellPath = nullptr; - - if ( auto proj = RimProject::current() ) - { - wellPath = proj->wellPathByName( QString::fromStdString( request->wellpath_name() ) ); - } - - if ( !wellPath ) - { - return grpc::Status( grpc::NOT_FOUND, "Well path " + request->wellpath_name() + " not found" ); - } - + std::set gridNames; auto compdata = RicWellPathExportCompletionDataFeatureImpl::completionDataForWellPath( wellPath, eclipseCase ); for ( const auto& cd : compdata ) { SimulatorCompdatEntry* grpcData = reply->add_compdat(); RiaWellPathDataToGrpcConverter::copyCompdatToGrpc( cd, grpcData ); + + auto completionCell = cd.completionDataGridCell(); + gridNames.insert( completionCell.lgrName() ); + + const auto wpimult = cd.wpimult(); + if ( wpimult != 0.0 && !RigCompletionData::isDefaultValue( wpimult ) ) + { + const auto wellName = wellPath->completionSettings()->wellNameForExport(); + + SimulatorWpimultEntry* grpcWpiData = reply->add_wpimult(); + grpcWpiData->set_well_name( wellName.toStdString() ); + grpcWpiData->set_pimult( wpimult ); + grpcWpiData->set_i( completionCell.localCellIndexI() + 1 ); + grpcWpiData->set_j( completionCell.localCellIndexJ() + 1 ); + grpcWpiData->set_k( completionCell.localCellIndexK() + 1 ); + grpcWpiData->set_grid_name( completionCell.lgrName().toStdString() ); + } } - auto ijPos = RicWellPathExportCompletionDataFeatureImpl::wellPathUpperGridIntersectionIJ( eclipseCase, wellPath ); - auto compSettings = wellPath->completionSettings(); + // See RicWellPathExportCompletionDataFeatureImpl::exportWelspeclToFile + // Find the grid with the uppermost intersection along the well path + std::tuple itemWithLowestMD = + std::make_tuple( std::numeric_limits::max(), cvf::Vec2i(), "" ); + + for ( const auto& gridName : gridNames ) + { + auto candidate = + RicWellPathExportCompletionDataFeatureImpl::wellPathUpperGridIntersectionIJ( eclipseCase, wellPath, gridName ); + if ( candidate.first < std::get<0>( itemWithLowestMD ) ) + { + itemWithLowestMD = std::make_tuple( candidate.first, candidate.second, gridName ); + } + } + + auto compSettings = wellPath->completionSettings(); + cvf::Vec2i ijIntersection = std::get<1>( itemWithLowestMD ); + QString lgrName = std::get<2>( itemWithLowestMD ); SimulatorWelspecsEntry* grpcData = reply->add_welspecs(); RiaWellPathDataToGrpcConverter::copyWelspecsToGrpc( compSettings, grpcData, - eclipseCase, - ijPos.second.x(), - ijPos.second.y() ); + ijIntersection.x(), + ijIntersection.y(), + lgrName.toStdString() ); // Multisegment wells @@ -99,6 +114,37 @@ grpc::Status RiaGrpcWellPathService::GetCompletionData( grpc::ServerContext* RiaWellPathDataToGrpcConverter::copyWsegvalvToGrpc( tables, reply ); RiaWellPathDataToGrpcConverter::copyWsegaicdToGrpc( tables, reply ); } +} + +} // namespace internal + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +grpc::Status RiaGrpcWellPathService::GetCompletionData( grpc::ServerContext* context, + const rips::SimulatorTableRequest* request, + rips::SimulatorTableData* reply ) + +{ + RimEclipseCase* eclipseCase = dynamic_cast( RiaGrpcHelper::findCase( request->case_id() ) ); + if ( !eclipseCase ) + { + return grpc::Status( grpc::NOT_FOUND, "Case not found" ); + } + + auto proj = RimProject::current(); + if ( !proj ) + { + return grpc::Status( grpc::INTERNAL, "No current project" ); + } + + auto wellPath = proj->wellPathByName( QString::fromStdString( request->wellpath_name() ) ); + if ( !wellPath ) + { + return grpc::Status( grpc::NOT_FOUND, "Well path " + request->wellpath_name() + " not found" ); + } + + internal::populateReply( eclipseCase, wellPath, reply ); return grpc::Status::OK; } @@ -126,48 +172,9 @@ grpc::Status RiaGrpcWellPathService::GetCompletionDataUnified( grpc::ServerConte for ( const std::string& wellPathName : request->wellpath_names() ) { RimWellPath* wellPath = proj->wellPathByName( QString::fromStdString( wellPathName ) ); - if ( !wellPath ) - { - // Continue processing other wells instead of failing the entire request - continue; - } + if ( !wellPath ) continue; - // Add completion data for this well - auto compdata = RicWellPathExportCompletionDataFeatureImpl::completionDataForWellPath( wellPath, eclipseCase ); - for ( const auto& cd : compdata ) - { - SimulatorCompdatEntry* grpcData = reply->add_compdat(); - RiaWellPathDataToGrpcConverter::copyCompdatToGrpc( cd, grpcData ); - } - - // Add welspecs data for this well - auto ijPos = RicWellPathExportCompletionDataFeatureImpl::wellPathUpperGridIntersectionIJ( eclipseCase, wellPath ); - auto compSettings = wellPath->completionSettings(); - - SimulatorWelspecsEntry* grpcData = reply->add_welspecs(); - RiaWellPathDataToGrpcConverter::copyWelspecsToGrpc( compSettings, - grpcData, - eclipseCase, - ijPos.second.x(), - ijPos.second.y() ); - - // Add multisegment well data for this well - int timeStep = 0; - auto mswDataContainer = RicWellPathExportMswTableData::extractSingleWellMswData( eclipseCase, wellPath, timeStep ); - if ( mswDataContainer.has_value() ) - { - auto tables = mswDataContainer.value(); - - if ( tables.hasWelsegsData() ) - { - SimulatorWelsegsEntry* grpcSegData = reply->add_welsegs(); - RiaWellPathDataToGrpcConverter::copyWelsegsToGrpc( tables, grpcSegData ); - } - - RiaWellPathDataToGrpcConverter::copyCompsegsToGrpc( tables, reply ); - RiaWellPathDataToGrpcConverter::copyWsegvalvToGrpc( tables, reply ); - RiaWellPathDataToGrpcConverter::copyWsegaicdToGrpc( tables, reply ); - } + internal::populateReply( eclipseCase, wellPath, reply ); } return grpc::Status::OK; diff --git a/GrpcInterface/RiaGrpcWellPathService.h b/GrpcInterface/RiaGrpcWellPathService.h index a9aaff06aa..27c3952f71 100644 --- a/GrpcInterface/RiaGrpcWellPathService.h +++ b/GrpcInterface/RiaGrpcWellPathService.h @@ -24,21 +24,8 @@ #include #include -namespace rips -{ -class SimulationWellRequest; -class SimulationWellStatus; -class SimulatorWelspecsEntry; -class SimulatorWelsegsEntry; -} // namespace rips - class RiaGrpcCallbackInterface; -class RimEclipseCase; -class RimWellPathCompletionSettings; -class RigCompletionData; -class RigMswTableData; - //================================================================================================== // // gRPC-service answering requests about grid information for a simulation wells diff --git a/GrpcInterface/RiaWellPathDataToGrpcConverter.cpp b/GrpcInterface/RiaWellPathDataToGrpcConverter.cpp index 7f903bd588..15eaf31775 100644 --- a/GrpcInterface/RiaWellPathDataToGrpcConverter.cpp +++ b/GrpcInterface/RiaWellPathDataToGrpcConverter.cpp @@ -72,6 +72,9 @@ void RiaWellPathDataToGrpcConverter::copyCompdatToGrpc( const RigCompletionData& compDat->set_start_md( inputData.startMD().value() ); compDat->set_end_md( inputData.endMD().value() ); } + + // Set LGR grid name, empty string for main grid + compDat->set_grid_name( inputData.completionDataGridCell().lgrName().toStdString() ); } //-------------------------------------------------------------------------------------------------- @@ -79,9 +82,9 @@ void RiaWellPathDataToGrpcConverter::copyCompdatToGrpc( const RigCompletionData& //-------------------------------------------------------------------------------------------------- void RiaWellPathDataToGrpcConverter::copyWelspecsToGrpc( const RimWellPathCompletionSettings* compSettings, rips::SimulatorWelspecsEntry* grpcData, - RimEclipseCase* eclipseCase, int gridI, - int gridJ ) + int gridJ, + std::string gridName ) { grpcData->set_well_name( compSettings->wellNameForExport().toStdString() ); grpcData->set_group_name( compSettings->groupNameForExport().toStdString() ); @@ -105,6 +108,9 @@ void RiaWellPathDataToGrpcConverter::copyWelspecsToGrpc( const RimWellPathComple grpcData->set_pvt_num( compSettings->wellBoreFluidPVT() ); grpcData->set_hydrostatic_density_calc( compSettings->hydrostaticDensityForExport().toStdString() ); grpcData->set_fip_region( compSettings->fluidInPlaceRegion() ); + + // Set LGR grid name, empty string for main grid + grpcData->set_grid_name( gridName ); } //-------------------------------------------------------------------------------------------------- diff --git a/GrpcInterface/RiaWellPathDataToGrpcConverter.h b/GrpcInterface/RiaWellPathDataToGrpcConverter.h index 5c0e507c4f..89f6133d81 100644 --- a/GrpcInterface/RiaWellPathDataToGrpcConverter.h +++ b/GrpcInterface/RiaWellPathDataToGrpcConverter.h @@ -22,7 +22,6 @@ class RigCompletionData; class RimWellPathCompletionSettings; -class RimEclipseCase; class RigMswTableData; //================================================================================================== @@ -37,9 +36,9 @@ public: static void copyWelspecsToGrpc( const RimWellPathCompletionSettings* compSettings, rips::SimulatorWelspecsEntry* grpcData, - RimEclipseCase* eclipseCase, int gridI, - int gridJ ); + int gridJ, + std::string gridName ); static void copyWelsegsToGrpc( const RigMswTableData& mswTableData, rips::SimulatorWelsegsEntry* grpcData ); static void copyCompsegsToGrpc( const RigMswTableData& mswTableData, rips::SimulatorTableData* reply );