Getting Started
This guide walks through a complete simulation of a single deep borehole heat exchanger.
Overview
A typical GeothermalWells.jl simulation involves:
- Defining material properties (rock, water, steel, etc.)
- Specifying borehole geometry
- Creating computational grids
- Setting initial conditions
- Choosing an inlet model
- Running the simulation
Complete Example
using GeothermalWells
using OrdinaryDiffEqStabilizedRK: ODEProblem, solve, ROCK2
using KernelAbstractions: CPU
# Choose backend: CPU() for testing, CUDABackend() for NVIDIA GPU
backend = CPU()
Float_used = Float64Using Float32 can cause numerical instabilities in some cases. It is recommended to use Float64 for reliable results.
Material Properties
Define thermal properties for all materials in the simulation:
materials = HomogenousMaterialProperties{Float_used}(
2.88, # k_rock - rock thermal conductivity [W/(m·K)]
2.17e6, # rho_c_rock - rock volumetric heat capacity [J/(m³·K)]
0.6, # k_water [W/(m·K)]
4.18e6, # rho_c_water [J/(m³·K)]
44.5, # k_steel [W/(m·K)]
3.73e6, # rho_c_steel [J/(m³·K)]
0.26, # k_insulating [W/(m·K)]
1.96e6, # rho_c_insulating [J/(m³·K)]
1.0, # k_backfill [W/(m·K)]
1.0 # rho_c_backfill [J/(m³·K)]
)For layered rock formations, use StratifiedMaterialProperties instead.
Borehole Geometry
Define the coaxial borehole heat exchanger geometry:
borehole = Borehole{Float_used}(
0.0, # xc - x-coordinate of center [m]
0.0, # yc - y-coordinate of center [m]
2000.0, # h - borehole depth [m]
0.0381, # r_inner - inner pipe radius [m]
0.01, # t_inner - inner pipe wall thickness [m]
0.0889, # r_outer - outer pipe inner radius [m]
0.01, # t_outer - outer pipe wall thickness [m]
0.0989, # r_backfill - backfill outer radius [m]
10.0, # ṁ - mass flow rate [kg/s]
500.0 # insulation_depth - depth of inner pipe insulation [m]
)
boreholes = (borehole,) # tuple of all boreholesGrid Setup
Create adaptive grids that are fine near the borehole and coarse far away:
# Domain boundaries
xmin, xmax = -100.0, 100.0
ymin, ymax = -100.0, 100.0
zmin, zmax = 0.0, 2200.0
# Grid parameters
dx_fine = 0.0025 # fine spacing near borehole [m]
growth_factor = 1.3 # geometric growth rate
dx_max = 10.0 # maximum spacing far from borehole [m]
dz = 100.0 # vertical spacing [m]
gridx = create_adaptive_grid_1d(
xmin=xmin, xmax=xmax,
dx_fine=dx_fine, growth_factor=growth_factor, dx_max=dx_max,
boreholes=boreholes, backend=backend, Float_used=Float_used, direction=:x
)
gridy = create_adaptive_grid_1d(
xmin=ymin, xmax=ymax,
dx_fine=dx_fine, growth_factor=growth_factor, dx_max=dx_max,
boreholes=boreholes, backend=backend, Float_used=Float_used, direction=:y
)
gridz = create_uniform_gridz_with_borehole_depths(
zmin=zmin, zmax=zmax, dz=dz,
boreholes=boreholes, backend=backend
)
println("Grid size: $(length(gridx)) x $(length(gridy)) x $(length(gridz))")Initial Condition
Set the initial temperature field with a geothermal gradient:
T0 = initial_condition_thermal_gradient(
backend, Float_used, gridx, gridy, gridz;
T_surface=10.0, # surface temperature [°C]
gradient=0.03 # thermal gradient [K/m]
)Inlet Model
Choose how the inlet temperature is determined:
# Option 1: Constant inlet temperature
inlet_model = ConstantInlet{Float_used}(20.0) # 20°C
# Option 2: Heat exchanger (inlet = outlet - ΔT)
# inlet_model = HeatExchangerInlet{Float_used}(5.0) # ΔT = 5KCreate Cache and Solve
cache = create_cache(
backend=backend,
gridx=gridx,
gridy=gridy,
gridz=gridz,
materials=materials,
boreholes=boreholes,
inlet_model=inlet_model
)
# Time span: simulate for 1 day
tspan = (0.0, 3600.0 * 24.0)
prob = ODEProblem(rhs_diffusion_z!, T0, tspan, cache)
# Callback handles ADI (horizontal diffusion) and advection, plus saves solutions
# This is required - without it, only vertical diffusion is computed
callback, saved_values = get_simulation_callback(
saveat=range(tspan..., 10), # times to save solutions
print_every_n=100 # print progress every N steps
)
# Solve with ROCK2 (stabilized explicit Runge-Kutta)
Δt = 80.0 # time step [s]
solve(
prob,
ROCK2(max_stages=100, eigen_est=eigen_estimator),
save_everystep=false,
callback=callback,
adaptive=false,
dt=Δt,
maxiters=Int(1e10)
)GPU Acceleration
For GPU acceleration, simply change the backend:
using CUDA: CUDABackend
backend = CUDABackend()
# All other code remains the same!Well Arrays
To simulate multiple boreholes, create a tuple of Borehole objects:
# 3x3 array with 30m spacing
boreholes = tuple(
(Borehole{Float_used}(
xc, yc, # center coordinates
2000.0, # depth
# ... other parameters
) for xc in [-30.0, 0.0, 30.0], yc in [-30.0, 0.0, 30.0])...
)Next Steps
- See the API Reference for detailed documentation
- Check the
examples/folder:single_borehole_basic.jl- Single borehole simulationwell_array_basic.jl- Multi-well array simulation
- See the reproducibility repository for advanced examples reproducing results from literature (Li et al. (2021), Hu et al. (2020), Brown et al. (2023))