Reservoir Simulation in Excel
Introduction
Petroleum Office runs reservoir simulation models from worksheet functions. A model is an ECLIPSE deck written as a table in the workbook. The PO.SIM.OPM functions hand it to OPM Flow, the open-source reservoir simulator installed with Petroleum Office, and spill the results back: summary series per report step, and cell and well states.
The engine runs outside Excel, in its own program. Excel stays responsive while a model runs, a crash in the engine can't take a workbook down, and 32-bit Excel can use it too.
The Deck table
A deck is a range with six columns under a header row:
| Section | Keyword | Statement | Record | Parameter | Value |
|---|---|---|---|---|---|
| RUNSPEC | TITLE | 1 | 1 | 1 | SPE1 - CASE 1 |
| RUNSPEC | DIMENS | 1 | 1 | 1 | 10 |
| RUNSPEC | DIMENS | 1 | 1 | 2 | 10 |
| RUNSPEC | DIMENS | 1 | 1 | 3 | 3 |
| RUNSPEC | OIL | ||||
| RUNSPEC | FIELD | ||||
| SCHEDULE | WELSPECS | 1 | 1 | 1 | 'PROD' |
| SUMMARY | FOPR |
- Section is the ECLIPSE section: RUNSPEC, GRID, EDIT, PROPS, REGIONS, SOLUTION, SUMMARY or SCHEDULE.
- Keyword is the keyword, as the deck writes it.
- Statement numbers repeats of a keyword within its section (1 when blank).
- Record and Parameter count the keyword's records and items from 1.
- Value is the item as the deck writes it:
10,8400.0,'PROD',300*1000,1*.
A flag keyword such as OIL, FIELD, or a field SUMMARY vector is a row with no record, parameter or value. Blank rows are skipped. A deck that names no unit keyword is METRIC, as ECLIPSE and Flow read it.
Excel takes a leading apostrophe as its own text prefix, so typing 'PROD' into a cell stores PROD'. The functions put the quote back.
The functions
PO.SIM.OPM.CHECK(deck)reads the deck in Excel and starts nothing, so it works without the engine installed.- Its first line is
No problems found.or a count of problems. - Then the deck's title, units, grid size, wells, number of SUMMARY series and rows.
- Then each problem on its own line: no DIMENS, no TSTEP or DATES to march, no SUMMARY series, or a SUMMARY well vector naming a well WELSPECS doesn't define.
- Its first line is
PO.SIM.OPM.SUMMARY(deck)spillsday, then the SUMMARY series in the section's order, a row per report step. Each kind of vector gets its own column or columns:- a field vector is one column (
FOPR); - a well vector gets one per well (
WBHP:PROD), or every WELSPECS well when the record names none; - a group or region vector gets one per group or region;
- a block vector gets one per i j k record (
BPR:1,1,1); - a connection vector gets one per well and connection (
COPR:PROD:10,10,3).
- a field vector is one column (
PO.SIM.OPM.RUN(deck)spills the end state askind | day | i | j | k | pressure | name | bhp:- a
cellrow per active cell, with its pressure; - a
wellrow per WELSPECS well, with its bottom-hole pressure; - where inactive cells hide a cell's position,
iis the active cell's number.
- a
PO.SIM.OPM.STATE0(deck)spills the same at day 0. Before the first step a well under bottom-hole-pressure control shows its scheduled target. Use it to check an initialisation before a long run.PO.SIM.OPM.DATA(deck)spills the deck as the ECLIPSE text the engine's own parser writes, one line per cell. Copy it into a.DATAfile to run the same model elsewhere.PO.SIM.OPM.VECTORS(deck)spills SUMMARY's series long, ready to filter, pivot or chart:vector | identifier | unit | day | value, a row per series per report step. The identifier is what the vector is of (PROD,1,1,1), blank for a field vector. The unit follows the deck's system (stb/day,psia,Mscf/stbfor FIELD), and is blank for a fraction or a vector it doesn't know. It reuses SUMMARY's run.PO.SIM.OPM.GRID(deck, properties, days)spills cell properties long:property | i | j | k | unit | day | value.propertiesnames any of PRESSURE, SWAT, SGAS, SOIL, RS and RV. By default it is PRESSURE plus what the deck's phases carry: SWAT and SGAS with more than one phase, RS with DISGAS.daysnames report days. Day 0 is the initial state, and the default is day 0 and the last report day.- A request that would overflow a sheet is refused before the run, with a hint to name fewer days or properties.
Results are in the deck's own units: a FIELD deck spills psia and stb/day, a METRIC deck bar and sm3/day, a LAB deck atm.
A deck from input tables
Typing every keyword, record and item is slow. The PO.SIM.DECK functions build the Deck table from a few small tables instead, in FIELD units, and run in Excel without starting the engine:
PO.SIM.DECK.BUILD(spec, grid, pvt, scal, wells, schedule)spills a complete deck. The spec'sPHASESpicks the family:single-phase(water),oil water, oroil water gas(black oil). Only black oil reads thepvtandscaltables; leave them empty otherwise.PO.SIM.DECK.CHECK(...)takes the same tables and lists what is wrong with each, or what the built deck's checks find, orNo problems found.PO.SIM.DECK.RUNSPEC,GRID,PROPS,SUMMARYandSCHEDULEspill one section each, andPO.SIM.DECK.COMBINE(a, b)joins two fragments. It refuses an item the two give different values, and fragments of different phases.PO.SIM.DECK.MAPGRID(spec, maps, wells, schedule)builds a single-phase corner-point deck between a top and a base map given at the pillar nodes.
The tables:
| Table | Columns |
|---|---|
| Spec | key and value, one row per value: PHASES, an optional TITLE, DIMENS.NX, DX, TOPS, the fluids by phase (WATER.MU, WATER.RHO, OIL.C, GAS.RHO), the rock (ROCK.C, ROCK.PREF), and the start (INIT.PRESSURE, INIT.SW) |
| Grid | i, j, k, property, value, a row per cell and property; or poro, permx, permy, permz (and ntg), one row for every cell or one per cell with i, j, k |
| Pvt | kind, pressure, rs, bo, muo, bg, mug: oil rows (saturated), undersaturated branch rows, gas rows |
| Scal | saturation, krw, krow, krg, krog |
| Wells | name, type, i, j, k1, k2 |
| Schedule | day, well, control, target, limit: a row per control a well takes from a day on (bhp, rate with an optional bottom-hole pressure limit, or gasrate for a gas injector), and report rows for the days a report step ends; without them, the Spec's TSTEP.DAYS and TSTEP.COUNT set uniform steps |
Every family reads the same Grid and Schedule, and names a quantity by the same Spec key, so a book changes family by changing PHASES and adding what the new family reads. A key only another family reads is kept, and PO.SIM.DECK.CHECK says which family reads it. TITLE names the deck's cases in the OPM Flow Simulator pane.
The tables began as the wildcat simulator's, whose Spec keys and Schedule columns differed by family. Its keys still read: FLUID.MUW and PVTW.MUW as WATER.MU, DENSITY.OIL as OIL.RHO, PVTO.CO as OIL.C, ROCK.CR as ROCK.C, and so on, and PO.SIM.DECK.CHECK names each one's new key. Its Schedule columns step, name, mode and bhplimit are refused, with the column that replaced each.
A built deck feeds the PO.SIM.OPM functions as it is: =PO.SIM.OPM.SUMMARY(PO.SIM.DECK.BUILD(B2:C30, E2:I3, , , K2:P4, R2:V12)).
A .DATA file as the deck
Every function also takes one cell naming an ECLIPSE .DATA file, such as C:\Models\SPE1CASE1.DATA, in place of a Deck range. The engine reads the file and the files it INCLUDEs. A model built in another simulator then runs without being typed in as a table.
An existing model's SUMMARY section often names series the engine doesn't report: a user-defined region set, or a count of wells. For a .DATA deck those are left out of SUMMARY and VECTORS. A Deck range is held to every series it names, so a typo there shows as an error rather than a missing column.
How runs work
A cell that runs a deck shows its progress while it waits: received once Excel has read the deck, validated once Excel's checks and Flow's own reading of it have passed, then queued, starting, running step k of N, and reading the result before it spills. A deck either of them refuses shows why instead. The pane names each stage the same way. Runs go one at a time, in order, whichever Excel or workbook asked. The OPM Flow Simulator pane has four tabs. Cases lists every case on this computer by the workbook that asked for it (another Excel's under its own heading), running, waiting and finished, with the selected case's diagnostics. Summary plots a finished case's vector over time, its history vector as dots. Grid draws a grid property in 3D or by layer, stepping through the report steps the run kept. Teaching books opens worked examples. Cancel run stops a waiting or running case; its cells say it was cancelled, and a recalc runs it again.
Runs are kept by the deck's content. SUMMARY, RUN and STATE0 on one deck share a single run, and recalculating a sheet whose deck hasn't changed returns the kept result at once. Change any cell of the deck and the next calculation runs it again.
A deck the engine refuses, or a run that fails, shows the reason in the cell as text. When the engine can't be reached the cell shows an error value, and a recalculation tries again.
Pipes and networks in Marlim3
Marlim3, Petrobras's open-source multiphase flow simulator for wells, pipelines and networks, runs from the same kind of range. Its deck uses the network dialect, which has the same six columns but different sections:
RUNSPEC: the phases, units (SI,METRICorFIELD) andSPECkeys such asSYSTEM INJ.NETWORK: the graph.VERTEXandEDGE(from, to, length, diameter, roughness, elevations), plusPROPERTYrows for materials and cross-sections.PROPS: the fluid.SOLUTION: the initial pressure.SCHEDULE: the terminals (TERMSPEC) and their rate or pressure (TCON).
| Function | What it returns |
|---|---|
PO.SIM.MR3.CHECK |
Whether Marlim3 can run the deck: its title, units and run kind, its problems, and every value the binding supplies or replaces. Starts nothing. |
PO.SIM.MR3.SUMMARY |
One row per branch: branch, day, liquid rate, the pressure where the branch starts and ends, and the developed friction gradient |
PO.SIM.MR3.PROFILE |
Long form, branch property duct cell length unit day value: pressure, hydrostatic and friction gradients and elevation along the pipe, a cell value at the cell's centre and a face value at its upstream face |
PO.SIM.MR3.TREND |
Long form, station length day property unit value: the variables a TREND station records at one point on the line |
PO.SIM.MR3.RUN |
How the run went: the engine, the run kind, the time it took, how a network converged and its branches, and the binding's notes |
PO.SIM.NET.BUILD |
The network Deck from input tables: spec, vertices, edges and terminals, with properties, schedule, groups and connections when the case has them. Runs in Excel and starts nothing. |
PO.SIM.NET.CHECK |
The input tables' problems, naming the table and the row, or No problems found. |
Building the deck from tables. Most cases are easier to write as tables than as keyword rows. The vertices and edges of the graph go one row each, the terminals hold the boundary rates and pressures, and long element | kind | key | value rows hold the materials, cross-sections and equipment. =PO.SIM.MR3.SUMMARY(PO.SIM.NET.BUILD(...)) runs them as they are.
What happens to the deck. The deck is turned into Marlim3's input by the same binding Team 76's network tools use, and the binding fills what the deck leaves out. It also replaces a few stated values the engine can't take. An injection branch, for example, runs at a fixed 50 kgf/cm² wellhead pressure whatever its outlet says. CHECK and RUN list every such value, so read them before trusting a pressure.
How runs work. Marlim3.exe runs on this computer as a separate program, one case at a time, and a case takes tens to hundreds of milliseconds. SUMMARY, PROFILE and RUN on one deck share a run. A recalc with the deck unchanged answers from memory. The Marlim3 pane (the Simulators group on the ribbon) lists this Excel's runs: waiting, running, finished, failed or cancelled. Each shows its deck, the input the engine ran, what the binding supplied, and the engine's log. Cancel stops a run, and a recalc runs it again. The pane's Teaching books add worked pipes to the open workbook, one sheet each.
Results. Results come back in the deck's units: pascals and metres under SI, bar under METRIC, psia and feet under FIELD. Liquid rate is in sm³/d in every system.
Networks of branches. A deck with DOCUMENT rede is a network: PROPERTY rows put each edge on a branch file (edge | branch | upstream.json), and CONNECTION rows join the branches, a collector and the branches that feed it. Marlim3 solves the branches together as one REDE run and iterates the pressure at each join until both sides agree within its convergence limit. SUMMARY and PROFILE spill a row per branch, named by its file (upstream), and a single-branch deck's one branch is main. RUN says whether the network converged; a network that does not solve says so in words instead of spilling. The Two-branch network teaching book is a worked case.
Trend stations. A profile is the whole line at one time; a trend is one point on the line over time. A TREND row in the NETWORK section names a station: its id, the line (PROD), an edge and a distance from that edge's start, or a length along the branch, a label, the sampling interval dt in seconds, and the variables to record, such as OUT PROD E4 200 1* Outlet 60 'pressure frictionPressureGradient' /. PO.SIM.MR3.TREND spills each station's samples, and its station argument picks one by label or by length. A trend has these limits today:
- Marlim3 3.8.1 records a station only on a production branch (
SPEC SYSTEM PROD). On an injection branch the engine writes no trend file, and TREND says so; CHECK and RUN carry the binding's note about it. - A case is run at t = 0, so a trend holds one sample until the binding runs a case over time.
- A network of branches (
DOCUMENT rede) does not bindTRENDrows. SERVICEstations are written to the engine's input but have not yet been checked against a run.
Limits. A case is solved at steady state, t = 0. A network runs as one steady REDE solve; transient networks are not supported.
Water chemistry and scale in PHREEQC
PHREEQC, the USGS geochemical code, answers what a water is and what it will do: its speciation, which minerals it is saturated with, and what precipitates when waters mix, cool or lose CO2. The input is PHREEQC's own language, written in a range one line per row, as the PHREEQC manual writes it. A cell's contents join its row with tabs, so SOLUTION_SPREAD laid out one value per cell reads as PHREEQC expects. The database is an argument, not a line of the input: phreeqc, wateq4f, llnl, pitzer, sit or iso.
| Function | What it does |
|---|---|
PO.SIM.PHREEQC.CHECK |
Reads the input against the database without running it: an element or phase the database doesn't define, a block missing what it needs (KINETICS without -steps), a line PO refuses (DATABASE, INCLUDE$), each by its cell. |
PO.SIM.PHREEQC.RUN |
Runs it, and says which database and version ran, its tables and its warnings. |
PO.SIM.PHREEQC.SELECTED |
Spills a SELECTED_OUTPUT table as PHREEQC writes it. |
PO.SIM.PHREEQC.SI |
Spills the saturation indices long form, a row per result and phase, with any values the input's USER_PUNCH writes beside each. |
PO.SIM.PHREEQC.BUILD |
Builds the input from tables: water analyses, the minerals to watch, and a mixing series or a temperature and pressure path. |
PO.SIM.PHREEQC.TABLES.CHECK |
Checks those tables, naming the table and the cell, and warns on an analysis out of charge balance. |
Reading a saturation index. SI = log(IAP/K). Above zero a mineral is supersaturated and can scale; below zero the water would dissolve it; at zero the two are in equilibrium. PHREEQC writes −999.999 where it can't compute one, such as barite in a water with no barium.
Which database. The database's activity model decides how far an answer can be trusted:
- pitzer for brines: formation waters and anything above about seawater strength. The Pitzer model holds to several molal, and it is the one for barite, celestite and halite in produced water.
- phreeqc, wateq4f or llnl for fresh to seawater-strength waters. Their ion-association model drifts as ionic strength rises past about 1 mol/kgw. llnl carries the most species and phases.
- sit, the specific-interaction model, holds to about 3–4 mol/kgw; sit.dat comes from the ThermoChimie database and is strongest for radionuclides and the minerals around them. iso is for isotopes.
The same input against two databases is two runs, and can give two answers; the Seawater into formation water teaching book runs barite against both.
How runs work. Each run is a separate program on this computer, one case at a time, and most take tens of milliseconds. A recalc of an unchanged input and database answers from memory, and functions over one input share a run. An input CHECK refuses never starts the engine; RUN, SELECTED and SI spill the same refusal. A run that takes more than two minutes is stopped and said so in words. The PHREEQC pane lists this Excel's runs by workbook, with each one's findings and verdict and Cancel. Its Waters tab shows a finished case's waters with their saturation indices as bars, and its Results tab plots a result along the case's mixing fraction, temperature or steps. Its Teaching books open three worked studies: seawater into formation water, calcite in a cooling well, and gypsum and anhydrite against temperature.
References
- Rasmussen, A. F., et al. (2021). The Open Porous Media Flow reservoir simulator. Computers & Mathematics with Applications, 81, 159–185.
- Odeh, A. S. (1981). Comparison of Solutions to a Three-Dimensional Black-Oil Reservoir Simulation Problem. Journal of Petroleum Technology, 33(1), 13–25.
- Parkhurst, D. L., and Appelo, C. A. J. (2013). Description of Input and Examples for PHREEQC Version 3. U.S. Geological Survey Techniques and Methods, book 6, chap. A43.
- Pitzer, K. S. (1973). Thermodynamics of electrolytes. I. Theoretical basis and general equations. Journal of Physical Chemistry, 77(2), 268–277.