Skip to content

Commit 35330a1

Browse files
authored
Merge pull request #28 from sintefmath/jd-refactor
Refactor to new JutulDarcy release
2 parents 2102418 + 24a63e4 commit 35330a1

25 files changed

Lines changed: 1570 additions & 192 deletions

File tree

Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "Fimbul"
22
uuid = "cac51d78-27de-4708-bd2b-bd13a82f652b"
33
authors = ["Øystein Klemetsdal"]
4-
version = "0.2.1"
4+
version = "0.3.0"
55

66
[deps]
77
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
@@ -23,7 +23,7 @@ Dates = "1.11.0"
2323
Gmsh = "0.3.1"
2424
Integrals = "4.5.0"
2525
Jutul = "0.4.5"
26-
JutulDarcy = "0.2.47"
26+
JutulDarcy = "0.3.0"
2727
LinearAlgebra = "1.11.0"
2828
Statistics = "1.11.1"
2929
julia = "1.7"

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,24 @@ The first time you run this code, Julia will compile the packages, which may tak
3838
>[!NOTE]
3939
>Interactive plotting requires `GLMakie`, which may not work if you are running Julia over SSH.
4040
41+
## Citing
42+
43+
The current main work describing `Fimbul.jl` is [*Fimbul.jl – Fast, Flexible, Robust, and Differentiable Geothermal Energy Simulation in Julia*, available through EarthDoc](https://doi.org/10.3997/2214-4609.202521164):
44+
45+
```bibtex
46+
@inproceedings{Klemetsdal2025,
47+
title = {Fimbul.jl – Fast, Flexible, Robust, and Differentiable Geothermal Energy Simulation in Julia},
48+
DOI = {10.3997/2214-4609.202521164},
49+
booktitle = {Sixth EAGE Global Energy Transition Conference & Exhibition (GET 2025)},
50+
publisher = {European Association of Geoscientists & Engineers},
51+
author = {Klemetsdal, Ø. and Andersen, O. and Møyner, O.},
52+
year = {2025},
53+
pages = {1–5}
54+
}<>
55+
```
56+
57+
[DOI link to extended abstract.](https://doi.org/10.3997/2214-4609.202521164) If you use Fimbul in your work, please cite this work.
58+
4159
## License
4260

4361
Fimbul.jl is licensed under the MIT License. See [LICENSE](LICENSE) for details.

docs/make.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ function build_fimbul_docs(
208208
modules = [Fimbul],
209209
authors = "Øystein Klemetsdal <oystein.klemetsdal@sintef.no> and contributors",
210210
repo = "https://github.com/sintefmath/Fimbul.jl/blob/{commit}{path}#{line}",
211-
warnonly = true,
211+
warnonly = false,
212212
sitename = "Fimbul.jl",
213213
checkdocs = :exports,
214214
plugins = [bib],

docs/src/index.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,21 @@ The first time you run this code, Julia will compile the packages, which may tak
5353
>Interactive plotting requires `GLMakie`, which may not work if you are running Julia over SSH.
5454
5555
## Working with Fimbul
56-
If you plan to use Fimbul extensively in your work, we strongly recommend that you read the documentation of [JutulDarcy](https://sintefmath.github.io/JutulDarcy.jl/dev/), in particular on [getting started](https://sintefmath.github.io/JutulDarcy.jl/dev/man/intro). This also covers the basics of installing Julia and creating a Julia environments, written for users who may not already be familiar with Julia package management.
56+
If you plan to use Fimbul extensively in your work, we strongly recommend that you read the documentation of [JutulDarcy](https://sintefmath.github.io/JutulDarcy.jl/dev/), in particular on [getting started](https://sintefmath.github.io/JutulDarcy.jl/dev/man/intro). This also covers the basics of installing Julia and creating a Julia environments, written for users who may not already be familiar with Julia package management.
57+
58+
## Citing
59+
The current main work describing `Fimbul.jl` is [*Fimbul.jl – Fast, Flexible, Robust, and Differentiable Geothermal Energy Simulation in Julia*, available through EarthDoc](https://doi.org/10.3997/2214-4609.202521164):
60+
61+
```bibtex
62+
@inproceedings{Klemetsdal2025,
63+
title = {Fimbul.jl – Fast, Flexible, Robust, and Differentiable Geothermal Energy Simulation in Julia},
64+
DOI = {10.3997/2214-4609.202521164},
65+
booktitle = {Sixth EAGE Global Energy Transition Conference &amp; Exhibition (GET 2025)},
66+
publisher = {European Association of Geoscientists & Engineers},
67+
author = {Klemetsdal, Ø. and Andersen, O. and Møyner, O.},
68+
year = {2025},
69+
pages = {1–5}
70+
}<>
71+
```
72+
73+
[DOI link to extended abstract.](https://doi.org/10.3997/2214-4609.202521164) If you use Fimbul in your work, please cite this work.

docs/src/man/cases/cases.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ egg_geothermal
1212

1313
## Geothermal energy production
1414
```@docs
15-
egg_geothermal_doublet
1615
geothermal_doublet
16+
egs
17+
ags
18+
egg_geothermal_doublet
1719
```
1820

1921
## Underground thermal energy storage
2022
```@docs
21-
egg_ates
23+
ates
2224
btes
25+
egg_ates
2326
```

docs/src/man/cases/utils.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Utilities
22

33
```@docs
4+
make_schedule
45
make_utes_schedule
56
```

examples/production/ags_demo.jl

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
# # Advanced Geothermal System (AGS)
2+
# This example demonstrates simulation and analysis of energy production from an
3+
# Advanced Geothermal System (AGS). AGS technology utilizes closed-loop
4+
# circulation systems to extract geothermal energy from deep hot rock
5+
# formations, offering a novel approach to geothermal energy extraction that
6+
# doesn't require natural permeability or fracture stimulation.
7+
#
8+
# The AGS well setup for this example was provided by Alexander Rath (OMV)
9+
10+
# Add required modules to namespace
11+
using Jutul, JutulDarcy, Fimbul # Core reservoir simulation framework
12+
using HYPRE # High-performance linear solvers
13+
using GLMakie # 3D visualization and plotting capabilities
14+
15+
# Useful SI units
16+
meter, hour, day, watt = si_units(:meter, :hour, :day, :watt);
17+
18+
# ## AGS setup
19+
# We consider an AGS system featuring a closed-loop configuration with a single
20+
# vertical well extending 2400 m deep, followed by two horizontal lateral
21+
# sections at depth for enhanced heat exchange with the surrounding rock. The
22+
# system includes a production well that returns heated water to the surface.
23+
24+
# ### Create simulation case
25+
# We set up a scenario describing 50 years of operation with a water circulation
26+
# rate of 50 L/s at an injection temperature of 25°C. The simulation will
27+
# output results four times per year for detailed analysis.
28+
reports_per_year = 4 # Output frequency for results analysis
29+
case = Fimbul.ags(;
30+
rate = 25meter^3/hour, # Water circulation rate
31+
temperature_inj = convert_to_si(25.0, :Celsius), # Injection temperature
32+
num_years = 50, # Years of operation
33+
report_interval = si_unit(:year)/reports_per_year,
34+
porosity = 0.01, # Low porosity rock matrix
35+
permeability = 1e-3*si_unit(:darcy), # Low permeability formation
36+
rock_thermal_conductivity = 2.5*watt/(meter*si_unit(:Kelvin)), # Rock thermal conductivity
37+
rock_heat_capacity = 900.0*si_unit(:joule)/(si_unit(:kilogram)*si_unit(:Kelvin)) # Rock heat capacity
38+
);
39+
40+
# ### Inspect model
41+
# Visualize the computational mesh and well configuration. The mesh is refined
42+
# around wells to accurately capture thermal and hydraulic processes in the
43+
# closed-loop system.
44+
msh = physical_representation(reservoir_model(case.model).data_domain)
45+
geo = tpfv_geometry(msh)
46+
fig = Figure(size = (800, 800))
47+
ax = Axis3(fig[1, 1]; zreversed = true, aspect = :data, perspectiveness = 0.5,
48+
title = "AGS System: Closed-loop well and mesh")
49+
Jutul.plot_mesh_edges!( # Show computational mesh with transparency
50+
ax, msh, alpha = 0.2)
51+
wells = get_model_wells(case.model)
52+
function plot_ags_wells( # Utility to plot wells in AGS system
53+
ax; colors = [:black, :black])
54+
for (i, (name, well)) in enumerate(wells)
55+
color = colors[i]
56+
label = string(name)
57+
if haskey(well.perforations, :reservoir)
58+
cells = well.perforations.reservoir
59+
if length(cells) > 0
60+
xy = geo.cell_centroids[1:2, cells]
61+
xy = hcat(xy[:,1], xy)'
62+
plot_mswell_values!(ax, case.model, name, xy;
63+
geo = geo, linewidth = 3, color = color, label = label)
64+
end
65+
end
66+
end
67+
end
68+
plot_ags_wells(ax)
69+
fig
70+
71+
# ## Simulate system
72+
# We set up the the simulator
73+
sim, cfg = setup_reservoir_simulator(case;
74+
output_substates = true, # Store results from timesteps between
75+
info_level = 0, # 0=progress bar, 1=basic, 2=detailed
76+
initial_dt = 5.0, # Initial timestep [s]
77+
presolve_wells = true, # Solve wells with fixed reservoir state at the beginning of each timestep
78+
relaxation = true); # Enable relaxation in Newton solver
79+
80+
# We add a specialized timestep selector to control solution quality during
81+
# thermal transients. This selector monitors temperature changes and adjusts
82+
# timesteps aiming at a maximum change of 5°C per timestep in both the reservoir
83+
# and the well.
84+
sel = VariableChangeTimestepSelector(:Temperature, 5.0;
85+
relative = false, model = :Reservoir)
86+
push!(cfg[:timestep_selectors], sel);
87+
sel = VariableChangeTimestepSelector(:Temperature, 5.0;
88+
relative = false, model = :AGS_supply)
89+
push!(cfg[:timestep_selectors], sel);
90+
91+
# NOTE: depending in your system, the simulation may take a few minutes to
92+
# complete.
93+
results = simulate_reservoir(case; simulator = sim, config = cfg)
94+
95+
# ## Interactive Visualization
96+
# Next, we analyze and visualize the simulation results interactively to
97+
# understand the AGS performance, thermal depletion patterns, and energy
98+
# production characteristics throughout the 50-year operational period.
99+
100+
# ### Reservoir state evolution
101+
# It is often most informative to visualize the deviation from the initial
102+
# conditions to highlight the extent of the thermal depletion zones around the
103+
# AGS system. We compute the change in reservoir variables to the initial state
104+
# for all timesteps.
105+
Δstates = JutulDarcy.delta_state(results.states, case.state0[:Reservoir])
106+
plot_res_args = (
107+
resolution = (600, 800), aspect = :data,
108+
colormap = :seaborn_icefire_gradient, key = :Temperature,
109+
well_arg = (markersize = 0.0, ),
110+
)
111+
plot_reservoir(case.model, Δstates; plot_res_args...)
112+
113+
# ### Final temperature change in the reservoir
114+
# We visualize the final temperature change in the reservoir after 50 years of
115+
# operation, with a seubset of cells cut out for better visibility.
116+
117+
# Define cells to cut out
118+
cut_out = geo.cell_centroids[1, :] .< 750.0
119+
cut_out = cut_out .|| geo.cell_centroids[2, :] .< 0.0
120+
cut_out = cut_out .&& geo.cell_centroids[3, :] .< 2400
121+
122+
fig = Figure(size = (800, 800))
123+
tot_time = round(sum(case.dt)/si_unit(:year), digits = 1)
124+
ax = Axis3(fig[1, 1]; title = "Temperature after $(tot_time) years",
125+
zreversed = true, elevation = pi/8,
126+
aspect = :data, perspectiveness = 0.5)
127+
plt = plot_cell_data!(ax, msh, Δstates[end][:Temperature];
128+
cells = .!cut_out, colormap = :seaborn_icefire_gradient)
129+
[plot_well!(ax, msh, well;
130+
color = :black, markersize = 0.0, fontsize = 0.0, linewidth = 1)
131+
for well in values(wells)]
132+
133+
Colorbar(fig[2,1], plt;
134+
label = "ΔT (°C)", vertical = false)
135+
fig
136+
137+
# ## Well Performance Analysis
138+
# Examine the well responses including flow rates, pressures, and temperatures.
139+
# The AGS system shows the circulation flow through the closed loop and the
140+
# thermal response as the system extracts heat from the surrounding rock matrix.
141+
plot_well_results(results.wells)
142+
143+
# ## Lateral Section Analysis
144+
# We analyze the performance of the lateral sections in the AGS system by
145+
# extracting temperature and power data along the lateral segments over time. This
146+
# analysis helps to understand how effectively the two laterals exchange heat with
147+
# the reservoir and contribute to overall energy production.
148+
section_data = Fimbul.get_section_data_ags(
149+
case, results.result.states, :AGS_supply)
150+
151+
# ### Set up plotting utilities
152+
colors = collect(cgrad(:BrBg, 8, categorical = true))[[2, end-1]]
153+
function plot_lateral_data!(ax, time, data; stacked = false)
154+
num_laterals = size(data, 2)-2
155+
y_prev = zeros(size(data, 1))
156+
for lno = 1:num_laterals
157+
y = data[:, lno+1]
158+
if stacked
159+
x = vcat(time, reverse(time))
160+
println("Size x: ", size(x), ", size y: ", size(y))
161+
y .+= y_prev
162+
y = vcat(y_prev, reverse(y))
163+
poly!(ax, x, y; color = colors[lno],
164+
strokecolor = :black, strokewidth = 1, label = "Lateral $lno")
165+
y_prev = data[:, lno+1]
166+
else
167+
lines!(ax, time, y; color = colors[lno], linewidth = 4, label = "Lateral $lno")
168+
end
169+
end
170+
end
171+
172+
# Plot lateral temperature and power over time
173+
fig = Figure(size = (800, 800))
174+
time = results.time ./ si_unit(:year)
175+
176+
ax_tmp = Axis( # Panel 1: Lateral temperature
177+
fig[1, 1:3]; title = "Lateral Temperature",
178+
ylabel = "Temperature (°C)", xlabel = "Time (years)")
179+
T = convert_from_si.(section_data[:Temperature], :Celsius)
180+
plot_lateral_data!(ax_tmp, time, T, stacked = false)
181+
hidexdecorations!(ax_tmp, grid = false)
182+
183+
ax_pwr = Axis( # Panel 2: Lateral power
184+
fig[2, 1:3]; title = "Lateral Power",
185+
ylabel = "Power (W)", xlabel = "Time (years)",
186+
limits = (nothing, (-0.05, 0.5)))
187+
MW = si_unit(:mega)*si_unit(:watt)
188+
plot_lateral_data!(ax_pwr, time, section_data[:Power]./MW, stacked = true)
189+
190+
ax_lat = Axis( # Panel 3: Lateral well trajectories for reference
191+
fig[1:2, 4]; aspect = DataAspect(), limits = ((-150, 100), nothing),
192+
xticks = [-150, 100])
193+
well_coords, _ = Fimbul.get_ags_trajectory()
194+
for (k, wc) in enumerate(well_coords[2:3])
195+
lines!(ax_lat, wc[:, 2], wc[:, 1]; linewidth = 4, color = colors[k])
196+
end
197+
198+
axislegend( # Add legend
199+
ax_tmp; position = :rt, fontsize = 20)
200+
fig

examples/production/doublet_demo.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ using GLMakie
1313
# The injector and producer wells are placed 100 m apart at the top, and run
1414
# parallel down to 800 m depth before they diverge to a distance of 1000 m at
1515
# 2500 m depth.
16-
case, plot_args = geothermal_doublet();
16+
case = geothermal_doublet();
1717

1818
# ## Inspect model
1919
# We first plot the computational mesh and wells. The mesh is refined around
2020
# the wells in the horizontal plane and vertically in and near the target
2121
# aquifer.
2222
msh = physical_representation(reservoir_model(case.model).data_domain)
2323
fig = Figure(size = (1200, 800))
24-
ax = Axis3(fig[1, 1], zreversed = true, aspect = plot_args.aspect)
24+
ax = Axis3(fig[1, 1], zreversed = true, aspect = :data)
2525
Jutul.plot_mesh_edges!(ax, msh, alpha = 1.0)
2626
wells = get_model_wells(case.model)
2727
for (k, w) in wells
@@ -31,7 +31,7 @@ fig
3131

3232
# ### Plot reservoir properties
3333
# Next, we visualize the reservoir interactively.
34-
plot_reservoir(case.model; plot_args...)
34+
plot_reservoir(case.model; aspect = :data)
3535

3636
# ## Simulate geothermal energy production
3737
# We simulate the geothermal doublet for 200 years. The producer is set to
@@ -47,7 +47,7 @@ results = simulate_reservoir(case; info_level = 0)
4747
# We first plot the reservoir state interactively. You can notice how the
4848
# cold front propagates from the injector well by filtering out high values.
4949
plot_reservoir(case.model, results.states;
50-
colormap = :seaborn_icefire_gradient, key = :Temperature, plot_args...)
50+
colormap = :seaborn_icefire_gradient, key = :Temperature, aspect = :data)
5151

5252
# ### Plot well output
5353
# Next, we plot the well output to examine the production rates and temperatures.
@@ -72,7 +72,7 @@ for (n, state) in enumerate(states)
7272
geo = geo, linewidth = 4, color = colors[n], alpha = 0.25)
7373
end
7474
# Highlight selected timesteps with solid lines and labels
75-
timesteps = [7, 21, 65, 200]
75+
timesteps = [12, 25, 50, 100]
7676
for n in timesteps
7777
T = convert_from_si.(states[n][:Producer][:Temperature], :Celsius)
7878
plot_mswell_values!(ax, case.model, :Producer, T;
@@ -99,7 +99,7 @@ for state in results.states
9999
push!(Δstates, Δstate)
100100
end
101101
plot_reservoir(case.model, Δstates;
102-
colormap = :seaborn_icefire_gradient, key = :Temperature, plot_args...)
102+
colormap = :seaborn_icefire_gradient, key = :Temperature, aspect = :data)
103103

104104
# ### 3D visualization of temperature changes
105105
# Finally, we plot the change in temperature at the same timesteps highlighted in

examples/storage/ates_demo.jl

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ darcy = si_unit(:darcy);
4040
# realistic geological and operational parameters for a medium-scale ATES
4141
# installation with 400m well spacing.
4242
num_years = 5
43-
case, layers = Fimbul.ates(;
43+
case = Fimbul.ates(;
4444
well_distance = 400.0, # Distance between wells [m]
4545
temperature_charge = convert_to_si(85, :Celsius), # Hot injection temperature
4646
temperature_discharge = convert_to_si(20, :Celsius), # Cold injection temperature
@@ -67,7 +67,7 @@ colors = [:red, :blue] # Hot well = red, Cold well = blue
6767
for (i, (k, w)) in enumerate(wells)
6868
plot_well!(ax, msh, w, color = colors[i], linewidth = 6)
6969
end
70-
plot_cell_data!(ax, msh, layers,
70+
plot_cell_data!(ax, msh, case.input_data[:layers],
7171
colormap = :rainbow,
7272
alpha = 0.3)
7373
fig
@@ -247,6 +247,7 @@ fig_wells
247247

248248
# Extract temperature along a horizontal line in the aquifer layer
249249
# This transect passes through the center of the aquifer between the two wells
250+
layers = case.input_data[:layers]
250251
ijk = [cell_ijk(msh, c) for c in 1:number_of_cells(msh)]
251252
j = div(maximum(getindex.(ijk, 2)) + minimum(getindex.(ijk, 2)), 2)
252253
k = div(maximum(getindex.(ijk[layers.==3], 3)) + minimum(getindex.(ijk[layers.==3], 3)), 2)
@@ -270,7 +271,10 @@ function plot_aquifer_temperature!(fig, T_line, stage, cycle)
270271
else
271272
steps = dch_start[cycle]:dch_stop[cycle]
272273
end
273-
colors = cgrad(:seaborn_icefire_gradient, length(steps), categorical = true)
274+
colors = cgrad(:RdBu, length(steps), categorical = true)
275+
if stage == "Charging"
276+
colors = reverse(colors)
277+
end
274278
for (n, T_n) in enumerate(T_line[steps])
275279
T_n = convert_from_si.(T_n, :Celsius)
276280
lines!(ax, x, T_n, color = colors[n], linewidth = 3,

0 commit comments

Comments
 (0)