NREL-erad 0.0.0a0__py3-none-any.whl → 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- erad/__init__.py +1 -0
- erad/constants.py +59 -11
- erad/default_fragility_curves/__init__.py +15 -0
- erad/default_fragility_curves/default_fire_boundary_dist.py +94 -0
- erad/default_fragility_curves/default_flood_depth.py +108 -0
- erad/default_fragility_curves/default_flood_velocity.py +101 -0
- erad/default_fragility_curves/default_fragility_curves.py +23 -0
- erad/default_fragility_curves/default_peak_ground_acceleration.py +163 -0
- erad/default_fragility_curves/default_peak_ground_velocity.py +94 -0
- erad/default_fragility_curves/default_wind_speed.py +94 -0
- erad/enums.py +40 -0
- erad/gdm_mapping.py +83 -0
- erad/models/__init__.py +1 -0
- erad/models/asset.py +287 -0
- erad/models/asset_mapping.py +20 -0
- erad/models/fragility_curve.py +116 -0
- erad/models/hazard/__init__.py +5 -0
- erad/models/hazard/base_models.py +12 -0
- erad/models/hazard/common.py +26 -0
- erad/models/hazard/earthquake.py +93 -0
- erad/models/hazard/flood.py +83 -0
- erad/models/hazard/wild_fire.py +121 -0
- erad/models/hazard/wind.py +143 -0
- erad/models/probability.py +73 -0
- erad/probability_builder.py +35 -0
- erad/quantities.py +25 -0
- erad/runner.py +122 -0
- erad/systems/__init__.py +2 -0
- erad/systems/asset_system.py +414 -0
- erad/systems/hazard_system.py +122 -0
- nrel_erad-0.1.0.dist-info/METADATA +55 -0
- nrel_erad-0.1.0.dist-info/RECORD +35 -0
- {NREL_erad-0.0.0a0.dist-info → nrel_erad-0.1.0.dist-info}/WHEEL +1 -1
- NREL_erad-0.0.0a0.dist-info/METADATA +0 -61
- NREL_erad-0.0.0a0.dist-info/RECORD +0 -42
- erad/cypher_queries/load_data_v1.cypher +0 -212
- erad/data/World_Earthquakes_1960_2016.csv +0 -23410
- erad/db/__init__.py +0 -0
- erad/db/assets/__init__.py +0 -0
- erad/db/assets/critical_infras.py +0 -171
- erad/db/assets/distribution_lines.py +0 -101
- erad/db/credential_model.py +0 -20
- erad/db/disaster_input_model.py +0 -23
- erad/db/inject_earthquake.py +0 -52
- erad/db/inject_flooding.py +0 -53
- erad/db/neo4j_.py +0 -162
- erad/db/utils.py +0 -14
- erad/exceptions.py +0 -68
- erad/metrics/__init__.py +0 -0
- erad/metrics/check_microgrid.py +0 -208
- erad/metrics/metric.py +0 -178
- erad/programs/__init__.py +0 -0
- erad/programs/backup.py +0 -62
- erad/programs/microgrid.py +0 -45
- erad/scenarios/__init__.py +0 -0
- erad/scenarios/abstract_scenario.py +0 -103
- erad/scenarios/common.py +0 -93
- erad/scenarios/earthquake_scenario.py +0 -161
- erad/scenarios/fire_scenario.py +0 -160
- erad/scenarios/flood_scenario.py +0 -494
- erad/scenarios/utilities.py +0 -76
- erad/scenarios/wind_scenario.py +0 -89
- erad/utils/__init__.py +0 -0
- erad/utils/ditto_utils.py +0 -252
- erad/utils/hifld_utils.py +0 -147
- erad/utils/opendss_utils.py +0 -357
- erad/utils/overpass.py +0 -76
- erad/utils/util.py +0 -178
- erad/visualization/__init__.py +0 -0
- erad/visualization/plot_graph.py +0 -218
- {NREL_erad-0.0.0a0.dist-info → nrel_erad-0.1.0.dist-info/licenses}/LICENSE.txt +0 -0
- {NREL_erad-0.0.0a0.dist-info → nrel_erad-0.1.0.dist-info}/top_level.txt +0 -0
erad/systems/__init__.py
ADDED
@@ -0,0 +1,414 @@
|
|
1
|
+
from collections import defaultdict, Counter
|
2
|
+
|
3
|
+
from gdm.distribution.enums import PlotingStyle, MapType
|
4
|
+
from gdm.distribution import DistributionSystem
|
5
|
+
from shapely.geometry import Point, LineString
|
6
|
+
import gdm.distribution.components as gdc
|
7
|
+
from gdm.quantities import Distance
|
8
|
+
import plotly.graph_objects as go
|
9
|
+
from infrasys import System
|
10
|
+
import geopandas as gpd
|
11
|
+
import networkx as nx
|
12
|
+
import pandas as pd
|
13
|
+
import numpy as np
|
14
|
+
|
15
|
+
|
16
|
+
from erad.constants import ASSET_TYPES, DEFAULT_TIME_STAMP
|
17
|
+
from erad.gdm_mapping import asset_to_gdm_mapping
|
18
|
+
from erad.models.asset import Asset, AssetState
|
19
|
+
from erad.enums import AssetTypes, NodeTypes
|
20
|
+
|
21
|
+
|
22
|
+
class AssetSystem(System):
|
23
|
+
def __init__(self, *args, **kwargs):
|
24
|
+
super().__init__(*args, **kwargs)
|
25
|
+
|
26
|
+
def add_component(self, component, **kwargs):
|
27
|
+
assert isinstance(
|
28
|
+
component, ASSET_TYPES
|
29
|
+
), f"Unsupported model type {component.__class__.__name__}"
|
30
|
+
return super().add_component(component, **kwargs)
|
31
|
+
|
32
|
+
def add_components(self, *components, **kwargs):
|
33
|
+
assert all(isinstance(component, ASSET_TYPES) for component in components), (
|
34
|
+
"Unsupported model types in passed component. Valid types are: \n"
|
35
|
+
+ "\n".join([s.__name__ for s in ASSET_TYPES])
|
36
|
+
)
|
37
|
+
return super().add_components(*components, **kwargs)
|
38
|
+
|
39
|
+
def get_dircted_graph(self):
|
40
|
+
"""Get the directed graph of the AssetSystem."""
|
41
|
+
|
42
|
+
graph = self.get_undirected_graph()
|
43
|
+
substations = list(
|
44
|
+
self.get_components(Asset, filter_func=lambda x: x.asset_type == AssetTypes.substation)
|
45
|
+
)
|
46
|
+
|
47
|
+
assert len(substations) <= 1, "There should be at most one substation in the asset system."
|
48
|
+
|
49
|
+
tree = nx.dfs_tree(graph, str(substations[0].connections[0]))
|
50
|
+
return tree
|
51
|
+
|
52
|
+
def get_undirected_graph(self):
|
53
|
+
"""Get the undirected graph of the AssetSystem."""
|
54
|
+
g = nx.Graph()
|
55
|
+
graph_data = []
|
56
|
+
for asset in self.get_components(Asset):
|
57
|
+
if len(asset.connections) == 2:
|
58
|
+
u, v = asset.connections
|
59
|
+
g.add_edge(str(u), str(v), **asset.model_dump())
|
60
|
+
else:
|
61
|
+
if asset.asset_type in NodeTypes:
|
62
|
+
g.add_node(str(asset.distribution_asset), **asset.model_dump())
|
63
|
+
else:
|
64
|
+
graph_data.append(asset.model_dump())
|
65
|
+
|
66
|
+
g.graph["metadata"] = graph_data
|
67
|
+
return g
|
68
|
+
|
69
|
+
@classmethod
|
70
|
+
def from_gdm(cls, dist_system: DistributionSystem) -> "AssetSystem":
|
71
|
+
"""Create a AssetSystem from a DistributionSystem."""
|
72
|
+
asset_map = AssetSystem.map_asets(dist_system)
|
73
|
+
# list_of_assets = AssetSystem._build_assets(asset_map)
|
74
|
+
system = AssetSystem(auto_add_composed_components=True)
|
75
|
+
list_of_assets = system._build_assets(asset_map)
|
76
|
+
system.add_components(*list_of_assets)
|
77
|
+
return system
|
78
|
+
|
79
|
+
def _add_node_data(
|
80
|
+
self, node_data: dict[str, list], asset: Asset, asset_state: AssetState | None = None
|
81
|
+
):
|
82
|
+
node_data["name"].append(asset.name)
|
83
|
+
node_data["type"].append(asset.asset_type.name)
|
84
|
+
node_data["height"].append(asset.height.to("meter").magnitude)
|
85
|
+
node_data["elevation"].append(asset.elevation.to("meter").magnitude)
|
86
|
+
node_data["latitude"].append(asset.latitude)
|
87
|
+
node_data["longitude"].append(asset.longitude)
|
88
|
+
node_data["timestamp"].append(asset_state.timestamp if asset_state else DEFAULT_TIME_STAMP)
|
89
|
+
node_data["survival_prob"].append(asset_state.survival_probability if asset_state else 1.0)
|
90
|
+
node_data["wind_speed"].append(asset_state.wind_speed if asset_state else None)
|
91
|
+
node_data["fire_boundary_dist"].append(
|
92
|
+
asset_state.fire_boundary_dist if asset_state else None
|
93
|
+
)
|
94
|
+
node_data["flood_depth"].append(asset_state.flood_depth if asset_state else None)
|
95
|
+
node_data["flood_velocity"].append(asset_state.flood_velocity if asset_state else None)
|
96
|
+
node_data["peak_ground_acceleration"].append(
|
97
|
+
asset_state.peak_ground_acceleration if asset_state else None
|
98
|
+
)
|
99
|
+
node_data["peak_ground_velocity"].append(
|
100
|
+
asset_state.peak_ground_velocity if asset_state else None
|
101
|
+
)
|
102
|
+
return node_data
|
103
|
+
|
104
|
+
def _add_edge_data(
|
105
|
+
self, edge_data: dict[str, list], asset: Asset, asset_state: AssetState | None = None
|
106
|
+
):
|
107
|
+
u, v = set(asset.connections)
|
108
|
+
buses = list(
|
109
|
+
self.get_components(Asset, filter_func=lambda x: x.distribution_asset in [u, v])
|
110
|
+
)
|
111
|
+
edge_data["name"].append(asset.name)
|
112
|
+
edge_data["type"].append(asset.asset_type.name)
|
113
|
+
edge_data["height"].append(asset.height.to("meter").magnitude)
|
114
|
+
edge_data["elevation"].append(asset.elevation.to("meter").magnitude)
|
115
|
+
edge_data["latitude"].append([b.latitude for b in buses])
|
116
|
+
edge_data["longitude"].append([b.longitude for b in buses])
|
117
|
+
edge_data["timestamp"].append(asset_state.timestamp if asset_state else DEFAULT_TIME_STAMP)
|
118
|
+
edge_data["survival_prob"].append(asset_state.survival_probability if asset_state else 1.0)
|
119
|
+
edge_data["wind_speed"].append(asset_state.wind_speed if asset_state else None)
|
120
|
+
edge_data["fire_boundary_dist"].append(
|
121
|
+
asset_state.fire_boundary_dist if asset_state else None
|
122
|
+
)
|
123
|
+
edge_data["flood_depth"].append(asset_state.flood_depth if asset_state else None)
|
124
|
+
edge_data["flood_velocity"].append(asset_state.flood_velocity if asset_state else None)
|
125
|
+
edge_data["peak_ground_acceleration"].append(
|
126
|
+
asset_state.peak_ground_acceleration if asset_state else None
|
127
|
+
)
|
128
|
+
edge_data["peak_ground_velocity"].append(
|
129
|
+
asset_state.peak_ground_velocity if asset_state else None
|
130
|
+
)
|
131
|
+
return edge_data
|
132
|
+
|
133
|
+
def to_gdf(self):
|
134
|
+
node_data = defaultdict(list)
|
135
|
+
edge_data = defaultdict(list)
|
136
|
+
assets: list[Asset] = self.get_components(Asset)
|
137
|
+
|
138
|
+
for asset in assets:
|
139
|
+
if len(set(asset.connections)) < 2:
|
140
|
+
if asset.asset_state:
|
141
|
+
for asset_state in asset.asset_state:
|
142
|
+
node_data = self._add_node_data(node_data, asset, asset_state)
|
143
|
+
else:
|
144
|
+
node_data = self._add_node_data(node_data, asset, None)
|
145
|
+
else:
|
146
|
+
if asset.asset_state:
|
147
|
+
for asset_state in asset.asset_state:
|
148
|
+
edge_data = self._add_edge_data(edge_data, asset, asset_state)
|
149
|
+
else:
|
150
|
+
edge_data = self._add_edge_data(edge_data, asset, None)
|
151
|
+
|
152
|
+
nodes_df = pd.DataFrame(node_data)
|
153
|
+
gdf_nodes = gpd.GeoDataFrame(
|
154
|
+
nodes_df,
|
155
|
+
geometry=gpd.points_from_xy(nodes_df.longitude, nodes_df.latitude),
|
156
|
+
crs="EPSG:4326",
|
157
|
+
)
|
158
|
+
edge_df = pd.DataFrame(edge_data)
|
159
|
+
edge_df = edge_df[edge_df["longitude"].apply(lambda x: len(x) == 2)]
|
160
|
+
geometry = [
|
161
|
+
LineString([Point(xy) for xy in zip(*xys)])
|
162
|
+
for xys in zip(edge_df["longitude"], edge_df["latitude"])
|
163
|
+
]
|
164
|
+
gdf_edges = gpd.GeoDataFrame(edge_df, geometry=geometry, crs="EPSG:4326")
|
165
|
+
complete_gdf = pd.concat([gdf_nodes, gdf_edges])
|
166
|
+
return complete_gdf
|
167
|
+
|
168
|
+
def to_geojson(self) -> str:
|
169
|
+
"""Create a GeoJSON from an AssetSystem."""
|
170
|
+
gdf_nodes = self.to_gdf()
|
171
|
+
return gdf_nodes.to_json()
|
172
|
+
|
173
|
+
def _prepopulate_bus_assets(
|
174
|
+
self,
|
175
|
+
asset_map: dict[AssetTypes : list[gdc.DistributionComponentBase]],
|
176
|
+
list_of_assets: dict[str, Asset],
|
177
|
+
):
|
178
|
+
for asset_type, components in asset_map.items():
|
179
|
+
for component in components:
|
180
|
+
lat, long = AssetSystem._get_component_coordinate(component)
|
181
|
+
if isinstance(component, gdc.DistributionBus):
|
182
|
+
list_of_assets[str(component.uuid)] = Asset(
|
183
|
+
name=component.name,
|
184
|
+
connections=[],
|
185
|
+
asset_type=asset_type,
|
186
|
+
distribution_asset=component.uuid,
|
187
|
+
height=Distance(3, "meter"),
|
188
|
+
latitude=lat,
|
189
|
+
longitude=long,
|
190
|
+
asset_state=[],
|
191
|
+
)
|
192
|
+
return list_of_assets
|
193
|
+
|
194
|
+
def _build_assets(
|
195
|
+
self,
|
196
|
+
asset_map: dict[AssetTypes : list[gdc.DistributionComponentBase]],
|
197
|
+
) -> list[Asset]:
|
198
|
+
list_of_assets: dict[str, Asset] = {}
|
199
|
+
|
200
|
+
self._prepopulate_bus_assets(asset_map, list_of_assets)
|
201
|
+
|
202
|
+
for asset_type, components in asset_map.items():
|
203
|
+
for component in components:
|
204
|
+
if not isinstance(component, gdc.DistributionBus):
|
205
|
+
lat, long = AssetSystem._get_component_coordinate(component)
|
206
|
+
if hasattr(component, "buses"):
|
207
|
+
connections = [c.uuid for c in component.buses]
|
208
|
+
elif hasattr(component, "bus"):
|
209
|
+
connections = [component.bus.uuid]
|
210
|
+
else:
|
211
|
+
connections = []
|
212
|
+
list_of_assets[str(component.uuid)] = Asset(
|
213
|
+
name=component.name,
|
214
|
+
connections=connections,
|
215
|
+
asset_type=asset_type,
|
216
|
+
distribution_asset=component.uuid,
|
217
|
+
height=Distance(3, "meter"),
|
218
|
+
latitude=lat,
|
219
|
+
longitude=long,
|
220
|
+
asset_state=[],
|
221
|
+
)
|
222
|
+
|
223
|
+
if len(connections) == 1:
|
224
|
+
if str(connections[0]) in list_of_assets:
|
225
|
+
asset = list_of_assets[str(connections[0])]
|
226
|
+
asset.devices.append(component.uuid)
|
227
|
+
|
228
|
+
return list_of_assets.values()
|
229
|
+
|
230
|
+
@staticmethod
|
231
|
+
def _get_component_coordinate(component: gdc.DistributionComponentBase):
|
232
|
+
if hasattr(component, "buses"):
|
233
|
+
xs = [bus.coordinate.x for bus in component.buses]
|
234
|
+
ys = [bus.coordinate.y for bus in component.buses]
|
235
|
+
return (sum(xs) / len(xs), sum(ys) / len(ys))
|
236
|
+
elif hasattr(component, "bus"):
|
237
|
+
return (component.bus.coordinate.x, component.bus.coordinate.y)
|
238
|
+
elif isinstance(component, gdc.DistributionBus):
|
239
|
+
return (component.coordinate.x, component.coordinate.y)
|
240
|
+
|
241
|
+
@staticmethod
|
242
|
+
def map_asets(
|
243
|
+
dist_system: DistributionSystem,
|
244
|
+
) -> dict[AssetTypes : list[gdc.DistributionComponentBase]]:
|
245
|
+
asset_dict = defaultdict(list)
|
246
|
+
for asset, filters in asset_to_gdm_mapping.items():
|
247
|
+
for filter_info in filters:
|
248
|
+
models = dist_system.get_components(
|
249
|
+
filter_info.component_type, filter_func=filter_info.component_filter
|
250
|
+
)
|
251
|
+
asset_dict[asset].extend(list(models))
|
252
|
+
|
253
|
+
AssetSystem._maps_buses(asset_dict, dist_system)
|
254
|
+
AssetSystem._map_transformers(asset_dict, dist_system)
|
255
|
+
return asset_dict
|
256
|
+
|
257
|
+
@staticmethod
|
258
|
+
def _maps_buses(
|
259
|
+
asset_dict: dict[AssetTypes : list[gdc.DistributionComponentBase]],
|
260
|
+
dist_system: DistributionSystem,
|
261
|
+
):
|
262
|
+
for bus in dist_system.get_components(gdc.DistributionBus):
|
263
|
+
asset_type = AssetSystem._get_bus_type(bus, asset_dict, dist_system)
|
264
|
+
if asset_type:
|
265
|
+
asset_dict[asset_type].append(bus)
|
266
|
+
|
267
|
+
@staticmethod
|
268
|
+
def _map_transformers(
|
269
|
+
asset_dict: dict[AssetTypes : list[gdc.DistributionComponentBase]],
|
270
|
+
dist_system: DistributionSystem,
|
271
|
+
):
|
272
|
+
for transformer in dist_system.get_components(gdc.DistributionTransformerBase):
|
273
|
+
bus_types = [
|
274
|
+
AssetSystem._get_bus_type(b, asset_dict, dist_system) for b in transformer.buses
|
275
|
+
]
|
276
|
+
if (
|
277
|
+
AssetTypes.transmission_junction_box in bus_types
|
278
|
+
or AssetTypes.transmission_tower in bus_types
|
279
|
+
):
|
280
|
+
asset_dict[AssetTypes.transformer_mad_mount].append(transformer)
|
281
|
+
elif all(bus_type == AssetTypes.distribution_junction_box for bus_type in bus_types):
|
282
|
+
asset_dict[AssetTypes.transformer_mad_mount].append(transformer)
|
283
|
+
else:
|
284
|
+
asset_dict[AssetTypes.transformer_mad_mount].append(transformer)
|
285
|
+
|
286
|
+
@staticmethod
|
287
|
+
def _get_bus_type(
|
288
|
+
bus: gdc.DistributionBus,
|
289
|
+
asset_dict: dict[AssetTypes : list[gdc.DistributionComponentBase]],
|
290
|
+
dist_system: DistributionSystem,
|
291
|
+
):
|
292
|
+
components = dist_system.get_bus_connected_components(bus.name, gdc.DistributionBranchBase)
|
293
|
+
connected_types = []
|
294
|
+
for component in components:
|
295
|
+
if component in asset_dict[AssetTypes.distribution_overhead_lines]:
|
296
|
+
connected_types.append(AssetTypes.distribution_poles)
|
297
|
+
elif component in asset_dict[AssetTypes.transmission_overhead_lines]:
|
298
|
+
connected_types.append(AssetTypes.transmission_tower)
|
299
|
+
elif component in asset_dict[AssetTypes.transmission_underground_cables]:
|
300
|
+
connected_types.append(AssetTypes.transmission_junction_box)
|
301
|
+
elif component in asset_dict[AssetTypes.distribution_underground_cables]:
|
302
|
+
connected_types.append(AssetTypes.distribution_junction_box)
|
303
|
+
else:
|
304
|
+
...
|
305
|
+
if connected_types:
|
306
|
+
counter = Counter(connected_types)
|
307
|
+
return counter.most_common(1)[0][0]
|
308
|
+
|
309
|
+
def _add_node_traces(
|
310
|
+
self,
|
311
|
+
time_index: int,
|
312
|
+
fig: go.Figure,
|
313
|
+
df_ts: gpd.GeoDataFrame,
|
314
|
+
plotting_object: go.Scattermap | go.Scattergeo,
|
315
|
+
):
|
316
|
+
points_only = df_ts[df_ts.geometry.geom_type == "Point"]
|
317
|
+
for asset_type in set(points_only["type"]):
|
318
|
+
print("Asset type: ", asset_type)
|
319
|
+
df_filt = points_only[points_only["type"] == asset_type]
|
320
|
+
text = [
|
321
|
+
"<br>".join([f"<b>{kk}:</b> {vv}" for kk, vv in rr.to_dict().items()][:-1])
|
322
|
+
for __, rr in df_filt.iterrows()
|
323
|
+
]
|
324
|
+
trace = plotting_object(
|
325
|
+
lat=df_filt.geometry.y,
|
326
|
+
lon=df_filt.geometry.x,
|
327
|
+
mode="markers",
|
328
|
+
marker=dict(size=10),
|
329
|
+
name=asset_type,
|
330
|
+
hovertext=text,
|
331
|
+
visible=(time_index == 0),
|
332
|
+
)
|
333
|
+
fig.add_trace(trace)
|
334
|
+
return fig
|
335
|
+
|
336
|
+
def _add_edge_traces(
|
337
|
+
self,
|
338
|
+
time_index: int,
|
339
|
+
fig: go.Figure,
|
340
|
+
df_ts: gpd.GeoDataFrame,
|
341
|
+
plotting_object: go.Scattermap | go.Scattergeo,
|
342
|
+
):
|
343
|
+
points_only = df_ts[df_ts.geometry.geom_type == "LineString"]
|
344
|
+
for asset_type in set(points_only["type"]):
|
345
|
+
df_filt = points_only[points_only["type"] == asset_type]
|
346
|
+
features = {k: [] for k in list(df_filt.columns) + ["text"]}
|
347
|
+
for _, row in df_filt.iterrows():
|
348
|
+
for c in df_filt.columns:
|
349
|
+
features[c] = np.append(features[c], row[c])
|
350
|
+
features["text"] = np.append(
|
351
|
+
features["text"],
|
352
|
+
"<br> ".join([f"<b>{kk}:</b> {vv}" for kk, vv in row.to_dict().items()][:-1]),
|
353
|
+
)
|
354
|
+
for c in df_filt.columns:
|
355
|
+
features[c] = np.append(features[c], None)
|
356
|
+
features["text"] = np.append(features["text"], None)
|
357
|
+
|
358
|
+
trace = plotting_object(
|
359
|
+
name=asset_type,
|
360
|
+
lat=features["latitude"],
|
361
|
+
lon=features["longitude"],
|
362
|
+
mode="lines",
|
363
|
+
hovertext=features["text"],
|
364
|
+
visible=(time_index == 0),
|
365
|
+
)
|
366
|
+
fig.add_trace(trace)
|
367
|
+
return fig
|
368
|
+
|
369
|
+
def plot(
|
370
|
+
self,
|
371
|
+
show: bool = True,
|
372
|
+
show_legend: bool = True,
|
373
|
+
map_type: MapType = MapType.SCATTER_MAP,
|
374
|
+
style: PlotingStyle = PlotingStyle.CARTO_POSITRON,
|
375
|
+
zoom_level: int = 11,
|
376
|
+
):
|
377
|
+
"""Plot the AssetSystem."""
|
378
|
+
plotting_object = getattr(go, map_type.value)
|
379
|
+
gdf = self.to_gdf()
|
380
|
+
gdf["timestamp"] = pd.to_datetime(gdf["timestamp"])
|
381
|
+
timestamps = sorted(gdf["timestamp"].unique())
|
382
|
+
fig = go.Figure()
|
383
|
+
steps = []
|
384
|
+
|
385
|
+
for i, ts in enumerate(timestamps):
|
386
|
+
df_ts = gdf[gdf["timestamp"] == ts]
|
387
|
+
|
388
|
+
fig = self._add_node_traces(i, fig, df_ts, plotting_object)
|
389
|
+
fig = self._add_edge_traces(i, fig, df_ts, plotting_object)
|
390
|
+
|
391
|
+
steps.append(
|
392
|
+
dict(
|
393
|
+
method="update",
|
394
|
+
label=str(ts.date()),
|
395
|
+
args=[{"visible": [j == i for j in range(len(timestamps))]}],
|
396
|
+
)
|
397
|
+
)
|
398
|
+
|
399
|
+
sliders = [dict(active=0, pad={"t": 50}, steps=steps)]
|
400
|
+
|
401
|
+
fig.update_layout(
|
402
|
+
mapbox=dict(
|
403
|
+
style=style.value,
|
404
|
+
zoom=zoom_level,
|
405
|
+
# center=dict(lat=gdf.geometry.y.mean(), lon=gdf.geometry.x.mean()),
|
406
|
+
),
|
407
|
+
sliders=sliders,
|
408
|
+
showlegend=True if show_legend else False,
|
409
|
+
)
|
410
|
+
|
411
|
+
if show:
|
412
|
+
fig.show()
|
413
|
+
|
414
|
+
return fig
|
@@ -0,0 +1,122 @@
|
|
1
|
+
from infrasys import System
|
2
|
+
|
3
|
+
from gdm.distribution.enums import PlotingStyle, MapType
|
4
|
+
import plotly.graph_objects as go
|
5
|
+
|
6
|
+
from erad.default_fragility_curves import DEFAULT_FRAGILTY_CURVES
|
7
|
+
from erad.constants import HAZARD_MODELS
|
8
|
+
import erad.models.fragility_curve as fc
|
9
|
+
import erad.models.hazard as hz
|
10
|
+
|
11
|
+
|
12
|
+
class HazardSystem(System):
|
13
|
+
def __init__(self, *args, **kwargs):
|
14
|
+
super().__init__(*args, **kwargs)
|
15
|
+
|
16
|
+
def add_component(self, component, **kwargs):
|
17
|
+
assert isinstance(
|
18
|
+
component, HAZARD_MODELS
|
19
|
+
), f"Unsupported model type {component.__class__.__name__}"
|
20
|
+
return super().add_component(component, **kwargs)
|
21
|
+
|
22
|
+
def add_components(self, *components, **kwargs):
|
23
|
+
assert all(isinstance(component, HAZARD_MODELS) for component in components), (
|
24
|
+
"Unsupported model types in passed component. Valid types are: \n"
|
25
|
+
+ "\n".join([s.__name__ for s in HAZARD_MODELS])
|
26
|
+
+ "\nPassed components: \n"
|
27
|
+
+ "\n".join(set([component.__class__.__name__ for component in components]))
|
28
|
+
)
|
29
|
+
return super().add_components(*components, **kwargs)
|
30
|
+
|
31
|
+
def to_json(self, filename, overwrite=False, indent=None, data=None):
|
32
|
+
if not list(self.get_components(fc.HazardFragilityCurves)):
|
33
|
+
self.add_components(*DEFAULT_FRAGILTY_CURVES)
|
34
|
+
return super().to_json(filename, overwrite, indent, data)
|
35
|
+
|
36
|
+
@classmethod
|
37
|
+
def fire_example(cls) -> "HazardSystem":
|
38
|
+
hazard = hz.FireModel.example()
|
39
|
+
system = HazardSystem(auto_add_composed_components=True)
|
40
|
+
system.add_component(hazard)
|
41
|
+
return system
|
42
|
+
|
43
|
+
@classmethod
|
44
|
+
def wind_example(cls) -> "HazardSystem":
|
45
|
+
hazard = hz.WindModel.example()
|
46
|
+
system = HazardSystem(auto_add_composed_components=True)
|
47
|
+
system.add_component(hazard)
|
48
|
+
return system
|
49
|
+
|
50
|
+
@classmethod
|
51
|
+
def earthquake_example(cls) -> "HazardSystem":
|
52
|
+
hazard = hz.EarthQuakeModel.example()
|
53
|
+
system = HazardSystem(auto_add_composed_components=True)
|
54
|
+
system.add_component(hazard)
|
55
|
+
return system
|
56
|
+
|
57
|
+
@classmethod
|
58
|
+
def flood_example(cls) -> "HazardSystem":
|
59
|
+
hazard = hz.FloodModel.example()
|
60
|
+
system = HazardSystem(auto_add_composed_components=True)
|
61
|
+
system.add_component(hazard)
|
62
|
+
return system
|
63
|
+
|
64
|
+
@classmethod
|
65
|
+
def multihazard_example(cls) -> "HazardSystem":
|
66
|
+
wind_hazard = hz.WindModel.example()
|
67
|
+
flood_hazard = hz.FloodModel.example()
|
68
|
+
system = HazardSystem(auto_add_composed_components=True)
|
69
|
+
system.add_component(wind_hazard)
|
70
|
+
system.add_component(flood_hazard)
|
71
|
+
return system
|
72
|
+
|
73
|
+
def plot(
|
74
|
+
self,
|
75
|
+
show: bool = True,
|
76
|
+
show_legend: bool = True,
|
77
|
+
map_type: MapType = MapType.SCATTER_MAP,
|
78
|
+
style: PlotingStyle = PlotingStyle.CARTO_POSITRON,
|
79
|
+
zoom_level: int = 11,
|
80
|
+
):
|
81
|
+
timestamps = sorted(
|
82
|
+
[model.timestamp for model in self.get_components(hz.BaseDisasterModel)]
|
83
|
+
)
|
84
|
+
|
85
|
+
fig = go.Figure()
|
86
|
+
steps = []
|
87
|
+
for i, ts in enumerate(timestamps):
|
88
|
+
hazards = self.get_components(
|
89
|
+
hz.BaseDisasterModel, filter_func=lambda x: x.timestamp == ts
|
90
|
+
)
|
91
|
+
map_obj = getattr(go, map_type.value)
|
92
|
+
num_traces = 0
|
93
|
+
for hazard in hazards:
|
94
|
+
num_traces += hazard.plot(i, fig, map_obj)
|
95
|
+
vis = [j == i for j in range(len(timestamps))]
|
96
|
+
result = [x for x in vis for _ in range(num_traces)]
|
97
|
+
steps.append(dict(method="update", label=str(ts), args=[{"visible": result}]))
|
98
|
+
|
99
|
+
sliders = [dict(active=0, pad={"t": 50}, steps=steps)]
|
100
|
+
|
101
|
+
if map_type == MapType.SCATTER_MAP:
|
102
|
+
fig.update_layout(
|
103
|
+
map={
|
104
|
+
"style": style.value,
|
105
|
+
"zoom": zoom_level,
|
106
|
+
},
|
107
|
+
sliders=sliders,
|
108
|
+
showlegend=True if show_legend else False,
|
109
|
+
)
|
110
|
+
else:
|
111
|
+
fig.update_layout(
|
112
|
+
geo=dict(
|
113
|
+
projection_scale=zoom_level,
|
114
|
+
),
|
115
|
+
sliders=sliders,
|
116
|
+
showlegend=True if show_legend else False,
|
117
|
+
)
|
118
|
+
|
119
|
+
if show:
|
120
|
+
fig.show()
|
121
|
+
|
122
|
+
return fig
|
@@ -0,0 +1,55 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: NREL-erad
|
3
|
+
Version: 0.1.0
|
4
|
+
Summary: Graph based scalable tool for computing energy resilience metrics for distribution systems.
|
5
|
+
Author-email: Kapil Duwadi <kapil.duwadi@nrel.gov>, Aadil Latif <aadil.altif@nrel.gov>, Kwami Sedzro <sherinann.abraham@nrel.gov>, Sherin Ann Abraham <kwami.sedzro@nrel.gov>, Bryan Palmintier <bryan.palmintier@nrel.gov>
|
6
|
+
Maintainer-email: Aadil Latif <Aadil.Latif@nrel.gov>
|
7
|
+
Project-URL: Homepage, https://github.com/nrel/erad
|
8
|
+
Keywords: Distribution,Earthquake,Energy,Fire,Flooding,Power,Python,Resilience,Systems
|
9
|
+
Classifier: License :: OSI Approved :: BSD License
|
10
|
+
Classifier: Operating System :: OS Independent
|
11
|
+
Classifier: Programming Language :: Python :: 3.8
|
12
|
+
Requires-Python: >=3.11
|
13
|
+
Description-Content-Type: text/markdown
|
14
|
+
License-File: LICENSE.txt
|
15
|
+
Requires-Dist: grid-data-models~=2.1.3
|
16
|
+
Requires-Dist: gdmloader
|
17
|
+
Requires-Dist: geopandas
|
18
|
+
Requires-Dist: requests
|
19
|
+
Requires-Dist: shapely
|
20
|
+
Requires-Dist: pandas
|
21
|
+
Requires-Dist: pyhigh
|
22
|
+
Requires-Dist: geopy
|
23
|
+
Requires-Dist: scipy
|
24
|
+
Provides-Extra: dev
|
25
|
+
Requires-Dist: black; extra == "dev"
|
26
|
+
Requires-Dist: mkdocs; extra == "dev"
|
27
|
+
Requires-Dist: mkdocs-jupyter; extra == "dev"
|
28
|
+
Requires-Dist: mkdocs-material; extra == "dev"
|
29
|
+
Requires-Dist: mkdocstrings[python]; extra == "dev"
|
30
|
+
Requires-Dist: pylint; extra == "dev"
|
31
|
+
Requires-Dist: pytest; extra == "dev"
|
32
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
33
|
+
Requires-Dist: ruff; extra == "dev"
|
34
|
+
Dynamic: license-file
|
35
|
+
|
36
|
+
# ERAD (<u>E</u>nergy <u>R</u>esilience <u>A</u>nalysis for electric <u>D</u>istribution systems)
|
37
|
+
<p align="center">
|
38
|
+
<img src="docs/images/logo.svg" width="250" style="display:flex;justify-content:center;">
|
39
|
+
<p align="center">Graph based python tool for computing energy resilience. </p>
|
40
|
+
</p>
|
41
|
+
|
42
|
+

