NREL-erad 0.0.0a0__py3-none-any.whl → 0.1.1__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 (73) hide show
  1. erad/__init__.py +1 -0
  2. erad/constants.py +80 -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 +300 -0
  15. erad/models/asset_mapping.py +20 -0
  16. erad/models/edit_store.py +22 -0
  17. erad/models/fragility_curve.py +116 -0
  18. erad/models/hazard/__init__.py +5 -0
  19. erad/models/hazard/base_models.py +12 -0
  20. erad/models/hazard/common.py +26 -0
  21. erad/models/hazard/earthquake.py +93 -0
  22. erad/models/hazard/flood.py +83 -0
  23. erad/models/hazard/wild_fire.py +121 -0
  24. erad/models/hazard/wind.py +143 -0
  25. erad/models/probability.py +76 -0
  26. erad/probability_builder.py +38 -0
  27. erad/quantities.py +31 -0
  28. erad/runner.py +125 -0
  29. erad/systems/__init__.py +2 -0
  30. erad/systems/asset_system.py +462 -0
  31. erad/systems/hazard_system.py +122 -0
  32. nrel_erad-0.1.1.dist-info/METADATA +61 -0
  33. nrel_erad-0.1.1.dist-info/RECORD +36 -0
  34. {NREL_erad-0.0.0a0.dist-info → nrel_erad-0.1.1.dist-info}/WHEEL +1 -1
  35. NREL_erad-0.0.0a0.dist-info/METADATA +0 -61
  36. NREL_erad-0.0.0a0.dist-info/RECORD +0 -42
  37. erad/cypher_queries/load_data_v1.cypher +0 -212
  38. erad/data/World_Earthquakes_1960_2016.csv +0 -23410
  39. erad/db/__init__.py +0 -0
  40. erad/db/assets/__init__.py +0 -0
  41. erad/db/assets/critical_infras.py +0 -171
  42. erad/db/assets/distribution_lines.py +0 -101
  43. erad/db/credential_model.py +0 -20
  44. erad/db/disaster_input_model.py +0 -23
  45. erad/db/inject_earthquake.py +0 -52
  46. erad/db/inject_flooding.py +0 -53
  47. erad/db/neo4j_.py +0 -162
  48. erad/db/utils.py +0 -14
  49. erad/exceptions.py +0 -68
  50. erad/metrics/__init__.py +0 -0
  51. erad/metrics/check_microgrid.py +0 -208
  52. erad/metrics/metric.py +0 -178
  53. erad/programs/__init__.py +0 -0
  54. erad/programs/backup.py +0 -62
  55. erad/programs/microgrid.py +0 -45
  56. erad/scenarios/__init__.py +0 -0
  57. erad/scenarios/abstract_scenario.py +0 -103
  58. erad/scenarios/common.py +0 -93
  59. erad/scenarios/earthquake_scenario.py +0 -161
  60. erad/scenarios/fire_scenario.py +0 -160
  61. erad/scenarios/flood_scenario.py +0 -494
  62. erad/scenarios/utilities.py +0 -76
  63. erad/scenarios/wind_scenario.py +0 -89
  64. erad/utils/__init__.py +0 -0
  65. erad/utils/ditto_utils.py +0 -252
  66. erad/utils/hifld_utils.py +0 -147
  67. erad/utils/opendss_utils.py +0 -357
  68. erad/utils/overpass.py +0 -76
  69. erad/utils/util.py +0 -178
  70. erad/visualization/__init__.py +0 -0
  71. erad/visualization/plot_graph.py +0 -218
  72. {NREL_erad-0.0.0a0.dist-info → nrel_erad-0.1.1.dist-info/licenses}/LICENSE.txt +0 -0
  73. {NREL_erad-0.0.0a0.dist-info → nrel_erad-0.1.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,94 @@
1
+ import erad.models.fragility_curve as frag
2
+ from erad.enums import AssetTypes
3
+ from erad.quantities import Speed
4
+
5
+ DEFAULT_PEAK_GROUND_VELOCITY_FRAGILITY_CURVES = frag.HazardFragilityCurves(
6
+ asset_state_param="peak_ground_velocity",
7
+ curves=[
8
+ frag.FragilityCurve(
9
+ asset_type=AssetTypes.switch,
10
+ prob_function=frag.ProbabilityFunction(
11
+ distribution="lognorm", parameters=[Speed(0.5, "cm/s"), Speed(35, "cm/s"), 2]
12
+ ),
13
+ ),
14
+ frag.FragilityCurve(
15
+ asset_type=AssetTypes.battery_storage,
16
+ prob_function=frag.ProbabilityFunction(
17
+ distribution="lognorm", parameters=[Speed(0.5, "cm/s"), Speed(35, "cm/s"), 2]
18
+ ),
19
+ ),
20
+ frag.FragilityCurve(
21
+ asset_type=AssetTypes.distribution_junction_box,
22
+ prob_function=frag.ProbabilityFunction(
23
+ distribution="lognorm", parameters=[Speed(0.5, "cm/s"), Speed(35, "cm/s"), 2]
24
+ ),
25
+ ),
26
+ frag.FragilityCurve(
27
+ asset_type=AssetTypes.distribution_overhead_lines,
28
+ prob_function=frag.ProbabilityFunction(
29
+ distribution="lognorm", parameters=[Speed(0.5, "cm/s"), Speed(40, "cm/s"), 2]
30
+ ),
31
+ ),
32
+ frag.FragilityCurve(
33
+ asset_type=AssetTypes.distribution_poles,
34
+ prob_function=frag.ProbabilityFunction(
35
+ distribution="lognorm", parameters=[Speed(0.5, "cm/s"), Speed(40, "cm/s"), 2]
36
+ ),
37
+ ),
38
+ frag.FragilityCurve(
39
+ asset_type=AssetTypes.distribution_underground_cables,
40
+ prob_function=frag.ProbabilityFunction(
41
+ distribution="lognorm", parameters=[Speed(0.5, "cm/s"), Speed(60, "cm/s"), 2]
42
+ ),
43
+ ),
44
+ frag.FragilityCurve(
45
+ asset_type=AssetTypes.solar_panels,
46
+ prob_function=frag.ProbabilityFunction(
47
+ distribution="lognorm", parameters=[Speed(0.5, "cm/s"), Speed(35, "cm/s"), 2]
48
+ ),
49
+ ),
50
+ frag.FragilityCurve(
51
+ asset_type=AssetTypes.substation,
52
+ prob_function=frag.ProbabilityFunction(
53
+ distribution="lognorm", parameters=[Speed(0.5, "cm/s"), Speed(50, "cm/s"), 2]
54
+ ),
55
+ ),
56
+ frag.FragilityCurve(
57
+ asset_type=AssetTypes.transformer_mad_mount,
58
+ prob_function=frag.ProbabilityFunction(
59
+ distribution="lognorm", parameters=[Speed(0.5, "cm/s"), Speed(35, "cm/s"), 2]
60
+ ),
61
+ ),
62
+ frag.FragilityCurve(
63
+ asset_type=AssetTypes.transformer_pole_mount,
64
+ prob_function=frag.ProbabilityFunction(
65
+ distribution="lognorm", parameters=[Speed(0.5, "cm/s"), Speed(40, "cm/s"), 2]
66
+ ),
67
+ ),
68
+ frag.FragilityCurve(
69
+ asset_type=AssetTypes.transmission_junction_box,
70
+ prob_function=frag.ProbabilityFunction(
71
+ distribution="lognorm", parameters=[Speed(0.5, "cm/s"), Speed(50, "cm/s"), 2]
72
+ ),
73
+ ),
74
+ frag.FragilityCurve(
75
+ asset_type=AssetTypes.transmission_overhead_lines,
76
+ prob_function=frag.ProbabilityFunction(
77
+ distribution="lognorm", parameters=[Speed(0.5, "cm/s"), Speed(45, "cm/s"), 2]
78
+ ),
79
+ ),
80
+ frag.FragilityCurve(
81
+ asset_type=AssetTypes.transmission_tower,
82
+ prob_function=frag.ProbabilityFunction(
83
+ distribution="lognorm", parameters=[Speed(0.5, "cm/s"), Speed(35, "cm/s"), 2]
84
+ ),
85
+ ),
86
+ frag.FragilityCurve(
87
+ asset_type=AssetTypes.transmission_underground_cables,
88
+ prob_function=frag.ProbabilityFunction(
89
+ distribution="lognorm",
90
+ parameters=[Speed(0.55, "cm/s"), Speed(65, "cm/s"), 1 / 0.55],
91
+ ),
92
+ ),
93
+ ],
94
+ )
@@ -0,0 +1,94 @@
1
+ import erad.models.fragility_curve as frag
2
+ from erad.enums import AssetTypes
3
+ from erad.quantities import Speed
4
+
5
+
6
+ DEFAULT_WIND_SPEED_FRAGILITY_CURVES = frag.HazardFragilityCurves(
7
+ asset_state_param="wind_speed",
8
+ curves=[
9
+ frag.FragilityCurve(
10
+ asset_type=AssetTypes.switch,
11
+ prob_function=frag.ProbabilityFunction(
12
+ distribution="lognorm", parameters=[Speed(0.45, "m/s"), Speed(50, "m/s"), 1 / 0.45]
13
+ ),
14
+ ),
15
+ frag.FragilityCurve(
16
+ asset_type=AssetTypes.battery_storage,
17
+ prob_function=frag.ProbabilityFunction(
18
+ distribution="lognorm", parameters=[Speed(0.45, "m/s"), Speed(50, "m/s"), 1 / 0.45]
19
+ ),
20
+ ),
21
+ frag.FragilityCurve(
22
+ asset_type=AssetTypes.distribution_junction_box,
23
+ prob_function=frag.ProbabilityFunction(
24
+ distribution="lognorm", parameters=[Speed(0.4, "m/s"), Speed(50, "m/s"), 1 / 0.4]
25
+ ),
26
+ ),
27
+ frag.FragilityCurve(
28
+ asset_type=AssetTypes.distribution_overhead_lines,
29
+ prob_function=frag.ProbabilityFunction(
30
+ distribution="lognorm", parameters=[Speed(0.35, "m/s"), Speed(45, "m/s"), 1 / 0.35]
31
+ ),
32
+ ),
33
+ frag.FragilityCurve(
34
+ asset_type=AssetTypes.distribution_poles,
35
+ prob_function=frag.ProbabilityFunction(
36
+ distribution="lognorm", parameters=[Speed(0.4, "m/s"), Speed(45, "m/s"), 1 / 0.4]
37
+ ),
38
+ ),
39
+ frag.FragilityCurve(
40
+ asset_type=AssetTypes.distribution_underground_cables,
41
+ prob_function=frag.ProbabilityFunction(
42
+ distribution="lognorm", parameters=[Speed(0.4, "m/s"), Speed(55, "m/s"), 1 / 0.4]
43
+ ),
44
+ ),
45
+ frag.FragilityCurve(
46
+ asset_type=AssetTypes.solar_panels,
47
+ prob_function=frag.ProbabilityFunction(
48
+ distribution="lognorm", parameters=[Speed(0.3, "m/s"), Speed(55, "m/s"), 1 / 0.3]
49
+ ),
50
+ ),
51
+ frag.FragilityCurve(
52
+ asset_type=AssetTypes.substation,
53
+ prob_function=frag.ProbabilityFunction(
54
+ distribution="lognorm", parameters=[Speed(0.4, "m/s"), Speed(55, "m/s"), 1 / 0.40]
55
+ ),
56
+ ),
57
+ frag.FragilityCurve(
58
+ asset_type=AssetTypes.transformer_mad_mount,
59
+ prob_function=frag.ProbabilityFunction(
60
+ distribution="lognorm", parameters=[Speed(0.35, "m/s"), Speed(50, "m/s"), 1 / 0.35]
61
+ ),
62
+ ),
63
+ frag.FragilityCurve(
64
+ asset_type=AssetTypes.transformer_pole_mount,
65
+ prob_function=frag.ProbabilityFunction(
66
+ distribution="lognorm", parameters=[Speed(0.3, "m/s"), Speed(45, "m/s"), 1 / 0.30]
67
+ ),
68
+ ),
69
+ frag.FragilityCurve(
70
+ asset_type=AssetTypes.transmission_junction_box,
71
+ prob_function=frag.ProbabilityFunction(
72
+ distribution="lognorm", parameters=[Speed(0.54, "m/s"), Speed(55, "m/s"), 1 / 0.54]
73
+ ),
74
+ ),
75
+ frag.FragilityCurve(
76
+ asset_type=AssetTypes.transmission_overhead_lines,
77
+ prob_function=frag.ProbabilityFunction(
78
+ distribution="lognorm", parameters=[Speed(0.35, "m/s"), Speed(50, "m/s"), 1 / 0.35]
79
+ ),
80
+ ),
81
+ frag.FragilityCurve(
82
+ asset_type=AssetTypes.transmission_tower,
83
+ prob_function=frag.ProbabilityFunction(
84
+ distribution="lognorm", parameters=[Speed(0.4, "m/s"), Speed(55, "m/s"), 1 / 0.40]
85
+ ),
86
+ ),
87
+ frag.FragilityCurve(
88
+ asset_type=AssetTypes.transmission_underground_cables,
89
+ prob_function=frag.ProbabilityFunction(
90
+ distribution="lognorm", parameters=[Speed(0.4, "m/s"), Speed(60, "m/s"), 1 / 0.40]
91
+ ),
92
+ ),
93
+ ],
94
+ )
erad/enums.py ADDED
@@ -0,0 +1,40 @@
1
+ from enum import IntEnum
2
+
3
+
4
+ class ScenarioTypes(IntEnum):
5
+ flood_m = 0
6
+ wind_m_per_s = 1
7
+ fire_m = 2
8
+ earthquake_pga = 3
9
+
10
+
11
+ class NodeTypes(IntEnum):
12
+ transmission_tower = 5
13
+ distribution_poles = 6
14
+ transmission_junction_box = 11
15
+ distribution_junction_box = 12
16
+
17
+
18
+ class AssetTypes(IntEnum):
19
+ substation = 0
20
+ solar_panels = 1
21
+ distribution_underground_cables = 2
22
+ transmission_underground_cables = 3
23
+ battery_storage = 4
24
+ transmission_tower = 5
25
+ distribution_poles = 6
26
+ transmission_overhead_lines = 7
27
+ distribution_overhead_lines = 8
28
+ transformer_mad_mount = 9
29
+ transformer_pole_mount = 10
30
+ transmission_junction_box = 11
31
+ distribution_junction_box = 12
32
+ switch = 13
33
+
34
+ @classmethod
35
+ def has_value(cls, value):
36
+ return value in cls._value2member_map_
37
+
38
+ @classmethod
39
+ def has_asset(cls, asset):
40
+ return asset in cls.__members__
erad/gdm_mapping.py ADDED
@@ -0,0 +1,83 @@
1
+ import gdm.distribution.components as gdc
2
+ import gdm.distribution.equipment as gde
3
+
4
+ from erad.enums import AssetTypes
5
+ from erad.models.asset_mapping import ComponentFilterModel
6
+
7
+
8
+ asset_to_gdm_mapping = {
9
+ AssetTypes.switch: [
10
+ ComponentFilterModel(
11
+ component_type=gdc.DistributionSwitchBase,
12
+ ),
13
+ ],
14
+ AssetTypes.switch: [
15
+ ComponentFilterModel(
16
+ component_type=gdc.DistributionSwitchBase,
17
+ ),
18
+ ],
19
+ AssetTypes.substation: [
20
+ ComponentFilterModel(
21
+ component_type=gdc.DistributionVoltageSource,
22
+ ),
23
+ ],
24
+ AssetTypes.solar_panels: [
25
+ ComponentFilterModel(
26
+ component_type=gdc.DistributionSolar,
27
+ ),
28
+ ],
29
+ AssetTypes.battery_storage: [
30
+ ComponentFilterModel(
31
+ component_type=gdc.DistributionBattery,
32
+ ),
33
+ ],
34
+ AssetTypes.distribution_underground_cables: [
35
+ ComponentFilterModel(
36
+ component_type=gdc.GeometryBranch,
37
+ component_filter=lambda x: x.equipment.conductors[0].__class__
38
+ == gde.ConcentricCableEquipment
39
+ and x.buses[0].rated_voltage.to("kilovolt").magnitude < 35.0,
40
+ ),
41
+ ComponentFilterModel(
42
+ component_type=gdc.MatrixImpedanceBranch,
43
+ component_filter=lambda x: x.equipment.c_matrix[0, 0]
44
+ .to("microfarad/kilometer")
45
+ .magnitude
46
+ > 0.05
47
+ and x.buses[0].rated_voltage.to("kilovolt").magnitude < 35.0,
48
+ ),
49
+ ],
50
+ AssetTypes.distribution_overhead_lines: [
51
+ ComponentFilterModel(
52
+ component_type=gdc.GeometryBranch,
53
+ component_filter=lambda x: (
54
+ x.equipment.conductors[0].__class__ == gde.BareConductorEquipment
55
+ )
56
+ and x.buses[0].rated_voltage.to("kilovolt").magnitude < 35.0,
57
+ ),
58
+ ComponentFilterModel(
59
+ component_type=gdc.MatrixImpedanceBranch,
60
+ component_filter=lambda x: x.equipment.c_matrix[0, 0]
61
+ .to("microfarad/kilometer")
62
+ .magnitude
63
+ < 0.05
64
+ and x.buses[0].rated_voltage.to("kilovolt").magnitude < 35.0,
65
+ ),
66
+ ],
67
+ AssetTypes.transmission_underground_cables: [
68
+ ComponentFilterModel(
69
+ component_type=gdc.GeometryBranch,
70
+ component_filter=lambda x: x.equipment.conductors[0].__class__
71
+ == gde.ConcentricCableEquipment
72
+ and x.buses[0].rated_voltage.to("kilovolt").magnitude > 35.0,
73
+ ),
74
+ ],
75
+ AssetTypes.transmission_overhead_lines: [
76
+ ComponentFilterModel(
77
+ component_type=gdc.GeometryBranch,
78
+ component_filter=lambda x: x.equipment.conductors[0].__class__
79
+ == gde.BareConductorEquipment
80
+ and x.buses[0].rated_voltage.to("kilovolt").magnitude > 35.0,
81
+ ),
82
+ ],
83
+ }
@@ -0,0 +1 @@
1
+
erad/models/asset.py ADDED
@@ -0,0 +1,300 @@
1
+ from datetime import datetime
2
+ from uuid import UUID
3
+ import math
4
+
5
+ from pydantic import computed_field, Field
6
+ from infrasys.quantities import Distance
7
+ from geopy.distance import geodesic
8
+ from shapely.geometry import Point
9
+ from pyhigh import get_elevation
10
+ from infrasys import Component
11
+ from loguru import logger
12
+
13
+ # from erad.constants import RASTER_DOWNLOAD_PATH
14
+ from erad.quantities import Acceleration, Speed
15
+ from erad.models.probability import (
16
+ AccelerationProbability,
17
+ DistanceProbability,
18
+ SpeedProbability,
19
+ )
20
+ from erad.enums import AssetTypes
21
+ import erad.models.hazard as hz
22
+
23
+
24
+ class AssetState(Component):
25
+ name: str = ""
26
+ timestamp: datetime
27
+ wind_speed: SpeedProbability | None = None
28
+ flood_velocity: SpeedProbability | None = None
29
+ flood_depth: DistanceProbability | None = None
30
+ fire_boundary_dist: DistanceProbability | None = None
31
+ peak_ground_velocity: SpeedProbability | None = None
32
+ peak_ground_acceleration: AccelerationProbability | None = None
33
+
34
+ def calculate_earthquake_vectors(
35
+ self, asset_coordinate: Point, hazard_model: hz.EarthQuakeModel
36
+ ):
37
+ """
38
+ Sources:
39
+
40
+ Atkinson, G.M., & Wald, D.J. (2007). “Did You Feel It?” intensity data: A surprisingly good
41
+ measure of earthquake ground motion. Seismological Research Letters, 78(3), 362–368.
42
+ """
43
+
44
+ epicenter_distance = Distance(
45
+ geodesic(
46
+ (hazard_model.origin.y, hazard_model.origin.x),
47
+ (asset_coordinate.y, asset_coordinate.x),
48
+ ).km,
49
+ "km",
50
+ )
51
+
52
+ hypocentral_distance = (hazard_model.depth**2 + epicenter_distance**2) ** 0.5
53
+
54
+ mmi = (
55
+ 3.31
56
+ + 1.28 * hazard_model.magnitude
57
+ - 1.42 * math.log10(hypocentral_distance.to("km").magnitude)
58
+ ) # Modified Mercalli Intensity
59
+
60
+ log_pgv = (mmi - 3.78) / 1.47
61
+ pgv = 10**log_pgv # cm/s
62
+ self.peak_ground_velocity = SpeedProbability(
63
+ speed=Speed(pgv, "centimeter/second"),
64
+ survival_probability=1,
65
+ )
66
+
67
+ log_pga = (mmi - 1.78) / 3.70
68
+ pga_per_g = 10**log_pga # % of g
69
+ self.peak_ground_acceleration = AccelerationProbability(
70
+ acceleration=Acceleration(pga_per_g / 100.0 * 9.80665, "meter/second**2"),
71
+ survival_probability=1,
72
+ )
73
+
74
+ def calculate_fire_vectors(self, asset_coordinate: Point, hazard_model: hz.FireModel):
75
+ """
76
+ Sources:
77
+
78
+ “Integrating Wildfire Risk Modeling with Electric Grid Asset Management” (EPRI)
79
+ “Modeling and Mitigating Wildfire Risk to the Power Grid” (PNNL, 2021)
80
+ “A Framework for Assessing Wildfire Ignition Risk on Distribution Systems”
81
+ (IEEE Transactions on Power Systems, 2020)
82
+ Improving Grid Safety and Resilience to Mitigate Ignition Incident and Fire Risks – EPRI
83
+ Wildfire Risk Reduction Methods 2024 – EPRI
84
+ """
85
+
86
+ distances_to_wild_fire_boundaries = []
87
+ for wild_fire_area in hazard_model.affected_areas:
88
+ if wild_fire_area.affected_area.contains(asset_coordinate):
89
+ distance = 0
90
+ else:
91
+ distance = wild_fire_area.affected_area.exterior.distance(asset_coordinate)
92
+ distances_to_wild_fire_boundaries.append(distance)
93
+ minimum_distance_km = min(distances_to_wild_fire_boundaries)
94
+ self.fire_boundary_dist = DistanceProbability(
95
+ distance=Distance(minimum_distance_km, "kilometer"),
96
+ survival_probability=1,
97
+ )
98
+
99
+ # survival_probability = 1 - 0.95 * math.exp(-k * minimum_distance_km)
100
+
101
+ def calculate_wind_vectors(self, asset_coordinate: Point, hazard_model: hz.WindModel):
102
+ r = Distance(
103
+ geodesic(
104
+ (hazard_model.center.y, hazard_model.center.x),
105
+ (asset_coordinate.y, asset_coordinate.x),
106
+ ).km,
107
+ "km",
108
+ )
109
+
110
+ r = r.to("nautical_mile")
111
+ r_max = hazard_model.radius_of_closest_isobar.to("nautical_mile")
112
+ r_v_max = hazard_model.radius_of_max_wind.to("nautical_mile")
113
+ v_max = hazard_model.max_wind_speed.to("knot")
114
+
115
+ k = 1.14
116
+ b = 10
117
+
118
+ a = math.log(b) / (r_max - r_v_max)
119
+ m = (1 / r_v_max) * math.log(k / (k + 1))
120
+
121
+ if r >= 0 and r < r_v_max:
122
+ wind_speed = k * v_max * (1 - math.exp(-m * r))
123
+ elif r >= r_v_max and r < r_max:
124
+ wind_speed = v_max * math.exp(-a * (r - r_v_max))
125
+ else:
126
+ wind_speed = 0
127
+
128
+ self.wind_speed = SpeedProbability(
129
+ speed=wind_speed.to("miles/hour"),
130
+ survival_probability=1,
131
+ )
132
+
133
+ def calculate_flood_vectors(
134
+ self, asset_coordinate: Point, hazard_model: hz.FloodModel, asset_total_elevation: Distance
135
+ ):
136
+ for area in hazard_model.affected_areas:
137
+ if area.affected_area.contains(asset_coordinate):
138
+ self.flood_depth = DistanceProbability(
139
+ distance=area.water_elevation - asset_total_elevation,
140
+ survival_probability=1,
141
+ )
142
+ self.flood_velocity = SpeedProbability(
143
+ speed=area.water_velocity,
144
+ survival_probability=1,
145
+ )
146
+
147
+ if self.flood_depth is None and self.flood_velocity is None:
148
+ self.flood_depth = DistanceProbability(
149
+ distance=Distance(-9999, "meter"),
150
+ survival_probability=1,
151
+ )
152
+ self.flood_velocity = SpeedProbability(
153
+ speed=Speed(0, "meter / second"),
154
+ survival_probability=1,
155
+ )
156
+
157
+ @computed_field
158
+ @property
159
+ def survival_probability(self) -> float:
160
+ wind_survival_probability = self.wind_speed.survival_probability if self.wind_speed else 1
161
+ water_flow_survival_probability = (
162
+ self.flood_velocity.survival_probability if self.flood_velocity else 1
163
+ )
164
+ water_level_survivial_probability = (
165
+ self.flood_depth.survival_probability if self.flood_depth else 1
166
+ )
167
+ fire_survival_probability = (
168
+ self.fire_boundary_dist.survival_probability if self.fire_boundary_dist else 1
169
+ )
170
+ pgv_survival_probability = (
171
+ self.peak_ground_velocity.survival_probability if self.peak_ground_velocity else 1
172
+ )
173
+ pga_survival_probability = (
174
+ self.peak_ground_acceleration.survival_probability
175
+ if self.peak_ground_acceleration
176
+ else 1
177
+ )
178
+
179
+ return (
180
+ wind_survival_probability
181
+ * water_flow_survival_probability
182
+ * water_level_survivial_probability
183
+ * fire_survival_probability
184
+ * pgv_survival_probability
185
+ * pga_survival_probability
186
+ )
187
+
188
+ @classmethod
189
+ def example(cls) -> "AssetState":
190
+ return AssetState(
191
+ timestamp=datetime.now(),
192
+ wind_speed=SpeedProbability.example(),
193
+ flood_velocity=SpeedProbability.example(),
194
+ flood_depth=DistanceProbability.example(),
195
+ fire_boundary_dist=DistanceProbability.example(),
196
+ peak_ground_velocity=SpeedProbability.example(),
197
+ peak_ground_acceleration=AccelerationProbability.example(),
198
+ )
199
+
200
+
201
+ class Asset(Component):
202
+ distribution_asset: UUID = Field(..., description="UUID of the distribution asset")
203
+ connections: list[UUID] = Field([], description="List of UUIDs of connected assets")
204
+ devices: list[UUID] = Field(
205
+ [], description="List of UUIDs of devices associated with the asset"
206
+ )
207
+ asset_type: AssetTypes = Field(..., description="Type of the asset")
208
+ height: Distance = Field(..., description="Height of the asset")
209
+ latitude: float = Field(..., ge=-90, le=90, description="Latitude in degrees")
210
+ longitude: float = Field(..., ge=-180, le=180, description="Longitude in degrees")
211
+ asset_state: list[AssetState] = Field(
212
+ ..., description="List of asset states associated with the asset"
213
+ )
214
+ _raster_handler: str | None = None
215
+
216
+ @computed_field
217
+ @property
218
+ def elevation(self) -> Distance:
219
+ try:
220
+ elev = Distance(get_elevation(lat=self.latitude, lon=self.longitude), "meter")
221
+ except Exception:
222
+ logger.warning(
223
+ f"Error getting elevation information for asset {self.name} with coordinates {self.latitude}, {self.longitude}. Defaulting to 999 meters"
224
+ )
225
+ elev = Distance(999, "meter")
226
+ return elev
227
+
228
+ def _get_asset_state_at_timestamp(self, timestamp: datetime) -> AssetState | None:
229
+ for asset_state in self.asset_state:
230
+ if asset_state.timestamp == timestamp:
231
+ return asset_state
232
+ return AssetState(
233
+ timestamp=timestamp,
234
+ )
235
+
236
+ def update_survival_probability(
237
+ self, time_stamp: datetime, hazard_model: hz.BaseDisasterModel, frag_curves
238
+ ):
239
+ asset_state = self._get_asset_state_at_timestamp(time_stamp)
240
+ asset_location = Point(self.longitude, self.latitude)
241
+
242
+ if isinstance(hazard_model, hz.EarthQuakeModel):
243
+ asset_state.calculate_earthquake_vectors(asset_location, hazard_model)
244
+ elif isinstance(hazard_model, hz.FireModel):
245
+ asset_state.calculate_fire_vectors(asset_location, hazard_model)
246
+ elif isinstance(hazard_model, hz.WindModel):
247
+ asset_state.calculate_wind_vectors(asset_location, hazard_model)
248
+ elif isinstance(hazard_model, hz.FloodModel):
249
+ asset_state.calculate_flood_vectors(
250
+ asset_location, hazard_model, self.elevation + self.height
251
+ )
252
+ else:
253
+ raise (f"Unsupported hazard type {hazard_model.__class__.__name__}")
254
+
255
+ self.calculate_probabilities(asset_state, frag_curves)
256
+ if asset_state not in self.asset_state:
257
+ self.asset_state.append(asset_state)
258
+ return asset_state
259
+
260
+ def calculate_probabilities(self, asset_state: AssetState, frag_curves):
261
+ fields = [
262
+ model_field
263
+ for model_field in list(asset_state.model_fields.keys())
264
+ if model_field not in ["uuid", "name", "timestamp"]
265
+ ]
266
+ for field in fields:
267
+ prob_model = getattr(asset_state, field)
268
+ if prob_model and prob_model.survival_probability == 1:
269
+ curve = self.get_vadid_curve(frag_curves, field)
270
+ if curve is None:
271
+ raise Exception(f"No fragility curve found for field - {field}")
272
+ prob_inst = curve.prob_model
273
+ quantity_name = [
274
+ model_field
275
+ for model_field in list(prob_model.model_fields.keys())
276
+ if model_field not in ["uuid", "name", "survival_probability"]
277
+ ][0]
278
+ quantity = getattr(prob_model, quantity_name)
279
+ prob_model.survival_probability = 1 - prob_inst.probability(quantity)
280
+
281
+ def get_vadid_curve(self, frag_curves, field: SyntaxError) -> float:
282
+ for frag_curve in frag_curves:
283
+ if frag_curve.asset_state_param == field:
284
+ for curve in frag_curve.curves:
285
+ if curve.asset_type == self.asset_type:
286
+ return curve.prob_function
287
+
288
+ return None
289
+
290
+ @classmethod
291
+ def example(cls) -> "Asset":
292
+ return Asset(
293
+ name="Asset 1",
294
+ asset_type=AssetTypes.distribution_poles,
295
+ distribution_asset=UUID("123e4567-e89b-12d3-a456-426614174000"),
296
+ height=Distance(100, "m"),
297
+ latitude=37.7749,
298
+ longitude=-122.4194,
299
+ asset_state=[AssetState.example()],
300
+ )
@@ -0,0 +1,20 @@
1
+ from typing import Type, Callable
2
+
3
+ from pydantic import BaseModel, ConfigDict
4
+ from infrasys import Component
5
+
6
+ from erad.enums import AssetTypes
7
+
8
+
9
+ class BaseEradScenarioModel(BaseModel):
10
+ model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid")
11
+
12
+
13
+ class ComponentFilterModel(BaseEradScenarioModel):
14
+ component_type: Type[Component]
15
+ component_filter: Callable | None = None
16
+
17
+
18
+ class AssetComponentMap(BaseEradScenarioModel):
19
+ asset_type: AssetTypes
20
+ filters: list[ComponentFilterModel] = []
@@ -0,0 +1,22 @@
1
+ from pathlib import Path
2
+ import json
3
+
4
+ from pydantic import BaseModel
5
+
6
+ from gdm.tracked_changes import TrackedChange
7
+
8
+
9
+ class EditStore(BaseModel):
10
+ updates: list[TrackedChange] = []
11
+
12
+ def to_json(self, filename: Path):
13
+ filename = Path(filename)
14
+ with open(filename, "w") as f:
15
+ f.write(self.model_dump_json(indent=4))
16
+
17
+ @classmethod
18
+ def from_json(cls, filename: Path):
19
+ filename = Path(filename)
20
+ with open(filename) as f:
21
+ data = json.load(f)
22
+ return EditStore(**data)