Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

567 create building repair analysis #575

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

### Added
- Repair analysis that calculates the recovery time matrix [#567](https://github.com/IN-CORE/pyincore/issues/567)


## [1.18.1] - 2024-04-30

Expand Down
8 changes: 8 additions & 0 deletions pyincore/analyses/buildingrepair/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copyright (c) 2019 University of Illinois and others. All rights reserved.
#
# This program and the accompanying materials are made available under the
# terms of the Mozilla Public License v2.0 which accompanies this distribution,
# and is available at https://www.mozilla.org/en-US/MPL/2.0/


from pyincore.analyses.buildingrepair.buildingrepair import BuildingRepair
189 changes: 189 additions & 0 deletions pyincore/analyses/buildingrepair/buildingrepair.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# Copyright (c) 2019 University of Illinois and others. All rights reserved.
#
# This program and the accompanying materials are made available under the
# terms of the Mozilla Public License v2.0 which accompanies this distribution,
# and is available at https://www.mozilla.org/en-US/MPL/2.0/

import numpy as np
import pandas as pd

from pyincore import BaseAnalysis, RepairService
from pyincore.analyses.buildingdamage.buildingutil import BuildingUtil


class BuildingRepair(BaseAnalysis):
"""
This analysis computes the repair time needed for each building from any damage state. The repair model followed
the FEMA P-58 approach and was controlled by fragility functions.

The outputs of this analysis is a CSV file with repair time for simulated damage state at the building level.

Contributors
| Science: Wanting Lisa Wang, John W. van de Lindt
| Implementation: NCSA IN-CORE Dev Team

Related publications
Wang, Wanting Lisa, and John W. van de Lindt. "Quantitative Modeling of Residential Building Disaster Recovery
and Effects of Pre-and Post-event Policies." International Journal of Disaster Risk Reduction (2021): 102259.

Args:
incore_client (IncoreClient): Service authentication.

"""

def __init__(self, incore_client):
self.repairsvc = RepairService(incore_client)

super(BuildingRepair, self).__init__(incore_client)

def run(self):
"""Executes the residential building recovery analysis.

Returns:
bool: True if successful, False otherwise.

"""
result_name = self.get_parameter("result_name")

buildings = self.get_input_dataset("buildings").get_inventory_reader()
buildings = list(buildings)
sample_damage_states = self.get_input_dataset("sample_damage_states").get_dataframe_from_csv(low_memory=False)

# Returns dataframe
recovery = self.recovery_rate(buildings, sample_damage_states)
self.set_result_csv_data("recovery", recovery, result_name + "_recovery", "dataframe")

return True

def recovery_rate(self, buildings, sample_damage_states):
""" Gets repair time required for each building.

Args:
buildings (list): List of buildings
sample_damage_states (pd.DataFrame): Samples' damage states

Returns:
pd.DataFrame: Repair time of all buildings for each sample
"""
seed = self.get_parameter("seed")
if seed is not None:
np.random.seed(seed)

num_samples = len(sample_damage_states["sample_damage_states"].iloc[0].split(","))

# Generate a long numpy matrix for combined N1, N2 samples
num_buildings = sample_damage_states.shape[0]
samples_n1_n2 = np.zeros((num_buildings, num_samples * num_samples))

# Now, we define an internal function to take care of the index for the prior case
def idx(x, y):
return x * num_samples + y

repair_key = self.get_parameter("repair_key")
if repair_key is None:
repair_key = BuildingUtil.DEFAULT_REPAIR_KEY
self.set_parameter("repair_key", repair_key)
repair_sets = self.repairsvc.match_inventory(self.get_input_dataset("dfr3_mapping_set"), buildings, repair_key)
repair_sets_by_guid = {} # get repair sets by guid so they can be mapped with output of monte carlo

for i, b in enumerate(buildings):
# if building id has a matched repair curve set
if b['id'] in repair_sets.keys():
repair_sets_by_guid[b["properties"]['guid']] = repair_sets[b['id']]
else:
repair_sets_by_guid[b["properties"]['guid']] = None

for build in range(0, num_buildings):
# Obtain the damage states
mapped_repair = repair_sets_by_guid[sample_damage_states["guid"].iloc[build]]
samples_mcs = sample_damage_states["sample_damage_states"].iloc[build].split(",")

# Use a lambda to obtain the damage state in numeric form. Note that since damage states are single digits,
# it suffices to look at the last character and convert into an integer value. Do this computation once
# per household only.
samples_mcs_ds = list(map(lambda x: int(x[-1]), samples_mcs))

# Now, perform the two nested loops, using the indexing function to simplify the syntax.
for i in range(0, num_samples):
state = samples_mcs_ds[i]

percent_func = np.random.random(num_samples)
# NOTE: Even though the kwarg name is "repair_time", it actually takes percent of functionality. DFR3
# system currently doesn't have a way to represent the name correctly when calculating the inverse.
if mapped_repair is not None:
repair_time = mapped_repair.repair_curves[state].solve_curve_for_inverse(
hazard_values={}, curve_parameters=mapped_repair.curve_parameters,
**{"repair_time": percent_func}
) / 7
else:
repair_time = np.full(num_samples, np.nan)

for j in range(0, num_samples):
samples_n1_n2[build, idx(i, j)] = round(repair_time[j], 1)

# Now, generate all the labels using list comprehension outside the loops
colnames = [f'sample_{i}_{j}' for i in range(0, num_samples) for j in range(0, num_samples)]
recovery_time = pd.DataFrame(samples_n1_n2, columns=colnames)
recovery_time.insert(0, 'guid', sample_damage_states["guid"])

return recovery_time

def get_spec(self):
"""Get specifications of the residential building recovery analysis.

Returns:
obj: A JSON object of specifications of the residential building recovery analysis.

"""
return {
'name': 'building repair',
'description': 'calculate building repair time',
'input_parameters': [
{
'id': 'result_name',
'required': True,
'description': 'name of the result',
'type': str
},
{
'id': 'repair_key',
'required': False,
'description': 'Repair key to use in mapping dataset',
'type': str
},
{
'id': 'seed',
'required': False,
'description': 'Initial seed for the probabilistic model',
'type': int
}
],
'input_datasets': [
{
'id': 'buildings',
'required': True,
'description': 'Building Inventory',
'type': ['ergo:buildingInventoryVer4', 'ergo:buildingInventoryVer5', 'ergo:buildingInventoryVer6',
'ergo:buildingInventoryVer7']
},
{
'id': 'dfr3_mapping_set',
'required': True,
'description': 'DFR3 Mapping Set Object',
'type': ['incore:dfr3MappingSet'],
},
{
'id': 'sample_damage_states',
'required': True,
'description': 'Sample damage states',
'type': ['incore:sampleDamageState']
},
],
'output_datasets': [
{
'id': 'recovery',
'description': 'CSV file of commercial building recovery time',
'type': 'incore:buildingRecoveryTime'
}
]
}
38 changes: 38 additions & 0 deletions tests/pyincore/analyses/buildingrepair/test_buildingrepair.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# This program and the accompanying materials are made available under the
# terms of the Mozilla Public License v2.0 which accompanies this distribution,
# and is available at https://www.mozilla.org/en-US/MPL/2.0/

from pyincore import IncoreClient, RepairService, MappingSet
from pyincore.analyses.buildingrepair.buildingrepair import BuildingRepair
import pyincore.globals as pyglobals


def run_with_base_class():
client = IncoreClient(pyglobals.INCORE_API_DEV_URL)

# Joplin
buildings = "5df7d0de425e0b00092d0082" # joplin ergo:buildingInventoryVer6 28k buildings

# sample_damage_states = "6112d9ccca3e973ce144b4d9" # 500 samples 28k buildings - MCS output format
sample_damage_states = "60f883c059a8cc52bab4dd77" # 10 samples 28k buildings - MCS output format

building_repair = BuildingRepair(client)
building_repair.load_remote_input_dataset("buildings", buildings)

mapping_id = "60edfa3efc0f3a7af53a21b5"
repair_service = RepairService(client)
mapping_set = MappingSet(repair_service.get_mapping(mapping_id))
building_repair.set_input_dataset('dfr3_mapping_set', mapping_set)

building_repair.load_remote_input_dataset("sample_damage_states", sample_damage_states)

building_repair.set_parameter("result_name", "joplin_repair_time")
building_repair.set_parameter("seed", 1238)

building_repair.run_analysis()

return True


if __name__ == '__main__':
run_with_base_class()
Loading