From 0cf52941ce642092221ce06017f3e5c670b86a14 Mon Sep 17 00:00:00 2001 From: wouterpeere Date: Sat, 11 Nov 2023 12:35:48 +0100 Subject: [PATCH 01/14] Implementation of the method. --- examples/regular_bore_field.py | 17 +++++++---- pygfunction/boreholes.py | 53 ++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/examples/regular_bore_field.py b/examples/regular_bore_field.py index aea2e78c..c45d3de4 100644 --- a/examples/regular_bore_field.py +++ b/examples/regular_bore_field.py @@ -27,25 +27,30 @@ 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 = gt.boreholes.rectangle_field(N_1, N_2, B, B, H, D, r_b) + + # Dense field of 4 x 3 borehole rows + dense_field = gt.boreholes.dense_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) + box_field = gt.boreholes.box_shaped_field(N_1, N_2, B, B, H, D, r_b) # U-shaped field of 4 x 3 boreholes - UField = gt.boreholes.U_shaped_field(N_1, N_2, B, B, H, D, r_b) + U_field = gt.boreholes.U_shaped_field(N_1, N_2, B, B, H, D, r_b) # L-shaped field of 4 x 3 boreholes - LField = gt.boreholes.L_shaped_field(N_1, N_2, B, B, H, D, r_b) + L_field = gt.boreholes.L_shaped_field(N_1, N_2, B, B, H, D, r_b) # Circular field of 8 boreholes - circleField = gt.boreholes.circle_field(N_b, R, H, D, r_b) + circle_field = gt.boreholes.circle_field(N_b, R, H, D, r_b) # ------------------------------------------------------------------------- # Draw bore fields # ------------------------------------------------------------------------- - for field in [rectangularField, boxField, UField, LField, circleField]: + for field in [rectangular_field, dense_field, box_field, U_field, L_field, circle_field]: gt.boreholes.visualize_field(field) + import matplotlib.pyplot as plt + plt.show() return diff --git a/pygfunction/boreholes.py b/pygfunction/boreholes.py index 5da7bf23..ace81732 100644 --- a/pygfunction/boreholes.py +++ b/pygfunction/boreholes.py @@ -713,6 +713,59 @@ def rectangle_field(N_1, N_2, B_1, B_2, H, D, r_b, tilt=0., origin=None): return borefield +def dense_field(N_1, N_2, B, H, D, r_b, include_last_borehole = True): + """ + Build a list of boreholes in a dense bore field configuration. + Here, the high density cylinder packing is used. This means that every borehole is a distance B (in meters) + away from each other. + + 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 + True if each row of boreholes should have equal lengths. False, if the uneven rows have one borehole less + so they are contained within the imaginary 'box' around the borefield + + Returns + ------- + boreField : list of Borehole objects + List of boreholes in the dense bore field. + + Examples + -------- + >>> boreField = gt.boreholes.dense_field(N_1=3, N_2=2, B=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:: + + 6 7 8 + 3 4 5 + 0 1 2 + + """ + borefield = [] + + for j in range(N_2): # y direction + for i in range(N_1): # x direction + x = i * B + (B/2 if j % 2 == 1 else 0) + y = j * B/2 + if include_last_borehole or (j % 2 == 0 or i != N_1 - 1): # last borehole in the x direction on an oneven row + borefield.append(Borehole(H, D, r_b, x, y)) + + return borefield + + 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. From 18666d6ce6ba97abd50ac12d91d014f270d8d9e5 Mon Sep 17 00:00:00 2001 From: wouterpeere Date: Sat, 11 Nov 2023 12:47:11 +0100 Subject: [PATCH 02/14] Implement tests --- .github/workflows/test.yml | 2 ++ tests/boreholes_test.py | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 29bb9918..75e78d9b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,10 +5,12 @@ on: branches: - master - maintenance/2.2.x + - create_dense_borefield pull_request: branches: - master - maintenance/2.2.x + - create_dense_borefield types: [opened, synchronize, reopened] jobs: diff --git a/tests/boreholes_test.py b/tests/boreholes_test.py index ebc3c5e9..8ff60740 100644 --- a/tests/boreholes_test.py +++ b/tests/boreholes_test.py @@ -94,6 +94,44 @@ def test_rectangular_field(N_1, N_2, B_1, B_2): ]) +# Test dense_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 + (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 + ]) +def test_rectangular_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_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: + assert len(field) == N_1 * N_2 + else: + assert len(field) == N_2 * N_1 * (N_1 - 1) / 2 + (0 if N_2 % 2 == 0 else N_2) + + assert np.all( + [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 From 9fd58c2b3c3ecc94d8b6c4be14a235790ff77a62 Mon Sep 17 00:00:00 2001 From: wouterpeere Date: Sat, 11 Nov 2023 13:14:37 +0100 Subject: [PATCH 03/14] Implemented tests --- pygfunction/boreholes.py | 8 ++++++-- tests/boreholes_test.py | 15 +++++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/pygfunction/boreholes.py b/pygfunction/boreholes.py index ace81732..ac700b41 100644 --- a/pygfunction/boreholes.py +++ b/pygfunction/boreholes.py @@ -756,10 +756,14 @@ def dense_field(N_1, N_2, B, H, D, r_b, include_last_borehole = True): """ borefield = [] + # check for line + if N_1 == 1 or N_2 == 1: + return rectangle_field(N_1, N_2, B, B, H, D, r_b) + for j in range(N_2): # y direction for i in range(N_1): # x direction - x = i * B + (B/2 if j % 2 == 1 else 0) - y = j * B/2 + x = i * B*np.sqrt(2) + (B*np.sqrt(2)/2 if j % 2 == 1 else 0) + y = j * B*np.sqrt(2)/2 if include_last_borehole or (j % 2 == 0 or i != N_1 - 1): # last borehole in the x direction on an oneven row borefield.append(Borehole(H, D, r_b, x, y)) diff --git a/tests/boreholes_test.py b/tests/boreholes_test.py index 8ff60740..90a119fd 100644 --- a/tests/boreholes_test.py +++ b/tests/boreholes_test.py @@ -101,13 +101,16 @@ def test_rectangular_field(N_1, N_2, B_1, B_2): (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 - ]) -def test_rectangular_field(N_1, N_2, B, include_last_element): + (10, 10, 7.5, False), # 10 by 10 + +]) +def test_dense_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] @@ -120,10 +123,12 @@ def test_rectangular_field(N_1, N_2, B, include_last_element): np.subtract.outer(x, x)**2 + np.subtract.outer(y, y)**2)[ ~np.eye(len(field), dtype=bool)] - if include_last_element: + if include_last_element or N_1 == 1 or N_2 == 1: assert len(field) == N_1 * N_2 + elif N_2 % 2 == 0: + assert len(field) == N_2 * (2 * N_1 - 1) / 2 else: - assert len(field) == N_2 * N_1 * (N_1 - 1) / 2 + (0 if N_2 % 2 == 0 else N_2) + assert len(field) == (N_2 - 1) * (2 * N_1 - 1) / 2 + N_1 assert np.all( [np.allclose(H, [b.H for b in field]), @@ -140,6 +145,8 @@ def test_rectangular_field(N_1, N_2, B, include_last_element): (2, 2, 5., 7.5), # 2 by 2 (different x/y spacings) (10, 9, 7.5, 5.), # 10 by 9 (different x/y spacings) ]) + + def test_L_shaped_field(N_1, N_2, B_1, B_2): H = 150. # Borehole length [m] D = 4. # Borehole buried depth [m] From 6fe29e65533a3cf4ccb181a3d6e27ae3b700b8a8 Mon Sep 17 00:00:00 2001 From: wouterpeere Date: Mon, 20 Nov 2023 11:52:20 +0100 Subject: [PATCH 04/14] Implemented other y-coord --- pygfunction/boreholes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygfunction/boreholes.py b/pygfunction/boreholes.py index ac700b41..4bfbf8ed 100644 --- a/pygfunction/boreholes.py +++ b/pygfunction/boreholes.py @@ -762,8 +762,8 @@ def dense_field(N_1, N_2, B, H, D, r_b, include_last_borehole = True): for j in range(N_2): # y direction for i in range(N_1): # x direction - x = i * B*np.sqrt(2) + (B*np.sqrt(2)/2 if j % 2 == 1 else 0) - y = j * B*np.sqrt(2)/2 + x = i * B + (B/2 if j % 2 == 1 else 0) + y = j * B*np.sqrt(3)/2 if include_last_borehole or (j % 2 == 0 or i != N_1 - 1): # last borehole in the x direction on an oneven row borefield.append(Borehole(H, D, r_b, x, y)) From d0d7e7f2962f041949d3236ab2c08256c50d8f82 Mon Sep 17 00:00:00 2001 From: wouterpeere Date: Wed, 22 Nov 2023 16:33:30 +0100 Subject: [PATCH 05/14] Implement changes --- examples/regular_bore_field.py | 9 ++- pygfunction/boreholes.py | 139 ++++++++++++++++++++++++++++----- tests/boreholes_test.py | 4 +- 3 files changed, 126 insertions(+), 26 deletions(-) diff --git a/examples/regular_bore_field.py b/examples/regular_bore_field.py index c45d3de4..632bb0ba 100644 --- a/examples/regular_bore_field.py +++ b/examples/regular_bore_field.py @@ -29,8 +29,11 @@ def main(): # Rectangular field of 4 x 3 boreholes rectangular_field = gt.boreholes.rectangle_field(N_1, N_2, B, B, H, D, r_b) - # Dense field of 4 x 3 borehole rows - dense_field = gt.boreholes.dense_field(N_1, N_2, B, H, D, r_b, False) + # Rectangular field triangular field of 4 x 3 borehole rows + rectangular_field_triangular = gt.boreholes.rectangle_field_triangular(N_1, N_2, B, B, H, D, r_b, False) + + # Dense field triangular field of 4 x 3 borehole rows + dense_field = gt.boreholes.dense_rectangle_field(N_1, N_2, B, H, D, r_b, False) # Box-shaped field of 4 x 3 boreholes box_field = gt.boreholes.box_shaped_field(N_1, N_2, B, B, H, D, r_b) @@ -47,7 +50,7 @@ def main(): # ------------------------------------------------------------------------- # Draw bore fields # ------------------------------------------------------------------------- - for field in [rectangular_field, dense_field, box_field, U_field, L_field, circle_field]: + for field in [rectangular_field, rectangular_field_triangular, dense_field, box_field, U_field, L_field, circle_field]: gt.boreholes.visualize_field(field) import matplotlib.pyplot as plt plt.show() diff --git a/pygfunction/boreholes.py b/pygfunction/boreholes.py index 4bfbf8ed..11e65bb3 100644 --- a/pygfunction/boreholes.py +++ b/pygfunction/boreholes.py @@ -713,11 +713,10 @@ def rectangle_field(N_1, N_2, B_1, B_2, H, D, r_b, tilt=0., origin=None): return borefield -def dense_field(N_1, N_2, B, H, D, r_b, include_last_borehole = True): +def rectangle_field_triangular(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 dense bore field configuration. - Here, the high density cylinder packing is used. This means that every borehole is a distance B (in meters) - away from each other. + Build a list of boreholes in a rectangular bore field configuration, with boreholes + placed in a triangular pattern. Parameters ---------- @@ -725,8 +724,10 @@ def dense_field(N_1, N_2, B, H, D, r_b, include_last_borehole = True): 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. + 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 @@ -736,36 +737,134 @@ def dense_field(N_1, N_2, B, H, D, r_b, include_last_borehole = True): include_last_borehole : bool True if each row of boreholes should have equal lengths. False, if the uneven 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 dense bore field. + List of boreholes in the rectangular bore field. + + Notes + ----- + Boreholes located at the origin will remain vertical. Examples -------- - >>> boreField = gt.boreholes.dense_field(N_1=3, N_2=2, B=5., H=100., D=2.5, r_b=0.05, include_last_borehole=True) + >>> 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:: + field layout is as follows, if `include_last_borehole` is True:: - 6 7 8 - 3 4 5 - 0 1 2 + 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 = [] - # check for line if N_1 == 1 or N_2 == 1: - return rectangle_field(N_1, N_2, B, B, H, D, r_b) + return rectangle_field(N_1, N_2, B_1, B_2, H, D, r_b, tilt, origin) - for j in range(N_2): # y direction - for i in range(N_1): # x direction - x = i * B + (B/2 if j % 2 == 1 else 0) - y = j * B*np.sqrt(3)/2 - if include_last_borehole or (j % 2 == 0 or i != N_1 - 1): # last borehole in the x direction on an oneven row - borefield.append(Borehole(H, D, r_b, x, y)) + if origin is None: + # When no origin is supplied, compute the origin to be at the center of + # the rectangle + x0 = (N_1 - 1) / 2 * B_1 + y0 = (N_2 - 1) / 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 hexagonal pattern. + + 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. + H : float + Borehole length (in meters). + D : float + Borehole buried depth (in meters). + r_b : float + Borehole radius (in meters). + include_last_borehole : bool + True if each row of boreholes should have equal lengths. False, if the uneven 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 + + """ + borefield = rectangle_field_triangular(N_1, N_2, B, np.sqrt(3)/2 * B, H, D, r_b, include_last_borehole, tilt=tilt, origin=origin) return borefield diff --git a/tests/boreholes_test.py b/tests/boreholes_test.py index 90a119fd..5f5a0a78 100644 --- a/tests/boreholes_test.py +++ b/tests/boreholes_test.py @@ -115,7 +115,7 @@ def test_dense_field(N_1, N_2, B, include_last_element): D = 4. # Borehole buried depth [m] r_b = 0.075 # Borehole radius [m] # Generate the bore field - field = gt.boreholes.dense_field(N_1, N_2, B, H, D, r_b, include_last_borehole=include_last_element) + 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]) @@ -145,8 +145,6 @@ def test_dense_field(N_1, N_2, B, include_last_element): (2, 2, 5., 7.5), # 2 by 2 (different x/y spacings) (10, 9, 7.5, 5.), # 10 by 9 (different x/y spacings) ]) - - def test_L_shaped_field(N_1, N_2, B_1, B_2): H = 150. # Borehole length [m] D = 4. # Borehole buried depth [m] From 8b6db60937cd3a90babb52a6e0e571767ab33945 Mon Sep 17 00:00:00 2001 From: wouterpeere Date: Thu, 23 Nov 2023 09:11:08 +0100 Subject: [PATCH 06/14] Fix tests dense field --- pygfunction/boreholes.py | 7 ++++--- tests/boreholes_test.py | 3 +++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pygfunction/boreholes.py b/pygfunction/boreholes.py index 11e65bb3..a9b1698a 100644 --- a/pygfunction/boreholes.py +++ b/pygfunction/boreholes.py @@ -864,9 +864,10 @@ def dense_rectangle_field(N_1, N_2, B, H, D, r_b, include_last_borehole, tilt=0. 0 1 2 """ - borefield = rectangle_field_triangular(N_1, N_2, B, np.sqrt(3)/2 * B, H, D, r_b, include_last_borehole, tilt=tilt, origin=origin) - - return borefield + if N_1 == 1: + # line field + return rectangle_field(N_1, N_2, B, B, H, D, r_b, tilt, origin) + return rectangle_field_triangular(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): diff --git a/tests/boreholes_test.py b/tests/boreholes_test.py index 5f5a0a78..c7502c32 100644 --- a/tests/boreholes_test.py +++ b/tests/boreholes_test.py @@ -116,6 +116,9 @@ def test_dense_field(N_1, N_2, B, include_last_element): 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) + gt.boreholes.visualize_field(field) + import matplotlib.pyplot as plt + plt.show() # Evaluate the borehole to borehole distances x = np.array([b.x for b in field]) y = np.array([b.y for b in field]) From 01db1aa025f8331771f7ade619a209c144ca691b Mon Sep 17 00:00:00 2001 From: wouterpeere Date: Thu, 23 Nov 2023 09:24:58 +0100 Subject: [PATCH 07/14] Implement test triangular field --- tests/boreholes_test.py | 67 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/tests/boreholes_test.py b/tests/boreholes_test.py index c7502c32..c87441e0 100644 --- a/tests/boreholes_test.py +++ b/tests/boreholes_test.py @@ -93,6 +93,58 @@ def test_rectangular_field(N_1, N_2, B_1, B_2): len(field) == 1 or np.isclose(np.min(dis), min(B_1, B_2)), ]) +# Test test_triangular_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_triangular_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.rectangle_field_triangular(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: + assert len(field) == N_1 * N_2 + elif N_2 % 2 == 0: + assert len(field) == N_2 * (2 * N_1 - 1) / 2 + else: + assert len(field) == (N_2 - 1) * (2 * N_1 - 1) / 2 + N_1 + + assert np.all( + [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.min(dis) >= np.minimum(B_1, B_2)]) + + # check correct x and y-positions + def find_nearest(array, value): + array = np.asarray(array) + idx = (np.abs(array - value)).argmin() + return array[idx] + + possible_x_coor = np.arange(0, N_1, 0.5) * B_1 + possible_y_coor = np.arange(0, N_2, 1) * B_2 + for borehole in field: + assert np.isclose(borehole.x, find_nearest(possible_x_coor, borehole.x)) + assert np.isclose(borehole.y, find_nearest(possible_y_coor, borehole.y)) + # Test dense_field @pytest.mark.parametrize("N_1, N_2, B, include_last_element", [ @@ -116,9 +168,6 @@ def test_dense_field(N_1, N_2, B, include_last_element): 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) - gt.boreholes.visualize_field(field) - import matplotlib.pyplot as plt - plt.show() # Evaluate the borehole to borehole distances x = np.array([b.x for b in field]) y = np.array([b.y for b in field]) @@ -139,6 +188,18 @@ def test_dense_field(N_1, N_2, B, include_last_element): np.allclose(r_b, [b.r_b for b in field]), len(field) == 1 or np.isclose(np.min(dis), B)]) + # check correct x and y-positions + def find_nearest(array, value): + array = np.asarray(array) + idx = (np.abs(array - value)).argmin() + return array[idx] + + possible_x_coor = np.arange(0, N_1, 0.5) * B + possible_y_coor = np.arange(0, N_2, 1) * B * (np.sqrt(3)/2 if N_1 > 1 else 1) + for borehole in field: + assert np.isclose(borehole.x, find_nearest(possible_x_coor, borehole.x)) + assert np.isclose(borehole.y, find_nearest(possible_y_coor, borehole.y)) + # Test L_shaped_field @pytest.mark.parametrize("N_1, N_2, B_1, B_2", [ From 90cb8bda97e85771d39286b687d381a74bc41a2b Mon Sep 17 00:00:00 2001 From: Massimo Cimmino Date: Mon, 22 Apr 2024 15:40:02 -0400 Subject: [PATCH 08/14] Remove branch from tests --- .github/workflows/test.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 75e78d9b..29bb9918 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,12 +5,10 @@ on: branches: - master - maintenance/2.2.x - - create_dense_borefield pull_request: branches: - master - maintenance/2.2.x - - create_dense_borefield types: [opened, synchronize, reopened] jobs: From 51b4621f78c256901bd545f599e49be3fd6975bf Mon Sep 17 00:00:00 2001 From: Massimo Cimmino Date: Mon, 22 Apr 2024 15:57:00 -0400 Subject: [PATCH 09/14] Rename field functions --- examples/regular_bore_field.py | 6 +++--- pygfunction/boreholes.py | 37 +++++++++++++++++++++------------- tests/boreholes_test.py | 10 ++++----- 3 files changed, 31 insertions(+), 22 deletions(-) diff --git a/examples/regular_bore_field.py b/examples/regular_bore_field.py index 632bb0ba..fea0665d 100644 --- a/examples/regular_bore_field.py +++ b/examples/regular_bore_field.py @@ -30,10 +30,10 @@ def main(): rectangular_field = gt.boreholes.rectangle_field(N_1, N_2, B, B, H, D, r_b) # Rectangular field triangular field of 4 x 3 borehole rows - rectangular_field_triangular = gt.boreholes.rectangle_field_triangular(N_1, N_2, B, B, H, D, r_b, False) + staggered_rectangular_field = 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 - dense_field = gt.boreholes.dense_rectangle_field(N_1, N_2, B, H, D, r_b, False) + dense_rectangular_field = gt.boreholes.dense_rectangle_field(N_1, N_2, B, H, D, r_b, False) # Box-shaped field of 4 x 3 boreholes box_field = gt.boreholes.box_shaped_field(N_1, N_2, B, B, H, D, r_b) @@ -50,7 +50,7 @@ def main(): # ------------------------------------------------------------------------- # Draw bore fields # ------------------------------------------------------------------------- - for field in [rectangular_field, rectangular_field_triangular, dense_field, box_field, U_field, L_field, circle_field]: + for field in [rectangular_field, staggered_rectangular_field, dense_rectangular_field, box_field, U_field, L_field, circle_field]: gt.boreholes.visualize_field(field) import matplotlib.pyplot as plt plt.show() diff --git a/pygfunction/boreholes.py b/pygfunction/boreholes.py index a9b1698a..d2aecca1 100644 --- a/pygfunction/boreholes.py +++ b/pygfunction/boreholes.py @@ -713,10 +713,12 @@ def rectangle_field(N_1, N_2, B_1, B_2, H, D, r_b, tilt=0., origin=None): return borefield -def rectangle_field_triangular(N_1, N_2, B_1, B_2, H, D, r_b, include_last_borehole, tilt=0., origin=None): +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 triangular pattern. + Build a list of boreholes in a rectangular bore field configuration, with + boreholes placed in a staggered configuration. Parameters ---------- @@ -735,8 +737,9 @@ def rectangle_field_triangular(N_1, N_2, B_1, B_2, H, D, r_b, include_last_boreh r_b : float Borehole radius (in meters). include_last_borehole : bool - True if each row of boreholes should have equal lengths. False, if the uneven rows have one borehole less - so they are contained within the imaginary 'box' around the borefield + 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. @@ -805,10 +808,12 @@ def rectangle_field_triangular(N_1, N_2, B_1, B_2, H, D, r_b, include_last_boreh return borefield -def dense_rectangle_field(N_1, N_2, B, H, D, r_b, include_last_borehole, tilt=0., origin=None): +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 hexagonal pattern. + Build a list of boreholes in a rectangular bore field configuration, with + boreholes placed in a staggered configuration with uniform spacing between + boreholes. Parameters ---------- @@ -816,7 +821,7 @@ def dense_rectangle_field(N_1, N_2, B, H, D, r_b, include_last_borehole, tilt=0. Number of borehole in the x direction. N_2 : int Number of borehole in the y direction. - B_1 : float + B : float Distance (in meters) between adjacent boreholes. H : float Borehole length (in meters). @@ -825,8 +830,9 @@ def dense_rectangle_field(N_1, N_2, B, H, D, r_b, include_last_borehole, tilt=0. r_b : float Borehole radius (in meters). include_last_borehole : bool - True if each row of boreholes should have equal lengths. False, if the uneven rows have one borehole less - so they are contained within the imaginary 'box' around the borefield + 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. @@ -847,8 +853,9 @@ def dense_rectangle_field(N_1, N_2, B, H, D, r_b, include_last_borehole, tilt=0. 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) + >>> 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:: @@ -867,7 +874,9 @@ def dense_rectangle_field(N_1, N_2, B, H, D, r_b, include_last_borehole, tilt=0. if N_1 == 1: # line field return rectangle_field(N_1, N_2, B, B, H, D, r_b, tilt, origin) - return rectangle_field_triangular(N_1, N_2, B, np.sqrt(3)/2 * B, H, D, r_b, include_last_borehole, tilt=tilt, origin=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): diff --git a/tests/boreholes_test.py b/tests/boreholes_test.py index c87441e0..34fb47a3 100644 --- a/tests/boreholes_test.py +++ b/tests/boreholes_test.py @@ -93,7 +93,7 @@ def test_rectangular_field(N_1, N_2, B_1, B_2): len(field) == 1 or np.isclose(np.min(dis), min(B_1, B_2)), ]) -# Test test_triangular_field +# 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 @@ -107,12 +107,12 @@ def test_rectangular_field(N_1, N_2, B_1, B_2): (10, 9, 7.5, 5., False), # 10 by 9 (different x/y spacings) ]) -def test_triangular_field(N_1, N_2, B_1, B_2, include_last_element): +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.rectangle_field_triangular(N_1, N_2, B_1, B_2, H, D, r_b, include_last_borehole=include_last_element) + 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]) @@ -146,7 +146,7 @@ def find_nearest(array, value): assert np.isclose(borehole.y, find_nearest(possible_y_coor, borehole.y)) -# Test dense_field +# 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 @@ -162,7 +162,7 @@ def find_nearest(array, value): (10, 10, 7.5, False), # 10 by 10 ]) -def test_dense_field(N_1, N_2, B, include_last_element): +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] From 38e31512a93522c974d82b75e0b1c9dc73e27c54 Mon Sep 17 00:00:00 2001 From: Massimo Cimmino Date: Mon, 22 Apr 2024 15:57:19 -0400 Subject: [PATCH 10/14] Evaluate field origin for staggered configurations --- pygfunction/boreholes.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pygfunction/boreholes.py b/pygfunction/boreholes.py index d2aecca1..a2de3be2 100644 --- a/pygfunction/boreholes.py +++ b/pygfunction/boreholes.py @@ -785,8 +785,12 @@ def staggered_rectangle_field( if origin is None: # When no origin is supplied, compute the origin to be at the center of # the rectangle - x0 = (N_1 - 1) / 2 * B_1 - y0 = (N_2 - 1) / 2 * B_2 + 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 From 15ff1779553e078bdeb86a8bc955063e409a2a28 Mon Sep 17 00:00:00 2001 From: Massimo Cimmino Date: Mon, 22 Apr 2024 15:59:32 -0400 Subject: [PATCH 11/14] Format example --- examples/regular_bore_field.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/examples/regular_bore_field.py b/examples/regular_bore_field.py index fea0665d..5213024a 100644 --- a/examples/regular_bore_field.py +++ b/examples/regular_bore_field.py @@ -2,6 +2,8 @@ """ Example of definition of a bore field using pre-defined configurations. """ +import matplotlib.pyplot as plt + import pygfunction as gt @@ -27,32 +29,35 @@ def main(): # ------------------------------------------------------------------------- # Rectangular field of 4 x 3 boreholes - rectangular_field = gt.boreholes.rectangle_field(N_1, N_2, B, B, H, D, r_b) + 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 - staggered_rectangular_field = gt.boreholes.staggered_rectangle_field(N_1, N_2, B, B, H, D, r_b, False) + 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 - dense_rectangular_field = gt.boreholes.dense_rectangle_field(N_1, N_2, B, H, D, r_b, False) + denseRectangularField = gt.boreholes.dense_rectangle_field( + N_1, N_2, B, H, D, r_b, False) # Box-shaped field of 4 x 3 boreholes - box_field = gt.boreholes.box_shaped_field(N_1, N_2, B, B, H, D, r_b) + boxField = gt.boreholes.box_shaped_field(N_1, N_2, B, B, H, D, r_b) # U-shaped field of 4 x 3 boreholes - U_field = gt.boreholes.U_shaped_field(N_1, N_2, B, B, H, D, r_b) + UField = gt.boreholes.U_shaped_field(N_1, N_2, B, B, H, D, r_b) # L-shaped field of 4 x 3 boreholes - L_field = gt.boreholes.L_shaped_field(N_1, N_2, B, B, H, D, r_b) + LField = gt.boreholes.L_shaped_field(N_1, N_2, B, B, H, D, r_b) # Circular field of 8 boreholes - circle_field = gt.boreholes.circle_field(N_b, R, H, D, r_b) + circleField = gt.boreholes.circle_field(N_b, R, H, D, r_b) # ------------------------------------------------------------------------- # Draw bore fields # ------------------------------------------------------------------------- - for field in [rectangular_field, staggered_rectangular_field, dense_rectangular_field, box_field, U_field, L_field, circle_field]: + for field in [ + rectangularField, staggeredRectangularField, denseRectangularField, + boxField, UField, LField, circleField]: gt.boreholes.visualize_field(field) - import matplotlib.pyplot as plt plt.show() return From ccfe0754c77f830b71af1a758d6bca2bfc89dc60 Mon Sep 17 00:00:00 2001 From: Massimo Cimmino Date: Mon, 22 Apr 2024 16:08:07 -0400 Subject: [PATCH 12/14] Format tests --- tests/boreholes_test.py | 58 ++++++++++++++--------------------------- 1 file changed, 20 insertions(+), 38 deletions(-) diff --git a/tests/boreholes_test.py b/tests/boreholes_test.py index 34fb47a3..d1a20e97 100644 --- a/tests/boreholes_test.py +++ b/tests/boreholes_test.py @@ -93,6 +93,7 @@ def test_rectangular_field(N_1, N_2, B_1, B_2): len(field) == 1 or np.isclose(np.min(dis), min(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 @@ -105,14 +106,15 @@ def test_rectangular_field(N_1, N_2, B_1, B_2): (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) + 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]) @@ -121,29 +123,19 @@ def test_staggered_rectangular_field(N_1, N_2, B_1, B_2, include_last_element): ~np.eye(len(field), dtype=bool)] if include_last_element or N_1 == 1 or N_2 == 1: - assert len(field) == N_1 * N_2 + expected_nBoreholes = N_1 * N_2 elif N_2 % 2 == 0: - assert len(field) == N_2 * (2 * N_1 - 1) / 2 + expected_nBoreholes = N_2 * (2 * N_1 - 1) / 2 else: - assert len(field) == (N_2 - 1) * (2 * N_1 - 1) / 2 + N_1 + expected_nBoreholes = (N_2 - 1) * (2 * N_1 - 1) / 2 + N_1 assert np.all( - [np.allclose(H, [b.H for b in field]), + [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.min(dis) >= np.minimum(B_1, B_2)]) - - # check correct x and y-positions - def find_nearest(array, value): - array = np.asarray(array) - idx = (np.abs(array - value)).argmin() - return array[idx] - - possible_x_coor = np.arange(0, N_1, 0.5) * B_1 - possible_y_coor = np.arange(0, N_2, 1) * B_2 - for borehole in field: - assert np.isclose(borehole.x, find_nearest(possible_x_coor, borehole.x)) - assert np.isclose(borehole.y, find_nearest(possible_y_coor, borehole.y)) + len(field) == 1 or np.isclose(np.min(dis), min(B_1, B_2)), + ]) # Test dense_rectangle_field @@ -160,14 +152,14 @@ def find_nearest(array, value): (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) + 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]) @@ -176,29 +168,19 @@ def test_dense_rectangle_field(N_1, N_2, B, include_last_element): ~np.eye(len(field), dtype=bool)] if include_last_element or N_1 == 1 or N_2 == 1: - assert len(field) == N_1 * N_2 + expected_nBoreholes = N_1 * N_2 elif N_2 % 2 == 0: - assert len(field) == N_2 * (2 * N_1 - 1) / 2 + expected_nBoreholes = N_2 * (2 * N_1 - 1) / 2 else: - assert len(field) == (N_2 - 1) * (2 * N_1 - 1) / 2 + N_1 + expected_nBoreholes = (N_2 - 1) * (2 * N_1 - 1) / 2 + N_1 assert np.all( - [np.allclose(H, [b.H for b in field]), + [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)]) - - # check correct x and y-positions - def find_nearest(array, value): - array = np.asarray(array) - idx = (np.abs(array - value)).argmin() - return array[idx] - - possible_x_coor = np.arange(0, N_1, 0.5) * B - possible_y_coor = np.arange(0, N_2, 1) * B * (np.sqrt(3)/2 if N_1 > 1 else 1) - for borehole in field: - assert np.isclose(borehole.x, find_nearest(possible_x_coor, borehole.x)) - assert np.isclose(borehole.y, find_nearest(possible_y_coor, borehole.y)) + len(field) == 1 or np.isclose(np.min(dis), B) + ]) # Test L_shaped_field From b925acdc33fe327f4d0c995db6b69643866c52ba Mon Sep 17 00:00:00 2001 From: Massimo Cimmino Date: Mon, 22 Apr 2024 16:09:49 -0400 Subject: [PATCH 13/14] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8e778e1..72fb8e21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. From 969a1b13e241ed1c9a15f6146c95a2881b7dc268 Mon Sep 17 00:00:00 2001 From: Massimo Cimmino Date: Mon, 22 Apr 2024 16:17:10 -0400 Subject: [PATCH 14/14] Correct minimum distance in tests --- tests/boreholes_test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/boreholes_test.py b/tests/boreholes_test.py index d1a20e97..4a43a771 100644 --- a/tests/boreholes_test.py +++ b/tests/boreholes_test.py @@ -134,7 +134,8 @@ def test_staggered_rectangular_field(N_1, N_2, B_1, B_2, include_last_element): 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, B_2)), + len(field) == 1 or np.isclose( + np.min(dis), min(B_1, np.sqrt(B_2**2 + 0.25 * B_1**2))), ])