Five Spot Pattern Single Phase#
Author: Zakariya Abugrin | Date: May 2025
Introduction#
This tutorial shows how to create a simple regular cartesian grid with a single phase fluid and a five-spot wellsβ pattern. The user can use this tutorial to test the change in pressure distribution based on the rock (i.e. grid) and fluid properties or using a different wellsβ pattern.
*the code used to generate this gif image is shown in Save simulation run as gif section below.
Import reservoirflow
#
We start with importing reservoirflow
as rf
. The abbreviation rf
refers to reservoirflow
where all modules under this library can be accessed. rf
is also used throughout the API documentation. We recommend our users to stick with this convention.
1import reservoirflow as rf
2
3print(rf.__version__)
0.1.0b3
1static = True
2notebook = True
Both
static
andnotebook
are set toTrue
to produce images for this notebook. For an interactive visualization in your local machine, set both arguments toFalse
. To allow for interactive visualizations in Colab, set onlystatic=False
and keepnotebook=True
.
1rf.utils.pyvista.set_mode("dark")
By default,
"dark"
mode is used with a black background. You can change to"light"
mode usingrf.utils.pyvista.set_mode()
function.
Define a regular cartesian grid#
We start with defining the grid model which represents the rock geometry and properties using grids
module. Currently, only RegularCartesian
grid models are supported. In the near future, Radial
and IrregularCartesian
models will be added.
1grid = rf.grids.RegularCartesian(
2 nx=7,
3 ny=7,
4 nz=3,
5 dx=200,
6 dy=200,
7 dz=40,
8 phi=0.30,
9 kx=1,
10 ky=1,
11 kz=1,
12 comp=1e-6,
13 unit="field",
14)
Note
Note that field
units are used in this tutorial. For more information about selecting a unit system or what units or factors are used for each property, see Units & Factors.
The grid object can be shown using show()
method. Argument label
can be used to show cells id or other properties. For more information, check RegularCartesian
.
1grid.show(
2 label="id",
3 boundary=True,
4 static=static,
5 notebook=notebook,
6)

