Skip to content

Commit 5d1f3cc

Browse files
committed
Add chapter 14
1 parent 82d5391 commit 5d1f3cc

File tree

4 files changed

+226
-4
lines changed

4 files changed

+226
-4
lines changed

14-energy.qmd

Lines changed: 224 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,225 @@
1-
# Petroleum reservoirs 🚧
1+
# Petroleum reservoirs
22

3-
## Coming soon...
3+
```{julia}
4+
#| echo: false
5+
#| output: false
6+
import Pkg
7+
Pkg.activate(".")
8+
```
9+
10+
Petroleum reservoirs present various modeling challenges related to
11+
their complex geometry and distribution of rock and fluid properties.
12+
Some of these challenges are still open in industry given the lack of
13+
software for advanced geospatial data science with unstructured meshes.
14+
In this chapter, we will illustrate how a very important "oil-in-place"
15+
calculation in reservoir management can be automated with the framework.
16+
17+
**TOOLS COVERED:** `@groupby`, `@transform`, `@combine`, `Unitify`, `Unit`,
18+
`GHC`, `volume`, `viewer`
19+
20+
**MODULES:**
21+
22+
```{julia}
23+
# framework
24+
using GeoStats
25+
26+
# IO modules
27+
using GeoIO
28+
29+
# viz modules
30+
import CairoMakie as Mke
31+
```
32+
33+
```{julia}
34+
#| echo: false
35+
#| output: false
36+
Mke.activate!(type = "png")
37+
```
38+
39+
::: {.callout-note}
40+
41+
Although we use CairoMakie.jl in this book, many of the 3D visualizations
42+
in this chapter demand a more performant Makie.jl backend. Consider using
43+
GLMakie.jl if you plan to reproduce the code locally.
44+
45+
:::
46+
47+
## Data
48+
49+
We will use reservoir simulation results of the Norne benchmark case, a real
50+
oil field from the Norwegian Sea. For more information, please check the
51+
[OPM project](https://opm-project.org). These results were simulated with
52+
the [JutulDarcy.jl](https://github.com/sintefmath/JutulDarcy.jl) reservoir
53+
simulator by @Moyner2025.
54+
55+
In particular, we will consider only two time steps of the simulation, named
56+
`norne1.vtu` and `norne2.vtu`. The data are stored in the open VTK format with
57+
`.vtu` extension, indicating that it is georeferenced over an unstructured mesh:
58+
59+
```{julia}
60+
norne₁ = GeoIO.load("data/norne1.vtu")
61+
norne₂ = GeoIO.load("data/norne2.vtu")
62+
63+
norne₁ |> viewer
64+
```
65+
66+
::: {.callout-note}
67+
68+
The [vtk.org](https://vtk.org) website provides official documentation for
69+
the various VTK file formats, including formats for image data (`.vti`),
70+
rectilinear grids (`.vtr`), structured grids (`.vts`), unstructured meshes
71+
(`.vtu`), etc.
72+
73+
:::
74+
75+
## Objectives
76+
77+
The Volume of Oil In Place ($VOIP$) is a global estimate of the volume of oil
78+
trapped in the subsurface. It is defined as an integral over the volume $V$
79+
of the reservoir:
80+
81+
$$
82+
VOIP = \int_V S_o \phi\ dV
83+
$$
84+
85+
where $\phi$ is the rock porosity and $S_o$ is the oil saturation. The integrand
86+
can be converted into Mass of Oil in Place ($MOIP$) using the oil density $\rho_o$:
87+
88+
$$
89+
MOIP = \int_V \rho_o S_o \phi\ dV
90+
$$
91+
92+
Likewise, the Mass of Water In Place ($MWIP$) and Mass of Gas in Place ($MGIP$)
93+
are defined using the respective fluid saturations and densities.
94+
95+
Our main objective is to estimate these masses of fluids in place over a reservoir model
96+
with non-trivial geometry, for different time steps within a physical reservoir simulation.
97+
This can be useful to understand rates of depletion and guide reservoir management.
98+
99+
Secondary objectives include the localization (through 3D visualization) of zones with high
100+
mass of hydrocarbons (oil + gas), and the calculation of zonal depletion, i.e., the change
101+
of hydrocarbon mass per zone, from a time step $t_1$ to a time step $t_2$:
102+
103+
$$
104+
Depletion = \left\{MOIP + MGIP\right\}_{t_1} - \left\{MOIP + MGIP\right\}_{t_2}
105+
$$
106+
107+
## Methodology
108+
109+
In order to identify zones of the reservoir with high mass of hydrocarbons, we need to
110+
compute the fluids in place for each element of the mesh, and group the elements based
111+
on their calculated masses. Given the zones, we can compute the zonal depletion.
112+
113+
The proposed methodology has the following steps:
114+
115+
1. Analysis of oil, gas and water in place
116+
2. Localization of hydrocarbon zones
117+
3. Calculation of zonal depletion
118+
119+
### Fluid analysis
120+
121+
Before we start our calculations, we need to rename the variables in the dataset to
122+
match our concise notation. We also need to correct the units of the variables to
123+
make sure that our final report has values that are easy to read.
124+
125+
The following pipeline performs the desired cleaning steps by exploiting bracket
126+
notation (e.g., `[kg/m^3]`) for units. The `Unitify` transform takes a geotable
127+
with bracket notation as input and converts the values of columns to unitful
128+
values:
129+
130+
```{julia}
131+
clean = Select(
132+
"porosity" => "ϕ",
133+
"saturation_oil" => "So",
134+
"saturation_gas" => "Sg",
135+
"saturation_water" => "Sw",
136+
"density_oil" => "ρo [kg/m^3]",
137+
"density_gas" => "ρg [kg/m^3]",
138+
"density_water" => "ρw [kg/m^3]"
139+
) → Unitify()
140+
```
141+
142+
The resulting geotable has variables with concise names and correct units:
143+
144+
```{julia}
145+
reservoir₁ = clean(norne₁)
146+
reservoir₂ = clean(norne₂)
147+
```
148+
149+
We `@transform` the reservoir and compute masses of fluids for each
150+
element of the mesh using the formulae in the beginning of the chapter:
151+
152+
```{julia}
153+
mass(reservoir) = @transform(reservoir,
154+
:MOIP = :ρo * :So * :ϕ * volume(:geometry),
155+
:MGIP = :ρg * :Sg * :ϕ * volume(:geometry),
156+
:MWIP = :ρw * :Sw * :ϕ * volume(:geometry)
157+
)
158+
159+
mass₁ = mass(reservoir₁)
160+
mass₂ = mass(reservoir₂)
161+
162+
mass₁ |> Select("MWIP") |> viewer
163+
```
164+
165+
### Hydrocarbon zones
166+
167+
We compute the mass of hydrocarbon in place $MHIP$ as the sum of oil and gas in
168+
the first time step, and cluster it with geostatistical hierarchical clustering
169+
(`GHC`) [@Fouedjio2016]. The method requires an approximate number of clusters
170+
that we set to $k=3$ (low, medium and high values) and a maximum range of
171+
geospatial association that we set to $\lambda = 500m$.
172+
173+
Additionally, we set an upper bound `nmax=1000` on the number of elements used
174+
in the dissimilarity matrix computation and the option `as="zone"` to name the
175+
column with clustering results.
176+
177+
178+
```{julia}
179+
zones = @transform(mass₁, :MHIP = :MOIP + :MGIP) |>
180+
Select("MHIP") |> GHC(3, 500u"m", nmax=1000, as="zone")
181+
182+
zones |> viewer
183+
```
184+
185+
### Zonal depletion
186+
187+
We concatenate all variables of interest in a single geotable to be able to use
188+
the geospatial [split-apply-combine](08-splitcombine.qmd) pattern, and compute
189+
the final summary table with statistics per zone:
190+
191+
```{julia}
192+
carbon₁ = mass₁ |> Select("MOIP" => "MOIP₁", "MGIP" => "MGIP₁")
193+
carbon₂ = mass₂ |> Select("MOIP" => "MOIP₂", "MGIP" => "MGIP₂")
194+
195+
data = [carbon₁ carbon₂ zones]
196+
```
197+
198+
The depletion per zone can be computed with
199+
200+
```{julia}
201+
summary = @chain data begin
202+
@groupby(:zone)
203+
@transform(:delta = :MOIP₁ + :MGIP₁ - :MOIP₂ - :MGIP₂)
204+
@combine(:depletion = sum(:delta))
205+
end
206+
```
207+
208+
or in $Mg$ (ton) after a change of `Unit`:
209+
210+
```{julia}
211+
summary |> Unit("depletion" => u"Mg")
212+
```
213+
214+
## Summary
215+
216+
In this chapter, we illustrated the application of the framework in the petroleum
217+
industry. Among other things, we learned how to
218+
219+
- Perform simple calculations involving fluids in place and unstructured meshes.
220+
- Identify zones of a petroleum reservoir using clustering methods and visualizations.
221+
222+
This open source technology can be used to create advanced dashboards for reservoir
223+
management without advanced programming skills. It addresses real issues raised by
224+
geospatial data scientists in industry who feel unproductive using rigid geomodeling
225+
software.

Manifest.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,9 +1014,9 @@ version = "0.8.8"
10141014

10151015
[[deps.GeoStatsTransforms]]
10161016
deps = ["ArnoldiMethod", "CategoricalArrays", "Clustering", "ColumnSelectors", "Combinatorics", "DataScienceTraits", "Distances", "GeoStatsModels", "GeoStatsProcesses", "GeoTables", "LinearAlgebra", "Meshes", "OhMyThreads", "PrecompileTools", "Random", "SparseArrays", "Statistics", "TableDistances", "TableTransforms", "Tables", "TiledIteration", "Unitful"]
1017-
git-tree-sha1 = "0087c18d8950390fcbac8c9de69adef31614a8c8"
1017+
git-tree-sha1 = "e0f39a85e78cce4c0d8a3026a1086e41885c0d20"
10181018
uuid = "725d9659-360f-4996-9c94-5f19c7e4a8a6"
1019-
version = "0.10.1"
1019+
version = "0.10.2"
10201020

10211021
[[deps.GeoStatsValidation]]
10221022
deps = ["ColumnSelectors", "DataScienceTraits", "DensityRatioEstimation", "GeoStatsBase", "GeoStatsModels", "GeoStatsTransforms", "GeoTables", "LossFunctions", "Meshes", "StatsLearnModels"]

data/norne1.vtu

2.16 MB
Binary file not shown.

data/norne2.vtu

2.47 MB
Binary file not shown.

0 commit comments

Comments
 (0)