Skip to content

Add API for MIFT Boundary Condition #315

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions pygfunction/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from . import (
borefield,
boreholes,
enums,
gfunction,
ground_heat_exchanger,
heat_transfer,
load_aggregation,
media,
Expand Down
3 changes: 3 additions & 0 deletions pygfunction/borefield.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing_extensions import Self # for compatibility with Python <= 3.10

from .boreholes import Borehole
from .enums import GHEType
from .utilities import _initialize_figure, _format_axes, _format_axes_3d


Expand Down Expand Up @@ -43,6 +44,8 @@ def __init__(
self, H: npt.ArrayLike, D: npt.ArrayLike, r_b: npt.ArrayLike,
x: npt.ArrayLike, y: npt.ArrayLike, tilt: npt.ArrayLike = 0.,
orientation: npt.ArrayLike = 0.):
self.ghe_type = GHEType.BOREFIElD

# Convert x and y coordinates to arrays
x = np.atleast_1d(x)
y = np.atleast_1d(y)
Expand Down
2 changes: 2 additions & 0 deletions pygfunction/boreholes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import numpy as np
from scipy.spatial.distance import pdist

from .enums import GHEType
from .utilities import _initialize_figure, _format_axes, _format_axes_3d


Expand Down Expand Up @@ -33,6 +34,7 @@ class Borehole(object):

"""
def __init__(self, H, D, r_b, x, y, tilt=0., orientation=0.):
self.ghe_type = GHEType.BOREHOLE
self.H = float(H) # Borehole length
self.D = float(D) # Borehole buried depth
self.r_b = float(r_b) # Borehole radius
Expand Down
15 changes: 15 additions & 0 deletions pygfunction/enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from enum import Enum, auto


class GHEType(Enum):
BOREHOLE = auto()
BOREFIElD = auto()
NETWORK = auto()
Comment on lines +4 to +7
Copy link
Contributor Author

@mitchute mitchute May 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needed this to break circular import issues.

Edit: I just noticed the lower case l in BOREFIElD. Need to update that.



class PipeType(Enum):
SINGLEUTUBE = auto()
DOUBLEUTUBEPARALLEL = auto()
DOUBLEUTUBESERIES = auto()
COAXIALANNULARINLET = auto()
COAXIALPIPEINLET = auto()
268 changes: 20 additions & 248 deletions pygfunction/gfunction.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,16 @@
from scipy.interpolate import interp1d as interp1d

from .borefield import Borefield
from .boreholes import Borehole, find_duplicates
from .networks import Network
from .solvers import (
Detailed,
Equivalent,
Similarities
)
from .boreholes import (
Borehole,
find_duplicates
)
from .enums import GHEType
from .utilities import (
segment_ratios,
_initialize_figure,
_format_axes
)
)


class gFunction(object):
Expand Down Expand Up @@ -259,6 +257,13 @@ def __init__(self, boreholes_or_network, alpha, time=None,
# Check the validity of inputs
self._check_inputs()

# needed to break circular imports
from .solvers import (
Detailed,
Equivalent,
Similarities
)
Comment on lines +260 to +265
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's some lingering circular dependencies among the files. Needed to import these here to break the circular import problems.


# Load the chosen solver
if self.method.lower()=='similarities':
self.solver = Similarities(
Expand Down Expand Up @@ -520,7 +525,7 @@ def visualize_heat_extraction_rates(

# Adjust figure to window
plt.tight_layout()

return fig

def visualize_heat_extraction_rate_profiles(
Expand Down Expand Up @@ -1236,11 +1241,11 @@ def _format_inputs(self, boreholes_or_network):

"""
# Convert borehole to a list if a single borehole is provided
if isinstance(boreholes_or_network, Borehole):
if boreholes_or_network.ghe_type == GHEType.BOREHOLE:
Comment on lines -1239 to +1244
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to circular import issues

boreholes_or_network = [boreholes_or_network]
# Check if a borefield or a network is provided as an input and
# correctly assign the variables self.boreholes and self.network
if isinstance(boreholes_or_network, Network):
elif boreholes_or_network.ghe_type == GHEType.NETWORK:
Comment on lines -1243 to +1248
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seemed to me like these should be chained if-else statements, not a series of if blocks, since they didn't return early.

self.network = boreholes_or_network
self.boreholes = boreholes_or_network.b
# If a network is provided and no boundary condition is provided,
Expand All @@ -1256,13 +1261,15 @@ def _format_inputs(self, boreholes_or_network):
self.m_flow_borehole = self.network.m_flow_network
if self.cp_f is None and type(self.network.cp_f) is float:
self.cp_f = self.network.cp_f
else:
elif boreholes_or_network.ghe_type == GHEType.BOREFIElD:
self.network = None
self.boreholes = boreholes_or_network
# If a borefield is provided and no boundary condition is provided,
# use 'UBWT'
if self.boundary_condition is None:
self.boundary_condition = 'UBWT'
else:
raise NotImplementedError(f"GHEType {boreholes_or_network.ghe_type} not implemented.")
# If the 'equivalent' solver is selected for the 'MIFT' condition,
# switch to the 'similarities' solver if boreholes are in series
if self.boundary_condition == 'MIFT' and self.method.lower() == 'equivalent':
Expand Down Expand Up @@ -1306,7 +1313,7 @@ def _check_inputs(self):
"objects."
assert not find_duplicates(self.boreholes), \
"There are duplicate boreholes in the borefield."
assert (self.network is None and not self.boundary_condition=='MIFT') or isinstance(self.network, Network), \
assert (self.network is None and not self.boundary_condition=='MIFT') or (self.network.ghe_type == GHEType.NETWORK), \
"The network is not a valid 'Network' object."
if self.boundary_condition == 'MIFT':
assert not (self.m_flow_network is None and self.m_flow_borehole is None), \
Expand Down Expand Up @@ -1531,238 +1538,3 @@ def uniform_temperature(boreholes, time, alpha, nSegments=8,
boundary_condition=boundary_condition, options=options)

return gFunc.gFunc


def equal_inlet_temperature(
boreholes, UTubes, m_flow_borehole, cp_f, time, alpha,
kind='linear', nSegments=8, segment_ratios=segment_ratios,
use_similarities=True, disTol=0.01, tol=1.0e-6, dtype=np.double,
disp=False, **kwargs):
Comment on lines -1536 to -1540
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are all tagged for deprecation. I removed them so I didn't have to continue to deal with circular import problems.

"""
Evaluate the g-function with equal inlet fluid temperatures.

This function superimposes the finite line source (FLS) solution to
estimate the g-function of a geothermal bore field. Each borehole is
modeled as a series of finite line source segments, as proposed in
[#EIFT-Cimmin2015]_.

Parameters
----------
boreholes : list of Borehole objects
List of boreholes included in the bore field.
UTubes : list of pipe objects
Model for pipes inside each borehole.
m_flow_borehole : float or array
Fluid mass flow rate per borehole (in kg/s).
cp_f : float
Fluid specific isobaric heat capacity (in J/kg.K).
time : float or array
Values of time (in seconds) for which the g-function is evaluated.
alpha : float
Soil thermal diffusivity (in m2/s).
nSegments : int or list, optional
Number of line segments used per borehole, or list of number of
line segments used for each borehole.
Default is 8.
segment_ratios : array, list of arrays, or callable, optional
Ratio of the borehole length represented by each segment. The
sum of ratios must be equal to 1. The shape of the array is of
(nSegments,) or list of (nSegments[i],). If segment_ratios==None,
segments of equal lengths are considered. If a callable is provided, it
must return an array of size (nSegments,) when provided with nSegments
(of type int) as an argument, or an array of size (nSegments[i],) when
provided with an element of nSegments (of type list).
Default is :func:`utilities.segment_ratios`.
kind : string, optional
Interpolation method used for segment-to-segment thermal response
factors. See documentation for scipy.interpolate.interp1d.
Default is 'linear'.
use_similarities : bool, optional
True if similarities are used to limit the number of FLS evaluations.
Default is True.
disTol : float, optional
Relative tolerance on radial distance. Two distances
(d1, d2) between two pairs of boreholes are considered equal if the
difference between the two distances (abs(d1-d2)) is below tolerance.
Default is 0.01.
tol : float, optional
Relative tolerance on length and depth. Two lengths H1, H2
(or depths D1, D2) are considered equal if abs(H1 - H2)/H2 < tol.
Default is 1.0e-6.
dtype : numpy dtype, optional
numpy data type used for matrices and vectors. Should be one of
numpy.single or numpy.double.
Default is numpy.double.
disp : bool, optional
Set to true to print progression messages.
Default is False.

Returns
-------
gFunction : float or array
Values of the g-function

References
----------
.. [#EIFT-Cimmin2015] Cimmino, M. (2015). The effects of borehole thermal
resistances and fluid flow rate on the g-functions of geothermal bore
fields. International Journal of Heat and Mass Transfer, 91, 1119-1127.

"""
# This function is deprecated as of v2.1. It will be removed in v3.0.
warnings.warn("`pygfunction.gfunction.equal_inlet_temperature` is "
"deprecated as of v2.1. It will be removed in v3.0. "
"New features are not fully supported by the function. "
"Use the `pygfunction.gfunction.gFunction` class instead.",
DeprecationWarning)

network = Network(
boreholes, UTubes, m_flow_network=m_flow_borehole*len(boreholes),
cp_f=cp_f, nSegments=nSegments)
boundary_condition = 'MIFT'
# Build options dict
options = {'nSegments':nSegments,
'segment_ratios':segment_ratios,
'disp':disp,
'kind':kind,
'disTol':disTol,
'tol':tol,
'dtype':dtype,
'disp':disp}
# Select the correct solver:
if use_similarities:
method='similarities'
else:
method='detailed'
# Evaluate g-function
gFunc = gFunction(
network, alpha, time=time, method=method,
boundary_condition=boundary_condition, options=options)

return gFunc.gFunc


def mixed_inlet_temperature(
network, m_flow_network, cp_f, time, alpha, kind='linear',
nSegments=8, segment_ratios=segment_ratios,
use_similarities=True, disTol=0.01, tol=1.0e-6, dtype=np.double,
disp=False, **kwargs):
"""
Evaluate the g-function with mixed inlet fluid temperatures.

This function superimposes the finite line source (FLS) solution to
estimate the g-function of a geothermal bore field. Each borehole is
modeled as a series of finite line source segments, as proposed in
[#MIFT-Cimmin2019]_. The piping configurations between boreholes can be any
combination of series and parallel connections.

Parameters
----------
network : Network objects
List of boreholes included in the bore field.
m_flow_network : float or array
Total mass flow rate into the network or inlet mass flow rates
into each circuit of the network (in kg/s). If a float is supplied,
the total mass flow rate is split equally into all circuits.
cp_f : float or array
Fluid specific isobaric heat capacity (in J/kg.degC).
Must be the same for all circuits (a single float can be supplied).
time : float or array
Values of time (in seconds) for which the g-function is evaluated.
alpha : float
Soil thermal diffusivity (in m2/s).
nSegments : int or list, optional
Number of line segments used per borehole, or list of number of
line segments used for each borehole.
Default is 8.
segment_ratios : array, list of arrays, or callable, optional
Ratio of the borehole length represented by each segment. The
sum of ratios must be equal to 1. The shape of the array is of
(nSegments,) or list of (nSegments[i],). If segment_ratios==None,
segments of equal lengths are considered.
Default is None.
kind : string, optional
Interpolation method used for segment-to-segment thermal response
factors. See documentation for scipy.interpolate.interp1d.
Default is 'linear'.
use_similarities : bool, optional
True if similarities are used to limit the number of FLS evaluations.
Default is True.
disTol : float, optional
Relative tolerance on radial distance. Two distances
(d1, d2) between two pairs of boreholes are considered equal if the
difference between the two distances (abs(d1-d2)) is below tolerance.
Default is 0.01.
tol : float, optional
Relative tolerance on length and depth. Two lengths H1, H2
(or depths D1, D2) are considered equal if abs(H1 - H2)/H2 < tol.
Default is 1.0e-6.
dtype : numpy dtype, optional
numpy data type used for matrices and vectors. Should be one of
numpy.single or numpy.double.
Default is numpy.double.
disp : bool, optional
Set to true to print progression messages.
Default is False.

Returns
-------
gFunction : float or array
Values of the g-function

Examples
--------
>>> b1 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=0., y=0.)
>>> b2 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=5., y=0.)
>>> Utube1 = gt.pipes.SingleUTube(pos=[(-0.05, 0), (0, -0.05)],
r_in=0.015, r_out=0.02,
borehole=b1,k_s=2, k_g=1, R_fp=0.1)
>>> Utube2 = gt.pipes.SingleUTube(pos=[(-0.05, 0), (0, -0.05)],
r_in=0.015, r_out=0.02,
borehole=b1,k_s=2, k_g=1, R_fp=0.1)
>>> bore_connectivity = [-1, 0]
>>> network = gt.networks.Network([b1, b2], [Utube1, Utube2], bore_connectivity)
>>> time = np.array([1.0*10**i for i in range(4, 12)])
>>> m_flow_network = 0.25
>>> cp_f = 4000.
>>> alpha = 1.0e-6
>>> gt.gfunction.mixed_inlet_temperature(network, m_flow_network, cp_f, time, alpha)
array([0.63782415, 1.63304116, 2.72191316, 4.04091713, 5.98240458,
7.77216202, 8.66195828, 8.77567215])

References
----------
.. [#MIFT-Cimmin2019] Cimmino, M. (2019). Semi-analytical method for
g-function calculation of bore fields with series- and
parallel-connected boreholes. Science and Technology for the Built
Environment, 25 (8), 1007-1022

"""
# This function is deprecated as of v2.1. It will be removed in v3.0.
warnings.warn("`pygfunction.gfunction.mixed_inlet_temperature` is "
"deprecated as of v2.1. It will be removed in v3.0. "
"New features are not fully supported by the function. "
"Use the `pygfunction.gfunction.gFunction` class instead.",
DeprecationWarning)

boundary_condition = 'MIFT'
# Build options dict
options = {'nSegments':nSegments,
'segment_ratios':segment_ratios,
'disp':disp,
'kind':kind,
'disTol':disTol,
'tol':tol,
'dtype':dtype,
'disp':disp}
# Select the correct solver:
if use_similarities:
method='similarities'
else:
method='detailed'
# Evaluate g-function
gFunc = gFunction(
network, alpha, time=time, method=method,
boundary_condition=boundary_condition, options=options)

return gFunc.gFunc
Loading