From 25014489902c01438229d118f526640a6bf47089 Mon Sep 17 00:00:00 2001 From: hgp297 Date: Tue, 14 Apr 2026 17:32:02 -0700 Subject: [PATCH 1/5] fix(red_tag): ZW fix for type-cast replace_case and scalar ser_qty Original commit from ZW (https://github.com/hgp297/Functional-Recovery-Python/commit/f262f1dc4b244da6fb1b1caf69b749025712a80d) may have been lost in git history cleanup. - force float conversion of replace_case and sim_rt to ensure no non-numeric items and enhance robustness - ser_qty now is 1D and scalar for edge cases where no component matched leading to len(series) == 0 --- src/atc138/red_tag.py | 46 ++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/src/atc138/red_tag.py b/src/atc138/red_tag.py index 18603f5..1b4525b 100644 --- a/src/atc138/red_tag.py +++ b/src/atc138/red_tag.py @@ -69,30 +69,43 @@ def simulate_tagging(damage, comps, sc_ids, sc_thresholds, red_tag_options ): # Check damage among each series within this structural system series = np.unique(np.array(damage['comp_ds_table']['structural_series_id'])[ss_filt_ds]) - ser_dmg = np.zeros([num_reals,len(series)]) - ser_qty = np.zeros([1,len(series)]) + + # Allocate ONCE (outside the ser loop) + ser_dmg = np.zeros((num_reals, len(series))) # [num_reals x num_series] + ser_qty = np.zeros(len(series)) # [num_series] + for ser in range(len(series)): - ser_filt_ds = np.array(damage['comp_ds_table']['structural_series_id']) == series[ser] - ser_filt_comp = np.array(comps['comp_table']['structural_series_id']) == series[ser] - - # Total damage within this series and system - ser_dmg[:,ser] = np.sum(sc_dmg[:,ser_filt_ds & ss_filt_ds], axis=1) - - # Total number of components within this series and system - ser_qty[:,ser] = np.sum(num_comps[ser_filt_comp & ss_filt_comp]) + ser_filt_ds = np.array(damage['comp_ds_table']['structural_series_id']) == series[ser] + ser_filt_comp = np.array(comps['comp_table']['structural_series_id']) == series[ser] + + # Total damage within this series and system (per realization) + ser_dmg[:, ser] = np.sum(sc_dmg[:, (ser_filt_ds & ss_filt_ds)], axis=1) + + # Total number of components within this series and system (constant across realizations) + ser_qty[ser] = np.sum(num_comps[ser_filt_comp & ss_filt_comp]) # Check if this system is causing a red tag if structural_systems[sys] == 12 and bool(red_tag_options.get('tag_coupling_beams_over_height', False)): # HARED CODED CHECK FOR COUPLING BEAMS # just do counts for now instead of adding to red tag per story check coupling_beam_dmg[:, direc-1] = coupling_beam_dmg[:,direc-1] + np.nanmax(ser_dmg, axis = 1) - coupling_beam_qty[0, direc-1] = coupling_beam_qty[0,direc-1] + np.nanmax(ser_qty, axis = 1) + coupling_beam_qty[0, direc-1] = coupling_beam_qty[0, direc-1] + np.nanmax(ser_qty) red_tag_impact_cb_sc = np.fmax(red_tag_impact_cb_sc, ss_filt_ds.reshape(1,-1) * sc_filt.reshape(1,-1) * (sc_dmg>0)) else: - sys_dmg = np.nanmax(ser_dmg, axis = 1) - sys_qty = np.nanmax(ser_qty, axis = 1) - sys_ratio = sys_dmg / sys_qty - sys_tag[:,sys] = sys_ratio > sc_thresholds[sc] + empty_bin = (ser_dmg.size == 0 or ser_dmg.shape[1] == 0 or ser_qty.size == 0) + + if empty_bin: + sys_ratio = np.zeros((num_reals,), dtype=float) # implies "not tagged" for this bin + else: + sys_dmg = np.nanmax(ser_dmg, axis=1) # (num_reals,) + sys_qty = float(np.nanmax(ser_qty)) # scalar + + # avoid division by 0 / NaN issues + sys_ratio = np.zeros((num_reals,), dtype=float) + if np.isfinite(sys_qty) and sys_qty > 0: + ok = np.isfinite(sys_dmg) + sys_ratio[ok] = sys_dmg[ok] / sys_qty + sys_tag[:, sys] = sys_ratio > sc_thresholds[sc] '''Calculate the impact that each component has on red tag (boolean, 1 = affects red tag, 0 = does not affect) @@ -160,7 +173,8 @@ def simulate_tagging(damage, comps, sc_ids, sc_thresholds, red_tag_options ): # Account for global red tag cases - replace_case = np.logical_not(np.isnan(np.array(simulated_replacement_time))) + sim_rt = np.array(simulated_replacement_time, dtype=float) + replace_case = ~np.isnan(sim_rt) red_tag[replace_case] = 1 return red_tag, red_tag_impact, inspection_tag From 15046b62e30cff51296ee4ef68254994a3030a49 Mon Sep 17 00:00:00 2001 From: hgp297 Date: Tue, 14 Apr 2026 17:41:32 -0700 Subject: [PATCH 2/5] fix(other_functionality & other_repair_schedule): force numeric - Force conversion of simulated_replacement_time to numeric in case of non-numeric cases (same fix as in red_tag.py) --- src/atc138/functionality/other_functionality_functions.py | 3 ++- src/atc138/repair_schedule/other_repair_schedule_functions.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/atc138/functionality/other_functionality_functions.py b/src/atc138/functionality/other_functionality_functions.py index 5442e92..1282f59 100644 --- a/src/atc138/functionality/other_functionality_functions.py +++ b/src/atc138/functionality/other_functionality_functions.py @@ -1540,7 +1540,8 @@ def fn_extract_recovery_metrics( tenant_unit_recovery_day, # Determine replacement cases - replace_cases = np.logical_not(np.isnan(simulated_replacement_time)) + sim_rt = np.array(simulated_replacement_time, dtype=float) + replace_cases = ~np.isnan(sim_rt) ''' Post process tenant-level recovery times Overwrite NaNs in tenant_unit_day_functional Only NaN where never had functional loss, therefore set to zero''' diff --git a/src/atc138/repair_schedule/other_repair_schedule_functions.py b/src/atc138/repair_schedule/other_repair_schedule_functions.py index 4a1aa41..a09d99c 100644 --- a/src/atc138/repair_schedule/other_repair_schedule_functions.py +++ b/src/atc138/repair_schedule/other_repair_schedule_functions.py @@ -813,7 +813,8 @@ def fn_format_gantt_chart_data( damage, systems, simulated_replacement_time): comps = np.unique(damage['comp_ds_table']['comp_id']) # Determine replacement cases - replace_cases = np.logical_not(np.isnan(simulated_replacement_time)) + sim_rt = np.array(simulated_replacement_time, dtype=float) + replace_cases = ~np.isnan(sim_rt) ## Reformat repair schedule data into various breakdowns # Per component From 5e4c649fb054f2bb5460726c618ecbd2fcd1011d Mon Sep 17 00:00:00 2001 From: hgp297 Date: Wed, 15 Apr 2026 16:23:33 -0700 Subject: [PATCH 3/5] fix(red_tag): concatenate struct_sys_alt - Fixed a bug to concatenate the two sets of structural systems and subassemblies (e.g. 5=SCBF, 6=SMRF) rather than adding them. i.e. result of structural_systems should be [5,6] and not [11] --- src/atc138/red_tag.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/atc138/red_tag.py b/src/atc138/red_tag.py index 1b4525b..7a87644 100644 --- a/src/atc138/red_tag.py +++ b/src/atc138/red_tag.py @@ -54,7 +54,9 @@ def simulate_tagging(damage, comps, sc_ids, sc_thresholds, red_tag_options ): num_comps = np.array(comps['story'][s]['qty_dir_' + str(direc)]) # For each structural system - structural_systems = np.unique(np.array(damage['comp_ds_table']['structural_system'] + damage['comp_ds_table']['structural_system_alt'])) + structural_systems = np.unique(np.concatenate( + (np.array(damage['comp_ds_table']['structural_system']), np.array(damage['comp_ds_table']['structural_system_alt'])) + )) structural_systems = np.delete(structural_systems, 0) # do not include components not assigned to a structural system sys_tag = np.zeros([num_reals, len(structural_systems)]).astype(bool) From f98adfaa2384b9fa98a4204606df9a3d71d1be5d Mon Sep 17 00:00:00 2001 From: hgp297 Date: Thu, 16 Apr 2026 14:45:08 -0700 Subject: [PATCH 4/5] docs: Assert no duplicates using uid - Detects `uid` in meta-tags and throws an error to the user to manually condense duplicate cmp-loc-dir-ds cases. - This case is not encountered if inputs were generated using PBE tool --- src/atc138/input_builder.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/atc138/input_builder.py b/src/atc138/input_builder.py index 1918205..438407a 100644 --- a/src/atc138/input_builder.py +++ b/src/atc138/input_builder.py @@ -740,6 +740,7 @@ def convert_pelicun(model_dir): accepted_first_tag = ['cmp', 'dmg', 'loss'] assert any(item in accepted_first_tag for item in dv_tag_meta), "Missing meta-tag in index column (i.e. 'cmp/dmg/loss-loc-dir-ds' must be provided at minimum)" assert set(reqd_tags) <= set(dv_tag_meta), "Missing meta-tag in index column (i.e. 'cmp/dmg/loss-loc-dir-ds' must be provided at minimum)" + assert 'uid' not in dv_tag_meta, "uid found in DV input meta-tags. Duplicate cmp/dmg/loss-loc-dir-ds is not supported via uid; Condense duplicate cmp/dmg/loss-loc-dir-ds" DV_time = dvs.loc[:, dvs.columns.str.upper().str.startswith("TIME")] DV_cost = dvs.loc[:, dvs.columns.str.upper().str.startswith("COST")] From e50020f2b8995ff73ee77aef19e7adc39977f44f Mon Sep 17 00:00:00 2001 From: hgp297 Date: Thu, 16 Apr 2026 14:53:55 -0700 Subject: [PATCH 5/5] doc: changed import examples on README - Script examples for import usage now follows installation behavior (i.e. no mentioning of path, importing atc138 vs. src.atc138) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 45bcd35..e9c4a05 100644 --- a/README.md +++ b/README.md @@ -68,10 +68,10 @@ python -m atc138.cli ./examples/ICSB ./examples/ICSB/output --seed 12345 --force ### Imported via Python script -Ensure that the `src/` directory is on the path of the main script. Then: +Once installed, the package can also be imported in scripts: ```python -from src.atc138 import driver +from atc138 import driver example_dir = './examples/ICSB' output_dir = './examples/ICSB/output' @@ -216,7 +216,7 @@ To use Pelicun outputs, ensure `simulated_inputs.json` does not exist in your mo The assessment will automatically detect Pelicun files and convert them to the standard ATC-138 format. Use the same CLI or Python API as normal: ```python -from src.atc138 import driver +from atc138 import driver example_dir = './examples/RCSW_4story_pelicun' output_dir = './examples/RCSW_4story_pelicun/output'