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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
# Python caches
__pycache__/

# Verification results
# Generated results
/results/
/release/

# Simulation output
output*
Expand Down
20 changes: 14 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,38 @@ Target release: 0.16.0, the first versioned MC/DC-VVP release.

### Added

- Add Kornreich k-eigenvalue slabs problem
- Add the analytical neutron $k$-eigenvalue MMS one-group slab, from [@ilhamv]
- Add the analytical neutron fixed-source MMS two-group slab, from [@ilhamv]
- Add the analytical neutron $k$-eigenvalue suite with subcritical and supercritical SHEM-361 cases, analytical matrix-eigenvalue references, active-cycle convergence studies, and uncertainty plots, from [@ilhamv]
- Add the analytical neutron $k$-eigenvalue suite with a semi-analytical homogeneous one-group slab, the Kornreich-Parsons heterogeneous slab benchmark, subcritical and supercritical infinite-medium SHEM-361 cases, active-cycle convergence studies, and uncertainty plots, from [@ilhamv]
- Add the analytical neutron fixed-source manufactured two-group slab, from [@ilhamv]
- Add energy-dependent weight-window and time-census variants of the infinite homogeneous SHEM-361 problem, from [@ilhamv]
- Add AZURV1 variants for basic variance-reduction techniques, analytical spatial weight windows, time censuses, and census-based tallies, from [@ilhamv]
- Add a neutron code-to-code verification suite for the C5G7 four-phase and Kobayashi dog-leg transients, including archived participating-code data, fixed largest-sample references, convergence metrics, and animated reference, comparison, and difference results, from [@ilhamv]
- Add preliminary SINBAD model scaffolds for OKTAVIAN Si-60, FNG SiC, FNG/TUD SiC, and RFNC photon-compound experiments based on their public benchmark entries, from [@ilhamv]
- Add top-level and suite-level READMEs describing layouts, configuration, launching, processing, cases, and references, from [@ilhamv]
- Add shared platform, user, and launch configuration for local and HPC campaigns, from [@ilhamv]
- Add top-level cleanup, result collection, and flat GitHub release-asset preparation workflows, from [@ilhamv]
- Add Black formatting checks through pre-commit and GitHub Actions, from [@ilhamv]

### Changed

- Distribute the analytical fixed-source slab cases across the $x$, $y$, and $z$ axes to exercise every Cartesian slab orientation, from [@ilhamv]
- Migration to Maestro-based launch, from [@ilhamv]
- Update analytical fixed-source cases for the simulation-owned MC/DC interface and unified material model, from [@ilhamv]
- Standardize **suite** and **case** as the VVP repository's organizational terminology, from [@ilhamv]
- Standardize **suite**, **case**, and **task** as the VVP repository's organizational terminology, from [@ilhamv]
- Organize fixed-source cases around consistent input, reference, processing, and optional plotting scripts, from [@ilhamv]
- Reorganize legacy neutron benchmarks as code-to-code verification cases without separate continuous-energy and multigroup directory levels, from [@ilhamv]
- Use `N_node` for HPC resource selection, apply case-specific `walltime_factor` values to suite base walltimes, and skip completed tasks while retaining partial results, from [@ilhamv]
- Organize processed results into convergence, reference, and comparison outputs and collect them through the top-level processing workflow, from [@ilhamv]
- Consolidate the canonical SHEM-361 multigroup dataset under the analytical fixed-source suite for reuse by all SHEM-361 cases, from [@ilhamv]

### Deprecated

### Removed

- Remove the legacy continuous-energy pulsed pin-cell cases, from [@ilhamv]

### Fixed

### Security

