mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-24 22:27:55 -05:00
- Remove unused imports in test files - Fix bare except clause to use except Exception - Fix None comparisons to use 'is not None' / 'is None' - Remove unused variable assignments - Add noqa comments for intentional patterns (star imports, method redefinition for different classes, import after sys.path modification) - Apply ruff formatting
848 lines
32 KiB
Python
848 lines
32 KiB
Python
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
|
|
|
|
|
|
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"
|
|
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"
|
|
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
|
|
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_welspecs_grid_name_field(rips_instance, initialize_test):
|
|
"""Test WELSPECS grid_name field behavior for main grid vs LGR grids"""
|
|
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 = "GridNameTestWell"
|
|
well_path.update()
|
|
|
|
# Add completion
|
|
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
|
|
assert len(completion_data.welspecs) > 0
|
|
|
|
# Test WELSPECS grid_name field
|
|
for welspec in completion_data.welspecs:
|
|
assert welspec.well_name == "GridNameTestWell"
|
|
|
|
# For main grid wells, grid_name should be empty or not present
|
|
# This results in WELSPECS output (not WELSPECL)
|
|
if welspec.HasField("grid_name"):
|
|
# If grid_name field exists, it should be empty for main grid
|
|
assert welspec.grid_name.strip() == "", (
|
|
"Main grid wells should have empty grid_name"
|
|
)
|
|
|
|
# Verify required WELSPECS fields
|
|
assert welspec.group_name, "Should have group name"
|
|
|
|
# Test specific grid coordinates (these should be deterministic for the test well path)
|
|
assert welspec.grid_i == 29, f"Expected grid_i=29, got {welspec.grid_i}"
|
|
assert welspec.grid_j == 41, f"Expected grid_j=41, got {welspec.grid_j}"
|
|
|
|
assert welspec.phase in [
|
|
"OIL",
|
|
"GAS",
|
|
"WATER",
|
|
"LIQ",
|
|
], "Should have valid phase"
|
|
|
|
print(
|
|
"Note: WELSPECL testing requires LGR grids. Current test case uses main grid only."
|
|
)
|
|
print(
|
|
"WELSPECL functionality would be tested when grid_name contains a non-empty string."
|
|
)
|
|
|
|
|
|
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
|
|
|
|
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}"
|