|
43
|
+

|
44
|
+
[](https://www.codefactor.io/repository/github/nrel/erad)
|
45
|
+
[](https://github.com/NREL/erad/blob/main/LICENSE.txt)
|
46
|
+
[](https://github.com/NREL/erad/issues)
|
47
|
+

|
48
|
+
|
49
|
+
[Visit full documentation here.](https://nrel.github.io/erad/)
|
50
|
+
|
51
|
+
Understanding the impact of disaster events on people's ability to access critical service is key to designing appropriate programs to minimize the overall impact. Flooded roads, downed power lines, flooded power substation etc. could impact access to critical services like electricity, food, health and more. The field of disaster modeling is still evolving and so is our understanding of how these events would impact our critical infrastructures such power grid, hospitals, groceries, banks etc.
|
52
|
+
|
53
|
+
ERAD is a free, open-source Python toolkit for computing energy resilience measures in the face of hazards like earthquakes and flooding. It uses graph database to store data and perform computation at the household level for a variety of critical services that are connected by power distribution network. It uses asset fragility curves, which are functions that relate hazard severity to survival probability for power system assets including cables, transformers, substations, roof-mounted solar panels, etc. recommended in top literature. Programs like undergrounding, microgrid, and electricity backup units for critical infrastructures may all be evaluated using metrics and compared across different neighborhoods to assess their effects on energy resilience.
|
54
|
+
|
55
|
+
ERAD is designed to be used by researchers, students, community stakeholders, distribution utilities to understand and possibly evaluate effectiveness of different post disaster programs to improve energy resilience. It was funded by National Renewable Energy Laboratory (NREL) and made publicly available with open license.
|
@@ -0,0 +1,35 @@
|
|
1
|
+
erad/__init__.py,sha256=sXLh7g3KC4QCFxcZGBTpG2scR7hmmBsMjq6LqRptkRg,22
|
2
|
+
erad/constants.py,sha256=5WFr2OwgEvWirj5RuL0QwziSYH1p9XBnJCGard1qxjM,1621
|
3
|
+
erad/enums.py,sha256=w4sPPIwOyjFsw9NCZy13NmH4nQWkWaDlqnP_BdNrrxI,916
|
4
|
+
erad/gdm_mapping.py,sha256=Ur6-vJpxrRhLszJuSv7AjMotyqZ9MRlz_5MArmBE6LQ,2937
|
5
|
+
erad/probability_builder.py,sha256=AHfdrdRrebXEKgrcBQk9ZqH7bMfTVok7sxShV_O5nu8,1370
|
6
|
+
erad/quantities.py,sha256=Qhl-0Qorv3AbBNotnS5hfxajZgRlpf6DE1iOCg7_0DI,454
|
7
|
+
erad/runner.py,sha256=Lvqm0YFt_E-v4SsRmVMgmnun8bU9AeDYwZB-w_dJXvk,4549
|
8
|
+
erad/default_fragility_curves/__init__.py,sha256=TEoZ01NIb1uIAs6AF0V1NJaPBA7FcYtQ5379Frwty5M,778
|
9
|
+
erad/default_fragility_curves/default_fire_boundary_dist.py,sha256=KX_pmJ9yyOqSavzRWk1cgkHbqyxAGmu8GCke9wh48YI,3623
|
10
|
+
erad/default_fragility_curves/default_flood_depth.py,sha256=uy4aAKqDNfsXQq1LKYViQhPIeq0EiO8Y_i3Ow3K5p6E,4208
|
11
|
+
erad/default_fragility_curves/default_flood_velocity.py,sha256=L0w9WH1cjounh8wHXii9DzgvvdDpe4jRs7m3AUrgPTM,4075
|
12
|
+
erad/default_fragility_curves/default_fragility_curves.py,sha256=uGQb0WhtgCyu_rNhh9UibJLdEELD1E28iUMQsLgSUpk,996
|
13
|
+
erad/default_fragility_curves/default_peak_ground_acceleration.py,sha256=cvjgm0jK7nEf0T8Tx0KyOyVPqnqj6GUTrKhqIIFZEK4,5695
|
14
|
+
erad/default_fragility_curves/default_peak_ground_velocity.py,sha256=-M6EWv5sq_M2FxQfad_kwORMk0RRfQVjNzNQo2NnKJA,3887
|
15
|
+
erad/default_fragility_curves/default_wind_speed.py,sha256=gtsNFJYcY2qA906lmFYTZDpxvfknk-kxuLgOId5-nLY,3915
|
16
|
+
erad/models/__init__.py,sha256=sXLh7g3KC4QCFxcZGBTpG2scR7hmmBsMjq6LqRptkRg,22
|
17
|
+
erad/models/asset.py,sha256=-SUjiaB9iIINDY8a1xhSxjv5gajB7LE7JG0EtxXwhHI,11237
|
18
|
+
erad/models/asset_mapping.py,sha256=vdqiraFo_Ssd4Se9YEne34TjW1MSCVErmLHFqJa96Nw,518
|
19
|
+
erad/models/fragility_curve.py,sha256=9K5q_VZVEqNKSPIcmrD2uhF9ClZ7p57oKY4jtxyu9tc,3725
|
20
|
+
erad/models/probability.py,sha256=hTVBCundyfX5NwoAyF_13osluJDWft1N2Il8XeLabfk,1933
|
21
|
+
erad/models/hazard/__init__.py,sha256=3PacFmU_iIxZRLiIvcRCXJLUDdgHputUEnuvQvJ8VNM,295
|
22
|
+
erad/models/hazard/base_models.py,sha256=nZndeKYIJrn_FDyDIfpfnGQiHdoXBz_mAtBn61iW4bY,423
|
23
|
+
erad/models/hazard/common.py,sha256=FIGPD8w3hsFyK_J99Wyp2p2KsC1McmtXD4zIrtqLjYM,815
|
24
|
+
erad/models/hazard/earthquake.py,sha256=OOKpl2g1PVOYdRIj1Gmi3F8sGc9Lhsquzs7-etrTjuI,3194
|
25
|
+
erad/models/hazard/flood.py,sha256=ScmqKu_ChbsfHWZy5SrkVixevLOrcQPY2eGlzOaWAio,2717
|
26
|
+
erad/models/hazard/wild_fire.py,sha256=hjlp_AI4I1P00F_p0N4pYVdTAmrBr4F4kXKvpmMGWoM,4110
|
27
|
+
erad/models/hazard/wind.py,sha256=YNmXnxMpNWb45UwPheQqC5PCaVN8wdnLLp8jQZjMHuw,5187
|
28
|
+
erad/systems/__init__.py,sha256=TYQ44-fJ0REaEg0I4RClxPK0BZTuvVlSEqmrh_xqTAs,102
|
29
|
+
erad/systems/asset_system.py,sha256=MDzc0p6RE81k8ZPjzjuil5fME89Rd7fqeYlUqjNCYnw,17267
|
30
|
+
erad/systems/hazard_system.py,sha256=dHrA4p1mnaQvBLQVIZWLhZ6NcGD3toyGElP-LW0HRX4,4305
|
31
|
+
nrel_erad-0.1.0.dist-info/licenses/LICENSE.txt,sha256=YhE40B_XkokpkrkfYYs75IWapyRMl_Jdo__vzVWO7eY,1571
|
32
|
+
nrel_erad-0.1.0.dist-info/METADATA,sha256=snlcuFSxtS0rJCaVkMYWwzjfGpf6EbsyfbZw3dJqFtA,4104
|
33
|
+
nrel_erad-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
34
|
+
nrel_erad-0.1.0.dist-info/top_level.txt,sha256=TR5AAzfVy08tAhcPCsDnwvQuVJ2oXZ334IFTb3iHj-k,5
|
35
|
+
nrel_erad-0.1.0.dist-info/RECORD,,
|