(WIP) feature: add pipe sizing to dhn analysis

This commit is contained in:
Majid Rezaei 2024-08-01 11:39:05 -04:00
parent 2ca52c157a
commit ad4a1f2b24
12 changed files with 1047 additions and 573 deletions

View File

View File

@ -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)

103
network_edges.csv Normal file
View File

@ -0,0 +1,103 @@
Start Node,End Node,Flow Rate,Diameter
1,2,7.036628334723999,100.31636393633916
1,3,4.21120645112289,77.60554307494739
1,55,3.5317773545013225,71.06997042924019
2,4,7.480662304076292,103.43309043327245
2,69,0.5550424616902951,28.174264035849028
3,29,3.996190684370673,75.5983953887572
3,88,0.2687697084402423,19.605577350674327
4,5,12.739252570195731,134.97752174009295
4,70,6.57323783264924,96.95699548330485
5,6,1.440813373451662,45.3934656880935
5,7,14.180065943647435,142.40610275642487
6,25,0.6885299581772856,31.379852409201234
6,66,0.9403542690929205,36.67205322096126
7,8,18.919213781899757,164.4905748238601
7,94,5.923934797815284,92.04381499868917
8,9,20.2831963688024,170.31687247962364
8,62,1.704978233628294,49.379750978468536
9,10,23.795992013358696,184.47664139533458
9,74,4.390994555695343,79.24482766451129
10,11,24.08473660300742,185.59250196750875
10,85,0.36093073706075773,22.71963814788559
11,12,29.818111567083733,206.5045112717333
11,97,7.166718705095327,101.23942160182479
12,13,30.27814518525654,208.09138919592667
12,87,0.5750420227159534,28.677366552586644
13,14,42.1891802453921,245.63487040066076
13,60,14.888793825169447,145.92148273668778
14,15,42.46951106929258,246.44959372826426
14,84,0.3504135298756097,22.386175758632653
15,16,42.71334684180593,247.15606782002874
15,100,0.30479471564177585,20.878206682814064
16,17,43.164835926660224,248.45888000248186
16,82,0.5643613560678524,28.40979566604211
17,18,43.40956096248419,249.1622090841966
17,67,0.3059062947800095,20.91624319844782
18,19,44.36915698128853,251.90110032375995
18,71,1.1994950235054551,41.417959700534844
19,20,44.628206053508535,252.63539166978182
19,90,0.3238113402749617,21.5196648389043
20,21,46.27014639132988,257.24083624469847
20,75,2.052425422276719,54.178024923945806
21,22,73.0360691675874,323.1901912619741
21,23,26.455032668422902,194.5107583389826
21,24,0.31089010783469323,21.08593812173577
22,49,84.50840788728556,347.6477603374658
22,81,14.340423399622829,143.2090497302142
23,31,25.527872344225216,191.07188392806023
23,68,1.158950405247153,40.71194974740254
24,56,0.3886126347933523,23.574795504777274
25,26,0.48524655942646255,26.34333218820642
25,95,0.2541042484385019,19.063183969533693
26,27,0.2982895780206631,20.654206550796754
26,65,0.23369622675732482,18.28164730458197
27,28,0.17282514505654928,15.721462386442168
27,59,0.15683054120527573,14.976309152909318
28,57,0.2160314313208167,17.577129300900673
29,30,3.8183263880744693,73.89686263587296
29,58,0.22233037037019066,17.831540803086607
30,33,3.699868945604976,72.74156600819312
30,77,0.1480718030867907,14.552099595104522
31,32,20.88880856250903,172.8408171354717
31,61,5.79882972714514,91.06671153446011
32,46,19.8454823967694,168.46911947690657
32,83,1.304157707174466,43.18714870672264
33,34,3.4711065659703353,70.45688678723134
33,102,0.2859529745433158,20.222590387225335
34,35,3.4711065659703735,70.45688678723174
35,36,2.259511486129423,56.84558663220748
35,86,1.5144938498012546,46.539662871966804
36,37,1.7967791166489435,50.691695985759985
36,76,0.5784154618507291,28.76136031308409
37,38,1.3079368277973644,43.24967618306041
37,98,0.6110528610646417,29.56166334911917
38,39,1.161455220287694,40.75592094041735
38,93,0.18310200938718202,16.182142867916955
39,40,1.0509571713728947,38.76876664450215
39,101,0.1381225611435788,14.054706710110416
40,41,0.9248009014795987,36.367512616181635
40,73,0.15769533736659458,15.017543626027152
41,42,0.7870341012651642,33.54951537030909
41,79,0.1722085002680287,15.693390021976926
42,43,0.6096172477277307,29.526916703688457
42,72,0.22177106692165793,17.809097799856982
43,44,0.5017777614037179,26.788301656702675
43,63,0.13479935790496445,13.884600479307904
44,45,0.257385665027338,19.185876901497235
44,80,0.3054901204704009,20.902010464636216
45,64,0.32173208128418995,21.45046247984601
46,47,18.899025568880468,164.4027895397298
46,99,1.1830710348610967,41.133426323360354
47,48,17.204865763811963,156.86105088056533
47,78,2.1176997563356554,55.032807545697466
48,50,15.889475871949896,150.74546712455864
48,89,1.644237364827739,48.492182678681466
49,53,4.137682980635671,76.92510229741136
49,54,88.64609086792123,356.0567884918405
50,52,15.88947587194994,150.74546712455884
51,52,15.107521674531634,146.98942430449688
51,96,18.884402093164404,164.33917235920677
52,91,0.977442746772752,37.38825018026857
53,92,5.172103725794528,86.004878956568
54,103,88.64609086792132,356.0567884918407
1 Start Node End Node Flow Rate Diameter
2 1 2 7.036628334723999 100.31636393633916
3 1 3 4.21120645112289 77.60554307494739
4 1 55 3.5317773545013225 71.06997042924019
5 2 4 7.480662304076292 103.43309043327245
6 2 69 0.5550424616902951 28.174264035849028
7 3 29 3.996190684370673 75.5983953887572
8 3 88 0.2687697084402423 19.605577350674327
9 4 5 12.739252570195731 134.97752174009295
10 4 70 6.57323783264924 96.95699548330485
11 5 6 1.440813373451662 45.3934656880935
12 5 7 14.180065943647435 142.40610275642487
13 6 25 0.6885299581772856 31.379852409201234
14 6 66 0.9403542690929205 36.67205322096126
15 7 8 18.919213781899757 164.4905748238601
16 7 94 5.923934797815284 92.04381499868917
17 8 9 20.2831963688024 170.31687247962364
18 8 62 1.704978233628294 49.379750978468536
19 9 10 23.795992013358696 184.47664139533458
20 9 74 4.390994555695343 79.24482766451129
21 10 11 24.08473660300742 185.59250196750875
22 10 85 0.36093073706075773 22.71963814788559
23 11 12 29.818111567083733 206.5045112717333
24 11 97 7.166718705095327 101.23942160182479
25 12 13 30.27814518525654 208.09138919592667
26 12 87 0.5750420227159534 28.677366552586644
27 13 14 42.1891802453921 245.63487040066076
28 13 60 14.888793825169447 145.92148273668778
29 14 15 42.46951106929258 246.44959372826426
30 14 84 0.3504135298756097 22.386175758632653
31 15 16 42.71334684180593 247.15606782002874
32 15 100 0.30479471564177585 20.878206682814064
33 16 17 43.164835926660224 248.45888000248186
34 16 82 0.5643613560678524 28.40979566604211
35 17 18 43.40956096248419 249.1622090841966
36 17 67 0.3059062947800095 20.91624319844782
37 18 19 44.36915698128853 251.90110032375995
38 18 71 1.1994950235054551 41.417959700534844
39 19 20 44.628206053508535 252.63539166978182
40 19 90 0.3238113402749617 21.5196648389043
41 20 21 46.27014639132988 257.24083624469847
42 20 75 2.052425422276719 54.178024923945806
43 21 22 73.0360691675874 323.1901912619741
44 21 23 26.455032668422902 194.5107583389826
45 21 24 0.31089010783469323 21.08593812173577
46 22 49 84.50840788728556 347.6477603374658
47 22 81 14.340423399622829 143.2090497302142
48 23 31 25.527872344225216 191.07188392806023
49 23 68 1.158950405247153 40.71194974740254
50 24 56 0.3886126347933523 23.574795504777274
51 25 26 0.48524655942646255 26.34333218820642
52 25 95 0.2541042484385019 19.063183969533693
53 26 27 0.2982895780206631 20.654206550796754
54 26 65 0.23369622675732482 18.28164730458197
55 27 28 0.17282514505654928 15.721462386442168
56 27 59 0.15683054120527573 14.976309152909318
57 28 57 0.2160314313208167 17.577129300900673
58 29 30 3.8183263880744693 73.89686263587296
59 29 58 0.22233037037019066 17.831540803086607
60 30 33 3.699868945604976 72.74156600819312
61 30 77 0.1480718030867907 14.552099595104522
62 31 32 20.88880856250903 172.8408171354717
63 31 61 5.79882972714514 91.06671153446011
64 32 46 19.8454823967694 168.46911947690657
65 32 83 1.304157707174466 43.18714870672264
66 33 34 3.4711065659703353 70.45688678723134
67 33 102 0.2859529745433158 20.222590387225335
68 34 35 3.4711065659703735 70.45688678723174
69 35 36 2.259511486129423 56.84558663220748
70 35 86 1.5144938498012546 46.539662871966804
71 36 37 1.7967791166489435 50.691695985759985
72 36 76 0.5784154618507291 28.76136031308409
73 37 38 1.3079368277973644 43.24967618306041
74 37 98 0.6110528610646417 29.56166334911917
75 38 39 1.161455220287694 40.75592094041735
76 38 93 0.18310200938718202 16.182142867916955
77 39 40 1.0509571713728947 38.76876664450215
78 39 101 0.1381225611435788 14.054706710110416
79 40 41 0.9248009014795987 36.367512616181635
80 40 73 0.15769533736659458 15.017543626027152
81 41 42 0.7870341012651642 33.54951537030909
82 41 79 0.1722085002680287 15.693390021976926
83 42 43 0.6096172477277307 29.526916703688457
84 42 72 0.22177106692165793 17.809097799856982
85 43 44 0.5017777614037179 26.788301656702675
86 43 63 0.13479935790496445 13.884600479307904
87 44 45 0.257385665027338 19.185876901497235
88 44 80 0.3054901204704009 20.902010464636216
89 45 64 0.32173208128418995 21.45046247984601
90 46 47 18.899025568880468 164.4027895397298
91 46 99 1.1830710348610967 41.133426323360354
92 47 48 17.204865763811963 156.86105088056533
93 47 78 2.1176997563356554 55.032807545697466
94 48 50 15.889475871949896 150.74546712455864
95 48 89 1.644237364827739 48.492182678681466
96 49 53 4.137682980635671 76.92510229741136
97 49 54 88.64609086792123 356.0567884918405
98 50 52 15.88947587194994 150.74546712455884
99 51 52 15.107521674531634 146.98942430449688
100 51 96 18.884402093164404 164.33917235920677
101 52 91 0.977442746772752 37.38825018026857
102 53 92 5.172103725794528 86.004878956568
103 54 103 88.64609086792132 356.0567884918407

