mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-26 15:10:22 -05:00
Well Event: Add opt-in column-aligned schedule output
Add an align_columns option to schedule generation. When enabled, each keyword is serialized with a '--'-prefixed column-header comment and data rows right-aligned into fixed-width columns, instead of the compact default form. The default output is unchanged. A new RimKeywordFactory::deckKeywordToAlignedString introspects the OPM deck model and renders each item in its own column (consecutive defaults are not collapsed into 'N*'). The flag is threaded through RicScheduleDataGenerator and exposed on the GenerateSchedule scriptable method as align_columns in the Python generate_schedule_text wrapper.
This commit is contained in:
1 parent
bce29354ea
commit
19c547cc2e
9 files changed
+317
-13
No files matched your search
+17
-5
@@ -237,6 +237,13 @@ def main():
|
||||
eclipse_case=case, export_msw_for_wells=[well_path]
|
||||
)
|
||||
|
||||
# Generate the same schedule with align_columns=True, which adds a "--"-prefixed
|
||||
# column-header comment per keyword and right-aligns the data into fixed-width
|
||||
# columns. Only the formatting differs from the unaligned text above.
|
||||
schedule_text_aligned = timeline.generate_schedule_text(
|
||||
eclipse_case=case, export_msw_for_wells=[well_path], align_columns=True
|
||||
)
|
||||
|
||||
if schedule_text:
|
||||
print(f"\n Generated schedule text ({len(schedule_text)} characters)")
|
||||
print(" " + "=" * 60)
|
||||
@@ -308,15 +315,20 @@ def main():
|
||||
print(f" - GRUPTREE entries: {schedule_text.count('GRUPTREE')}")
|
||||
print(f" - TUNING entries: {schedule_text.count('TUNING')}")
|
||||
|
||||
# Save to file
|
||||
output_file = "generated_schedule.sch"
|
||||
with open(output_file, "w") as f:
|
||||
# Save both formats to file
|
||||
unaligned_file = "generate_schedule_unaligned.sch"
|
||||
with open(unaligned_file, "w") as f:
|
||||
f.write(schedule_text)
|
||||
print(f"\n Schedule text saved to: {output_file}")
|
||||
print(f"\n Unaligned schedule text saved to: {unaligned_file}")
|
||||
|
||||
aligned_file = "generate_schedule_aligned.sch"
|
||||
with open(aligned_file, "w") as f:
|
||||
f.write(schedule_text_aligned)
|
||||
print(f" Aligned schedule text saved to: {aligned_file}")
|
||||
|
||||
# Show example of generated keywords
|
||||
print("\n8. Example of generated Eclipse keywords:")
|
||||
print(" (See generated_schedule.sch for complete output)")
|
||||
print(f" (See {unaligned_file} / {aligned_file} for complete output)")
|
||||
if "WELSEGS" in schedule_text:
|
||||
print("\n Sample WELSEGS segment:")
|
||||
for line in lines:
|
||||
|
||||
@@ -1262,6 +1262,94 @@ class TestScheduleGeneration:
|
||||
|
||||
assert "COMPDAT" in schedule_text
|
||||
|
||||
def test_align_columns_adds_headers_and_alignment(self, project_with_case_and_well):
|
||||
"""align_columns=True must add a '--'-prefixed column-header comment per keyword and
|
||||
indent right-aligned data rows, while the default (align_columns=False) keeps the
|
||||
compact form. Only the formatting should differ between the two."""
|
||||
project, case, timeline = project_with_case_and_well
|
||||
well_path = project.well_paths()[0]
|
||||
|
||||
# A perforation (COMPDAT) plus a WCONHIST keyword event gives several tabular keywords
|
||||
# plus the always-present DATES keyword to exercise the aligned formatter.
|
||||
timeline.add_perf_event(
|
||||
event_date="2024-01-01",
|
||||
well_path=well_path,
|
||||
start_md=2000.0,
|
||||
end_md=2200.0,
|
||||
diameter=0.1,
|
||||
state="OPEN",
|
||||
)
|
||||
timeline.add_well_keyword_event(
|
||||
event_date="2024-01-01",
|
||||
well_path=well_path,
|
||||
keyword_name="WCONHIST",
|
||||
keyword_data={
|
||||
"WELL": well_path.name,
|
||||
"STATUS": "OPEN",
|
||||
"CMODE": "RESV",
|
||||
"ORAT": 3999.99,
|
||||
"VFP_TABLE": 1,
|
||||
},
|
||||
)
|
||||
|
||||
timeline.set_timestamp(timestamp="2024-01-01")
|
||||
|
||||
# Force the first date to a DATES keyword (instead of the default leading comment) so the
|
||||
# aligned DATES column header is exercised.
|
||||
aligned = timeline.generate_schedule_text(
|
||||
eclipse_case=case,
|
||||
export_msw_for_wells=project.well_paths(),
|
||||
first_date_as_comment=False,
|
||||
align_columns=True,
|
||||
)
|
||||
default = timeline.generate_schedule_text(
|
||||
eclipse_case=case,
|
||||
export_msw_for_wells=project.well_paths(),
|
||||
first_date_as_comment=False,
|
||||
align_columns=False,
|
||||
)
|
||||
|
||||
print(f"\nAligned schedule text:\n{aligned}")
|
||||
print(f"\nDefault schedule text:\n{default}")
|
||||
|
||||
# The DATES keyword is always present; aligned output prefixes its item names as a comment.
|
||||
assert "--DAY" in aligned, "Aligned output should carry a DATES column header"
|
||||
assert "--DAY" not in default, "Default output should not carry column headers"
|
||||
|
||||
# WCONHIST item names appear only as a header comment in the aligned form (their values,
|
||||
# e.g. 'OPEN'/'RESV', are what show up in both forms).
|
||||
assert "CMODE" in aligned and "STATUS" in aligned, (
|
||||
"Aligned output should list WCONHIST item names in a header comment"
|
||||
)
|
||||
assert "CMODE" not in default and "STATUS" not in default, (
|
||||
"Default output should not list item names"
|
||||
)
|
||||
|
||||
# Each aligned column-header line starts with '--' and its data rows are indented two
|
||||
# spaces and terminate with ' /'.
|
||||
wconhist_block = aligned.split("WCONHIST\n", 1)[1]
|
||||
header_line = wconhist_block.splitlines()[0]
|
||||
data_line = wconhist_block.splitlines()[1]
|
||||
# Header is a comment whose names are right-aligned into their columns, so it starts with
|
||||
# '--' and lists WELL/STATUS/CMODE (the first column may be padded ahead of 'WELL').
|
||||
assert header_line.startswith("--") and "WELL" in header_line, (
|
||||
f"WCONHIST header should be a '--' comment listing item names: {header_line!r}"
|
||||
)
|
||||
assert data_line.startswith(" "), (
|
||||
f"WCONHIST data row should be indented two spaces: {data_line!r}"
|
||||
)
|
||||
assert data_line.rstrip().endswith("/"), (
|
||||
f"WCONHIST data row should end with '/': {data_line!r}"
|
||||
)
|
||||
# Per-column defaults stay as individual '1*' markers (never accumulated into 'N*').
|
||||
assert "1*" in data_line and " 2*" not in data_line, (
|
||||
f"WCONHIST data row should keep per-column '1*' markers: {data_line!r}"
|
||||
)
|
||||
|
||||
# Same keywords are produced either way.
|
||||
for keyword in ("DATES", "COMPDAT", "WCONHIST"):
|
||||
assert keyword in aligned and keyword in default
|
||||
|
||||
def test_perf_completion_number_triggers_complump(self, project_with_case_and_well):
|
||||
"""#13273 follow-up: a completion_number on add_perf_event must surface as a
|
||||
COMPLUMP keyword (with that number) in the generated schedule.
|
||||
|
||||
@@ -289,6 +289,7 @@ def generate_schedule_text(
|
||||
eclipse_case: EclipseCase,
|
||||
export_msw_for_wells: List[WellPath] = [],
|
||||
first_date_as_comment: bool = True,
|
||||
align_columns: bool = False,
|
||||
) -> str:
|
||||
"""Generate Eclipse schedule text for all wells in the collection.
|
||||
|
||||
@@ -310,6 +311,9 @@ def generate_schedule_text(
|
||||
2024") instead of a DATES keyword. This avoids a DATES entry equal
|
||||
to the simulation start date, which some commercial simulators
|
||||
reject. Later dates are always emitted as DATES keywords.
|
||||
align_columns (bool): When True, emit each keyword with a "--"-prefixed
|
||||
column-header comment and right-aligned, fixed-width columns instead
|
||||
of the compact default form. Defaults to False.
|
||||
|
||||
Returns:
|
||||
str: Eclipse schedule text containing DATES, COMPDAT, WELSEGS, WCONPROD, etc.
|
||||
@@ -353,6 +357,7 @@ def generate_schedule_text(
|
||||
eclipse_case=eclipse_case,
|
||||
export_msw_for_wells=export_msw_for_wells,
|
||||
first_date_as_comment=first_date_as_comment,
|
||||
align_columns=align_columns,
|
||||
)
|
||||
if container and container.values:
|
||||
return "".join(container.values)
|
||||
|
||||
Reference in new issue
Block a user