[Unreleased]: https://github.com/ilhamv/mcdc-vvp/tree/master
[Unreleased]: https://github.com/mcdc-project/mcdc-vvp/tree/dev
[@ilhamv]: https://github.com/ilhamv
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ Workflow orchestration is performed using [Maestro](https://github.com/llnl/maes
configs/ Shared platform, user, and launch configurations
verification/ Verification suites and their cases
results/ Processed results organized by suite
release/ Flattened figures prepared as release assets

launch.py Launch all enabled suites
process.py Process suites and collect their results
cleanup.py Remove generated outputs and processed results
prepare_release.py Prepare figures for a GitHub release
```

MC/DC-VVP uses **suite**, **case**, and **task** as standard terms for its organizational hierarchy:
Expand Down Expand Up @@ -90,6 +92,15 @@ An existing suite `results/` directory can still be collected when no Maestro ru
Within each suite, `convergence/` contains study-wide convergence figures and `comparison/` contains plots or animations from the largest-statistics result.
Collecting a suite replaces that suite's existing top-level results.

Prepare the collected PNG and GIF figures for upload as GitHub release assets:

```bash
python prepare_release.py
```

The script recreates `release/`, copies every figure from `results/`, and replaces each directory boundary in its relative path with `--` to form a unique flat asset name.
The structured files in `results/` are not modified.

Remove generated case outputs and processed results from every registered suite:

```bash
Expand Down
61 changes: 61 additions & 0 deletions prepare_release.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""Prepare flat GitHub release assets from collected VVP results."""

import shutil
from pathlib import Path

# ======================================================================================
# Paths
# ======================================================================================

REPO_DIR = Path(__file__).resolve().parent
RESULTS_DIR = REPO_DIR / "results"
RELEASE_DIR = REPO_DIR / "release"


# ======================================================================================
# Discover result figures
# ======================================================================================

if not RESULTS_DIR.is_dir():
raise FileNotFoundError(f"Results directory not found: {RESULTS_DIR}")

figures = sorted(
path
for path in RESULTS_DIR.rglob("*")
if path.is_file() and path.suffix.lower() in {".png", ".gif"}
)

if not figures:
raise FileNotFoundError(f"No PNG or GIF results found in {RESULTS_DIR}")


# ======================================================================================
# Prepare flat release assets
# ======================================================================================

if RELEASE_DIR.is_dir():
shutil.rmtree(RELEASE_DIR)
RELEASE_DIR.mkdir()

total_size = 0
for source in figures:
relative_path = source.relative_to(RESULTS_DIR)
asset_name = "--".join(relative_path.parts)
destination = RELEASE_DIR / asset_name

if destination.exists():
raise FileExistsError(f"Flattened asset name is not unique: {destination.name}")

shutil.copy2(source, destination)
total_size += destination.stat().st_size
print(f"{relative_path} -> {destination.name}")


# ======================================================================================
# Summary
# ======================================================================================

print()
print(f"Assets: {len(figures)}")
print(f"Size : {total_size / 1024**2:.1f} MiB")
print(f"Release directory: {RELEASE_DIR}")
3 changes: 3 additions & 0 deletions verification/code_to_code/neutron/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ results/
launch_config.yaml Effective suite launch configuration
task.yaml Task-generation configuration used by the launch
convergence/ Statistical-convergence figures
reference/ Largest-sample fixed-reference figures and animations
comparison/ Largest-sample comparison and difference animations

task.yaml Configure task generation for each case
Expand Down Expand Up @@ -97,7 +98,9 @@ python process.py maestro_run_<timestamp>
```

Convergence figures are written to `results/convergence/`.
Fixed-reference figures and animations are written to `results/reference/`.
Animated spatial comparisons and relative-difference evolution at the largest shared sample size are written to `results/comparison/`.
Each case may produce `reference_*.png` figures or `reference_*.gif` animations from the arithmetic mean of the largest-sample participating-code estimates.
Each case produces `comparison.gif` for the participating-code solutions and `difference.gif` for their relative differences.
The top-level `process.py` collects these figures under the repository's `results/` directory.

Expand Down
17 changes: 17 additions & 0 deletions verification/code_to_code/neutron/cases/c5g7-4phase/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
sys.path.insert(0, str(SUITE_DIR))

from util import (
animate_spatial_reference,
comparison_reference,
load_openmc_tally,
particle_counts,
Expand Down Expand Up @@ -61,6 +62,22 @@ def main():
)
openmc_reference = load_openmc_fission(reference_files[-1])
reference = comparison_reference(mcdc_reference, openmc_reference)

# Present the fixed largest-sample reference used by the convergence study.
largest_output = case_dir / f"output_{int(N_particle[-1])}.h5"
with h5py.File(largest_output, "r") as f:
time = f["tallies/tracklength_tally_0/grid/time"][:]
x = f["tallies/tracklength_tally_0/grid/x"][:]
y = f["tallies/tracklength_tally_0/grid/y"][:]
z = f["tallies/tracklength_tally_0/grid/z"][:]
animate_spatial_reference(
"fission",
0.5 * (time[:-1] + time[1:]),
(x, y, z),
reference,
filename="reference_fission.gif",
)

del mcdc_reference, openmc_reference

for index, (count, reference_file) in enumerate(zip(N_particle, reference_files)):
Expand Down
19 changes: 19 additions & 0 deletions verification/code_to_code/neutron/cases/kobayashi/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
sys.path.insert(0, str(SUITE_DIR))

from util import (
animate_spatial_reference,
comparison_reference,
load_openmc_tally,
particle_counts,
Expand Down Expand Up @@ -72,6 +73,24 @@ def main():
mcdc_density_reference,
openmc_density_reference,
)

# Present the fixed largest-sample reference used by the convergence study.
largest_output = case_dir / f"output_{int(N_particle[-1])}.h5"
with h5py.File(largest_output, "r") as f:
time = f["tallies/tracklength_tally_0/grid/time"][:]
x = f["tallies/tracklength_tally_0/grid/x"][:]
y = f["tallies/tracklength_tally_0/grid/y"][:]
z = f["tallies/tracklength_tally_0/grid/z"][:]
animate_spatial_reference(
"flux",
0.5 * (time[:-1] + time[1:]),
(x, y, z),
flux_reference,
history=density_reference,
history_label="Neutron density",
filename="reference_flux.gif",
)

del (
mcdc_flux_reference,
mcdc_density_reference,
Expand Down
26 changes: 22 additions & 4 deletions verification/code_to_code/neutron/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ def clear_case_figures(case_dir):
figure.unlink()


def collect_case_figures(case_dir, destination, case_name):
def collect_case_figures(case_dir, destination, case_name, patterns):
"""Move generated case figures into a named suite results directory."""
for pattern in ("*.png", "*.gif"):
for pattern in patterns:
for figure in case_dir.glob(pattern):
figure.replace(destination / f"{case_name}_{figure.name}")

Expand Down Expand Up @@ -90,13 +90,15 @@ def collect_case_figures(case_dir, destination, case_name):
results_dir = suite_dir / "results"
convergence_dir = results_dir / "convergence"
comparison_dir = results_dir / "comparison"
reference_dir = results_dir / "reference"

# Start with an empty suite results hierarchy on every processing run.
if results_dir.is_dir():
shutil.rmtree(results_dir)

convergence_dir.mkdir(parents=True, exist_ok=True)
comparison_dir.mkdir(parents=True, exist_ok=True)
reference_dir.mkdir(parents=True, exist_ok=True)

# Keep the effective launch and task definitions beside the processed figures.
shutil.copy2(launch_config_file, results_dir / "launch_config.yaml")
Expand Down Expand Up @@ -130,7 +132,18 @@ def collect_case_figures(case_dir, destination, case_name):
cwd=case_dir,
check=True,
)
collect_case_figures(case_dir, convergence_dir, case_name)
collect_case_figures(
case_dir,
convergence_dir,
case_name,
("convergence_*.png",),
)
collect_case_figures(
case_dir,
reference_dir,
case_name,
("reference_*.png", "reference_*.gif"),
)

# Compare the participating codes at the largest shared sample size.
counts = particle_counts(task["logN_min"], task["logN_max"], task["N_task"])
Expand All @@ -149,7 +162,12 @@ def collect_case_figures(case_dir, destination, case_name):
cwd=case_dir,
check=True,
)
collect_case_figures(case_dir, comparison_dir, case_name)
collect_case_figures(
case_dir,
comparison_dir,
case_name,
("comparison.gif", "difference.gif"),
)


# ======================================================================================
Expand Down
85 changes: 85 additions & 0 deletions verification/code_to_code/neutron/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,91 @@ def update(frame):
plt.close(fig)


def animate_spatial_reference(
score,
time,
spatial_edges,
reference,
history=None,
history_label=None,
filename="reference.gif",
):
"""Animate a fixed comparison reference and its integral history."""
time = np.asarray(time)
reference = np.asarray(reference)
x, y, z = (np.asarray(edges) for edges in spatial_edges)
projections = _spatial_projections(reference)

if history is None:
history = np.sum(reference, axis=(1, 2, 3))
history = np.asarray(history)
if history.shape != time.shape:
raise ValueError(
"The reference history and time grid must have the same shape."
)
if history_label is None:
history_label = f"Total {score}"

projection_data = (
("XY", x, y, projections[0]),
("XZ", x, z, projections[1]),
("YZ", y, z, projections[2]),
)
norm = _positive_norm(*projections)

fig, axes = plt.subplots(2, 2, figsize=(12, 9), constrained_layout=True)

# Show the transient's integral response beside its spatial projections.
history_axis = axes[0, 0]
positive_history = np.where(history > 0.0, history, np.nan)
history_axis.plot(time, positive_history, "b")
marker = history_axis.plot([], [], "ro", fillstyle="none")[0]
if np.any(history > 0.0):
history_axis.set_yscale("log")
history_axis.set_xlabel("Time")
history_axis.set_ylabel(history_label)
history_axis.grid()

images = []
for axis, (plane, horizontal, vertical, projection) in zip(
(axes[0, 1], axes[1, 0], axes[1, 1]), projection_data
):
image = axis.imshow(
projection[0].T,
extent=(horizontal[0], horizontal[-1], vertical[0], vertical[-1]),
origin="lower",
aspect="auto",
cmap="viridis",
norm=norm,
)
axis.set_title(f"{score.capitalize()}-{plane}")
axis.set_xlabel(plane[0].lower())
axis.set_ylabel(plane[1].lower())
images.append((image, projection))

fig.colorbar(
images[0][0],
ax=(axes[0, 1], axes[1, 0], axes[1, 1]),
label=score.capitalize(),
)
title = fig.suptitle(f"{score.capitalize()} reference, t = {time[0]:.3g}")

def update(frame):
marker.set_data([time[frame]], [history[frame]])
for image, projection in images:
image.set_data(projection[frame].T)
title.set_text(f"{score.capitalize()} reference, t = {time[frame]:.3g}")
return [title, marker, *(image for image, _ in images)]

simulation = animation.FuncAnimation(fig, update, frames=len(time))
simulation.save(
filename,
writer=animation.PillowWriter(fps=max(2, len(time) // 10)),
dpi=120,
)
plt.close(fig)


def animate_spatial_difference(
score,
time,
Expand Down
Loading