Skip to content
Merged
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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'
Expand Down
3 changes: 2 additions & 1 deletion src/atc138/functionality/other_functionality_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'''
Expand Down
1 change: 1 addition & 0 deletions src/atc138/input_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")]

Expand Down
50 changes: 33 additions & 17 deletions src/atc138/red_tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good

(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)
Expand All @@ -69,30 +71,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])

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a computational benefit to reducing the array dimensions here? I typically like to keep the "meanings" of array dimensions consistent where possible (ie rows = realizations, columns = comps), for more traceable matrix calculations, but that may be more of my matlab bent, and the traditional python approach may be to keep things as dimensionally simple as possilbe.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Dustin, good point. I think it’s less about computational benefit and more about matching the physical meaning.

ser_dmg varies across realizations, so keeping it 2D (reals × series) makes sense. But ser_qty is just the installed quantity and shouldn't vary with realizations, so I think it's best to keep it 1D.

When it was 2D before, it would cause issues in some models. For example, reductions could be mathematically wrong, and empty selections can cause the analysis to error out.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will push back a bit on the physical meaning; the quantity of components can change with realization, if you are uncertain of the quantity (in fact I think PELICUN supports this). However, we just dont vary this in the examples (and probably dont robustly support it downstream). However, if its causing problems, then that is a bigger issue, and I am happy with the change.


# 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)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes seem good


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)
Expand Down Expand Up @@ -160,7 +175,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)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, so this is the third place where this exact same calc is occuring. Seems like we need to caluclate "replace_case" earlier, in preprocesseing, or as part of the build process.

red_tag[replace_case] = 1

return red_tag, red_tag_impact, inspection_tag
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down