diff --git a/docs/source/io_formats/settings.rst b/docs/source/io_formats/settings.rst index e4ab0e169..d1174e531 100644 --- a/docs/source/io_formats/settings.rst +++ b/docs/source/io_formats/settings.rst @@ -7,6 +7,19 @@ Settings Specification -- settings.xml All simulation parameters and miscellaneous options are specified in the settings.xml file. +------------------------------- +```` Element +------------------------------- + +The ```` element determines whether the atomic relaxation +cascade, the X-ray fluorescence photons and Auger electrons emitted when an +inner-shell vacancy is filled, is simulated following photoelectric and +incoherent (Compton) scattering interactions. Disabling this can speed up +photon transport calculations where the detailed secondary particle cascade is +not of interest. + + *Default*: true + --------------------- ```` Element --------------------- diff --git a/docs/source/usersguide/settings.rst b/docs/source/usersguide/settings.rst index 5a04fedd7..8ac07f3c8 100644 --- a/docs/source/usersguide/settings.rst +++ b/docs/source/usersguide/settings.rst @@ -604,6 +604,13 @@ transport:: settings.photon_transport = True +Atomic relaxation (the cascade of fluorescence photons and Auger electrons +emitted when an inner-shell vacancy is filled) is enabled by default whenever +photon transport is on. It can be disabled using the +:attr:`Settings.atomic_relaxation` attribute:: + + settings.atomic_relaxation = False + The way in which OpenMC handles secondary charged particles can be specified with the :attr:`Settings.electron_treatment` attribute. By default, the :ref:`thick-target bremsstrahlung ` (TTB) approximation is used to generate diff --git a/include/openmc/settings.h b/include/openmc/settings.h index 19ef6e5d2..7f8a28986 100644 --- a/include/openmc/settings.h +++ b/include/openmc/settings.h @@ -77,6 +77,7 @@ extern "C" bool output_summary; //!< write summary.h5? extern bool output_tallies; //!< write tallies.out? extern bool particle_restart_run; //!< particle restart run? extern "C" bool photon_transport; //!< photon transport turned on? +extern bool atomic_relaxation; //!< atomic relaxation enabled? extern "C" bool reduce_tallies; //!< reduce tallies at end of batch? extern bool res_scat_on; //!< use resonance upscattering method? extern "C" bool restart_run; //!< restart run? diff --git a/openmc/settings.py b/openmc/settings.py index 342fd5e53..7961f7974 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -41,6 +41,10 @@ class Settings: Attributes ---------- + atomic_relaxation : bool + Whether to simulate the atomic relaxation cascade (fluorescence photons + and Auger electrons) following photoelectric and incoherent scattering + interactions. batches : int Number of batches to simulate confidence_intervals : bool @@ -402,6 +406,7 @@ class Settings: self._confidence_intervals = None self._electron_treatment = None self._photon_transport = None + self._atomic_relaxation = None self._plot_seed = None self._ptables = None self._uniform_source_sampling = None @@ -663,6 +668,15 @@ class Settings: electron_treatment, ['led', 'ttb']) self._electron_treatment = electron_treatment + @property + def atomic_relaxation(self) -> bool: + return self._atomic_relaxation + + @atomic_relaxation.setter + def atomic_relaxation(self, atomic_relaxation: bool): + cv.check_type('atomic relaxation', atomic_relaxation, bool) + self._atomic_relaxation = atomic_relaxation + @property def ptables(self) -> bool: return self._ptables @@ -1631,6 +1645,11 @@ class Settings: element = ET.SubElement(root, "electron_treatment") element.text = str(self._electron_treatment) + def _create_atomic_relaxation_subelement(self, root): + if self._atomic_relaxation is not None: + element = ET.SubElement(root, "atomic_relaxation") + element.text = str(self._atomic_relaxation).lower() + def _create_photon_transport_subelement(self, root): if self._photon_transport is not None: element = ET.SubElement(root, "photon_transport") @@ -2129,6 +2148,11 @@ class Settings: if text is not None: self.electron_treatment = text + def _atomic_relaxation_from_xml_element(self, root): + text = get_text(root, 'atomic_relaxation') + if text is not None: + self.atomic_relaxation = text in ('true', '1') + def _energy_mode_from_xml_element(self, root): text = get_text(root, 'energy_mode') if text is not None: @@ -2478,6 +2502,7 @@ class Settings: self._create_collision_track_subelement(element) self._create_confidence_intervals(element) self._create_electron_treatment_subelement(element) + self._create_atomic_relaxation_subelement(element) self._create_energy_mode_subelement(element) self._create_max_order_subelement(element) self._create_photon_transport_subelement(element) @@ -2594,6 +2619,7 @@ class Settings: settings._collision_track_from_xml_element(elem) settings._confidence_intervals_from_xml_element(elem) settings._electron_treatment_from_xml_element(elem) + settings._atomic_relaxation_from_xml_element(elem) settings._energy_mode_from_xml_element(elem) settings._max_order_from_xml_element(elem) settings._photon_transport_from_xml_element(elem) diff --git a/src/photon.cpp b/src/photon.cpp index 5e2ba6884..6bbdc928f 100644 --- a/src/photon.cpp +++ b/src/photon.cpp @@ -154,8 +154,7 @@ PhotonInteraction::PhotonInteraction(hid_t group) hid_t tgroup = open_group(rgroup, designator.c_str()); - // Read binding energy energy and number of electrons if atomic relaxation - // data is present + // Read binding energy if atomic relaxation data is present if (attribute_exists(tgroup, "binding_energy")) { has_atomic_relaxation_ = true; read_attribute(tgroup, "binding_energy", shell.binding_energy); @@ -174,7 +173,7 @@ PhotonInteraction::PhotonInteraction(hid_t group) i); cross_section = tensor::where(xs > 0, tensor::log(xs), 0); - if (object_exists(tgroup, "transitions")) { + if (settings::atomic_relaxation && object_exists(tgroup, "transitions")) { // Determine dimensions of transitions dset = open_dataset(tgroup, "transitions"); auto dims = object_shape(dset); @@ -206,9 +205,8 @@ PhotonInteraction::PhotonInteraction(hid_t group) // Check the maximum size of the atomic relaxation stack auto max_size = this->calc_max_stack_size(); if (max_size > MAX_STACK_SIZE && mpi::master) { - warning(fmt::format( - "The subshell vacancy stack in atomic relaxation can grow up to {}, but " - "the stack size limit is set to {}.", + warning(fmt::format("The subshell vacancy stack in atomic relaxation can " + "grow up to {}, but the stack size limit is set to {}.", max_size, MAX_STACK_SIZE)); } @@ -231,7 +229,7 @@ PhotonInteraction::PhotonInteraction(hid_t group) // Map Compton subshell data to atomic relaxation data by finding the // subshell with the equivalent binding energy - if (has_atomic_relaxation_) { + if (settings::atomic_relaxation && has_atomic_relaxation_) { auto is_close = [](double a, double b) { return std::abs(a - b) / a < FP_REL_PRECISION; }; diff --git a/src/physics.cpp b/src/physics.cpp index 72b4a6611..106bd1aa2 100644 --- a/src/physics.cpp +++ b/src/physics.cpp @@ -355,7 +355,8 @@ void sample_photon_reaction(Particle& p) // Allow electrons to fill orbital and produce Auger electrons and // fluorescent photons. Since Compton subshell data does not match atomic // relaxation data, use the mapping between the data to find the subshell - if (i_shell >= 0 && element.subshell_map_[i_shell] >= 0) { + if (settings::atomic_relaxation && i_shell >= 0 && + element.subshell_map_[i_shell] >= 0) { element.atomic_relaxation(element.subshell_map_[i_shell], p); } @@ -427,7 +428,9 @@ void sample_photon_reaction(Particle& p) // Allow electrons to fill orbital and produce auger electrons // and fluorescent photons - element.atomic_relaxation(i_shell, p); + if (settings::atomic_relaxation) { + element.atomic_relaxation(i_shell, p); + } p.event() = TallyEvent::ABSORB; p.event_mt() = 533 + shell.index_subshell; p.wgt() = 0.0; diff --git a/src/settings.cpp b/src/settings.cpp index 6b159f6e9..f41e5fd11 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -62,6 +62,7 @@ bool output_summary {true}; bool output_tallies {true}; bool particle_restart_run {false}; bool photon_transport {false}; +bool atomic_relaxation {true}; bool reduce_tallies {true}; bool res_scat_on {false}; bool restart_run {false}; @@ -607,6 +608,11 @@ void read_settings_xml(pugi::xml_node root) } } + // Check for atomic relaxation + if (check_for_node(root, "atomic_relaxation")) { + atomic_relaxation = get_node_value_bool(root, "atomic_relaxation"); + } + // Number of bins for logarithmic grid if (check_for_node(root, "log_grid_bins")) { n_log_bins = std::stoi(get_node_value(root, "log_grid_bins")); diff --git a/tests/regression_tests/atomic_relaxation/__init__.py b/tests/regression_tests/atomic_relaxation/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/atomic_relaxation/inputs_true.dat b/tests/regression_tests/atomic_relaxation/inputs_true.dat new file mode 100644 index 000000000..637e04285 --- /dev/null +++ b/tests/regression_tests/atomic_relaxation/inputs_true.dat @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + fixed source + 10000 + 1 + + + 1000000.0 1.0 + + + led + false + true + + + + photon electron + + + 1 + flux heating + + + diff --git a/tests/regression_tests/atomic_relaxation/results_true.dat b/tests/regression_tests/atomic_relaxation/results_true.dat new file mode 100644 index 000000000..6f100dac8 --- /dev/null +++ b/tests/regression_tests/atomic_relaxation/results_true.dat @@ -0,0 +1,9 @@ +tally 1: +1.956204E+00 +3.826732E+00 +7.918768E+04 +6.270688E+09 +0.000000E+00 +0.000000E+00 +9.208123E+05 +8.478953E+11 diff --git a/tests/regression_tests/atomic_relaxation/test.py b/tests/regression_tests/atomic_relaxation/test.py new file mode 100644 index 000000000..0d2413f59 --- /dev/null +++ b/tests/regression_tests/atomic_relaxation/test.py @@ -0,0 +1,41 @@ +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + + +@pytest.fixture +def model(): + mat = openmc.Material() + mat.add_nuclide('Pb208', 1.0) + mat.set_density('g/cm3', 11.35) + + sphere = openmc.Sphere(r=1.0e9, boundary_type='reflective') + inside_sphere = openmc.Cell(fill=mat, region=-sphere) + model = openmc.Model() + model.geometry = openmc.Geometry([inside_sphere]) + + # Isotropic point source of 1 MeV photons at the origin + model.settings.source = openmc.IndependentSource( + particle='photon', + energy=openmc.stats.delta_function(1.0e6) + ) + + # Fixed-source photon transport with atomic relaxation disabled + model.settings.particles = 10000 + model.settings.batches = 1 + model.settings.photon_transport = True + model.settings.electron_treatment = 'led' + model.settings.atomic_relaxation = False + model.settings.run_mode = 'fixed source' + + tally = openmc.Tally() + tally.filters = [openmc.ParticleFilter(['photon', 'electron'])] + tally.scores = ['flux', 'heating'] + model.tallies = [tally] + return model + + +def test_atomic_relaxation(model): + harness = PyAPITestHarness('statepoint.1.h5', model=model) + harness.main() diff --git a/tests/unit_tests/test_settings.py b/tests/unit_tests/test_settings.py index 9f693802c..115b6470a 100644 --- a/tests/unit_tests/test_settings.py +++ b/tests/unit_tests/test_settings.py @@ -59,6 +59,7 @@ def test_export_to_xml(run_in_tmpdir): s.log_grid_bins = 2000 s.photon_transport = False s.electron_treatment = 'led' + s.atomic_relaxation = False s.write_initial_source = True s.weight_window_checkpoints = {'surface': True, 'collision': False} source_region_mesh = openmc.RegularMesh() @@ -147,6 +148,7 @@ def test_export_to_xml(run_in_tmpdir): assert s.log_grid_bins == 2000 assert not s.photon_transport assert s.electron_treatment == 'led' + assert not s.atomic_relaxation assert s.write_initial_source assert len(s.volume_calculations) == 1 vol = s.volume_calculations[0]