One model, two simulators
"""One model, two simulators: compile SPE1 for OPM Flow and for JutulDarcy, run both, compare.
SPE1 (Odeh, 1981) is the industry's first comparative-solution benchmark: gas injected into athree-phase black oil. This is its FIELD-unit case, SPE1CASE2. Both targets take it whole:JutulDarcy's capability table refuses nothing in it, and JutulDarcy runs PetroScript's deck exactlyas it runs the deck's own source. One model compiles to two decks. Each run is read by the samenames, in the same units, so comparing them is a join on the date. The difference between theengines is reported, never judged here: two solvers on one model differ a little, most where awell switches control.
Flow keywords it exercises: the whole of SPE1CASE2 (PVTO, PVDG, SGOF, EQUIL, WCONPROD, WCONINJE),plus FOPT in the summary.
Run it with ``python examples/two_targets.py [DECK]``. DECK defaults to SPE1CASE2 in the opm-testscorpus at ``opm-tests/``; it writes ``out/two_targets/``. Flow is found through``PETROSCRIPT_FLOW`` or ``flow`` on PATH, JutulDarcy through ``PETROSCRIPT_JUTULDARCY``."""
import sys
import petroscript as ps
path = sys.argv[1] if len(sys.argv) > 1 else "opm-tests/spe1/SPE1CASE2.DATA"m = ps.load(path)m = m.replace(summary=m.summary.replace(field=[*m.summary.field, "oil_production_total"]))
decks = {target: ps.compile(m, target) for target in ("opm-flow", "jutuldarcy")}for target, deck in decks.items(): deck.write(f"out/two_targets/{target}") print(target, "deck:", ", ".join(deck.files))
found = {simulator.name for simulator in ps.simulators()}runs = {t: d.run(folder=f"out/two_targets/{t}/run") for t, d in decks.items() if t in found}if not runs: print("no simulator found (set PETROSCRIPT_FLOW and PETROSCRIPT_JUTULDARCY); the decks are in " "out/two_targets") sys.exit(0)for target in decks.keys() - runs.keys(): print(f"{target}: no runner found, so there is nothing to compare its run with")
def series(run, quantity, well=None): """One quantity by date: the field's, or one well's."""
frame = run.field(quantity) if well is None else run.wells(quantity).filter(well=well) return frame.select("date", "value", "unit")
asked = [ ("oil rate", "oil_production_rate", None), ("cumulative oil", "oil_production_total", None), ("PROD BHP", "bottom_hole_pressure", "PROD"),]if len(runs) == 1: (target, run), = runs.items() for label, quantity, well in asked: last = series(run, quantity, well).row(-1) print(f"{target} {label} at the end: {last[1]:.1f} {last[2]}") sys.exit(0)
flow, jutul = runs["opm-flow"], runs["jutuldarcy"]print(f"{'date':10} {'what':14} {'OPM Flow':>14} {'JutulDarcy':>14} {'diff':>7}")for label, quantity, well in asked: # Flow writes every time step, JutulDarcy each report: compare on JutulDarcy's report dates both = series(jutul, quantity, well).join(series(flow, quantity, well), on="date", suffix="_flow") both = both.with_columns(diff=(both["value"] - both["value_flow"]) / both["value_flow"].abs()) shown = both.select("date", "value", "unit", "value_flow", "diff").gather_every(24) for date, value, unit, flowed, diff in shown.iter_rows(): print(f"{date:%Y-%m-%d} {label:14} {flowed:14.1f} {value:14.1f} {diff:7.2%} {unit}") worst = both["diff"].abs().max() print(f"largest difference, {label}: {worst:.2%} over {both.height} report dates")PetroScript is licensed under the Apache License 2.0. © 2026 Blauweiss EDV LLC. PetroScript by Blauweiss AI.For AI agents: llms.txt