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 .github/workflows/test_urls.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ jobs:
ping_urls:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: 3.9
python-version: 3.12

- name: Install testing dependencies
run: |
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ make_compton = "openmc_data.other.make_compton:main"
make_stopping_powers = "openmc_data.other.make_stopping_powers:main"
add_branching_ratios = "openmc_data.depletion.add_branching_ratios:main"
combine_libraries = "openmc_data.other.combine_libraries:main"
remove_corrupt_h5 = "openmc_data.other.remove_corrupt_h5:main"
sample_sandy = "openmc_data.other.sample_sandy:main"
reduce_chain = "openmc_data.depletion.reduce_chain:main"

Expand Down
93 changes: 93 additions & 0 deletions src/openmc_data/other/remove_corrupt_h5.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!/usr/bin/env python3
"""Scan a directory of OpenMC HDF5 data files and remove any that are corrupt.

Useful after an interrupted ``generate_*`` / ``convert_*`` run (e.g. Ctrl-C).
Those scripts resume by skipping ENDF/ACE files whose output ``.h5`` already
exists, checking only for existence -- not integrity. A file left half-written
when the process was killed is therefore treated as "done" and silently kept in
the library. Running this first deletes any unreadable ``.h5`` files so that a
rerun regenerates them.

Examples
--------
# Remove any truncated/corrupt files (fast, opens each with h5py)
remove_corrupt_h5 tendl-2025-hdf5

# Also fully load each file with openmc (slower, neutron data only)
remove_corrupt_h5 tendl-2025-hdf5 --deep

# Report without deleting
remove_corrupt_h5 tendl-2025-hdf5 --dry-run
"""
import argparse
import glob
import os

import h5py

try:
import openmc.data
except ModuleNotFoundError:
openmc = None


def check_file(path, deep=False):
"""Return an error string if ``path`` is corrupt, otherwise ``None``.

The default check opens the file with h5py, which catches the common
failure mode of a truncated write. ``deep=True`` additionally loads the
file with :class:`openmc.data.IncidentNeutron`.
"""
try:
with h5py.File(path, "r"):
pass
except Exception as e: # noqa: BLE001 - report any read failure
return str(e)
if deep:
if openmc is None:
raise ModuleNotFoundError(
"openmc is required for --deep validation but is not installed."
)
try:
openmc.data.IncidentNeutron.from_hdf5(path)
except Exception as e: # noqa: BLE001 - report any load failure
return str(e)
return None


def main():
parser = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument(
"directory", help="Directory containing the .h5 files to check"
)
parser.add_argument(
"--deep",
action="store_true",
help="Also fully load each file with openmc (slower, neutron data only)",
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Report corrupt files without deleting them",
)
args = parser.parse_args()

files = sorted(glob.glob(os.path.join(args.directory, "*.h5")))
print(f"Scanning {len(files)} .h5 files in {args.directory} ...")
removed = 0
for f in files:
reason = check_file(f, deep=args.deep)
if reason is not None:
action = "would remove" if args.dry_run else "removing"
print(f"{action} corrupt: {f} -> {reason}")
if not args.dry_run:
os.remove(f)
removed += 1
verb = "corrupt" if args.dry_run else "removed"
print(f"Done, {removed} {verb} file(s); {len(files) - removed} intact")


if __name__ == "__main__":
main()
Loading