Fragility Curve Calculation with Detailed Damage Ratio Analysis
Setup and Environment Configuration¶
First, we'll set up the notebook environment with automatic reloading for development.
%load_ext autoreload
%autoreload 2
Let's check our current working directory and navigate to the package root if needed.
%pwd
'/home/mammad/Documents/EDU/cope/CHEERSafe/LossModel/examples'
%cd ..
/home/mammad/Documents/EDU/cope/CHEERSafe/LossModel
System Configuration¶
Let's check the available CPU cores for parallel processing configuration.
import os
cpu_count = os.cpu_count()
print(f"Available CPU cores: {cpu_count}")
Available CPU cores: 24
Package Configuration and Imports¶
Configure the analysis parameters and import required modules.
Key Configuration for This Demo:
EXPORT_DETAILED_DAMAGE_RATIOS = True: Enables export of detailed component-level damage ratios to CSV
When enabled, the following columns are exported per (building_type, cindex) combination:
m: Building type identifierc: Component configuration indexwind_orientation_index: Wind orientation indexwind_speed_index: Wind speed indexflood_depth_index: Flood depth indexdr_total: Total building damage ratiodr_explicit: Explicit damage ratio (wind components)dr_implicit: Implicit damage ratio (interior + flood)dr_exp_wind_RC: Roof Coverings explicit damage ratiodr_exp_wind_RS: Roof Sheathings explicit damage ratiodr_exp_wind_RWC: Roof-Wall Connections explicit damage ratiodr_exp_wind_O: Openings explicit damage ratiodr_exp_wind_W: Walls explicit damage ratiodr_imp_wind_I: Interior implicit damage ratiodr_imp_wind_E: Electrical implicit damage ratiodr_imp_wind_M: Mechanical implicit damage ratiodr_imp_wind_P: Plumbing implicit damage ratiodr_imp_flood: Flood implicit damage ratio
from cheersafe.config import Config
# Configure analysis parameters
Config.NUM_OF_WORKERS = 20
Config.NUM_OF_ITERATIONS = 300
Config.INPUTS_PATH = "Inputs"
Config.OUTPUTS_PATH = "Outputs2"
# Enable detailed damage ratio export to CSV
Config.EXPORT_DETAILED_DAMAGE_RATIOS = True
print(f"EXPORT_DETAILED_DAMAGE_RATIOS: {Config.EXPORT_DETAILED_DAMAGE_RATIOS}")
# Import core analysis modules
from cheersafe.damage_probability_calculation import DamageProbabilityCalculation
from cheersafe.inventory import BuildingInventory
from cheersafe.tools.stats import Covariance, Sampling
EXPORT_DETAILED_DAMAGE_RATIOS: True
Initialize Core Components¶
Set up the building inventory and statistical analysis tools.
# Initialize building inventory with building geometry data
myBuildingInventory = BuildingInventory(buildings_file_name="BuildingGeometry_Old.json")
# Initialize statistical analysis components
myCovariance = Covariance()
mySampling = Sampling()
Create Main Analysis Engine¶
Initialize the damage probability calculation engine with our configured components.
# Initialize the main damage probability calculation engine
myDamageProbabilityCalculation = DamageProbabilityCalculation(
BuildingInventory=myBuildingInventory,
Sampling=mySampling,
Covariance=myCovariance
)
Configure Analysis Parameters¶
Specify which building types and resistance indices to process in this analysis.
# Set building types to process
myDamageProbabilityCalculation.set_building_types_to_process([2])
# Set resistance indices to process
myDamageProbabilityCalculation.set_cindex_to_process([45])
2026-04-08 12:00:18,018 - INFO - Set to process building types: [2] 2026-04-08 12:00:18,018 - INFO - Set to process component indices: [45]
Run Damage Probability Analysis¶
Execute the main damage probability calculation. With EXPORT_DETAILED_DAMAGE_RATIOS = True, this will also generate CSV files with detailed component-level damage ratios averaged over Monte Carlo iterations.
# Calculate average damage probability across all scenarios
# This will also export detailed damage ratios to CSV
myDamageProbabilityCalculation.get_average_damage_probability()
2026-04-08 12:00:18,028 - INFO - CIndex: 45 2026-04-08 12:00:18,029 - INFO - Building type: 2 2026-04-08 12:00:18,030 - INFO - Collecting detailed damage ratios for building 2, cIndex 45 [Parallel(n_jobs=20)]: Using backend LokyBackend with 20 concurrent workers. [Parallel(n_jobs=20)]: Done 10 tasks | elapsed: 0.8s [Parallel(n_jobs=20)]: Done 160 tasks | elapsed: 2.4s [Parallel(n_jobs=20)]: Done 300 out of 300 | elapsed: 3.7s finished [Parallel(n_jobs=20)]: Using backend LokyBackend with 20 concurrent workers. [Parallel(n_jobs=20)]: Done 10 tasks | elapsed: 0.2s [Parallel(n_jobs=20)]: Done 300 out of 300 | elapsed: 3.1s finished [Parallel(n_jobs=20)]: Using backend LokyBackend with 20 concurrent workers. [Parallel(n_jobs=20)]: Done 10 tasks | elapsed: 0.2s [Parallel(n_jobs=20)]: Done 300 out of 300 | elapsed: 3.2s finished [Parallel(n_jobs=20)]: Using backend LokyBackend with 20 concurrent workers. [Parallel(n_jobs=20)]: Done 10 tasks | elapsed: 0.2s [Parallel(n_jobs=20)]: Done 300 out of 300 | elapsed: 3.1s finished [Parallel(n_jobs=20)]: Using backend LokyBackend with 20 concurrent workers. [Parallel(n_jobs=20)]: Done 10 tasks | elapsed: 0.2s [Parallel(n_jobs=20)]: Done 300 out of 300 | elapsed: 3.1s finished [Parallel(n_jobs=20)]: Using backend LokyBackend with 20 concurrent workers. [Parallel(n_jobs=20)]: Done 10 tasks | elapsed: 0.2s [Parallel(n_jobs=20)]: Done 261 out of 300 | elapsed: 2.9s remaining: 0.4s [Parallel(n_jobs=20)]: Done 300 out of 300 | elapsed: 3.1s finished [Parallel(n_jobs=20)]: Using backend LokyBackend with 20 concurrent workers. [Parallel(n_jobs=20)]: Done 10 tasks | elapsed: 0.2s [Parallel(n_jobs=20)]: Done 261 out of 300 | elapsed: 2.9s remaining: 0.4s [Parallel(n_jobs=20)]: Done 300 out of 300 | elapsed: 3.1s finished [Parallel(n_jobs=20)]: Using backend LokyBackend with 20 concurrent workers. [Parallel(n_jobs=20)]: Done 10 tasks | elapsed: 0.2s [Parallel(n_jobs=20)]: Done 261 out of 300 | elapsed: 2.8s remaining: 0.4s [Parallel(n_jobs=20)]: Done 300 out of 300 | elapsed: 3.1s finished 2026-04-08 12:00:53,172 - INFO - Saved result for building type 2, cIndex 45, with 300 iterations to Outputs2/ProbDS_wfd_m2_c45_iter300.pkl 2026-04-08 12:01:12,902 - INFO - Saved detailed damage ratios for building type 2, cIndex 45 to Outputs2/DetailedDR_m2_c45_iter300.csv 2026-04-08 12:01:13,921 - INFO - All specified combinations processed and saved to individual files.
True
Load and Explore the Detailed Damage Ratio CSV¶
The detailed damage ratios are saved to CSV files with the naming pattern:
DetailedDR_m{building_type}_c{cindex}_iter{NUM_OF_ITERATIONS}.csv
Let's load and explore the generated CSV file.
import pandas as pd
# Define parameters matching our analysis
building_type = 2
c_index = 45
num_iterations = Config.NUM_OF_ITERATIONS
# Construct the CSV file path
csv_file_path = f"{Config.OUTPUTS_PATH}/DetailedDR_m{building_type}_c{c_index}_iter{num_iterations}.csv"
print(f"Loading: {csv_file_path}")
# Load the CSV file
df_detailed = pd.read_csv(csv_file_path)
print(f"\nDataFrame shape: {df_detailed.shape}")
print(f"Number of rows: {len(df_detailed)}")
print(f"Number of columns: {len(df_detailed.columns)}")
Loading: Outputs2/DetailedDR_m2_c45_iter300.csv DataFrame shape: (27200, 18) Number of rows: 27200 Number of columns: 18
View Column Names¶
Let's see all the available columns in the detailed damage ratio output.
print("Available columns:")
for i, col in enumerate(df_detailed.columns, 1):
print(f" {i}. {col}")
Available columns: 1. m 2. c 3. wind_orientation_index 4. wind_speed_index 5. flood_depth_index 6. dr_total 7. dr_explicit 8. dr_implicit 9. dr_exp_wind_RC 10. dr_exp_wind_RS 11. dr_exp_wind_RWC 12. dr_exp_wind_O 13. dr_exp_wind_W 14. dr_imp_wind_I 15. dr_imp_wind_E 16. dr_imp_wind_M 17. dr_imp_wind_P 18. dr_imp_flood
Preview the Data¶
Let's look at the first few rows of the detailed damage ratio data.
# Display first 10 rows
df_detailed.head(10)
| m | c | wind_orientation_index | wind_speed_index | flood_depth_index | dr_total | dr_explicit | dr_implicit | dr_exp_wind_RC | dr_exp_wind_RS | dr_exp_wind_RWC | dr_exp_wind_O | dr_exp_wind_W | dr_imp_wind_I | dr_imp_wind_E | dr_imp_wind_M | dr_imp_wind_P | dr_imp_flood | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 2 | 45 | 0 | 0 | 0 | 0.000000 | 0.0 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.000000 |
| 1 | 2 | 45 | 0 | 0 | 1 | 0.020333 | 0.0 | 0.020333 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.020333 |
| 2 | 2 | 45 | 0 | 0 | 2 | 0.031900 | 0.0 | 0.031900 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.031900 |
| 3 | 2 | 45 | 0 | 0 | 3 | 0.044267 | 0.0 | 0.044267 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.044267 |
| 4 | 2 | 45 | 0 | 0 | 4 | 0.101433 | 0.0 | 0.101433 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.101433 |
| 5 | 2 | 45 | 0 | 0 | 5 | 0.111900 | 0.0 | 0.111900 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.111900 |
| 6 | 2 | 45 | 0 | 0 | 6 | 0.103533 | 0.0 | 0.103533 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.103533 |
| 7 | 2 | 45 | 0 | 0 | 7 | 0.103267 | 0.0 | 0.103267 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.103267 |
| 8 | 2 | 45 | 0 | 0 | 8 | 0.103700 | 0.0 | 0.103700 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.103700 |
| 9 | 2 | 45 | 0 | 0 | 9 | 0.104667 | 0.0 | 0.104667 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.104667 |
Summary Statistics¶
Let's examine the statistical summary of the damage ratios.
# Get summary statistics for damage ratio columns
dr_columns = [col for col in df_detailed.columns if col.startswith('dr_')]
df_detailed[dr_columns].describe()
| dr_total | dr_explicit | dr_implicit | dr_exp_wind_RC | dr_exp_wind_RS | dr_exp_wind_RWC | dr_exp_wind_O | dr_exp_wind_W | dr_imp_wind_I | dr_imp_wind_E | dr_imp_wind_M | dr_imp_wind_P | dr_imp_flood | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 27200.000000 | 27200.000000 | 27200.000000 | 27200.000000 | 27200.000000 | 27200.000000 | 27200.000000 | 27200.000000 | 27200.000000 | 27200.000000 | 27200.000000 | 27200.000000 | 27200.000000 |
| mean | 0.389094 | 0.041385 | 0.347710 | 0.141045 | 0.023409 | 0.131079 | 0.060146 | 0.099075 | 0.127035 | 0.091658 | 0.080754 | 0.074572 | 0.297269 |
| std | 0.238040 | 0.082886 | 0.181640 | 0.263299 | 0.065129 | 0.241197 | 0.137125 | 0.215998 | 0.237235 | 0.187577 | 0.168088 | 0.156553 | 0.156552 |
| min | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| 25% | 0.229824 | 0.000459 | 0.221171 | 0.002406 | 0.000656 | 0.000225 | 0.001296 | 0.000833 | 0.002548 | 0.001193 | 0.001105 | 0.000813 | 0.208600 |
| 50% | 0.285464 | 0.003261 | 0.279783 | 0.015729 | 0.001750 | 0.004910 | 0.005926 | 0.008333 | 0.014932 | 0.009457 | 0.007542 | 0.006728 | 0.259200 |
| 75% | 0.533996 | 0.021698 | 0.506483 | 0.097177 | 0.006385 | 0.094032 | 0.029861 | 0.035833 | 0.077234 | 0.043726 | 0.035814 | 0.032186 | 0.314167 |
| max | 1.309914 | 0.351574 | 0.964592 | 0.954000 | 0.481667 | 0.784279 | 0.789444 | 0.895000 | 0.898538 | 0.777176 | 0.720423 | 0.649599 | 0.700000 |
Analyze Damage Ratios by Wind Speed¶
Let's see how damage ratios vary with wind speed index.
# Group by wind speed index and calculate mean damage ratios
df_by_wind_speed = df_detailed.groupby('wind_speed_index')[dr_columns].mean()
df_by_wind_speed
| dr_total | dr_explicit | dr_implicit | dr_exp_wind_RC | dr_exp_wind_RS | dr_exp_wind_RWC | dr_exp_wind_O | dr_exp_wind_W | dr_imp_wind_I | dr_imp_wind_E | dr_imp_wind_M | dr_imp_wind_P | dr_imp_flood | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| wind_speed_index | |||||||||||||
| 0 | 0.297433 | 0.000000 | 0.297433 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.297433 |
| 1 | 0.297453 | 0.000086 | 0.297367 | 0.000635 | 0.000281 | 0.000000 | 0.000370 | 0.000000 | 0.000331 | 0.000160 | 0.000135 | 0.000124 | 0.297281 |
| 2 | 0.297477 | 0.000112 | 0.297365 | 0.000797 | 0.000333 | 0.000051 | 0.000486 | 0.000000 | 0.000418 | 0.000224 | 0.000163 | 0.000151 | 0.297255 |
| 3 | 0.297825 | 0.000175 | 0.297649 | 0.001115 | 0.000365 | 0.000062 | 0.000579 | 0.000208 | 0.001036 | 0.000442 | 0.000363 | 0.000297 | 0.297356 |
| 4 | 0.297801 | 0.000254 | 0.297547 | 0.001609 | 0.000453 | 0.000101 | 0.000856 | 0.000312 | 0.001012 | 0.000486 | 0.000572 | 0.000434 | 0.297266 |
| 5 | 0.298245 | 0.000387 | 0.297858 | 0.002031 | 0.000578 | 0.000169 | 0.001181 | 0.000729 | 0.002201 | 0.000831 | 0.000810 | 0.000553 | 0.297261 |
| 6 | 0.298437 | 0.000507 | 0.297930 | 0.002672 | 0.000672 | 0.000298 | 0.001366 | 0.001042 | 0.002751 | 0.001425 | 0.001164 | 0.000977 | 0.297110 |
| 7 | 0.299056 | 0.000720 | 0.298336 | 0.003484 | 0.000740 | 0.000479 | 0.001690 | 0.001771 | 0.004021 | 0.002393 | 0.001882 | 0.001359 | 0.297148 |
| 8 | 0.299710 | 0.000979 | 0.298731 | 0.005005 | 0.000865 | 0.000760 | 0.002500 | 0.002187 | 0.004754 | 0.002528 | 0.002588 | 0.001924 | 0.297304 |
| 9 | 0.300959 | 0.001408 | 0.299550 | 0.006708 | 0.001078 | 0.001667 | 0.003356 | 0.003229 | 0.006921 | 0.003590 | 0.002814 | 0.003184 | 0.297409 |
| 10 | 0.302040 | 0.001876 | 0.300164 | 0.008937 | 0.001281 | 0.002432 | 0.004236 | 0.004375 | 0.009073 | 0.005125 | 0.004567 | 0.003611 | 0.297312 |
| 11 | 0.303861 | 0.002519 | 0.301342 | 0.011896 | 0.001469 | 0.003491 | 0.004838 | 0.006354 | 0.012571 | 0.007227 | 0.006348 | 0.005423 | 0.297252 |
| 12 | 0.305689 | 0.003307 | 0.302382 | 0.015615 | 0.001812 | 0.005788 | 0.006111 | 0.007917 | 0.014790 | 0.008613 | 0.006763 | 0.006875 | 0.297421 |
| 13 | 0.308394 | 0.004617 | 0.303777 | 0.020958 | 0.002198 | 0.010698 | 0.007894 | 0.010521 | 0.019925 | 0.011298 | 0.010181 | 0.009255 | 0.296995 |
| 14 | 0.311815 | 0.006302 | 0.305512 | 0.029625 | 0.002807 | 0.016318 | 0.010694 | 0.013229 | 0.024600 | 0.015405 | 0.011634 | 0.011737 | 0.297041 |
| 15 | 0.316396 | 0.008635 | 0.307761 | 0.039573 | 0.003609 | 0.025377 | 0.014259 | 0.017292 | 0.030323 | 0.018970 | 0.016010 | 0.014611 | 0.297518 |
| 16 | 0.322582 | 0.011553 | 0.311030 | 0.055328 | 0.004630 | 0.039809 | 0.017778 | 0.020104 | 0.042372 | 0.025248 | 0.021499 | 0.018402 | 0.297209 |
| 17 | 0.330461 | 0.015345 | 0.315116 | 0.072344 | 0.005510 | 0.060512 | 0.022824 | 0.023958 | 0.056231 | 0.031330 | 0.027421 | 0.022825 | 0.297235 |
| 18 | 0.341980 | 0.020631 | 0.321349 | 0.091755 | 0.006521 | 0.088204 | 0.028819 | 0.032187 | 0.075499 | 0.042312 | 0.034559 | 0.030671 | 0.297450 |
| 19 | 0.357897 | 0.027560 | 0.330336 | 0.115229 | 0.007708 | 0.119803 | 0.037338 | 0.045729 | 0.104589 | 0.056733 | 0.047052 | 0.043477 | 0.297106 |
| 20 | 0.432066 | 0.062350 | 0.369716 | 0.231755 | 0.018349 | 0.276976 | 0.084653 | 0.110937 | 0.223118 | 0.127202 | 0.104651 | 0.095318 | 0.297086 |
| 21 | 0.553979 | 0.118585 | 0.435394 | 0.409974 | 0.041214 | 0.481256 | 0.168056 | 0.239375 | 0.392066 | 0.241355 | 0.207342 | 0.187989 | 0.297213 |
| 22 | 0.703885 | 0.186866 | 0.517019 | 0.621974 | 0.085099 | 0.642016 | 0.270162 | 0.435104 | 0.564181 | 0.396285 | 0.350096 | 0.317450 | 0.297407 |
| 23 | 0.858093 | 0.252448 | 0.605645 | 0.832068 | 0.152943 | 0.729741 | 0.360903 | 0.650104 | 0.722590 | 0.563578 | 0.504040 | 0.477434 | 0.297475 |
| 24 | 0.993829 | 0.307392 | 0.686437 | 0.945026 | 0.244708 | 0.770968 | 0.452708 | 0.850208 | 0.860498 | 0.728690 | 0.656190 | 0.610219 | 0.297194 |
Visualize Component Damage Contributions¶
Let's visualize how different components contribute to the total damage ratio.
import matplotlib.pyplot as plt
# Explicit wind damage components
explicit_cols = ['dr_exp_wind_RC', 'dr_exp_wind_RS', 'dr_exp_wind_RWC', 'dr_exp_wind_O', 'dr_exp_wind_W']
explicit_labels = ['Roof Coverings', 'Roof Sheathings', 'Roof-Wall Conn.', 'Openings', 'Walls']
# Group by wind speed and get mean
df_wind_grouped = df_detailed.groupby('wind_speed_index')[explicit_cols].mean()
# Create the plot
fig, ax = plt.subplots(figsize=(12, 6))
df_wind_grouped.plot(kind='bar', ax=ax, width=0.8)
ax.set_xlabel('Wind Speed Index')
ax.set_ylabel('Mean Damage Ratio')
ax.set_title('Explicit Wind Damage Ratios by Wind Speed Index')
ax.legend(explicit_labels, loc='upper left', bbox_to_anchor=(1, 1))
plt.xticks(rotation=0)
plt.tight_layout()
plt.show()
Compare Explicit vs Implicit Damage¶
Let's compare the explicit and implicit damage contributions across wind speeds.
# Group by wind speed and get mean for total, explicit, and implicit
comparison_cols = ['dr_total', 'dr_explicit', 'dr_implicit']
df_comparison = df_detailed.groupby('wind_speed_index')[comparison_cols].mean()
# Create the plot
fig, ax = plt.subplots(figsize=(10, 6))
df_comparison.plot(kind='line', ax=ax, marker='o', linewidth=2, markersize=8)
ax.set_xlabel('Wind Speed Index')
ax.set_ylabel('Mean Damage Ratio')
ax.set_title('Total, Explicit, and Implicit Damage Ratios by Wind Speed Index')
ax.legend(['Total', 'Explicit (Wind)', 'Implicit (Interior + Flood)'])
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
Reset Configuration¶
After the analysis, you may want to reset the configuration to default values.
# Reset all configuration to defaults
Config.reset_flags()
print(f"EXPORT_DETAILED_DAMAGE_RATIOS after reset: {Config.EXPORT_DETAILED_DAMAGE_RATIOS}")
EXPORT_DETAILED_DAMAGE_RATIOS after reset: False
Summary¶
This notebook demonstrated how to:
- Enable detailed damage ratio export: Set
Config.EXPORT_DETAILED_DAMAGE_RATIOS = True - Run the analysis: Execute Monte Carlo simulation which now also exports CSV files
- Load and explore: Use pandas to load and analyze the detailed component-level damage ratios
- Visualize: Create plots showing damage ratio contributions by component and wind speed
Output Files¶
The detailed damage ratio CSV files are saved to Config.OUTPUTS_PATH with the naming pattern:
DetailedDR_m{building_type}_c{cindex}_iter{NUM_OF_ITERATIONS}.csv
Performance Note¶
Collecting detailed damage ratios is computationally more expensive than the standard analysis. Only enable EXPORT_DETAILED_DAMAGE_RATIOS when you need component-level breakdown for analysis or debugging.