Quickstart

This page shows a minimal end-to-end example: a single borehole with a Single U-tube configuration, one soil layer, and no inter-borehole thermal interference. It is the simplest possible pyCaRM simulation and a good starting point before moving to multi-borehole fields.

Imports

import numpy as np
from pathlib import Path

from carm import (
    BoreholeGeometry,
    BoreholeMesh,
    BoreholeThermalProperties,
    SingleUtube,
)
from carm import EnvironmentalProperties, EnvironmentalTimeSeries
from carm import Fluid
from carm import GroundGeometry, GroundMesh
from carm import PhysicalModel
from carm import Simulation

Fluid

Define the thermal and physical properties of the heat carrier fluid:

fluid = Fluid(
    k_w=0.5687,       # thermal conductivity [W/(m·K)]
    rho_w=1000.14,    # density [kg/m³]
    cp_w=4207.4,      # specific heat [J/(kg·K)]
    ni_w=1.496e-6,    # kinematic viscosity [m²/s]
)

Borehole

Assemble the borehole geometry, mesh, thermal properties, and pipe configuration into a SingleUtube object:

bore_geom = BoreholeGeometry(Lbore=100, D0=0.15)
bore_mesh = BoreholeMesh(m_mesh=40)
bore_th_props = BoreholeThermalProperties(cp_0=1460, rho_0=1655, k0=1.8)

props_b = SingleUtube(
    geom=bore_geom,
    mesh=bore_mesh,
    thermalprops=bore_th_props,
    fluid=fluid,
    Rp0=0.25,
    RppB=0.72,
    pipe_spacing=0.0823,
    pipe_thick=0.003,
    Dpi=0.026,
    n_pipes=2,
)

Ground

Define the ground geometry and radial/axial mesh. stratification is a list of layers, each specified as (k, rho, cp, permeability):

ground_geom = GroundGeometry(D0=0.15, L=100, L_sup=1, L_inf=10, rn=10)
ground_mesh = GroundMesh(n_mesh=20, m_mesh=40, m_mesh_sup=4, m_mesh_inf=40)

stratification = [(1.8, 947.37, 1900, 111)]   # single homogeneous layer

Environmental boundary conditions

Load time-varying surface boundary conditions from an Excel file and define the surface thermal properties:

path = Path("input_env.xlsx")

env_input = EnvironmentalTimeSeries.from_excel(Tm=13, path=path)
env_props = EnvironmentalProperties(
    R_ext=0.04,
    absorptance=0.7,
    eps=0.95,
    At=10,
    tau=0,
    tau_y=365 * 24 * 3600,
    tau_shift=210 * 24 * 3600,
)

Physical model

Combine all components into a PhysicalModel. Tg is the undisturbed ground temperature [°C]:

model = PhysicalModel(
    ground_geom=ground_geom,
    ground_mesh=ground_mesh,
    borehole=props_b,
    fluid=fluid,
    Tg=13,
    stratification=stratification,
)

Simulation

Define the inlet fluid temperature Tf1 and mass flow rate mw_tot as arrays of shape (n_groups, n_steps). For a single borehole in parallel (the default), n_groups = 1:

dt      = 3600          # timestep [s]
n_steps = 276

Tf1    = np.full((1, n_steps), 2.0)      # inlet temperature [°C]
mw_tot = np.full((1, n_steps), 0.1657)   # mass flow rate [kg/s]

simulation = Simulation(
    model=model,
    envinput=env_input,
    envprops=env_props,
    timesteps=dt,
    n_steps=n_steps,
    mw_tot=mw_tot,
    Tf1=Tf1,
)

T_history = simulation.run()

T_history has shape (n_steps + 1, n_bhes, n_dof), where index 0 along the first axis is the initial condition. See Output for how to extract temperatures of interest from this array.

Extracting the outlet temperature

As a quick check, extract and plot the outlet fluid temperature over time:

import matplotlib.pyplot as plt

m_mesh_sup = 4
n_mesh     = 20
m_mesh     = 40

nsup    = m_mesh_sup + 1
nground = n_mesh * m_mesh

Tfout = T_history[1:, 0, nsup + nground + (props_b.n_equations - 1)]
time  = np.arange(1, n_steps + 1) * dt / 3600   # [h]

plt.plot(time, Tfout)
plt.xlabel("Time [h]")
plt.ylabel("Outlet temperature [°C]")
plt.grid(True, linestyle="--", alpha=0.4)
plt.tight_layout()
plt.show()

Note

For multi-borehole fields in parallel or series, see the examples in the examples/ directory of the repository.