Help needed in 3D BHE CXA design

Hello all,

I am a drilling engineer and we are working on a technology to reach approximately 20km by rotary drilling. The objective with this technology is to create big bore coaxial enhanced closed loop geothermal well. The drilling design design is done but I am not an expert in geothermal simulation software. I have tried Waiwera software but found out it was not capable of simulating of supercritical conditions of water.

During my journey i stumbled upon OpenGeoSys and the work of Dr. Chaofan Chen which exactly matches our case except the depths and additional hydraulic stimulation scenarios. Dr Chaofan Chen CXA BHE design and Files

See below the scematic of a CXA setup but with the comments of the parameters I am looking to simulation. Our flowrate will be maximum 230 liters per second.

After trying myself several days unsuccessfully I decided to send Dr. Chaofan Chen a message and he indicated that here on the forum are probably people who can help me setup the system for simulation.

So therefore a humble request if there is anyone here who can help me to setup my scenario with the 2 frac scenarios to help me simulate the geothermal performance of the well over time.

Another question I have in case there are geological specialist around here. We are working with the theory (and field proven) that fractured rocks can thermally merge over time and that then the frac radius and total interval length can be counted as a full downhole heat exchanger. In case of 100 fracs in 8000m this thermal merging would take 30 years and in case of 200 fracs in 4000m this thermal merging would take 1.3 years. Some people say the thermal merging time can be fully ignored when the fracs are filled with a solid super thermal material (scenario 2) Does anyone have experience in this or can comment on this?

Many many thanks! Jan

Hello Jan,

thanks for your interest using OGS for this interesting questions. We recommend to start with a simple model like the one of Chaofan and get this running on your machine. If you encounter concrete issues with this simple case, we should be able to help you relatively fast.

Regarding your questions of the fractures: In the BHE process of OGS it is from my knowledge not possible, to consider fractures directly. One idea to consider the filled fractures, is to assign a new material id to some specific elements in the pre processing to handle the fractures as own material in the prj-file. This simple tutorial of ogstools may help you with this: Setting initial properties and variables in bulk meshes — ogstools 0.7.1 documentation

As a simpler starting point, you can also make thin layers with different properties to test the general behavior. For the meshing, maybe this tool can help you: Creating a BHE mesh (Borehole Heat Exchanger) — ogstools 0.7.1 documentation

So start as simple as possible and if you encounter specific problems with this, just write us here in the post.

Best regards, Max

Hi Max,

After working out howto setup a mesh (in docker + macosx) i found that this code somewhat works.

root ➜ /workspace/cxc/adjusted/test $ cat mesh8.py
import pyvista as pv
import pathlib
from typing import List
import os
import ogstools as ot
from ogstools.meshlib import Mesh
from ogstools.meshlib.gmsh_BHE import BHE, gen_bhe_mesh

Debug: Check Gmsh and environment

