Compare commits

..

1 Commits

Author SHA1 Message Date
0d8572e94b feat: add DHNNode, DHNEdge and DHNNetwork classes 2024-12-04 17:10:54 -05:00
10 changed files with 260 additions and 24 deletions

View File

@ -0,0 +1,116 @@
from typing import List, Dict, Union
from hub.city_model_structure.attributes.edge import Edge
class DHNEdge(Edge):
"""
District Heating Network Edge class, inherits from Edge.
"""
def __init__(self, name, nodes=None, length=0.0, diameter=0.0, nominal_diameter=0,
mass_flow_rate=None, roughness=0.0, pressure_drop=0.0):
super().__init__(name, nodes)
self._length = length
self._diameter = diameter
self._nominal_diameter = nominal_diameter
self._mass_flow_rate = mass_flow_rate if mass_flow_rate else {} # Initialize as empty dictionary
self._roughness = roughness
self._pressure_drop = pressure_drop
@property
def length(self) -> float:
"""
Get length of the edge
:return: float
"""
return self._length
@length.setter
def length(self, value: Union[None, float]):
"""
Set length of the edge
:param value: float
"""
self._length = max(value or 0.0, 0.0)
@property
def diameter(self) -> float:
"""
Get diameter of the edge
:return: float
"""
return self._diameter
@diameter.setter
def diameter(self, value: Union[None, float]):
"""
Set diameter of the edge
:param value: float
"""
self._diameter = value if value and value > 0 else 0.1 # Default to 0.1 if invalid
@property
def nominal_diameter(self) -> int:
"""
Get nominal diameter of the edge
:return: int
"""
return self._nominal_diameter
@nominal_diameter.setter
def nominal_diameter(self, value: Union[None, int]):
"""
Set nominal diameter of the edge
:param value: int
"""
self._nominal_diameter = value if value and value > 0 else 1 # Default to 1 if invalid
@property
def mass_flow_rate(self) -> Dict:
"""
Get mass flow rate data
:return: dict
"""
return self._mass_flow_rate
@mass_flow_rate.setter
def mass_flow_rate(self, value: Union[None, Dict]):
"""
Set mass flow rate data
:param value: dict
"""
if value and isinstance(value, dict):
self._mass_flow_rate = value
else:
self._mass_flow_rate = {} # Default to empty dictionary
@property
def roughness(self) -> float:
"""
Get roughness of the edge
:return: float
"""
return self._roughness
@roughness.setter
def roughness(self, value: Union[None, float]):
"""
Set roughness of the edge
:param value: float
"""
self._roughness = max(value or 0.0, 0.0)
@property
def pressure_drop(self) -> float:
"""
Get pressure drop across the edge
:return: float
"""
return self._pressure_drop
@pressure_drop.setter
def pressure_drop(self, value: Union[None, float]):
"""
Set pressure drop across the edge
:param value: float
"""
self._pressure_drop = max(value or 0.0, 0.0)

View File

@ -0,0 +1,81 @@
from typing import Union
from hub.city_model_structure.attributes.node import Node
class DHNNode(Node):
"""
District Heating Network Node class, inherits from Node.
"""
def __init__(self, name, edges=None, pos=(0, 0), heat_exchanger_resistance=0, building=None, valve_resistance=0):
super().__init__(name, edges)
self._pos = pos
self._heat_exchanger_resistance = heat_exchanger_resistance
self._building = building
self._valve_resistance = valve_resistance
@property
def pos(self) -> tuple:
"""
Get the position of the node
:return: tuple
"""
return self._pos
@pos.setter
def pos(self, value: Union[None, tuple]):
"""
Set the position of the node
:param value: tuple
"""
if value and isinstance(value, tuple) and len(value) == 2:
self._pos = value
else:
self._pos = (0, 0) # Default position
@property
def heat_exchanger_resistance(self) -> int:
"""
Get heat exchanger resistance
:return: int
"""
return self._heat_exchanger_resistance
@heat_exchanger_resistance.setter
def heat_exchanger_resistance(self, value: Union[None, int]):
"""
Set heat exchanger resistance
:param value: int
"""
self._heat_exchanger_resistance = max(value or 0, 0)
@property
def building(self):
"""
Get the associated building
:return: object
"""
return self._building
@building.setter
def building(self, value):
"""
Set the associated building
:param value: object
"""
self._building = value
@property
def valve_resistance(self) -> int:
"""
Get valve resistance
:return: int
"""
return self._valve_resistance
@valve_resistance.setter
def valve_resistance(self, value: Union[None, int]):
"""
Set valve resistance
:param value: int
"""
self._valve_resistance = max(value or 0, 0)

