Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions demos/run_simulation_presized_manual_tilted.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"version": 2,
"topology": [
{
"type": "ground_heat_exchanger",
"name": "ghe1"
}
],
"fluid": {
"fluid_name": "WATER",
"concentration_percent": 0,
"temperature": 20
},
"ground_heat_exchanger": {
"ghe1": {
"flow_rate": 0.5,
"flow_type": "BOREHOLE",
"grout": {
"conductivity": 1,
"rho_cp": 3901000
},
"soil": {
"conductivity": 2,
"rho_cp": 2343493,
"undisturbed_temp": 18.3
},
"pipe": {
"inner_diameter": 0.03404,
"outer_diameter": 0.04216,
"shank_spacing": 0.01856,
"roughness": 0.000001,
"conductivity": 0.4,
"rho_cp": 1542000,
"arrangement": "SINGLEUTUBE"
},
"borehole": {
"buried_depth": 4,
"diameter": 0.15
},
"pre_designed": {
"arrangement": "MANUAL",
"H": 150,
"x": [0.0, 7.5, 15.0, 22.5, 30.0, 37.5, 45.0, 52.5],
"y": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
"tilts": [0.349, 0.349, 0.349, 0.349, 0.349, 0.349, 0.349, 0.349],
"orientations": [
3.142, 2.356, -2.356, 1.571, -1.571, 0.785, -0.785, 0.0
]
}
}
}
}
16 changes: 16 additions & 0 deletions ghedesigner/ghe/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,12 +416,26 @@ def get_g_function(self, ghe_dict: dict, boundary_condition="MIFT") -> tuple[nda
if len(x_positions) != len(y_positions):
raise RuntimeError("Borehole location coordinate mismatch, make sure length of x and y are equal")
locations = list(zip(x_positions, y_positions))
# Preserve None when tilts/orientations are omitted so the equivalent
# solver stays in use for vertical fields.
tilts: Sequence[float] | None = pre_designed.get("tilts")
orientations: Sequence[float] | None = pre_designed.get("orientations")
if (tilts is None) != (orientations is None):
raise RuntimeError("tilts and orientations must both be provided or omitted")
if (
tilts is not None
and orientations is not None
and (len(tilts) != len(locations) or len(orientations) != len(locations))
):
raise RuntimeError("tilts/orientations arrays must match length of x/y")
elif pre_designed["arrangement"] == "RECTANGLE":
num_bh_x = pre_designed["boreholes_in_x_dimension"]
num_bh_y = pre_designed["boreholes_in_y_dimension"]
spacing_x = pre_designed["spacing_in_x_dimension"]
spacing_y = pre_designed["spacing_in_y_dimension"]
locations = rectangle(num_bh_x, num_bh_y, spacing_x, spacing_y)
tilts = None
orientations = None
else:
raise RuntimeError("Invalid arrangement type for pre_designed borehole field")

Expand Down Expand Up @@ -453,6 +467,8 @@ def get_g_function(self, ghe_dict: dict, boundary_condition="MIFT") -> tuple[nda
self.grout,
self.soil,
boundary_condition=boundary_condition,
tilts=tilts,
orientations=orientations,
)

single_u_bh = SingleUTube(
Expand Down
14 changes: 14 additions & 0 deletions ghedesigner/schemas/ghedesigner.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,20 @@
"items": {
"type": "number"
}
},
"tilts": {
"type": "array",
"description": "Optional per-borehole tilt angle from vertical, in radians, one value for each borehole in the field. Defaults to vertical if omitted. Length must match x and y.",
"items": {
"type": "number"
}
},
"orientations": {
"type": "array",
"description": "Optional per-borehole orientation (azimuth of the tilt direction), in radians, one value for each borehole in the field. Defaults to vertical if omitted. Length must match x and y.",
"items": {
"type": "number"
}
}
},
"required": ["arrangement", "H", "x", "y"],
Expand Down
67 changes: 67 additions & 0 deletions ghedesigner/tests/test_pre_designed_ghe.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from unittest import TestCase

import pytest

from ghedesigner.enums import PipeType
from ghedesigner.ghe.manager import GroundHeatExchanger

Expand Down Expand Up @@ -84,3 +86,68 @@ def test_pre_designed_ghe_single_u(self):

# TODO: should be investigated further - test values are not as close as I had hoped, but
# MIFT results are consistent with what was happening previously

def test_pre_designed_ghe_tilted_manual(self):
"""Optional tilts/orientations arrays in MANUAL pre_designed produce
g-functions that differ from the equivalent all-vertical configuration,
and mismatched array lengths raise."""
ghe = GroundHeatExchanger(
grout_conductivity=1.0,
grout_rho_cp=3901000.0,
soil_conductivity=2.0,
soil_rho_cp=2000000,
soil_undisturbed_temperature=10,
borehole_buried_depth=2.0,
borehole_radius=0.08,
pipe_arrangement_type=PipeType.SINGLEUTUBE,
pipe_parameters={
"conductivity": 0.4,
"rho_cp": 1500000.0,
"inner_diameter": 0.03404,
"outer_diameter": 0.04216,
"shank_spacing": 0.01856,
"roughness": 1e-6,
},
fluid_name="water",
fluid_concentration_percent=0,
fluid_temperature=20,
)

# 2x2 field at 5 m, baseline all-vertical
base = {
"flow_rate": 0.5,
"flow_type": "BOREHOLE",
"pre_designed": {
"arrangement": "MANUAL",
"H": 192,
"x": [0.0, 0.0, 5.0, 5.0],
"y": [0.0, 5.0, 0.0, 5.0],
},
}
_, g_vertical, _ = ghe.get_g_function(base, boundary_condition="UBWT")

# Same field, two boreholes tilted 15 degrees (0.262 rad)
tilted = {
**base,
"pre_designed": {
**base["pre_designed"],
"tilts": [0.0, 0.0, 0.262, 0.262],
"orientations": [0.0, 0.0, 0.0, 0.0],
},
}
_, g_tilted, _ = ghe.get_g_function(tilted, boundary_condition="UBWT")

# Tilt must affect long-time field interaction; LTS g-function must differ
self.assertNotAlmostEqual(float(g_vertical[-1]), float(g_tilted[-1]), delta=0.01)

# Length-mismatch validation
bad = {
**base,
"pre_designed": {
**base["pre_designed"],
"tilts": [0.262],
"orientations": [0.0],
},
}
with pytest.raises(RuntimeError):
ghe.get_g_function(bad, boundary_condition="UBWT")