Skip to content

Pelicun conversion module#8

Open
hgp297 wants to merge 29 commits intoOpenPBEE:mainfrom
hgp297:convert_pelicun
Open

Pelicun conversion module#8
hgp297 wants to merge 29 commits intoOpenPBEE:mainfrom
hgp297:convert_pelicun

Conversation

@hgp297
Copy link
Collaborator

@hgp297 hgp297 commented Mar 3, 2026

Summary

Added a Pelicun conversion method to input_builder.py. All major changes are implemented in input_builder.convert_pelicun(), and core ATC-138 functions are unmodified.

Key Changes

1. Support for Pelicun outputs as input files

  • input_builder.convert_pelicun will be called if Pelicun files are found in a model directory
  • build_simulated_inputs() now searches for DMG_sample.csv as an indicator of Pelicun outputs
  • Required input files described in README
  • Added force_rebuild_flag in driver.run_analysis() and cli.py to allow overriding of existing simulated_inputs
  • Added examples/RCSW_4story_pelicun

2. Reorganized plotting module

  • Fixed plotting modules for Python conversion
  • Moved plotting under plotters/main_plot_functionality
  • Added p_gantt as an argument to plot realizations other than median

3. Hotfixes

  • build_inputs.py -> build_simulated_inputs(): check for tenant_units to populate now matches, and for story vice versa (Under # Write in individual dictionaries...)
  • functionality.other_functionality_functions: Fixed typo in sys.exit

4. Documentation

  • Updated README: Updated README.md to reflect the package changes.
    • Description of required inputs
    • Added sample run instructions
    • Major assumptions and unimplemented features documented under HP: comments

5. Implementation notes

  • Examples for Pelicun requires a custom damage_state_attribute_mapping.csv to override is_sim_ds for elevators
  • Damage to cladding is assigned random sides
  • Currently does not simulate racked door damage

Verification

  • Tested on PBE application v4.4.2, which implements Pelicun >= 3.8.1
  • Example Run: Ran the RCSW_4story_pelicun example:
from src.atc138 import driver

example_dir = './examples/RCSW_4story_pelicun'
output_dir = './examples/RCSW_4story_pelicun/output'

driver.run_analysis(example_dir, output_dir, seed=985)

from plotters.main_plot_functionality import plot_results
output_dir = './examples/RCSW_4story_pelicun/output'
plot_results(output_dir, p_gantt=50)
  • Results:
Building simulated_inputs.json from raw inputs...
found custom damage_state_attribute_mapping.csv in inputs. Overriding static tables...
Pelicun outputs found in ./examples/RCSW_4story_pelicun. Attempting build...
Recovery assessment complete
time to run 7.26s

Plots generated

Added a Pelicun conversion method to `input_builder.py`. This commit is
not yet functional due to incomplete simulated_damage.json outputs.

Changes:
	- build_simulated_inputs() now searches for DMG_sample.csv as an
	  indicator of Pelicun outputs
	- convert_pelicun() builds raw inputs using Pelicun outputs.
	- (hotfix) in build_inputs.py -> build_simulated_inputs():
		- check for 'tenant_units' to populate now matches, and
		  for 'story' vice versa
		- Under # Write in individual dictionaries...

Input:
	- CMP_QNT.csv
	- DL_summary.csv
	- DMG_sample.csv
	- DV_repair_sample.csv
	- general_inputs.json
	- input.json

Output:
	- building_model.json
	- damage_consequences.json
	- comp_ds_list.csv
	- comp_population.csv
	- simulated_damage.json
- Changed file structure to match src/ reorganization
- Temporarily handled edge case where `repair_start_day` is all NaN by
  replacing it with 0.0
- Removed x-lim on recovery plot as it is tied to currently faulty "Full
  recovery day"
- driver.run_analysis(force_rebuild=True) will now rebuild
  simulated_inputs.json even if existing file is found.
plotters/main_plot_functionality

- Removed plotModel_PBEE_recovery.py (moved under plotters for future
  archiving)
- Added plot_results() function to plotters.main_plot_functionality
- For input_builder.csv, damage for each side (affecting cladding) is
randomly assigned a percentage summing up to 1.0
- Separated "tenant_units" and "story" from simulated_damage.json.
  "Story" now only includes the ["qnt_damaged_dir_n"] keys.

Future implementation should consider allowing for side damage to be
specified via general_inputs, e.g.
- Set seed occurs earlier to account for randomness in side damage
  (cladding) assignment
- Fixed overwrite in comp_population.csv to prevent resetting of
  component quantity if same component is listed multiple times in
CMP_QNT.csv
- allow users to specify engineering cost as a fraction of repair cost
  in general_inputs.json
- updated example `general_inputs.json` to reflect this
- removed json typo error
- removed edge case NaN handling in other_plot_functions
@hgp297 hgp297 requested review from dustin-cook and zsarnoczay March 3, 2026 19:54
hgp297 added 2 commits March 4, 2026 10:51
- Allow users to specify `side_damage_ratio.csv`, an array to split
  cladding damage by direction
- Elevators and stair count can now be overridden from `CMP_QNT.csv` using
  `general_input.json`
- Default action for simultaneous damage state (elevators) is now to
  turn off the `is_sim_ds` flag without needing a custom
`damage_state_attribute_mapping.csv`. Users are warned about
simultaneous ds in multiple PGs and are recommended to change
component.
- Added robustness in cmp-loc-dir-ds metatag detection and reorder
  appropriately in DMG and DV samples.
- doc: Added default and override instructions to README for the above.
Copy link
Collaborator

@zsarnoczay zsarnoczay left a comment

Choose a reason for hiding this comment

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

What I did

  • Checked out the PR locally.
  • Confirmed the example in the PR description runs end-to-end as written.
  • Reviewed every file included in the PR, with an emphasis on Pelicun support (cli.py, driver.py, input_builder.py, README.md) and plotting utilities.

Summary

Overall, this PR looks solid and close to ready. I am not approving yet because there are a small number of blocking comments below that I believe should be addressed before merge. Most other items are non-blocking or future-looking and can be deferred.

Comment labels used below

  • blocking: Must be addressed before merging (high-impact correctness/robustness, or very low-effort fixes that prevent likely user confusion).
  • non-blocking: Nice-to-have improvements that could land in this PR, but I’m fine moving them to issues or a follow-up.
  • future: Larger scope, design/documentation items, or work that warrants a dedicated issue. I included a link to the corresponding issue in each of these comments.

parser.add_argument("input_dir", help="Path to the directory containing input files (e.g., simulated_inputs.json)")
parser.add_argument("output_dir", help="Path to the directory where outputs will be saved")
parser.add_argument("--seed", type=int, help="Random seed for reproducibility", default=None)
parser.add_argument("--force_rebuild", type=bool, help="Flag to force override of simulated_inputs.json and rebuild", default=False)
Copy link
Collaborator

Choose a reason for hiding this comment

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

non-blocking:
Switch --force_rebuild to action='store_true' for ergonomic CLI behavior. With type=bool, users must pass a value (e.g., --force_rebuild True). The conventional approach is:

  • parser.add_argument("--force_rebuild", action="store_true", help="…")

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Addressed in 2f9c071

# ds attributes
static_data_dir = os.path.join(os.path.dirname(__file__), 'data')

ds_attributes = pd.read_csv(
Copy link
Collaborator

Choose a reason for hiding this comment

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

blocking:
Honor model-level overrides for damage_state_attribute_mapping.csv by using load_custom_static_tables(model_dir, static_data_dir, 'damage_state_attribute_mapping.csv') instead of loading directly from the packaged static directory. This aligns with how other static tables are loaded and enables per-model customizations. It’s a low-risk, high-value robustness fix and prevents confusing behavior when users provide an override that is silently ignored.

Proposed change (conceptual):

  • static_data_dir = os.path.join(os.path.dirname(file), 'data')
  • ds_attributes = load_custom_static_tables(model_dir, static_data_dir, 'damage_state_attribute_mapping.csv')

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Addressed in 52f0ced


# remove the units row
damage = damage[:-1]
dvs = dvs[:-1]
Copy link
Collaborator

Choose a reason for hiding this comment

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

blocking:
Drop the "Units" row explicitly rather than by position when removing preamble rows from Pelicun CSVs. Position-based slicing can silently remove valid data if the file structure changes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Addressed in 52f0ced


with open(os.path.join(model_dir, 'input.json')) as file:
pelicun_inputs = json.load(file)
file.close()
Copy link
Collaborator

Choose a reason for hiding this comment

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

non-blocking:
Remove the explicit file.close() when using with open(...). Context managers automatically close files even on exceptions. This is a small cleanup that reduces noise:

  • with open(os.path.join(model_dir, 'input.json')) as file:
    pelicun_inputs = json.load(file)

    no file.close() needed

Do the same for general_inputs.json below.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Addressed in 52f0ced

df.columns = new_cols
return df

def story_mask(location, num_stories):
Copy link
Collaborator

Choose a reason for hiding this comment

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

non-blocking:
Support 'top' as an alias for the roof story to improve Pelicun compatibility.

Also note that roof assigns components above the top story. For example, in a 3-story building, top assigns to story 3 while roof assigns to story 4. This is important to get the correct roof accelerations for equipment put on the roof slab as opposed to the slab of the top floor.

See the corresponding code in Pelicun for further details: https://github.com/NHERI-SimCenter/pelicun/blob/73111704f36022268cb8b87989c413b53af6f9f0/pelicun/model/pelicun_model.py#L328

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

"top" and "roof" now both place the component at the highest location available. However, from the examples I checked, there are no component placed at locations above the top story (i.e. there is no explicit roof). To support this, n_stories buildings should be expanded to support n_stories+1 location, then roof components should be placed (and repaired/tagged) there accordingly.

Recommend this be classified as "future", tracked at issue #18

- **systems.csv**: Attributes of each default ssytem considered in the method.
- **temp_repair_class.csv**: Attributes of each temprary repair class considered in the method.

## Building from Pelicun outputs
Copy link
Collaborator

Choose a reason for hiding this comment

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

non-blocking:
Polish the README Pelicun section to provide additional details and improve user experience:

  • Note that if simulated_inputs.json exists, the builder will not re-run unless --force_rebuild is used.
  • Mention that DV_bldg_repair_sample.csv is supported as a fallback name for decision variables.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Addressed in 2f9c071

Copy link
Collaborator

Choose a reason for hiding this comment

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

future:
Decide whether to keep this legacy (?) script. If retained, clearly mark it as legacy and align its behavior with the recommended entry point - currently it is not working properly; otherwise, remove it to reduce redundancy. No need to modify it in this PR.

Created #15 to track

Copy link
Collaborator

Choose a reason for hiding this comment

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

non-blocking:

  • Add a short "Plotting outputs" note pointing to plotters/main_plot_functionality.plot_results(outputs_dir, p_gantt=50) as the recommended entry point.
  • Add brief module-level docstrings clarifying roles of main_plot_functionality.py, other_plot_functions.py.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Addressed in 2f9c071


from copy import deepcopy

with open(os.path.join(model_dir, 'input.json')) as file:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Consider removing input.json as a hard requirement by moving its few fields (number of stories, replacement cost, plan area) into general_inputs.json.

Created #16 to track

# calculate repair cost ratio
# HP: check nomenclature "-" vs "_"

# HP: missing racked stair doors per story, racked entry doors
Copy link
Collaborator

Choose a reason for hiding this comment

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

Created #17 to track

hgp297 added 2 commits March 12, 2026 12:27
- damage_state_attribute_mapping.csv loading now uses
  load_custom_static_tables method
- drop the Units row now uses the first column (cmp-loc-dir indicator)
  to explicitly identify "unit" string (case insensitive)
- removed file.close()
- Switched CLI --force_rebuild argument to store_true to add
  default=False behavior
- Aliased "top" for "roof" in location assignment. This location is set
  to be the highest location in the building. e.g. for a 3-story
building, both roof and top would place the component at [1, 2, (3)]. Recommend future
  expand to support explicit roof.
- clean_frag_id refactored to split on '.' and rejoin in two parts
- gated comp_population to only components verified in comp_ds_list
- reuse clean_frag_id wherever period formatting happens
- Removed nested import of re
- Added --force_rebuild details to README
- Added plotters detail to README
- Added module-level docstring to plotters/main_plot_functionality and
  plotters/other_plot_functions.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants