2025-09-19 15:10:08 +07:00
|
|
|
from math import isnan
|
2017-02-16 21:02:34 -05:00
|
|
|
import os
|
|
|
|
|
import hashlib
|
|
|
|
|
|
|
|
|
|
import numpy as np
|
|
|
|
|
import openmc
|
|
|
|
|
|
2018-01-29 10:53:46 -06:00
|
|
|
from tests.testing_harness import PyAPITestHarness
|
2018-01-29 13:46:40 -06:00
|
|
|
from tests.regression_tests import config
|
2018-01-29 10:53:46 -06:00
|
|
|
|
2017-02-16 21:02:34 -05:00
|
|
|
# OpenMC simulation parameters
|
2017-02-18 05:20:30 -05:00
|
|
|
batches = 10
|
|
|
|
|
inactive = 5
|
|
|
|
|
particles = 100
|
2017-02-16 21:02:34 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_mgxs_library(convert):
|
|
|
|
|
# Instantiate the energy group data
|
|
|
|
|
groups = openmc.mgxs.EnergyGroups(group_edges=[1e-5, 0.625, 20.0e6])
|
|
|
|
|
|
2018-09-03 06:48:02 -04:00
|
|
|
# Instantiate the 2-group (C5G7) cross section data
|
2017-02-16 21:02:34 -05:00
|
|
|
uo2_xsdata = openmc.XSdata('UO2', groups)
|
|
|
|
|
uo2_xsdata.order = 2
|
|
|
|
|
uo2_xsdata.set_total([2., 2.])
|
|
|
|
|
uo2_xsdata.set_absorption([1., 1.])
|
|
|
|
|
scatter_matrix = np.array([[[0.75, 0.25],
|
|
|
|
|
[0.00, 1.00]],
|
|
|
|
|
[[0.75 / 3., 0.25 / 3.],
|
|
|
|
|
[0.00 / 3., 1.00 / 3.]],
|
|
|
|
|
[[0.75 / 4., 0.25 / 4.],
|
|
|
|
|
[0.00 / 4., 1.00 / 4.]]])
|
|
|
|
|
scatter_matrix = np.rollaxis(scatter_matrix, 0, 3)
|
|
|
|
|
uo2_xsdata.set_scatter_matrix(scatter_matrix)
|
|
|
|
|
uo2_xsdata.set_fission([0.5, 0.5])
|
|
|
|
|
uo2_xsdata.set_nu_fission([1., 1.])
|
|
|
|
|
uo2_xsdata.set_chi([1., 0.])
|
|
|
|
|
|
|
|
|
|
mg_cross_sections_file = openmc.MGXSLibrary(groups)
|
|
|
|
|
mg_cross_sections_file.add_xsdatas([uo2_xsdata])
|
|
|
|
|
|
|
|
|
|
if convert is not None:
|
|
|
|
|
if isinstance(convert[0], list):
|
|
|
|
|
for conv in convert:
|
|
|
|
|
if conv[0] in ['legendre', 'tabular', 'histogram']:
|
|
|
|
|
mg_cross_sections_file = \
|
|
|
|
|
mg_cross_sections_file.convert_scatter_format(
|
|
|
|
|
conv[0], conv[1])
|
|
|
|
|
elif conv[0] in ['angle', 'isotropic']:
|
|
|
|
|
mg_cross_sections_file = \
|
|
|
|
|
mg_cross_sections_file.convert_representation(
|
|
|
|
|
conv[0], conv[1], conv[1])
|
|
|
|
|
elif convert[0] in ['legendre', 'tabular', 'histogram']:
|
|
|
|
|
mg_cross_sections_file = \
|
|
|
|
|
mg_cross_sections_file.convert_scatter_format(
|
|
|
|
|
convert[0], convert[1])
|
|
|
|
|
elif convert[0] in ['angle', 'isotropic']:
|
|
|
|
|
mg_cross_sections_file = \
|
|
|
|
|
mg_cross_sections_file.convert_representation(
|
|
|
|
|
convert[0], convert[1], convert[1])
|
|
|
|
|
|
|
|
|
|
mg_cross_sections_file.export_to_hdf5()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MGXSTestHarness(PyAPITestHarness):
|
2023-02-28 11:31:04 -05:00
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
|
super().__init__(*args, **kwargs)
|
2017-02-16 21:02:34 -05:00
|
|
|
# Instantiate some Macroscopic Data
|
|
|
|
|
uo2_data = openmc.Macroscopic('UO2')
|
|
|
|
|
|
|
|
|
|
# Instantiate some Materials and register the appropriate objects
|
|
|
|
|
mat = openmc.Material(material_id=1, name='UO2 fuel')
|
Fixed several bugs related to decay-rate
1) In mdgxs.py, DecayRate, get_xs(). There is no need to reverse the order
since there isn't an energy group filter anymore. Also, the new_shape
addition formats the cross section like all the others. I updated
mgxs_library_hdf5 test results accordingly.
2) In material.cpp. If you use a macroscopic dnesity other than 1.0,
the code still pushed back '1.0'. My change fixes that. Using a macro
density other than 1.0 wasn't tested anywhere, so I modifed mg_convert
to take a density of 1.1. The default macro density of 1.0 is used in
many other tests.
3) tally_scoring.cpp. Notice that the score is accumulated, then sent
to the function score_fission_delayed_dg where the tally is accumulated.
This amounts to "double accumulation". It makes sense to accumulate the score
over the delayed groups if there is no delayed group filter, but in this
case, when delayed group filter is not None, the score needs to reset
each time.
In other parts of SCORE_DECAY_RATE, I think the distinction between
accumulating and resetting the score is correct. The only place I wasn't
sure was line 1057 (and its equivalent in multi-group, line 1962).
It's too bad the decay-rate isn't tested very thoroughly. Although I
don't have time to do it myself, I think adding a DelayedGroups filter
to the mg_tallies test would be a good start.
2020-11-04 16:58:43 +00:00
|
|
|
mat.set_density('macro', 1.1)
|
2017-02-16 21:02:34 -05:00
|
|
|
mat.add_macroscopic(uo2_data)
|
|
|
|
|
|
|
|
|
|
# Instantiate a Materials collection and export to XML
|
|
|
|
|
materials_file = openmc.Materials([mat])
|
|
|
|
|
materials_file.cross_sections = "./mgxs.h5"
|
2023-02-28 11:31:04 -05:00
|
|
|
self._model.materials = materials_file
|
2017-02-16 21:02:34 -05:00
|
|
|
|
|
|
|
|
# Instantiate ZCylinder surfaces
|
|
|
|
|
left = openmc.XPlane(surface_id=4, x0=-5., name='left')
|
|
|
|
|
right = openmc.XPlane(surface_id=5, x0=5., name='right')
|
|
|
|
|
bottom = openmc.YPlane(surface_id=6, y0=-5., name='bottom')
|
|
|
|
|
top = openmc.YPlane(surface_id=7, y0=5., name='top')
|
|
|
|
|
|
|
|
|
|
left.boundary_type = 'reflective'
|
|
|
|
|
right.boundary_type = 'vacuum'
|
|
|
|
|
top.boundary_type = 'reflective'
|
|
|
|
|
bottom.boundary_type = 'reflective'
|
|
|
|
|
|
|
|
|
|
# Instantiate Cells
|
|
|
|
|
fuel = openmc.Cell(cell_id=1, name='cell 1')
|
|
|
|
|
|
|
|
|
|
# Use surface half-spaces to define regions
|
|
|
|
|
fuel.region = +left & -right & +bottom & -top
|
|
|
|
|
|
|
|
|
|
# Register Materials with Cells
|
|
|
|
|
fuel.fill = mat
|
|
|
|
|
|
|
|
|
|
# Instantiate Universe
|
|
|
|
|
root = openmc.Universe(universe_id=0, name='root universe')
|
|
|
|
|
|
|
|
|
|
# Register Cells with Universe
|
|
|
|
|
root.add_cells([fuel])
|
|
|
|
|
|
|
|
|
|
# Instantiate a Geometry, register the root Universe, and export to XML
|
2023-02-28 11:31:04 -05:00
|
|
|
self._model.geometry = openmc.Geometry(root)
|
2017-02-16 21:02:34 -05:00
|
|
|
|
|
|
|
|
settings_file = openmc.Settings()
|
|
|
|
|
settings_file.energy_mode = "multi-group"
|
|
|
|
|
settings_file.batches = batches
|
|
|
|
|
settings_file.inactive = inactive
|
|
|
|
|
settings_file.particles = particles
|
|
|
|
|
|
|
|
|
|
# Create an initial uniform spatial source distribution
|
|
|
|
|
bounds = [-5, -5, -5, 5, 5, 5]
|
|
|
|
|
uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:])
|
2023-06-20 21:27:55 -05:00
|
|
|
settings_file.source = openmc.IndependentSource(space=uniform_dist)
|
2017-02-16 21:02:34 -05:00
|
|
|
|
2023-02-28 11:31:04 -05:00
|
|
|
self._model.settings = settings_file
|
2017-02-16 21:02:34 -05:00
|
|
|
|
|
|
|
|
def _run_openmc(self):
|
|
|
|
|
# Run multiple conversions to compare results
|
2017-02-17 07:48:45 -05:00
|
|
|
cases = [['legendre', 2], ['legendre', 0],
|
2017-02-16 21:02:34 -05:00
|
|
|
['tabular', 33], ['histogram', 32],
|
|
|
|
|
[['tabular', 33], ['legendre', 1]],
|
|
|
|
|
[['tabular', 33], ['tabular', 3]],
|
|
|
|
|
[['tabular', 33], ['histogram', 32]],
|
|
|
|
|
[['histogram', 32], ['legendre', 1]],
|
|
|
|
|
[['histogram', 32], ['tabular', 3]],
|
|
|
|
|
[['histogram', 32], ['histogram', 16]],
|
|
|
|
|
['angle', 2], [['angle', 2], ['isotropic', None]]]
|
|
|
|
|
|
|
|
|
|
outstr = ''
|
|
|
|
|
for case in cases:
|
|
|
|
|
build_mgxs_library(case)
|
|
|
|
|
|
2018-01-29 13:46:40 -06:00
|
|
|
if config['mpi']:
|
|
|
|
|
mpi_args = [config['mpiexec'], '-n', config['mpi_np']]
|
|
|
|
|
openmc.run(openmc_exec=config['exe'], mpi_args=mpi_args)
|
2017-02-16 21:02:34 -05:00
|
|
|
|
|
|
|
|
else:
|
2018-01-29 13:46:40 -06:00
|
|
|
openmc.run(openmc_exec=config['exe'])
|
2017-02-16 21:02:34 -05:00
|
|
|
|
2018-01-29 13:46:40 -06:00
|
|
|
with openmc.StatePoint('statepoint.{}.h5'.format(batches)) as sp:
|
2025-09-19 15:10:08 +07:00
|
|
|
# Sometimes NaN results are produced; convert these to 0.0
|
|
|
|
|
std_dev = 0.0 if isnan(sp.keff.s) else sp.keff.s
|
|
|
|
|
|
2018-01-29 13:46:40 -06:00
|
|
|
# Write out k-combined.
|
|
|
|
|
outstr += 'k-combined:\n'
|
2018-03-02 16:19:10 -06:00
|
|
|
form = '{:12.6E} {:12.6E}\n'
|
2025-09-19 15:10:08 +07:00
|
|
|
outstr += form.format(sp.keff.n, std_dev)
|
2017-03-04 05:13:55 -05:00
|
|
|
|
2017-02-16 21:02:34 -05:00
|
|
|
return outstr
|
|
|
|
|
|
|
|
|
|
def _get_results(self, outstr, hash_output=False):
|
|
|
|
|
# Hash the results if necessary.
|
|
|
|
|
if hash_output:
|
|
|
|
|
sha512 = hashlib.sha512()
|
|
|
|
|
sha512.update(outstr.encode('utf-8'))
|
|
|
|
|
outstr = sha512.hexdigest()
|
|
|
|
|
|
|
|
|
|
return outstr
|
|
|
|
|
|
|
|
|
|
def _cleanup(self):
|
2018-02-06 13:14:17 -05:00
|
|
|
super()._cleanup()
|
2017-02-16 21:02:34 -05:00
|
|
|
f = os.path.join(os.getcwd(), 'mgxs.h5')
|
|
|
|
|
if os.path.exists(f):
|
|
|
|
|
os.remove(f)
|
|
|
|
|
|
|
|
|
|
def execute_test(self):
|
|
|
|
|
"""Build input XMLs, run OpenMC, and verify correct results."""
|
|
|
|
|
try:
|
|
|
|
|
self._build_inputs()
|
|
|
|
|
inputs = self._get_inputs()
|
|
|
|
|
self._write_inputs(inputs)
|
|
|
|
|
self._compare_inputs()
|
|
|
|
|
outstr = self._run_openmc()
|
|
|
|
|
results = self._get_results(outstr)
|
|
|
|
|
self._write_results(results)
|
|
|
|
|
self._compare_results()
|
|
|
|
|
finally:
|
|
|
|
|
self._cleanup()
|
|
|
|
|
|
|
|
|
|
def update_results(self):
|
|
|
|
|
"""Update results_true.dat and inputs_true.dat"""
|
|
|
|
|
try:
|
|
|
|
|
self._build_inputs()
|
|
|
|
|
inputs = self._get_inputs()
|
|
|
|
|
self._write_inputs(inputs)
|
|
|
|
|
self._overwrite_inputs()
|
|
|
|
|
outstr = self._run_openmc()
|
|
|
|
|
results = self._get_results(outstr)
|
|
|
|
|
self._write_results(results)
|
|
|
|
|
self._overwrite_results()
|
|
|
|
|
finally:
|
|
|
|
|
self._cleanup()
|
|
|
|
|
|
|
|
|
|
|
2018-01-29 12:10:24 -06:00
|
|
|
def test_mg_convert():
|
2023-02-28 11:31:04 -05:00
|
|
|
harness = MGXSTestHarness('statepoint.10.h5', model=openmc.Model())
|
2017-02-16 21:02:34 -05:00
|
|
|
harness.main()
|