View File

@ -0,0 +1,48 @@
from hub.city_model_structure.network import Network
from typing import Union
from hub.city_model_structure.attributes.dhn_node import DHNNode
from hub.city_model_structure.attributes.dhn_edge import DHNEdge
class DHNNetwork(Network):
"""
District Heating Network class, inherits from Network.
"""
def __init__(self, name, edges=None, nodes=None):
super().__init__(name, edges, nodes)
def add_node(self, node: DHNNode):
"""
Add a DHN node to the network
:param node: DHNNode
"""
self._nodes.append(node)
def add_edge(self, edge: DHNEdge):
"""
Add a DHN edge to the network
:param edge: DHNEdge
"""
self._edges.append(edge)
def find_node_by_name(self, name: str) -> Union[None, DHNNode]:
"""
Find a node in the network by name
:param name: str
:return: DHNNode or None
"""
for node in self._nodes:
if node.name == name:
return node
return None
def find_edge_by_name(self, name: str) -> Union[None, DHNEdge]:
"""
Find an edge in the network by name
:param name: str
:return: DHNEdge or None
"""
for edge in self._edges:
if edge.name == name:
return edge
return None

View File

@ -10,6 +10,7 @@ import copy
import os import os
import shutil import shutil
import subprocess import subprocess
from datetime import datetime
import hub.exports.building_energy.idf_helper as idf_cte import hub.exports.building_energy.idf_helper as idf_cte
import hub.helpers.constants as cte import hub.helpers.constants as cte
@ -47,6 +48,7 @@ class CercIdf(IdfBase):
_thermostat_added_to_idf = {} _thermostat_added_to_idf = {}
def __init__(self, city, output_path, idf_file_path, idd_file_path, epw_file_path, target_buildings=None): def __init__(self, city, output_path, idf_file_path, idd_file_path, epw_file_path, target_buildings=None):
self._start = datetime.now()
super().__init__(city, output_path, idf_file_path, idd_file_path, epw_file_path, target_buildings) super().__init__(city, output_path, idf_file_path, idd_file_path, epw_file_path, target_buildings)
self._add_surfaces = IdfSurfaces.add self._add_surfaces = IdfSurfaces.add
self._add_file_schedule = IdfFileSchedule.add self._add_file_schedule = IdfFileSchedule.add
@ -231,6 +233,7 @@ class CercIdf(IdfBase):
# Merge files # Merge files
self._merge_files() self._merge_files()
self._add_outputs() self._add_outputs()
print(f'{len(self._city.buildings)} buildings export completed in: {datetime.now() - self._start}')
@property @property
def _energy_plus(self): def _energy_plus(self):
@ -245,4 +248,5 @@ class CercIdf(IdfBase):
'--readvars', '--readvars',
'--output-prefix', f'{self._city.name}_', '--output-prefix', f'{self._city.name}_',
self._output_file_path] self._output_file_path]
print(cmd)
subprocess.run(cmd, cwd=self._output_path) subprocess.run(cmd, cwd=self._output_path)

View File

