Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions docs/gap_roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ less often. The technical detail follows for whoever picks the item up.
| 8 | Give user-facing names a clear meaning | Open |
| 9 | Represent a localized casing hole | Experimental; static opening/isolation and one-year pressure-driven transfer verified |
| 10 | Generate hole-transfer reporting from the workbook | Done; verified on CIRRUS (Smeaheia, one year) |
| 11 | Align FIPLEG aquifer reporting with the current coarse-grid boundaries | Open; region 5 definition needs review |
| 11 | Align FIPLEG aquifer reporting with the current coarse-grid boundaries | Implemented; CIRRUS INIT check pending |
| 12 | Run only new or unfinished workbook cases | Open; batch reruns every case and rewrites the manifest |
| 13 | Clean up the CIRRUS template deck | Partial; generated `BRINE` now matches salt tables; CIRRUS comparison and cleanup pending |

Expand Down Expand Up @@ -560,10 +560,23 @@ invalid locations, and unchanged cases without reporting.

### 11. Align FIPLEG aquifer reporting with the current coarse-grid boundaries

**Status: open; outdated definition reported by the user (2026-10-12).**
FIPLEG 5 was intended to identify aquifer cells with multiplied pore volume.
The aquifer representation has since moved to the outer edges of the coarse
grid; its reporting zonation needs to be checked against that current placement.
**Status: implemented; CIRRUS INIT check pending.** Outdated definition
reported by the user (2026-10-12): FIPLEG 5 was intended to identify aquifer
cells with multiplied pore volume. The aquifer representation moved to the
outer edges of the coarse grid, but region 5 stayed on the bottom
`aquifer_layers` of the reservoir.

**Decision (2026-10-17):** region 5 is the four outer coarse-grid columns in the
reservoir layers. Water and overburden edge cells keep regions 1 and 2, even
though their pore volume is also multiplied, so ocean and overburden regions
keep their identity. Region 3 is now the whole reservoir minus those edge
columns, including the bottom `aquifer_layers`, which keep `aquifer_permx` but
are no longer labelled 5. The edge boxes for `FIPLEG 5` and the `PORV`
multiplier come from one shared list in `prepare_init_case.py`, so they cannot
drift apart. The wellbore cells (region 6: pipe, open hole and casing-cement boxes) sit in
a central parent cell (10, 10 in the default 20 x 20 grid), away from the edge
columns; other grid sizes were not checked. Cases generated earlier are not
comparable region-for-region.

**In practice:** regional inventories and transfers labelled as aquifer
quantities should refer to the actual boundary-aquifer cells, not their former
Expand Down
22 changes: 13 additions & 9 deletions runscripts/prepare_init_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ def region(keyword: str, value: object, start: int, end: int) -> str:
formatted_value = f"{value:g}" if isinstance(value, (int, float)) else str(value)
return f"{keyword} {formatted_value} 1 {nx} 1 {ny} {start} {end} /"

# Outer coarse-grid columns (i0 i1 j0 j1): the aquifer boundary used for both PORV and FIPLEG 5.
edge_columns = ((1, 1, 1, ny), (nx, nx, 1, ny), (2, nx - 1, 1, 1), (2, nx - 1, ny, ny))

def edge_region(keyword: str, value: object, start: int, end: int) -> list[str]:
return [f"{keyword} {value:g} {i0} {i1} {j0} {j1} {start} {end} /" for i0, i1, j0, j1 in edge_columns]

