Skip to content

Commit

Permalink
Merge pull request #270 from wouterpeere/create_dense_borefield
Browse files Browse the repository at this point in the history
Create dense borefield function
  • Loading branch information
MassimoCimmino authored Apr 22, 2024
2 parents a0a82df + 969a1b1 commit 6b15c86
Show file tree
Hide file tree
Showing 4 changed files with 278 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Version 2.3 (in development)

### New features

* [Issue 276](https://github.com/MassimoCimmino/pygfunction/issues/276) - Added functions to the `boreholes` module for the generation of rectangular fields in a staggered configuration.

### Bug fixes

* [Issue 274](https://github.com/MassimoCimmino/pygfunction/issues/274) - Fixed scalar assignment from ndim-1 array. It is deprecated as of `numpy` version `1.25`. Only ndim-0 arrays can be treated as scalars.
Expand Down
15 changes: 14 additions & 1 deletion examples/regular_bore_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
""" Example of definition of a bore field using pre-defined configurations.
"""
import matplotlib.pyplot as plt

import pygfunction as gt


Expand Down Expand Up @@ -29,6 +31,14 @@ def main():
# Rectangular field of 4 x 3 boreholes
rectangularField = gt.boreholes.rectangle_field(N_1, N_2, B, B, H, D, r_b)

# Rectangular field triangular field of 4 x 3 borehole rows
staggeredRectangularField = gt.boreholes.staggered_rectangle_field(
N_1, N_2, B, B, H, D, r_b, False)

# Dense field triangular field of 4 x 3 borehole rows
denseRectangularField = gt.boreholes.dense_rectangle_field(
N_1, N_2, B, H, D, r_b, False)

# Box-shaped field of 4 x 3 boreholes
boxField = gt.boreholes.box_shaped_field(N_1, N_2, B, B, H, D, r_b)

Expand All @@ -44,8 +54,11 @@ def main():
# -------------------------------------------------------------------------
# Draw bore fields
# -------------------------------------------------------------------------
for field in [rectangularField, boxField, UField, LField, circleField]:
for field in [
rectangularField, staggeredRectangularField, denseRectangularField,
boxField, UField, LField, circleField]:
gt.boreholes.visualize_field(field)
plt.show()

return

Expand Down
170 changes: 170 additions & 0 deletions pygfunction/boreholes.py
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,176 @@ def rectangle_field(N_1, N_2, B_1, B_2, H, D, r_b, tilt=0., origin=None):
return borefield


def staggered_rectangle_field(
N_1, N_2, B_1, B_2, H, D, r_b, include_last_borehole, tilt=0.,
origin=None):
"""
Build a list of boreholes in a rectangular bore field configuration, with
boreholes placed in a staggered configuration.
Parameters
----------
N_1 : int
Number of borehole in the x direction.
N_2 : int
Number of borehole in the y direction.
B_1 : float
Distance (in meters) between adjacent boreholes in the x direction.
B_2 : float
Distance (in meters) between adjacent boreholes in the y direction.
H : float
Borehole length (in meters).
D : float
Borehole buried depth (in meters).
r_b : float
Borehole radius (in meters).
include_last_borehole : bool
If True, then each row of boreholes has equal numbers of boreholes.
If False, then the staggered rows have one borehole less so they are
contained within the imaginary 'box' around the borefield.
tilt : float, optional
Angle (in radians) from vertical of the axis of the borehole. The
orientation of the tilt is orthogonal to the origin coordinate.
Default is 0.
origin : tuple, optional
A coordinate indicating the origin of reference for orientation of
boreholes.
Default is the center of the rectangle.
Returns
-------
boreField : list of Borehole objects
List of boreholes in the rectangular bore field.
Notes
-----
Boreholes located at the origin will remain vertical.
Examples
--------
>>> boreField = gt.boreholes.rectangle_field(N_1=3, N_2=2, B_1=5., B_2=5.,
H=100., D=2.5, r_b=0.05)
The bore field is constructed line by line. For N_1=3 and N_2=3, the bore
field layout is as follows, if `include_last_borehole` is True::
6 7 8
3 4 5
0 1 2
and if `include_last_borehole` is False::
5 6 7
3 4
0 1 2
"""
borefield = []

if N_1 == 1 or N_2 == 1:
return rectangle_field(N_1, N_2, B_1, B_2, H, D, r_b, tilt, origin)

if origin is None:
# When no origin is supplied, compute the origin to be at the center of
# the rectangle
if include_last_borehole:
x0 = (N_1 - 1) / 2 * B_1
y0 = (N_2 - 1) / 2 * B_2
else:
x0 = (N_1 - 0.5) / 2 * B_1
y0 = (N_2 - 0.5) / 2 * B_2
else:
x0, y0 = origin

for j in range(N_2):
for i in range(N_1):
x = i * B_1 + (B_1 / 2 if j % 2 == 1 else 0)
y = j * B_2
# The borehole is inclined only if it does not lie on the origin
if np.sqrt((x - x0)**2 + (y - y0)**2) > r_b:
orientation = np.arctan2(y - y0, x - x0)
if i < (N_1 - 1) or include_last_borehole or (j % 2 == 0):
borefield.append(
Borehole(
H, D, r_b, x, y, tilt=tilt, orientation=orientation))
else:
if i < (N_1 - 1) or include_last_borehole or (j % 2 == 0):
borefield.append(Borehole(H, D, r_b, x, y))

return borefield


def dense_rectangle_field(
N_1, N_2, B, H, D, r_b, include_last_borehole, tilt=0., origin=None):
"""
Build a list of boreholes in a rectangular bore field configuration, with
boreholes placed in a staggered configuration with uniform spacing between
boreholes.
Parameters
----------
N_1 : int
Number of borehole in the x direction.
N_2 : int
Number of borehole in the y direction.
B : float
Distance (in meters) between adjacent boreholes.
H : float
Borehole length (in meters).
D : float
Borehole buried depth (in meters).
r_b : float
Borehole radius (in meters).
include_last_borehole : bool
If True, then each row of boreholes has equal numbers of boreholes.
If False, then the staggered rows have one borehole less so they are
contained within the imaginary 'box' around the borefield.
tilt : float, optional
Angle (in radians) from vertical of the axis of the borehole. The
orientation of the tilt is orthogonal to the origin coordinate.
Default is 0.
origin : tuple, optional
A coordinate indicating the origin of reference for orientation of
boreholes.
Default is the center of the rectangle.
Returns
-------
boreField : list of Borehole objects
List of boreholes in the rectangular bore field.
Notes
-----
Boreholes located at the origin will remain vertical.
Examples
--------
>>> boreField = gt.boreholes.rectangle_field(
N_1=3, N_2=2, B_1=5., B_2=5., H=100., D=2.5, r_b=0.05,
include_last_borehole=True)
The bore field is constructed line by line. For N_1=3 and N_2=3, the bore
field layout is as follows, if `include_last_borehole` is True::
6 7 8
3 4 5
0 1 2
and if `include_last_borehole` is False::
5 6 7
3 4
0 1 2
"""
if N_1 == 1:
# line field
return rectangle_field(N_1, N_2, B, B, H, D, r_b, tilt, origin)
return staggered_rectangle_field(
N_1, N_2, B, np.sqrt(3)/2 * B, H, D, r_b, include_last_borehole,
tilt=tilt, origin=origin)


def L_shaped_field(N_1, N_2, B_1, B_2, H, D, r_b, tilt=0., origin=None):
"""
Build a list of boreholes in a L-shaped bore field configuration.
Expand Down
90 changes: 90 additions & 0 deletions tests/boreholes_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,96 @@ def test_rectangular_field(N_1, N_2, B_1, B_2):
])


# Test staggered_rectangle_field
@pytest.mark.parametrize("N_1, N_2, B_1, B_2, include_last_element", [
(1, 1, 5., 5., True), # 1 by 1
(2, 1, 5., 5., True), # 2 by 1
(1, 2, 5., 5., True), # 1 by 2
(2, 2, 5., 7.5, True), # 2 by 2 (different x/y spacings)
(10, 9, 7.5, 5., True), # 10 by 9 (different x/y spacings),
(1, 1, 5., 5., False), # 1 by 1
(2, 1, 5., 5., False), # 2 by 1
(1, 2, 5., 5., False), # 1 by 2
(2, 2, 5., 7.5, False), # 2 by 2 (different x/y spacings)
(10, 9, 7.5, 5., False), # 10 by 9 (different x/y spacings)
])
def test_staggered_rectangular_field(N_1, N_2, B_1, B_2, include_last_element):
H = 150. # Borehole length [m]
D = 4. # Borehole buried depth [m]
r_b = 0.075 # Borehole radius [m]
# Generate the bore field
field = gt.boreholes.staggered_rectangle_field(
N_1, N_2, B_1, B_2, H, D, r_b,
include_last_borehole=include_last_element)
# Evaluate the borehole to borehole distances
x = np.array([b.x for b in field])
y = np.array([b.y for b in field])
dis = np.sqrt(
np.subtract.outer(x, x)**2 + np.subtract.outer(y, y)**2)[
~np.eye(len(field), dtype=bool)]

if include_last_element or N_1 == 1 or N_2 == 1:
expected_nBoreholes = N_1 * N_2
elif N_2 % 2 == 0:
expected_nBoreholes = N_2 * (2 * N_1 - 1) / 2
else:
expected_nBoreholes = (N_2 - 1) * (2 * N_1 - 1) / 2 + N_1

assert np.all(
[len(field) == expected_nBoreholes,
np.allclose(H, [b.H for b in field]),
np.allclose(D, [b.D for b in field]),
np.allclose(r_b, [b.r_b for b in field]),
len(field) == 1 or np.isclose(
np.min(dis), min(B_1, np.sqrt(B_2**2 + 0.25 * B_1**2))),
])


# Test dense_rectangle_field
@pytest.mark.parametrize("N_1, N_2, B, include_last_element", [
(1, 1, 5., True), # 1 by 1
(2, 1, 5., True), # 2 by 1
(1, 2, 5., True), # 1 by 2
(2, 2, 5., True), # 2 by 2
(10, 9, 7.5, True), # 10 by 9
(10, 10, 7.5, True), # 10 by 10
(1, 1, 5., False), # 1 by 1
(2, 1, 5., False), # 2 by 1
(1, 2, 5., False), # 1 by 2
(2, 2, 5., False), # 2 by 2
(10, 9, 7.5, False), # 10 by 9
(10, 10, 7.5, False), # 10 by 10
])
def test_dense_rectangle_field(N_1, N_2, B, include_last_element):
H = 150. # Borehole length [m]
D = 4. # Borehole buried depth [m]
r_b = 0.075 # Borehole radius [m]
# Generate the bore field
field = gt.boreholes.dense_rectangle_field(
N_1, N_2, B, H, D, r_b, include_last_borehole=include_last_element)
# Evaluate the borehole to borehole distances
x = np.array([b.x for b in field])
y = np.array([b.y for b in field])
dis = np.sqrt(
np.subtract.outer(x, x)**2 + np.subtract.outer(y, y)**2)[
~np.eye(len(field), dtype=bool)]

if include_last_element or N_1 == 1 or N_2 == 1:
expected_nBoreholes = N_1 * N_2
elif N_2 % 2 == 0:
expected_nBoreholes = N_2 * (2 * N_1 - 1) / 2
else:
expected_nBoreholes = (N_2 - 1) * (2 * N_1 - 1) / 2 + N_1

assert np.all(
[len(field) == expected_nBoreholes,
np.allclose(H, [b.H for b in field]),
np.allclose(D, [b.D for b in field]),
np.allclose(r_b, [b.r_b for b in field]),
len(field) == 1 or np.isclose(np.min(dis), B)
])


# Test L_shaped_field
@pytest.mark.parametrize("N_1, N_2, B_1, B_2", [
(1, 1, 5., 5.), # 1 by 1
Expand Down

0 comments on commit 6b15c86

Please sign in to comment.