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.
Files changed (72) hide show
  1. erad/__init__.py +1 -0
  2. erad/constants.py +59 -11
  3. erad/default_fragility_curves/__init__.py +15 -0
  4. erad/default_fragility_curves/default_fire_boundary_dist.py +94 -0
  5. erad/default_fragility_curves/default_flood_depth.py +108 -0
  6. erad/default_fragility_curves/default_flood_velocity.py +101 -0
  7. erad/default_fragility_curves/default_fragility_curves.py +23 -0
  8. erad/default_fragility_curves/default_peak_ground_acceleration.py +163 -0
  9. erad/default_fragility_curves/default_peak_ground_velocity.py +94 -0
  10. erad/default_fragility_curves/default_wind_speed.py +94 -0
  11. erad/enums.py +40 -0
  12. erad/gdm_mapping.py +83 -0
  13. erad/models/__init__.py +1 -0
  14. erad/models/asset.py +287 -0
  15. erad/models/asset_mapping.py +20 -0
  16. erad/models/fragility_curve.py +116 -0
  17. erad/models/hazard/__init__.py +5 -0
  18. erad/models/hazard/base_models.py +12 -0
  19. erad/models/hazard/common.py +26 -0
  20. erad/models/hazard/earthquake.py +93 -0
  21. erad/models/hazard/flood.py +83 -0
  22. erad/models/hazard/wild_fire.py +121 -0
  23. erad/models/hazard/wind.py +143 -0
  24. erad/models/probability.py +73 -0
  25. erad/probability_builder.py +35 -0
  26. erad/quantities.py +25 -0
  27. erad/runner.py +122 -0
  28. erad/systems/__init__.py +2 -0
  29. erad/systems/asset_system.py +414 -0
  30. erad/systems/hazard_system.py +122 -0
  31. nrel_erad-0.1.0.dist-info/METADATA +55 -0
  32. nrel_erad-0.1.0.dist-info/RECORD +35 -0
  33. {NREL_erad-0.0.0a0.dist-info → nrel_erad-0.1.0.dist-info}/WHEEL +1 -1
  34. NREL_erad-0.0.0a0.dist-info/METADATA +0 -61
  35. NREL_erad-0.0.0a0.dist-info/RECORD +0 -42
  36. erad/cypher_queries/load_data_v1.cypher +0 -212
  37. erad/data/World_Earthquakes_1960_2016.csv +0 -23410
  38. erad/db/__init__.py +0 -0
  39. erad/db/assets/__init__.py +0 -0
  40. erad/db/assets/critical_infras.py +0 -171
  41. erad/db/assets/distribution_lines.py +0 -101
  42. erad/db/credential_model.py +0 -20
  43. erad/db/disaster_input_model.py +0 -23
  44. erad/db/inject_earthquake.py +0 -52
  45. erad/db/inject_flooding.py +0 -53
  46. erad/db/neo4j_.py +0 -162
  47. erad/db/utils.py +0 -14
  48. erad/exceptions.py +0 -68
  49. erad/metrics/__init__.py +0 -0
  50. erad/metrics/check_microgrid.py +0 -208
  51. erad/metrics/metric.py +0 -178
  52. erad/programs/__init__.py +0 -0
  53. erad/programs/backup.py +0 -62
  54. erad/programs/microgrid.py +0 -45
  55. erad/scenarios/__init__.py +0 -0
  56. erad/scenarios/abstract_scenario.py +0 -103
  57. erad/scenarios/common.py +0 -93
  58. erad/scenarios/earthquake_scenario.py +0 -161
  59. erad/scenarios/fire_scenario.py +0 -160
  60. erad/scenarios/flood_scenario.py +0 -494
  61. erad/scenarios/utilities.py +0 -76
  62. erad/scenarios/wind_scenario.py +0 -89
  63. erad/utils/__init__.py +0 -0
  64. erad/utils/ditto_utils.py +0 -252
  65. erad/utils/hifld_utils.py +0 -147
  66. erad/utils/opendss_utils.py +0 -357
  67. erad/utils/overpass.py +0 -76
  68. erad/utils/util.py +0 -178
  69. erad/visualization/__init__.py +0 -0
  70. erad/visualization/plot_graph.py +0 -218
  71. {NREL_erad-0.0.0a0.dist-info → nrel_erad-0.1.0.dist-info/licenses}/LICENSE.txt +0 -0
  72. {NREL_erad-0.0.0a0.dist-info → nrel_erad-0.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,93 @@
1
+ from datetime import datetime
2
+ import sqlite3
3
+ import os
4
+
5
+ from pydantic import field_serializer, field_validator
6
+ from gdm.quantities import Distance
7
+ from shapely.geometry import Point
8
+ import plotly.graph_objects as go
9
+ import pandas as pd
10
+
11
+ from erad.models.hazard.common import ERAD_DB, HISTROIC_EARTHQUAKE_TABLE
12
+ from erad.models.hazard.base_models import BaseDisasterModel
13
+
14
+
15
+ class EarthQuakeModel(BaseDisasterModel):
16
+ timestamp: datetime
17
+ origin: Point
18
+ depth: Distance
19
+ magnitude: float
20
+
21
+ @field_validator("origin", mode="before")
22
+ def deserialize_point(cls, value):
23
+ if isinstance(value, dict) and value.get("type") == "Point":
24
+ coords = value["coordinates"]
25
+ return Point(coords)
26
+ return value
27
+
28
+ @field_serializer("origin")
29
+ def serialize_location(self, point: Point, _info):
30
+ return {"type": "Point", "coordinates": (point.x, point.y)}
31
+
32
+ @classmethod
33
+ def example(cls) -> "EarthQuakeModel":
34
+ return EarthQuakeModel(
35
+ name="earthquake 1",
36
+ timestamp=datetime.now(),
37
+ origin=Point(-120.93036, 36.60144),
38
+ depth=Distance(300, "km"),
39
+ magnitude=5.0,
40
+ )
41
+
42
+ @classmethod
43
+ def from_earthquake_code(cls, earthquake_code: str) -> "EarthQuakeModel":
44
+ assert os.path.exists(ERAD_DB), f"The data file {ERAD_DB} not found"
45
+
46
+ conn = sqlite3.connect(ERAD_DB)
47
+ earthquake_data = pd.read_sql(
48
+ f"SELECT * FROM {HISTROIC_EARTHQUAKE_TABLE} WHERE ID = '{earthquake_code}'", conn
49
+ )
50
+ conn.close()
51
+
52
+ assert not earthquake_data.empty, f"No earthquake {earthquake_code} found in the database"
53
+
54
+ earthquake_data["Date"] = pd.to_datetime(earthquake_data["Date"])
55
+ earthquake_data["Time"] = pd.to_datetime(earthquake_data["Time"])
56
+ earthquake_data["DateTime"] = earthquake_data.apply(
57
+ lambda row: datetime.combine(row["Date"].date(), row["Time"].time()), axis=1
58
+ )
59
+
60
+ long = earthquake_data.Longitude.values[0]
61
+ lat = earthquake_data.Latitude.values[0]
62
+
63
+ return cls(
64
+ name=earthquake_code,
65
+ timestamp=earthquake_data.DateTime.values[0].astype("datetime64[ms]").astype(datetime),
66
+ origin=Point(long, lat),
67
+ depth=Distance(earthquake_data.Depth.values[0], "km"),
68
+ magnitude=earthquake_data.Magnitude.values[0],
69
+ )
70
+
71
+ def plot(
72
+ self,
73
+ time_index: int = 0,
74
+ figure: go.Figure = go.Figure(),
75
+ map_obj: type[go.Scattergeo | go.Scattermap] = go.Scattermap,
76
+ ) -> int:
77
+ figure.add_trace(
78
+ map_obj(
79
+ lat=[self.origin.y],
80
+ lon=[self.origin.x],
81
+ mode="markers",
82
+ marker=dict(size=[self.magnitude * 10], color=[self.depth.magnitude], opacity=0.4),
83
+ name="Earthquake",
84
+ hovertext=[
85
+ f"""
86
+ <br> <b>Earthquake depth:</b> {self.depth}
87
+ <br> <b>Earthquake magnitude:</b> {self.magnitude}
88
+ """
89
+ ],
90
+ visible=(time_index == 0),
91
+ )
92
+ )
93
+ return 1
@@ -0,0 +1,83 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import field_serializer, field_validator
4
+ from shapely.geometry import Polygon, Point
5
+ from gdm.quantities import Distance
6
+ import plotly.graph_objects as go
7
+ from infrasys import Component
8
+
9
+ from erad.models.hazard.base_models import BaseDisasterModel
10
+ from erad.quantities import Speed
11
+
12
+
13
+ class FloodModelArea(Component):
14
+ name: str = ""
15
+ affected_area: Polygon
16
+ water_velocity: Speed
17
+ water_elevation: Distance
18
+
19
+ @field_validator("affected_area", mode="before")
20
+ def deserialize_polygon(cls, value):
21
+ if isinstance(value, dict) and value.get("type") == "Polygon":
22
+ points = [Point(c) for c in value["coordinates"]]
23
+ return Polygon(points)
24
+ return value
25
+
26
+ @field_serializer("affected_area")
27
+ def serialize_polygon(self, poly: Polygon, _info):
28
+ return {"type": "Polygon", "coordinates": list(poly.exterior.coords)}
29
+
30
+ @classmethod
31
+ def example(cls) -> "FloodModelArea":
32
+ return FloodModelArea(
33
+ affected_area=Polygon(
34
+ [
35
+ (-120.93036, 36.60144),
36
+ (-120.91072, 36.60206),
37
+ (-120.91127, 36.5712),
38
+ (-120.93405, 36.58100),
39
+ ]
40
+ ),
41
+ water_velocity=Speed(50, "meter/second"),
42
+ water_elevation=Distance(10, "feet"),
43
+ )
44
+
45
+
46
+ class FloodModel(BaseDisasterModel):
47
+ timestamp: datetime
48
+ affected_areas: list[FloodModelArea]
49
+
50
+ @classmethod
51
+ def example(cls) -> "FloodModel":
52
+ return FloodModel(
53
+ name="flood 1",
54
+ timestamp=datetime.now(),
55
+ affected_areas=[FloodModelArea.example()],
56
+ )
57
+
58
+ def plot(
59
+ self,
60
+ time_index: int = 0,
61
+ figure: go.Figure = go.Figure(),
62
+ map_obj: type[go.Scattergeo | go.Scattermap] = go.Scattermap,
63
+ ) -> int:
64
+ for area in self.affected_areas:
65
+ lon, lat = area.affected_area.exterior.xy # returns x and y sequences
66
+ figure.add_trace(
67
+ map_obj(
68
+ lon=lon.tolist(),
69
+ lat=lat.tolist(),
70
+ mode="markers+lines+text",
71
+ fill="toself",
72
+ marker={
73
+ "size": 10,
74
+ "color": [area.water_elevation.magnitude],
75
+ },
76
+ hovertemplate=f"""
77
+ <br> <b>Water velocity:</b> {area.water_velocity}
78
+ <br> <b>Water elevation:</b> {area.water_elevation}
79
+ """,
80
+ visible=(time_index == 0),
81
+ )
82
+ )
83
+ return len(self.affected_areas)
@@ -0,0 +1,121 @@
1
+ from datetime import datetime
2
+ import sqlite3
3
+ import os
4
+
5
+ from shapely.geometry import MultiPolygon, Polygon, Point
6
+ from pydantic import field_serializer, field_validator
7
+ import plotly.graph_objects as go
8
+ from gdm.quantities import Angle
9
+ from infrasys import Component
10
+ from shapely import wkb
11
+ import pandas as pd
12
+
13
+ from erad.models.hazard.common import ERAD_DB, HISTROIC_FIRE_TABLE
14
+ from erad.models.hazard.base_models import BaseDisasterModel
15
+ from erad.quantities import Speed
16
+
17
+
18
+ class FireModelArea(Component):
19
+ name: str = ""
20
+ affected_area: Polygon
21
+ wind_speed: Speed
22
+ wind_direction: Angle
23
+
24
+ @field_validator("affected_area", mode="before")
25
+ def deserialize_polygon(cls, value):
26
+ if isinstance(value, dict) and value.get("type") == "Polygon":
27
+ points = [Point(c) for c in value["coordinates"]]
28
+ return Polygon(points)
29
+ return value
30
+
31
+ @field_serializer("affected_area")
32
+ def serialize_polygon(self, poly: Polygon, _info):
33
+ return {"type": "Polygon", "coordinates": list(poly.exterior.coords)}
34
+
35
+ @classmethod
36
+ def example(cls) -> "FireModelArea":
37
+ return FireModelArea(
38
+ affected_area=Polygon(
39
+ [
40
+ (-120.93036, 36.60144),
41
+ (-120.91072, 36.60206),
42
+ (-120.91127, 36.5712),
43
+ (-120.93405, 36.58100),
44
+ ]
45
+ ),
46
+ wind_speed=Speed(50, "miles/hour"),
47
+ wind_direction=Angle(45, "deg"),
48
+ )
49
+
50
+
51
+ class FireModel(BaseDisasterModel):
52
+ timestamp: datetime
53
+ affected_areas: list[FireModelArea]
54
+
55
+ @classmethod
56
+ def example(cls) -> "FireModel":
57
+ return FireModel(
58
+ name="fire 1",
59
+ timestamp=datetime.now(),
60
+ affected_areas=[FireModelArea.example()],
61
+ )
62
+
63
+ @classmethod
64
+ def from_wildfire_name(cls, wildfire_name: str) -> "FireModel":
65
+ assert os.path.exists(ERAD_DB), f"The data file {ERAD_DB} not found"
66
+ conn = sqlite3.connect(ERAD_DB)
67
+ fire_data = pd.read_sql(
68
+ f"SELECT * FROM {HISTROIC_FIRE_TABLE} WHERE firename = '{wildfire_name}';", conn
69
+ )
70
+ if fire_data.empty:
71
+ raise ValueError(
72
+ f"Fire '{wildfire_name}' not found in table '{HISTROIC_FIRE_TABLE}' in the database"
73
+ )
74
+ conn.close()
75
+ fire_data["discoverydatetime"] = pd.to_datetime(fire_data["discoverydatetime"])
76
+ geometry: MultiPolygon = [wkb.loads(g) for g in fire_data.GEOMETRY][0]
77
+ areas = []
78
+ for i, poly in enumerate(geometry.geoms):
79
+ areas.append(
80
+ FireModelArea(
81
+ affected_area=poly,
82
+ wind_speed=Speed(-999, "miles/hour"),
83
+ wind_direction=Angle(0, "deg"),
84
+ )
85
+ )
86
+ return cls(
87
+ name=wildfire_name,
88
+ timestamp=fire_data["discoverydatetime"]
89
+ .values[0]
90
+ .astype("datetime64[ms]")
91
+ .astype(datetime),
92
+ affected_areas=areas,
93
+ )
94
+
95
+ def plot(
96
+ self,
97
+ time_index: int = 0,
98
+ figure: go.Figure = go.Figure(),
99
+ map_obj: type[go.Scattergeo | go.Scattermap] = go.Scattermap,
100
+ ) -> int:
101
+ for area in self.affected_areas:
102
+ lon, lat = area.affected_area.exterior.xy # returns x and y sequences
103
+ figure.add_trace(
104
+ map_obj(
105
+ lon=lon.tolist(),
106
+ lat=lat.tolist(),
107
+ mode="markers+lines+text",
108
+ fill="toself",
109
+ marker={
110
+ "size": 10,
111
+ "color": [area.wind_speed.magnitude],
112
+ },
113
+ hovertemplate=f"""
114
+ <br> <b>Wind speed:</b> {area.wind_speed}
115
+ <br> <b>Wind direction:</b> {area.wind_direction}
116
+ """,
117
+ hoverinfo="text",
118
+ visible=(time_index == 0),
119
+ )
120
+ )
121
+ return len(self.affected_areas)
@@ -0,0 +1,143 @@
1
+ from datetime import datetime
2
+ import sqlite3
3
+ import os
4
+
5
+ from pydantic import field_serializer, field_validator
6
+ from infrasys.quantities import Distance
7
+ from shapely.geometry import Point
8
+ import plotly.graph_objects as go
9
+ import geopandas as gpd
10
+ import pandas as pd
11
+
12
+ from erad.models.hazard.common import ERAD_DB, HISTROIC_HURRICANE_TABLE
13
+ from erad.models.hazard.base_models import BaseDisasterModel
14
+ from erad.quantities import Speed, Pressure
15
+
16
+
17
+ class WindModel(BaseDisasterModel):
18
+ timestamp: datetime
19
+ center: Point
20
+ max_wind_speed: Speed
21
+ radius_of_max_wind: Distance
22
+ radius_of_closest_isobar: Distance
23
+ air_pressure: Pressure
24
+
25
+ @field_validator("center", mode="before")
26
+ def deserialize_point(cls, value):
27
+ if isinstance(value, dict) and value.get("type") == "Point":
28
+ coords = value["coordinates"]
29
+ return Point(coords)
30
+ return value
31
+
32
+ @field_serializer("center")
33
+ def serialize_location(self, point: Point, _info):
34
+ return {"type": "Point", "coordinates": (point.x, point.y)}
35
+
36
+ @classmethod
37
+ def example(cls) -> "WindModel":
38
+ return WindModel(
39
+ name="hurricane 1",
40
+ timestamp=datetime.now(),
41
+ center=Point(-121.93036, 36.60144),
42
+ max_wind_speed=Speed(50, "miles/hour"),
43
+ air_pressure=Pressure(1013.25, "hPa"),
44
+ radius_of_max_wind=Distance(50, "miles"),
45
+ radius_of_closest_isobar=Distance(300, "miles"),
46
+ )
47
+
48
+ @classmethod
49
+ def from_hurricane_sid(cls, hurricane_sid: str) -> list["WindModel"]:
50
+ assert os.path.exists(ERAD_DB), f"The data file {ERAD_DB} not found"
51
+ conn = sqlite3.connect(ERAD_DB)
52
+ hurricane_data = pd.read_sql(
53
+ f"SELECT * FROM {HISTROIC_HURRICANE_TABLE} WHERE `SID ` = '{hurricane_sid}';", conn
54
+ )
55
+ cols = [
56
+ "LAT (degrees_north)",
57
+ "LON (degrees_east)",
58
+ "USA_WIND (kts)",
59
+ "USA_ROCI (nmile)",
60
+ "USA_RMW (nmile)",
61
+ "USA_POCI (mb)",
62
+ "ISO_TIME ",
63
+ ]
64
+ hurricane_data = hurricane_data[cols]
65
+ for col in cols:
66
+ hurricane_data = hurricane_data[hurricane_data[col] != " "]
67
+ if hurricane_data.empty:
68
+ raise ValueError(
69
+ f"Hurricane '{hurricane_sid}' not found in column 'SID', table '{HISTROIC_HURRICANE_TABLE}' in the database"
70
+ )
71
+ conn.close()
72
+ geometry = [
73
+ Point(lat, lon)
74
+ for lat, lon in zip(
75
+ hurricane_data["LAT (degrees_north)"], hurricane_data["LON (degrees_east)"]
76
+ )
77
+ ]
78
+ hurricane_data["ISO_TIME "] = pd.to_datetime(hurricane_data["ISO_TIME "])
79
+ hurricane_data = gpd.GeoDataFrame(hurricane_data, geometry=geometry)
80
+ hurricane_data.set_crs("epsg:4326")
81
+ track = []
82
+ for idx, row in hurricane_data.iterrows():
83
+ track.append(
84
+ WindModel(
85
+ name=hurricane_sid,
86
+ timestamp=row["ISO_TIME "],
87
+ center=row["geometry"],
88
+ max_wind_speed=Speed(float(row["USA_WIND (kts)"]), "knots"),
89
+ radius_of_max_wind=Distance(float(row["USA_RMW (nmile)"]), "nautical_mile"),
90
+ radius_of_closest_isobar=Distance(
91
+ float(row["USA_ROCI (nmile)"]), "nautical_mile"
92
+ ),
93
+ air_pressure=Pressure(float(row["USA_POCI (mb)"]), "millibar"),
94
+ )
95
+ )
96
+
97
+ return track
98
+
99
+ def plot(
100
+ self,
101
+ time_index: int = 0,
102
+ figure: go.Figure = go.Figure(),
103
+ map_obj: type[go.Scattergeo | go.Scattermap] = go.Scattermap,
104
+ ) -> int:
105
+ figure.add_trace(
106
+ map_obj(
107
+ lat=[self.center.x],
108
+ lon=[self.center.y],
109
+ mode="markers",
110
+ marker=dict(
111
+ size=[self.radius_of_closest_isobar.magnitude / 5],
112
+ color="lightblue",
113
+ opacity=0.4,
114
+ ),
115
+ name="Radius of closest isobar", # Name for the legend
116
+ visible=(time_index == 0),
117
+ )
118
+ )
119
+
120
+ figure.add_trace(
121
+ map_obj(
122
+ lat=[self.center.x],
123
+ lon=[self.center.y],
124
+ mode="markers",
125
+ marker=dict(
126
+ size=[self.radius_of_max_wind.magnitude / 5],
127
+ color=[self.max_wind_speed.magnitude],
128
+ showscale=False,
129
+ opacity=0.4,
130
+ ),
131
+ visible=(time_index == 0),
132
+ hovertext=[
133
+ f"""
134
+ <br> <b>Max wind speed:</b> {self.max_wind_speed}
135
+ <br> <b>Radius of max wind speed:</b> {self.radius_of_max_wind}
136
+ <br> <b>Radius of closest isobar:</b> {self.radius_of_closest_isobar}
137
+ <br> <b>Air pressure:</b> {self.air_pressure}
138
+ """
139
+ ],
140
+ name="Radius of max wind", # Name for the legend
141
+ )
142
+ )
143
+ return 2
@@ -0,0 +1,73 @@
1
+ from typing import Annotated
2
+
3
+ from gdm.quantities import Distance
4
+ from infrasys import Component
5
+ from pydantic import Field
6
+ from abc import ABC
7
+
8
+ from erad.quantities import Speed, Acceleration, Temperature
9
+
10
+
11
+ class BaseProbabilityModel(Component, ABC):
12
+ name: str = ""
13
+ survival_probability: Annotated[
14
+ float,
15
+ Field(1.0, ge=0, le=1, description="Asset survival probability"),
16
+ ]
17
+
18
+
19
+ class SpeedProbability(BaseProbabilityModel):
20
+ speed: Annotated[
21
+ Speed,
22
+ Field(
23
+ ...,
24
+ description="Represents the speed of a scenario parameter experienced by the asset e.g speed of wind",
25
+ ),
26
+ ]
27
+
28
+ @classmethod
29
+ def example(cls) -> "SpeedProbability":
30
+ return SpeedProbability(
31
+ speed=Speed(50, "m/s"),
32
+ survival_probability=1.0,
33
+ )
34
+
35
+
36
+ class TemperatureProbability(BaseProbabilityModel):
37
+ temperature: Annotated[
38
+ Temperature,
39
+ Field(..., description="Temperature of the asset"),
40
+ ]
41
+
42
+ @classmethod
43
+ def example(cls) -> "TemperatureProbability":
44
+ return TemperatureProbability(
45
+ temperature=Temperature(0, "degC"),
46
+ survival_probability=1.0,
47
+ )
48
+
49
+
50
+ class DistanceProbability(BaseProbabilityModel):
51
+ distance: Annotated[
52
+ Distance,
53
+ Field(..., description="Distance of asset from the source / boundary of a disaster event"),
54
+ ]
55
+
56
+ @classmethod
57
+ def example(cls) -> "DistanceProbability":
58
+ return DistanceProbability(
59
+ distance=Distance(0, "m"),
60
+ survival_probability=1.0,
61
+ )
62
+
63
+
64
+ class AccelerationProbability(BaseProbabilityModel):
65
+ name: str = ""
66
+ acceleration: Acceleration = Acceleration(0, "m/s**2")
67
+
68
+ @classmethod
69
+ def example(cls) -> "AccelerationProbability":
70
+ return AccelerationProbability(
71
+ acceleration=Acceleration(0, "m/s**2"),
72
+ survival_probability=1.0,
73
+ )
@@ -0,0 +1,35 @@
1
+ from infrasys import BaseQuantity
2
+ import scipy.stats as stats
3
+
4
+
5
+ class ProbabilityFunctionBuilder:
6
+ """Class containing utility fuctions for sceario definations."""
7
+
8
+ def __init__(self, dist, params: list[float | BaseQuantity]):
9
+ """Constructor for BaseScenario class.
10
+
11
+ Args:
12
+ dist (str): Name of teh distribution. Should follow Scipy naming convention
13
+ params (list): A list of parameters for the chosen distribution function. See Scipy.stats documentation
14
+ """
15
+ base_quantity = [p for p in params if isinstance(p, BaseQuantity)][0]
16
+ self.quantity = base_quantity.__class__
17
+ self.units = base_quantity.units
18
+ self.dist = getattr(stats, dist)
19
+ self.params = [p.magnitude if isinstance(p, BaseQuantity) else p for p in params]
20
+ return
21
+
22
+ def sample(self):
23
+ """Sample the distribution"""
24
+ return self.quantity(self.dist.rvs(*self.params, size=1)[0], self.units)
25
+
26
+ def probability(self, value: BaseQuantity) -> float:
27
+ """Calculates survival probability of a given asset.
28
+
29
+ Args:
30
+ value (float): value for vetor of interest. Will change with scenarions
31
+ """
32
+ assert isinstance(value, BaseQuantity), "Value must be a BaseQuantity"
33
+
34
+ cdf = self.dist.cdf
35
+ return cdf(value.to(self.units).magnitude, *self.params)
erad/quantities.py ADDED
@@ -0,0 +1,25 @@
1
+ from infrasys.base_quantity import BaseQuantity
2
+
3
+
4
+ class Speed(BaseQuantity):
5
+ """Quantity representing speed"""
6
+
7
+ __base_unit__ = "m/second"
8
+
9
+
10
+ class Acceleration(BaseQuantity):
11
+ """Quantity representing acceleration."""
12
+
13
+ __base_unit__ = "m/second**2"
14
+
15
+
16
+ class Temperature(BaseQuantity):
17
+ __base_unit__ = "degC"
18
+
19
+
20
+ class Pressure(BaseQuantity):
21
+ __base_unit__ = "millibar"
22
+
23
+
24
+ class Flow(BaseQuantity):
25
+ __base_unit__ = "feet**3/second"
erad/runner.py ADDED
@@ -0,0 +1,122 @@
1
+ from datetime import datetime
2
+
3
+ from gdm.distribution import DistributionSystem
4
+ from loguru import logger
5
+ import numpy as np
6
+
7
+ from gdm.tracked_changes import (
8
+ TrackedChange,
9
+ PropertyEdit,
10
+ )
11
+
12
+ from erad.default_fragility_curves import DEFAULT_FRAGILTY_CURVES
13
+ from erad.models.fragility_curve import HazardFragilityCurves
14
+ from erad.systems.hazard_system import HazardSystem
15
+ from erad.systems.asset_system import AssetSystem
16
+ from erad.constants import HAZARD_TYPES
17
+ from erad.models.asset import Asset
18
+
19
+
20
+ class HarzardSimulator:
21
+ def __init__(self, asset_system: AssetSystem):
22
+ self._asset_system = asset_system
23
+ self.assets: list[Asset] = list(asset_system.get_components(Asset))
24
+
25
+ @classmethod
26
+ def from_gdm(cls, dist_system: DistributionSystem) -> "HarzardSimulator":
27
+ """Create a HarzardSimulator from a DistributionSystem."""
28
+ asset_system = AssetSystem.from_gdm(dist_system)
29
+ return cls(asset_system)
30
+
31
+ @property
32
+ def asset_system(self) -> AssetSystem:
33
+ """Get the AssetSystem."""
34
+ return self._asset_system
35
+
36
+ def _get_time_stamps(self) -> list[datetime]:
37
+ timestamps = []
38
+ for model_type in HAZARD_TYPES:
39
+ for model in self.hazard_system.get_components(model_type):
40
+ timestamps.append(model.timestamp)
41
+ return sorted(timestamps)
42
+
43
+ def run(self, hazard_system: HazardSystem, curve_set: str = "DEFAULT_CURVES"):
44
+ probability_models = list(
45
+ hazard_system.get_components(
46
+ HazardFragilityCurves, filter_func=lambda x: x.name == curve_set
47
+ )
48
+ )
49
+
50
+ if not probability_models:
51
+ logger.warning(
52
+ "No HazardFragilityCurves definations found in the passed HazardSystem using default curve definations"
53
+ )
54
+ probability_models = DEFAULT_FRAGILTY_CURVES
55
+
56
+ self.hazard_system = hazard_system
57
+ self.timestamps = self._get_time_stamps()
58
+ for timestamp in self.timestamps:
59
+ for hazard_type in HAZARD_TYPES:
60
+ for hazard_model in self.hazard_system.get_components(
61
+ hazard_type, filter_func=lambda x: x.timestamp == timestamp
62
+ ):
63
+ for asset in self.assets:
64
+ asset.update_survival_probability(
65
+ timestamp, hazard_model, probability_models
66
+ )
67
+
68
+
69
+ class HazardScenarioGenerator:
70
+ def __init__(
71
+ self,
72
+ asset_system: AssetSystem,
73
+ hazard_system: HazardSystem,
74
+ curve_set: str = "DEFAULT_CURVES",
75
+ ):
76
+ self.assets = list(asset_system.iter_all_components())
77
+ self.harzard_simulator = HarzardSimulator(asset_system)
78
+ self.harzard_simulator.run(hazard_system, curve_set)
79
+
80
+ def _sample(self, scenario_name: str) -> list[TrackedChange]:
81
+ outaged_assets = []
82
+ tracked_changes = []
83
+
84
+ n_assets = len(self.assets)
85
+ n_timestamps = len(self.assets[0].asset_state)
86
+
87
+ ramdom_samples = np.random.random((n_assets, n_timestamps))
88
+
89
+ for ii, asset in enumerate(self.assets):
90
+ for jj, state in enumerate(
91
+ sorted(asset.asset_state, key=lambda asset_state: asset_state.timestamp)
92
+ ):
93
+ if (
94
+ ramdom_samples[ii, jj] > state.survival_probability
95
+ and asset.name not in outaged_assets
96
+ ):
97
+ tracked_changes.append(
98
+ TrackedChange(
99
+ scenario_name=scenario_name,
100
+ timestamp=state.timestamp,
101
+ edits=[
102
+ PropertyEdit(
103
+ component_uuid=asset.distribution_asset,
104
+ name="in_service",
105
+ value=False,
106
+ )
107
+ ],
108
+ ),
109
+ )
110
+ outaged_assets.append(asset.name)
111
+
112
+ return tracked_changes
113
+
114
+ def samples(self, number_of_samples: int = 1, seed: int = 0) -> list[TrackedChange]:
115
+ if number_of_samples < 1:
116
+ raise ValueError("number_of_samples should be a positive integer")
117
+ np.random.seed(seed)
118
+ tracked_changes = []
119
+ for i in range(number_of_samples):
120
+ scenario_name = f"sample_{i}"
121
+ tracked_changes.extend(self._sample(scenario_name))
122
+ return tracked_changes