from json import loads, dumps
from pathlib import Path
from sys import exit, stderr
from time import time
from typing import List, Optional, Union
import click
from jsonschema import ValidationError
from ghedesigner import VERSION
from ghedesigner.borehole import GHEBorehole
from ghedesigner.constants import DEG_TO_RAD
from ghedesigner.design import AnyBisectionType, DesignBase, DesignNearSquare, DesignRectangle, DesignBiRectangle
from ghedesigner.design import DesignBiZoned, DesignBiRectangleConstrained, DesignRowWise
from ghedesigner.enums import BHPipeType, TimestepType, DesignGeomType, FlowConfigType
from ghedesigner.geometry import GeometricConstraints, GeometricConstraintsRectangle, GeometricConstraintsNearSquare
from ghedesigner.geometry import GeometricConstraintsBiRectangle, GeometricConstraintsBiZoned
from ghedesigner.geometry import GeometricConstraintsBiRectangleConstrained, GeometricConstraintsRowWise
from ghedesigner.media import GHEFluid, Grout, Pipe, Soil
from ghedesigner.output import OutputManager
from ghedesigner.simulation import SimulationParameters
from ghedesigner.utilities import write_idf
from ghedesigner.validate import validate_input_file
[docs]
class GHEManager:
def __init__(self):
self._fluid: Optional[GHEFluid] = None
self._grout: Optional[Grout] = None
self._soil: Optional[Soil] = None
self._pipe: Optional[Pipe] = None
self.pipe_type: Optional[BHPipeType] = None
self._borehole: Optional[GHEBorehole] = None
self._simulation_parameters: Optional[SimulationParameters] = None
self._ground_loads: Optional[List[float]] = None
# OK so geometric_constraints is tricky. We have base classes, yay!
# Unfortunately, the functionality between the child classes is not actually
# collapsed into a base class function ... yet. So there will be complaints
# about types temporarily. It's going in the right direction though.
self.geom_type: Optional[DesignGeomType] = None
self._geometric_constraints: Optional[GeometricConstraints] = None
self._design: Optional[DesignBase] = None
self._search: Optional[AnyBisectionType] = None
self.results: Optional[OutputManager] = None
# some things for results
self._search_time: int = 0
self.summary_results: dict = {}
[docs]
def set_design_geometry_type(self, design_geometry_str: str, throw: bool = True) -> int:
"""
Sets the design type.
:param design_geometry_str: design geometry input string.
:param throw: By default, function will raise an exception on error, override to false to not raise exception
:returns: Zero if successful, nonzero if failure
:rtype: int
"""
design_geometry_str = str(design_geometry_str).upper()
if design_geometry_str == DesignGeomType.BIRECTANGLE.name:
self.geom_type = DesignGeomType.BIRECTANGLE
elif design_geometry_str == DesignGeomType.BIRECTANGLECONSTRAINED.name:
self.geom_type = DesignGeomType.BIRECTANGLECONSTRAINED
elif design_geometry_str == DesignGeomType.BIZONEDRECTANGLE.name:
self.geom_type = DesignGeomType.BIZONEDRECTANGLE
elif design_geometry_str == DesignGeomType.NEARSQUARE.name:
self.geom_type = DesignGeomType.NEARSQUARE
elif design_geometry_str == DesignGeomType.RECTANGLE.name:
self.geom_type = DesignGeomType.RECTANGLE
elif design_geometry_str == DesignGeomType.ROWWISE.name:
self.geom_type = DesignGeomType.ROWWISE
else:
message = "Geometry constraint method not supported."
print(message, file=stderr)
if throw:
raise ValueError(message)
return 1
return 0
[docs]
def set_pipe_type(self, bh_pipe_str: str, throw: bool = True) -> int:
"""
Sets the borehole pipe type.
:param bh_pipe_str: pipe type input string.
:param throw: By default, function will raise an exception on error, override to false to not raise exception
:returns: Zero if successful, nonzero if failure
:rtype: int
"""
bh_pipe_str = str(bh_pipe_str).upper()
if bh_pipe_str == BHPipeType.SINGLEUTUBE.name:
self.pipe_type = BHPipeType.SINGLEUTUBE
elif bh_pipe_str == BHPipeType.DOUBLEUTUBEPARALLEL.name:
self.pipe_type = BHPipeType.DOUBLEUTUBEPARALLEL
elif bh_pipe_str == BHPipeType.DOUBLEUTUBESERIES.name:
self.pipe_type = BHPipeType.DOUBLEUTUBESERIES
elif bh_pipe_str == BHPipeType.COAXIAL.name:
self.pipe_type = BHPipeType.COAXIAL
else:
message = f"Borehole pipe type \"{bh_pipe_str}\" not supported."
print(message, file=stderr)
if throw:
raise ValueError(message)
return 1
return 0
[docs]
def set_fluid(self, fluid_name: str = "Water", concentration_percent: float = 0.0, temperature: float = 20.0,
throw: bool = True) -> int:
"""
Sets the fluid instance.
:param fluid_name: fluid name input string.
:param concentration_percent: concentration percent of antifreeze mixture.
:param temperature: design fluid temperature, in C.
:param throw: By default, function will raise an exception on error, override to false to not raise exception
:returns: Zero if successful, nonzero if failure
:rtype: int
"""
try:
self._fluid = GHEFluid(fluid_str=fluid_name,
percent=concentration_percent,
temperature=temperature)
return 0
except ValueError:
message = "Invalid fluid property input data."
print(message, file=stderr)
if throw:
raise ValueError(message)
return 1
[docs]
def set_grout(self, conductivity: float, rho_cp: float) -> int:
"""
Sets the grout instance.
:param conductivity: thermal conductivity, in W/m-K.
:param rho_cp: volumetric heat capacity, in J/m^3-K.
:returns: Zero if successful, nonzero if failure
:rtype: int
"""
self._grout = Grout(conductivity, rho_cp)
return 0
[docs]
def set_soil(self, conductivity: float, rho_cp: float, undisturbed_temp: float) -> int:
"""
Sets the soil instance.
:param conductivity: thermal conductivity, in W/m-K.
:param rho_cp: volumetric heat capacity, in J/m^3-K.
:param undisturbed_temp: undisturbed soil temperature, in C.
:returns: Zero if successful, nonzero if failure
:rtype: int
"""
self._soil = Soil(conductivity, rho_cp, undisturbed_temp)
return 0
[docs]
def set_single_u_tube_pipe(self, inner_diameter: float, outer_diameter: float, shank_spacing: float,
roughness: float, conductivity: float, rho_cp: float) -> int:
"""
Sets the pipe instance for a single u-tube pipe.
:param inner_diameter: inner pipe diameter, in m.
:param outer_diameter: outer pipe diameter, in m.
:param shank_spacing: shank spacing between the u-tube legs, in m, as measured edge-to-edge.
:param roughness: pipe surface roughness, in m.
:param conductivity: thermal conductivity, in W/m-K.
:param rho_cp: volumetric heat capacity, in J/m^3-K.
:returns: Zero if successful, nonzero if failure
:rtype: int
"""
r_in = inner_diameter / 2.0
r_out = outer_diameter / 2.0
self.pipe_type = BHPipeType.SINGLEUTUBE
pipe_positions = Pipe.place_pipes(shank_spacing, r_out, 1)
self._pipe = Pipe(pipe_positions, r_in, r_out, shank_spacing, roughness, conductivity, rho_cp)
return 0
[docs]
def set_double_u_tube_pipe_parallel(self, inner_diameter: float, outer_diameter: float, shank_spacing: float,
roughness: float, conductivity: float, rho_cp: float) -> int:
"""
Sets the pipe instance for a double u-tube pipe in a parallel configuration.
:param inner_diameter: inner pipe diameter, in m.
:param outer_diameter: outer pipe diameter, in m.
:param shank_spacing: shank spacing between the u-tube legs, in m, as measured edge-to-edge.
:param roughness: pipe surface roughness, in m.
:param conductivity: thermal conductivity, in W/m-K.
:param rho_cp: volumetric heat capacity, in J/m^3-K.
:returns: Zero if successful, nonzero if failure
:rtype: int
"""
r_in = inner_diameter / 2.0
r_out = outer_diameter / 2.0
self.pipe_type = BHPipeType.DOUBLEUTUBEPARALLEL
pipe_positions = Pipe.place_pipes(shank_spacing, r_out, 2)
self._pipe = Pipe(pipe_positions, r_in, r_out, shank_spacing, roughness, conductivity, rho_cp)
return 0
[docs]
def set_double_u_tube_pipe_series(self, inner_diameter: float, outer_diameter: float, shank_spacing: float,
roughness: float, conductivity: float, rho_cp: float) -> int:
"""
Sets the pipe instance for a double u-tube pipe in a series configuration.
:param inner_diameter: inner pipe diameter, in m.
:param outer_diameter: outer pipe diameter, in m.
:param shank_spacing: shank spacing between the u-tube legs, in m, as measured edge-to-edge.
:param roughness: pipe surface roughness, in m.
:param conductivity: thermal conductivity, in W/m-K.
:param rho_cp: volumetric heat capacity, in J/m^3-K.
:returns: Zero if successful, nonzero if failure
:rtype: int
"""
r_in = inner_diameter / 2.0
r_out = outer_diameter / 2.0
self.pipe_type = BHPipeType.DOUBLEUTUBESERIES
pipe_positions = Pipe.place_pipes(shank_spacing, r_out, 2)
self._pipe = Pipe(pipe_positions, r_in, r_out, shank_spacing, roughness, conductivity, rho_cp)
return 0
[docs]
def set_coaxial_pipe(self, inner_pipe_d_in: float, inner_pipe_d_out: float, outer_pipe_d_in: float,
outer_pipe_d_out: float, roughness: float, conductivity_inner: float,
conductivity_outer: float,
rho_cp: float) -> int:
"""
Sets the pipe instance for a coaxial pipe.
:param inner_pipe_d_in: inner pipe inner diameter, in m.
:param inner_pipe_d_out: inner pipe outer diameter, in m.
:param outer_pipe_d_in: outer pipe inner diameter, in m.
:param outer_pipe_d_out: outer pipe outer diameter, in m.
:param roughness: pipe surface roughness, in m.
:param conductivity_inner: thermal conductivity of inner pipe, in W/m-K.
:param conductivity_outer: thermal conductivity of outer pipe, in W/m-K.
:param rho_cp: volumetric heat capacity, in J/m^3-K.
:returns: Zero if successful, nonzero if failure
:rtype: int
"""
self.pipe_type = BHPipeType.COAXIAL
# Note: This convention is different from pygfunction
r_inner = [inner_pipe_d_in / 2.0, inner_pipe_d_out / 2.0] # The radii of the inner pipe from in to out
r_outer = [outer_pipe_d_in / 2.0, outer_pipe_d_out / 2.0] # The radii of the outer pipe from in to out
k_p = [conductivity_inner, conductivity_outer]
self._pipe = Pipe((0, 0), r_inner, r_outer, 0, roughness, k_p, rho_cp)
return 0
[docs]
def set_borehole(self, height: float, buried_depth: float, diameter: float) -> int:
"""
Sets the borehole instance
:param height: height, or active length, of the borehole, in m.
:param buried_depth: depth of top of borehole below the ground surface, in m.
:param diameter: diameter of the borehole, in m.
:returns: Zero if successful, nonzero if failure
:rtype: int
"""
radius = diameter / 2.0
self._borehole = GHEBorehole(height, buried_depth, radius, x=0.0, y=0.0)
return 0
[docs]
def set_simulation_parameters(
self, num_months: int, max_eft: float, min_eft: float, max_height: float, min_height: float,
max_boreholes: int = None, continue_if_design_unmet: bool = False
) -> int:
"""
Sets the simulation parameters
:param num_months: number of months in simulation.
:param max_eft: maximum heat pump entering fluid temperature, in C.
:param min_eft: minimum heat pump entering fluid temperature, in C.
:param max_height: maximum height of borehole, in m.
:param min_height: minimum height of borehole, in m.
:param max_boreholes: maximum boreholes in search algorithms.
:param continue_if_design_unmet: continues to process if design unmet.
:returns: Zero if successful, nonzero if failure
:rtype: int
"""
self._simulation_parameters = SimulationParameters(
1,
num_months,
max_eft,
min_eft,
max_height,
min_height,
max_boreholes,
continue_if_design_unmet
)
return 0
[docs]
def set_ground_loads_from_hourly_list(self, hourly_ground_loads: List[float]) -> int:
"""
Sets the ground loads based on a list input.
:param hourly_ground_loads: annual, hourly ground loads, in W.
positive values indicate heat extraction, negative values indicate heat rejection.
:returns: Zero if successful, nonzero if failure
:rtype: int
"""
# TODO: Add API methods for different load inputs
self._ground_loads = hourly_ground_loads
return 0
[docs]
def set_geometry_constraints_near_square(self, b: float, length: float) -> int:
"""
Sets the geometry constraints for the near-square design method.
:param b: borehole-to-borehole spacing, in m.
:param length: side length of the sizing domain, in m.
:returns: Zero if successful, nonzero if failure
:rtype: int
"""
self._geometric_constraints = GeometricConstraintsNearSquare(b, length)
return 0
[docs]
def set_geometry_constraints_rectangle(self, length: float, width: float, b_min: float, b_max: float) -> int:
"""
Sets the geometry constraints for the rectangle design method.
:param length: side length of the sizing domain, in m.
:param width: side width of the sizing domain, in m.
:param b_min: minimum borehole-to-borehole spacing, in m.
:param b_max: maximum borehole-to-borehole spacing, in m.
:returns: Zero if successful, nonzero if failure
:rtype: int
"""
self.geom_type = DesignGeomType.RECTANGLE
self._geometric_constraints = GeometricConstraintsRectangle(width, length, b_min, b_max)
return 0
[docs]
def set_geometry_constraints_bi_rectangle(self, length: float, width: float, b_min: float,
b_max_x: float, b_max_y: float) -> int:
"""
Sets the geometry constraints for the bi-rectangle design method.
:param length: side length of the sizing domain, in m.
:param width: side width of the sizing domain, in m.
:param b_min: minimum borehole-to-borehole spacing, in m.
:param b_max_x: maximum borehole-to-borehole spacing in the x-direction, in m.
:param b_max_y: maximum borehole-to-borehole spacing in the y-direction, in m.
:returns: Zero if successful, nonzero if failure
:rtype: int
"""
self.geom_type = DesignGeomType.BIRECTANGLE
self._geometric_constraints = GeometricConstraintsBiRectangle(width, length, b_min, b_max_x, b_max_y)
return 0
[docs]
def set_geometry_constraints_bi_zoned_rectangle(self, length: float, width: float, b_min: float,
b_max_x: float, b_max_y: float) -> int:
"""
Sets the geometry constraints for the bi-zoned rectangle design method.
:param length: side length of the sizing domain, in m.
:param width: side width of the sizing domain, in m.
:param b_min: minimum borehole-to-borehole spacing, in m.
:param b_max_x: maximum borehole-to-borehole spacing in the x-direction, in m.
:param b_max_y: maximum borehole-to-borehole spacing in the y-direction, in m.
:returns: Zero if successful, nonzero if failure
:rtype: int
"""
self.geom_type = DesignGeomType.BIZONEDRECTANGLE
self._geometric_constraints = GeometricConstraintsBiZoned(width, length, b_min, b_max_x, b_max_y)
return 0
[docs]
def set_geometry_constraints_bi_rectangle_constrained(self, b_min: float, b_max_x: float, b_max_y: float,
property_boundary: list, no_go_boundaries: list) -> int:
"""
Sets the geometry constraints for the bi-rectangle constrained design method.
:param b_min: minimum borehole-to-borehole spacing, in m.
:param b_max_x: maximum borehole-to-borehole spacing in the x-direction, in m.
:param b_max_y: maximum borehole-to-borehole spacing in the y-direction, in m.
:param property_boundary: property boundary points, in m.
:param no_go_boundaries: boundary points for no-go zones, in m.
:returns: Zero if successful, nonzero if failure
:rtype: int
"""
self.geom_type = DesignGeomType.BIRECTANGLECONSTRAINED
self._geometric_constraints = GeometricConstraintsBiRectangleConstrained(b_min, b_max_x, b_max_y,
property_boundary, no_go_boundaries)
return 0
[docs]
def set_geometry_constraints_rowwise(self, perimeter_spacing_ratio: Union[float, None],
max_spacing: float, min_spacing: float, spacing_step: float,
max_rotation: float, min_rotation: float, rotate_step: float,
property_boundary: list, no_go_boundaries: list) -> int:
"""
Sets the geometry constraints for the row-wise design method.
:param perimeter_spacing_ratio: the ratio between the minimum spacing between
boreholes placed along the property and no-go zones and the standard borehole-to-borehole
spacing used for internal boreholes.
:param max_spacing: the largest minimum spacing that will be used to generate a RowWise field.
:param min_spacing: the smallest minimum spacing that will be used to generate a RowWise field.
:param spacing_step: the distance in spacing from the design found in the first part of first
search to exhaustively check in the second part.
:param max_rotation: the maximum rotation of the rows of each field relative to horizontal that
will be used in the search.
:param min_rotation: the minimum rotation of the rows of each field relative to horizontal that
will be used in the search.
:param rotate_step: step size for field rotation search.
:param property_boundary: property boundary points.
:param no_go_boundaries: boundary points for no-go zones.
:returns: Zero if successful, nonzero if failure
:rtype: int
"""
# convert from degrees to radians
max_rotation = max_rotation * DEG_TO_RAD
min_rotation = min_rotation * DEG_TO_RAD
self.geom_type = DesignGeomType.ROWWISE
self._geometric_constraints = GeometricConstraintsRowWise(perimeter_spacing_ratio,
min_spacing, max_spacing, spacing_step,
min_rotation, max_rotation, rotate_step,
property_boundary, no_go_boundaries)
return 0
[docs]
def set_design(self, flow_rate: float, flow_type_str: str, throw: bool = True) -> int:
"""
Set the design method.
:param flow_rate: design flow rate, in lps.
:param flow_type_str: flow type string input.
:param throw: By default, function will raise an exception on error, override to false to not raise exception
:returns: Zero if successful, nonzero if failure
:rtype: int
"""
flow_type_str = flow_type_str.upper()
if flow_type_str == FlowConfigType.SYSTEM.name:
flow_type = FlowConfigType.SYSTEM
elif flow_type_str == FlowConfigType.BOREHOLE.name:
flow_type = FlowConfigType.BOREHOLE
else:
message = f"FlowConfig \"{flow_type_str}\" is not implemented."
print(message, file=stderr)
if throw:
raise ValueError(message)
return 1
if self._geometric_constraints.type is None:
message = "Geometric constraints must be set before `set_design` is called."
print(message, file=stderr)
if throw:
raise ValueError(message)
return 1
if self._geometric_constraints.type == DesignGeomType.NEARSQUARE:
# temporary disable of the type checker because of the _geometric_constraints member
# noinspection PyTypeChecker
self._design = DesignNearSquare(
flow_rate,
self._borehole,
self.pipe_type,
self._fluid,
self._pipe,
self._grout,
self._soil,
self._simulation_parameters,
self._geometric_constraints,
self._ground_loads,
flow_type=flow_type,
method=TimestepType.HYBRID,
)
elif self._geometric_constraints.type == DesignGeomType.RECTANGLE:
# temporary disable of the type checker because of the _geometric_constraints member
# noinspection PyTypeChecker
self._design = DesignRectangle(
flow_rate,
self._borehole,
self.pipe_type,
self._fluid,
self._pipe,
self._grout,
self._soil,
self._simulation_parameters,
self._geometric_constraints,
self._ground_loads,
flow_type=flow_type,
method=TimestepType.HYBRID,
)
elif self._geometric_constraints.type == DesignGeomType.BIRECTANGLE:
# temporary disable of the type checker because of the _geometric_constraints member
# noinspection PyTypeChecker
self._design = DesignBiRectangle(
flow_rate,
self._borehole,
self.pipe_type,
self._fluid,
self._pipe,
self._grout,
self._soil,
self._simulation_parameters,
self._geometric_constraints,
self._ground_loads,
flow_type=flow_type,
method=TimestepType.HYBRID,
)
elif self._geometric_constraints.type == DesignGeomType.BIZONEDRECTANGLE:
# temporary disable of the type checker because of the _geometric_constraints member
# noinspection PyTypeChecker
self._design = DesignBiZoned(
flow_rate,
self._borehole,
self.pipe_type,
self._fluid,
self._pipe,
self._grout,
self._soil,
self._simulation_parameters,
self._geometric_constraints,
self._ground_loads,
flow_type=flow_type,
method=TimestepType.HYBRID,
)
elif self._geometric_constraints.type == DesignGeomType.BIRECTANGLECONSTRAINED:
# temporary disable of the type checker because of the _geometric_constraints member
# noinspection PyTypeChecker
self._design = DesignBiRectangleConstrained(
flow_rate,
self._borehole,
self.pipe_type,
self._fluid,
self._pipe,
self._grout,
self._soil,
self._simulation_parameters,
self._geometric_constraints,
self._ground_loads,
flow_type=flow_type,
method=TimestepType.HYBRID,
)
elif self._geometric_constraints.type == DesignGeomType.ROWWISE:
# temporary disable of the type checker because of the _geometric_constraints member
# noinspection PyTypeChecker
self._design = DesignRowWise(
flow_rate,
self._borehole,
self.pipe_type,
self._fluid,
self._pipe,
self._grout,
self._soil,
self._simulation_parameters,
self._geometric_constraints,
self._ground_loads,
flow_type=flow_type,
method=TimestepType.HYBRID,
)
else:
message = "This design method has not been implemented"
print(message, file=stderr)
if throw:
raise ValueError(message)
return 1
return 0
[docs]
def find_design(self, throw: bool = True) -> int:
"""
Calls design methods to execute sizing.
:param throw: By default, function will raise an exception on error, override to false to not raise exception
:returns: Zero if successful, nonzero if failure
:rtype: int
"""
if any([x is None for x in [
self._fluid,
self._grout,
self._soil,
self._pipe,
self._borehole,
self._simulation_parameters,
self._ground_loads,
self._geometric_constraints,
self._design,
]]):
message = "All GHE properties must be set before GHEManager.find_design is called."
print(message, file=stderr)
if throw:
raise ValueError(message)
return 1
start_time = time()
self._search = self._design.find_design()
self._search.ghe.compute_g_functions()
self._search_time = time() - start_time
self._search.ghe.size(method=TimestepType.HYBRID)
return 0
[docs]
def prepare_results(self, project_name: str, note: str, author: str, iteration_name: str):
"""
Prepares the output results.
"""
self.results = OutputManager(
self._search,
self._search_time,
project_name,
note,
author,
iteration_name,
load_method=TimestepType.HYBRID,
)
[docs]
def write_output_files(self, output_directory: Path, output_file_suffix: str = ""):
"""
Writes the output files.
:param output_directory: output directory for output files.
:param output_file_suffix: adds a string suffix to the output files.
"""
self.results.write_all_output_files(output_directory=output_directory, file_suffix=output_file_suffix)
def _run_manager_from_cli_worker(input_file_path: Path, output_directory: Path) -> int:
"""
Worker function to run simulation.
:param input_file_path: path to input file. Input file must exist.
:param output_directory: path to write output files. Output directory must be a valid path.
"""
# validate inputs against schema before doing anything
if validate_input_file(input_file_path) != 0:
return 1
inputs = loads(input_file_path.read_text())
ghe = GHEManager()
version = inputs['version']
if version != VERSION:
print("Mismatched version, could be a problem", file=stderr)
fluid_props = inputs['fluid'] # type: dict
grout_props = inputs['grout'] # type: dict
soil_props = inputs['soil'] # type: dict
pipe_props = inputs['pipe'] # type: dict
borehole_props = inputs['borehole'] # type: dict
sim_props = inputs['simulation'] # type: dict
constraint_props = inputs['geometric_constraints'] # type: dict
design_props = inputs['design'] # type: dict
ground_load_props = inputs['loads']['ground_loads'] # type: list
ghe.set_fluid(**fluid_props, throw=False)
ghe.set_grout(**grout_props)
ghe.set_soil(**soil_props)
ghe.set_pipe_type(pipe_props["arrangement"], throw=False)
if ghe.pipe_type == BHPipeType.SINGLEUTUBE:
ghe.set_single_u_tube_pipe(
inner_diameter=pipe_props["inner_diameter"],
outer_diameter=pipe_props["outer_diameter"],
shank_spacing=pipe_props["shank_spacing"],
roughness=pipe_props["roughness"],
conductivity=pipe_props["conductivity"],
rho_cp=pipe_props["rho_cp"]
)
elif ghe.pipe_type == BHPipeType.DOUBLEUTUBEPARALLEL:
ghe.set_double_u_tube_pipe_parallel(
inner_diameter=pipe_props["inner_diameter"],
outer_diameter=pipe_props["outer_diameter"],
shank_spacing=pipe_props["shank_spacing"],
roughness=pipe_props["roughness"],
conductivity=pipe_props["conductivity"],
rho_cp=pipe_props["rho_cp"]
)
elif ghe.pipe_type == BHPipeType.DOUBLEUTUBESERIES:
ghe.set_double_u_tube_pipe_series(
inner_diameter=pipe_props["inner_diameter"],
outer_diameter=pipe_props["outer_diameter"],
shank_spacing=pipe_props["shank_spacing"],
roughness=pipe_props["roughness"],
conductivity=pipe_props["conductivity"],
rho_cp=pipe_props["rho_cp"]
)
elif ghe.pipe_type == BHPipeType.COAXIAL:
ghe.set_coaxial_pipe(
inner_pipe_d_in=pipe_props["inner_pipe_d_in"],
inner_pipe_d_out=pipe_props["inner_pipe_d_out"],
outer_pipe_d_in=pipe_props["outer_pipe_d_in"],
outer_pipe_d_out=pipe_props["outer_pipe_d_out"],
roughness=pipe_props["roughness"],
conductivity_inner=pipe_props["conductivity_inner"],
conductivity_outer=pipe_props["conductivity_outer"],
rho_cp=pipe_props["rho_cp"]
)
ghe.set_borehole(
height=constraint_props["max_height"],
buried_depth=borehole_props["buried_depth"],
diameter=borehole_props["diameter"]
)
ghe.set_ground_loads_from_hourly_list(ground_load_props)
max_bh = design_props["max_boreholes"] if "max_boreholes" in design_props.keys() else None
continue_if_design_unmet = design_props[
"continue_if_design_unmet"] if "continue_if_design_unmet" in design_props.keys() else False
ghe.set_simulation_parameters(
num_months=sim_props["num_months"],
max_eft=design_props["max_eft"],
min_eft=design_props["min_eft"],
max_height=constraint_props["max_height"],
min_height=constraint_props["min_height"],
max_boreholes=max_bh,
continue_if_design_unmet=continue_if_design_unmet
)
if ghe.set_design_geometry_type(constraint_props["method"], throw=False) != 0:
return 1
if ghe.geom_type == DesignGeomType.RECTANGLE:
ghe.set_geometry_constraints_rectangle(
length=constraint_props["length"],
width=constraint_props["width"],
b_min=constraint_props["b_min"],
b_max=constraint_props["b_max"],
)
elif ghe.geom_type == DesignGeomType.NEARSQUARE:
ghe.set_geometry_constraints_near_square(
b=constraint_props["b"],
length=constraint_props["length"]
)
elif ghe.geom_type == DesignGeomType.BIRECTANGLE:
ghe.set_geometry_constraints_bi_rectangle(
length=constraint_props["length"],
width=constraint_props["width"],
b_min=constraint_props["b_min"],
b_max_x=constraint_props["b_max_x"],
b_max_y=constraint_props["b_max_y"]
)
elif ghe.geom_type == DesignGeomType.BIZONEDRECTANGLE:
ghe.set_geometry_constraints_bi_zoned_rectangle(
length=constraint_props["length"],
width=constraint_props["width"],
b_min=constraint_props["b_min"],
b_max_x=constraint_props["b_max_x"],
b_max_y=constraint_props["b_max_y"]
)
elif ghe.geom_type == DesignGeomType.BIRECTANGLECONSTRAINED:
ghe.set_geometry_constraints_bi_rectangle_constrained(
b_min=constraint_props["b_min"],
b_max_x=constraint_props["b_max_x"],
b_max_y=constraint_props["b_max_y"],
property_boundary=constraint_props["property_boundary"],
no_go_boundaries=constraint_props["no_go_boundaries"]
)
elif ghe.geom_type == DesignGeomType.ROWWISE:
# We are using perimeter calculations if present
if "perimeter_spacing_ratio" in constraint_props.keys():
perimeter_spacing_ratio = constraint_props["perimeter_spacing_ratio"]
else:
perimeter_spacing_ratio = None
ghe.set_geometry_constraints_rowwise(
perimeter_spacing_ratio=perimeter_spacing_ratio,
max_spacing=constraint_props["max_spacing"],
min_spacing=constraint_props["min_spacing"],
spacing_step=constraint_props["spacing_step"],
max_rotation=constraint_props["max_rotation"],
min_rotation=constraint_props["min_rotation"],
rotate_step=constraint_props["rotate_step"],
property_boundary=constraint_props["property_boundary"],
no_go_boundaries=constraint_props["no_go_boundaries"]
)
else:
print("Geometry constraint method not supported.", file=stderr)
return 1
ghe.set_design(
flow_rate=design_props["flow_rate"],
flow_type_str=design_props["flow_type"],
throw=False
)
ghe.find_design(throw=False)
ghe.prepare_results("GHEDesigner Run from CLI", "Notes", "Author", "Iteration Name")
ghe.write_output_files(output_directory)
return 0
@click.command(name="GHEDesignerCommandLine")
@click.argument("input-path", type=click.Path(exists=True), required=True)
@click.argument("output-directory", type=click.Path(exists=False), required=False)
@click.version_option(VERSION)
@click.option(
"--validate-only",
default=False,
is_flag=True,
show_default=False,
help="Validate input file and exit."
)
@click.option(
"-c",
"--convert",
help="Convert output to specified format. Options supported: 'IDF'."
)
def run_manager_from_cli(input_path, output_directory, validate_only, convert):
input_path = Path(input_path).resolve()
if validate_only:
try:
validate_input_file(input_path)
print("Valid input file.")
return 0
except ValidationError:
print("Schema validation error. See previous error message for details.", file=stderr)
return 1
if convert:
if convert == "IDF":
try:
write_idf(input_path)
print("Output converted to IDF objects.")
return 0
except:
print("Conversion to IDF error.", file=stderr)
return 1
else:
print(f"Unsupported conversion format type: {format}", file=stderr)
return 1
if output_directory is None:
print('Output directory path must be passed as an argument, aborting', file=stderr)
return 1
output_path = Path(output_directory).resolve()
return _run_manager_from_cli_worker(input_path, output_path)
if __name__ == "__main__":
exit(run_manager_from_cli())