Skip to content

Commit 50e1a99

Browse files
authored
Merge pull request #65 from SteSeg/uq_w_schema
UQ-TMC workflow - new
2 parents ffa451d + 2ece71b commit 50e1a99

File tree

13 files changed

+775
-95
lines changed

13 files changed

+775
-95
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,7 @@ docs/source/_build/
5959

6060
# Visual Studio Code configuration files
6161
.vscode/
62+
63+
# Virtualenv
64+
.venv/
65+
venv/

TODO.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33

44

55
## Benchmarks API
6+
- UQ-TMC: Handle the NJOY dependency
7+
- UQ-TMC: separate the preparation of run from the execution of the tmc loops (xml files outside the loop, tmc-manager like this: https://github.com/openmc-dev/openmc/pull/3508)
8+
- UQ-TMC: implement list of models in tmc engine
9+
- UQ-TMC: the first realization should be the original xs "mean" (baseline)
10+
- UQ-TMC: allow more options to user (e.g. library to perturb)
11+
- UQ-TMC: allow a list of nuclides to perturb
12+
- UQ-TMC: allow list of nuclides and perturbations from specifications file
613
- Add capability of merging multiple surface results for oktavian (should happen only with surface tallies)
714
- Add expected tally results shape in `schema` and `specifications` to deal with the point here above
815
- Try to manage results as numpy arrays instead of pandas dataframes in `OpenmcBenchmark.postprocess()`

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ dependencies = [
2525
"xarray",
2626
"tables",
2727
"cad-to-dagmc",
28-
"pydagmc @ git+https://github.com/svalinn/pydagmc.git"
28+
"pydagmc @ git+https://github.com/svalinn/pydagmc.git",
29+
"sandy",
2930
]
3031

3132
[tool.setuptools.package-data]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from openmc_fusion_benchmarks.benchmark import *
22
from openmc_fusion_benchmarks.validate import *
3+
import openmc_fusion_benchmarks.uq
34

45
__version__ = "0.1.0"

src/openmc_fusion_benchmarks/benchmark.py

Lines changed: 51 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import xarray as xr
77
import h5py
88
from .validate import validate_benchmark
9+
from .utils import _openmc_to_ofb, _save_result
10+
from .uq.tmc_engine import tmc_engine
911

1012
import openmc
1113
import pydagmc
@@ -59,7 +61,7 @@ def _build_model(self):
5961
pass
6062

6163
@abstractmethod
62-
def postprocess(self):
64+
def _postprocess(self):
6365
"""Post-process the model after running."""
6466
pass
6567

@@ -68,6 +70,11 @@ def run(self):
6870
"""Run the benchmark simulation."""
6971
pass
7072

73+
@abstractmethod
74+
def _uncertainty_quantification(self):
75+
"""Perform uncertainty quantification for the benchmark."""
76+
pass
77+
7178
def _read_metadata(self):
7279
"""Read metadata from the benchmark specification."""
7380
metadata = self._benchmark_spec['metadata']
@@ -137,8 +144,7 @@ def _build_materials(self):
137144

138145
materials = openmc.Materials()
139146
for m in material_data:
140-
mat = openmc.Material(name=m['name'])
141-
mat.id = m['id']
147+
mat = openmc.Material(material_id=m['id'], name=m['name'])
142148
mat.set_density(m['density']['units'], m['density']['value'])
143149

144150
# Ensure fraction type is valid
@@ -395,77 +401,55 @@ def _build_model(self):
395401
)
396402
return model
397403