print(“Gmsh version:”)
os.system(“gmsh --version”)
print(“Temporary files before meshing:”)
os.system(“ls -l /tmp/*.msh || echo ‘No .msh files found’”)

Scaling factor for mesh sizes (based on depth increase: 16300 / 70 ≈ 232.857)

scaling_factor = 16300.0 / 70.0
inner_mesh_size = 6.0 * (scaling_factor ** 0.5) # Square root scaling for balance
outer_mesh_size = 12.0 * (scaling_factor ** 0.5)
dist_box_x = 3.0 * (scaling_factor ** 0.5)
dist_box_y = 3.0 * (scaling_factor ** 0.5)
target_z_size_coarse = 100.0 # Reduced for stability
target_z_size_fine = 50.0 # Reduced for stability

Generate the 3D mesh

print(“Input parameters:”, {
“length”: 70.0,
“width”: 20.0,
“layer”: [16300.0],
“groundwater”: ,
“BHE”: [{“x”: 1.0, “y”: 10.0, “z_begin”: -10.0, “z_end”: -16210.0, “borehole_radius”: 0.108}],
“meshing_type”: “structured”,
“refinement”: {“dist_box_x”: dist_box_x, “dist_box_y”: dist_box_y,
“inner_mesh_size”: inner_mesh_size, “outer_mesh_size”: outer_mesh_size}
})

try:
bhe_meshes = gen_bhe_mesh(
length=70.0,
width=20.0,
layer=[16300.0],
groundwater=,
BHE_Array=[
BHE(
x=1.0,
y=10.0,
z_begin=-10.0,
z_end=-16210.0,
borehole_radius=0.108
),
],
target_z_size_coarse=target_z_size_coarse,
target_z_size_fine=target_z_size_fine,
n_refinement_layers=3, # Increased for smoother transitions
meshing_type=“structured”, # Changed to supported type
dist_box_x=dist_box_x,
dist_box_y=dist_box_y,
inner_mesh_size=inner_mesh_size,
outer_mesh_size=outer_mesh_size,
propagation=1.2,
order=1,
out_name=pathlib.Path(“domain.vtu”)
)
except Exception as e:
print(“Mesh generation failed:”, str(e))
print(“Fallback files are inappropriate for 16300m domain. Please check meshing parameters.”)
raise

print(“Temporary files after meshing:”)
os.system(“ls -l /tmp/*.msh || echo ‘No .msh files found’”)

def load_and_plot(mesh_filenames: List[str]):
pv.set_plot_theme(“document”)
plotter = pv.Plotter(off_screen=True)
domain_mesh_pv = pv.read(mesh_filenames[0])
print(“Domain mesh arrays:”, domain_mesh_pv.array_names)
bhe_line = domain_mesh_pv.extract_cells_by_type(pv.CellType.LINE)
if “MaterialIDs” in domain_mesh_pv.array_names:
plotter.add_mesh(
domain_mesh_pv,
scalars=“MaterialIDs”,
cmap=“viridis”,
show_scalar_bar=True,
scalar_bar_args={“title”: “Material IDs”},
opacity=0.8,
)
else:
print(“MaterialIDs not found. Available arrays:”, domain_mesh_pv.array_names)
plotter.add_mesh(domain_mesh_pv, style=“surface”, color=“grey”, opacity=0.8)
plotter.add_mesh(domain_mesh_pv, style=“wireframe”, color=“black”, opacity=0.3)
plotter.add_mesh(bhe_line, color=“red”, line_width=3)
offsets = [(0, 0, 100), (0, 0, -100), (2, 0, 0)]
for submesh_file, offset in zip(mesh_filenames[1:], offsets, strict=False):
submesh_pv = pv.read(submesh_file)
print(f"Submesh {submesh_file}:", submesh_pv)
plotter.add_mesh(
submesh_pv.translate(offset), show_edges=True, color=“lightgrey”, opacity=0.7
)
plotter.camera_position = “iso”
plotter.screenshot(“deep_bhe_plot.png”)
print(“Mesh generated! Plot saved as ‘deep_bhe_plot.png’.”)
print(“Filenames:”, mesh_filenames)

load_and_plot(bhe_meshes)

But if i load this one with https://gitlab.opengeosys.org/ogs/ogs/-/raw/master/Tests/Data/Parabolic/T/3D_deep_BHE/3D_deep_BHE_CXA.prj (adjusted for the depth ofcourse)

It seems to give me output temperatures of 25 degrees Celsius but Im expecting more like 300 degrees Celsius.

The simulation does run and it does produce the vtu files but the result in temperature isn’t correct. I am suspecting it is not applying a geothermal gradient (i looking at 3 deg.C per 100mtrs)

Kind regards,

Jan

Hi Jan,

sorry for the delay in response. Very good, that the meshing somehow works. I didn’t get all the code lines in these few minutes, but due to the running simulation I assume it’s fine.:slight_smile:

The initial soil temperature is set constant to 25 in the provided prj-file from the benchmark. If you want a simple geothermal gradient in the initial temperature field you can change the definition of the parameter T0.

<parameter>
      <name>T0</name>
     <type>Function</type>
     <expression>25-0.03*z</expression>
</parameter>

Please note that the z-coordinate is negative, if the top surface start at 0m. So you will get a linear increase with greater depths in the initial field. With this higher temperatures in the initial field, your simulated temperatures of the BHE should be as expected.

I hope, that this helps you. If you further questions, just write me here in this channel.

Good luck with your simulation!

Kind regards,
Max

Hi Max thank you!
I found this one out too. Do you know perhaps if I also need to do something with BHE1?

Also I was thinking of the mesh… the bottom 4000mtr will be fracked every 20mtrs to approximately 480mtr radius. Do I need to create a mesh of 1000mtr diameter x 16200mtr length then? To accomodate this? For simplicity i imagine the fractures as radial discs with +/- 0.5" diameter going down to 0 in the tip…

With the frac network my objective is to create a full diameter downhole heat exchanger that can support the heat demand from the well, There is theory of thermal merging of fracs that says that within year X the thermal merging of the fracs is completed and you can use the whole cilinder volume as heat exchanger. For example for frac spacing 100mtr this is 30 years but for 20mtr this is 1.3 years. I would like to model this into OGS just to determine my required frac radius to create a sustainable system that reduces the temperature less then 0,5 celsius degrees downhole (depending of flow rate ofcourse). I have no idea howto model this into ogs so any help is welcome :smiley:

Kind regards,

Jan

Hi Jan,

I think a constant initial temperature for the BHE will be fine, nevertheless you can use the function parameter with the number of components for the used BHE type - the CXC type has 3 components I think. (You can see this easy in the prj-file in the specification of the process variable temperature_BHE1.)

For your model area: A bit more soil below your BHE will be good, especially with a boundary condition like a geothermal flux from the bottom surface. To get this radial oriented mesh and the fractures as complete elements, you need to script it your own in gmsh - maybe the source code of the ogstools function will help with this. But at this point, I’m not familiar how to model fractures. I was simply thinking on an own material id for the elements of the fracture and use an own medium definition for it.

And one important thing to note: The CXC BHE in OGS is a closed loop, so no liquid from the BHE will get in contact with the surrounding soil.

Best regards,
Max

Thanks Max,

Another question I have this in my prj




AqueousLiquid


phase_velocity
Constant
0 0 0


specific_heat_capacity
Constant
4068


density
Constant
992.92



How can I adapt this for super critical fluid? So that it calculates it for example from the python iapws library or any other way?

Kind regards,

Jan

Hi Jan,
Some of the IAPWS parameter library are implemented as their own type see for example here: OGS: [tag] WaterDensityIAPWSIF97Region1

I think the problem is the BHE process is T only so it does not solve the hydraulic equation so you don’t have pressure as a primary variable also for the iapws types. It seems there was not yet a need to extend this. Other options would be to write a function yourself for the parameters using the depth and temperature https://doxygen.opengeosys.org/d9/dfe/ogs_file_param__properties__property__function
Similar topic is also this phase velocity tag. When you want to have a groundwater flow you can give the darcy velocity as x y z vectors, but it is not coupled with the temperature.
As far is i know you can’t use a python script for the soil parameters. Hope i could help.
Best Maxi