irie 0.0.5__py3-none-any.whl → 0.0.6__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.

Potentially problematic release.


This version of irie might be problematic. Click here for more details.

Files changed (61) hide show
  1. irie/apps/config.py +0 -1
  2. irie/apps/evaluation/identification.py +1 -1
  3. irie/apps/evaluation/models.py +3 -3
  4. irie/apps/evaluation/views.py +3 -3
  5. irie/apps/events/admin.py +2 -2
  6. irie/apps/events/migrations/0002_rename_event_eventrecord.py +19 -0
  7. irie/apps/events/migrations/0003_hazardevent.py +21 -0
  8. irie/apps/events/models.py +55 -5
  9. irie/apps/events/views.py +48 -3
  10. irie/apps/events/views_events.py +6 -10
  11. irie/apps/inventory/filters.py +37 -0
  12. irie/apps/inventory/models.py +7 -0
  13. irie/apps/inventory/urls.py +1 -0
  14. irie/apps/inventory/views.py +134 -227
  15. irie/apps/prediction/forms.py +4 -8
  16. irie/apps/prediction/metrics.py +0 -2
  17. irie/apps/prediction/migrations/0002_alter_predictormodel_protocol.py +18 -0
  18. irie/apps/prediction/models.py +4 -4
  19. irie/apps/prediction/predictor.py +18 -12
  20. irie/apps/prediction/runners/__init__.py +3 -398
  21. irie/apps/prediction/runners/hazus.py +579 -0
  22. irie/apps/prediction/runners/opensees/__init__.py +395 -0
  23. irie/apps/prediction/runners/{utilities.py → opensees/utilities.py} +7 -7
  24. irie/apps/prediction/runners/ssid.py +414 -0
  25. irie/apps/prediction/urls.py +1 -1
  26. irie/apps/prediction/views.py +45 -22
  27. irie/apps/site/view_sdof.py +2 -2
  28. irie/apps/templates/admin/base_site.html +3 -1
  29. irie/apps/templates/css/admin-extra.css +7 -0
  30. irie/apps/templates/includes/sidebar.html +17 -14
  31. irie/apps/templates/inventory/asset-event-summary.html +3 -2
  32. irie/apps/templates/inventory/asset-profile.html +126 -38
  33. irie/apps/templates/inventory/asset-table.html +191 -135
  34. irie/apps/templates/inventory/dashboard.html +105 -27
  35. irie/apps/templates/inventory/preamble.tex +131 -0
  36. irie/apps/templates/inventory/report.tex +59 -0
  37. irie/apps/templates/networks/corridor_table.html +2 -2
  38. irie/apps/templates/networks/networks.html +164 -0
  39. irie/apps/templates/prediction/asset-predictors.html +6 -6
  40. irie/apps/templates/prediction/form-submission.html +3 -3
  41. irie/apps/templates/prediction/hazus/event.html +33 -0
  42. irie/apps/templates/prediction/hazus/history.html +1 -0
  43. irie/apps/templates/prediction/hazus/history.js +44 -0
  44. irie/apps/templates/prediction/{new-predictor.html → new-runner.html} +12 -8
  45. irie/apps/templates/site/index.html +29 -47
  46. irie/core/urls.py +7 -2
  47. irie/init/__main__.py +2 -0
  48. irie/init/bridges.py +5 -3
  49. irie/init/management/commands/init_assets.py +24 -45
  50. irie/init/management/commands/init_corridors.py +3 -6
  51. irie/init/management/commands/init_predictors.py +23 -8
  52. irie/post/__main__.py +88 -0
  53. {irie-0.0.5.dist-info → irie-0.0.6.dist-info}/METADATA +5 -3
  54. {irie-0.0.5.dist-info → irie-0.0.6.dist-info}/RECORD +61 -47
  55. /irie/apps/prediction/runners/{metrics.py → opensees/metrics.py} +0 -0
  56. /irie/apps/prediction/runners/{xmlutils.py → opensees/xmlutils.py} +0 -0
  57. /irie/apps/prediction/runners/{zipped.py → opensees/zipped.py} +0 -0
  58. /irie/init/data/{04.tar → nbi/04.tar} +0 -0
  59. {irie-0.0.5.dist-info → irie-0.0.6.dist-info}/WHEEL +0 -0
  60. {irie-0.0.5.dist-info → irie-0.0.6.dist-info}/entry_points.txt +0 -0
  61. {irie-0.0.5.dist-info → irie-0.0.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,579 @@
1
+ """
2
+ From [1] Section 7.1.3 and [2] Section 9.1:
3
+ Bridges are classified based on the following structural characteristics:
4
+ - Seismic Design
5
+ - Number of spans: single vs. multiple span bridges
6
+ - Structure type: concrete, steel, and others
7
+ - Pier type: multiple column bents, single column bents, and pier walls
8
+ - Abutment type and bearing type: monolithic vs. non-monolithic, high rocker bearings, low
9
+ steel bearings, and neoprene rubber bearings
10
+ - Span continuity: continuous, discontinuous (in-span hinges), and simply supported
11
+
12
+ The seismic design of a bridge is taken into account in terms of the
13
+ (i) spectrum modification factor,
14
+ (ii) strength reduction factor due to cyclic motion,
15
+ (iii) drift limits, and
16
+ (iv) the longitudinal reinforcement ratio.
17
+
18
+ [1] Hazus earthquake technical manual
19
+ https://www.fema.gov/sites/default/files/2020-10/fema_hazus_earthquake_technical_manual_4-2.pdf
20
+
21
+ [2] Hazus inventory technical manual
22
+ https://www.fema.gov/sites/default/files/documents/fema_hazus-6-inventory-technical-manual.pdf
23
+
24
+ [3] Mander and Basoz
25
+ https://www.researchgate.net/profile/Jb-Mander/publication/292691534_Seismic_fragility_curve_theory_for_highway_bridges/links/5a7346d7aca2720bc0dbb653/Seismic-fragility-curve-theory-for-highway-bridges.pdf
26
+
27
+ -------------------------------------------
28
+
29
+ Table 9-6 in [2] (also 7-1 in [1])
30
+
31
+ HWB1 All Non-CA < 1990 N/A > 150 N/A EQ1 0 Conventional Major Bridge - Length > 150 meters
32
+ All CA < 1975 N/A > 150 N/A EQ1 0 Conventional Major Bridge - Length > 150 meters
33
+ HWB2 All Non-CA >= 1990 N/A > 150 N/A EQ1 0 Seismic Major Bridge - Length > 150 meters
34
+ All CA >= 1975 N/A > 150 N/A EQ1 0 Seismic Major Bridge - Length > 150 meters
35
+ HWB3 All Non-CA < 1990 1 N/A N/A EQ1 1 Conventional Single Span
36
+ All CA < 1975 1 N/A N/A EQ1 1 Conventional Single Span
37
+ HWB4 All Non-CA >= 1990 1 N/A N/A EQ1 1 Seismic Single Span
38
+ All CA >= 1975 1 N/A N/A EQ1 1 Seismic Single Span
39
+ HWB5 101 106 Non-CA < 1990 N/A N/A N/A EQ1 0 Conventional Multi-Col. Bent, Simple Support - Concrete
40
+ HWB6 101 106 CA < 1975 N/A N/A N/A EQ1 0 Conventional Multi-Col. Bent, Simple Support - Concrete
41
+ HWB7 101 106 Non-CA >= 1990 N/A N/A N/A EQ1 0 Seismic Multi-Col. Bent, Simple Support - Concrete
42
+ 101 106 CA >= 1975 N/A N/A N/A EQ1 0 Seismic Multi-Col. Bent, Simple Support - Concrete
43
+ HWB8 205 206 CA < 1975 N/A N/A N/A EQ2 0 Conventional Single Col., Box Girder - Continuous Concrete
44
+ HWB9 205 206 CA >= 1975 N/A N/A N/A EQ3 0 Seismic Single Col., Box Girder - Continuous Concrete
45
+ HWB10 201 206 Non-CA < 1990 N/A N/A N/A EQ2 1 Conventional Continuous Concrete
46
+ 201 206 CA < 1975 N/A N/A N/A EQ2 1 Conventional Continuous Concrete
47
+ HWB11 201 206 Non-CA >= 1990 N/A N/A N/A EQ3 1 Seismic Continuous Concrete
48
+ 201 206 CA >= 1975 N/A N/A N/A EQ3 1 Seismic Continuous Concrete
49
+ HWB12 301 306 Non-CA < 1990 N/A N/A No EQ4 0 Conventional Multi-Col. Bent, Simple Support - Steel
50
+ HWB13 301 306 CA < 1975 N/A N/A No EQ4 0 Conventional Multi-Col. Bent, Simple Support - Steel
51
+ HWB14 301 306 Non-CA >= 1990 N/A N/A N/A EQ1 0 Seismic Multi-Col. Bent, Simple Support - Steel
52
+ 301 306 CA >= 1975 N/A N/A N/A EQ1 0 Seismic Multi-Col. Bent, Simple Support - Steel
53
+ HWB15 402 410 Non-CA < 1990 N/A N/A No EQ5 1 Conventional Continuous Steel
54
+ 402 410 CA < 1975 N/A N/A No EQ5 1 Conventional Continuous Steel
55
+ HWB16 402 410 Non-CA >= 1990 N/A N/A N/A EQ3 1 Seismic Continuous Steel
56
+ 402 410 CA >= 1975 N/A N/A N/A EQ3 1 Seismic Continuous Steel
57
+ HWB17 501 506 Non-CA < 1990 N/A N/A N/A EQ1 0 Conventional Multi-Col. Bent, Simple Support - Prestressed Concrete
58
+ HWB18 501 506 CA < 1975 N/A N/A N/A EQ1 0 Conventional Multi-Col. Bent, Simple Support - Prestressed Concrete
59
+ HWB19 501 506 Non-CA >= 1990 N/A N/A N/A EQ1 0 Seismic Multi-Col. Bent, Simple Support - Prestressed Concrete
60
+ 501 506 CA >= 1975 N/A N/A N/A EQ1 0 Seismic Multi-Col. Bent, Simple Support - Prestressed Concrete
61
+ HWB20 605 606 CA < 1975 N/A N/A N/A EQ2 0 Conventional Single Col., Box Girder - Prestressed Continuous Concrete
62
+ HWB21 605 606 CA >= 1975 N/A N/A N/A EQ3 0 Seismic Single Col., Box Girder - Prestressed Continuous Concrete
63
+ HWB22 601 607 Non-CA < 1990 N/A N/A N/A EQ2 1 Conventional Continuous Concrete
64
+ 601 607 CA < 1975 N/A N/A N/A EQ2 1 Conventional Continuous Concrete
65
+ HWB23 601 607 Non-CA >= 1990 N/A N/A N/A EQ3 1 Seismic Continuous Concrete
66
+ 601 607 CA >= 1975 N/A N/A N/A EQ3 1 Seismic Continuous Concrete
67
+ HWB24 301 306 Non-CA < 1990 N/A N/A Yes EQ6 0 Conventional Multi-Col. Bent, Simple Support - Steel
68
+ HWB25 301 306 CA < 1975 N/A N/A Yes EQ6 0 Conventional Multi-Col. Bent, Simple Support - Steel
69
+ HWB26 402 410 Non-CA < 1990 N/A N/A Yes EQ7 1 Conventional Continuous Steel
70
+ HWB27 402 410 CA < 1975 N/A N/A Yes EQ7 1 Conventional Continuous Steel
71
+ HWB28 N/A N/A All other bridges that are not classified
72
+
73
+
74
+ --------------------------
75
+
76
+ Table 9-7: Hazus Highway System Classification
77
+
78
+ HWB1 Major Bridge - Length > 150 meters (Conventional Design)
79
+ HWB2 Major Bridge - Length > 150 meters (Seismic Design)
80
+ HWB3 Single Span - (Not HWB1 or HWB2) (Conventional Design)
81
+ HWB4 Single Span - (Not HWB1 or HWB2) (Seismic Design)
82
+ HWB5 Concrete, Multi-Column Bent, Simple Support (Conventional Design), Non-California (Non CA)
83
+ HWB6 Concrete, Multi-Column Bent, Simple Support (Conventional Design), California (CA)
84
+ HWB7 Concrete, Multi-Column Bent, Simple Support (Seismic Design)
85
+ HWB8 Continuous Concrete, Single Column, Box Girder (Conventional Design)
86
+ HWB9 Continuous Concrete, Single Column, Box Girder (Seismic Design)
87
+ HWB10 Continuous Concrete, (Not HWB8 or HWB9) (Conventional Design)
88
+ HWB11 Continuous Concrete, (Not HWB8 or HWB9) (Seismic Design)
89
+ HWB12 Steel, Multi-Column Bent, Simple Support (Conventional Design), Non-California (Non-CA)
90
+ HWB13 Steel, Multi-Column Bent, Simple Support (Conventional Design), California (CA)
91
+ HWB14 Steel, Multi-Column Bent, Simple Support (Seismic Design)
92
+ HWB15 Continuous Steel (Conventional Design)
93
+ HWB16 Continuous Steel (Seismic Design)
94
+ HWB17 PS Concrete Multi-Column Bent, Simple Support (Conventional Design), Non-California
95
+ HWB18 PS Concrete, Multi-Column Bent, Simple Support (Conventional Design), California (CA)
96
+ HWB19 PS Concrete, Multi-Column Bent, Simple Support (Seismic Design)
97
+ HWB20 PS Concrete, Single Column, Box Girder (Conventional Design)
98
+ HWB21 PS Concrete, Single Column, Box Girder (Seismic Design)
99
+ HWB22 Continuous Concrete, (Not HWB20/HWB21) (Conventional Design)
100
+ HWB23 Continuous Concrete, (Not HWB20/HWB21) (Seismic Design)
101
+ HWB24 Same definition as HWB12 except the bridge length is less than 20 meters
102
+ HWB25 Same definition as HWB13 except the bridge length is less than 20 meters
103
+ HWB26 Same definition as HWB15 except the bridge length is less than 20 meters and Non-CA
104
+ HWB27 Same definition as HWB15 except the bridge length is less than 20 meters and in CA
105
+ HWB28 All other bridges that are not classified (including wooden bridges)
106
+
107
+
108
+ -----------------------------
109
+
110
+ Table 7-6 Fragility Function Median Values for Highway Bridges
111
+
112
+ HWB1 0.40 0.50 0.70 0.90 3.9 3.9 3.9 13.8
113
+ HWB2 0.60 0.90 1.10 1.70 3.9 3.9 3.9 13.8
114
+ HWB3 0.80 1.00 1.20 1.70 3.9 3.9 3.9 13.8
115
+ HWB4 0.80 1.00 1.20 1.70 3.9 3.9 3.9 13.8
116
+ HWB5 0.25 0.35 0.45 0.70 3.9 3.9 3.9 13.8
117
+ HWB6 0.30 0.50 0.60 0.90 3.9 3.9 3.9 13.8
118
+ HWB7 0.50 0.80 1.10 1.70 3.9 3.9 3.9 13.8
119
+ HWB8 0.35 0.45 0.55 0.80 3.9 3.9 3.9 13.8
120
+ HWB9 0.60 0.90 1.30 1.60 3.9 3.9 3.9 13.8
121
+ HWB10 0.60 0.90 1.10 1.50 3.9 3.9 3.9 13.8
122
+ HWB11 0.90 0.90 1.10 1.50 3.9 3.9 3.9 13.8
123
+ HWB12 0.25 0.35 0.45 0.70 3.9 3.9 3.9 13.8
124
+ HWB13 0.30 0.50 0.60 0.90 3.9 3.9 3.9 13.8
125
+ HWB14 0.50 0.80 1.10 1.70 3.9 3.9 3.9 13.8
126
+ HWB15 0.75 0.75 0.75 1.10 3.9 3.9 3.9 13.8
127
+ HWB16 0.90 0.90 1.10 1.50 3.9 3.9 3.9 13.8
128
+ HWB17 0.25 0.35 0.45 0.70 3.9 3.9 3.9 13.8
129
+ HWB18 0.30 0.50 0.60 0.90 3.9 3.9 3.9 13.8
130
+ HWB19 0.50 0.80 1.10 1.70 3.9 3.9 3.9 13.8
131
+ HWB20 0.35 0.45 0.55 0.80 3.9 3.9 3.9 13.8
132
+ HWB21 0.60 0.90 1.30 1.60 3.9 3.9 3.9 13.8
133
+ HWB22 0.60 0.90 1.10 1.50 3.9 3.9 3.9 13.8
134
+ HWB23 0.90 0.90 1.10 1.50 3.9 3.9 3.9 13.8
135
+ HWB24 0.25 0.35 0.45 0.70 3.9 3.9 3.9 13.8
136
+ HWB25 0.30 0.50 0.60 0.90 3.9 3.9 3.9 13.8
137
+ HWB26 0.75 0.75 0.75 1.10 3.9 3.9 3.9 13.8
138
+ HWB27 0.75 0.75 0.75 1.10 3.9 3.9 3.9 13.8
139
+ HWB28 0.80 1.00 1.20 1.70 3.9 3.9 3.9 13.8
140
+ """
141
+ import math
142
+ from scipy.stats import norm
143
+
144
+ Slight, Moderate, Extensive, Complete = range(4)
145
+
146
+ # State codes; We'll add more later, right now we assume
147
+ # everything is in California
148
+ class StateCodes:
149
+ California = 22
150
+
151
+ def hazus_fragility(
152
+ nbi_data: dict,
153
+ pga: float = 0.5, # Peak Ground Acceleration (g)
154
+ sa_03s: float = 1.1, # Spectral Acceleration at 0.3 seconds (g)
155
+ sa_10s: float = 1.4, # Spectral Acceleration at 1.0 seconds (g)
156
+ soil_type: str = "B", # Soil classification ("A", "B", "C", "D", "E")
157
+ level: int = None, # Optional: Specify a damage state (0 = Slight, 1 = Moderate, etc.)
158
+ generate_plot: bool = False, # Generate fragility curve plot
159
+ return_data: bool = False
160
+ ) -> dict:
161
+ """
162
+ Compute fragility probabilities for a given bridge using the Hazus methodology.
163
+
164
+ Args:
165
+ - nbi_data (dict): NBI data containing bridge-specific properties.
166
+ - pga (float): Peak Ground Acceleration (g).
167
+ - sa_03s (float): Spectral Acceleration at 0.3 seconds (g).
168
+ - sa_10s (float): Spectral Acceleration at 1.0 seconds (g).
169
+ - soil_type (str): Soil classification ("A", "B", "C", "D", "E").
170
+ - level (int): Specify a damage state (0 = Slight, 1 = Moderate, etc.) (optional).
171
+ - generate_plot (bool): Whether to generate and return fragility curve plots.
172
+
173
+ Returns:
174
+ - dict: Fragility probabilities for all damage states, optionally with fragility curve plot.
175
+ - float: Probability for the specified damage state (if `level` is provided).
176
+ """
177
+ # Step 0: Extract relevant bridge properties
178
+ properties = _bridge_info(nbi_data)
179
+
180
+ # Step 1: Determine Hazus bridge type
181
+ hazus_type: int = _hazus_type(properties)
182
+ if hazus_type == -1:
183
+ raise ValueError("Bridge type not found in Hazus classification")
184
+
185
+ # Step 2: Call _hazus_curve to compute fragility probabilities
186
+ fragility_probs = _hazus_curve(hazus_type, properties, pga, sa_03s, sa_10s, soil_type)
187
+
188
+ # Step 3: Generate fragility curve plot if requested
189
+ if generate_plot or return_data:
190
+ # Adjust sa_range to start from 0
191
+ sa_range = [0.0] + [0.1 * i for i in range(1, 21)] # Include 0 explicitly
192
+
193
+ curves = {state: [] for state in fragility_probs.keys()}
194
+
195
+ # Generate fragility values, handling the case for Sa = 0
196
+ for sa in sa_range:
197
+ for state in fragility_probs.keys():
198
+ median = get_old_medians(hazus_type)[state]
199
+ dispersion = 0.6 # β (dispersion factor)
200
+ curves[state].append(
201
+ norm.cdf((math.log(sa / median)) / dispersion) if sa > 0 else 0
202
+ )
203
+
204
+ if return_data:
205
+ fragility_probs["curves"] = curves
206
+ fragility_probs["sa_range"] = sa_range
207
+
208
+ # Step 4: Handle `level` as an integer
209
+ damage_states = ["Slight", "Moderate", "Extensive", "Complete"]
210
+ if level is not None:
211
+ if level not in range(4):
212
+ raise ValueError(f"Invalid level: {level}. Must be an integer in range(4).")
213
+ damage_state = damage_states[level]
214
+ return fragility_probs[damage_state]
215
+
216
+ # Step 5: Return all probabilities and optionally the plot
217
+ return fragility_probs
218
+
219
+
220
+ def _bridge_info(nbi: dict) -> dict:
221
+ """
222
+ Safely extract bridge properties, handling undefined skew angles and other placeholders.
223
+ """
224
+ try:
225
+ nbi_bridge = nbi.get("NBI_BRIDGE", {})
226
+ nbi_superstructure = nbi.get("NBI_SUPERSTRUCTURE_DECK", {})
227
+
228
+ # Parse "Skew Angle"
229
+ skew_angle_str = nbi_bridge.get("Skew Angle", "0")
230
+ if "99" in skew_angle_str: # Handle undefined skew
231
+ skew_angle = 0
232
+ else:
233
+ skew_angle = float(skew_angle_str.split(" - ")[0])
234
+
235
+ return {
236
+ "state_code": StateCodes.California,
237
+ "year_built": int(nbi_bridge.get("Year Built", 0)),
238
+ "skew_angle": skew_angle,
239
+ "service_type": int(
240
+ nbi_bridge.get("Type of Service on Bridge Code", "0 - Unknown").split(" - ")[0]
241
+ + nbi_bridge.get("Type Of Service Under Bridge Code", "0 - Unknown").split(" - ")[0]
242
+ ),
243
+ "material_flag": int(nbi_superstructure.get("Main Span Material", "0 - Unknown").split(" - ")[0]),
244
+ "geometry_flag": int(nbi_superstructure.get("Main Span Design", "0 - Unknown").split(" - ")[0]),
245
+ "span_count": int(nbi_superstructure.get("Number of Spans in Main Unit", 0)),
246
+ "approach_spans": int(nbi_superstructure.get("Number of Approach Spans", 0)),
247
+ "max_span_length": float(nbi_bridge.get("Length of Maximum Span", 0.0)),
248
+ "total_length": float(nbi_bridge.get("Structure Length", 0.0)),
249
+ "deck_width": float(nbi_bridge.get("Deck Width - Out to Out", 0.0)),
250
+ }
251
+ except ValueError as e:
252
+ raise ValueError(f"Error processing NBI data: {e}")
253
+
254
+
255
+
256
+
257
+ def _hazus_curve(type: int, properties: dict, pga: float, sa_03s: float, sa_10s: float, soil_type: str) -> dict:
258
+ """
259
+ Compute fragility probabilities for the four damage states and optionally generate fragility curves.
260
+
261
+ Args:
262
+ - type (int): Bridge classification (integer from 1 to 28).
263
+ - properties (dict): Dictionary containing bridge-specific properties.
264
+ - pga (float): Peak Ground Acceleration (g).
265
+ - sa_03s (float): Spectral Acceleration at 0.3 seconds (g).
266
+ - sa_10s (float): Spectral Acceleration at 1.0 seconds (g).
267
+ - soil_type (str): Soil classification ("A", "B", "C", "D", "E").
268
+ - generate_curve (bool): If True, generate and display fragility curves.
269
+
270
+ Returns:
271
+ - dict: Fragility probabilities for Slight, Moderate, Extensive, and Complete damage states.
272
+ """
273
+ # Validate inputs
274
+ required_keys = {"span_count", "skew_angle"}
275
+ missing_keys = required_keys - properties.keys()
276
+ if missing_keys:
277
+ raise ValueError(f"Missing required properties: {missing_keys}")
278
+
279
+ valid_soil_types = {"A", "B", "C", "D", "E"}
280
+ if soil_type not in valid_soil_types:
281
+ raise ValueError(f"Invalid soil type: {soil_type}. Must be one of {valid_soil_types}.")
282
+
283
+ span_count = properties["span_count"]
284
+ skew_angle = properties["skew_angle"]
285
+
286
+ # Call modify_ground_motion to get the modified values
287
+ modified_values = modify_ground_motion(pga, sa_03s, sa_10s, soil_type)
288
+ modified_pga = modified_values['modified_pga']
289
+ modified_sa_03s = modified_values['modified_sa_03s']
290
+ modified_sa_10s = modified_values['modified_sa_10s']
291
+
292
+ # Compute K_skew, K_shape, K3D
293
+ K_skew = math.sqrt(math.sin(math.radians(90 - skew_angle)))
294
+ if modified_sa_03s == 0:
295
+ raise ValueError("Modified Sa(0.3 sec) cannot be zero.")
296
+ K_shape = (2.5 * modified_sa_10s) / modified_sa_03s
297
+ A, B = get_a_b(type)
298
+ if span_count - B == 0:
299
+ raise ValueError("Invalid span count resulting in division by zero.")
300
+ K3D = 1 + A / (span_count - B)
301
+
302
+ # Retrieve old medians and compute new medians
303
+ old_medians = get_old_medians(type)
304
+ I_shape = get_i_shape(type)
305
+ factor_slight = 1 if I_shape == 0 else min(1, K_shape)
306
+
307
+ new_medians = {"Slight": old_medians["Slight"] * factor_slight}
308
+ damage_states = ["Moderate", "Extensive", "Complete"]
309
+ new_medians.update(
310
+ {state: old_medians[state] * K_skew * K3D for state in damage_states}
311
+ )
312
+
313
+ # Compute fragility probabilities
314
+ def compute_probability(sa: float, median: float, beta: float = 0.6) -> float:
315
+ return norm.cdf((math.log(sa / median)) / beta)
316
+
317
+ if modified_sa_10s <= 0:
318
+ raise ValueError("Modified Sa(1.0 sec) must be positive.")
319
+ fragility_probs = {
320
+ state: compute_probability(modified_sa_10s, median)
321
+ for state, median in new_medians.items()
322
+ }
323
+
324
+ return fragility_probs
325
+
326
+
327
+ def modify_ground_motion(pga: float, sa_03s: float, sa_10s: float, soil_type: str) -> dict:
328
+ """
329
+ Modify PGA, Sa(0.3 sec), and Sa(1.0 sec) based on soil amplification factors (Table 4.7).
330
+
331
+ Inputs:
332
+ - pga (float): Peak Ground Acceleration (g).
333
+ - sa_03s (float): Spectral Acceleration at 0.3 seconds (g).
334
+ - sa_10s (float): Spectral Acceleration at 1.0 seconds (g).
335
+ - soil_type (str): Soil classification ("A", "B", "C", "D", "E").
336
+
337
+ Returns:
338
+ - dict: Modified PGA, Sa(0.3 sec), and Sa(1.0 sec).
339
+ """
340
+
341
+ # Amplification factors for each parameter
342
+ amplification_factors = {
343
+ "FPGA": [
344
+ (0.1, {"A": 0.8, "B": 0.9, "C": 1.3, "D": 1.6, "E": 2.4}),
345
+ (0.2, {"A": 0.8, "B": 0.9, "C": 1.2, "D": 1.4, "E": 1.9}),
346
+ (0.3, {"A": 0.8, "B": 0.9, "C": 1.2, "D": 1.3, "E": 1.6}),
347
+ (0.4, {"A": 0.8, "B": 0.9, "C": 1.2, "D": 1.2, "E": 1.4}),
348
+ (0.5, {"A": 0.8, "B": 0.9, "C": 1.2, "D": 1.1, "E": 1.2}),
349
+ (0.6, {"A": 0.8, "B": 0.9, "C": 1.2, "D": 1.1, "E": 1.1}),
350
+ ],
351
+ "FA": [
352
+ (0.25, {"A": 0.8, "B": 0.9, "C": 1.3, "D": 1.6, "E": 2.4}),
353
+ (0.5, {"A": 0.8, "B": 0.9, "C": 1.3, "D": 1.4, "E": 1.7}),
354
+ (0.75, {"A": 0.8, "B": 0.9, "C": 1.2, "D": 1.2, "E": 1.3}),
355
+ (1.0, {"A": 0.8, "B": 0.9, "C": 1.2, "D": 1.1, "E": 1.1}),
356
+ (1.25, {"A": 0.8, "B": 0.9, "C": 1.2, "D": 1.0, "E": 0.9}),
357
+ (1.5, {"A": 0.8, "B": 0.9, "C": 1.2, "D": 1.0, "E": 0.8}),
358
+ ],
359
+ "FV": [
360
+ (0.1, {"A": 0.8, "B": 0.8, "C": 1.5, "D": 2.4, "E": 4.2}),
361
+ (0.2, {"A": 0.8, "B": 0.8, "C": 1.5, "D": 2.2, "E": 3.3}),
362
+ (0.3, {"A": 0.8, "B": 0.8, "C": 1.5, "D": 2.0, "E": 2.8}),
363
+ (0.4, {"A": 0.8, "B": 0.8, "C": 1.5, "D": 1.9, "E": 2.4}),
364
+ (0.5, {"A": 0.8, "B": 0.8, "C": 1.5, "D": 1.8, "E": 2.2}),
365
+ (0.6, {"A": 0.8, "B": 0.8, "C": 1.4, "D": 1.7, "E": 2.0}),
366
+ ],
367
+ }
368
+
369
+ def get_factor(value, factor_type):
370
+ rows = amplification_factors[factor_type]
371
+ # Below minimum threshold
372
+ if value <= rows[0][0]:
373
+ return rows[0][1][soil_type]
374
+ # Above maximum threshold
375
+ if value > rows[-1][0]:
376
+ return rows[-1][1][soil_type]
377
+ # Linear interpolation for intermediate values
378
+ for i in range(len(rows) - 1):
379
+ lower_bound, lower_factors = rows[i]
380
+ upper_bound, upper_factors = rows[i + 1]
381
+ if lower_bound < value <= upper_bound:
382
+ lower_factor = lower_factors[soil_type]
383
+ upper_factor = upper_factors[soil_type]
384
+ # Interpolate
385
+ return lower_factor + (upper_factor - lower_factor) * (value - lower_bound) / (upper_bound - lower_bound)
386
+ raise ValueError("Interpolation failed unexpectedly.")
387
+
388
+ # Calculate modified values
389
+ modified_pga = pga * get_factor(pga, "FPGA")
390
+ modified_sa_03s = sa_03s * get_factor(sa_03s, "FA")
391
+ modified_sa_10s = sa_10s * get_factor(sa_10s, "FV")
392
+
393
+ return {"modified_pga": modified_pga,
394
+ "modified_sa_03s": modified_sa_03s,
395
+ "modified_sa_10s": modified_sa_10s,
396
+ }
397
+
398
+ def get_a_b(bridge_type: int) -> tuple:
399
+ """
400
+ Retrieve coefficients A and B for K3D calculation based on bridge type.
401
+
402
+ Args:
403
+ - bridge_type (int): The bridge type (integer between 1 and 28).
404
+
405
+ Returns:
406
+ - tuple: Coefficients (A, B) corresponding to the bridge type's equation.
407
+ """
408
+ # Mapping of equations to A and B values from Table 7-2
409
+ equation_to_ab = {
410
+ "EQ1": (0.25, 1),
411
+ "EQ2": (0.33, 0),
412
+ "EQ3": (0.33, 1),
413
+ "EQ4": (0.09, 1),
414
+ "EQ5": (0.05, 0),
415
+ "EQ6": (0.20, 1),
416
+ "EQ7": (0.10, 0),
417
+ }
418
+
419
+ # Map bridge type to equations from Table 7-1
420
+ bridge_to_equation = {
421
+ 1: "EQ1", 2: "EQ1", 3: "EQ1", 4: "EQ1", 5: "EQ1", 6: "EQ1", 7: "EQ1", 8: "EQ2", 9: "EQ3", 10: "EQ2",
422
+ 11: "EQ3", 12: "EQ4", 13: "EQ4", 14: "EQ1", 15: "EQ5", 16: "EQ3", 17: "EQ1", 18: "EQ1", 19: "EQ1",
423
+ 20: "EQ2", 21: "EQ3", 22: "EQ2", 23: "EQ3", 24: "EQ6", 25: "EQ6", 26: "EQ7", 27: "EQ7",
424
+ }
425
+
426
+ # Get the equation for the bridge type
427
+ equation = bridge_to_equation.get(bridge_type)
428
+ if not equation:
429
+ raise ValueError(f"Unknown bridge type: {bridge_type}")
430
+
431
+ # Retrieve and return A and B values
432
+ return equation_to_ab[equation]
433
+
434
+ def get_i_shape(bridge_type: int) -> int:
435
+ """
436
+ Retrieve I_shape (indicator for skew effects) for the given bridge type
437
+ from Table 7-1.
438
+
439
+ Args:
440
+ - bridge_type (int): The bridge type (integer from 1 to 28).
441
+
442
+ Returns:
443
+ - int: I_shape value (0 or 1) based on Table 7-1.
444
+ """
445
+ # Mapping of bridge types to I_shape values from Table 7-1
446
+ i_shape_mapping = {
447
+ 1: 0, 2: 0, 3: 1, 4: 1, 5: 0, 6: 0,
448
+ 7: 0, 8: 0, 9: 0, 10: 1, 11: 1, 12: 0,
449
+ 13: 0, 14: 0, 15: 1, 16: 1, 17: 0, 18: 0,
450
+ 19: 0, 20: 0, 21: 0, 22: 1, 23: 1, 24: 0,
451
+ 25: 0, 26: 1, 27: 1,
452
+ }
453
+
454
+ if bridge_type not in i_shape_mapping:
455
+ raise ValueError(f"Unknown bridge type: {bridge_type}")
456
+
457
+ return i_shape_mapping[bridge_type]
458
+
459
+ def get_old_medians(bridge_type: int) -> dict:
460
+ """
461
+ Retrieve the old medians for Slight, Moderate, Extensive, and Complete damage states
462
+ from Table 7-6 based on the bridge type.
463
+
464
+ Args:
465
+ - bridge_type (int): The bridge type (integer from 1 to 28).
466
+
467
+ Returns:
468
+ - dict: Old median values for each damage state.
469
+ """
470
+ old_medians = {
471
+ 1: {"Slight": 0.40, "Moderate": 0.50, "Extensive": 0.70, "Complete": 0.90},
472
+ 2: {"Slight": 0.60, "Moderate": 0.90, "Extensive": 1.10, "Complete": 1.70},
473
+ 3: {"Slight": 0.80, "Moderate": 1.00, "Extensive": 1.20, "Complete": 1.70},
474
+ 4: {"Slight": 0.80, "Moderate": 1.00, "Extensive": 1.20, "Complete": 1.70},
475
+ 5: {"Slight": 0.25, "Moderate": 0.35, "Extensive": 0.45, "Complete": 0.70},
476
+ 6: {"Slight": 0.30, "Moderate": 0.50, "Extensive": 0.60, "Complete": 0.90},
477
+ 7: {"Slight": 0.50, "Moderate": 0.80, "Extensive": 1.10, "Complete": 1.70},
478
+ 8: {"Slight": 0.35, "Moderate": 0.45, "Extensive": 0.55, "Complete": 0.80},
479
+ 9: {"Slight": 0.60, "Moderate": 0.90, "Extensive": 1.30, "Complete": 1.60},
480
+ 10: {"Slight": 0.60, "Moderate": 0.90, "Extensive": 1.10, "Complete": 1.50},
481
+ 11: {"Slight": 0.90, "Moderate": 0.90, "Extensive": 1.10, "Complete": 1.50},
482
+ 12: {"Slight": 0.25, "Moderate": 0.35, "Extensive": 0.45, "Complete": 0.70},
483
+ 13: {"Slight": 0.30, "Moderate": 0.50, "Extensive": 0.60, "Complete": 0.90},
484
+ 14: {"Slight": 0.50, "Moderate": 0.80, "Extensive": 1.10, "Complete": 1.70},
485
+ 15: {"Slight": 0.75, "Moderate": 0.75, "Extensive": 0.75, "Complete": 1.10},
486
+ 16: {"Slight": 0.90, "Moderate": 0.90, "Extensive": 1.10, "Complete": 1.50},
487
+ 17: {"Slight": 0.25, "Moderate": 0.35, "Extensive": 0.45, "Complete": 0.70},
488
+ 18: {"Slight": 0.30, "Moderate": 0.50, "Extensive": 0.60, "Complete": 0.90},
489
+ 19: {"Slight": 0.50, "Moderate": 0.80, "Extensive": 1.10, "Complete": 1.70},
490
+ 20: {"Slight": 0.35, "Moderate": 0.45, "Extensive": 0.55, "Complete": 0.80},
491
+ 21: {"Slight": 0.60, "Moderate": 0.90, "Extensive": 1.30, "Complete": 1.60},
492
+ 22: {"Slight": 0.60, "Moderate": 0.90, "Extensive": 1.10, "Complete": 1.50},
493
+ 23: {"Slight": 0.90, "Moderate": 0.90, "Extensive": 1.10, "Complete": 1.50},
494
+ 24: {"Slight": 0.25, "Moderate": 0.35, "Extensive": 0.45, "Complete": 0.70},
495
+ 25: {"Slight": 0.30, "Moderate": 0.50, "Extensive": 0.60, "Complete": 0.90},
496
+ 26: {"Slight": 0.75, "Moderate": 0.75, "Extensive": 0.75, "Complete": 1.10},
497
+ 27: {"Slight": 0.75, "Moderate": 0.75, "Extensive": 0.75, "Complete": 1.10},
498
+ 28: {"Slight": 0.80, "Moderate": 1.00, "Extensive": 1.20, "Complete": 1.70},
499
+ }
500
+
501
+ if bridge_type not in old_medians:
502
+ raise ValueError(f"Unknown bridge type: {bridge_type}")
503
+
504
+ return old_medians[bridge_type]
505
+
506
+ def _hazus_type(properties: dict) -> int:
507
+ """
508
+ Classify the bridge into one of the Hazus types (1-28) using the properties extracted.
509
+ The mapping logic is based on Table 9.6.
510
+
511
+ Args:
512
+ properties (dict): A dictionary containing the bridge properties extracted from `_bridge_info()`
513
+
514
+ Returns:
515
+ int: Hazus type classification (1-28) or -1 if no match is found
516
+ """
517
+ year_built = properties["year_built"]
518
+ span_count = properties["span_count"]
519
+ max_length = properties["max_span_length"]
520
+ total_length = properties["total_length"]
521
+ material_flag = properties["material_flag"]
522
+ geometry_flag = properties["geometry_flag"]
523
+
524
+ bridge_class = -1
525
+
526
+ # Determine if the bridge is seismic based on year built and state code
527
+ seismic_year = 1975 if properties["state_code"] == StateCodes.California else 1990
528
+
529
+ # Implement classification rules from Table 9-6
530
+ # Some classes are not relevant and they're not included.
531
+ if year_built < seismic_year:
532
+ if max_length > 150:
533
+ bridge_class = 1 # Older, long-span bridges
534
+ else:
535
+ if span_count == 1:
536
+ bridge_class = 3 # Older, short, single-span bridges
537
+ else:
538
+ if material_flag == 1 :
539
+ bridge_class = 6 # Concrete, simple support
540
+ elif material_flag == 2 and geometry_flag == 6:
541
+ bridge_class = 8 # Single box, continuous concrete
542
+ elif material_flag == 2:
543
+ bridge_class = 10 # Continuous concrete
544
+ elif material_flag == 3 and total_length >= 20:
545
+ bridge_class = 13 # Steel, simple support, total length >= 20
546
+ elif material_flag == 3 and total_length < 20:
547
+ bridge_class = 25 # Steel, simple support, total length < 20
548
+ elif material_flag == 4 and total_length >= 20:
549
+ bridge_class = 15 # Continuous steel, total length >= 20
550
+ elif material_flag == 4 and total_length < 20:
551
+ bridge_class = 17 # Continuous steel, total length < 20
552
+ elif material_flag == 5:
553
+ bridge_class = 18 # Prestressed concrete, simple support
554
+ elif material_flag == 6 and geometry_flag == 6:
555
+ bridge_class = 20 # Prestressed continuous concrete, single box
556
+ else:
557
+ if max_length > 150:
558
+ bridge_class = 2 # Newer, long-span bridges
559
+ else:
560
+ if span_count == 1:
561
+ bridge_class = 4 # Newer, short, single-span bridges
562
+ else:
563
+ if material_flag == 1:
564
+ bridge_class = 7 # Concrete, simple support
565
+ elif material_flag == 2 and geometry_flag == 6:
566
+ bridge_class = 9 # Single box, continuous concrete
567
+ elif material_flag == 2:
568
+ bridge_class = 11 # Continuous concrete
569
+ elif material_flag == 3:
570
+ bridge_class = 14 # Steel, simple support
571
+ elif material_flag == 4:
572
+ bridge_class = 16 # Continuous steel
573
+ elif material_flag == 5:
574
+ bridge_class = 19 # Prestressed concrete, simple support
575
+ elif material_flag == 6:
576
+ bridge_class = 21 # Prestressed continuous concrete, single box
577
+
578
+ return bridge_class
579
+