Chapter 7 — Naming a set of wells
A dated change acts on a selection: the well= of ps.Shut, ps.Open, ps.Produce,
ps.Adjust and their siblings. There are four different things a selection can be. They are
not four spellings of one idea; they differ in when the set is known, and that difference
is the whole chapter.
producers = [ps.Well(name=f"PROD{n}", i=n, j=1) for n in (1, 2, 3)]trio = model.replace( name="TRIO", grid=ps.grid.Cartesian(shape=(3, 1, 1), dx=500.0, dy=500.0, dz=40.0, top=2500.0), rock=model.rock.replace(porosity=0.25), wells=producers, schedule=ps.Schedule(end="2020-06-01", report="monthly", events=[ ps.Event("2020-01-01", *(change for well in producers for change in ( ps.Perforate(well=well.name, k=(1, 1), diameter=0.2), ps.Produce(well=well.name, oil_rate=100.0, bhp_limit=500.0), )))]),)
def schedule_of(model: ps.Model) -> str: """The SCHEDULE section of the deck PetroScript writes for ``model``."""
return ps.compile(model).files[f"{model.name}.DATA"].split("\nSCHEDULE\n")[1]The wells you name. The ordinary case: the set is known while you are writing. One name, or a list of them, and the card names each well:
named = trio.at("2020-02-01", ps.Shut(well=["PROD1", "PROD2"]))assert "WELOPEN\n 'PROD1' 'SHUT' /\n 'PROD2' 'SHUT' /\n/" in schedule_of(named)A pattern (well="PROD*") is written as the pattern, and the run matches it against the
wells it holds when the card acts. A string in well= is always about wells, even when it
leads with an asterisk: well="*OD1" is the wells whose names end in OD1, and the deck
gets it as '\*OD1', with the backslash the deck needs there (a leading asterisk alone would
name a list).
The list that holds them. A well list is a named set declared in the schedule, so that
a card can address the set instead of repeating itself per well. Declaring it is a dated
change, ps.wells.WellList, and a later statement names the list by the list itself:
prods = ps.wells.WellList(name="PRODS", wells=["PROD1", "PROD2"])listed = trio.at("2020-02-01", prods)listed = listed.at("2020-03-01", ps.Shut(well=prods))text = schedule_of(listed)assert "WLIST\n '*PRODS' NEW 'PROD1' 'PROD2' /" in textassert "WELOPEN\n '*PRODS' 'SHUT' /" in textWhere the list object is not at hand, name it by its name: well=ps.actions.well(lists= "PRODS"), the same spelling a rule’s condition uses. The deck writes a list as '*PRODS',
with the leading asterisk; in Python that asterisk is never needed, because a string is
always about wells.
Note what the shut writes. It writes '*PRODS' 'SHUT', one record, naming the list. It does
not write two records naming PROD1 and PROD2. That is not a formatting preference. A
list is edited as the run goes on, so “shut the wells in PRODS” and “shut PROD1 and PROD2”
are different instructions that happen to agree on the day you wrote them, and only the
first one keeps agreeing.
Editing a list is its own act, and it says which act it is: action="add" extends a list an
earlier statement opened, "delete" takes the named wells out of it, and "move" takes
them out of whatever list holds them and puts them in this one. "new", the default, opens
it. An edit that names a list nothing has declared is refused with a code, not a silent new
list:
extended = listed.at("2020-04-01", ps.wells.WellList(name="PRODS", wells=["PROD3"], action="add"))assert "WLIST\n '*PRODS' ADD 'PROD3' /" in schedule_of(extended)ps.testing.assert_issue( lambda: trio.at("2020-02-01", ps.wells.WellList(name="NOPE", wells=["PROD3"], action="add")), code="schedule.well_list.undeclared",)The lists whose names fit. A card may address a family of lists rather than one, by a
pattern over list names: well=ps.actions.well(lists="PROD?") names every list whose name
is PROD and one more character, and the deck gets '*PROD?'. It is written as stated, and the run resolves it at the step the
card acts, when a list may have been declared, edited or emptied since you wrote it. “The
lists called PROD-something” and “the lists called PROD-something today” are different
instructions, and only the first one keeps agreeing.
The wells that fired. Inside an operating rule the interesting set is often the one the
condition selected, and there is nothing to name, because the set is decided by the run at
the time step the rule fires. A rule’s changes name it well="?":
low = ps.actions.When(ps.actions.well().bottom_hole_pressure < 900.0, name="LOWBHP", then=[ps.Shut(well="?")], runs=10)ruled = trio.at("2020-02-01", low)assert "ACTIONX\n LOWBHP 10 /\n WBHP '*' < 900 /\n/\nWELOPEN\n '?' 'SHUT' /" in schedule_of(ruled)ps.actions.well() with no well is the condition over every well, and the rule states how
many times it may fire: the simulator fires a rule once by default, so only the first well
to cross would change. "?" is the one selection that cannot be resolved at any position in
the deck, which is exactly why the other three are not resolved there either. Outside a
rule’s then= it is refused, and the refusal is the true sentence: nothing fired, so there
is no set.
try: trio.at("2020-02-01", ps.Shut(well="?"))except ps.PetroScriptError as error: assert "'?' is the wells whose rule condition fired" in str(error)else: raise AssertionError("'?' was read outside a rule")What this buys you when you read a deck back
Section titled “What this buys you when you read a deck back”The four selections survive a round trip as themselves. Load a deck that declares a list and
the model holds a ps.wells.WellList; compile it and the list is a list again, with the act
that declared it, and a card that named the list still names it:
ps.compile(listed).write("listed")back = ps.load("listed/TRIO.DATA")assert back.get("schedule/2020-02-01/'*PRODS'/well_list") == ps.wells.WellList( name="PRODS", wells=["PROD1", "PROD2"])assert "WELOPEN\n '*PRODS' 'SHUT' /" in schedule_of(back)A pattern comes back a pattern. This matters most when you are reading somebody else’s deck: the compiled deck tells you what the deck said, not what the loader decided it must have meant.
PetroScript is licensed under the Apache License 2.0. © 2026 Blauweiss EDV LLC. PetroScript by Blauweiss AI.For AI agents: llms.txt