3
pipe_cost_analysis.csv Normal file
View File

@ -0,0 +1,3 @@
Nominal Diameter (DN),Total Length (m),Cost per Meter ($),Total Cost ($)
1000,1903.0006309788755,860,1636580.542641833
900,20.922808875980245,860,17993.61563334301
1 Nominal Diameter (DN) Total Length (m) Cost per Meter ($) Total Cost ($)
2 1000 1903.0006309788755 860 1636580.542641833
3 900 20.922808875980245 860 17993.61563334301

View File

@ -0,0 +1,86 @@
import json
import csv
import logging
class NetworkGraphExporter:
"""
A class to export a network graph to various formats like CSV and GeoJSON.
"""
def __init__(self, graph):
"""
Initialize the exporter with a network graph.
:param graph: A NetworkX graph object.
"""
self.graph = graph
def to_csv(self, file_path):
"""
Save the graph nodes with their type, names, and positions to a CSV file.
:param file_path: The path to the output CSV file.
"""
try:
with open(file_path, mode='w', newline='') as file:
writer = csv.writer(file)
writer.writerow(['Node ID', 'Name', 'Type', 'Position'])
for node, data in self.graph.nodes(data=True):
writer.writerow([node, data['name'], data['type'], data['pos']])
logging.info(f"Graph successfully saved to CSV file: {file_path}")
except Exception as e:
logging.error(f"Error saving graph to CSV file: {e}")
def to_geojson(self, file_path):
"""
Save the graph to a GeoJSON file.
:param file_path: The path to the output GeoJSON file.
"""
try:
features = []
for node, data in self.graph.nodes(data=True):
feature = {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": data['pos']
},
"properties": {
"id": node,
"name": data['name'],
"type": data['type']
}
}
features.append(feature)
for u, v, data in self.graph.edges(data=True):
feature = {
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [self.graph.nodes[u]['pos'], self.graph.nodes[v]['pos']]
},
"properties": {
"length": data['length']
}
}
features.append(feature)
geojson = {
"type": "FeatureCollection",
"features": features
}
with open(file_path, 'w') as f:
json.dump(geojson, f, indent=2)
logging.info(f"Graph successfully saved to GeoJSON file: {file_path}")
except Exception as e:
logging.error(f"Error saving graph to GeoJSON file: {e}")
# Configure logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logging.getLogger("numexpr").setLevel(logging.ERROR)

View File

