(WIP) feature: add pipe sizing to dhn analysis
This commit is contained in:
parent
ad2d0a86d5
commit
8bddefabde
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,7 +29,6 @@ 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):
|
||||
"""
|
||||
|
@ -41,6 +39,8 @@ class DistrictHeatingNetworkCreator:
|
|||
"""
|
||||
self.buildings_file = buildings_file
|
||||
self.roads_file = roads_file
|
||||
self.node_counter = 0 # Counter to assign unique integer IDs to nodes
|
||||
self.node_mapping = {} # Mapping from (x, y) tuples to integer IDs
|
||||
|
||||
def run(self) -> nx.Graph:
|
||||
"""
|
||||
|
@ -57,6 +57,7 @@ class DistrictHeatingNetworkCreator:
|
|||
self._iteratively_remove_edges()
|
||||
self._add_centroids_to_mst()
|
||||
self._convert_edge_weights_to_meters()
|
||||
self._reassign_node_ids() # Reassign node IDs to be sequential
|
||||
return self.final_mst
|
||||
except Exception as e:
|
||||
logging.error(f"Error during network creation: {e}")
|
||||
|
@ -73,6 +74,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 +82,7 @@ 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))
|
||||
|
||||
# Load road data
|
||||
with open(self.roads_file, 'r') as file:
|
||||
|
@ -184,11 +187,24 @@ 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 = self._get_or_create_node_id(coords[i])
|
||||
v = self._get_or_create_node_id(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
|
||||
|
||||
def _get_or_create_node_id(self, pos):
|
||||
"""
|
||||
Get the node ID for a position or create a new one if it doesn't exist.
|
||||
"""
|
||||
if pos not in self.node_mapping:
|
||||
self.node_counter += 1
|
||||
self.node_mapping[pos] = self.node_counter
|
||||
# Initially, assume the node is a junction
|
||||
self.G.add_node(self.node_counter, pos=pos, type='junction', name=f'junction_{self.node_counter}')
|
||||
return self.node_mapping[pos]
|
||||
|
||||
def _create_mst(self):
|
||||
"""
|
||||
Create a Minimum Spanning Tree (MST) from the graph.
|
||||
|
@ -197,9 +213,9 @@ class DistrictHeatingNetworkCreator:
|
|||
def find_paths_between_nearest_points(g: nx.Graph, nearest_points: List[Point]) -> List[Tuple]:
|
||||
edges = []
|
||||
for i, start_point in enumerate(nearest_points):
|
||||
start = (start_point.x, start_point.y)
|
||||
start = self._get_or_create_node_id((start_point.x, start_point.y))
|
||||
for end_point in nearest_points[i + 1:]:
|
||||
end = (end_point.x, end_point.y)
|
||||
end = self._get_or_create_node_id((end_point.x, end_point.y))
|
||||
if nx.has_path(g, start, end):
|
||||
path = nx.shortest_path(g, source=start, target=end, weight='weight')
|
||||
path_edges = list(zip(path[:-1], path[1:]))
|
||||
|
@ -219,21 +235,41 @@ class DistrictHeatingNetworkCreator:
|
|||
final_edges.extend((x, y, self.G[x][y]['weight']) for x, y in path_edges)
|
||||
self.final_mst = nx.Graph()
|
||||
self.final_mst.add_weighted_edges_from(final_edges)
|
||||
|
||||
# Ensure all nodes in final_mst have the required attributes
|
||||
for node in self.final_mst.nodes:
|
||||
pos = self._get_pos_from_node(node)
|
||||
if pos:
|
||||
if 'type' not in self.final_mst.nodes[node]:
|
||||
self.final_mst.nodes[node]['type'] = 'junction'
|
||||
self.final_mst.nodes[node].update({
|
||||
'pos': pos,
|
||||
'name': f'junction_{node}' if self.final_mst.nodes[node]['type'] == 'junction' else self.final_mst.nodes[node]['name']
|
||||
})
|
||||
except Exception as e:
|
||||
logging.error(f"Error creating MST: {e}")
|
||||
raise
|
||||
|
||||
def _get_pos_from_node(self, node):
|
||||
"""
|
||||
Get the position (x, y) for a node.
|
||||
"""
|
||||
for pos, node_id in self.node_mapping.items():
|
||||
if node_id == node:
|
||||
return pos
|
||||
return None
|
||||
|
||||
def _iteratively_remove_edges(self):
|
||||
"""
|
||||
Iteratively remove edges that do not have any nearest points and have one end with only one connection.
|
||||
Also remove nodes that don't have any connections and street nodes with only one connection.
|
||||
"""
|
||||
nearest_points_tuples = [(point.x, point.y) for point in self.nearest_points]
|
||||
nearest_points_ids = [self._get_or_create_node_id((point.x, point.y)) for point in self.nearest_points]
|
||||
|
||||
def find_edges_to_remove(graph: nx.Graph) -> List[Tuple]:
|
||||
edges_to_remove = []
|
||||
for u, v, d in graph.edges(data=True):
|
||||
if u not in nearest_points_tuples and v not in nearest_points_tuples:
|
||||
if u not in nearest_points_ids and v not in nearest_points_ids:
|
||||
if graph.degree(u) == 1 or graph.degree(v) == 1:
|
||||
edges_to_remove.append((u, v, d))
|
||||
return edges_to_remove
|
||||
|
@ -259,7 +295,7 @@ class DistrictHeatingNetworkCreator:
|
|||
def find_single_connection_street_nodes(graph: nx.Graph) -> List[Tuple]:
|
||||
single_connection_street_nodes = []
|
||||
for node in graph.nodes():
|
||||
if node not in nearest_points_tuples and graph.degree(node) == 1:
|
||||
if node not in nearest_points_ids and graph.degree(node) == 1:
|
||||
single_connection_street_nodes.append(node)
|
||||
return single_connection_street_nodes
|
||||
|
||||
|
@ -284,24 +320,25 @@ class DistrictHeatingNetworkCreator:
|
|||
"""
|
||||
try:
|
||||
for i, centroid in enumerate(self.centroids):
|
||||
centroid_tuple = (centroid.x, centroid.y)
|
||||
building_name = self.building_names[i]
|
||||
pos = (centroid.x, centroid.y)
|
||||
node_id = self._get_or_create_node_id(pos)
|
||||
|
||||
# Add the centroid node with its attributes
|
||||
self.final_mst.add_node(centroid_tuple, type='building', name=building_name)
|
||||
# Update the node to be a building
|
||||
self.final_mst.add_node(node_id, pos=pos, type='building', name=building_name)
|
||||
|
||||
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)
|
||||
if self.final_mst.nodes[node].get('type') == 'junction':
|
||||
node_point = Point(self.final_mst.nodes[node]['pos'])
|
||||
distance = centroid.distance(node_point)
|
||||
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(node_id, nearest_point, weight=min_distance)
|
||||
except Exception as e:
|
||||
logging.error(f"Error adding centroids to MST: {e}")
|
||||
raise
|
||||
|
@ -312,20 +349,37 @@ 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)
|
||||
pos_u = self._get_pos_from_node(u)
|
||||
pos_v = self._get_pos_from_node(v)
|
||||
distance = haversine(pos_u[0], pos_u[1], pos_v[0], pos_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 _reassign_node_ids(self):
|
||||
"""
|
||||
Reassign node IDs to ensure they are sequential starting from 1.
|
||||
"""
|
||||
mapping = {old_id: new_id for new_id, old_id in enumerate(self.final_mst.nodes, start=1)}
|
||||
self.final_mst = nx.relabel_nodes(self.final_mst, mapping)
|
||||
for node_id in self.final_mst.nodes:
|
||||
if 'type' not in self.final_mst.nodes[node_id]:
|
||||
self.final_mst.nodes[node_id]['type'] = 'junction'
|
||||
pos = self._get_pos_from_node(node_id)
|
||||
node_type = self.final_mst.nodes[node_id]['type']
|
||||
name = self.final_mst.nodes[node_id].get('name', f'junction_{node_id}')
|
||||
self.final_mst.nodes[node_id].update({
|
||||
'pos': pos,
|
||||
'name': name if node_type == 'building' else f'junction_{node_id}'
|
||||
})
|
||||
|
||||
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()}
|
||||
pos = {node: self.final_mst.nodes[node]['pos'] 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')
|
||||
plt.title('District Heating Network Graph')
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user