lines = [
"EQUALS",
"FIPLEG 4 /",
Expand All @@ -163,14 +169,15 @@ def region(keyword: str, value: object, start: int, end: int) -> str:
"",
"-- Reservoir and aquifer properties",
]
lines.append(region("FIPLEG", 3, reservoir_start, reservoir_end))
if aquifer_start is None:
lines.extend([region("PERMX", reservoir_permx, reservoir_start, reservoir_end), region("FIPLEG", 3, reservoir_start, reservoir_end)])
lines.append(region("PERMX", reservoir_permx, reservoir_start, reservoir_end))
else:
if aquifer_start > reservoir_start:
lines.extend(
[region("PERMX", reservoir_permx, reservoir_start, aquifer_start - 1), region("FIPLEG", 3, reservoir_start, aquifer_start - 1)]
)
lines.extend([region("PERMX", aquifer_permx, aquifer_start, reservoir_end), region("FIPLEG", 5, aquifer_start, reservoir_end)])
lines.append(region("PERMX", reservoir_permx, reservoir_start, aquifer_start - 1))
lines.append(region("PERMX", aquifer_permx, aquifer_start, reservoir_end))
lines.append("-- FIPLEG 5: aquifer boundary, the outer coarse-grid columns in reservoir layers")
lines.extend(edge_region("FIPLEG", 5, reservoir_start, reservoir_end))
lines.extend(
[
"",
Expand All @@ -187,10 +194,7 @@ def region(keyword: str, value: object, start: int, end: int) -> str:
"",
"MULTIPLY",
"-- Multiply pore volume on the four outer coarse-grid sides",
f"PORV {porv_multiplier:g} 1 1 1 {ny} 1 {nz} /",
f"PORV {porv_multiplier:g} {nx} {nx} 1 {ny} 1 {nz} /",
f"PORV {porv_multiplier:g} 2 {nx - 1} 1 1 1 {nz} /",
f"PORV {porv_multiplier:g} 2 {nx - 1} {ny} {ny} 1 {nz} /",
*edge_region("PORV", porv_multiplier, 1, nz),
"",
"-- Do not reduce permeability in the water layer",
f"PERMZ {permz_multiplier:g} 1 {nx} 1 {ny} {water_end + 1} {nz} /",
Expand Down
8 changes: 8 additions & 0 deletions src/GaP/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,14 @@ uv run --extra visualization python runscripts/plot_region_fluxes.py \
--output-root cases --zonation FIPLEG --list-vectors
```

Generated coarse grids label `FIPLEG` as: 1 water, 2 overburden, 3 reservoir,
5 aquifer boundary, and 6 the wellbore cells inside the LGR (pipe, open hole
and casing-cement boxes; the rest of the LGR is not labelled 6). Region 5 is the four outer coarse-grid
columns in the reservoir layers, the cells whose pore volume is multiplied
(`porv_multiplier`); the water and overburden edge cells keep 1 and 2. Cases
generated before this definition (region 5 on the bottom `aquifer_layers`) are
not comparable region-for-region.

The list reports the exact keys and the cases exporting each one. If `1-6`
exists but `6-1` does not, only `1-6` is selectable; reversing a pair is not
inferred. Select a pair with `--vector`, repeat it for multiple panels, or
Expand Down
41 changes: 39 additions & 2 deletions tests/gap/test_prepare_init_case_from_xlsx.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import importlib.util
from pathlib import Path
import subprocess
import sys
Expand All @@ -11,6 +12,12 @@
from src.WellClass.libs.well_pressure.pressure_table import PressureTable


PREPARE_PATH = Path(__file__).parents[2] / "runscripts/prepare_init_case.py"
PREPARE_SPEC = importlib.util.spec_from_file_location("prepare_init_case", PREPARE_PATH)
prepare_init_case = importlib.util.module_from_spec(PREPARE_SPEC)
PREPARE_SPEC.loader.exec_module(prepare_init_case)


def _write_minimal_workbook(path: Path, *, salinity: float = 0.032) -> None:
metadata = pd.DataFrame({"key": ["namespace", "name", "author"], "value": ["screen", "xlsx-test", "pytest"]})
header = pd.DataFrame(
Expand Down Expand Up @@ -127,13 +134,43 @@ def test_prepare_init_case_from_xlsx_stages_files(tmp_path):
assert "PERMX 10000 1 20 1 20 1 3 /" in grdecl_text
assert "PERMX 0.001 1 20 1 20 4 18 /" in grdecl_text
assert "PORO 1 1 20 1 20 1 3 /" in grdecl_text
assert "FIPLEG 3 1 20 1 20 19 55 /" in grdecl_text
assert "FIPLEG 5 1 20 1 20 56 58 /" in grdecl_text
assert "FIPLEG 3 1 20 1 20 19 58 /" in grdecl_text
assert "FIPLEG 5 1 1 1 20 19 58 /" in grdecl_text
assert "PERMX 1000 1 20 1 20 56 58 /" in grdecl_text
assert "TRANZ 0 1 20 1 20 19 19 /" in grdecl_text
assert "PERMZ 0.1 1 20 1 20 4 58 /" in grdecl_text


def _boxes(text: str, keyword: str) -> list[tuple[str, ...]]:
return [tuple(line.split()[2:]) for line in text.splitlines() if line.startswith(f"{keyword} ")]


@pytest.mark.parametrize("aquifer_layers", [0, 3])
def test_fipleg_aquifer_marks_reservoir_edge_columns_with_multiplied_pore_volume(tmp_path, aquifer_layers):
root = Path(__file__).parents[2]
grdecl = tmp_path / "grid.grdecl"
grdecl.write_text((root / "test_data/examples/wildcat-pflotran/include/TEMP_GRD.grdecl").read_text(encoding="utf-8"))
prepare_init_case.parameterize_grdecl(
grdecl, water_layers=3, overburden_layers=15, reservoir_layers=40,
aquifer_layers=aquifer_layers, aquifer_permx=250.0,
)
text = grdecl.read_text(encoding="utf-8")
edges = [("1", "1", "1", "20"), ("20", "20", "1", "20"), ("2", "19", "1", "1"), ("2", "19", "20", "20")]
porv = _boxes(text, "PORV")
assert [box[:4] for box in porv] == edges
assert {box[4:] for box in porv} == {("1", "58", "/")}
fipleg5 = [tuple(line.split()[2:]) for line in text.splitlines() if line.startswith("FIPLEG 5 ")]
assert [box[:4] for box in fipleg5] == edges
assert {box[4:] for box in fipleg5} == {("19", "58", "/")}
# Water, overburden and the interior of the reservoir keep their own regions.
assert "FIPLEG 1 1 20 1 20 1 3 /" in text
assert "FIPLEG 2 1 20 1 20 4 18 /" in text
assert "FIPLEG 3 1 20 1 20 19 58 /" in text
assert text.index("FIPLEG 3 1 20 1 20 19 58 /") < text.index("FIPLEG 5 1 1 1 20 19 58 /")
# Physical aquifer properties are unchanged: the bottom layers keep aquifer_permx.
assert ("PERMX 250 1 20 1 20 56 58 /" in text) == bool(aquifer_layers)


def test_workbook_keeps_simulation_assumptions_outside_well_model(tmp_path):
workbook = tmp_path / "well_input.xlsx"
_write_minimal_workbook(workbook)
Expand Down
Loading