Source code for honeybee_phhvac.hot_water_system

# -*- coding: utf-8 -*-
# -*- Python Version: 2.7 -*-

"""Honeybee-PH-HVAC: Hot Water System."""

try:
    pass
except:
    pass  # IronPython

from uuid import uuid4

try:
    from ladybug_geometry.geometry3d.pointvector import Point3D, Vector3D
except ImportError as e:
    raise ImportError("Failed to import ladybug_geometry", e)

try:
    from honeybee_phhvac import hot_water_devices as hwd
    from honeybee_phhvac import hot_water_piping as hwp
except ImportError as e:
    raise ImportError("Failed to import honeybee_phhvac", e)


[docs] class PhHotWaterSystem_FromDictError(Exception): def __init__(self, _expected_types, _input_type): self.msg = 'Error: Expected type of "{}". Got: {}'.format(_expected_types, _input_type) super(PhHotWaterSystem_FromDictError, self).__init__(self.msg)
[docs] class PhHotWaterSystem(object): """PH-HVAC: Hot Water System.""" def __init__(self): self.identifier = str(uuid4()) self.id_num = 0 self.display_name = "_unnamed_hot_water_system_" self.tank_1 = None # type: Optional[hwd.PhHvacHotWaterTank] self.tank_2 = None # type: Optional[hwd.PhHvacHotWaterTank] self.tank_buffer = None # type: Optional[hwd.PhHvacHotWaterTank] self.tank_solar = None # type: Optional[hwd.PhHvacHotWaterTank] self._heaters = {} # type: Dict[str, hwd.PhHvacHotWaterHeater] self._distribution_piping = {} # type: Dict[str, hwp.PhHvacPipeTrunk] self._recirc_piping = {} # type: Dict[str, hwp.PhHvacPipeElement] self._number_tap_points = None # type: Optional[int] @property def total_distribution_pipe_length(self): # type: () -> float """Returns the total length of all trunk, branch, and fixture piping.""" return sum(_.total_length for _ in self._distribution_piping.values()) @property def total_home_run_fixture_pipe_length(self): # type: () -> float """Returns the total length of all fixture piping from end to end. NOTE: This method will count the branch and trunk lengths for EACH of the fixture pipes. The result will be a total hot-water transport length as if all the pipes were 'home-run' style. This value is used for the PHPP calculations and is not a true representation of the piping in the model. """ return sum(_.total_home_run_fixture_length for _ in self._distribution_piping.values()) @property def total_recirc_pipe_length(self): # type: () -> float """Returns the total length of all recirculation piping.""" return sum(_.length for _ in self._recirc_piping.values()) @property def recirc_temp(self): # type: () -> float """Return the length weighted average of recirculation piping temperatures""" if not self._recirc_piping or self.total_recirc_pipe_length == 0: return 60.0 return sum([v.water_temp_c * v.length for v in self._recirc_piping.values()]) / self.total_recirc_pipe_length @property def recirc_hours(self): # type: () -> int """Return the length-weighted average of recirculation piping hours.""" if not self._recirc_piping or self.total_recirc_pipe_length == 0: return 24 return int( sum([v.daily_period * v.length for v in self._recirc_piping.values()]) / self.total_recirc_pipe_length ) @property def number_tap_points(self): # type: () -> int """Unless set explicitly by the user, will return the number of Branch Pipe Elements.""" if self._number_tap_points: return self._number_tap_points else: return sum(br.num_fixtures for br in self._distribution_piping.values()) @number_tap_points.setter def number_tap_points(self, _input): # type: (Optional[int]) -> None if _input: self._number_tap_points = int(_input) @property def heaters(self): # type: () -> ValuesView[hwd.PhHvacHotWaterHeater] """Returns a list of all the heaters on the system.""" return self._heaters.values()
[docs] def clear_heaters(self): self._heaters = {}
[docs] def add_heater(self, _heater): # type: (Optional[hwd.PhHvacHotWaterHeater]) -> None """Adds a new hot-water heater to the system.""" if not _heater: return assert hasattr(_heater, "to_dict"), 'Error: HW-Heater "{}" is not serializable?'.format(_heater) self._heaters[_heater.identifier] = _heater
[docs] def add_distribution_piping(self, _distribution_piping, _key=None): # type: (Union[hwp.PhHvacPipeTrunk, hwp.PhHvacPipeBranch, hwp.PhHvacPipeElement], Optional[str]) -> None """Add a new distribution (branch, trunk, fixture) to the system. If a branch or fixture pipe is passed, a 0-length trunk will be created and the branch or fixture will be added to it before adding to the system. """ if isinstance(_distribution_piping, hwp.PhHvacPipeTrunk): # -- Add the trunk to the collection new_trunk = _distribution_piping elif isinstance(_distribution_piping, hwp.PhHvacPipeBranch): # -- Build a new Trunk and add the branch to it new_trunk = hwp.PhHvacPipeTrunk() new_trunk.add_branch(_distribution_piping) elif isinstance(_distribution_piping, hwp.PhHvacPipeElement): # -- Build a new Trunk and Branch, add the fixture to it new_branch = hwp.PhHvacPipeBranch() new_branch.add_fixture(_distribution_piping) new_trunk = hwp.PhHvacPipeTrunk() new_trunk.add_branch(new_branch) else: raise ValueError( 'Error: Expected type of "PhPipeTrunk", "PhPipeBranch", or "PhPipeElement". Got: {}'.format( type(_distribution_piping) ) ) self._distribution_piping[_key or new_trunk.identifier] = new_trunk
[docs] def clear_distribution_piping(self): """Clear all distribution piping (Trunks) from the system.""" self._distribution_piping = {}
@property def distribution_piping(self): # type: () -> ValuesView[hwp.PhHvacPipeTrunk] """Returns a list of all the distribution-piping (Trunks) in the system.""" return self._distribution_piping.values()
[docs] def add_recirc_piping(self, _recirc_piping, _key=None): # type: (hwp.PhHvacPipeElement, Optional[str]) -> None self._recirc_piping[_key or _recirc_piping.identifier] = _recirc_piping
[docs] def clear_recirc_piping(self): self._recirc_piping = {}
@property def recirc_piping(self): # type: () -> ValuesView[hwp.PhHvacPipeElement] """Returns a list of all the recirculation-piping objects in the system.""" return self._recirc_piping.values() @property def tanks(self): # type: () -> list[hwd.PhHvacHotWaterTank | None] """Return a list of the system tanks in order (1, 2, buffer, solar).""" return [self.tank_1, self.tank_2, self.tank_buffer, self.tank_solar]
[docs] def to_dict(self, abridged=False, _include_properties=False): # type: (bool, bool) -> dict[str, dict] d = {} if abridged: d["type"] = "PhHvacHotWaterSystemAbridged" else: d["type"] = "PhHvacHotWaterSystemPh" d["id_num"] = self.id_num d["identifier"] = self.identifier d["display_name"] = self.display_name if self.tank_1: d["tank_1"] = self.tank_1.to_dict(_include_properties) if self.tank_2: d["tank_2"] = self.tank_2.to_dict(_include_properties) if self.tank_buffer: d["tank_buffer"] = self.tank_buffer.to_dict(_include_properties) if self.tank_solar: d["tank_solar"] = self.tank_solar.to_dict(_include_properties) d["heaters"] = {} for heater in self.heaters: d["heaters"][heater.identifier] = heater.to_dict(_include_properties) d["distribution_piping"] = {} for distribution_piping in self.distribution_piping: d["distribution_piping"][distribution_piping.identifier] = distribution_piping.to_dict(_include_properties) d["recirc_piping"] = {} for recirc_piping in self.recirc_piping: d["recirc_piping"][recirc_piping.identifier] = recirc_piping.to_dict(_include_properties) d["number_tap_points"] = self._number_tap_points d["recirc_temp"] = self.recirc_temp d["recirc_hours"] = self.recirc_hours if _include_properties: d["total_distribution_pipe_length"] = self.total_distribution_pipe_length d["total_home_run_fixture_pipe_length"] = self.total_home_run_fixture_pipe_length d["total_recirc_pipe_length"] = self.total_recirc_pipe_length return d
[docs] @classmethod def from_dict(cls, _input_dict): # type: (dict) -> PhHotWaterSystem valid_types = ("PhHvacHotWaterSystemPh", "PhHvacHotWaterSystemAbridged") if _input_dict["type"] not in valid_types: raise PhHotWaterSystem_FromDictError(valid_types, _input_dict["type"]) new_system = cls() new_system.identifier = _input_dict.get("identifier", str(uuid4())) new_system.id_num = _input_dict["id_num"] new_system.display_name = _input_dict["display_name"] if _input_dict.get("tank_1", None): new_system.tank_1 = hwd.PhHvacHotWaterTank.from_dict(_input_dict["tank_1"]) if _input_dict.get("tank_2", None): new_system.tank_2 = hwd.PhHvacHotWaterTank.from_dict(_input_dict["tank_2"]) if _input_dict.get("tank_buffer", None): new_system.tank_buffer = hwd.PhHvacHotWaterTank.from_dict(_input_dict["tank_buffer"]) if _input_dict.get("tank_solar", None): new_system.tank_solar = hwd.PhHvacHotWaterTank.from_dict(_input_dict["tank_solar"]) for heater_dict in _input_dict["heaters"].values(): new_system.add_heater(hwd.PhHvacHotWaterHeaterBuilder.from_dict(heater_dict)) for distribution_piping_dict in _input_dict["distribution_piping"].values(): new_system.add_distribution_piping(hwp.PhHvacPipeTrunk.from_dict(distribution_piping_dict)) for recirc_piping_dict in _input_dict["recirc_piping"].values(): new_system.add_recirc_piping(hwp.PhHvacPipeElement.from_dict(recirc_piping_dict)) new_system._number_tap_points = _input_dict["number_tap_points"] return new_system
[docs] def apply_properties_from_dict(self, abridged_data): return
def __copy__(self): # type: () -> PhHotWaterSystem new_obj = PhHotWaterSystem() new_obj.id_num = self.id_num new_obj.display_name = self.display_name if self.tank_1: new_obj.tank_1 = self.tank_1.duplicate() if self.tank_2: new_obj.tank_2 = self.tank_2.duplicate() if self.tank_buffer: new_obj.tank_buffer = self.tank_buffer.duplicate() if self.tank_solar: new_obj.tank_solar = self.tank_solar.duplicate() for k, v in self._heaters.items(): new_obj.add_heater(v.duplicate()) for k, v in self._distribution_piping.items(): new_obj.add_distribution_piping(v.duplicate(), _key=k) for k, v in self._recirc_piping.items(): new_obj.add_recirc_piping(v.duplicate(), _key=k) new_obj._number_tap_points = self._number_tap_points return new_obj
[docs] def duplicate(self): # type: (Any) -> PhHotWaterSystem return self.__copy__()
def __str__(self): return "{}: id={}".format(self.__class__.__name__, self.id_num) def __repr__(self): """Properties representation.""" return "{!r}(id_num={!r}, tank_1={!r}, tank_2={!r}, tank_buffer={!r}, tank_solar={!r})".format( self.__class__.__name__, self.id_num, self.tank_1, self.tank_2, self.tank_buffer, self.tank_solar, )
[docs] def ToString(self): """Overwrite .NET ToString.""" return self.__repr__()
def __add__(self, other): # type: (PhHotWaterSystem) -> PhHotWaterSystem new_obj = self.duplicate() for heater in self.heaters: new_obj.add_heater(heater) for heater in other.heaters: new_obj.add_heater(heater) for distribution_pipe in self.distribution_piping: new_obj.add_distribution_piping(distribution_pipe) for distribution_pipe in other.distribution_piping: new_obj.add_distribution_piping(distribution_pipe) for recirc_pipe in self.recirc_piping: new_obj.add_recirc_piping(recirc_pipe) for recirc_pipe in other.recirc_piping: new_obj.add_recirc_piping(recirc_pipe) if self._number_tap_points or other._number_tap_points: # -- If either have their tap-points number set explicitly by the use # -- set the new object # as well new_obj.number_tap_points = self.number_tap_points + other.number_tap_points else: # -- If neither obj's have their tap-point number set explicitly by the user # -- Set as None, which will return the length of the Branch Piping dict by default. self._number_tap_points = None return new_obj def __radd__(self, other): # type: (PhHotWaterSystem) -> PhHotWaterSystem if isinstance(other, int): return self else: return self + other def __eq__(self, other): # type: (PhHotWaterSystem) -> bool if not isinstance(other, PhHotWaterSystem): return False if self.id_num != other.id_num: return False if self.tank_1 != other.tank_1: return False if self.tank_2 != other.tank_2: return False if self.tank_buffer != other.tank_buffer: return False if self.tank_solar != other.tank_solar: return False if len(self.heaters) != len(other.heaters): return False for heater in self.heaters: if heater not in other.heaters: return False if len(self.distribution_piping) != len(other.distribution_piping): return False for distribution_pipe in self.distribution_piping: if distribution_pipe not in other.distribution_piping: return False if len(self.recirc_piping) != len(other.recirc_piping): return False for recirc_pipe in self.recirc_piping: if recirc_pipe not in other.recirc_piping: return False if self.number_tap_points != other.number_tap_points: return False return True
[docs] def move(self, moving_vec3D): # type: (Point3D) -> PhHotWaterSystem """Move the System's piping along a vector. Args: moving_vec3D: A Vector3D with the direction and distance to move the ray. Reflect: A new PhHotWaterSystem object with the moved piping. """ new_system = self.duplicate() new_system.identifier = self.identifier new_system.clear_distribution_piping() new_system.clear_recirc_piping() for k, pipe in self._distribution_piping.items(): new_system.add_distribution_piping(pipe.move(moving_vec3D), _key=k) for k, pipe in self._recirc_piping.items(): new_system.add_recirc_piping(pipe.move(moving_vec3D), _key=k) return new_system
[docs] def rotate(self, axis_vec3D, angle_degrees, origin_pt3D): # type: (Point3D, float, Point3D) -> PhHotWaterSystem """Rotate the System's piping by a certain angle around an axis and origin. Right hand rule applies: If axis has a positive orientation, rotation will be clockwise. If axis has a negative orientation, rotation will be counterclockwise. Args: axis_vec3D: A Vector3D axis representing the axis of rotation. angle_degrees: An angle for rotation in degrees. origin_pt3D: A Point3D for the origin around which the object will be rotated. Returns: A new PhHotWaterSystem object with the rotated piping. """ new_system = self.duplicate() new_system.identifier = self.identifier new_system.clear_distribution_piping() new_system.clear_recirc_piping() for k, pipe in self._distribution_piping.items(): new_system.add_distribution_piping(pipe.rotate(axis_vec3D, angle_degrees, origin_pt3D), _key=k) for k, pipe in self._recirc_piping.items(): new_system.add_recirc_piping(pipe.rotate(axis_vec3D, angle_degrees, origin_pt3D), _key=k) return new_system
[docs] def rotate_xy(self, angle_degrees, origin_pt3D): # type: (float, Point3D) -> PhHotWaterSystem """Rotate the System's piping counterclockwise in the XY plane by a certain angle. Args: angle_degrees: An angle in degrees. origin_pt3D: A Point3D for the origin around which the object will be rotated. Returns: A new PhHotWaterSystem object with the rotated piping. """ new_system = self.duplicate() new_system.identifier = self.identifier new_system.clear_distribution_piping() new_system.clear_recirc_piping() for k, pipe in self._distribution_piping.items(): new_system.add_distribution_piping(pipe.rotate_xy(angle_degrees, origin_pt3D), _key=k) for k, pipe in self._recirc_piping.items(): new_system.add_recirc_piping(pipe.rotate_xy(angle_degrees, origin_pt3D), _key=k) return new_system
[docs] def reflect(self, normal_vec3D, origin_pt3D): # type: (Vector3D, Point3D) -> PhHotWaterSystem """Reflected the System's piping across a plane with the input normal vector and origin. Args: normal_vec3D: A Vector3D representing the normal vector for the plane across which the line segment will be reflected. THIS VECTOR MUST BE NORMALIZED. origin_pt3D: A Point3D representing the origin from which to reflect. Returns: A new PhHotWaterSystem object with the reflected piping. """ new_system = self.duplicate() new_system.identifier = self.identifier new_system.clear_distribution_piping() new_system.clear_recirc_piping() for k, pipe in self._distribution_piping.items(): new_system.add_distribution_piping(pipe.reflect(normal_vec3D, origin_pt3D), _key=k) for k, pipe in self._recirc_piping.items(): new_system.add_recirc_piping(pipe.reflect(normal_vec3D, origin_pt3D), _key=k) return new_system
[docs] def scale(self, scale_factor, origin_pt3D=None): # type: (float, Optional[Point3D]) -> PhHotWaterSystem """Scale the System's piping by a factor from an origin point. Args: scale_factor: A number representing how much the line segment should be scaled. origin_pt3D: A Point3D representing the origin from which to scale. If None, it will be scaled from the World origin (0, 0, 0). Returns: A new PhHotWaterSystem object with the scaled piping. """ new_system = self.duplicate() new_system.identifier = self.identifier new_system.clear_distribution_piping() new_system.clear_recirc_piping() for k, pipe in self._distribution_piping.items(): new_system.add_distribution_piping(pipe.scale(scale_factor, origin_pt3D), _key=k) for k, pipe in self._recirc_piping.items(): new_system.add_recirc_piping(pipe.scale(scale_factor, origin_pt3D), _key=k) return new_system