@ -8,12 +8,10 @@ Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concord
""" """
import copy import copy
import datetime import datetime
import shutil import glob
import subprocess import os
from pathlib import Path from pathlib import Path
from geomeppy import IDF from geomeppy import IDF
import hub.helpers.constants as cte import hub.helpers.constants as cte
from hub.city_model_structure.attributes.schedule import Schedule from hub.city_model_structure.attributes.schedule import Schedule
from hub.city_model_structure.building_demand.thermal_zone import ThermalZone from hub.city_model_structure.building_demand.thermal_zone import ThermalZone
@ -531,7 +529,6 @@ class Idf:
self._remove_sizing_periods() self._remove_sizing_periods()
self._rename_building(self._city.name) self._rename_building(self._city.name)
self._lod = self._city.level_of_detail.geometry self._lod = self._city.level_of_detail.geometry
is_target = False
for building in self._city.buildings: for building in self._city.buildings:
is_target = building.name in self._target_buildings or building.name in self._adjacent_buildings is_target = building.name in self._target_buildings or building.name in self._adjacent_buildings
for internal_zone in building.internal_zones: for internal_zone in building.internal_zones:
@ -659,20 +656,12 @@ class Idf:
self._add_surfaces(building, building.name) self._add_surfaces(building, building.name)
return self._idf return self._idf
@property
def _energy_plus(self):
return shutil.which('energyplus')
def run(self): def run(self):
cmd = [self._energy_plus, """
'--weather', self._epw_file_path, Start the energy plus simulation
'--output-directory', self._output_path, """
'--idd', self._idd_file_path, self._idf.run(expandobjects=False, readvars=True, output_directory=self._output_path,
'--expandobjects', output_prefix=f'{self._city.name}_')
'--readvars',
'--output-prefix', f'{self._city.name}_',
self._idf_file_path]
subprocess.run(cmd, cwd=self._output_path)
def _add_block(self, building): def _add_block(self, building):
_points = self._matrix_to_2d_list(building.foot_print.coordinates) _points = self._matrix_to_2d_list(building.foot_print.coordinates)

View File

@ -20,7 +20,7 @@ class EnergyPlus:
header_parts = header.split(':') header_parts = header.split(':')
building_name = header_parts[0] building_name = header_parts[0]
variable = ':'.join(header_parts[1:]).strip() # concat the rest and ensure that : it's reintroduced just in case variable = ':'.join(header_parts[1:]).strip() # concat the rest and ensure that : it's reintroduced just in case
if variable == '': if variable is '':
continue continue
if building_name not in self._summary_variables: if building_name not in self._summary_variables:
self._building_energy_demands[variable] = [] # initialize the list of variables self._building_energy_demands[variable] = [] # initialize the list of variables

View File

@ -1,4 +1,4 @@
""" """
Hub version number Hub version number
""" """
__version__ = '0.3.0.5' __version__ = '0.3.0.3'

View File

@ -1,5 +1,5 @@
xmltodict xmltodict
numpy numpy==1.26.4
trimesh[all] trimesh[all]
pyproj pyproj
pandas pandas

View File

@ -59,7 +59,6 @@ setup(
'hub.exports', 'hub.exports',
'hub.exports.building_energy', 'hub.exports.building_energy',
'hub.exports.building_energy.idf_files', 'hub.exports.building_energy.idf_files',
'hub.exports.building_energy.idf_helper',
'hub.exports.building_energy.insel', 'hub.exports.building_energy.insel',
'hub.exports.energy_systems', 'hub.exports.energy_systems',
'hub.exports.formats', 'hub.exports.formats',

View File

@ -144,8 +144,7 @@ class TestExports(TestCase):
UsageFactory('nrcan', city).enrich() UsageFactory('nrcan', city).enrich()
WeatherFactory('epw', city).enrich() WeatherFactory('epw', city).enrich()
try: try:
_idf = EnergyBuildingsExportsFactory('idf', city, self._output_path).export() idf = EnergyBuildingsExportsFactory('idf', city, self._output_path).export()
_idf.run()
except Exception: except Exception:
self.fail("Idf ExportsFactory raised ExceptionType unexpectedly!") self.fail("Idf ExportsFactory raised ExceptionType unexpectedly!")