Note
Cells are given an id based on natural order starting form the bottom left corner, see grid.cells_id
. Cells id increase in x from left to right, in y from front to back, and in z from bottom to top. Boundaries are also counted. You can use the cell id to access its properties, or add a well in that location as shown in Add wells section below.
Define a single phase compressible fluid#
After defining the grid model, we can use the fluids
module to define a fluid. A single phase fluid can be defined using SinglePhase
class which accepts arguments such s viscosity (mu), formation volume factor (B), density (rho), compressibility (comp) etc. For more information, check SinglePhase
.
1fluid = rf.fluids.SinglePhase(
2 mu=0.5,
3 B=1,
4 rho=50,
5 comp=1e-5,
6 unit="field",
7)
Create a reservoir simulation model#
To construct a reservoir simulation model, both fluid
and grid
objects are required as input arguments in models
module. In addition, you can also define the initial reservoir pressure (pi), time step duration (dt), start date etc. For more information, check models
. Currently, only BlackOil
models are supported. In the future, more advanced models will be added such as Compositional
or Thermal
.
1model = rf.models.BlackOil(
2 grid=grid,
3 fluid=fluid,
4 pi=3000,
5 dt=1,
6 start_date="01.01.2000",
7 unit="field",
8)
Define boundary conditions#
If you start the simulation run without defining a boundary condition, then all boundaries are set to zero flow rate. To define a boundary condition, you can use set_boundaries()
method where you specify the boundary condition as dictionary where keys are cells id and values are tuple of (βcondβ, value). For example, a constant zero rate boundary condition in cell_id=0
can be set as model.set_boundaries({0: ("rate", 0)})
.
Below we set all the boundaries cells to zero rate (default behavior).
1model.set_boundaries({i: ("rate", 0) for i in grid.get_boundaries("id", fmt="array")})
Add wells#
To make a five spot pattern, single cell wells are used to add wells to specific cells as following:
Injection wells#
4 injectors: 182, 186, 218, 222
1for cell_id in [182, 186, 218, 222]:
2 model.set_well(cell_id=cell_id, pwf=3500, s=0, r=3.5)
Note
Since the bottom hole flowing pressure (BHFP or pwf
) is higher than the initial reservoir pressure, then these wells will have an injection rates (i.e. a positive flow rate means additional fluid is injected into the reservoir). This might not be the expected behavior in reservoir simulation and this might be reversed in the future where injection rates will be expressed as a negative rate (depends on the usersβ feedback). Feel free to comment on this.
Production well#
1 producer: 202
1model.set_well(
2 cell_id=202,
3 pwf=1000,
4 s=0,
5 r=3.5,
6)
Note
In this case, pwf
is lower than the initial reservoir pressure, which will lead to a production rates (i.e. a negative flow rate means a fluid is produced from the reservoir).
Compile the model#
Before you can run the model, you need to compile a solution for it. By compiling a solution, you actually decide the solution you want to use for your model. Interestingly, reservoirflow
provides multiple solutions for the same model based on your configuration.
Hint
Compiling solutions is the most interesting idea introduced in reservoirflow
which allows to solve the same model using different solutions so we can compare them with each other and/or combine them together.
Currently, a numerical
solution based on Finite-Difference-Method (FDM
) is available. You can read more about the available solution in the solutions
. Below, we compile our model using a numerical
solution using FDM
.
1model.compile(stype="numerical", method="FDM", sparse=False)
[info] FDM was assigned as model.solution.
Attention
A model can not be run until it is complied. Methods such as solve()
and run()
will be functioning only after the model is compiled.
Run the model with 40 time steps#
To run the simulation model, use run()
method as shown below. Here, you can set the number of time steps based on nsteps
argument. Note that each time step will have a time duration as defined by dt
argument when the model was constructed.
There are two modes available run the model based on vectorize
argument, True
means vectorized and False
means symbolized. In addition, you can also select a solver which can be direct
, iterative
, or neurical
. Developing solvers based on neural-networks is also a new idea introduced by reservoirflow
.
1model.run(
2 nsteps=40,
3 vectorize=True,
4 isolver=None,
5)
[info] Simulation run started: 40 timesteps.
[info] Simulation run of 40 steps finished in 0.12 seconds.
[info] Material Balance Error: 1.3280299082651936e-10.
Visualize the simulation run in 3D show#
You can interactively show the simulation run in a 3D show using show()
method. There is a lot of functionality here. For interactive visualization, set static
and notebook
to False
. For more information, check the BlackOil.show()
.
Note
Here, boundaries are also added (boundary=True
) but they appear in a gray color. That is because boundaries are defined as a zero flow rate where pressures are undefined (i.e. np.nan
). If boundary conditions were set to constant pressures, values will appear based on the defined color map.
1model.show(
2 prop="pressures",
3 boundary=True,
4 cmap="Blues",
5 static=static,
6 notebook=notebook,
7)