398-
def postprocess(self, statepoint: str = 'statepoint.100.h5', mesh: str = 'mesh.h5m'):
404+
def _postprocess(self, statepoint: openmc.StatePoint, mesh: str = 'mesh.h5m'):
399405
"""Post-process the model after running."""
400406
# Retrieve tallies data from specifications
401407
tallies_data = self._benchmark_spec['tallies']
402-
# Read openmc statepoint file
403-
sp = openmc.StatePoint(statepoint)
404-
# Read mesh file
405-
mesh = pydagmc.DAGModel(mesh)
406-
407-
# Cycle tallies in specifications
408-
for spec_t in tallies_data:
409-
# Get corresponding tally from statepoint
410-
df = sp.get_tally(name=spec_t['name']).get_pandas_dataframe()
411-
412-
# Preparing tally dataframe
413-
df = df.drop(columns=['surface', 'cell', 'particle', 'nuclide',
414-
'score', 'energyfunction'], errors='ignore')
415-
# Cyle tally filters
416-
norm = 1
417-
for f in spec_t['filters']:
418-
if f['type'] == 'cell':
419-
# Get cell volumes for normalization
420-
norm = [mesh.volumes_by_id[v].area for v in f['values']]
421-
elif f['type'] == 'surface':
422-
# Get surface areas for normalization
423-
norm = [mesh.surfaces_by_id[v].area for v in f['values']]
424-
elif f['type'] == 'material':
425-
raise NotImplementedError(
426-
'Material filter not implemented in postprocess yet.')
427-
428-
# Normalize the tally data
429-
df['mean'] = df['mean'] / norm
430-
df['std. dev.'] = df['std. dev.'] / norm
431-
432-
# Convert to xarray and add dimensions
433-
t = xr.DataArray(
434-
df.values[np.newaxis, :, :], # shape: (1, r, c)
435-
dims=["case", "row", "column"],
436-
coords={
437-
"case": ["0"],
438-
"column": df.columns,
439-
"row": np.arange(df.shape[0])
440-
},
441-
name=spec_t['name']
442-
)
443-
444-
# Save the tally data to a netCDF file
445-
t.to_netcdf(f"benchmark_results.h5",
446-
group=f"{spec_t['name']}", mode='a')
447-
448-
# Add some metadata attributes
449-
with h5py.File(f"benchmark_results.h5", "a") as f:
450-
f.attrs['benchmark_name'] = self.name
451-
# f.attrs['benchmark_version'] = self._benchmark_spec['metadata'].get(
452-
# 'version', 'N/A')
453-
# f.attrs['description'] = self._benchmark_spec['metadata'].get(
454-
# 'description', 'N/A')
455-
# f.attrs['title'] = self._benchmark_spec['metadata'].get(
456-
# 'title', 'N/A')
457-
# f.attrs['literature'] = self._benchmark_spec['metadata'].get(
458-
# 'references', [])
459-
f.attrs['code'] = f"openmc {sp.version[0]}.{sp.version[1]}.{sp.version[2]}"
408+
409+
_openmc_to_ofb(
410+
spec_tallies=tallies_data,
411+
statepoint=statepoint,
412+
mesh=mesh
413+
)
414+
415+
return
416+
417+
def _uncertainty_quantification(self, *args, **kwargs):
418+
"""Perform uncertainty quantification for the benchmark."""
419+
uq_data = self._benchmark_spec['uncertainty_quantification']
420+
tallies_data = self._benchmark_spec['tallies']
421+
422+
mesh = 'mesh.h5m'
423+
424+
# Run a TMC for every nuclide present in specifications
425+
for n, r in zip(uq_data[0]['nuclides'], uq_data[0]['realizations']):
426+
tmc_engine(model=self.model, realizations=r,
427+
lib_name='endfb_80', nuclide=n[0], reaction=None,
428+
perturb_xs=True, _is_benchmark=True, _spec_tallies=tallies_data,
429+
_mesh=mesh, *args, **kwargs)
460430

461431
return
462432

463-
def run(self, *args, **kwargs):
433+
def run(self, uq: bool = False, *args, **kwargs):
464434
"""Run the benchmark simulation."""
465-
# Run the OpenMC model
466-
statepoint = self.model.run(*args, **kwargs)
467435

468-
# Post-process the results
469-
self.postprocess(statepoint=statepoint)
436+
# Check if benchmark_results.h5 already exists and delete it
437+
path = Path("benchmark_results.h5")
438+
if path.exists():
439+
path.unlink()
440+
warnings.warn(
441+
f"Existing file '{path}' was found and deleted.", UserWarning)
442+
443+
# Run the model
444+
if uq:
445+
# Perform uncertainty quantification
446+
self._uncertainty_quantification(*args, **kwargs)
447+
else:
448+
# Run the OpenMC model
449+
# self._run_model(*args, **kwargs)
450+
# Run the OpenMC model
451+
statepoint = self.model.run(*args, **kwargs)
452+
# Post-process the results
453+
self._postprocess(statepoint=statepoint)
470454

471455
return

src/openmc_fusion_benchmarks/benchmarks/benchmark_schema.yaml

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ components:
4343
irradiation_schedule:
4444
$ref: '#/components/schemas/IrradiationSchedule'
4545
uncertainty_quantification:
46-
$ref: '#/components/schemas/UncertaintyQuantification'
46+
type: array
47+
items:
48+
$ref: '#/components/schemas/UncertaintyQuantification'
4749

4850
Metadata:
4951
type: object
@@ -312,21 +314,37 @@ components:
312314
UncertaintyQuantification:
313315
type: object
314316
properties:
315-
# nuclear_data:
316-
# type: boolean
317-
# materials:
318-
# type: object
319-
# properties:
320-
# density:
321-
# type: boolean
322-
# composition:
323-
# type: boolean
324-
# source:
325-
# type: object
326-
# properties:
327-
# geometry:
328-
# type: boolean
329-
# angle:
330-
# type: boolean
331-
# energy:
332-
# type: boolean
317+
realizations:
318+
type: array
319+
items:
320+
type: integer
321+
required: [realizations]
322+
allOf:
323+
- oneOf:
324+
- type: object
325+
properties:
326+
type:
327+
type: string
328+
const: nuclear_data
329+
nuclides:
330+
type: array
331+
items:
332+
type: array
333+
items:
334+
type: string
335+
reactions:
336+
type: array
337+
items:
338+
type: string
339+
required: [type, nuclides, reactions]
340+
341+
# - type: object
342+
# properties:
343+
# type:
344+
# type: string
345+
# const: material
346+
# materials:
347+
# type: array
348+
# items:
349+
# type: string
350+
# required: [type, materials]

