(WIP) feature: add pipe sizing to dhn analysis
This commit is contained in:
parent
ad2d0a86d5
commit
629cb4fe7a
23
main.py
23
main.py
|
@ -25,6 +25,7 @@ from scripts.pv_feasibility import pv_feasibility
|
|||
import matplotlib.pyplot as plt
|
||||
from scripts.district_heating_network.district_heating_network_creator import DistrictHeatingNetworkCreator
|
||||
from scripts.district_heating_network.road_processor import road_processor
|
||||
from scripts.district_heating_network.district_heating_factory import DistrictHeatingFactory
|
||||
|
||||
base_path = Path(__file__).parent
|
||||
dir_manager = DirectoryManager(base_path)
|
||||
|
@ -61,7 +62,7 @@ UsageFactory('nrcan', city).enrich()
|
|||
WeatherFactory('epw', city).enrich()
|
||||
|
||||
# EnergyPlus workflow
|
||||
# energy_plus_workflow(city, energy_plus_output_path)
|
||||
energy_plus_workflow(city, energy_plus_output_path)
|
||||
|
||||
roads_file = road_processor(location[1], location[0], 0.001)
|
||||
|
||||
|
@ -69,5 +70,21 @@ dhn_creator = DistrictHeatingNetworkCreator(geojson_file_path, roads_file)
|
|||
|
||||
network_graph = dhn_creator.run()
|
||||
|
||||
for node_id, attrs in network_graph.nodes(data=True):
|
||||
print(f"Node {node_id} has attributes: {dict(attrs)}")
|
||||
DistrictHeatingFactory(
|
||||
city,
|
||||
network_graph,
|
||||
60,
|
||||
40,
|
||||
0.8
|
||||
).enrich()
|
||||
|
||||
DistrictHeatingFactory(
|
||||
city,
|
||||
network_graph,
|
||||
60,
|
||||
40,
|
||||
0.8
|
||||
).sizing()
|
||||
|
||||
for u, v, attributes in network_graph.edges(data=True):
|
||||
print(f"Edge between {u} and {v} with attributes: {attributes}")
|
||||
|
|
|
@ -1,20 +1,26 @@
|
|||
import networkx as nx
|
||||
import logging
|
||||
import CoolProp as CP
|
||||
import math
|
||||
|
||||
|
||||
class DistrictHeatingFactory:
|
||||
"""
|
||||
DistrictHeatingFactory class
|
||||
"""
|
||||
|
||||
def __init__(self, city, graph):
|
||||
def __init__(self, city, graph, supply_temperature, return_temperature, simultaneity_factor):
|
||||
self._city = city
|
||||
self._network_graph = graph
|
||||
self._supply_temperature = supply_temperature
|
||||
self._return_temperature = return_temperature
|
||||
self.simultaneity_factor = simultaneity_factor
|
||||
self.fluid = "Water"
|
||||
|
||||
def enrich(self):
|
||||
"""
|
||||
Enrich the network graph nodes with attributes from the city buildings.
|
||||
Enrich the network graph nodes with the whole building object from the city buildings.
|
||||
"""
|
||||
|
||||
for node in self._network_graph.nodes(data=True):
|
||||
node_id, node_attrs = node
|
||||
if node_attrs.get('type') == 'building':
|
||||
|
@ -22,11 +28,51 @@ class DistrictHeatingFactory:
|
|||
building_found = False
|
||||
for building in self._city.buildings:
|
||||
if building.name == building_name:
|
||||
building_attrs = vars(building)
|
||||
for attr, value in building_attrs.items():
|
||||
if attr not in self._network_graph.nodes[node_id]:
|
||||
self._network_graph.nodes[node_id][attr] = value
|
||||
self._network_graph.nodes[node_id]['building_obj'] = building
|
||||
building_found = True
|
||||
break
|
||||
if not building_found:
|
||||
logging.error(msg=f"Building with name '{building_name}' not found in city.")
|
||||
logging.error(msg=f"Building with name '{building_name}' not found in city.")
|
||||
|
||||
def sizing(self):
|
||||
"""
|
||||
Calculate the diameter of the pipes in the district heating network.
|
||||
"""
|
||||
for node in self._network_graph.nodes(data=True):
|
||||
node_id, node_attrs = node
|
||||
if node_attrs.get('type') == 'building':
|
||||
building = node_attrs.get('building_obj') # Adjusted key to match your data
|
||||
if building.heating_peak_load["year"][0]:
|
||||
# Calculate peak mass flow rate
|
||||
peak_mass_flow_rate = building.heating_peak_load["year"][0] / CP.PropsSI('C',
|
||||
'T',
|
||||
(
|
||||
self._supply_temperature +
|
||||
self._return_temperature
|
||||
) / 2,
|
||||
'P',
|
||||
101325,
|
||||
self.fluid) / (
|
||||
self._supply_temperature - self._return_temperature)
|
||||
|
||||
# Calculate density of the fluid
|
||||
density = CP.PropsSI('D', # 'D' for density
|
||||
'T',
|
||||
(
|
||||
self._supply_temperature +
|
||||
self._return_temperature
|
||||
) / 2,
|
||||
'P',
|
||||
101325,
|
||||
self.fluid)
|
||||
|
||||
# Set the design velocity (V)
|
||||
velocity = 0.9 # m/s
|
||||
|
||||
# Calculate the diameter (D)
|
||||
diameter = math.sqrt((4 * peak_mass_flow_rate) / (density * velocity * math.pi)) * 1000 # mm
|
||||
|
||||
# Find the edge connected to the building node
|
||||
for neighbor in self._network_graph.neighbors(node_id):
|
||||
if not self._network_graph.nodes[neighbor].get('type') == 'building': # Ensure it's a pipe connection
|
||||
self._network_graph.edges[node_id, neighbor]['diameter'] = diameter
|
||||
|
|
|
@ -8,9 +8,8 @@ from typing import List, Tuple
|
|||
from rtree import index
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
logging.getLogger('numexpr').setLevel(logging.ERROR)
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
|
||||
logging.getLogger("numexpr").setLevel(logging.ERROR)
|
||||
|
||||
def haversine(lon1, lat1, lon2, lat2):
|
||||
"""
|
||||
|
@ -30,17 +29,21 @@ def haversine(lon1, lat1, lon2, lat2):
|
|||
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
|
||||
return R * c # Output distance in meters
|
||||
|
||||
|
||||
class DistrictHeatingNetworkCreator:
|
||||
def __init__(self, buildings_file: str, roads_file: str):
|
||||
def __init__(self, buildings_file: str, roads_file: str, central_plant_locations: List[Tuple[float, float]]):
|
||||
"""
|
||||
Initialize the class with paths to the buildings and roads data files.
|
||||
Initialize the class with paths to the buildings and roads data files, and central plant locations.
|
||||
|
||||
:param buildings_file: Path to the GeoJSON file containing building data.
|
||||
:param roads_file: Path to the GeoJSON file containing roads data.
|
||||
:param central_plant_locations: List of tuples containing the coordinates of central plant locations.
|
||||
"""
|
||||
if len(central_plant_locations) < 1:
|
||||
raise ValueError("The list of central plant locations must have at least one member.")
|
||||
|
||||
self.buildings_file = buildings_file
|
||||
self.roads_file = roads_file
|
||||
self.central_plant_locations = central_plant_locations
|
||||
|
||||
def run(self) -> nx.Graph:
|
||||
"""
|
||||
|
@ -57,7 +60,8 @@ class DistrictHeatingNetworkCreator:
|
|||
self._iteratively_remove_edges()
|
||||
self._add_centroids_to_mst()
|
||||
self._convert_edge_weights_to_meters()
|
||||
return self.final_mst
|
||||
self._create_final_network_graph()
|
||||
return self.network_graph
|
||||
except Exception as e:
|
||||
logging.error(f"Error during network creation: {e}")
|
||||
raise
|
||||
|
@ -73,6 +77,7 @@ class DistrictHeatingNetworkCreator:
|
|||
|
||||
self.centroids = []
|
||||
self.building_names = []
|
||||
self.building_positions = []
|
||||
buildings = city['features']
|
||||
for building in buildings:
|
||||
coordinates = building['geometry']['coordinates'][0]
|
||||
|
@ -80,6 +85,14 @@ class DistrictHeatingNetworkCreator:
|
|||
centroid = building_polygon.centroid
|
||||
self.centroids.append(centroid)
|
||||
self.building_names.append(str(building['id']))
|
||||
self.building_positions.append((centroid.x, centroid.y))
|
||||
|
||||
# Add central plant locations as centroids
|
||||
for i, loc in enumerate(self.central_plant_locations, start=1):
|
||||
centroid = Point(loc)
|
||||
self.centroids.append(centroid)
|
||||
self.building_names.append(f'central_plant_{i}')
|
||||
self.building_positions.append((centroid.x, centroid.y))
|
||||
|
||||
# Load road data
|
||||
with open(self.roads_file, 'r') as file:
|
||||
|
@ -184,7 +197,9 @@ class DistrictHeatingNetworkCreator:
|
|||
for line in self.cleaned_lines:
|
||||
coords = list(line.coords)
|
||||
for i in range(len(coords) - 1):
|
||||
self.G.add_edge(coords[i], coords[i + 1], weight=Point(coords[i]).distance(Point(coords[i + 1])))
|
||||
u = coords[i]
|
||||
v = coords[i + 1]
|
||||
self.G.add_edge(u, v, weight=Point(coords[i]).distance(Point(coords[i + 1])))
|
||||
except Exception as e:
|
||||
logging.error(f"Error creating graph: {e}")
|
||||
raise
|
||||
|
@ -284,24 +299,22 @@ class DistrictHeatingNetworkCreator:
|
|||
"""
|
||||
try:
|
||||
for i, centroid in enumerate(self.centroids):
|
||||
centroid_tuple = (centroid.x, centroid.y)
|
||||
building_name = self.building_names[i]
|
||||
|
||||
# Add the centroid node with its attributes
|
||||
self.final_mst.add_node(centroid_tuple, type='building', name=building_name)
|
||||
pos = (centroid.x, centroid.y)
|
||||
node_type = 'building' if 'central_plant' not in building_name else 'generation'
|
||||
self.final_mst.add_node(pos, type=node_type, name=building_name, pos=pos)
|
||||
|
||||
nearest_point = None
|
||||
min_distance = float('inf')
|
||||
for node in self.final_mst.nodes():
|
||||
if self.final_mst.nodes[node].get('type') != 'building':
|
||||
node_point = Point(node)
|
||||
distance = centroid.distance(node_point)
|
||||
if self.final_mst.nodes[node].get('type') != 'building' and self.final_mst.nodes[node].get('type') != 'generation':
|
||||
distance = centroid.distance(Point(node))
|
||||
if distance < min_distance:
|
||||
min_distance = distance
|
||||
nearest_point = node
|
||||
|
||||
if nearest_point:
|
||||
self.final_mst.add_edge(centroid_tuple, nearest_point, weight=min_distance)
|
||||
self.final_mst.add_edge(pos, nearest_point, weight=min_distance)
|
||||
except Exception as e:
|
||||
logging.error(f"Error adding centroids to MST: {e}")
|
||||
raise
|
||||
|
@ -312,22 +325,49 @@ class DistrictHeatingNetworkCreator:
|
|||
"""
|
||||
try:
|
||||
for u, v, data in self.final_mst.edges(data=True):
|
||||
lon1, lat1 = u
|
||||
lon2, lat2 = v
|
||||
distance = haversine(lon1, lat1, lon2, lat2)
|
||||
distance = haversine(u[0], u[1], v[0], v[1])
|
||||
self.final_mst[u][v]['weight'] = distance
|
||||
except Exception as e:
|
||||
logging.error(f"Error converting edge weights to meters: {e}")
|
||||
raise
|
||||
|
||||
def _create_final_network_graph(self):
|
||||
"""
|
||||
Create the final network graph with the required attributes from the final MST.
|
||||
"""
|
||||
self.network_graph = nx.Graph()
|
||||
node_id = 1
|
||||
node_mapping = {}
|
||||
for node in self.final_mst.nodes:
|
||||
pos = node
|
||||
if 'type' in self.final_mst.nodes[node]:
|
||||
if self.final_mst.nodes[node]['type'] == 'building':
|
||||
name = self.final_mst.nodes[node]['name']
|
||||
node_type = 'building'
|
||||
elif self.final_mst.nodes[node]['type'] == 'generation':
|
||||
name = self.final_mst.nodes[node]['name']
|
||||
node_type = 'generation'
|
||||
else:
|
||||
name = f'junction_{node_id}'
|
||||
node_type = 'junction'
|
||||
self.network_graph.add_node(node_id, name=name, type=node_type, pos=pos)
|
||||
node_mapping[node] = node_id
|
||||
node_id += 1
|
||||
for u, v, data in self.final_mst.edges(data=True):
|
||||
u_new = node_mapping[u]
|
||||
v_new = node_mapping[v]
|
||||
length = data['weight']
|
||||
self.network_graph.add_edge(u_new, v_new, length=length)
|
||||
|
||||
def plot_network_graph(self):
|
||||
"""
|
||||
Plot the network graph using matplotlib and networkx.
|
||||
"""
|
||||
plt.figure(figsize=(15, 10))
|
||||
pos = {node: (node[0], node[1]) for node in self.final_mst.nodes()}
|
||||
nx.draw_networkx_nodes(self.final_mst, pos, node_color='blue', node_size=50)
|
||||
nx.draw_networkx_edges(self.final_mst, pos, edge_color='gray')
|
||||
pos = {node: data['pos'] for node, data in self.network_graph.nodes(data=True)}
|
||||
nx.draw_networkx_nodes(self.network_graph, pos, node_color='blue', node_size=50)
|
||||
nx.draw_networkx_edges(self.network_graph, pos, edge_color='gray')
|
||||
plt.title('District Heating Network Graph')
|
||||
plt.axis('off')
|
||||
plt.show()
|
||||
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user