Tip
Use the color maps names as str
with cmap
argument. Matplotlib color maps are supported, for more color maps, check Choosing Colormaps in Matplotlib. In the above image, cmap="Blues"
was used. In Save simulation run as gif section below, cmap="tab10"
was used instead.
Show results as a pandas DataFrame
#
The results of the Simulation run can be accessed as a pandas DateFrame
using get_df()
method. You can select columns
and scale
values. For more information, check BlackOil.get_df()
.
1df = model.get_df(columns=["time", "date", "wells"], units=True).round()
2df
Time [days] | Date [d.m.y] | Qw182 [stb/day] | Qw186 [stb/day] | Qw218 [stb/day] | Qw222 [stb/day] | Qw202 [stb/day] | Pwf182 [psia] | Pwf186 [psia] | Pwf218 [psia] | Pwf222 [psia] | Pwf202 [psia] | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
Step | ||||||||||||
0 | 0 | 01.01.2000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 3000.0 | 3000.0 | 3000.0 | 3000.0 | 3000.0 |
1 | 1 | 02.01.2000 | 55.0 | 55.0 | 55.0 | 55.0 | -222.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
2 | 2 | 03.01.2000 | 54.0 | 54.0 | 54.0 | 54.0 | -217.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
3 | 3 | 04.01.2000 | 53.0 | 53.0 | 53.0 | 53.0 | -213.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
4 | 4 | 05.01.2000 | 53.0 | 53.0 | 53.0 | 53.0 | -210.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
5 | 5 | 06.01.2000 | 52.0 | 52.0 | 52.0 | 52.0 | -208.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
6 | 6 | 07.01.2000 | 52.0 | 52.0 | 52.0 | 52.0 | -207.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
7 | 7 | 08.01.2000 | 51.0 | 51.0 | 51.0 | 51.0 | -205.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
8 | 8 | 09.01.2000 | 51.0 | 51.0 | 51.0 | 51.0 | -204.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
9 | 9 | 10.01.2000 | 51.0 | 51.0 | 51.0 | 51.0 | -203.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
10 | 10 | 11.01.2000 | 51.0 | 51.0 | 51.0 | 51.0 | -203.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
11 | 11 | 12.01.2000 | 50.0 | 50.0 | 50.0 | 50.0 | -202.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
12 | 12 | 13.01.2000 | 50.0 | 50.0 | 50.0 | 50.0 | -201.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
13 | 13 | 14.01.2000 | 50.0 | 50.0 | 50.0 | 50.0 | -201.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
14 | 14 | 15.01.2000 | 50.0 | 50.0 | 50.0 | 50.0 | -200.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
15 | 15 | 16.01.2000 | 50.0 | 50.0 | 50.0 | 50.0 | -200.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
16 | 16 | 17.01.2000 | 50.0 | 50.0 | 50.0 | 50.0 | -200.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
17 | 17 | 18.01.2000 | 50.0 | 50.0 | 50.0 | 50.0 | -199.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
18 | 18 | 19.01.2000 | 50.0 | 50.0 | 50.0 | 50.0 | -199.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
19 | 19 | 20.01.2000 | 50.0 | 50.0 | 50.0 | 50.0 | -199.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
20 | 20 | 21.01.2000 | 50.0 | 50.0 | 50.0 | 50.0 | -199.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
21 | 21 | 22.01.2000 | 50.0 | 50.0 | 50.0 | 50.0 | -198.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
22 | 22 | 23.01.2000 | 50.0 | 50.0 | 50.0 | 50.0 | -198.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
23 | 23 | 24.01.2000 | 50.0 | 50.0 | 50.0 | 50.0 | -198.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
24 | 24 | 25.01.2000 | 50.0 | 50.0 | 50.0 | 50.0 | -198.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
25 | 25 | 26.01.2000 | 50.0 | 50.0 | 50.0 | 50.0 | -198.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
26 | 26 | 27.01.2000 | 50.0 | 50.0 | 50.0 | 50.0 | -198.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
27 | 27 | 28.01.2000 | 50.0 | 50.0 | 50.0 | 50.0 | -198.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
28 | 28 | 29.01.2000 | 49.0 | 49.0 | 49.0 | 49.0 | -197.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
29 | 29 | 30.01.2000 | 49.0 | 49.0 | 49.0 | 49.0 | -197.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
30 | 30 | 31.01.2000 | 49.0 | 49.0 | 49.0 | 49.0 | -197.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
31 | 31 | 01.02.2000 | 49.0 | 49.0 | 49.0 | 49.0 | -197.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
32 | 32 | 02.02.2000 | 49.0 | 49.0 | 49.0 | 49.0 | -197.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
33 | 33 | 03.02.2000 | 49.0 | 49.0 | 49.0 | 49.0 | -197.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
34 | 34 | 04.02.2000 | 49.0 | 49.0 | 49.0 | 49.0 | -197.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
35 | 35 | 05.02.2000 | 49.0 | 49.0 | 49.0 | 49.0 | -197.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
36 | 36 | 06.02.2000 | 49.0 | 49.0 | 49.0 | 49.0 | -197.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
37 | 37 | 07.02.2000 | 49.0 | 49.0 | 49.0 | 49.0 | -197.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
38 | 38 | 08.02.2000 | 49.0 | 49.0 | 49.0 | 49.0 | -197.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
39 | 39 | 09.02.2000 | 49.0 | 49.0 | 49.0 | 49.0 | -197.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
40 | 40 | 10.02.2000 | 49.0 | 49.0 | 49.0 | 49.0 | -197.0 | 3500.0 | 3500.0 | 3500.0 | 3500.0 | 1000.0 |
Save simulation run as gif#
You can save gif movie of the simulation run using save_gif()
method. For more information, check the BlackOil.save_gif()
.
1if not static:
2 model.save_gif(
3 prop="pressures",
4 boundary=False,
5 cmap="tab10",
6 file_name="attachments/grid_animated.gif",
7 window_size=rf.utils.pyvista.get_window_size("hd"),
8 )
Tip
To save a gif movie of the simulation run, set static=False
or just remove the first line and unindent.
Comments π¬#
Feel free to make a comment, ask a question, or share your opinion about this specific content. Please keep in mind the Commenting Guidelines β.