src/openmc_fusion_benchmarks/benchmarks/oktavian_al/specifications.yaml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,4 +191,11 @@ tallies:
191191
4700000., 4800000., 4900000., 5000000., 5500000., 6000000.,
192192
6500000., 7000000., 7500000., 8000000., 8500000., 9000000.,
193193
9500000., 10000000., 10500000., 11000000.]
194-
units: particle/src/cm**2
194+
units: particle/src/cm**2
195+
196+
uncertainty_quantification:
197+
- type: nuclear_data
198+
description: "Uncertainty in nuclear data"
199+
nuclides: [[Al27]]
200+
reactions: [total]
201+
realizations: [500]
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from .uq_utils import *
2+
from .tmc_engine import *
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
from .uq_utils import perturb_xs_xml, get_nuclide_gnds, perturb_to_hdf5
2+
from pathlib import Path
3+
import openmc
4+
import numpy as np
5+
import xarray as xr
6+
7+
from ..utils import _openmc_to_ofb, _save_result
8+
9+
10+
def tmc_engine(model: openmc.Model, realizations: int, lib_name: str, nuclide,
11+
reaction: int = None, perturb_xs: bool = True,
12+
_is_benchmark: bool = False, _mesh=None, _spec_tallies=None,
13+
*args, **kwargs):
14+
"""Runs a TMC simulation on a given OpenMC model object.
15+
With perturb_xs=True it is possible to perturb the cross sections of a
16+
specific nuclide and reaction from a given nuclear data library
17+
automatically before the starting of the actual TMC simulation.
18+
The results of the TMC simulation are stored in a .h5 file as OpenMC
19+
tallies in Xarray DataArray format.
20+
21+
Parameters
22+
----------
23+
model : openmc.Model
24+
OpenMC model object to run TMC simulations on
25+
realizations : int
26+
Number of samples to run in the TMC simulation
27+
(i.e. number of times the xs is perturbed)
28+
lib_name : str
29+
Name of the nuclear data library to perturb the xs from
30+
(e.g. 'ENDF/B-VIII.0')
31+
nuclide : str or int
32+
Identifier of the nuclide for which the cross section is perturbed
33+
(GNDS, ZAID or ZAM)
34+
reaction : int, optional
35+
MT value for the specific reaction to perturb, by default None
36+
perturb_xs : bool, optional
37+
Flag for the automatic generation of perturbed .h5 xs files right
38+
before running the TMC simulation. Set to False if the use has already
39+
a set of perturbed xs in .h5 format
40+
to point to, by default True
41+
"""
42+
43+
# convert nuclide to gnds name
44+
nuclide = get_nuclide_gnds(nuclide)
45+
xs_file = f'cross_sections_mod.xml'
46+
47+
# runs sandy and generates perturbed xs only if perturb_xs is True
48+
if perturb_xs:
49+
perturb_to_hdf5(realizations, lib_name, nuclide, reaction, nprocesses=1,
50+
error=.001)
51+
52+
for n in np.arange(realizations):
53+
54+
directory = f"{nuclide}_{lib_name}"
55+
xs_h5_file = f"{directory}/{nuclide}_{n}_{lib_name}.h5"
56+
perturb_xs_xml(xs_file, xs_h5_file, nuclide)
57+
openmc.config['cross_sections'] = xs_file
58+
59+
# run simulation
60+
statepoint = model.run(*args, **kwargs)
61+
62+
# Postprocess results in case of standalone TMC run
63+
sp = openmc.StatePoint(statepoint)
64+
65+
# realization_label = f'{nuclide}_{n}_{lib_name}'
66+
realization_label = f'{nuclide}_{n}'
67+
if _is_benchmark:
68+
# Postprocess results in case of OFB benchmark model
69+
_openmc_to_ofb(statepoint=sp,
70+
realization_label=realization_label,
71+
mesh=_mesh,
72+
spec_tallies=_spec_tallies,
73+
*args, **kwargs)
74+
else:
75+
# open tally and push to netcdf ofb style
76+
for t in sp.tallies:
77+
tally = sp.get_tally(id=t)
78+
df = tally.get_pandas_dataframe()
79+
80+
# Convert to xarray and add dimensions
81+
t = xr.DataArray(
82+
df.values[np.newaxis, :, :], # shape: (1, r, c)
83+
dims=["realization", "row", "column"],
84+
coords={
85+
"realization": [realization_label],
86+
"column": df.columns,
87+
"row": np.arange(df.shape[0]),
88+
},
89+
name=t.name
90+
)
91+
92+
_save_result(new_result=t, filename="tmc_results.h5",
93+
group=t.name, realization_label=f'{nuclide}_{n}_{lib_name}')
94+
95+
Path('summary.h5').unlink(missing_ok=True)
96+
Path(statepoint).unlink(missing_ok=True)

0 commit comments

Comments
 (0)