@ -1,32 +1,251 @@
import networkx as nx
import CoolProp.CoolProp as CP
import math
import logging
import numpy as np
import csv
class DistrictHeatingFactory:
"""
DistrictHeatingFactory class
This class is responsible for managing the district heating network, including
enriching the network graph with building data, calculating flow rates,
sizing pipes, and analyzing costs.
"""
def __init__(self, city, graph):
def __init__(self, city, graph, supply_temperature, return_temperature, simultaneity_factor):
"""
Initialize the DistrictHeatingFactory object.
:param city: The city object containing buildings and their heating demands.
:param graph: The network graph representing the district heating network.
:param supply_temperature: The supply temperature of the heating fluid in the network (°C).
:param return_temperature: The return temperature of the heating fluid in the network (°C).
:param simultaneity_factor: The simultaneity factor used to adjust flow rates for non-building pipes.
"""
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" # The fluid used in the heating network
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
This method associates each building node in the network graph with its corresponding
building object from the city, allowing access to heating demand data during calculations.
"""
for node_id, node_attrs in self._network_graph.nodes(data=True):
if node_attrs.get('type') == 'building':
building_name = node_attrs.get('name')
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 calculate_flow_rates(self, A, Gext):
"""
Solve the linear system to find the flow rates in each branch.
:param A: The incidence matrix representing the network connections.
:param Gext: The external flow rates for each node in the network.
:return: The calculated flow rates for each edge, or None if an error occurs.
"""
try:
G = np.linalg.lstsq(A, Gext, rcond=None)[0]
return G
except np.linalg.LinAlgError as e:
logging.error(f"Error solving the linear system: {e}")
return None
def switch_nodes(self, A, edge_index, node_index, edge):
"""
Switch the in and out nodes for the given edge in the incidence matrix A.
:param A: The incidence matrix representing the network connections.
:param edge_index: The index of edges in the incidence matrix.
:param node_index: The index of nodes in the incidence matrix.
:param edge: The edge (u, v) to switch.
"""
u, v = edge
i = node_index[u]
j = node_index[v]
k = edge_index[edge]
A[i, k], A[j, k] = -A[i, k], -A[j, k]
def sizing(self):
"""
Calculate the hourly mass flow rates, assign them to the edges, and determine the pipe diameters.
This method generates the flow rates for each hour, adjusting the incidence matrix as needed to
ensure all flow rates are positive. It also applies the simultaneity factor to non-building pipes.
"""
num_nodes = self._network_graph.number_of_nodes()
num_edges = self._network_graph.number_of_edges()
A = np.zeros((num_nodes, num_edges)) # Initialize incidence matrix
node_index = {node: i for i, node in enumerate(self._network_graph.nodes())}
edge_index = {edge: i for i, edge in enumerate(self._network_graph.edges())}
# Initialize mass flow rate attribute for each edge
for u, v, data in self._network_graph.edges(data=True):
self._network_graph.edges[u, v]['mass_flow_rate'] = {"hour": [], "peak": None}
# Get the length of the hourly demand for the first building (assuming all buildings have the same length)
building = next(iter(self._city.buildings))
num_hours = len(building.heating_demand['hour'])
# Loop through each hour to generate Gext and solve AG = Gext
for hour in range(8760):
Gext = np.zeros(num_nodes)
# Calculate the hourly mass flow rates for each edge and fill Gext
for edge in self._network_graph.edges(data=True):
u, v, data = edge
for node in [u, v]:
if self._network_graph.nodes[node].get('type') == 'building':
building = self._network_graph.nodes[node].get('building_obj')
if building and "hour" in building.heating_demand:
hourly_demand = building.heating_demand["hour"][hour] # Get demand for current hour
specific_heat_capacity = CP.PropsSI('C', 'T', (self._supply_temperature + self._return_temperature) / 2,
'P', 101325, self.fluid)
mass_flow_rate = hourly_demand / 3600 / (
specific_heat_capacity * (self._supply_temperature - self._return_temperature))
Gext[node_index[node]] += mass_flow_rate
# Update incidence matrix A
i = node_index[u]
j = node_index[v]
k = edge_index[(u, v)]
A[i, k] = 1
A[j, k] = -1
# Solve for G (flow rates)
G = self.calculate_flow_rates(A, Gext)
if G is None:
return
# Check for negative flow rates and adjust A accordingly
iterations = 0
max_iterations = num_edges * 2
while any(flow_rate < 0 for flow_rate in G) and iterations < max_iterations:
for idx, flow_rate in enumerate(G):
if flow_rate < 0:
G[idx] = -G[idx] # Invert the sign directly
iterations += 1
# Store the final flow rates in the edges for this hour
for idx, (edge, flow_rate) in enumerate(zip(self._network_graph.edges(), G)):
u, v = edge
if not (self._network_graph.nodes[u].get('type') == 'building' or self._network_graph.nodes[v].get(
'type') == 'building'):
flow_rate *= self.simultaneity_factor # Apply simultaneity factor for non-building pipes
data = self._network_graph.edges[u, v]
data['mass_flow_rate']["hour"].append(flow_rate) # Append the calculated flow rate
# Calculate the peak flow rate for each edge
for u, v, data in self._network_graph.edges(data=True):
data['mass_flow_rate']['peak'] = max(data['mass_flow_rate']['hour'])
def calculate_diameters_and_costs(self, pipe_data):
"""
Calculate the diameter and costs of the pipes based on the maximum flow rate in each edge.
:param pipe_data: A list of dictionaries containing pipe specifications, including inner diameters
and costs per meter for different nominal diameters (DN).
"""
for u, v, data in self._network_graph.edges(data=True):
flow_rate = data.get('mass_flow_rate', {}).get('peak')
if flow_rate is not None:
try:
# Calculate the density of the fluid
density = CP.PropsSI('D', 'T', (self._supply_temperature + self._return_temperature) / 2, 'P', 101325,
self.fluid)
velocity = 0.9 # Desired fluid velocity in m/s
# Calculate the diameter of the pipe required for the given flow rate
diameter = math.sqrt((4 * abs(flow_rate)) / (density * velocity * math.pi)) * 1000 # Convert to mm
self._network_graph.edges[u, v]['diameter'] = diameter
# Match to the closest nominal diameter from the pipe data
closest_pipe = self.match_nominal_diameter(diameter, pipe_data)
self._network_graph.edges[u, v]['nominal_diameter'] = closest_pipe['DN']
self._network_graph.edges[u, v]['cost_per_meter'] = closest_pipe['cost_per_meter']
except Exception as e:
logging.error(f"Error calculating diameter or matching nominal diameter for edge ({u}, {v}): {e}")
def match_nominal_diameter(self, diameter, pipe_data):
"""
Match the calculated diameter to the closest nominal diameter.
:param diameter: The calculated diameter of the pipe (in mm).
:param pipe_data: A list of dictionaries containing pipe specifications, including inner diameters
and costs per meter for different nominal diameters (DN).
:return: The dictionary representing the pipe with the closest nominal diameter.
"""
closest_pipe = min(pipe_data, key=lambda x: abs(x['inner_diameter'] - diameter))
return closest_pipe
def analyze_costs(self):
"""
Analyze the costs based on the nominal diameters of the pipes.
This method calculates the total cost of piping for each nominal diameter group
and returns a summary of the grouped pipes and the total cost.
:return: A tuple containing the grouped pipe data and the total cost of piping.
"""
pipe_groups = {}
total_cost = 0 # Initialize total cost
for u, v, data in self._network_graph.edges(data=True):
dn = data.get('nominal_diameter')
if dn is not None:
pipe_length = self._network_graph.edges[u, v].get('length', 1) * 2 # Multiply by 2 for supply and return
cost_per_meter = data.get('cost_per_meter', 0)
if dn not in pipe_groups:
pipe_groups[dn] = {
'DN': dn,
'total_length': 0,
'cost_per_meter': cost_per_meter
}
pipe_groups[dn]['total_length'] += pipe_length
group_cost = pipe_length * cost_per_meter
total_cost += group_cost # Add to total cost
# Calculate total cost for each group
for group in pipe_groups.values():
group['total_cost'] = group['total_length'] * group['cost_per_meter']
return pipe_groups, total_cost # Return both the grouped data and total cost
def save_pipe_groups_to_csv(self, filename):
"""
Save the pipe groups and their total lengths to a CSV file.
:param filename: The name of the CSV file to save the data to.
"""
pipe_groups, _ = self.analyze_costs()
with open(filename, mode='w', newline='') as file:
writer = csv.writer(file)
# Write the header
writer.writerow(["Nominal Diameter (DN)", "Total Length (m)", "Cost per Meter", "Total Cost"])
# Write the data for each pipe group
for group in pipe_groups.values():
writer.writerow([
group['DN'],
group['total_length'],
group['cost_per_meter'],
group['total_cost']
])
logging.info(f"Pipe groups and their lengths have been saved to {filename}")

View File

@ -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,48 @@ 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()

View File

@ -1,54 +0,0 @@
import json
from shapely import LineString, Point
import networkx as nx
from pathlib import Path
def networkx_to_geojson(graph: nx.Graph) -> Path:
"""
Convert a NetworkX graph to GeoJSON format.
:param graph: A NetworkX graph.
:return: GeoJSON formatted dictionary.
"""
features = []
for u, v, data in graph.edges(data=True):
line = LineString([u, v])
feature = {
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": list(line.coords)
},
"properties": {
"weight": data.get("weight", 1.0)
}
}
features.append(feature)
for node, data in graph.nodes(data=True):
point = Point(node)
feature = {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": list(point.coords)[0]
},
"properties": {
"type": data.get("type", "unknown"),
"id": data.get("id", "N/A")
}
}
features.append(feature)
geojson = {
"type": "FeatureCollection",
"features": features
}
output_geojson_file = Path('./out_files/network_graph.geojson').resolve()
with open(output_geojson_file, 'w') as file:
json.dump(geojson, file, indent=4)
return output_geojson_file

View File

@ -0,0 +1,191 @@
[
{
"DN": 16,
"inner_diameter": 16.1,
"outer_diameter": 21.3,
"thickness": 2.6,
"cost_per_meter": 320
},
{
"DN": 20,
"inner_diameter": 21.7,
"outer_diameter": 26.9,
"thickness": 2.6,
"cost_per_meter": 320
},
{
"DN": 25,
"inner_diameter": 27.3,
"outer_diameter": 33.7,
"thickness": 3.2,
"cost_per_meter": 320
},
{
"DN": 32,
"inner_diameter": 37.2,
"outer_diameter": 42.4,
"thickness": 2.6,
"cost_per_meter": 350
},
{
"DN": 40,
"inner_diameter": 43.1,
"outer_diameter": 48.3,
"thickness": 2.6,
"cost_per_meter": 375
},
{
"DN": 50,
"inner_diameter": 54.5,
"outer_diameter": 60.3,
"thickness": 2.9,
"cost_per_meter": 400
},
{
"DN": 65,
"inner_diameter": 70.3,
"outer_diameter": 76.1,
"thickness": 2.9,
"cost_per_meter": 450
},
{
"DN": 80,
"inner_diameter": 82.5,
"outer_diameter": 88.9,
"thickness": 3.2,
"cost_per_meter": 480
},
{
"DN": 90,
"inner_diameter": 100.8,
"outer_diameter": 108,
"thickness": 3.6,
"cost_per_meter": 480
},
{
"DN": 100,
"inner_diameter": 107.1,
"outer_diameter": 114.3,
"thickness": 3.6,
"cost_per_meter": 550
},
{
"DN": 110,
"inner_diameter": 125.8,
"outer_diameter": 133,
"thickness": 3.6,
"cost_per_meter": 550
},
{
"DN": 125,
"inner_diameter": 132.5,
"outer_diameter": 139.7,
"thickness": 3.6,
"cost_per_meter": 630
},
{
"DN": 140,
"inner_diameter": 151,
"outer_diameter": 159,
"thickness": 4,
"cost_per_meter": 700
},
{
"DN": 150,
"inner_diameter": 160.3,
"outer_diameter": 168.3,
"thickness": 4,
"cost_per_meter": 700
},
{
"DN": 180,
"inner_diameter": 184.7,
"outer_diameter": 193.7,
"thickness": 4.5,
"cost_per_meter": 700
},
{
"DN": 200,
"inner_diameter": 210.1,
"outer_diameter": 219.1,
"thickness": 4.5,
"cost_per_meter": 860
},
{
"DN": 250,
"inner_diameter": 263,
"outer_diameter": 273,
"thickness": 5,
"cost_per_meter": 860
},
{
"DN": 300,
"inner_diameter": 312.7,
"outer_diameter": 323.9,
"thickness": 5.6,
"cost_per_meter": 860
},
{
"DN": 350,
"inner_diameter": 344.4,
"outer_diameter": 355.6,
"thickness": 5.6,
"cost_per_meter": 860
},
{
"DN": 400,
"inner_diameter": 393.8,
"outer_diameter": 406.4,
"thickness": 6.3,
"cost_per_meter": 860
},
{
"DN": 450,
"inner_diameter": 444.4,
"outer_diameter": 457,
"thickness": 6.3,
"cost_per_meter": 860
},
{
"DN": 500,
"inner_diameter": 495.4,
"outer_diameter": 508,
"thickness": 6.3,
"cost_per_meter": 860
},
{
"DN": 600,
"inner_diameter": 595.8,
"outer_diameter": 610,
"thickness": 7.1,
"cost_per_meter": 860
},
{
"DN": 700,
"inner_diameter": 696.8,
"outer_diameter": 711,
"thickness": 7.1,
"cost_per_meter": 860
},
{
"DN": 800,
"inner_diameter": 797,
"outer_diameter": 813,
"thickness": 8,
"cost_per_meter": 860
},
{
"DN": 900,
"inner_diameter": 894,
"outer_diameter": 914,
"thickness": 10,
"cost_per_meter": 860
},
{
"DN": 1000,
"inner_diameter": 996,
"outer_diameter": 1016,
"thickness": 10,
"cost_per_meter": 860
}
]

View File

@ -1,54 +0,0 @@
from pathlib import Path
import subprocess
from scripts.ep_run_enrich import energy_plus_workflow
from hub.imports.geometry_factory import GeometryFactory
from hub.helpers.dictionaries import Dictionaries
from hub.imports.construction_factory import ConstructionFactory
from hub.imports.usage_factory import UsageFactory
from hub.imports.weather_factory import WeatherFactory
from hub.imports.results_factory import ResultFactory
from scripts.energy_system_retrofit_report import EnergySystemRetrofitReport
from scripts.geojson_creator import process_geojson
from scripts import random_assignation
from hub.imports.energy_systems_factory import EnergySystemsFactory
from scripts.energy_system_sizing import SystemSizing
from scripts.solar_angles import CitySolarAngles
from scripts.pv_sizing_and_simulation import PVSizingSimulation
from scripts.energy_system_retrofit_results import consumption_data, cost_data
from scripts.energy_system_sizing_and_simulation_factory import EnergySystemsSimulationFactory
from scripts.costs.cost import Cost
from scripts.costs.constants import SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, SYSTEM_RETROFIT_AND_PV, CURRENT_STATUS
import hub.helpers.constants as cte
from hub.exports.exports_factory import ExportsFactory
from scripts.pv_feasibility import pv_feasibility
# Specify the GeoJSON file path
input_files_path = (Path(__file__).parent / 'input_files')
input_files_path.mkdir(parents=True, exist_ok=True)
geojson_file = process_geojson(x=-73.5681295982132, y=45.49218262677643, diff=0.0001)
geojson_file_path = input_files_path / 'output_buildings.geojson'
output_path = (Path(__file__).parent / 'out_files').resolve()
output_path.mkdir(parents=True, exist_ok=True)
energy_plus_output_path = output_path / 'energy_plus_outputs'
energy_plus_output_path.mkdir(parents=True, exist_ok=True)
simulation_results_path = (Path(__file__).parent / 'out_files' / 'simulation_results').resolve()
simulation_results_path.mkdir(parents=True, exist_ok=True)
sra_output_path = output_path / 'sra_outputs'
sra_output_path.mkdir(parents=True, exist_ok=True)
cost_analysis_output_path = output_path / 'cost_analysis'
cost_analysis_output_path.mkdir(parents=True, exist_ok=True)
city = GeometryFactory(file_type='geojson',
path=geojson_file_path,
height_field='height',
year_of_construction_field='year_of_construction',
function_field='function',
function_to_hub=Dictionaries().montreal_function_to_hub_function).city
ConstructionFactory('nrcan', city).enrich()
UsageFactory('nrcan', city).enrich()
WeatherFactory('epw', city).enrich()
energy_plus_workflow(city, energy_plus_output_path)
random_assignation.call_random(city.buildings, random_assignation.residential_new_systems_percentage)
EnergySystemsFactory('montreal_future', city).enrich()
for building in city.buildings:
EnergySystemsSimulationFactory('archetype1', building=building, output_path=simulation_results_path).enrich()

View File

@ -6,8 +6,8 @@
"metadata": {
"collapsed": true,
"ExecuteTime": {
"end_time": "2024-07-31T21:38:47.230085Z",
"start_time": "2024-07-31T21:38:47.206748Z"
"end_time": "2024-08-15T14:40:08.404936Z",
"start_time": "2024-08-15T14:40:05.693805Z"
}
},
"source": [
@ -39,13 +39,13 @@
"import numpy as np"
],
"outputs": [],
"execution_count": 110
"execution_count": 1
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2024-07-31T21:38:47.861524Z",
"start_time": "2024-07-31T21:38:47.843529Z"
"end_time": "2024-08-15T14:40:10.900495Z",
"start_time": "2024-08-15T14:40:10.887502Z"
}
},
"cell_type": "code",
@ -68,18 +68,18 @@
],
"id": "7d895f0e4ec2b851",
"outputs": [],
"execution_count": 111
"execution_count": 2
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2024-07-31T21:39:02.661096Z",
"start_time": "2024-07-31T21:38:48.727802Z"
"end_time": "2024-08-15T14:40:28.062552Z",
"start_time": "2024-08-15T14:40:14.862729Z"
}
},
"cell_type": "code",
"source": [
"location = [45.53067276979674, -73.70234652694087]\n",
"location = [45.4934614681437, -73.57982834742518]\n",
"process_geojson(x=location[1], y=location[0], diff=0.001)"
],
"id": "20dfb8fa42189fc2",
@ -90,18 +90,18 @@
"WindowsPath('C:/Users/ab_reza/Majid/Concordia/Repositories/energy_system_modelling_workflow/input_files/output_buildings.geojson')"
]
},
"execution_count": 112,
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"execution_count": 112
"execution_count": 3
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2024-07-31T21:39:02.992446Z",
"start_time": "2024-07-31T21:39:02.663422Z"
"end_time": "2024-08-15T14:40:50.178460Z",
"start_time": "2024-08-15T14:40:49.803207Z"
}
},
"cell_type": "code",
@ -115,26 +115,26 @@
],
"id": "c03ae7cae09d4b21",
"outputs": [],
"execution_count": 113
"execution_count": 4
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2024-07-31T21:39:03.340164Z",
"start_time": "2024-07-31T21:39:02.993466Z"
"end_time": "2024-08-15T14:40:51.418370Z",
"start_time": "2024-08-15T14:40:50.955297Z"
}
},
"cell_type": "code",
"source": "ConstructionFactory('nrcan', city).enrich()",
"id": "c7d73638802e40d9",
"outputs": [],
"execution_count": 114
"execution_count": 5
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2024-07-31T21:39:04.079698Z",
"start_time": "2024-07-31T21:39:03.342163Z"
"end_time": "2024-08-15T14:40:53.472502Z",
"start_time": "2024-08-15T14:40:52.177895Z"
}
},
"cell_type": "code",
@ -150,26 +150,44 @@
]
}
],
"execution_count": 115
"execution_count": 6
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2024-07-31T21:39:04.648022Z",
"start_time": "2024-07-31T21:39:04.081700Z"
"end_time": "2024-08-15T14:40:54.887194Z",
"start_time": "2024-08-15T14:40:54.250538Z"
}
},
"cell_type": "code",
"source": "WeatherFactory('epw', city).enrich()",
"id": "f66c01cb42c33b64",
"outputs": [],
"execution_count": 116
"execution_count": 7
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2024-07-31T21:40:39.688386Z",
"start_time": "2024-07-31T21:39:04.650024Z"
"end_time": "2024-08-15T14:41:09.374580Z",
"start_time": "2024-08-15T14:40:58.248879Z"
}
},
"cell_type": "code",
"source": [
"ExportsFactory('sra', city, output_path).export()\n",
"sra_path = (output_path / f'{city.name}_sra.xml').resolve()\n",
"subprocess.run(['sra', str(sra_path)])\n",
"ResultFactory('sra', city, output_path).enrich()"
],
"id": "34adfa891341c9c7",
"outputs": [],
"execution_count": 8
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2024-08-15T14:42:48.500687Z",
"start_time": "2024-08-15T14:41:19.629346Z"
}
},
"cell_type": "code",
@ -180,467 +198,389 @@
"name": "stdout",
"output_type": "stream",
"text": [
"exporting:\n",
"exporting:\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"C:\\Users\\ab_reza\\miniconda3\\envs\\hub\\lib\\site-packages\\geomeppy\\geom\\surfaces.py:39: UserWarning: To create surfaces with >120 vertices, ensure you have customised your IDD before running EnergyPlus. https://unmethours.com/question/9343/energy-idf-parsing-error/?answer=9344#post-id-9344\n",
" warnings.warn(\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
" idf exported...\n",
"\r\n",
"C:/EnergyPlusV23-2-0\\energyplus.exe --weather C:\\Users\\ab_reza\\Majid\\Concordia\\Repositories\\energy_system_modelling_workflow\\hub\\data\\weather\\epw\\CAN_PQ_Montreal.Intl.AP.716270_CWEC.epw --output-directory C:\\Users\\ab_reza\\Majid\\Concordia\\Repositories\\energy_system_modelling_workflow\\out_files\\energy_plus_outputs --idd C:\\Users\\ab_reza\\Majid\\Concordia\\Repositories\\energy_system_modelling_workflow\\hub\\exports\\building_energy\\idf_files\\Energy+.idd --expandobjects --readvars --output-prefix Laval_ C:\\Users\\ab_reza\\Majid\\Concordia\\Repositories\\energy_system_modelling_workflow\\out_files\\energy_plus_outputs\\Laval_602570.idf\r\n",
"C:/EnergyPlusV23-2-0\\energyplus.exe --weather C:\\Users\\ab_reza\\Majid\\Concordia\\Repositories\\energy_system_modelling_workflow\\hub\\data\\weather\\epw\\CAN_PQ_Montreal.Intl.AP.716270_CWEC.epw --output-directory C:\\Users\\ab_reza\\Majid\\Concordia\\Repositories\\energy_system_modelling_workflow\\out_files\\energy_plus_outputs --idd C:\\Users\\ab_reza\\Majid\\Concordia\\Repositories\\energy_system_modelling_workflow\\hub\\exports\\building_energy\\idf_files\\Energy+.idd --expandobjects --readvars --output-prefix Montreal_ C:\\Users\\ab_reza\\Majid\\Concordia\\Repositories\\energy_system_modelling_workflow\\out_files\\energy_plus_outputs\\Montreal_f358e1.idf\r\n",
"\n"
]
}
],
"execution_count": 117
"execution_count": 9
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2024-07-31T21:53:14.440222Z",
"start_time": "2024-07-31T21:53:10.290860Z"
"end_time": "2024-08-15T14:42:56.710789Z",
"start_time": "2024-08-15T14:42:50.914244Z"
}
},
"cell_type": "code",
"source": [
"from scripts.district_heating_network.district_heating_network_creator import DistrictHeatingNetworkCreator\n",
"from scripts.district_heating_network.road_processor import road_processor\n",
"from pathlib import Path\n",
"import time\n",
"from scripts.district_heating_network.geojson_graph_creator import networkx_to_geojson\n",
"roads_file = road_processor(location[1], location[0], 0.001)\n",
"central_plant_locations = [(-73.57812571080625, 45.49499447346277)] # Add at least one location\n",
"\n",
"dhn_creator = DistrictHeatingNetworkCreator(geojson_file_path, roads_file)\n",
"roads_file = \"./input_files/roads.json\"\n",
"\n",
"dhn_creator = DistrictHeatingNetworkCreator(geojson_file_path, roads_file, central_plant_locations)\n",
"\n",
"network_graph = dhn_creator.run()"
],
"id": "8403846b0831b51d",
"outputs": [
{
"ename": "AttributeError",
"evalue": "'Graph' object has no attribute 'building_names'",
"output_type": "error",
"traceback": [
"\u001B[1;31m---------------------------------------------------------------------------\u001B[0m",
"\u001B[1;31mAttributeError\u001B[0m Traceback (most recent call last)",
"Cell \u001B[1;32mIn[121], line 11\u001B[0m\n\u001B[0;32m 8\u001B[0m dhn_creator \u001B[38;5;241m=\u001B[39m DistrictHeatingNetworkCreator(geojson_file_path, roads_file)\n\u001B[0;32m 10\u001B[0m network_graph \u001B[38;5;241m=\u001B[39m dhn_creator\u001B[38;5;241m.\u001B[39mrun()\n\u001B[1;32m---> 11\u001B[0m \u001B[43mnetwork_graph\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mbuilding_names\u001B[49m\n",
"\u001B[1;31mAttributeError\u001B[0m: 'Graph' object has no attribute 'building_names'"
]
}
],
"execution_count": 121
"id": "df85fafcb61d6749",
"outputs": [],
"execution_count": 10
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2024-07-31T21:43:05.521839Z",
"start_time": "2024-07-31T21:43:05.503748Z"
"end_time": "2024-08-15T15:16:25.568470Z",
"start_time": "2024-08-15T15:16:25.522471Z"
}
},
"cell_type": "code",
"source": [
"for node_id, attrs in network_graph.nodes(data=True):\n",
" print(f\"Node {node_id} has attributes: {list(attrs.keys())}\")"
],
"id": "9c4c32ed4a5b5434",
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Node (-73.70263014634182, 45.52966550204674) has attributes: []\n",
"Node (-73.70252245592799, 45.52959782722166) has attributes: []\n",
"Node (-73.70277983402246, 45.52975956880018) has attributes: []\n",
"Node (-73.70292834674622, 45.52985289718704) has attributes: []\n",
"Node (-73.70299601156968, 45.52989541912497) has attributes: []\n",
"Node (-73.70304798829301, 45.52992808234479) has attributes: []\n",
"Node (-73.70315317772048, 45.52999418549968) has attributes: []\n",
"Node (-73.70322951375971, 45.530042156604246) has attributes: []\n",
"Node (-73.70334527410391, 45.53011490273612) has attributes: []\n",
"Node (-73.70388612860485, 45.530454786598085) has attributes: []\n",
"Node (-73.70321670301797, 45.53098320823811) has attributes: []\n",
"Node (-73.70309371940914, 45.53090572804479) has attributes: []\n",
"Node (-73.70336752508702, 45.53107818505422) has attributes: []\n",
"Node (-73.70300302780161, 45.53115122842582) has attributes: []\n",
"Node (-73.70298632291501, 45.53083806779961) has attributes: []\n",
"Node (-73.70284664272657, 45.53075006869057) has attributes: []\n",
"Node (-73.70282694240179, 45.530737657402696) has attributes: []\n",
"Node (-73.70268296446567, 45.530646950694454) has attributes: []\n",
"Node (-73.70262035905371, 45.53060750902034) has attributes: []\n",
"Node (-73.70250974072788, 45.53053781900757) has attributes: []\n",
"Node (-73.70248122664219, 45.530519855013075) has attributes: []\n",
"Node (-73.70237692791034, 45.53045414637121) has attributes: []\n",
"Node (-73.70241425825014, 45.52952983362164) has attributes: []\n",
"Node (-73.70258909924681, 45.53147671471601) has attributes: []\n",
"Node (-73.70246956317335, 45.531401341489406) has attributes: []\n",
"Node (-73.70281850395438, 45.53162108764596) has attributes: []\n",
"Node (-73.70235595692806, 45.53165968576366) has attributes: []\n",
"Node (-73.70235908646175, 45.53133168062488) has attributes: []\n",
"Node (-73.70226538550632, 45.5312725976791) has attributes: []\n",
"Node (-73.7022262934011, 45.531247948232114) has attributes: []\n",
"Node (-73.70218216283965, 45.53122012179686) has attributes: []\n",
"Node (-73.7020876584622, 45.53116053225497) has attributes: []\n",
"Node (-73.70208089954498, 45.53115627043355) has attributes: []\n",
"Node (-73.70195718026818, 45.531078259496624) has attributes: []\n",
"Node (-73.7019336727694, 45.53106343689135) has attributes: []\n",
"Node (-73.70183972286668, 45.53100419697237) has attributes: []\n",
"Node (-73.70182154258106, 45.53099273343045) has attributes: []\n",
"Node (-73.70170504466955, 45.530919275910655) has attributes: []\n",
"Node (-73.70169068527439, 45.5309102216234) has attributes: []\n",
"Node (-73.70191018896638, 45.53200952628766) has attributes: []\n",
"Node (-73.70343390828414, 45.5311199883841) has attributes: []\n",
"Node (-73.70308928370066, 45.53179149942939) has attributes: []\n",
"Node (-73.70154615235963, 45.53081908668964) has attributes: []\n",
"Node (-73.70149535566978, 45.53078705694076) has attributes: []\n",
"Node (-73.70139243548935, 45.530722160831516) has attributes: []\n",
"Node (-73.70235555653572, 45.5304406823149) has attributes: []\n",
"Node (-73.70223631048641, 45.530365556799865) has attributes: []\n",
"Node (-73.70218808966641, 45.53033517747947) has attributes: []\n",
"Node (-73.7020516180255, 45.53024919976893) has attributes: []\n",
"Node (-73.70202483520858, 45.530232326481084) has attributes: []\n",
"Node (-73.70189576536478, 45.53015101193401) has attributes: []\n",
"Node (-73.70188535693748, 45.53014445458083) has attributes: []\n",
"Node (-73.70176137113975, 45.53006634300427) has attributes: []\n",
"Node (-73.70171679336974, 45.53003825882077) has attributes: []\n",
"Node (-73.70161674578377, 45.52997522841877) has attributes: []\n",
"Node (-73.70157021391765, 45.52994591314646) has attributes: []\n",
"Node (-73.70145508528618, 45.52987338162208) has attributes: []\n",
"Node (-73.7015262783945, 45.53176766055835) has attributes: []\n",
"Node (-73.70142255824699, 45.531702316306436) has attributes: []\n",
"Node (-73.70132694890151, 45.53164208190352) has attributes: []\n",
"Node (-73.70249378379357, 45.529882494691094) has attributes: ['type', 'id']\n",
"Node (-73.70236957992, 45.530697070843594) has attributes: ['type', 'id']\n",
"Node (-73.7023772579133, 45.52982887967387) has attributes: ['type', 'id']\n",
"Node (-73.70310348189996, 45.530242710105696) has attributes: ['type', 'id']\n",
"Node (-73.70219141578475, 45.5309810002753) has attributes: ['type', 'id']\n",
"Node (-73.7015878987858, 45.53110506016847) has attributes: ['type', 'id']\n",
"Node (-73.70197756808213, 45.531335127032875) has attributes: ['type', 'id']\n",
"Node (-73.70171824652937, 45.53119684899265) has attributes: ['type', 'id']\n",
"Node (-73.70181225980849, 45.53125598840158) has attributes: ['type', 'id']\n",
"Node (-73.70212216033907, 45.53141309516707) has attributes: ['type', 'id']\n",
"Node (-73.70224797036111, 45.531522088920134) has attributes: ['type', 'id']\n",
"Node (-73.70319066728962, 45.53075184355254) has attributes: ['type', 'id']\n",
"Node (-73.70309318391786, 45.53066844829803) has attributes: ['type', 'id']\n",
"Node (-73.70326346262547, 45.53124343502157) has attributes: ['type', 'id']\n",
"Node (-73.70289161913149, 45.53100954740511) has attributes: ['type', 'id']\n",
"Node (-73.7031243168426, 45.52969124795911) has attributes: ['type', 'id']\n",
"Node (-73.70332165936908, 45.531298238343524) has attributes: ['type', 'id']\n",
"Node (-73.70291683392738, 45.531464843960194) has attributes: ['type', 'id']\n",
"Node (-73.70257423757026, 45.53123533603945) has attributes: ['type', 'id']\n",
"Node (-73.70246354979903, 45.53116600989907) has attributes: ['type', 'id']\n",
"Node (-73.70137270924536, 45.53098156462814) has attributes: ['type', 'id']\n",
"Node (-73.70228611728258, 45.52973374332967) has attributes: ['type', 'id']\n",
"Node (-73.70192277090158, 45.530832193189546) has attributes: ['type', 'id']\n",
"Node (-73.70247403248253, 45.530300013163604) has attributes: ['type', 'id']\n",
"Node (-73.70233258364674, 45.53021274328478) has attributes: ['type', 'id']\n",
"Node (-73.70150159992788, 45.530157998392504) has attributes: ['type', 'id']\n",
"Node (-73.70178207574742, 45.53033147043354) has attributes: ['type', 'id']\n",
"Node (-73.70279118480165, 45.53007116190442) has attributes: ['type', 'id']\n",
"Node (-73.70290386342012, 45.53015742711493) has attributes: ['type', 'id']\n",
"Node (-73.70199360008198, 45.529972641218336) has attributes: ['type', 'id']\n",
"Node (-73.7032815855412, 45.52978985115031) has attributes: ['type', 'id']\n",
"Node (-73.70166271484868, 45.53063422765041) has attributes: ['type', 'id']\n",
"Node (-73.7015006171488, 45.530550593136034) has attributes: ['type', 'id']\n",
"Node (-73.70265213028476, 45.529962782747816) has attributes: ['type', 'id']\n",
"Node (-73.7029326957311, 45.53056979610127) has attributes: ['type', 'id']\n",
"Node (-73.70166661687237, 45.5297928936099) has attributes: ['type', 'id']\n",
"Node (-73.70193452736822, 45.53043505670828) has attributes: ['type', 'id']\n",
"Node (-73.70320906423977, 45.53033165241546) has attributes: ['type', 'id']\n",
"Node (-73.70242433058544, 45.531020523149344) has attributes: ['type', 'id']\n",
"Node (-73.70229173916934, 45.53104634226288) has attributes: ['type', 'id']\n",
"Node (-73.70164581777142, 45.53024975981883) has attributes: ['type', 'id']\n",
"Node (-73.70181323564402, 45.52988517687263) has attributes: ['type', 'id']\n",
"Node (-73.70207977647193, 45.53050710203167) has attributes: ['type', 'id']\n",
"Node (-73.70180201572698, 45.53073366018695) has attributes: ['type', 'id']\n",
"Node (-73.70260551746348, 45.53038579346295) has attributes: ['type', 'id']\n",
"Node (-73.7015368490746, 45.531520903846236) has attributes: ['type', 'id']\n",
"Node (-73.70277909755795, 45.530494359508104) has attributes: ['type', 'id']\n",
"Node (-73.7016306503588, 45.531601992190964) has attributes: ['type', 'id']\n",
"Node (-73.703188128229, 45.531634438129004) has attributes: ['type', 'id']\n",
"Node (-73.70225201894137, 45.5306050266003) has attributes: ['type', 'id']\n",
"Node (-73.70250211711432, 45.53079519337939) has attributes: ['type', 'id']\n",
"Node (-73.70143287673753, 45.53147394391961) has attributes: ['type', 'id']\n",
"Node (-73.7015564456529, 45.52971249323039) has attributes: ['type', 'id']\n",
"Node (-73.70213321668199, 45.530060293550356) has attributes: ['type', 'id']\n",
"Node (-73.70205098392802, 45.53092949418992) has attributes: ['type', 'id']\n",
"Node (-73.70273955351598, 45.53092005042424) has attributes: ['type', 'id']\n"
]
}
],
"execution_count": 119
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2024-07-31T21:29:21.717811Z",
"start_time": "2024-07-31T21:29:21.697811Z"
}
},
"cell_type": "code",
"source": [
"from scripts.district_heating_network.district_heating_factory import DistrictHeatingFactory\n",
"import CoolProp.CoolProp as CP\n",
"import math\n",
"import logging\n",
"import numpy as np\n",
"import csv\n",
"\n",
"DistrictHeatingFactory(city=city, graph=network_graph)"
"\n",
"class DistrictHeatingFactory:\n",
" \"\"\"\n",
" DistrictHeatingFactory class\n",
"\n",
" This class is responsible for managing the district heating network, including\n",
" enriching the network graph with building data, calculating flow rates,\n",
" sizing pipes, and analyzing costs.\n",
" \"\"\"\n",
"\n",
" def __init__(self, city, graph, supply_temperature, return_temperature, simultaneity_factor):\n",
" \"\"\"\n",
" Initialize the DistrictHeatingFactory object.\n",
"\n",
" :param city: The city object containing buildings and their heating demands.\n",
" :param graph: The network graph representing the district heating network.\n",
" :param supply_temperature: The supply temperature of the heating fluid in the network (°C).\n",
" :param return_temperature: The return temperature of the heating fluid in the network (°C).\n",
" :param simultaneity_factor: The simultaneity factor used to adjust flow rates for non-building pipes.\n",
" \"\"\"\n",
" self._city = city\n",
" self._network_graph = graph\n",
" self._supply_temperature = supply_temperature\n",
" self._return_temperature = return_temperature\n",
" self.simultaneity_factor = simultaneity_factor\n",
" self.fluid = \"Water\" # The fluid used in the heating network\n",
"\n",
" def enrich(self):\n",
" \"\"\"\n",
" Enrich the network graph nodes with the whole building object from the city buildings.\n",
"\n",
" This method associates each building node in the network graph with its corresponding\n",
" building object from the city, allowing access to heating demand data during calculations.\n",
" \"\"\"\n",
" for node_id, node_attrs in self._network_graph.nodes(data=True):\n",
" if node_attrs.get('type') == 'building':\n",
" building_name = node_attrs.get('name')\n",
" building_found = False\n",
" for building in self._city.buildings:\n",
" if building.name == building_name:\n",
" self._network_graph.nodes[node_id]['building_obj'] = building\n",
" building_found = True\n",
" break\n",
" if not building_found:\n",
" logging.error(msg=f\"Building with name '{building_name}' not found in city.\")\n",
"\n",
" def calculate_flow_rates(self, A, Gext):\n",
" \"\"\"\n",
" Solve the linear system to find the flow rates in each branch.\n",
"\n",
" :param A: The incidence matrix representing the network connections.\n",
" :param Gext: The external flow rates for each node in the network.\n",
" :return: The calculated flow rates for each edge, or None if an error occurs.\n",
" \"\"\"\n",
" try:\n",
" G = np.linalg.lstsq(A, Gext, rcond=None)[0]\n",
" return G\n",
" except np.linalg.LinAlgError as e:\n",
" logging.error(f\"Error solving the linear system: {e}\")\n",
" return None\n",
"\n",
" def switch_nodes(self, A, edge_index, node_index, edge):\n",
" \"\"\"\n",
" Switch the in and out nodes for the given edge in the incidence matrix A.\n",
"\n",
" :param A: The incidence matrix representing the network connections.\n",
" :param edge_index: The index of edges in the incidence matrix.\n",
" :param node_index: The index of nodes in the incidence matrix.\n",
" :param edge: The edge (u, v) to switch.\n",
" \"\"\"\n",
" u, v = edge\n",
" i = node_index[u]\n",
" j = node_index[v]\n",
" k = edge_index[edge]\n",
" A[i, k], A[j, k] = -A[i, k], -A[j, k]\n",
"\n",
" def sizing(self):\n",
" \"\"\"\n",
" Calculate the hourly mass flow rates, assign them to the edges, and determine the pipe diameters.\n",
"\n",
" This method generates the flow rates for each hour, adjusting the incidence matrix as needed to\n",
" ensure all flow rates are positive. It also applies the simultaneity factor to non-building pipes.\n",
" \"\"\"\n",
" num_nodes = self._network_graph.number_of_nodes()\n",
" num_edges = self._network_graph.number_of_edges()\n",
" A = np.zeros((num_nodes, num_edges)) # Initialize incidence matrix\n",
" node_index = {node: i for i, node in enumerate(self._network_graph.nodes())}\n",
" edge_index = {edge: i for i, edge in enumerate(self._network_graph.edges())}\n",
"\n",
" # Initialize mass flow rate attribute for each edge\n",
" for u, v, data in self._network_graph.edges(data=True):\n",
" self._network_graph.edges[u, v]['mass_flow_rate'] = {\"hour\": [], \"peak\": None}\n",
"\n",
" # Get the length of the hourly demand for the first building (assuming all buildings have the same length)\n",
" building = next(iter(self._city.buildings))\n",
" num_hours = len(building.heating_demand['hour'])\n",
"\n",
" # Loop through each hour to generate Gext and solve AG = Gext\n",
" for hour in range(8760):\n",
" Gext = np.zeros(num_nodes)\n",
"\n",
" # Calculate the hourly mass flow rates for each edge and fill Gext\n",
" for edge in self._network_graph.edges(data=True):\n",
" u, v, data = edge\n",
" for node in [u, v]:\n",
" if self._network_graph.nodes[node].get('type') == 'building':\n",
" building = self._network_graph.nodes[node].get('building_obj')\n",
" if building and \"hour\" in building.heating_demand:\n",
" hourly_demand = building.heating_demand[\"hour\"][hour] # Get demand for current hour\n",
" specific_heat_capacity = CP.PropsSI('C', 'T', (self._supply_temperature + self._return_temperature) / 2,\n",
" 'P', 101325, self.fluid)\n",
" mass_flow_rate = hourly_demand / 3600 / (\n",
" specific_heat_capacity * (self._supply_temperature - self._return_temperature))\n",
" Gext[node_index[node]] += mass_flow_rate\n",
"\n",
" # Update incidence matrix A\n",
" i = node_index[u]\n",
" j = node_index[v]\n",
" k = edge_index[(u, v)]\n",
" A[i, k] = 1\n",
" A[j, k] = -1\n",
"\n",
" # Solve for G (flow rates)\n",
" G = self.calculate_flow_rates(A, Gext)\n",
" if G is None:\n",
" return\n",
"\n",
" # Check for negative flow rates and adjust A accordingly\n",
" iterations = 0\n",
" max_iterations = num_edges * 2\n",
" while any(flow_rate < 0 for flow_rate in G) and iterations < max_iterations:\n",
" for idx, flow_rate in enumerate(G):\n",
" if flow_rate < 0:\n",
" G[idx] = -G[idx] # Invert the sign directly\n",
" iterations += 1\n",
"\n",
" # Store the final flow rates in the edges for this hour\n",
" for idx, (edge, flow_rate) in enumerate(zip(self._network_graph.edges(), G)):\n",
" u, v = edge\n",
" if not (self._network_graph.nodes[u].get('type') == 'building' or self._network_graph.nodes[v].get(\n",
" 'type') == 'building'):\n",
" flow_rate *= self.simultaneity_factor # Apply simultaneity factor for non-building pipes\n",
" data = self._network_graph.edges[u, v]\n",
" data['mass_flow_rate'][\"hour\"].append(flow_rate) # Append the calculated flow rate\n",
"\n",
" # Calculate the peak flow rate for each edge\n",
" for u, v, data in self._network_graph.edges(data=True):\n",
" data['mass_flow_rate']['peak'] = max(data['mass_flow_rate']['hour'])\n",
"\n",
" def calculate_diameters_and_costs(self, pipe_data):\n",
" \"\"\"\n",
" Calculate the diameter and costs of the pipes based on the maximum flow rate in each edge.\n",
"\n",
" :param pipe_data: A list of dictionaries containing pipe specifications, including inner diameters\n",
" and costs per meter for different nominal diameters (DN).\n",
" \"\"\"\n",
" for u, v, data in self._network_graph.edges(data=True):\n",
" flow_rate = data.get('mass_flow_rate', {}).get('peak')\n",
" if flow_rate is not None:\n",
" try:\n",
" # Calculate the density of the fluid\n",
" density = CP.PropsSI('D', 'T', (self._supply_temperature + self._return_temperature) / 2, 'P', 101325,\n",
" self.fluid)\n",
" velocity = 0.9 # Desired fluid velocity in m/s\n",
" # Calculate the diameter of the pipe required for the given flow rate\n",
" diameter = math.sqrt((4 * abs(flow_rate)) / (density * velocity * math.pi)) * 1000 # Convert to mm\n",
" self._network_graph.edges[u, v]['diameter'] = diameter\n",
"\n",
" # Match to the closest nominal diameter from the pipe data\n",
" closest_pipe = self.match_nominal_diameter(diameter, pipe_data)\n",
" self._network_graph.edges[u, v]['nominal_diameter'] = closest_pipe['DN']\n",
" self._network_graph.edges[u, v]['cost_per_meter'] = closest_pipe['cost_per_meter']\n",
" except Exception as e:\n",
" logging.error(f\"Error calculating diameter or matching nominal diameter for edge ({u}, {v}): {e}\")\n",
"\n",
" def match_nominal_diameter(self, diameter, pipe_data):\n",
" \"\"\"\n",
" Match the calculated diameter to the closest nominal diameter.\n",
"\n",
" :param diameter: The calculated diameter of the pipe (in mm).\n",
" :param pipe_data: A list of dictionaries containing pipe specifications, including inner diameters\n",
" and costs per meter for different nominal diameters (DN).\n",
" :return: The dictionary representing the pipe with the closest nominal diameter.\n",
" \"\"\"\n",
" closest_pipe = min(pipe_data, key=lambda x: abs(x['inner_diameter'] - diameter))\n",
" return closest_pipe\n",
"\n",
" def analyze_costs(self):\n",
" \"\"\"\n",
" Analyze the costs based on the nominal diameters of the pipes.\n",
"\n",
" This method calculates the total cost of piping for each nominal diameter group\n",
" and returns a summary of the grouped pipes and the total cost.\n",
"\n",
" :return: A tuple containing the grouped pipe data and the total cost of piping.\n",
" \"\"\"\n",
" pipe_groups = {}\n",
" total_cost = 0 # Initialize total cost\n",
"\n",
" for u, v, data in self._network_graph.edges(data=True):\n",
" dn = data.get('nominal_diameter')\n",
" if dn is not None:\n",
" pipe_length = self._network_graph.edges[u, v].get('length', 1) * 2 # Multiply by 2 for supply and return\n",
" cost_per_meter = data.get('cost_per_meter', 0)\n",
"\n",
" if dn not in pipe_groups:\n",
" pipe_groups[dn] = {\n",
" 'DN': dn,\n",
" 'total_length': 0,\n",
" 'cost_per_meter': cost_per_meter\n",
" }\n",
" pipe_groups[dn]['total_length'] += pipe_length\n",
" group_cost = pipe_length * cost_per_meter\n",
" total_cost += group_cost # Add to total cost\n",
"\n",
" # Calculate total cost for each group\n",
" for group in pipe_groups.values():\n",
" group['total_cost'] = group['total_length'] * group['cost_per_meter']\n",
"\n",
" return pipe_groups, total_cost # Return both the grouped data and total cost\n",
"\n",
" def save_pipe_groups_to_csv(self, filename):\n",
" \"\"\"\n",
" Save the pipe groups and their total lengths to a CSV file.\n",
"\n",
" :param filename: The name of the CSV file to save the data to.\n",
" \"\"\"\n",
" pipe_groups, _ = self.analyze_costs()\n",
"\n",
" with open(filename, mode='w', newline='') as file:\n",
" writer = csv.writer(file)\n",
" # Write the header\n",
" writer.writerow([\"Nominal Diameter (DN)\", \"Total Length (m)\", \"Cost per Meter\", \"Total Cost\"])\n",
"\n",
" # Write the data for each pipe group\n",
" for group in pipe_groups.values():\n",
" writer.writerow([\n",
" group['DN'],\n",
" group['total_length'],\n",
" group['cost_per_meter'],\n",
" group['total_cost']\n",
" ])\n",
"\n",
" logging.info(f\"Pipe groups and their lengths have been saved to {filename}\")"
],
"id": "25e14bd5433e3d95",
"outputs": [
{
"ename": "TypeError",
"evalue": "__init__() got an unexpected keyword argument 'graph'",
"output_type": "error",
"traceback": [
"\u001B[1;31m---------------------------------------------------------------------------\u001B[0m",
"\u001B[1;31mTypeError\u001B[0m Traceback (most recent call last)",
"Cell \u001B[1;32mIn[94], line 3\u001B[0m\n\u001B[0;32m 1\u001B[0m \u001B[38;5;28;01mfrom\u001B[39;00m \u001B[38;5;21;01mscripts\u001B[39;00m\u001B[38;5;21;01m.\u001B[39;00m\u001B[38;5;21;01mdistrict_heating_network\u001B[39;00m\u001B[38;5;21;01m.\u001B[39;00m\u001B[38;5;21;01mdistrict_heating_factory\u001B[39;00m \u001B[38;5;28;01mimport\u001B[39;00m DistrictHeatingFactory\n\u001B[1;32m----> 3\u001B[0m \u001B[43mDistrictHeatingFactory\u001B[49m\u001B[43m(\u001B[49m\u001B[43mcity\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mcity\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mgraph\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mnetwork_graph\u001B[49m\u001B[43m)\u001B[49m\n",
"\u001B[1;31mTypeError\u001B[0m: __init__() got an unexpected keyword argument 'graph'"
]
}
],
"execution_count": 94
"id": "9a6aafa0ea6fe3b3",
"outputs": [],
"execution_count": 15
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2024-07-31T21:18:46.818842Z",
"start_time": "2024-07-31T21:18:46.799573Z"
"end_time": "2024-08-15T15:16:27.537156Z",
"start_time": "2024-08-15T15:16:27.526152Z"
}
},
"cell_type": "code",
"source": [
"for node_id, attrs in network_graph.nodes(data=True):\n",
" print(f\"Node {node_id} has attributes: {list(attrs.keys())}\")"
"import json\n",
"# Example usage:\n",
"# Load the pipe data from an external JSON file\n",
"pipe_data_file = './scripts/district_heating_network/pipe_data.json'\n",
"with open(pipe_data_file, 'r') as f:\n",
" pipe_data = json.load(f)\n",
"\n",
"# Assuming `city` and `network_graph` are already defined and populated\n",
"factory = DistrictHeatingFactory(\n",
" city=city,\n",
" graph=network_graph,\n",
" supply_temperature=80 + 273, # in Kelvin\n",
" return_temperature=60 + 273, # in Kelvin\n",
" simultaneity_factor=0.9\n",
")\n",
"\n",
"# Enrich the network graph with building objects\n",
"factory.enrich()"
],
"id": "ad48fbc87a598b85",
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Node (-73.70263014634182, 45.52966550204674) has attributes: []\n",
"Node (-73.70252245592799, 45.52959782722166) has attributes: []\n",
"Node (-73.70277983402246, 45.52975956880018) has attributes: []\n",
"Node (-73.70292834674622, 45.52985289718704) has attributes: []\n",
"Node (-73.70299601156968, 45.52989541912497) has attributes: []\n",
"Node (-73.70304798829301, 45.52992808234479) has attributes: []\n",
"Node (-73.70315317772048, 45.52999418549968) has attributes: []\n",
"Node (-73.70322951375971, 45.530042156604246) has attributes: []\n",
"Node (-73.70334527410391, 45.53011490273612) has attributes: []\n",
"Node (-73.70388612860485, 45.530454786598085) has attributes: []\n",
"Node (-73.70321670301797, 45.53098320823811) has attributes: []\n",
"Node (-73.70309371940914, 45.53090572804479) has attributes: []\n",
"Node (-73.70336752508702, 45.53107818505422) has attributes: []\n",
"Node (-73.70300302780161, 45.53115122842582) has attributes: []\n",
"Node (-73.70298632291501, 45.53083806779961) has attributes: []\n",
"Node (-73.70284664272657, 45.53075006869057) has attributes: []\n",
"Node (-73.70282694240179, 45.530737657402696) has attributes: []\n",
"Node (-73.70268296446567, 45.530646950694454) has attributes: []\n",
"Node (-73.70262035905371, 45.53060750902034) has attributes: []\n",
"Node (-73.70250974072788, 45.53053781900757) has attributes: []\n",
"Node (-73.70248122664219, 45.530519855013075) has attributes: []\n",
"Node (-73.70237692791034, 45.53045414637121) has attributes: []\n",
"Node (-73.70241425825014, 45.52952983362164) has attributes: []\n",
"Node (-73.70258909924681, 45.53147671471601) has attributes: []\n",
"Node (-73.70246956317335, 45.531401341489406) has attributes: []\n",
"Node (-73.70281850395438, 45.53162108764596) has attributes: []\n",
"Node (-73.70235595692806, 45.53165968576366) has attributes: []\n",
"Node (-73.70235908646175, 45.53133168062488) has attributes: []\n",
"Node (-73.70226538550632, 45.5312725976791) has attributes: []\n",
"Node (-73.7022262934011, 45.531247948232114) has attributes: []\n",
"Node (-73.70218216283965, 45.53122012179686) has attributes: []\n",
"Node (-73.7020876584622, 45.53116053225497) has attributes: []\n",
"Node (-73.70208089954498, 45.53115627043355) has attributes: []\n",
"Node (-73.70195718026818, 45.531078259496624) has attributes: []\n",
"Node (-73.7019336727694, 45.53106343689135) has attributes: []\n",
"Node (-73.70183972286668, 45.53100419697237) has attributes: []\n",
"Node (-73.70182154258106, 45.53099273343045) has attributes: []\n",
"Node (-73.70170504466955, 45.530919275910655) has attributes: []\n",
"Node (-73.70169068527439, 45.5309102216234) has attributes: []\n",
"Node (-73.70191018896638, 45.53200952628766) has attributes: []\n",
"Node (-73.70343390828414, 45.5311199883841) has attributes: []\n",
"Node (-73.70308928370066, 45.53179149942939) has attributes: []\n",
"Node (-73.70154615235963, 45.53081908668964) has attributes: []\n",
"Node (-73.70149535566978, 45.53078705694076) has attributes: []\n",
"Node (-73.70139243548935, 45.530722160831516) has attributes: []\n",
"Node (-73.70235555653572, 45.5304406823149) has attributes: []\n",
"Node (-73.70223631048641, 45.530365556799865) has attributes: []\n",
"Node (-73.70218808966641, 45.53033517747947) has attributes: []\n",
"Node (-73.7020516180255, 45.53024919976893) has attributes: []\n",
"Node (-73.70202483520858, 45.530232326481084) has attributes: []\n",
"Node (-73.70189576536478, 45.53015101193401) has attributes: []\n",
"Node (-73.70188535693748, 45.53014445458083) has attributes: []\n",
"Node (-73.70176137113975, 45.53006634300427) has attributes: []\n",
"Node (-73.70171679336974, 45.53003825882077) has attributes: []\n",
"Node (-73.70161674578377, 45.52997522841877) has attributes: []\n",
"Node (-73.70157021391765, 45.52994591314646) has attributes: []\n",
"Node (-73.70145508528618, 45.52987338162208) has attributes: []\n",
"Node (-73.7015262783945, 45.53176766055835) has attributes: []\n",
"Node (-73.70142255824699, 45.531702316306436) has attributes: []\n",
"Node (-73.70132694890151, 45.53164208190352) has attributes: []\n",
"Node (-73.70249378379357, 45.529882494691094) has attributes: ['type', 'id']\n",
"Node (-73.70236957992, 45.530697070843594) has attributes: ['type', 'id']\n",
"Node (-73.7023772579133, 45.52982887967387) has attributes: ['type', 'id']\n",
"Node (-73.70310348189996, 45.530242710105696) has attributes: ['type', 'id']\n",
"Node (-73.70219141578475, 45.5309810002753) has attributes: ['type', 'id']\n",
"Node (-73.7015878987858, 45.53110506016847) has attributes: ['type', 'id']\n",
"Node (-73.70197756808213, 45.531335127032875) has attributes: ['type', 'id']\n",
"Node (-73.70171824652937, 45.53119684899265) has attributes: ['type', 'id']\n",
"Node (-73.70181225980849, 45.53125598840158) has attributes: ['type', 'id']\n",
"Node (-73.70212216033907, 45.53141309516707) has attributes: ['type', 'id']\n",
"Node (-73.70224797036111, 45.531522088920134) has attributes: ['type', 'id']\n",
"Node (-73.70319066728962, 45.53075184355254) has attributes: ['type', 'id']\n",
"Node (-73.70309318391786, 45.53066844829803) has attributes: ['type', 'id']\n",
"Node (-73.70326346262547, 45.53124343502157) has attributes: ['type', 'id']\n",
"Node (-73.70289161913149, 45.53100954740511) has attributes: ['type', 'id']\n",
"Node (-73.7031243168426, 45.52969124795911) has attributes: ['type', 'id']\n",
"Node (-73.70332165936908, 45.531298238343524) has attributes: ['type', 'id']\n",
"Node (-73.70291683392738, 45.531464843960194) has attributes: ['type', 'id']\n",
"Node (-73.70257423757026, 45.53123533603945) has attributes: ['type', 'id']\n",
"Node (-73.70246354979903, 45.53116600989907) has attributes: ['type', 'id']\n",
"Node (-73.70137270924536, 45.53098156462814) has attributes: ['type', 'id']\n",
"Node (-73.70228611728258, 45.52973374332967) has attributes: ['type', 'id']\n",
"Node (-73.70192277090158, 45.530832193189546) has attributes: ['type', 'id']\n",
"Node (-73.70247403248253, 45.530300013163604) has attributes: ['type', 'id']\n",
"Node (-73.70233258364674, 45.53021274328478) has attributes: ['type', 'id']\n",
"Node (-73.70150159992788, 45.530157998392504) has attributes: ['type', 'id']\n",
"Node (-73.70178207574742, 45.53033147043354) has attributes: ['type', 'id']\n",
"Node (-73.70279118480165, 45.53007116190442) has attributes: ['type', 'id']\n",
"Node (-73.70290386342012, 45.53015742711493) has attributes: ['type', 'id']\n",
"Node (-73.70199360008198, 45.529972641218336) has attributes: ['type', 'id']\n",
"Node (-73.7032815855412, 45.52978985115031) has attributes: ['type', 'id']\n",
"Node (-73.70166271484868, 45.53063422765041) has attributes: ['type', 'id']\n",
"Node (-73.7015006171488, 45.530550593136034) has attributes: ['type', 'id']\n",
"Node (-73.70265213028476, 45.529962782747816) has attributes: ['type', 'id']\n",
"Node (-73.7029326957311, 45.53056979610127) has attributes: ['type', 'id']\n",
"Node (-73.70166661687237, 45.5297928936099) has attributes: ['type', 'id']\n",
"Node (-73.70193452736822, 45.53043505670828) has attributes: ['type', 'id']\n",
"Node (-73.70320906423977, 45.53033165241546) has attributes: ['type', 'id']\n",
"Node (-73.70242433058544, 45.531020523149344) has attributes: ['type', 'id']\n",
"Node (-73.70229173916934, 45.53104634226288) has attributes: ['type', 'id']\n",
"Node (-73.70164581777142, 45.53024975981883) has attributes: ['type', 'id']\n",
"Node (-73.70181323564402, 45.52988517687263) has attributes: ['type', 'id']\n",
"Node (-73.70207977647193, 45.53050710203167) has attributes: ['type', 'id']\n",
"Node (-73.70180201572698, 45.53073366018695) has attributes: ['type', 'id']\n",
"Node (-73.70260551746348, 45.53038579346295) has attributes: ['type', 'id']\n",
"Node (-73.7015368490746, 45.531520903846236) has attributes: ['type', 'id']\n",
"Node (-73.70277909755795, 45.530494359508104) has attributes: ['type', 'id']\n",
"Node (-73.7016306503588, 45.531601992190964) has attributes: ['type', 'id']\n",
"Node (-73.703188128229, 45.531634438129004) has attributes: ['type', 'id']\n",
"Node (-73.70225201894137, 45.5306050266003) has attributes: ['type', 'id']\n",
"Node (-73.70250211711432, 45.53079519337939) has attributes: ['type', 'id']\n",
"Node (-73.70143287673753, 45.53147394391961) has attributes: ['type', 'id']\n",
"Node (-73.7015564456529, 45.52971249323039) has attributes: ['type', 'id']\n",
"Node (-73.70213321668199, 45.530060293550356) has attributes: ['type', 'id']\n",
"Node (-73.70205098392802, 45.53092949418992) has attributes: ['type', 'id']\n",
"Node (-73.70273955351598, 45.53092005042424) has attributes: ['type', 'id']\n"
]
}
],
"execution_count": 80
"id": "786700abaa5b6c74",
"outputs": [],
"execution_count": 16
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2024-07-31T20:23:57.446448Z",
"start_time": "2024-07-31T20:23:57.431469Z"
"end_time": "2024-08-15T15:19:29.830050Z",
"start_time": "2024-08-15T15:18:38.495444Z"
}
},
"cell_type": "code",
"source": [
"for building in city.buildings:\n",
" print(building.name)"
"factory.sizing()\n",
"\n",
"# Calculate diameters and costs based on flow rates\n",
"factory.calculate_diameters_and_costs(pipe_data)\n",
"\n",
"# Analyze and print the cost summary\n",
"pipe_groups, total_cost = factory.analyze_costs()\n",
"print(f\"Total Cost: {total_cost}\")\n",
"print(f\"Pipe Groups: {pipe_groups}\")\n",
"\n",
"# Save the pipe groups with total costs to a CSV file\n",
"factory.save_pipe_groups_to_csv('pipe_groups.csv')"
],
"id": "5b96a042e349e0eb",
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"65418\n",
"70816\n",
"73478\n",
"82649\n",
"84906\n",
"87241\n",
"87719\n",
"88675\n",
"88747\n",
"89061\n",
"89062\n",
"89251\n",
"91214\n",
"92337\n",
"92399\n",
"92520\n",
"92979\n",
"93149\n",
"95265\n",
"95266\n",
"95465\n",
"95704\n",
"96241\n",
"96579\n",
"96580\n",
"96930\n",
"96931\n",
"96996\n",
"96997\n",
"97648\n",
"98087\n",
"98666\n",
"98667\n",
"102035\n",
"103043\n",
"103740\n",
"103795\n",
"107302\n",
"108296\n",
"108297\n",
"109211\n",
"109305\n",
"109773\n",
"110561\n",
"110873\n",
"113368\n",
"116927\n",
"118062\n",
"118250\n",
"119143\n",
"120435\n",
"124177\n",
"125538\n",
"128322\n",
"129429\n",
"130498\n"
]
}
],
"execution_count": 75
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2024-07-31T19:35:10.949715Z",
"start_time": "2024-07-31T19:35:09.846007Z"
}
},
"cell_type": "code",
"source": "",
"id": "2bb88967eb45bcec",
"id": "41dd3be0d8929532",
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"IOPub data rate exceeded.\n",
"The Jupyter server will temporarily stop sending output\n",
"to the client in order to avoid crashing it.\n",
"To change this limit, set the config variable\n",
"`--ServerApp.iopub_data_rate_limit`.\n",
"\n",
"Current values:\n",
"ServerApp.iopub_data_rate_limit=1000000.0 (bytes/sec)\n",
"ServerApp.rate_limit_window=3.0 (secs)\n",
"\n"
"2024-08-15 11:19:29,827 - INFO - Pipe groups and their lengths have been saved to pipe_groups.csv\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Total Cost: 947047.7964692218\n",
"Pipe Groups: {180: {'DN': 180, 'total_length': 114.65086054771263, 'cost_per_meter': 700, 'total_cost': 80255.60238339884}, 200: {'DN': 200, 'total_length': 86.77407605049403, 'cost_per_meter': 860, 'total_cost': 74625.70540342487}, 65: {'DN': 65, 'total_length': 44.868598440532345, 'cost_per_meter': 450, 'total_cost': 20190.869298239555}, 25: {'DN': 25, 'total_length': 132.90459259943202, 'cost_per_meter': 320, 'total_cost': 42529.46963181825}, 32: {'DN': 32, 'total_length': 598.8970732341874, 'cost_per_meter': 350, 'total_cost': 209613.9756319656}, 100: {'DN': 100, 'total_length': 79.2938042713124, 'cost_per_meter': 550, 'total_cost': 43611.59234922182}, 90: {'DN': 90, 'total_length': 54.759698157730206, 'cost_per_meter': 480, 'total_cost': 26284.6551157105}, 40: {'DN': 40, 'total_length': 63.86094729702201, 'cost_per_meter': 375, 'total_cost': 23947.855236383253}, 80: {'DN': 80, 'total_length': 100.27318873963688, 'cost_per_meter': 480, 'total_cost': 48131.1305950257}, 125: {'DN': 125, 'total_length': 48.86387183404508, 'cost_per_meter': 630, 'total_cost': 30784.2392554484}, 150: {'DN': 150, 'total_length': 311.37141225770273, 'cost_per_meter': 700, 'total_cost': 217959.98858039192}, 140: {'DN': 140, 'total_length': 46.482545907577126, 'cost_per_meter': 700, 'total_cost': 32537.782135303987}, 50: {'DN': 50, 'total_length': 239.55061954479896, 'cost_per_meter': 400, 'total_cost': 95820.24781791959}, 110: {'DN': 110, 'total_length': 1.3721509726715793, 'cost_per_meter': 550, 'total_cost': 754.6830349693686}}\n"
]
}
],
"execution_count": 52
"execution_count": 18
},
{
"metadata": {},
@ -648,7 +588,7 @@
"outputs": [],
"execution_count": null,
"source": "",
"id": "f7c0742941b4f2d1"
"id": "aaa1f7f2ad504c88"
}
],
"metadata": {