From 88d3b150f755e3e0e2a455b8182eb1e7ad7ab487 Mon Sep 17 00:00:00 2001 From: Chen Wang Date: Thu, 6 Jun 2024 16:25:55 -0500 Subject: [PATCH] 557 rename monte carlo failure probability to monte carlo limit state probability (#576) * update MCS * update requirements * Update CHANGELOG.md * module rst * resolve merge conflict --- CHANGELOG.md | 27 +- docs/source/modules.rst | 49 +-- .../montecarlofailureprobability.py | 336 +---------------- .../__init__.py | 9 + .../montecarlolimitstateprobability.py | 339 ++++++++++++++++++ .../test_montecarlolimitstateprobability.py | 30 ++ 6 files changed, 418 insertions(+), 372 deletions(-) create mode 100644 pyincore/analyses/montecarlolimitstateprobability/__init__.py create mode 100644 pyincore/analyses/montecarlolimitstateprobability/montecarlolimitstateprobability.py create mode 100644 tests/pyincore/analyses/montecarlolimitstateprobability/test_montecarlolimitstateprobability.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 30820f42d..6db3e6b4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Update Non-structural Building Damage to support flood [#562](https://github.com/IN-CORE/pyincore/issues/562) - Update flood input to non-structural building damage for combined wind-wave-surge building [#566](https://github.com/IN-CORE/pyincore/issues/566) - Rename transportation recovery analysis to traffic flow recovery analysis [#558](https://github.com/IN-CORE/pyincore/issues/558) +- Rename Monte Carlo Failure Probability to Monte Carlo Limit State Probability [#557](https://github.com/IN-CORE/pyincore/issues/557) +- Rename Housing Recovery to Housing Valuation Recovery Analysis [#560](https://github.com/IN-CORE/pyincore/issues/560) +- Rename Building Portfolio Analysis to Building Cluster Recovery Analysis [#559](https://github.com/IN-CORE/pyincore/issues/559) - Rename nonstructural building damage [#537](https://github.com/IN-CORE/pyincore/issues/537) - Rename building damage to building structural damage [#561](https://github.com/IN-CORE/pyincore/issues/561) @@ -20,28 +23,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Gas Facility Damage Analysis [#568](https://github.com/IN-CORE/pyincore/issues/568) - Copyrights to transportation recovery analysis [#579](https://github.com/IN-CORE/pyincore/issues/579) - Buyout Model Analyses [#539](https://github.com/IN-CORE/pyincore/issues/539) +- Google Analytics to the documentation site [#547](https://github.com/IN-CORE/pyincore/issues/547) ### Fixed - Permission error in clearing cache process [#563](https://github.com/IN-CORE/pyincore/issues/563) - - -## [Unreleased] - -### Changed -- Rename Housing Recovery to Housing Valuation Recovery Analysis [#560](https://github.com/IN-CORE/pyincore/issues/560) - - -## [Unreleased] - -### Changed -- Rename Building Portfolio Analysis to Building Cluster Recovery Analysis [#559](https://github.com/IN-CORE/pyincore/issues/559) - - -## [Unrelased] - -### Fixed - Out of index error in dfr3 service's property conversion when the rule is not found [#555](https://github.com/IN-CORE/pyincore/issues/555) + ## [1.18.1] - 2024-04-30 ### Changed @@ -56,11 +44,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Unnecessary dependency in setup.py [#519](https://github.com/IN-CORE/pyincore/issues/519) -## [Unreleased] - -### Added -- Google Analytics to the documentation site [#547](https://github.com/IN-CORE/pyincore/issues/547) - ## [1.18.0] - 2024-04-03 ### Added diff --git a/docs/source/modules.rst b/docs/source/modules.rst index afe113a84..41c356929 100644 --- a/docs/source/modules.rst +++ b/docs/source/modules.rst @@ -68,7 +68,7 @@ analyses/combinedwindwavesurgebuildingdamage :members: analyses/combinedwindwavesurgebuildingloss -============================================ +========================================== .. autoclass:: pyincore.analyses.combinedwindwavesurgebuildingloss.CombinedWindWaveSurgeBuildingLoss :members: @@ -78,7 +78,7 @@ analyses/commercialbuildingrecovery :members: analyses/core_cge_ml -=================================== +==================== .. autoclass:: core_cge_ml.corecgeml.CoreCGEML :members: @@ -100,7 +100,7 @@ analyses/epfdamage :members: analyses/epfrepaircost -================== +====================== .. autoclass:: epfrepaircost.epfrepaircost.EpfRepairCost :members: @@ -117,7 +117,7 @@ analyses/example :members: analyses/galvestoncge -================== +===================== .. autoclass:: galvestoncge.galvestoncge.GalvestonCGEModel :members: .. autoclass:: galvestoncge.equationlib.VarContainer @@ -140,7 +140,7 @@ analyses/galvestoncge :members: analyses/gasfacilitydamage -============================ +========================== .. autoclass:: gasfacilitydamage.gasfacilitydamage.GasFacilityDamage :members: .. autoclass:: gasfacilitydamage.gfutil.GfUtil @@ -162,14 +162,14 @@ analyses/housingunitallocation :members: analyses/housingvaluationrecovery -======================== +================================= .. autoclass:: housingvaluationrecovery.housingvaluationrecovery.HousingValuationRecovery :members: .. autoclass:: housingvaluationrecovery.housingvaluationrecoveryutil.HousingValuationRecoveryUtil :members: analyses/indp -============================== +============= .. autoclass:: indp.indp.INDP :members: .. autoclass:: indp.dislocationutils.DislocationUtil @@ -215,7 +215,7 @@ analyses/joplincge :members: analyses/joplinempiricalbuildingrestoration -=================================== +=========================================== .. autoclass:: joplinempiricalbuildingrestoration.joplinempiricalbuildingrestoration.JoplinEmpiricalBuildingRestoration :members: .. autoclass:: joplinempiricalbuildingrestoration.joplinempirrestor_util.JoplinEmpirRestorUtil @@ -227,13 +227,18 @@ analyses/meandamage :members: analyses/mlenabledcgeslc -=================== +======================== .. autoclass:: mlenabledcgeslc.mlcgeslc.MlEnabledCgeSlc :members: analyses/montecarlofailureprobability ===================================== -.. autoclass:: montecarlofailureprobability.montecarlofailureprobability.MonteCarloFailureProbability +.. deprecated:: 1.19.0 + This class will be deprecated soon. Use :class:`montecarlolimitstateprobability.MonteCarloLimitStateProbability` instead. + +analyses/montecarlolimitstateprobability +======================================== +.. autoclass:: montecarlolimitstateprobability.montecarlolimitstateprobability.MonteCarloLimitStateProbability :members: analyses/multiobjectiveretrofitoptimization @@ -268,12 +273,12 @@ analyses/pipelinedamagerepairrate :members: analyses/pipelinefunctionality -========================= +============================== .. autoclass:: pipelinefunctionality.pipelinefunctionality.PipelineFunctionality :members: analyses/pipelinerepaircost -============================ +=========================== .. autoclass:: pipelinerepaircost.pipelinerepaircost.PipelineRepairCost :members: @@ -315,7 +320,7 @@ analyses/seasidecge :members: analyses/saltlakecge -================== +==================== .. autoclass:: saltlakecge.saltlakecge.SaltLakeCGEModel :members: .. autoclass:: saltlakecge.equationlib.VarContainer @@ -345,7 +350,7 @@ analyses/socialvulnerability This class will be deprecated soon. Use :class:`socialvulnerabilityscore.SocialVulnerabilityScore` instead. analyses/socialvulnerabilityscore -============================ +================================= .. autoclass:: socialvulnerabilityscore.socialvulnerabilityscore.SocialVulnerabilityScore :members: @@ -378,7 +383,7 @@ analyses/transportationrecovery This class will be deprecated soon. Use :class:`trafficflowrecovery.TrafficFlowRecovery` instead. analyses/trafficflowrecovery -=============================== +============================ .. autoclass:: trafficflowrecovery.trafficflowrecovery.TrafficFlowRecovery :members: .. autoclass:: trafficflowrecovery.trafficflowrecoveryutil.TrafficFlowRecoveryUtil @@ -404,7 +409,7 @@ analyses/waterfacilitydamage :members: analyses/waterfacilityrepaircost -================================= +================================ .. autoclass:: waterfacilityrepaircost.waterfacilityrepaircost.WaterFacilityRepairCost :members: @@ -414,7 +419,7 @@ analyses/waterfacilityrestoration :members: analyses/wfnfunctionality -================================= +========================= .. autoclass:: wfnfunctionality.wfnfunctionality.WfnFunctionality :members: @@ -422,7 +427,7 @@ models ^^^^^^ models/hazard/earthquake -=================== +======================== .. autoclass:: models.earthquake.Earthquake :members: @@ -432,7 +437,7 @@ models/hazard/flood :members: models/hazard/hazard -=================== +==================== .. autoclass:: models.hazard.Hazard :members: @@ -487,7 +492,7 @@ models/mappingset :members: models/networkdataset -================= +===================== .. autoclass:: models.networkdataset.NetworkDataset :members: @@ -502,7 +507,7 @@ models/restorationcurveset :members: models/units -========================== +============ .. autoclass:: models.units.Units :members: @@ -515,7 +520,7 @@ utils/analysisutil :members: utils/cge_ml_file_util -================== +====================== .. autoclass:: utils.cge_ml_file_util.CGEMLFileUtil :members: diff --git a/pyincore/analyses/montecarlofailureprobability/montecarlofailureprobability.py b/pyincore/analyses/montecarlofailureprobability/montecarlofailureprobability.py index 2ae4235a5..2467a56da 100644 --- a/pyincore/analyses/montecarlofailureprobability/montecarlofailureprobability.py +++ b/pyincore/analyses/montecarlofailureprobability/montecarlofailureprobability.py @@ -1,339 +1,19 @@ -# 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 collections -import concurrent.futures -import numpy as np +from deprecated.sphinx import deprecated -from typing import List -from pyincore import BaseAnalysis, AnalysisUtil +from pyincore.analyses.montecarlolimitstateprobability import MonteCarloLimitStateProbability -class MonteCarloFailureProbability(BaseAnalysis): - """ - Args: - incore_client (IncoreClient): Service authentication. - - """ - +@deprecated(version='1.19.0', reason="This class will be deprecated soon. Use MonteCarloLimitStateProbability instead.") +class MonteCarloFailureProbability(): def __init__(self, incore_client): - super(MonteCarloFailureProbability, self).__init__(incore_client) - - def get_spec(self): - """Get specifications of the monte carlo failure probability analysis. - - Returns: - obj: A JSON object of specifications of the monte carlo failure probability analysis. - - """ - return { - 'name': 'monte-carlo-failure-probability', - 'description': 'calculate the probability of failure in monte-carlo simulation', - 'input_parameters': [ - { - 'id': 'result_name', - 'required': True, - 'description': 'basename of the result datasets. This analysis will create two outputs: failure ' - 'proability and failure state with the basename in the filename. ' - 'For example: "result_name = joplin_mcs_building" will result in ' - '"joplin_mcs_building_failure_state.csv" and ' - '"joplin_mcs_building_failure_probability.csv"', - 'type': str - }, - { - 'id': 'num_cpu', - 'required': False, - 'description': 'If using parallel execution, the number of cpus to request', - 'type': int - }, - { - 'id': 'num_samples', - 'required': True, - 'description': 'Number of MC samples', - 'type': int - }, - { - 'id': 'damage_interval_keys', - 'required': True, - 'description': 'Column name of the damage interval', - 'type': List[str] - }, - { - 'id': 'failure_state_keys', - 'required': True, - 'description': 'Column name of the damage interval that considered as damaged', - 'type': List[str] - }, - { - 'id': 'seed', - 'required': False, - 'description': 'Initial seed for the probabilistic model', - 'type': int - }, - ], - 'input_datasets': [ - { - 'id': 'damage', - 'required': True, - 'description': 'damage result that has damage intervals in it', - 'type': ['ergo:buildingDamageVer4', - 'ergo:buildingDamageVer5', - 'ergo:buildingDamageVer6', - 'ergo:nsBuildingInventoryDamage', - 'ergo:nsBuildingInventoryDamageVer2', - 'ergo:nsBuildingInventoryDamageVer3', - 'ergo:bridgeDamage', - 'ergo:bridgeDamageVer2', - 'ergo:bridgeDamageVer3', - 'ergo:waterFacilityDamageVer4', - 'ergo:waterFacilityDamageVer5', - 'ergo:waterFacilityDamageVer6', - 'ergo:roadDamage', - 'ergo:roadDamageVer2', - 'ergo:roadDamageVer3', - 'incore:epfDamage', - 'incore:epfDamageVer2', - 'incore:epfDamageVer3', - 'incore:pipelineDamage', - 'incore:pipelineDamageVer2', - 'incore:pipelineDamageVer3'] - }, - - ], - 'output_datasets': [ - { - 'id': 'failure_probability', - 'description': 'CSV file of failure probability', - 'type': 'incore:failureProbability' - }, - { - 'id': 'sample_failure_state', - 'description': 'CSV file of failure state for each sample', - 'type': 'incore:sampleFailureState' - }, - { - 'id': 'sample_damage_states', - 'description': 'CSV file of simulated damage states for each sample', - 'type': 'incore:sampleDamageState' - } - ] - } - - def run(self): - """Executes mc failure probability analysis.""" - - # read in file and parameters - damage = self.get_input_dataset("damage").get_csv_reader() - damage_result = AnalysisUtil.get_csv_table_rows(damage, ignore_first_row=False) - - # setting number of cpus to use - user_defined_cpu = 1 - if not self.get_parameter("num_cpu") is None and self.get_parameter( - "num_cpu") > 0: - user_defined_cpu = self.get_parameter("num_cpu") - - num_workers = AnalysisUtil.determine_parallelism_locally(self, - len( - damage_result), - user_defined_cpu) - - avg_bulk_input_size = int(len(damage_result) / num_workers) - inventory_args = [] - count = 0 - inventory_list = damage_result - - seed = self.get_parameter("seed") - seed_list = [] - if seed is not None: - while count < len(inventory_list): - inventory_args.append( - inventory_list[count:count + avg_bulk_input_size]) - seed_list.append([seed + i for i in range(count - 1, count + avg_bulk_input_size - 1)]) - count += avg_bulk_input_size - else: - while count < len(inventory_list): - inventory_args.append( - inventory_list[count:count + avg_bulk_input_size]) - seed_list.append([None for i in range(count - 1, count + avg_bulk_input_size - 1)]) - count += avg_bulk_input_size - - fs_results, fp_results, samples_results = self.monte_carlo_failure_probability_concurrent_future( - self.monte_carlo_failure_probability_bulk_input, num_workers, - inventory_args, seed_list) - self.set_result_csv_data("sample_failure_state", - fs_results, name=self.get_parameter("result_name") + "_failure_state") - self.set_result_csv_data("failure_probability", - fp_results, name=self.get_parameter("result_name") + "_failure_probability") - self.set_result_csv_data("sample_damage_states", - samples_results, name=self.get_parameter("result_name") + "_sample_damage_states") - return True - - def monte_carlo_failure_probability_concurrent_future(self, function_name, - parallelism, *args): - """Utilizes concurrent.future module. - - Args: - function_name (function): The function to be parallelized. - parallelism (int): Number of workers in parallelization. - *args: All the arguments in order to pass into parameter function_name. - - Returns: - list: A list of dictionary with id/guid and failure state for N samples. - list: A list dictionary with failure probability and other data/metadata. + self._delegate = MonteCarloLimitStateProbability(incore_client) + def __getattr__(self, name): """ - fs_output = [] - fp_output = [] - samples_output = [] - with concurrent.futures.ProcessPoolExecutor( - max_workers=parallelism) as executor: - for fs_ret, fp_ret, samples_ret in executor.map(function_name, *args): - fs_output.extend(fs_ret) - fp_output.extend(fp_ret) - samples_output.extend(samples_ret) - - return fs_output, fp_output, samples_output - - def monte_carlo_failure_probability_bulk_input(self, damage, seed_list): - """Run analysis for monte carlo failure probability calculation - - Args: - damage (obj): An output of building/bridge/waterfacility/epn damage that has damage interval. - seed_list (list): Random number generator seed per building for reproducibility. - - Returns: - fs_results (list): A list of dictionary with id/guid and failure state for N samples - fp_results (list): A list dictionary with failure probability and other data/metadata. - - """ - damage_interval_keys = self.get_parameter("damage_interval_keys") - failure_state_keys = self.get_parameter("failure_state_keys") - num_samples = self.get_parameter("num_samples") - - fs_result = [] - fp_result = [] - samples_output = [] - - i = 0 - for dmg in damage: - fs, fp, samples_result = self.monte_carlo_failure_probability(dmg, damage_interval_keys, failure_state_keys, - num_samples, seed_list[i]) - fs_result.append(fs) - fp_result.append(fp) - samples_output.append(samples_result) - i += 1 - - return fs_result, fp_result, samples_output - - def monte_carlo_failure_probability(self, dmg, damage_interval_keys, - failure_state_keys, num_samples, seed): - """Calculates building damage results for a single building. - - Args: - dmg (obj): Damage analysis output for a single entry. - damage_interval_keys (list): A list of the name of the damage intervals. - failure_state_keys (list): A list of the name of the damage state that is considered as failed. - num_samples (int): Number of samples for mc simulation. - seed (int): Random number generator seed for reproducibility. - - Returns: - dict: A dictionary with id/guid and failure state for N samples - dict: A dictionary with failure probability and other data/metadata. - dict: A dictionary with id/guid and damage states for N samples - - """ - # failure state - fs_result = collections.OrderedDict() - - # sample damage states - samples_result = collections.OrderedDict() - - # copying guid/id column to the sample damage failure table - if 'guid' in dmg.keys(): - fs_result['guid'] = dmg['guid'] - samples_result['guid'] = dmg['guid'] - - elif 'id' in dmg.keys(): - fs_result['id'] = dmg['id'] - samples_result['id'] = dmg['id'] - else: - fs_result['id'] = 'NA' - samples_result['id'] = 'NA' - - # failure probability - fp_result = collections.OrderedDict() - fp_result['guid'] = dmg['guid'] - - ds_sample = self.sample_damage_interval(dmg, damage_interval_keys, - num_samples, seed) - func, fp = self.calc_probability_failure_value(ds_sample, failure_state_keys) - - fs_result['failure'] = ",".join(func.values()) - fp_result['failure_probability'] = fp - samples_result['sample_damage_states'] = ','.join(ds_sample.values()) - - return fs_result, fp_result, samples_result - - def sample_damage_interval(self, dmg, damage_interval_keys, num_samples, seed): - """ - Dylan Sanderson code to calculate the Monte Carlo simulations of damage state. - - Args: - dmg (dict): Damage results that contains dmg interval values. - damage_interval_keys (list): Keys of the damage states. - num_samples (int): Number of simulation. - seed (int): Random number generator seed for reproducibility. - - Returns: - dict: A dictionary of damage states. - - """ - ds = {} - random_generator = np.random.RandomState(seed) - for i in range(num_samples): - # each sample should have a unique seed - rnd_num = random_generator.uniform(0, 1) - prob_val = 0 - flag = True - for ds_name in damage_interval_keys: - - if rnd_num < prob_val + AnalysisUtil.float_to_decimal(dmg[ds_name]): - ds['sample_{}'.format(i)] = ds_name - flag = False - break - else: - prob_val += AnalysisUtil.float_to_decimal(dmg[ds_name]) - if flag: - print("cannot determine MC damage state!") - break - - return ds - - def calc_probability_failure_value(self, ds_sample, failure_state_keys): - """ - Lisa Wang's approach to calculate a single value of failure probability. - - Args: - ds_sample (dict): A dictionary of damage states. - failure_state_keys (list): Damage state keys that considered as failure. - - Returns: - float: Failure state on each sample 0 (failed), 1 (not failed). - float: Failure probability (0 - 1). - + Delegate attribute access to the MonteCarloLimitStateProbability instance. """ - count = 0 - func = {} - for sample, state in ds_sample.items(): - if state in failure_state_keys: - func[sample] = "0" - count += 1 - else: - func[sample] = "1" - if len(ds_sample): - return func, count / len(ds_sample) - else: - return func, np.nan + return getattr(self._delegate, name) diff --git a/pyincore/analyses/montecarlolimitstateprobability/__init__.py b/pyincore/analyses/montecarlolimitstateprobability/__init__.py new file mode 100644 index 000000000..a358ba14c --- /dev/null +++ b/pyincore/analyses/montecarlolimitstateprobability/__init__.py @@ -0,0 +1,9 @@ +# 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.montecarlolimitstateprobability.montecarlolimitstateprobability import \ + MonteCarloLimitStateProbability diff --git a/pyincore/analyses/montecarlolimitstateprobability/montecarlolimitstateprobability.py b/pyincore/analyses/montecarlolimitstateprobability/montecarlolimitstateprobability.py new file mode 100644 index 000000000..9bc219544 --- /dev/null +++ b/pyincore/analyses/montecarlolimitstateprobability/montecarlolimitstateprobability.py @@ -0,0 +1,339 @@ +# 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 collections +import concurrent.futures +import numpy as np + +from typing import List +from pyincore import BaseAnalysis, AnalysisUtil + + +class MonteCarloLimitStateProbability(BaseAnalysis): + """ + Args: + incore_client (IncoreClient): Service authentication. + + """ + + def __init__(self, incore_client): + super(MonteCarloLimitStateProbability, self).__init__(incore_client) + + def get_spec(self): + """Get specifications of the monte carlo limit state probability analysis. + + Returns: + obj: A JSON object of specifications of the monte carlo limit state probability analysis. + + """ + return { + 'name': 'monte-carlo-limit-state-probability', + 'description': 'calculate the probability of limit state in monte-carlo simulation', + 'input_parameters': [ + { + 'id': 'result_name', + 'required': True, + 'description': 'basename of the result datasets. This analysis will create two outputs: failure ' + 'proability and failure state with the basename in the filename. ' + 'For example: "result_name = joplin_mcs_building" will result in ' + '"joplin_mcs_building_failure_state.csv" and ' + '"joplin_mcs_building_failure_probability.csv"', + 'type': str + }, + { + 'id': 'num_cpu', + 'required': False, + 'description': 'If using parallel execution, the number of cpus to request', + 'type': int + }, + { + 'id': 'num_samples', + 'required': True, + 'description': 'Number of MC samples', + 'type': int + }, + { + 'id': 'damage_interval_keys', + 'required': True, + 'description': 'Column name of the damage interval', + 'type': List[str] + }, + { + 'id': 'failure_state_keys', + 'required': True, + 'description': 'Column name of the damage interval that considered as damaged', + 'type': List[str] + }, + { + 'id': 'seed', + 'required': False, + 'description': 'Initial seed for the probabilistic model', + 'type': int + }, + ], + 'input_datasets': [ + { + 'id': 'damage', + 'required': True, + 'description': 'damage result that has damage intervals in it', + 'type': ['ergo:buildingDamageVer4', + 'ergo:buildingDamageVer5', + 'ergo:buildingDamageVer6', + 'ergo:nsBuildingInventoryDamage', + 'ergo:nsBuildingInventoryDamageVer2', + 'ergo:nsBuildingInventoryDamageVer3', + 'ergo:bridgeDamage', + 'ergo:bridgeDamageVer2', + 'ergo:bridgeDamageVer3', + 'ergo:waterFacilityDamageVer4', + 'ergo:waterFacilityDamageVer5', + 'ergo:waterFacilityDamageVer6', + 'ergo:roadDamage', + 'ergo:roadDamageVer2', + 'ergo:roadDamageVer3', + 'incore:epfDamage', + 'incore:epfDamageVer2', + 'incore:epfDamageVer3', + 'incore:pipelineDamage', + 'incore:pipelineDamageVer2', + 'incore:pipelineDamageVer3'] + }, + + ], + 'output_datasets': [ + { + 'id': 'failure_probability', + 'description': 'CSV file of failure probability', + 'type': 'incore:failureProbability' + }, + { + 'id': 'sample_failure_state', + 'description': 'CSV file of failure state for each sample', + 'type': 'incore:sampleFailureState' + }, + { + 'id': 'sample_damage_states', + 'description': 'CSV file of simulated damage states for each sample', + 'type': 'incore:sampleDamageState' + } + ] + } + + def run(self): + """Executes mc limit state probability analysis.""" + + # read in file and parameters + damage = self.get_input_dataset("damage").get_csv_reader() + damage_result = AnalysisUtil.get_csv_table_rows(damage, ignore_first_row=False) + + # setting number of cpus to use + user_defined_cpu = 1 + if not self.get_parameter("num_cpu") is None and self.get_parameter( + "num_cpu") > 0: + user_defined_cpu = self.get_parameter("num_cpu") + + num_workers = AnalysisUtil.determine_parallelism_locally(self, + len( + damage_result), + user_defined_cpu) + + avg_bulk_input_size = int(len(damage_result) / num_workers) + inventory_args = [] + count = 0 + inventory_list = damage_result + + seed = self.get_parameter("seed") + seed_list = [] + if seed is not None: + while count < len(inventory_list): + inventory_args.append( + inventory_list[count:count + avg_bulk_input_size]) + seed_list.append([seed + i for i in range(count - 1, count + avg_bulk_input_size - 1)]) + count += avg_bulk_input_size + else: + while count < len(inventory_list): + inventory_args.append( + inventory_list[count:count + avg_bulk_input_size]) + seed_list.append([None for i in range(count - 1, count + avg_bulk_input_size - 1)]) + count += avg_bulk_input_size + + fs_results, fp_results, samples_results = self.monte_carlo_failure_probability_concurrent_future( + self.monte_carlo_failure_probability_bulk_input, num_workers, + inventory_args, seed_list) + self.set_result_csv_data("sample_failure_state", + fs_results, name=self.get_parameter("result_name") + "_failure_state") + self.set_result_csv_data("failure_probability", + fp_results, name=self.get_parameter("result_name") + "_failure_probability") + self.set_result_csv_data("sample_damage_states", + samples_results, name=self.get_parameter("result_name") + "_sample_damage_states") + return True + + def monte_carlo_failure_probability_concurrent_future(self, function_name, + parallelism, *args): + """Utilizes concurrent.future module. + + Args: + function_name (function): The function to be parallelized. + parallelism (int): Number of workers in parallelization. + *args: All the arguments in order to pass into parameter function_name. + + Returns: + list: A list of dictionary with id/guid and failure state for N samples. + list: A list dictionary with failure probability and other data/metadata. + + """ + fs_output = [] + fp_output = [] + samples_output = [] + with concurrent.futures.ProcessPoolExecutor( + max_workers=parallelism) as executor: + for fs_ret, fp_ret, samples_ret in executor.map(function_name, *args): + fs_output.extend(fs_ret) + fp_output.extend(fp_ret) + samples_output.extend(samples_ret) + + return fs_output, fp_output, samples_output + + def monte_carlo_failure_probability_bulk_input(self, damage, seed_list): + """Run analysis for monte carlo failure probability calculation + + Args: + damage (obj): An output of building/bridge/waterfacility/epn damage that has damage interval. + seed_list (list): Random number generator seed per building for reproducibility. + + Returns: + fs_results (list): A list of dictionary with id/guid and failure state for N samples + fp_results (list): A list dictionary with failure probability and other data/metadata. + + """ + damage_interval_keys = self.get_parameter("damage_interval_keys") + failure_state_keys = self.get_parameter("failure_state_keys") + num_samples = self.get_parameter("num_samples") + + fs_result = [] + fp_result = [] + samples_output = [] + + i = 0 + for dmg in damage: + fs, fp, samples_result = self.monte_carlo_failure_probability(dmg, damage_interval_keys, failure_state_keys, + num_samples, seed_list[i]) + fs_result.append(fs) + fp_result.append(fp) + samples_output.append(samples_result) + i += 1 + + return fs_result, fp_result, samples_output + + def monte_carlo_failure_probability(self, dmg, damage_interval_keys, + failure_state_keys, num_samples, seed): + """Calculates building damage results for a single building. + + Args: + dmg (obj): Damage analysis output for a single entry. + damage_interval_keys (list): A list of the name of the damage intervals. + failure_state_keys (list): A list of the name of the damage state that is considered as failed. + num_samples (int): Number of samples for mc simulation. + seed (int): Random number generator seed for reproducibility. + + Returns: + dict: A dictionary with id/guid and failure state for N samples + dict: A dictionary with failure probability and other data/metadata. + dict: A dictionary with id/guid and damage states for N samples + + """ + # failure state + fs_result = collections.OrderedDict() + + # sample damage states + samples_result = collections.OrderedDict() + + # copying guid/id column to the sample damage failure table + if 'guid' in dmg.keys(): + fs_result['guid'] = dmg['guid'] + samples_result['guid'] = dmg['guid'] + + elif 'id' in dmg.keys(): + fs_result['id'] = dmg['id'] + samples_result['id'] = dmg['id'] + else: + fs_result['id'] = 'NA' + samples_result['id'] = 'NA' + + # failure probability + fp_result = collections.OrderedDict() + fp_result['guid'] = dmg['guid'] + + ds_sample = self.sample_damage_interval(dmg, damage_interval_keys, + num_samples, seed) + func, fp = self.calc_probability_failure_value(ds_sample, failure_state_keys) + + fs_result['failure'] = ",".join(func.values()) + fp_result['failure_probability'] = fp + samples_result['sample_damage_states'] = ','.join(ds_sample.values()) + + return fs_result, fp_result, samples_result + + def sample_damage_interval(self, dmg, damage_interval_keys, num_samples, seed): + """ + Dylan Sanderson code to calculate the Monte Carlo simulations of damage state. + + Args: + dmg (dict): Damage results that contains dmg interval values. + damage_interval_keys (list): Keys of the damage states. + num_samples (int): Number of simulation. + seed (int): Random number generator seed for reproducibility. + + Returns: + dict: A dictionary of damage states. + + """ + ds = {} + random_generator = np.random.RandomState(seed) + for i in range(num_samples): + # each sample should have a unique seed + rnd_num = random_generator.uniform(0, 1) + prob_val = 0 + flag = True + for ds_name in damage_interval_keys: + + if rnd_num < prob_val + AnalysisUtil.float_to_decimal(dmg[ds_name]): + ds['sample_{}'.format(i)] = ds_name + flag = False + break + else: + prob_val += AnalysisUtil.float_to_decimal(dmg[ds_name]) + if flag: + print("cannot determine MC damage state!") + break + + return ds + + def calc_probability_failure_value(self, ds_sample, failure_state_keys): + """ + Lisa Wang's approach to calculate a single value of failure probability. + + Args: + ds_sample (dict): A dictionary of damage states. + failure_state_keys (list): Damage state keys that considered as failure. + + Returns: + float: Failure state on each sample 0 (failed), 1 (not failed). + float: Failure probability (0 - 1). + + """ + count = 0 + func = {} + for sample, state in ds_sample.items(): + if state in failure_state_keys: + func[sample] = "0" + count += 1 + else: + func[sample] = "1" + if len(ds_sample): + return func, count / len(ds_sample) + else: + return func, np.nan diff --git a/tests/pyincore/analyses/montecarlolimitstateprobability/test_montecarlolimitstateprobability.py b/tests/pyincore/analyses/montecarlolimitstateprobability/test_montecarlolimitstateprobability.py new file mode 100644 index 000000000..9828ce8c3 --- /dev/null +++ b/tests/pyincore/analyses/montecarlolimitstateprobability/test_montecarlolimitstateprobability.py @@ -0,0 +1,30 @@ +from pyincore.client import IncoreClient +from pyincore.analyses.montecarlolimitstateprobability import \ + MonteCarloLimitStateProbability +import pyincore.globals as pyglobals + + +def run_with_base_class(): + client = IncoreClient(pyglobals.INCORE_API_DEV_URL) + mc = MonteCarloLimitStateProbability(client) + + # # example of using local dataset + # damage_dataset = Dataset.from_file("memphis_bldg_dmg_result.csv", + # "ergo:buildingDamageVer4") + # mc.set_input_dataset("damage", damage_dataset) + mc.load_remote_input_dataset("damage", "602d96e4b1db9c28aeeebdce") + mc.set_parameter("result_name", "building_damage") + mc.set_parameter("num_cpu", 8) + mc.set_parameter("num_samples", 10) + mc.set_parameter("damage_interval_keys", + ["DS_0", "DS_1", "DS_2", "DS_3"]) + mc.set_parameter("failure_state_keys", ["DS_1", "DS_2", "DS_3"]) + + # optional parameter + mc.set_parameter("seed", 2) + + mc.run_analysis() + + +if __name__ == '__main__': + run_with_base_class()