Skip to content

Commit d4e0452

Browse files
committed
+ tests
1 parent 476efd8 commit d4e0452

4 files changed

Lines changed: 330 additions & 1 deletion

File tree

test/test_benchmark.py

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,9 @@ def test_generate_report_uses_default_config(mock_validate, valid_yaml, tmp_path
156156
bench = DummyBenchmark("dummy")
157157

158158
results_path = tmp_path / "benchmark_results.h5"
159-
results_path.write_text("data")
159+
import h5py
160+
with h5py.File(results_path, "w"):
161+
pass
160162
monkeypatch.chdir(tmp_path)
161163

162164
captured = {}
@@ -212,6 +214,101 @@ def test_generate_report_missing_results_warns(mock_validate, valid_yaml, tmp_pa
212214
bench._generate_report(report_config=None)
213215

214216

217+
@patch("openmc_fusion_benchmarks.benchmark.validate_benchmark")
218+
def test_write_spec_snapshot_writes_group(mock_validate, valid_yaml, tmp_path):
219+
with patch.object(Path, "open", mock_open(read_data=valid_yaml)):
220+
bench = DummyBenchmark("dummy")
221+
222+
results_path = tmp_path / "benchmark_results.h5"
223+
import h5py
224+
with h5py.File(results_path, "w"):
225+
pass
226+
227+
bench._write_spec_snapshot(str(results_path))
228+
229+
import h5py
230+
231+
with h5py.File(results_path, "r") as handle:
232+
assert "specifications" in handle
233+
assert "yaml" in handle["specifications"]
234+
235+
236+
@patch("openmc_fusion_benchmarks.benchmark.validate_benchmark")
237+
def test_write_run_metadata_writes_attrs(mock_validate, valid_yaml, tmp_path):
238+
with patch.object(Path, "open", mock_open(read_data=valid_yaml)):
239+
bench = DummyBenchmark("dummy")
240+
241+
results_path = tmp_path / "benchmark_results.h5"
242+
results_path.write_text("data")
243+
244+
bench._write_run_metadata(
245+
code_name="openmc",
246+
code_version="0.15.2",
247+
nuclear_data_name="endfb",
248+
nuclear_data_version="8.0",
249+
geometry="cad",
250+
filename=str(results_path),
251+
)
252+
253+
import h5py
254+
255+
with h5py.File(results_path, "r") as handle:
256+
attrs = handle["run_metadata"].attrs
257+
assert attrs["code_name"] == "openmc"
258+
assert attrs["code_version"] == "0.15.2"
259+
assert attrs["nuclear_data_name"] == "endfb"
260+
assert attrs["nuclear_data_version"] == "8.0"
261+
assert attrs["geometry"] == "cad"
262+
263+
264+
@patch("openmc_fusion_benchmarks.benchmark.validate_benchmark")
265+
def test_generate_report_without_reference(mock_validate, valid_yaml, tmp_path, monkeypatch):
266+
with patch.object(Path, "open", mock_open(read_data=valid_yaml)):
267+
bench = DummyBenchmark("dummy")
268+
269+
results_path = tmp_path / "benchmark_results.h5"
270+
results_path.write_text("data")
271+
monkeypatch.chdir(tmp_path)
272+
273+
def _fake_from_file(path):
274+
return Mock(filepath=Path(path))
275+
276+
def _fake_from_database(_name, filename="reference_results.h5"):
277+
raise FileNotFoundError("missing")
278+
279+
captured = {}
280+
281+
def _fake_build_report(sources, config):
282+
captured["sources"] = sources
283+
return Mock()
284+
285+
monkeypatch.setattr(
286+
"openmc_fusion_benchmarks.benchmark.BenchmarkResults.from_file",
287+
_fake_from_file,
288+
)
289+
monkeypatch.setattr(
290+
"openmc_fusion_benchmarks.benchmark.BenchmarkResults.from_database",
291+
_fake_from_database,
292+
)
293+
monkeypatch.setattr(
294+
"openmc_fusion_benchmarks.benchmark.build_report",
295+
_fake_build_report,
296+
)
297+
monkeypatch.setattr(
298+
"openmc_fusion_benchmarks.benchmark.render_yaml",
299+
lambda report, path: path,
300+
)
301+
monkeypatch.setattr(
302+
"openmc_fusion_benchmarks.benchmark.render_pdf",
303+
lambda report, path, plots_dir: path,
304+
)
305+
306+
bench._generate_report(report_config=None)
307+
308+
assert captured["sources"][0].kind == "calculation"
309+
assert len(captured["sources"]) == 1
310+
311+
215312
@pytest.mark.skipif(not OPENMC_AVAILABLE, reason="OpenMC not installed")
216313
def test_openmc_benchmark_build_materials():
217314
"""Test OpenmcBenchmark._build_materials with atomic fractions."""

test/test_database.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,21 @@ def test_resolve_database_path_provides_helpful_error():
6565
except FileNotFoundError as e:
6666
# Error message should contain available benchmarks
6767
assert "Available benchmarks:" in str(e)
68+
69+
70+
def test_list_database_benchmarks_fallback(monkeypatch):
71+
def _raise_files(_pkg):
72+
raise ModuleNotFoundError("no resources")
73+
74+
monkeypatch.setattr("openmc_fusion_benchmarks.database.files", _raise_files)
75+
benchmarks = list_database_benchmarks()
76+
assert "oktavian_al" in benchmarks
77+
78+
79+
def test_resolve_database_path_fallback(monkeypatch):
80+
def _raise_files(_pkg):
81+
raise ModuleNotFoundError("no resources")
82+
83+
monkeypatch.setattr("openmc_fusion_benchmarks.database.files", _raise_files)
84+
path = _resolve_database_path("oktavian_al", "experiment.h5")
85+
assert path.exists()

test/test_report.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,15 @@ def _module_available(name: str) -> bool:
7474
)
7575
from openmc_fusion_benchmarks.report.renderers import (
7676
_collect_observable_metrics,
77+
_observable_metrics_for_verbosity,
78+
_quality_metric_description,
79+
_quality_metric_equation,
80+
_quality_metrics_for_verbosity,
7781
render_plots_for_report,
7882
render_yaml,
7983
)
84+
from openmc_fusion_benchmarks.report import plots as report_plots
85+
from openmc_fusion_benchmarks.tallies import BaseTally
8086

8187

8288
def _write_structured_group(filepath: Path, group: str, tally_name: str) -> None:
@@ -337,3 +343,48 @@ def test_collect_observable_metrics(results_pair, tmp_path):
337343
assert entries
338344
assert entries[0]["tally"] == "tally_1"
339345
assert "rms_relative_deviation" in entries[0]
346+
347+
348+
def test_quality_metrics_for_verbosity():
349+
assert _quality_metrics_for_verbosity(0) == ["ce"]
350+
assert _quality_metrics_for_verbosity(1) == ["ce", "chi2_contribution"]
351+
assert "relative_deviation" in _quality_metrics_for_verbosity(2)
352+
assert "normalized_residual" in _quality_metrics_for_verbosity(3)
353+
354+
355+
def test_observable_metrics_for_verbosity():
356+
assert _observable_metrics_for_verbosity(0) == ["rms_relative_deviation"]
357+
assert "reduced_chi2" in _observable_metrics_for_verbosity(1)
358+
assert "mean_abs_normalized_residual" in _observable_metrics_for_verbosity(3)
359+
360+
361+
def test_quality_metric_equations_and_descriptions():
362+
assert _quality_metric_equation("ce")
363+
assert _quality_metric_description("ce")
364+
assert _quality_metric_equation("unknown") == ""
365+
assert _quality_metric_description("unknown") == ""
366+
367+
368+
def test_plot_utilities_auto_scale_and_default_x():
369+
values = np.array([1.0, 1000.0])
370+
assert report_plots._auto_scale(values, threshold=100.0) == "log"
371+
assert report_plots._auto_scale(np.array([-1.0, 2.0]), threshold=100.0) == "linear"
372+
373+
da = xr.DataArray(
374+
np.array([1.0, 2.0, 3.0]),
375+
dims=("energy",),
376+
coords={"energy": np.array([0.1, 1.0, 10.0])},
377+
)
378+
tally = BaseTally(da)
379+
x_vals = report_plots._default_x(tally)
380+
assert np.allclose(x_vals, np.array([0.1, 1.0, 10.0]))
381+
382+
383+
def test_plot_utilities_flatten_tally():
384+
da = xr.DataArray(
385+
np.arange(6.0).reshape(2, 3),
386+
dims=("cell", "energy"),
387+
)
388+
tally = BaseTally(da)
389+
flat = report_plots._flatten_tally(tally)
390+
assert flat.shape == (6,)

test/test_uq_utils.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
import pytest
22
import types
3+
from pathlib import Path
34
from unittest.mock import patch
45
from openmc_fusion_benchmarks.uq import (
56
get_nuclide_zaid,
67
zaid_to_zam,
78
get_nuclide_gnds,
89
get_reaction_mt
910
)
11+
from openmc_fusion_benchmarks.uq.uq_utils import (
12+
ace_to_hdf5,
13+
get_ace_files,
14+
perturb_to_hdf5,
15+
perturb_xs_xml,
16+
remove_ace_files,
17+
)
1018

1119

1220
def test_zaid_to_zam_len4():
@@ -151,3 +159,158 @@ def test_get_reaction_mt_unknown_reaction():
151159
def test_unsupported_type():
152160
with pytest.raises(TypeError, match="Unsupported nuclide type"):
153161
get_nuclide_gnds((1, 1))
162+
163+
164+
def test_remove_ace_files_removes_matching(tmp_path):
165+
lib_name = "mylib"
166+
extensions = ["03c", "03c.xsd", lib_name]
167+
for ext in extensions:
168+
(tmp_path / f"test.{ext}").write_text("data")
169+
170+
remove_ace_files(str(tmp_path), lib_name)
171+
for ext in extensions:
172+
assert not (tmp_path / f"test.{ext}").exists()
173+
174+
175+
def test_get_ace_files_calls_sandy(monkeypatch):
176+
class FakeTape:
177+
def get_perturbations(self, *args, **kwargs):
178+
return ["p1", "p2"]
179+
180+
def apply_perturbations(self, *args, **kwargs):
181+
return ["ace1", "ace2"]
182+
183+
def _fake_get_endf6_file(*args, **kwargs):
184+
return FakeTape()
185+
186+
fake_sandy = types.SimpleNamespace(get_endf6_file=_fake_get_endf6_file)
187+
monkeypatch.setattr("openmc_fusion_benchmarks.uq.uq_utils.sandy", fake_sandy, raising=False)
188+
189+
fake_openmc = _fake_openmc_with_data(REACTION_MT={"(n,gamma)": 102})
190+
monkeypatch.setattr("openmc_fusion_benchmarks.uq.uq_utils.openmc", fake_openmc, raising=False)
191+
192+
ace_files = get_ace_files(2, "endfb_80", "H1", "(n,gamma)", 1, 0.001)
193+
assert ace_files == ["ace1", "ace2"]
194+
195+
196+
def test_ace_to_hdf5_exports_and_cleans(tmp_path, monkeypatch):
197+
monkeypatch.chdir(tmp_path)
198+
199+
class FakeIncident:
200+
def export_to_hdf5(self, path):
201+
Path(path).write_text("h5")
202+
203+
class FakeOpenmcData:
204+
class IncidentNeutron:
205+
@staticmethod
206+
def from_ace(_path):
207+
return FakeIncident()
208+
209+
@staticmethod
210+
def gnds_name(z, a, m):
211+
return f"H{a}" if z == 1 else f"U{a}"
212+
213+
@staticmethod
214+
def zam(nuclide):
215+
return fake_zam(nuclide)
216+
217+
fake_openmc = types.SimpleNamespace(data=FakeOpenmcData())
218+
monkeypatch.setattr("openmc_fusion_benchmarks.uq.uq_utils.openmc", fake_openmc, raising=False)
219+
220+
removed = {"called": False}
221+
222+
def _fake_remove(_directory, _lib):
223+
removed["called"] = True
224+
225+
monkeypatch.setattr("openmc_fusion_benchmarks.uq.uq_utils.remove_ace_files", _fake_remove)
226+
227+
ace_to_hdf5(2, "endfb_80", "H1", remove_ace=True)
228+
assert removed["called"] is True
229+
assert (tmp_path / "H1_endfb_80" / "H1_0_endfb_80.h5").exists()
230+
231+
232+
def test_perturb_to_hdf5_calls_helpers(monkeypatch):
233+
calls = {"ace": False, "h5": False}
234+
235+
def _fake_get_ace(*args, **kwargs):
236+
calls["ace"] = True
237+
238+
def _fake_ace_to_hdf5(*args, **kwargs):
239+
calls["h5"] = True
240+
241+
monkeypatch.setattr("openmc_fusion_benchmarks.uq.uq_utils.get_ace_files", _fake_get_ace)
242+
monkeypatch.setattr("openmc_fusion_benchmarks.uq.uq_utils.ace_to_hdf5", _fake_ace_to_hdf5)
243+
244+
perturb_to_hdf5(1, "endfb_80", "H1", "(n,gamma)")
245+
assert calls["ace"] is True
246+
assert calls["h5"] is True
247+
248+
249+
def test_perturb_xs_xml_updates_library(monkeypatch, tmp_path):
250+
xs_file = tmp_path / "cross_sections.xml"
251+
xs_h5 = tmp_path / "new_xs.h5"
252+
xs_h5.write_text("data")
253+
254+
class FakeLibraries:
255+
def __init__(self):
256+
self.data = {"U235": {"path": "old"}}
257+
258+
def get_by_material(self, nuclide):
259+
return self.data[nuclide]
260+
261+
class FakeDataLibrary:
262+
def __init__(self):
263+
self.libraries = FakeLibraries()
264+
265+
def export_to_xml(self, path):
266+
Path(path).write_text("xml")
267+
268+
def append(self, entry):
269+
self.libraries.data[entry["materials"][0]] = {"path": entry["path"]}
270+
271+
@classmethod
272+
def from_xml(cls, _path):
273+
return cls()
274+
275+
fake_openmc = types.SimpleNamespace(
276+
config={"cross_sections": str(xs_file)},
277+
data=types.SimpleNamespace(DataLibrary=FakeDataLibrary),
278+
)
279+
monkeypatch.setattr("openmc_fusion_benchmarks.uq.uq_utils.openmc", fake_openmc, raising=False)
280+
281+
perturb_xs_xml(str(xs_file), str(xs_h5), "U235")
282+
assert xs_file.exists()
283+
284+
285+
def test_perturb_xs_xml_append_on_typeerror(monkeypatch, tmp_path):
286+
xs_file = tmp_path / "cross_sections.xml"
287+
xs_h5 = tmp_path / "new_xs.h5"
288+
xs_h5.write_text("data")
289+
290+
class FakeLibraries:
291+
def get_by_material(self, _nuclide):
292+
raise TypeError("missing")
293+
294+
class FakeDataLibrary:
295+
def __init__(self):
296+
self.libraries = FakeLibraries()
297+
self.appended = False
298+
299+
def export_to_xml(self, path):
300+
Path(path).write_text("xml")
301+
302+
def append(self, _entry):
303+
self.appended = True
304+
305+
@classmethod
306+
def from_xml(cls, _path):
307+
return cls()
308+
309+
fake_openmc = types.SimpleNamespace(
310+
config={"cross_sections": str(xs_file)},
311+
data=types.SimpleNamespace(DataLibrary=FakeDataLibrary),
312+
)
313+
monkeypatch.setattr("openmc_fusion_benchmarks.uq.uq_utils.openmc", fake_openmc, raising=False)
314+
315+
perturb_xs_xml(str(xs_file), str(xs_h5), "U235")
316+
assert xs_file.exists()

0 commit comments

Comments
 (0)