From 24655dfd5db9fced3baad9ccc7197615e25ef520 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 31 Mar 2025 09:29:30 -0500 Subject: [PATCH 001/159] Report plot ID instead of index for unsupported plot types in random ray mode (#3361) --- src/random_ray/random_ray_simulation.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/random_ray/random_ray_simulation.cpp b/src/random_ray/random_ray_simulation.cpp index bf01d2f5766..36517bee462 100644 --- a/src/random_ray/random_ray_simulation.cpp +++ b/src/random_ray/random_ray_simulation.cpp @@ -303,20 +303,21 @@ void validate_random_ray_inputs() for (int p = 0; p < model::plots.size(); p++) { // Get handle to OpenMC plot object - Plot* openmc_plot = dynamic_cast(model::plots[p].get()); + const auto& openmc_plottable = model::plots[p]; + Plot* openmc_plot = dynamic_cast(openmc_plottable.get()); // Random ray plots only support voxel plots if (!openmc_plot) { warning(fmt::format( "Plot {} will not be used for end of simulation data plotting -- only " "voxel plotting is allowed in random ray mode.", - p)); + openmc_plottable->id())); continue; } else if (openmc_plot->type_ != Plot::PlotType::voxel) { warning(fmt::format( "Plot {} will not be used for end of simulation data plotting -- only " "voxel plotting is allowed in random ray mode.", - p)); + openmc_plottable->id())); continue; } } From b67771a3d0451be6857e8459a6d2a15a476ec994 Mon Sep 17 00:00:00 2001 From: John Tramm Date: Tue, 1 Apr 2025 21:47:49 -0500 Subject: [PATCH 002/159] Random Ray AutoMagic Setup (#3351) Co-authored-by: Paul Romano --- .github/workflows/ci.yml | 5 + docs/source/io_formats/settings.rst | 8 + docs/source/pythonapi/deplete.rst | 8 +- docs/source/usersguide/random_ray.rst | 212 +++++++- docs/source/usersguide/variance_reduction.rst | 83 +-- .../openmc/random_ray/flat_source_domain.h | 10 + openmc/model/model.py | 482 ++++++++++++++++++ openmc/settings.py | 17 +- src/random_ray/flat_source_domain.cpp | 55 ++ src/random_ray/random_ray_simulation.cpp | 44 +- src/settings.cpp | 9 + .../mgxs_library_ce_to_mg/test.py | 9 +- .../mgxs_library_ce_to_mg_nuclides/test.py | 9 +- .../random_ray_auto_convert/__init__.py | 0 .../infinite_medium/inputs_true.dat | 64 +++ .../infinite_medium/results_true.dat | 2 + .../material_wise/inputs_true.dat | 64 +++ .../material_wise/results_true.dat | 2 + .../stochastic_slab/inputs_true.dat | 64 +++ .../stochastic_slab/results_true.dat | 2 + .../random_ray_auto_convert/test.py | 54 ++ .../__init__.py | 0 .../inputs_true.dat | 65 +++ .../results_true.dat | 2 + .../random_ray_diagonal_stabilization/test.py | 61 +++ tools/ci/gha-install-dagmc.sh | 4 +- 26 files changed, 1243 insertions(+), 92 deletions(-) create mode 100644 tests/regression_tests/random_ray_auto_convert/__init__.py create mode 100644 tests/regression_tests/random_ray_auto_convert/infinite_medium/inputs_true.dat create mode 100644 tests/regression_tests/random_ray_auto_convert/infinite_medium/results_true.dat create mode 100644 tests/regression_tests/random_ray_auto_convert/material_wise/inputs_true.dat create mode 100644 tests/regression_tests/random_ray_auto_convert/material_wise/results_true.dat create mode 100644 tests/regression_tests/random_ray_auto_convert/stochastic_slab/inputs_true.dat create mode 100644 tests/regression_tests/random_ray_auto_convert/stochastic_slab/results_true.dat create mode 100644 tests/regression_tests/random_ray_auto_convert/test.py create mode 100644 tests/regression_tests/random_ray_diagonal_stabilization/__init__.py create mode 100644 tests/regression_tests/random_ray_diagonal_stabilization/inputs_true.dat create mode 100644 tests/regression_tests/random_ray_diagonal_stabilization/results_true.dat create mode 100644 tests/regression_tests/random_ray_diagonal_stabilization/test.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 877ec68338e..38a49b60219 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,6 +81,11 @@ jobs: RDMAV_FORK_SAFE: 1 steps: + - name: Setup cmake + uses: jwlawson/actions-setup-cmake@v2 + with: + cmake-version: '3.31' + - name: Checkout repository uses: actions/checkout@v4 with: diff --git a/docs/source/io_formats/settings.rst b/docs/source/io_formats/settings.rst index a59d08049ba..cef9bf911f9 100644 --- a/docs/source/io_formats/settings.rst +++ b/docs/source/io_formats/settings.rst @@ -487,6 +487,14 @@ found in the :ref:`random ray user guide `. :type: The type of the domain. Can be ``material``, ``cell``, or ``universe``. + + :diagonal_stabilization_rho: + The rho factor for use with diagonal stabilization. This technique is + applied when negative diagonal (in-group) elements are detected in + the scattering matrix of input MGXS data, which is a common feature + of transport corrected MGXS data. + + *Default*: 1.0 ---------------------------------- ```` Element diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 5d02fa6b980..3967f1a1810 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -78,20 +78,18 @@ A minimal example for performing depletion would be: >>> import openmc.deplete >>> geometry = openmc.Geometry.from_xml() >>> settings = openmc.Settings.from_xml() - >>> model = openmc.model.Model(geometry, settings) + >>> model = openmc.Model(geometry, settings) # Representation of a depletion chain >>> chain_file = "chain_casl.xml" - >>> operator = openmc.deplete.CoupledOperator( - ... model, chain_file) + >>> operator = openmc.deplete.CoupledOperator(model, chain_file) # Set up 5 time steps of one day each >>> dt = [24 * 60 * 60] * 5 >>> power = 1e6 # constant power of 1 MW # Deplete using mid-point predictor-corrector - >>> cecm = openmc.deplete.CECMIntegrator( - ... operator, dt, power) + >>> cecm = openmc.deplete.CECMIntegrator(operator, dt, power) >>> cecm.integrate() Internal Classes and Functions diff --git a/docs/source/usersguide/random_ray.rst b/docs/source/usersguide/random_ray.rst index f28aed78451..ce982119660 100644 --- a/docs/source/usersguide/random_ray.rst +++ b/docs/source/usersguide/random_ray.rst @@ -11,6 +11,76 @@ active batches `. However, there are a couple of settings that are unique to the random ray solver and a few areas that the random ray run strategy differs, both of which will be described in this section. +.. _quick_start: + +----------- +Quick Start +----------- + +While this page contains a comprehensive guide to the random ray solver and +its various parameters, the process of converting an existing continuous energy +Monte Carlo model to a random ray model can be largely automated via convenience +functions in OpenMC's Python interface:: + + # Define continuous energy model as normal + model = openmc.Model() + ... + + # Convert model to multigroup (will auto-generate MGXS library if needed) + model.convert_to_multigroup() + + # Convert model to random ray and initialize random ray parameters + # to reasonable defaults based on the specifics of the geometry + model.convert_to_random_ray() + + # (Optional) Overlay source region decomposition mesh to improve fidelity of the + # random ray solver. Adjust 'n' for fidelity vs runtime. + n = 100 + mesh = openmc.RegularMesh() + mesh.dimension = (n, n, n) + mesh.lower_left = model.geometry.bounding_box.lower_left + mesh.upper_right = model.geometry.bounding_box.upper_right + model.settings.random_ray['source_region_meshes'] = [(mesh, [model.geometry.root_universe])] + + # (Optional) Improve fidelity of the random ray solver by enabling linear sources + model.settings.random_ray['source_shape'] = 'linear' + + # (Optional) Increase the number of rays/batch, to reduce uncertainty + model.settings.particles = 500 + +The above strategy first converts the continuous energy model to a multigroup +one using the :meth:`openmc.Model.convert_to_multigroup` method. By default, +this will internally run a coarsely converged continuous energy Monte Carlo +simulation to produce an estimated multigroup macroscopic cross section set for +each material specified in the model, and store this data into a multigroup +cross section library file (``mgxs.h5``) that can be used by the random ray +solver. + +The :meth:`openmc.Model.convert_to_random_ray` method enables random ray mode +and performs an analysis of the model geometry to determine reasonable values +for all required parameters. If default behavior is not satisfactory, the user +can manually adjust the settings in the :attr:`~openmc.Settings.random_ray` +dictionary in the :class:`openmc.Settings` as described in the sections below. + +Finally a few optional steps are shown. The first (recommended) step overlays a +mesh over the geometry to create smaller source regions so that source +resolution improves and the random ray solver becomes more accurate. Varying the +mesh resolution can be used to trade off between accuracy and runtime. +High-fidelity fission reactor simulation may require source region sizes below 1 +cm, while larger fixed source problems with some tolerance for error may be able +to use source regions of 10 or 100 cm. + +We also enable linear sources, which can improve the accuracy of the random ray +solver and/or allow for a much coarser mesh resolution to be overlaid. Finally, +the number of rays per batch is adjusted. The goal here is to ensure that the +source region miss rate is below 1%, which is reported by OpenMC at the end of +the simulation (or before via a warning if it is very high). + +.. warning:: + If using a mesh filter for tallying or weight window generation, ensure that + the same mesh is used for source region decomposition via + ``model.settings.random_ray['source_region_meshes']``. + ------------------------ Enabling Random Ray Mode ------------------------ @@ -557,29 +627,118 @@ variety of problem types (or through a multidimensional parameter sweep of design variables) with only modest errors and at greatly reduced cost as compared to using only continuous energy Monte Carlo. +~~~~~~~~~~~~ +The Easy Way +~~~~~~~~~~~~ + +The easiest way to generate a multigroup cross section library is to use the +:meth:`openmc.Model.convert_to_multigroup` method. This method will +automatically output a multigroup cross section library file (``mgxs.h5``) from +a continuous energy Monte Carlo model and alter the material definitions in the +model to use these multigroup cross sections. An example is given below:: + + # Assume we already have a working continuous energy model + model.convert_to_multigroup( + method="material_wise", + groups="CASMO-2", + nparticles=2000, + overwrite_mgxs_library=False, + mgxs_path="mgxs.h5", + correction=None + ) + +The most important parameter to set is the ``method`` parameter, which can be +either "stochastic_slab", "material_wise", or "infinite_medium". An overview +of these methods is given below: + +.. list-table:: Comparison of Automatic MGXS Generation Methods + :header-rows: 1 + :widths: 10 30 30 30 + + * - Method + - Description + - Pros + - Cons + * - ``material_wise`` (default) + - * Higher Fidelity + * Runs a CE simulation with the original geometry and source, tallying + cross sections with a material filter. + - * Typically the most accurate of the three methods + * Accurately captures (averaged over the full problem domain) + both spatial and resonance self shielding effects + - * Potentially slower as the full geometry must be run + * If a material is only present far from the source and doesn't get tallied + to in the CE simulation, the MGXS will be zero for that material. + * - ``stochastic_slab`` + - * Medium Fidelity + * Runs a CE simulation with a greatly simplified geometry, where materials + are randomly assigned to layers in a 1D "stochastic slab sandwich" geometry + - * Still captures resonant self shielding and resonance effects between materials + * Fast due to the simplified geometry + * Able to produce cross section data for all materials, regardless of how + far they are from the source in the original geometry + - * Does not capture most spatial self shielding effects, e.g., no lattice physics. + * - ``infinite_medium`` + - * Lower Fidelity + * Runs one CE simulation per material independently. Each simulation is just + an infinite medium slowing down problem, with an assumed external source term. + - * Simple + - * Poor accuracy (no spatial information, no lattice physics, no resonance effects + between materials) + * May hang if a material has a k-infinity greater than 1.0 + +When selecting a non-default energy group structure, you can manually define +group boundaries or specify the name of a known group structure (a list of which +can be found at :data:`openmc.mgxs.GROUP_STRUCTURES`). The ``nparticles`` +parameter can be adjusted upward to improve the fidelity of the generated cross +section library. The ``correction`` parameter can be set to ``"P0"`` to enable +P0 transport correction. The ``overwrite_mgxs_library`` parameter can be set to +``True`` to overwrite an existing MGXS library file, or ``False`` to skip +generation and use an existing library file. + +.. note:: + MGXS transport correction (via setting the ``correction`` parameter in the + :meth:`openmc.Model.convert_to_multigroup` method to ``"P0"``) may + result in negative in-group scattering cross sections, which can cause + numerical instability. To mitigate this, during a random ray solve OpenMC + will automatically apply + `diagonal stabilization `_ + with a :math:`\rho` default value of 1.0, which can be adjusted with the + ``settings.random_ray['diagonal_stabilization_rho']`` parameter. + +Ultimately, the methods described above are all just approximations. +Approximations in the generated MGXS data will fundamentally limit the potential +accuracy of the random ray solver. However, the methods described above are all +useful in that they can provide a good starting point for a random ray +simulation, and if more fidelity is needed the user may wish to follow the +instructions below or experiment with transport correction techniques to improve +the fidelity of the generated MGXS data. + +~~~~~~~~~~~~ +The Hard Way +~~~~~~~~~~~~ + We give here a quick summary of how to produce a multigroup cross section data file (``mgxs.h5``) from a starting point of a typical continuous energy Monte -Carlo input file. Notably, continuous energy input files define materials as a -mixture of nuclides with different densities, whereas multigroup materials are -simply defined by which name they correspond to in a ``mgxs.h5`` library file. +Carlo model. Notably, continuous energy models define materials as a mixture of +nuclides with different densities, whereas multigroup materials are simply +defined by which name they correspond to in a ``mgxs.h5`` library file. To generate the cross section data, we begin with a continuous energy Monte -Carlo input deck and add in the required tallies that will be needed to generate -our library. In this example, we will specify material-wise cross sections and a -two group energy decomposition:: +Carlo model and add in the tallies that are needed to generate our library. In +this example, we will specify material-wise cross sections and a two-group +energy decomposition:: # Define geometry ... - ... geometry = openmc.Geometry() ... - ... # Initialize MGXS library with a finished OpenMC geometry object mgxs_lib = openmc.mgxs.Library(geometry) # Pick energy group structure - groups = openmc.mgxs.EnergyGroups(openmc.mgxs.GROUP_STRUCTURES['CASMO-2']) + groups = openmc.mgxs.EnergyGroups('CASMO-2') mgxs_lib.energy_groups = groups # Disable transport correction @@ -587,7 +746,7 @@ two group energy decomposition:: # Specify needed cross sections for random ray mgxs_lib.mgxs_types = ['total', 'absorption', 'nu-fission', 'fission', - 'nu-scatter matrix', 'multiplicity matrix', 'chi'] + 'nu-scatter matrix', 'multiplicity matrix', 'chi'] # Specify a "cell" domain type for the cross section tally filters mgxs_lib.domain_type = "material" @@ -614,13 +773,13 @@ two group energy decomposition:: ... When selecting an energy decomposition, you can manually define group boundaries -or pick out a group structure already known to OpenMC (a list of which can be -found at :class:`openmc.mgxs.GROUP_STRUCTURES`). Once the above input deck has -been run, the resulting statepoint file will contain the needed flux and -reaction rate tally data so that a MGXS library file can be generated. Below is -the postprocessing script needed to generate the ``mgxs.h5`` library file given -a statepoint file (e.g., ``statepoint.100.h5``) file and summary file (e.g., -``summary.h5``) that resulted from running our previous example:: +or specify the name of known group structure (a list of which can be found at +:data:`openmc.mgxs.GROUP_STRUCTURES`). Once the above model has been run, the +resulting statepoint file will contain the needed flux and reaction rate tally +data so that a MGXS library file can be generated. Below is the postprocessing +script needed to generate the ``mgxs.h5`` library file given a statepoint file +(e.g., ``statepoint.100.h5``) file and summary file (e.g., ``summary.h5``) that +resulted from running our previous example:: import openmc @@ -628,10 +787,7 @@ a statepoint file (e.g., ``statepoint.100.h5``) file and summary file (e.g., geom = summary.geometry mats = summary.materials - statepoint_filename = 'statepoint.100.h5' - sp = openmc.StatePoint(statepoint_filename) - - groups = openmc.mgxs.EnergyGroups(openmc.mgxs.GROUP_STRUCTURES['CASMO-2']) + groups = openmc.mgxs.EnergyGroups('CASMO-2') mgxs_lib = openmc.mgxs.Library(geom) mgxs_lib.energy_groups = groups mgxs_lib.correction = None @@ -653,10 +809,10 @@ a statepoint file (e.g., ``statepoint.100.h5``) file and summary file (e.g., # Construct all tallies needed for the multi-group cross section library mgxs_lib.build_library() - mgxs_lib.load_from_statepoint(sp) + with openmc.StatePoint('statepoint.100.h5') as sp: + mgxs_lib.load_from_statepoint(sp) - names = [] - for mat in mgxs_lib.domains: names.append(mat.name) + names = [mat.name for mat in mgxs_lib.domains] # Create a MGXS File which can then be written to disk mgxs_file = mgxs_lib.create_mg_library(xs_type='macro', xsdata_names=names) @@ -665,8 +821,8 @@ a statepoint file (e.g., ``statepoint.100.h5``) file and summary file (e.g., mgxs_file.export_to_hdf5("mgxs.h5") Notably, the postprocessing script needs to match the same -:class:`openmc.mgxs.Library` settings that were used to generate the tallies, -but otherwise is able to discern the rest of the simulation details from the +:class:`openmc.mgxs.Library` settings that were used to generate the tallies but +is otherwise able to discern the rest of the simulation details from the statepoint and summary files. Once the postprocessing script is successfully run, the ``mgxs.h5`` file can be loaded by subsequent runs of OpenMC. @@ -701,11 +857,11 @@ multigroup library instead of defining their isotopic contents, as:: water_data = openmc.Macroscopic('Hot borated water') # Instantiate some Materials and register the appropriate Macroscopic objects - fuel= openmc.Material(name='UO2 (2.4%)') + fuel = openmc.Material(name='UO2 (2.4%)') fuel.set_density('macro', 1.0) fuel.add_macroscopic(fuel_data) - water= openmc.Material(name='Hot borated water') + water = openmc.Material(name='Hot borated water') water.set_density('macro', 1.0) water.add_macroscopic(water_data) diff --git a/docs/source/usersguide/variance_reduction.rst b/docs/source/usersguide/variance_reduction.rst index f8ee9c35c48..3256fa35f36 100644 --- a/docs/source/usersguide/variance_reduction.rst +++ b/docs/source/usersguide/variance_reduction.rst @@ -88,59 +88,66 @@ random ray mode can be found in the :ref:`Random Ray User Guide `. ray solver. A high level overview of the current workflow for generation of weight windows with FW-CADIS using random ray is given below. -1. Produce approximate multigroup cross section data (stored in a ``mgxs.h5`` - library). There is more information on generating multigroup cross sections - via OpenMC in the :ref:`multigroup materials ` user guide, and a - specific example of generating cross section data for use with random ray in - the :ref:`random ray MGXS guide `. - -2. Make a copy of your continuous energy Python input file. You'll edit the new - file to work in multigroup mode with random ray for producing weight windows. - -3. Adjust the material definitions in your new multigroup Python file to utilize - the multigroup cross sections instead of nuclide-wise continuous energy data. - There is a specific example of making this conversion in the :ref:`random ray - MGXS guide `. - -4. Configure OpenMC to run in random ray mode (by adding several standard random - ray input flags and settings to the :attr:`openmc.Settings.random_ray` - dictionary). More information can be found in the :ref:`Random Ray User - Guide `. - -5. Add in a :class:`~openmc.WeightWindowGenerator` in a similar manner as for +1. Begin by making a deepy copy of your continuous energy Python model and then + convert the copy to be multigroup and use the random ray transport solver. + The conversion process can largely be automated as described in more detail + in the :ref:`random ray quick start guide `, summarized below:: + + # Define continuous energy model + ce_model = openmc.pwr_pin_cell() # example, replace with your model + + # Make a copy to convert to multigroup and random ray + model = copy.deepcopy(ce_model) + + # Convert model to multigroup (will auto-generate MGXS library if needed) + model.convert_to_multigroup() + + # Convert model to random ray and initialize random ray parameters + # to reasonable defaults based on the specifics of the geometry + model.convert_to_random_ray() + + # (Optional) Overlay source region decomposition mesh to improve fidelity of the + # random ray solver. Adjust 'n' for fidelity vs runtime. + n = 10 + mesh = openmc.RegularMesh() + mesh.dimension = (n, n, n) + mesh.lower_left = model.geometry.bounding_box.lower_left + mesh.upper_right = model.geometry.bounding_box.upper_right + model.settings.random_ray['source_region_meshes'] = [(mesh, [model.geometry.root_universe])] + + # (Optional) Improve fidelity of the random ray solver by enabling linear sources + model.settings.random_ray['source_shape'] = 'linear' + + # (Optional) Increase the number of rays/batch, to reduce uncertainty + model.settings.particles = 500 + + If you need to improve the fidelity of the MGXS library, there is more + information on generating multigroup cross sections via OpenMC in the + :ref:`random ray MGXS guide `. + +2. Add in a :class:`~openmc.WeightWindowGenerator` in a similar manner as for MAGIC generation with Monte Carlo and set the :attr:`method` attribute set to ``"fw_cadis"``:: - # Define weight window spatial mesh - ww_mesh = openmc.RegularMesh() - ww_mesh.dimension = (10, 10, 10) - ww_mesh.lower_left = (0.0, 0.0, 0.0) - ww_mesh.upper_right = (100.0, 100.0, 100.0) - - # Create weight window object and adjust parameters + # Create weight window object and adjust parameters, using the same mesh + # we used for source region decomposition wwg = openmc.WeightWindowGenerator( method='fw_cadis', - mesh=ww_mesh, + mesh=mesh, max_realizations=settings.batches ) # Add generator to openmc.settings object settings.weight_window_generators = wwg - .. warning:: If using FW-CADIS weight window generation, ensure that the selected weight window mesh does not subdivide any source regions in the problem. This can - be ensured by assigning the weight window tally mesh to the root universe so - as to create source region boundaries that conform to the mesh, as in the - example below. - -:: - - root = model.geometry.root_universe - settings.random_ray['source_region_meshes'] = [(ww_mesh, [root])] + be ensured by using the same mesh for both source region subdivision (i.e., + assigning to ``model.settings.random_ray['source_region_meshes']``) and for + weight window generation. -6. When running your multigroup random ray input deck, OpenMC will automatically +3. When running your multigroup random ray input deck, OpenMC will automatically run a forward solve followed by an adjoint solve, with a ``weight_windows.h5`` file generated at the end. The ``weight_windows.h5`` file will contain FW-CADIS generated weight windows. This file can be used in diff --git a/include/openmc/random_ray/flat_source_domain.h b/include/openmc/random_ray/flat_source_domain.h index 39d87d663a2..656b0354a7b 100644 --- a/include/openmc/random_ray/flat_source_domain.h +++ b/include/openmc/random_ray/flat_source_domain.h @@ -58,6 +58,7 @@ class FlatSourceDomain { SourceRegionHandle get_subdivided_source_region_handle( int64_t sr, int mesh_bin, Position r, double dist, Direction u); void finalize_discovered_source_regions(); + void apply_transport_stabilization(); int64_t n_source_regions() const { return source_regions_.n_source_regions(); @@ -71,6 +72,9 @@ class FlatSourceDomain { // Static Data members static bool volume_normalized_flux_tallies_; static bool adjoint_; // If the user wants outputs based on the adjoint flux + static double + diagonal_stabilization_rho_; // Adjusts strength of diagonal stabilization + // for transport corrected MGXS data // Static variables to store source region meshes and domains static std::unordered_map>> @@ -127,6 +131,12 @@ class FlatSourceDomain { std::unordered_map source_region_map_; + // If transport corrected MGXS data is being used, there may be negative + // in-group scattering cross sections that can result in instability in MOC + // and random ray if used naively. This flag enables a stabilization + // technique. + bool is_transport_stabilization_needed_ {false}; + protected: //---------------------------------------------------------------------------- // Methods diff --git a/openmc/model/model.py b/openmc/model/model.py index e8558f4ad05..260059f4a84 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -1,9 +1,12 @@ from __future__ import annotations from collections.abc import Iterable, Sequence +import copy from functools import lru_cache from pathlib import Path import math from numbers import Integral, Real +import random +import re from tempfile import NamedTemporaryFile, TemporaryDirectory import warnings @@ -1439,3 +1442,482 @@ def differentiate_mats(self, diff_volume_method: str = None, depletable_only: bo self.materials = openmc.Materials( self.geometry.get_all_materials().values() ) + + def _generate_infinite_medium_mgxs(self, groups, nparticles, mgxs_path, correction): + """Generate a MGXS library by running multiple OpenMC simulations, each + representing an infinite medium simulation of a single isolated + material. A discrete source is used to sample particles, with an equal + strength spread across each of the energy groups. This is a highly naive + method that ignores all spatial self shielding effects and all resonance + shielding effects between materials. + + Parameters + ---------- + groups : openmc.mgxs.EnergyGroups + Energy group structure for the MGXS. + mgxs_path : path-like + Filename for the MGXS HDF5 file. + """ + warnings.warn("The infinite medium method of generating MGXS may hang " + "if a material has a k-infinity > 1.0.") + mgxs_sets = [] + for material in self.materials: + openmc.reset_auto_ids() + model = openmc.Model() + + # Set materials on the model + model.materials = [material] + + # Settings + model.settings.batches = 100 + model.settings.particles = nparticles + model.settings.run_mode = 'fixed source' + + # Make a discrete source that is uniform over the bins of the group structure + n_groups = groups.num_groups + midpoints = [] + strengths = [] + for i in range(n_groups): + bounds = groups.get_group_bounds(i+1) + midpoints.append((bounds[0] + bounds[1]) / 2.0) + strengths.append(1.0) + + energy_distribution = openmc.stats.Discrete(x=midpoints, p=strengths) + model.settings.source = openmc.IndependentSource( + space=openmc.stats.Point(), energy=energy_distribution) + model.settings.output = {'summary': True, 'tallies': False} + + # Geometry + box = openmc.model.RectangularPrism( + 100000.0, 100000.0, boundary_type='reflective') + name = material.name + infinite_cell = openmc.Cell(name=name, fill=material, region=-box) + infinite_universe = openmc.Universe(name=name, cells=[infinite_cell]) + model.geometry.root_universe = infinite_universe + + # Add MGXS Tallies + + # Initialize MGXS library with a finished OpenMC geometry object + mgxs_lib = openmc.mgxs.Library(model.geometry) + + # Pick energy group structure + mgxs_lib.energy_groups = groups + + # Disable transport correction + mgxs_lib.correction = correction + + # Specify needed cross sections for random ray + if correction == 'P0': + mgxs_lib.mgxs_types = [ + 'nu-transport', 'absorption', 'nu-fission', 'fission', + 'consistent nu-scatter matrix', 'multiplicity matrix', 'chi' + ] + elif correction is None: + mgxs_lib.mgxs_types = [ + 'total', 'absorption', 'nu-fission', 'fission', + 'consistent nu-scatter matrix', 'multiplicity matrix', 'chi' + ] + + # Specify a "cell" domain type for the cross section tally filters + mgxs_lib.domain_type = "material" + + # Specify the cell domains over which to compute multi-group cross sections + mgxs_lib.domains = model.geometry.get_all_materials().values() + + # Do not compute cross sections on a nuclide-by-nuclide basis + mgxs_lib.by_nuclide = False + + # Check the library - if no errors are raised, then the library is satisfactory. + mgxs_lib.check_library_for_openmc_mgxs() + + # Construct all tallies needed for the multi-group cross section library + mgxs_lib.build_library() + + # Create a "tallies.xml" file for the MGXS Library + mgxs_lib.add_to_tallies_file(model.tallies, merge=True) + + # Run + statepoint_filename = model.run() + + # Load MGXS + with openmc.StatePoint(statepoint_filename) as sp: + mgxs_lib.load_from_statepoint(sp) + + # Create a MGXS File which can then be written to disk + mgxs_set = mgxs_lib.get_xsdata(domain=material, xsdata_name=name) + mgxs_sets.append(mgxs_set) + + # Write the file to disk + mgxs_file = openmc.MGXSLibrary(energy_groups=groups) + for mgxs_set in mgxs_sets: + mgxs_file.add_xsdata(mgxs_set) + mgxs_file.export_to_hdf5(mgxs_path) + + @staticmethod + def _create_stochastic_slab_geometry(materials, cell_thickness=1.0, num_repeats=100): + """Create a geometry representing a stochastic "sandwich" of materials in a + layered slab geometry. To reduce the impact of the order of materials in + the slab, the materials are applied to 'num_repeats' different randomly + positioned layers of 'cell_thickness' each. + + Parameters + ---------- + materials : list of openmc.Material + List of materials to assign. Each material will appear exactly num_repeats times, + then the ordering is randomly shuffled. + cell_thickness : float, optional + Thickness of each lattice cell in x (default 1.0 cm). + num_repeats : int, optional + Number of repeats for each material (default 100). + + Returns + ------- + geometry : openmc.Geometry + The constructed geometry. + box : openmc.stats.Box + A spatial sampling distribution covering the full slab domain. + """ + if not materials: + raise ValueError("At least one material must be provided.") + + num_materials = len(materials) + total_cells = num_materials * num_repeats + total_width = total_cells * cell_thickness + + # Generate an infinite cell/universe for each material + universes = [] + for i in range(num_materials): + cell = openmc.Cell(fill=materials[i]) + universes.append(openmc.Universe(cells=[cell])) + + # Make a list of randomized material idx assignments for the stochastic slab + assignments = list(range(num_materials)) * num_repeats + random.seed(42) + random.shuffle(assignments) + + # Create a list of the (randomized) universe assignments to be used + # when defining the problem lattice. + lattice_entries = [universes[m] for m in assignments] + + # Create the RectLattice for the 1D material variation in x. + lattice = openmc.RectLattice() + lattice.pitch = (cell_thickness, total_width, total_width) + lattice.lower_left = (0.0, 0.0, 0.0) + lattice.universes = [[lattice_entries]] + lattice.outer = universes[0] + + # Define the six outer surfaces with reflective boundary conditions + rpp = openmc.model.RectangularParallelepiped( + 0.0, total_width, 0.0, total_width, 0.0, total_width, + boundary_type='reflective' + ) + + # Create an outer cell that fills with the lattice. + outer_cell = openmc.Cell(fill=lattice, region=-rpp) + + # Build the geometry + geometry = openmc.Geometry([outer_cell]) + + # Define the spatial distribution that covers the full cubic domain + box = openmc.stats.Box(*outer_cell.bounding_box) + + return geometry, box + + def _generate_stochastic_slab_mgxs(self, groups, nparticles, mgxs_path, correction) -> None: + """Generate MGXS assuming a stochastic "sandwich" of materials in a layered + slab geometry. While geometry-specific spatial shielding effects are not + captured, this method can be useful when the geometry has materials only + found far from the source region that the "material_wise" method would + not be capable of generating cross sections for. Conversely, this method + will generate cross sections for all materials in the problem regardless + of type. If this is a fixed source problem, a discrete source is used to + sample particles, with an equal strength spread across each of the + energy groups. + + Parameters + ---------- + groups : openmc.mgxs.EnergyGroups + Energy group structure for the MGXS. + mgxs_path : path-like + Filename for the MGXS HDF5 file. + """ + openmc.reset_auto_ids() + model = openmc.Model() + model.materials = self.materials + + # Settings + model.settings.batches = 200 + model.settings.inactive = 100 + model.settings.particles = nparticles + model.settings.output = {'summary': True, 'tallies': False} + model.settings.run_mode = self.settings.run_mode + + # Stochastic slab geometry + model.geometry, spatial_distribution = Model._create_stochastic_slab_geometry( + model.materials) + + # Make a discrete source that is uniform over the bins of the group structure + n_groups = groups.num_groups + midpoints = [] + strengths = [] + for i in range(n_groups): + bounds = groups.get_group_bounds(i+1) + midpoints.append((bounds[0] + bounds[1]) / 2.0) + strengths.append(1.0) + + energy_distribution = openmc.stats.Discrete(x=midpoints, p=strengths) + model.settings.source = [openmc.IndependentSource( + space=spatial_distribution, energy=energy_distribution, strength=1.0)] + + model.settings.output = {'summary': True, 'tallies': False} + + # Add MGXS Tallies + + # Initialize MGXS library with a finished OpenMC geometry object + mgxs_lib = openmc.mgxs.Library(model.geometry) + + # Pick energy group structure + mgxs_lib.energy_groups = groups + + # Disable transport correction + mgxs_lib.correction = correction + + # Specify needed cross sections for random ray + if correction == 'P0': + mgxs_lib.mgxs_types = ['nu-transport', 'absorption', 'nu-fission', 'fission', + 'consistent nu-scatter matrix', 'multiplicity matrix', 'chi'] + elif correction is None: + mgxs_lib.mgxs_types = ['total', 'absorption', 'nu-fission', 'fission', + 'consistent nu-scatter matrix', 'multiplicity matrix', 'chi'] + + # Specify a "cell" domain type for the cross section tally filters + mgxs_lib.domain_type = "material" + + # Specify the cell domains over which to compute multi-group cross sections + mgxs_lib.domains = model.geometry.get_all_materials().values() + + # Do not compute cross sections on a nuclide-by-nuclide basis + mgxs_lib.by_nuclide = False + + # Check the library - if no errors are raised, then the library is satisfactory. + mgxs_lib.check_library_for_openmc_mgxs() + + # Construct all tallies needed for the multi-group cross section library + mgxs_lib.build_library() + + # Create a "tallies.xml" file for the MGXS Library + mgxs_lib.add_to_tallies_file(model.tallies, merge=True) + + # Run + statepoint_filename = model.run() + + # Load MGXS + with openmc.StatePoint(statepoint_filename) as sp: + mgxs_lib.load_from_statepoint(sp) + + names = [mat.name for mat in mgxs_lib.domains] + + # Create a MGXS File which can then be written to disk + mgxs_file = mgxs_lib.create_mg_library(xs_type='macro', xsdata_names=names) + mgxs_file.export_to_hdf5(mgxs_path) + + def _generate_material_wise_mgxs(self, groups, nparticles, mgxs_path, correction) -> None: + """Generate a material-wise MGXS library for the model by running the + original continuous energy OpenMC simulation of the full material + geometry and source, and tally MGXS data for each material. This method + accurately conserves reaction rates totaled over the entire simulation + domain. However, when the geometry has materials only found far from the + source region, it is possible the Monte Carlo solver may not be able to + score any tallies to these material types, thus resulting in zero cross + section values for these materials. For such cases, the "stochastic + slab" method may be more appropriate. + + Parameters + ---------- + groups : openmc.mgxs.EnergyGroups + Energy group structure for the MGXS. + mgxs_path : str + Filename for the MGXS HDF5 file. + """ + openmc.reset_auto_ids() + model = copy.deepcopy(self) + model.tallies = openmc.Tallies() + + # Settings + model.settings.batches = 200 + model.settings.inactive = 100 + model.settings.particles = nparticles + model.settings.output = {'summary': True, 'tallies': False} + + # Add MGXS Tallies + + # Initialize MGXS library with a finished OpenMC geometry object + mgxs_lib = openmc.mgxs.Library(model.geometry) + + # Pick energy group structure + mgxs_lib.energy_groups = groups + + # Disable transport correction + mgxs_lib.correction = correction + + # Specify needed cross sections for random ray + if correction == 'P0': + mgxs_lib.mgxs_types = [ + 'nu-transport', 'absorption', 'nu-fission', 'fission', + 'consistent nu-scatter matrix', 'multiplicity matrix', 'chi' + ] + elif correction is None: + mgxs_lib.mgxs_types = [ + 'total', 'absorption', 'nu-fission', 'fission', + 'consistent nu-scatter matrix', 'multiplicity matrix', 'chi' + ] + + # Specify a "cell" domain type for the cross section tally filters + mgxs_lib.domain_type = "material" + + # Specify the cell domains over which to compute multi-group cross sections + mgxs_lib.domains = model.geometry.get_all_materials().values() + + # Do not compute cross sections on a nuclide-by-nuclide basis + mgxs_lib.by_nuclide = False + + # Check the library - if no errors are raised, then the library is satisfactory. + mgxs_lib.check_library_for_openmc_mgxs() + + # Construct all tallies needed for the multi-group cross section library + mgxs_lib.build_library() + + # Create a "tallies.xml" file for the MGXS Library + mgxs_lib.add_to_tallies_file(model.tallies, merge=True) + + # Run + statepoint_filename = model.run() + + # Load MGXS + with openmc.StatePoint(statepoint_filename) as sp: + mgxs_lib.load_from_statepoint(sp) + + names = [mat.name for mat in mgxs_lib.domains] + + # Create a MGXS File which can then be written to disk + mgxs_file = mgxs_lib.create_mg_library( + xs_type='macro', xsdata_names=names) + mgxs_file.export_to_hdf5(mgxs_path) + + def convert_to_multigroup(self, method="material_wise", groups='CASMO-2', + nparticles=2000, overwrite_mgxs_library=False, + mgxs_path: PathLike = "mgxs.h5", correction=None): + """Convert all materials from continuous energy to multigroup. + + If no MGXS data library file is found, generate one using one or more + continuous energy Monte Carlo simulations. + + Parameters + ---------- + method : {"material_wise", "stochastic_slab", "infinite_medium"}, optional + Method to generate the MGXS. + groups : openmc.mgxs.EnergyGroups or str, optional + Energy group structure for the MGXS or the name of the group + structure (based on keys from openmc.mgxs.GROUP_STRUCTURES). + mgxs_path : str, optional + Filename of the mgxs.h5 library file. + correction : str, optional + Transport correction to apply to the MGXS. Options are None and + "P0". + """ + if isinstance(groups, str): + groups = openmc.mgxs.EnergyGroups(groups) + + # Make sure all materials have a name, and that the name is a valid HDF5 + # dataset name + for material in self.materials: + if material.name is None: + material.name = f"material {material.id}" + material.name = re.sub(r'[^a-zA-Z0-9]', '_', material.name) + + # If needed, generate the needed MGXS data library file + if not Path(mgxs_path).is_file() or overwrite_mgxs_library: + if method == "infinite_medium": + self._generate_infinite_medium_mgxs( + groups, nparticles, mgxs_path, correction) + elif method == "material_wise": + self._generate_material_wise_mgxs( + groups, nparticles, mgxs_path, correction) + elif method == "stochastic_slab": + self._generate_stochastic_slab_mgxs( + groups, nparticles, mgxs_path, correction) + else: + raise ValueError( + f'MGXS generation method "{method}" not recognized') + else: + print(f'Existing MGXS library file "{mgxs_path}" will be used') + + # Convert all continuous energy materials to multigroup + self.materials.cross_sections = mgxs_path + for material in self.materials: + material.set_density('macro', 1.0) + material._nuclides = [] + material._sab = [] + material.add_macroscopic(material.name) + + self.settings.energy_mode = 'multi-group' + + def convert_to_random_ray(self): + """Convert a multigroup model to use random ray. + + This method determines values for the needed settings and adds them to + the settings.random_ray dictionary so as to enable random ray mode. The + settings that are populated are: + + - 'ray_source' (openmc.IndependentSource): Where random ray starting + points are sampled from. + - 'distance_inactive' (float): The "dead zone" distance at the beginning + of the ray. + - 'distance_active' (float): The "active" distance of the ray + - 'particles' (int): Number of rays to simulate + + The method will determine reasonable defaults for each of the above + variables based on analysis of the model's geometry. The function will + have no effect if the random ray dictionary is already defined in the + model settings. + """ + # If the random ray dictionary is already set, don't overwrite it + if self.settings.random_ray: + warnings.warn("Random ray conversion skipped as " + "settings.random_ray dictionary is already set.") + return + + if self.settings.energy_mode != 'multi-group': + raise ValueError( + "Random ray conversion failed: energy mode must be " + "'multi-group'. Use convert_to_multigroup() first." + ) + + # Helper function for detecting infinity + def _replace_infinity(value): + if np.isinf(value): + return 1.0 if value > 0 else -1.0 + return value + + # Get a bounding box for sampling rays. We can utilize the geometry's bounding box + # though for 2D problems we need to detect the infinities and replace them with an + # arbitrary finite value. + bounding_box = self.geometry.bounding_box + lower_left = [_replace_infinity(v) for v in bounding_box.lower_left] + upper_right = [_replace_infinity(v) for v in bounding_box.upper_right] + uniform_dist_ray = openmc.stats.Box(lower_left, upper_right) + rr_source = openmc.IndependentSource(space=uniform_dist_ray) + self.settings.random_ray['ray_source'] = rr_source + + # For the dead zone and active length, a reasonable guess is the larger of either: + # 1) The maximum chord length through the geometry (as defined by its bounding box) + # 2) 30 cm + # Then, set the active length to be 5x longer than the dead zone length, for the sake of efficiency. + chord_length = np.array(upper_right) - np.array(lower_left) + max_length = max(np.linalg.norm(chord_length), 30.0) + + self.settings.random_ray['distance_inactive'] = max_length + self.settings.random_ray['distance_active'] = 5 * max_length + + # Take a wild guess as to how many rays are needed + self.settings.particles = 2 * int(max_length) diff --git a/openmc/settings.py b/openmc/settings.py index 17c24ba81e7..a2bf51a51e5 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -187,6 +187,17 @@ class Settings: or openmc.Universe. The mesh will be applied to the listed domains to subdivide source regions so as to improve accuracy and/or conform with tally meshes. + :diagonal_stabilization_rho: + The rho factor for use with diagonal stabilization. This technique is + applied when negative diagonal (in-group) elements are detected in + the scattering matrix of input MGXS data, which is a common feature + of transport corrected MGXS data. The default is 1.0, which ensures + no negative diagonal elements are present in the iteration matrix and + thus stabilizes the simulation. A value of 0.0 will disable diagonal + stabilization. Values between 0.0 and 1.0 will apply a degree of + stabilization, which may be desirable as stronger diagonal stabilization + also tends to dampen the convergence rate of the solver, thus requiring + more iterations to converge. .. versionadded:: 0.15.0 resonance_scattering : dict @@ -1177,6 +1188,10 @@ def random_ray(self, random_ray: dict): elif key == 'sample_method': cv.check_value('sample method', value, ('prng', 'halton')) + elif key == 'diagonal_stabilization_rho': + cv.check_type('diagonal stabilization rho', value, Real) + cv.check_greater_than('diagonal stabilization rho', + value, 0.0, True) else: raise ValueError(f'Unable to set random ray to "{key}" which is ' 'unsupported by OpenMC') @@ -2018,7 +2033,7 @@ def _random_ray_from_xml_element(self, root): if elem is not None: self.random_ray = {} for child in elem: - if child.tag in ('distance_inactive', 'distance_active'): + if child.tag in ('distance_inactive', 'distance_active', 'diagonal_stabilization_rho'): self.random_ray[child.tag] = float(child.text) elif child.tag == 'source': source = SourceBase.from_xml_element(child) diff --git a/src/random_ray/flat_source_domain.cpp b/src/random_ray/flat_source_domain.cpp index 33651574fa0..ff8e5d23273 100644 --- a/src/random_ray/flat_source_domain.cpp +++ b/src/random_ray/flat_source_domain.cpp @@ -30,6 +30,7 @@ RandomRayVolumeEstimator FlatSourceDomain::volume_estimator_ { RandomRayVolumeEstimator::HYBRID}; bool FlatSourceDomain::volume_normalized_flux_tallies_ {false}; bool FlatSourceDomain::adjoint_ {false}; +double FlatSourceDomain::diagonal_stabilization_rho_ {1.0}; std::unordered_map>> FlatSourceDomain::mesh_domain_map_; @@ -1106,6 +1107,11 @@ void FlatSourceDomain::flatten_xs() double sigma_s = m.get_xs(MgxsType::NU_SCATTER, g_in, &g_out, NULL, NULL, t, a); sigma_s_.push_back(sigma_s); + // For transport corrected XS data, diagonal elements may be negative. + // In this case, set a flag to enable transport stabilization for the + // simulation. + if (g_out == g_in && sigma_s < 0.0) + is_transport_stabilization_needed_ = true; } } else { sigma_t_.push_back(0); @@ -1427,4 +1433,53 @@ void FlatSourceDomain::finalize_discovered_source_regions() discovered_source_regions_.clear(); } +// This is the "diagonal stabilization" technique developed by Gunow et al. in: +// +// Geoffrey Gunow, Benoit Forget, Kord Smith, Stabilization of multi-group +// neutron transport with transport-corrected cross-sections, Annals of Nuclear +// Energy, Volume 126, 2019, Pages 211-219, ISSN 0306-4549, +// https://doi.org/10.1016/j.anucene.2018.10.036. +void FlatSourceDomain::apply_transport_stabilization() +{ + // Don't do anything if all in-group scattering + // cross sections are positive + if (!is_transport_stabilization_needed_) { + return; + } + + // Apply the stabilization factor to all source elements +#pragma omp parallel for + for (int64_t sr = 0; sr < n_source_regions(); sr++) { + int material = source_regions_.material(sr); + if (material == MATERIAL_VOID) { + continue; + } + for (int g = 0; g < negroups_; g++) { + // Only apply stabilization if the diagonal (in-group) scattering XS is + // negative + double sigma_s = + sigma_s_[material * negroups_ * negroups_ + g * negroups_ + g]; + if (sigma_s < 0.0) { + double sigma_t = sigma_t_[material * negroups_ + g]; + double phi_new = source_regions_.scalar_flux_new(sr, g); + double phi_old = source_regions_.scalar_flux_old(sr, g); + + // Equation 18 in the above Gunow et al. 2019 paper. For a default + // rho of 1.0, this ensures there are no negative diagonal elements + // in the iteration matrix. A lesser rho could be used (or exposed + // as a user input parameter) to reduce the negative impact on + // convergence rate though would need to be experimentally tested to see + // if it doesn't become unstable. rho = 1.0 is good as it gives the + // highest assurance of stability, and the impacts on convergence rate + // are pretty mild. + double D = diagonal_stabilization_rho_ * sigma_s / sigma_t; + + // Equation 16 in the above Gunow et al. 2019 paper + source_regions_.scalar_flux_new(sr, g) = + (phi_new - D * phi_old) / (1.0 - D); + } + } + } +} + } // namespace openmc diff --git a/src/random_ray/random_ray_simulation.cpp b/src/random_ray/random_ray_simulation.cpp index 36517bee462..60b00a5be78 100644 --- a/src/random_ray/random_ray_simulation.cpp +++ b/src/random_ray/random_ray_simulation.cpp @@ -478,6 +478,9 @@ void RandomRaySimulation::simulate() // Add source to scalar flux, compute number of FSR hits int64_t n_hits = domain_->add_source_to_scalar_flux(); + // Apply transport stabilization factors + domain_->apply_transport_stabilization(); + if (settings::run_mode == RunMode::EIGENVALUE) { // Compute random ray k-eff k_eff_ = domain_->compute_k_eff(k_eff_); @@ -577,17 +580,23 @@ void RandomRaySimulation::print_results_random_ray( header("Simulation Statistics", 4); fmt::print( " Total Iterations = {}\n", settings::n_batches); - fmt::print(" Flat Source Regions (FSRs) = {}\n", n_source_regions); fmt::print( - " FSRs Containing External Sources = {}\n", n_external_source_regions); + " Number of Rays per Iteration = {}\n", settings::n_particles); + fmt::print(" Inactive Distance = {} cm\n", + RandomRay::distance_inactive_); + fmt::print(" Active Distance = {} cm\n", + RandomRay::distance_active_); + fmt::print(" Source Regions (SRs) = {}\n", n_source_regions); + fmt::print( + " SRs Containing External Sources = {}\n", n_external_source_regions); fmt::print(" Total Geometric Intersections = {:.4e}\n", static_cast(total_geometric_intersections)); fmt::print(" Avg per Iteration = {:.4e}\n", static_cast(total_geometric_intersections) / settings::n_batches); - fmt::print(" Avg per Iteration per FSR = {:.2f}\n", + fmt::print(" Avg per Iteration per SR = {:.2f}\n", static_cast(total_geometric_intersections) / static_cast(settings::n_batches) / n_source_regions); - fmt::print(" Avg FSR Miss Rate per Iteration = {:.4f}%\n", avg_miss_rate); + fmt::print(" Avg SR Miss Rate per Iteration = {:.4f}%\n", avg_miss_rate); fmt::print(" Energy Groups = {}\n", negroups); fmt::print( " Total Integrations = {:.4e}\n", total_integrations); @@ -613,6 +622,33 @@ void RandomRaySimulation::print_results_random_ray( std::string adjoint_true = (FlatSourceDomain::adjoint_) ? "ON" : "OFF"; fmt::print(" Adjoint Flux Mode = {}\n", adjoint_true); + std::string shape; + switch (RandomRay::source_shape_) { + case RandomRaySourceShape::FLAT: + shape = "Flat"; + break; + case RandomRaySourceShape::LINEAR: + shape = "Linear"; + break; + case RandomRaySourceShape::LINEAR_XY: + shape = "Linear XY"; + break; + default: + fatal_error("Invalid random ray source shape"); + } + fmt::print(" Source Shape = {}\n", shape); + std::string sample_method = + (RandomRay::sample_method_ == RandomRaySampleMethod::PRNG) ? "PRNG" + : "Halton"; + fmt::print(" Sample Method = {}\n", sample_method); + + if (domain_->is_transport_stabilization_needed_) { + fmt::print(" Transport XS Stabilization Used = YES (rho = {:.3f})\n", + FlatSourceDomain::diagonal_stabilization_rho_); + } else { + fmt::print(" Transport XS Stabilization Used = NO\n"); + } + header("Timing Statistics", 4); show_time("Total time for initialization", time_initialize.elapsed()); show_time("Reading cross sections", time_read_xs.elapsed(), 1); diff --git a/src/settings.cpp b/src/settings.cpp index 49b6590ecc7..53fcdec38b3 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -345,6 +345,15 @@ void get_run_parameters(pugi::xml_node node_base) } } } + if (check_for_node(random_ray_node, "diagonal_stabilization_rho")) { + FlatSourceDomain::diagonal_stabilization_rho_ = std::stod( + get_node_value(random_ray_node, "diagonal_stabilization_rho")); + if (FlatSourceDomain::diagonal_stabilization_rho_ < 0.0 || + FlatSourceDomain::diagonal_stabilization_rho_ > 1.0) { + fatal_error("Random ray diagonal stabilization rho factor must be " + "between 0 and 1"); + } + } } } diff --git a/tests/regression_tests/mgxs_library_ce_to_mg/test.py b/tests/regression_tests/mgxs_library_ce_to_mg/test.py index 4dff6732357..48a715997ae 100644 --- a/tests/regression_tests/mgxs_library_ce_to_mg/test.py +++ b/tests/regression_tests/mgxs_library_ce_to_mg/test.py @@ -40,8 +40,8 @@ def _run_openmc(self): # Build MG Inputs # Get data needed to execute Library calculations. - sp = openmc.StatePoint(self._sp_name) - self.mgxs_lib.load_from_statepoint(sp) + with openmc.StatePoint(self._sp_name) as sp: + self.mgxs_lib.load_from_statepoint(sp) self._model.mgxs_file, self._model.materials, \ self._model.geometry = self.mgxs_lib.create_mg_mode() @@ -55,11 +55,6 @@ def _run_openmc(self): self._model.export_to_model_xml() self._model.mgxs_file.export_to_hdf5() - # Enforce closing statepoint and summary files so HDF5 - # does not throw an error during the next OpenMC execution - sp._f.close() - sp._summary._f.close() - # Re-run MG mode. if config['mpi']: mpi_args = [config['mpiexec'], '-n', config['mpi_np']] diff --git a/tests/regression_tests/mgxs_library_ce_to_mg_nuclides/test.py b/tests/regression_tests/mgxs_library_ce_to_mg_nuclides/test.py index ee0cdf0d926..a77ad242965 100644 --- a/tests/regression_tests/mgxs_library_ce_to_mg_nuclides/test.py +++ b/tests/regression_tests/mgxs_library_ce_to_mg_nuclides/test.py @@ -40,8 +40,8 @@ def _run_openmc(self): # Build MG Inputs # Get data needed to execute Library calculations. - sp = openmc.StatePoint(self._sp_name) - self.mgxs_lib.load_from_statepoint(sp) + with openmc.StatePoint(self._sp_name) as sp: + self.mgxs_lib.load_from_statepoint(sp) self._model.mgxs_file, self._model.materials, \ self._model.geometry = self.mgxs_lib.create_mg_mode() @@ -55,11 +55,6 @@ def _run_openmc(self): self._model.export_to_model_xml() self._model.mgxs_file.export_to_hdf5() - # Enforce closing statepoint and summary files so HDF5 - # does not throw an error during the next OpenMC execution - sp._f.close() - sp._summary._f.close() - # Re-run MG mode. if config['mpi']: mpi_args = [config['mpiexec'], '-n', config['mpi_np']] diff --git a/tests/regression_tests/random_ray_auto_convert/__init__.py b/tests/regression_tests/random_ray_auto_convert/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/random_ray_auto_convert/infinite_medium/inputs_true.dat b/tests/regression_tests/random_ray_auto_convert/infinite_medium/inputs_true.dat new file mode 100644 index 00000000000..02be4ea7a80 --- /dev/null +++ b/tests/regression_tests/random_ray_auto_convert/infinite_medium/inputs_true.dat @@ -0,0 +1,64 @@ + + + + mgxs.h5 + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 10 + 5 + + + -0.63 -0.63 -1 0.63 0.63 1 + + + true + + + multi-group + + + + -0.63 -0.63 -1.0 0.63 0.63 1.0 + + + 30.0 + 150.0 + + + + + + linear + + + 2 2 + -0.63 -0.63 + 0.63 0.63 + + + diff --git a/tests/regression_tests/random_ray_auto_convert/infinite_medium/results_true.dat b/tests/regression_tests/random_ray_auto_convert/infinite_medium/results_true.dat new file mode 100644 index 00000000000..a0d7fc545f3 --- /dev/null +++ b/tests/regression_tests/random_ray_auto_convert/infinite_medium/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +7.796949E-01 1.055316E-02 diff --git a/tests/regression_tests/random_ray_auto_convert/material_wise/inputs_true.dat b/tests/regression_tests/random_ray_auto_convert/material_wise/inputs_true.dat new file mode 100644 index 00000000000..02be4ea7a80 --- /dev/null +++ b/tests/regression_tests/random_ray_auto_convert/material_wise/inputs_true.dat @@ -0,0 +1,64 @@ + + + + mgxs.h5 + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 10 + 5 + + + -0.63 -0.63 -1 0.63 0.63 1 + + + true + + + multi-group + + + + -0.63 -0.63 -1.0 0.63 0.63 1.0 + + + 30.0 + 150.0 + + + + + + linear + + + 2 2 + -0.63 -0.63 + 0.63 0.63 + + + diff --git a/tests/regression_tests/random_ray_auto_convert/material_wise/results_true.dat b/tests/regression_tests/random_ray_auto_convert/material_wise/results_true.dat new file mode 100644 index 00000000000..ebf510cfc74 --- /dev/null +++ b/tests/regression_tests/random_ray_auto_convert/material_wise/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +7.375068E-01 7.015839E-03 diff --git a/tests/regression_tests/random_ray_auto_convert/stochastic_slab/inputs_true.dat b/tests/regression_tests/random_ray_auto_convert/stochastic_slab/inputs_true.dat new file mode 100644 index 00000000000..02be4ea7a80 --- /dev/null +++ b/tests/regression_tests/random_ray_auto_convert/stochastic_slab/inputs_true.dat @@ -0,0 +1,64 @@ + + + + mgxs.h5 + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 10 + 5 + + + -0.63 -0.63 -1 0.63 0.63 1 + + + true + + + multi-group + + + + -0.63 -0.63 -1.0 0.63 0.63 1.0 + + + 30.0 + 150.0 + + + + + + linear + + + 2 2 + -0.63 -0.63 + 0.63 0.63 + + + diff --git a/tests/regression_tests/random_ray_auto_convert/stochastic_slab/results_true.dat b/tests/regression_tests/random_ray_auto_convert/stochastic_slab/results_true.dat new file mode 100644 index 00000000000..2f444fad3c8 --- /dev/null +++ b/tests/regression_tests/random_ray_auto_convert/stochastic_slab/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +7.499679E-01 8.107614E-03 diff --git a/tests/regression_tests/random_ray_auto_convert/test.py b/tests/regression_tests/random_ray_auto_convert/test.py new file mode 100644 index 00000000000..fa7f2f17f49 --- /dev/null +++ b/tests/regression_tests/random_ray_auto_convert/test.py @@ -0,0 +1,54 @@ +import os + +import openmc +from openmc.examples import pwr_pin_cell +from openmc import RegularMesh +from openmc.utility_funcs import change_directory +import pytest + +from tests.testing_harness import TolerantPyAPITestHarness + + +class MGXSTestHarness(TolerantPyAPITestHarness): + def _cleanup(self): + super()._cleanup() + f = 'mgxs.h5' + if os.path.exists(f): + os.remove(f) + + +@pytest.mark.parametrize("method", ["material_wise", "stochastic_slab", "infinite_medium"]) +def test_random_ray_auto_convert(method): + with change_directory(method): + openmc.reset_auto_ids() + + # Start with a normal continuous energy model + model = pwr_pin_cell() + + # Convert to a multi-group model + model.convert_to_multigroup( + method=method, groups='CASMO-2', nparticles=30, + overwrite_mgxs_library=False, mgxs_path="mgxs.h5" + ) + + # Convert to a random ray model + model.convert_to_random_ray() + + # Set the number of particles + model.settings.particles = 100 + + # Overlay a basic 2x2 mesh + n = 2 + mesh = RegularMesh() + mesh.dimension = (n, n) + bbox = model.geometry.bounding_box + mesh.lower_left = (bbox.lower_left[0], bbox.lower_left[1]) + mesh.upper_right = (bbox.upper_right[0], bbox.upper_right[1]) + model.settings.random_ray['source_region_meshes'] = [ + (mesh, [model.geometry.root_universe])] + + # Set the source shape to linear + model.settings.random_ray['source_shape'] = 'linear' + + harness = MGXSTestHarness('statepoint.10.h5', model) + harness.main() diff --git a/tests/regression_tests/random_ray_diagonal_stabilization/__init__.py b/tests/regression_tests/random_ray_diagonal_stabilization/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/random_ray_diagonal_stabilization/inputs_true.dat b/tests/regression_tests/random_ray_diagonal_stabilization/inputs_true.dat new file mode 100644 index 00000000000..4d4ed76dcaf --- /dev/null +++ b/tests/regression_tests/random_ray_diagonal_stabilization/inputs_true.dat @@ -0,0 +1,65 @@ + + + + mgxs.h5 + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 20 + 15 + + + -0.63 -0.63 -1 0.63 0.63 1 + + + true + + + multi-group + + + + -0.63 -0.63 -1.0 0.63 0.63 1.0 + + + 30.0 + 150.0 + + + + + + linear + 0.5 + + + 2 2 + -0.63 -0.63 + 0.63 0.63 + + + diff --git a/tests/regression_tests/random_ray_diagonal_stabilization/results_true.dat b/tests/regression_tests/random_ray_diagonal_stabilization/results_true.dat new file mode 100644 index 00000000000..c6ce6aab94b --- /dev/null +++ b/tests/regression_tests/random_ray_diagonal_stabilization/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +7.152917E-01 1.430362E-02 diff --git a/tests/regression_tests/random_ray_diagonal_stabilization/test.py b/tests/regression_tests/random_ray_diagonal_stabilization/test.py new file mode 100644 index 00000000000..c7a1c9f7cd9 --- /dev/null +++ b/tests/regression_tests/random_ray_diagonal_stabilization/test.py @@ -0,0 +1,61 @@ +import os + +from openmc.examples import pwr_pin_cell +from openmc import RegularMesh + +from tests.testing_harness import TolerantPyAPITestHarness + + +class MGXSTestHarness(TolerantPyAPITestHarness): + def _cleanup(self): + super()._cleanup() + f = 'mgxs.h5' + if os.path.exists(f): + os.remove(f) + + +def test_random_ray_diagonal_stabilization(): + # Start with a normal continuous energy model + model = pwr_pin_cell() + + # Convert to a multi-group model, with 70 group XS + # and transport correction enabled. This will generate + # MGXS data with some negatives on the diagonal, in order + # to trigger diagonal correction. + model.convert_to_multigroup( + method='material_wise', groups='CASMO-70', nparticles=30, + overwrite_mgxs_library=True, mgxs_path="mgxs.h5", correction='P0' + ) + + # Convert to a random ray model + model.convert_to_random_ray() + + # Set the number of particles + model.settings.particles = 100 + + # Overlay a basic 2x2 mesh + n = 2 + mesh = RegularMesh() + mesh.dimension = (n, n) + bbox = model.geometry.bounding_box + mesh.lower_left = (bbox.lower_left[0], bbox.lower_left[1]) + mesh.upper_right = (bbox.upper_right[0], bbox.upper_right[1]) + model.settings.random_ray['source_region_meshes'] = [ + (mesh, [model.geometry.root_universe])] + + # Set the source shape to linear + model.settings.random_ray['source_shape'] = 'linear' + + # Explicitly set the diagonal stabilization rho (default is otherwise 1.0). + # Note that if we set this to 0.0 (thus distabling stabilization), the + # problem should fail due to instability, so this is actually a good test + # problem. + model.settings.random_ray['diagonal_stabilization_rho'] = 0.5 + + # If rho was 0.0, the instability would cause failure after iteration 14, + # so we go a little past that. + model.settings.inactive = 15 + model.settings.batches = 20 + + harness = MGXSTestHarness('statepoint.20.h5', model) + harness.main() diff --git a/tools/ci/gha-install-dagmc.sh b/tools/ci/gha-install-dagmc.sh index 82759c9bcce..8d0648a5041 100755 --- a/tools/ci/gha-install-dagmc.sh +++ b/tools/ci/gha-install-dagmc.sh @@ -3,7 +3,7 @@ set -ex # MOAB Variables -MOAB_BRANCH='Version5.1.0' +MOAB_BRANCH='5.5.1' MOAB_REPO='https://bitbucket.org/fathomteam/moab/' MOAB_INSTALL_DIR=$HOME/MOAB/ @@ -19,7 +19,7 @@ cd $HOME mkdir MOAB && cd MOAB git clone -b $MOAB_BRANCH $MOAB_REPO mkdir build && cd build -cmake ../moab -DENABLE_HDF5=ON -DENABLE_NETCDF=ON -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=$MOAB_INSTALL_DIR -DENABLE_BLASLAPACK=OFF +cmake ../moab -DENABLE_HDF5=ON -DENABLE_NETCDF=ON -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=$MOAB_INSTALL_DIR make -j && make -j install rm -rf $HOME/MOAB/moab $HOME/MOAB/build From 07f5334616c2ca92ad0a04dfa8c900e080509f70 Mon Sep 17 00:00:00 2001 From: John Tramm Date: Wed, 2 Apr 2025 00:40:07 -0500 Subject: [PATCH 003/159] Random Ray Point Source Locator (#3360) Co-authored-by: Paul Romano --- docs/source/methods/random_ray.rst | 3 +- docs/source/usersguide/random_ray.rst | 9 +- .../openmc/random_ray/flat_source_domain.h | 8 +- src/random_ray/flat_source_domain.cpp | 119 ++++++-- src/random_ray/random_ray_simulation.cpp | 23 +- .../__init__.py | 0 .../inputs_true.dat | 253 ++++++++++++++++++ .../results_true.dat | 9 + .../random_ray_point_source_locator/test.py | 44 +++ 9 files changed, 435 insertions(+), 33 deletions(-) create mode 100644 tests/regression_tests/random_ray_point_source_locator/__init__.py create mode 100644 tests/regression_tests/random_ray_point_source_locator/inputs_true.dat create mode 100644 tests/regression_tests/random_ray_point_source_locator/results_true.dat create mode 100644 tests/regression_tests/random_ray_point_source_locator/test.py diff --git a/docs/source/methods/random_ray.rst b/docs/source/methods/random_ray.rst index eb22b0544da..41f95f0a9ea 100644 --- a/docs/source/methods/random_ray.rst +++ b/docs/source/methods/random_ray.rst @@ -1052,7 +1052,8 @@ random ray and Monte Carlo, however. regions. Thus, in the OpenMC implementation of random ray, particle sources are restricted to being volumetric and isotropic, although different energy spectrums are supported. Fixed sources can be applied to specific materials, - cells, or universes. + cells, or universes. Point sources are "smeared" to fill the volume of the + source region that contains the point source coordinate. - **Inactive batches:** In Monte Carlo, use of a fixed source implies that all batches are active batches, as there is no longer a need to develop a fission diff --git a/docs/source/usersguide/random_ray.rst b/docs/source/usersguide/random_ray.rst index ce982119660..138ae910c9a 100644 --- a/docs/source/usersguide/random_ray.rst +++ b/docs/source/usersguide/random_ray.rst @@ -919,9 +919,12 @@ Monte Carlo solver. Currently, all of the following conditions must be met for the particle source to be valid in random ray mode: -- One or more domain ids must be specified that indicate which cells, universes, - or materials the source applies to. This implicitly limits the source type to - being volumetric. This is specified via the ``domains`` constraint placed on the +- Either a point source must be used, or a domain constraint must be specified + that indicates which cells, universes, or materials the source applies to. In + either case, this implicitly limits the source type to being volumetric, as + even in the point source case the source will be "smeared" throughout the + source region that contains the point source coordinate. A source domain is + specified via the ``domains`` constraint placed on the :class:`openmc.IndependentSource` Python class. - The source must be isotropic (default for a source) - The source must use a discrete (i.e., multigroup) energy distribution. The diff --git a/include/openmc/random_ray/flat_source_domain.h b/include/openmc/random_ray/flat_source_domain.h index 656b0354a7b..9fdd5587508 100644 --- a/include/openmc/random_ray/flat_source_domain.h +++ b/include/openmc/random_ray/flat_source_domain.h @@ -131,6 +131,12 @@ class FlatSourceDomain { std::unordered_map source_region_map_; + // Map that relates a SourceRegionKey to the external source index. This map + // is used to check if there are any point sources within a subdivided source + // region at the time it is discovered. + std::unordered_map + point_source_map_; + // If transport corrected MGXS data is being used, there may be negative // in-group scattering cross sections that can result in instability in MOC // and random ray if used naively. This flag enables a stabilization @@ -141,7 +147,7 @@ class FlatSourceDomain { //---------------------------------------------------------------------------- // Methods void apply_external_source_to_source_region( - Discrete* discrete, double strength_factor, int64_t sr); + Discrete* discrete, double strength_factor, SourceRegionHandle& srh); void apply_external_source_to_cell_instances(int32_t i_cell, Discrete* discrete, double strength_factor, int target_material_id, const vector& instances); diff --git a/src/random_ray/flat_source_domain.cpp b/src/random_ray/flat_source_domain.cpp index ff8e5d23273..8ab30abf207 100644 --- a/src/random_ray/flat_source_domain.cpp +++ b/src/random_ray/flat_source_domain.cpp @@ -946,17 +946,16 @@ void FlatSourceDomain::output_to_vtk() const } void FlatSourceDomain::apply_external_source_to_source_region( - Discrete* discrete, double strength_factor, int64_t sr) + Discrete* discrete, double strength_factor, SourceRegionHandle& srh) { - source_regions_.external_source_present(sr) = 1; + srh.external_source_present() = 1; const auto& discrete_energies = discrete->x(); const auto& discrete_probs = discrete->prob(); for (int i = 0; i < discrete_energies.size(); i++) { int g = data::mg.get_group_index(discrete_energies[i]); - source_regions_.external_source(sr, g) += - discrete_probs[i] * strength_factor; + srh.external_source(g) += discrete_probs[i] * strength_factor; } } @@ -980,8 +979,9 @@ void FlatSourceDomain::apply_external_source_to_cell_instances(int32_t i_cell, if (target_material_id == C_NONE || cell_material_id == target_material_id) { int64_t source_region = source_region_offsets_[i_cell] + j; - apply_external_source_to_source_region( - discrete, strength_factor, source_region); + SourceRegionHandle srh = + source_regions_.get_source_region_handle(source_region); + apply_external_source_to_source_region(discrete, strength_factor, srh); } } } @@ -1023,34 +1023,88 @@ void FlatSourceDomain::convert_external_sources() { // Loop over external sources for (int es = 0; es < model::external_sources.size(); es++) { + + // Extract source information Source* s = model::external_sources[es].get(); IndependentSource* is = dynamic_cast(s); Discrete* energy = dynamic_cast(is->energy()); const std::unordered_set& domain_ids = is->domain_ids(); - double strength_factor = is->strength(); - if (is->domain_type() == Source::DomainType::MATERIAL) { - for (int32_t material_id : domain_ids) { - for (int i_cell = 0; i_cell < model::cells.size(); i_cell++) { - apply_external_source_to_cell_and_children( - i_cell, energy, strength_factor, material_id); - } + // If there is no domain constraint specified, then this must be a point + // source. In this case, we need to find the source region that contains the + // point source and apply or relate it to the external source. + if (is->domain_ids().size() == 0) { + + // Extract the point source coordinate and find the base source region at + // that point + auto sp = dynamic_cast(is->space()); + GeometryState gs; + gs.r() = sp->r(); + gs.r_last() = sp->r(); + gs.u() = {1.0, 0.0, 0.0}; + bool found = exhaustive_find_cell(gs); + if (!found) { + fatal_error(fmt::format("Could not find cell containing external " + "point source at {}", + sp->r())); } - } else if (is->domain_type() == Source::DomainType::CELL) { - for (int32_t cell_id : domain_ids) { - int32_t i_cell = model::cell_map[cell_id]; - apply_external_source_to_cell_and_children( - i_cell, energy, strength_factor, C_NONE); + int i_cell = gs.lowest_coord().cell; + int64_t sr = source_region_offsets_[i_cell] + gs.cell_instance(); + + if (RandomRay::mesh_subdivision_enabled_) { + // If mesh subdivision is enabled, we need to determine which subdivided + // mesh bin the point source coordinate is in as well + int mesh_idx = source_regions_.mesh(sr); + int mesh_bin; + if (mesh_idx == C_NONE) { + mesh_bin = 0; + } else { + mesh_bin = model::meshes[mesh_idx]->get_bin(gs.r()); + } + // With the source region and mesh bin known, we can use the + // accompanying SourceRegionKey as a key into a map that stores the + // corresponding external source index for the point source. Notably, we + // do not actually apply the external source to any source regions here, + // as if mesh subdivision is enabled, they haven't actually been + // discovered & initilized yet. When discovered, they will read from the + // point_source_map to determine if there are any point source terms + // that should be applied. + SourceRegionKey key {sr, mesh_bin}; + point_source_map_[key] = es; + } else { + // If we are not using mesh subdivision, we can apply the external + // source directly to the source region as we do for volumetric domain + // constraint sources. + SourceRegionHandle srh = source_regions_.get_source_region_handle(sr); + apply_external_source_to_source_region(energy, strength_factor, srh); } - } else if (is->domain_type() == Source::DomainType::UNIVERSE) { - for (int32_t universe_id : domain_ids) { - int32_t i_universe = model::universe_map[universe_id]; - Universe& universe = *model::universes[i_universe]; - for (int32_t i_cell : universe.cells_) { + + } else { + // If not a point source, then use the volumetric domain constraints to + // determine which source regions to apply the external source to. + if (is->domain_type() == Source::DomainType::MATERIAL) { + for (int32_t material_id : domain_ids) { + for (int i_cell = 0; i_cell < model::cells.size(); i_cell++) { + apply_external_source_to_cell_and_children( + i_cell, energy, strength_factor, material_id); + } + } + } else if (is->domain_type() == Source::DomainType::CELL) { + for (int32_t cell_id : domain_ids) { + int32_t i_cell = model::cell_map[cell_id]; apply_external_source_to_cell_and_children( i_cell, energy, strength_factor, C_NONE); } + } else if (is->domain_type() == Source::DomainType::UNIVERSE) { + for (int32_t universe_id : domain_ids) { + int32_t i_universe = model::universe_map[universe_id]; + Universe& universe = *model::universes[i_universe]; + for (int32_t i_cell : universe.cells_) { + apply_external_source_to_cell_and_children( + i_cell, energy, strength_factor, C_NONE); + } + } } } } // End loop over external sources @@ -1399,6 +1453,25 @@ SourceRegionHandle FlatSourceDomain::get_subdivided_source_region_handle( sr_key, {base_source_regions_.get_source_region_handle(sr), sr}); discovered_source_regions_.unlock(sr_key); SourceRegionHandle handle {*sr_ptr}; + + // Check if the new source region contains a point source and apply it if so + auto it2 = point_source_map_.find(sr_key); + if (it2 != point_source_map_.end()) { + int es = it2->second; + auto s = model::external_sources[es].get(); + auto is = dynamic_cast(s); + auto energy = dynamic_cast(is->energy()); + double strength_factor = is->strength(); + apply_external_source_to_source_region(energy, strength_factor, handle); + int material = handle.material(); + if (material != MATERIAL_VOID) { + for (int g = 0; g < negroups_; g++) { + double sigma_t = sigma_t_[material * negroups_ + g]; + handle.external_source(g) /= sigma_t; + } + } + } + return handle; } diff --git a/src/random_ray/random_ray_simulation.cpp b/src/random_ray/random_ray_simulation.cpp index 60b00a5be78..b831efcd99a 100644 --- a/src/random_ray/random_ray_simulation.cpp +++ b/src/random_ray/random_ray_simulation.cpp @@ -281,10 +281,21 @@ void validate_random_ray_inputs() "allowed in random ray mode."); } - // Validate that a domain ID was specified - if (is->domain_ids().size() == 0) { - fatal_error("Fixed sources must be specified by domain " - "id (cell, material, or universe) in random ray mode."); + // Validate that a domain ID was specified OR that it is a point source + auto sp = dynamic_cast(is->space()); + if (is->domain_ids().size() == 0 && !sp) { + fatal_error("Fixed sources must be point source or spatially " + "constrained by domain id (cell, material, or universe) in " + "random ray mode."); + } else if (is->domain_ids().size() > 0 && sp) { + // If both a domain constraint and a non-default point source location + // are specified, notify user that domain constraint takes precedence. + if (sp->r().x == 0.0 && sp->r().y == 0.0 && sp->r().z == 0.0) { + warning("Fixed source has both a domain constraint and a point " + "type spatial distribution. The domain constraint takes " + "precedence in random ray mode -- point source coordinate " + "will be ignored."); + } } // Check that a discrete energy distribution was used @@ -393,12 +404,12 @@ RandomRaySimulation::RandomRaySimulation() void RandomRaySimulation::apply_fixed_sources_and_mesh_domains() { + domain_->apply_meshes(); if (settings::run_mode == RunMode::FIXED_SOURCE) { // Transfer external source user inputs onto random ray source regions domain_->convert_external_sources(); domain_->count_external_source_regions(); } - domain_->apply_meshes(); } void RandomRaySimulation::prepare_fixed_sources_adjoint( @@ -517,6 +528,8 @@ void RandomRaySimulation::simulate() finalize_generation(); finalize_batch(); } // End random ray power iteration loop + + domain_->count_external_source_regions(); } void RandomRaySimulation::output_simulation_results() const diff --git a/tests/regression_tests/random_ray_point_source_locator/__init__.py b/tests/regression_tests/random_ray_point_source_locator/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/random_ray_point_source_locator/inputs_true.dat b/tests/regression_tests/random_ray_point_source_locator/inputs_true.dat new file mode 100644 index 00000000000..9c7cd4ab5b8 --- /dev/null +++ b/tests/regression_tests/random_ray_point_source_locator/inputs_true.dat @@ -0,0 +1,253 @@ + + + + mgxs.h5 + + + + + + + + + + + + + + + + + + + + + 2.5 2.5 2.5 + 12 12 12 + 0.0 0.0 0.0 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + + + + + + + + + + fixed source + 90 + 30 + 15 + + + 2.5 2.5 2.5 + + + 100.0 1.0 + + + multi-group + + 500.0 + 100.0 + + + 0.0 0.0 0.0 30.0 30.0 30.0 + + + True + + + + + + + + 30 30 30 + 0.0 0.0 0.0 + 30.0 30.0 30.0 + + + + + 1 + + + 2 + + + 3 + + + 3 + flux + tracklength + + + 2 + flux + tracklength + + + 1 + flux + tracklength + + + diff --git a/tests/regression_tests/random_ray_point_source_locator/results_true.dat b/tests/regression_tests/random_ray_point_source_locator/results_true.dat new file mode 100644 index 00000000000..1785dda574a --- /dev/null +++ b/tests/regression_tests/random_ray_point_source_locator/results_true.dat @@ -0,0 +1,9 @@ +tally 1: +2.633900E+00 +2.948207E+00 +tally 2: +1.440463E-01 +3.294032E-03 +tally 3: +9.425207E-03 +1.089748E-05 diff --git a/tests/regression_tests/random_ray_point_source_locator/test.py b/tests/regression_tests/random_ray_point_source_locator/test.py new file mode 100644 index 00000000000..fd3d8a18fe9 --- /dev/null +++ b/tests/regression_tests/random_ray_point_source_locator/test.py @@ -0,0 +1,44 @@ +import os + +import openmc +from openmc.examples import random_ray_three_region_cube + +from tests.testing_harness import TolerantPyAPITestHarness + + +class MGXSTestHarness(TolerantPyAPITestHarness): + def _cleanup(self): + super()._cleanup() + f = 'mgxs.h5' + if os.path.exists(f): + os.remove(f) + + +def test_random_ray_point_source_locator(): + model = random_ray_three_region_cube() + + # Overlay subdivided SR mesh to reduce resolution from 2.5cm -> 1cm + width = 30.0 + mesh = openmc.RegularMesh() + mesh.dimension = (30, 30, 30) + mesh.lower_left = (0.0, 0.0, 0.0) + mesh.upper_right = (width, width, width) + model.settings.random_ray['source_region_meshes'] = [ + (mesh, [model.geometry.root_universe]), + ] + + # Define a point source + strengths = [1.0] + midpoints = [100.0] + energy_distribution = openmc.stats.Discrete(x=midpoints, p=strengths) + spatial_distribution = openmc.stats.Point([2.5, 2.5, 2.5]) + source = openmc.IndependentSource( + energy=energy_distribution, space=spatial_distribution, strength=3.14) + model.settings.source = [source] + + # Settings + model.settings.inactive = 15 + model.settings.batches = 30 + + harness = MGXSTestHarness('statepoint.30.h5', model) + harness.main() From cdc254ccf4245282d5aff19bd769daf180ef1a37 Mon Sep 17 00:00:00 2001 From: John Tramm Date: Thu, 10 Apr 2025 12:31:33 -0500 Subject: [PATCH 004/159] Fix for Issue Loading MGXS Data Files with LLVM 20 or Newer (#3368) Co-authored-by: Paul Romano --- src/mgxs.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/mgxs.cpp b/src/mgxs.cpp index eae3e5817ce..a88d2c196b6 100644 --- a/src/mgxs.cpp +++ b/src/mgxs.cpp @@ -99,7 +99,21 @@ void Mgxs::metadata_from_hdf5(hid_t xs_id, const vector& temperature, case TemperatureMethod::NEAREST: // Determine actual temperatures to read for (const auto& T : temperature) { - auto i_closest = xt::argmin(xt::abs(temps_available - T))[0]; + // Determine the closest temperature value + // NOTE: the below block could be replaced with the following line, + // though this gives a runtime error if using LLVM 20 or newer, + // likely due to a bug in xtensor. + // auto i_closest = xt::argmin(xt::abs(temps_available - T))[0]; + double closest = std::numeric_limits::max(); + int i_closest = 0; + for (int i = 0; i < temps_available.size(); i++) { + double diff = std::abs(temps_available[i] - T); + if (diff < closest) { + closest = diff; + i_closest = i; + } + } + double temp_actual = temps_available[i_closest]; if (std::fabs(temp_actual - T) < settings::temperature_tolerance) { if (std::find(temps_to_read.begin(), temps_to_read.end(), From bd95b52f4d711fd3e62ec80fed9a4f1d1778198f Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Fri, 11 Apr 2025 23:35:30 +0200 Subject: [PATCH 005/159] Add check for equal value bins in an EnergyFilter (#3372) --- openmc/filter.py | 4 ++-- tests/unit_tests/test_filters.py | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/openmc/filter.py b/openmc/filter.py index 35a7e7c7e96..755777c5e7d 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -1238,7 +1238,7 @@ def check_bins(self, bins): cv.check_type('filter value', v1, Real) # Make sure that each tuple has values that are increasing - if v1 < v0: + if v1 <= v0: raise ValueError(f'Values {v0} and {v1} appear to be out of ' 'order') @@ -1384,7 +1384,7 @@ class EnergyFilter(RealFilter): ---------- values : Iterable of Real A list of values for which each successive pair constitutes a range of - energies in [eV] for a single bin + energies in [eV] for a single bin. Entries must be positive and ascending. filter_id : int Unique identifier for the filter diff --git a/tests/unit_tests/test_filters.py b/tests/unit_tests/test_filters.py index 55bb62075bd..c3b73719232 100644 --- a/tests/unit_tests/test_filters.py +++ b/tests/unit_tests/test_filters.py @@ -294,3 +294,21 @@ def test_tabular_from_energyfilter(): tab = efilter.get_tabular(values=np.array([10, 10, 5]), interpolation='linear-linear') assert tab.interpolation == 'linear-linear' + + +def test_energy_filter(): + + # testing that bins descending value raises error + msg = "Values 1.0 and 0.5 appear to be out of order" + with raises(ValueError, match=msg): + openmc.EnergyFilter([0.0, 1.0, 0.5]) + + # testing that bins with same value raises error + msg = "Values 0.25 and 0.25 appear to be out of order" + with raises(ValueError, match=msg): + openmc.EnergyFilter([0.0, 0.25, 0.25]) + + # testing that negative bins values raises error + msg = 'Unable to set "filter value" to "-1.2" since it is less than "0.0"' + with raises(ValueError, match=msg): + openmc.EnergyFilter([-1.2, 0.25, 0.5]) From c1a4d43da81ea0e04f063ae450d44133e3d72b9c Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 11 Apr 2025 18:43:27 -0500 Subject: [PATCH 006/159] Add methods on Material class for waste disposal rating / classification (#3366) Co-authored-by: Ethan Peterson --- openmc/material.py | 100 ++++++- openmc/waste.py | 279 ++++++++++++++++++ tests/unit_tests/test_material.py | 6 + tests/unit_tests/test_waste_classification.py | 98 ++++++ 4 files changed, 475 insertions(+), 8 deletions(-) create mode 100644 openmc/waste.py create mode 100644 tests/unit_tests/test_waste_classification.py diff --git a/openmc/material.py b/openmc/material.py index f40ac805f70..9677fbba32a 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -18,6 +18,7 @@ from ._xml import clean_indentation, reorder_attributes from .mixin import IDManagerMixin from .utility_funcs import input_path +from . import waste from openmc.checkvalue import PathLike from openmc.stats import Univariate, Discrete, Mixture from openmc.data.data import _get_element_symbol @@ -30,6 +31,7 @@ # Smallest normalized floating point number _SMALLEST_NORMAL = sys.float_info.min +_BECQUEREL_PER_CURIE = 3.7e10 NuclideTuple = namedtuple('NuclideTuple', ['name', 'percent', 'percent_type']) @@ -1058,7 +1060,6 @@ def get_nuclide_atom_densities(self, nuclide: str | None = None) -> dict[str, fl nuc_densities.append(nuc.percent) nuc_density_types.append(nuc.percent_type) - nucs = np.array(nucs) nuc_densities = np.array(nuc_densities) nuc_density_types = np.array(nuc_density_types) @@ -1137,17 +1138,16 @@ def get_element_atom_densities(self, element: str | None = None) -> dict[str, fl def get_activity(self, units: str = 'Bq/cm3', by_nuclide: bool = False, volume: float | None = None) -> dict[str, float] | float: - """Returns the activity of the material or for each nuclide in the - material in units of [Bq], [Bq/g], [Bq/kg] or [Bq/cm3]. + """Returns the activity of the material or of each nuclide within. .. versionadded:: 0.13.1 Parameters ---------- - units : {'Bq', 'Bq/g', 'Bq/kg', 'Bq/cm3'} + units : {'Bq', 'Bq/g', 'Bq/kg', 'Bq/cm3', 'Ci', 'Ci/m3'} Specifies the type of activity to return, options include total - activity [Bq], specific [Bq/g, Bq/kg] or volumetric activity - [Bq/cm3]. Default is volumetric activity [Bq/cm3]. + activity [Bq,Ci], specific [Bq/g, Bq/kg] or volumetric activity + [Bq/cm3,Ci/m3]. Default is volumetric activity [Bq/cm3]. by_nuclide : bool Specifies if the activity should be returned for the material as a whole or per nuclide. Default is False. @@ -1165,17 +1165,24 @@ def get_activity(self, units: str = 'Bq/cm3', by_nuclide: bool = False, of the material is returned as a float. """ - cv.check_value('units', units, {'Bq', 'Bq/g', 'Bq/kg', 'Bq/cm3'}) + cv.check_value('units', units, {'Bq', 'Bq/g', 'Bq/kg', 'Bq/cm3', 'Ci', 'Ci/m3'}) cv.check_type('by_nuclide', by_nuclide, bool) + if volume is None: + volume = self.volume + if units == 'Bq': - multiplier = volume if volume is not None else self.volume + multiplier = volume elif units == 'Bq/cm3': multiplier = 1 elif units == 'Bq/g': multiplier = 1.0 / self.get_mass_density() elif units == 'Bq/kg': multiplier = 1000.0 / self.get_mass_density() + elif units == 'Ci': + multiplier = volume / _BECQUEREL_PER_CURIE + elif units == 'Ci/m3': + multiplier = 1e6 / _BECQUEREL_PER_CURIE activity = {} for nuclide, atoms_per_bcm in self.get_nuclide_atom_densities().items(): @@ -1316,6 +1323,83 @@ def get_mass(self, nuclide: str | None = None, volume: float | None = None) -> f raise ValueError("Volume must be set in order to determine mass.") return volume*self.get_mass_density(nuclide) + def waste_classification(self, metal: bool = False) -> str: + """Classify the material for near-surface waste disposal. + + This method determines a waste classification for the material based on + the NRC regulations (10 CFR 61.55). Note that the NRC regulations do not + consider many long-lived radionuclides relevant to fusion systems; for + fusion applications, it is recommended to calculate a waste disposal + rating based on limits by Fetter et al. using the + :meth:`~openmc.Material.waste_disposal_rating` method. + + Parameters + ---------- + metal : bool, optional + Whether or not the material is in metal form. + + Returns + ------- + str + The waste disposal classification, which can be "Class A", "Class + B", "Class C", or "GTCC" (greater than class C). + + """ + return waste._waste_classification(self, metal=metal) + + def waste_disposal_rating( + self, + limits: str | dict[str, float] = 'Fetter', + metal: bool = False, + ) -> float: + """Return the waste disposal rating for the material. + + This method returns a waste disposal rating for the material based on a + set of specific activity limits. The waste disposal rating is a single + number that represents the sum of the ratios of the specific activity + for each radionuclide in the material against a nuclide-specific limit. + A value less than 1.0 indicates that the material "meets" the limits + whereas a value greater than 1.0 exceeds the limits. + + Note that the limits for NRC do not consider many long-lived + radionuclides relevant to fusion systems. A paper by `Fetter et al. + `_ applies the NRC + methodology to calculate specific activity limits for an expanded set of + radionuclides. + + Parameters + ---------- + limits : str or dict, optional + The name of a predefined set of specific activity limits or a + dictionary that contains specific activity limits for radionuclides, + where keys are nuclide names and values are activities in units of + [Ci/m3]. The predefined options are: + + - 'Fetter': Uses limits from Fetter et al. (1990) + - 'NRC_long': Uses the 10 CFR 61.55 limits for long-lived + radionuclides + - 'NRC_short_A': Uses the 10 CFR 61.55 class A limits for + short-lived radionuclides + - 'NRC_short_B': Uses the 10 CFR 61.55 class B limits for + short-lived radionuclides + - 'NRC_short_C': Uses the 10 CFR 61.55 class C limits for + short-lived radionuclides + metal : bool, optional + Whether or not the material is in metal form (only applicable for + NRC based limits) + + Returns + ------- + float + The waste disposal rating for the material. + + See also + -------- + Material.waste_classification() + + """ + return waste._waste_disposal_rating(self, limits, metal) + def clone(self, memo: dict | None = None) -> Material: """Create a copy of this material with a new unique ID. diff --git a/openmc/waste.py b/openmc/waste.py new file mode 100644 index 00000000000..7dd45b8e3be --- /dev/null +++ b/openmc/waste.py @@ -0,0 +1,279 @@ +from __future__ import annotations + +import openmc +from openmc.data import half_life + + +def _waste_classification(mat: openmc.Material, metal: bool = True) -> str: + """Classify a material for near-surface waste disposal. + + This method determines a waste classification for a material based on the + NRC regulations (10 CFR 61.55). + + Parameters + ---------- + mat : openmc.Material + The material to classify. + metal : bool, optional + Whether or not the material is in metal form. This changes the + acceptable limits in Tables 1 and 2 for certain nuclides. + + Returns + ------- + str + The waste disposal classification, which can be "Class A", "Class B", + "Class C", or "GTCC" (greater than class C). + + """ + # Determine metrics based on Tables 1 and 2 using sum of fractions rule for + # mixture of radionuclides from §61.55(a)(7) + ratio1 = _waste_disposal_rating(mat, 'NRC_long', metal=metal) + ratio2 = [ + _waste_disposal_rating(mat, 'NRC_short_A', metal=metal), + _waste_disposal_rating(mat, 'NRC_short_B', metal=metal), + _waste_disposal_rating(mat, 'NRC_short_C', metal=metal), + ] + + # Determine which nuclides are present in Table 1 and Table 2 + table1_nuclides_present = (ratio1 > 0.0) + table2_nuclides_present = any(x > 0.0 for x in ratio2) + + # Helper function for classifying based on Table 2 + def classify_table2(col1, col2, col3): + if col1 < 1.0: + return "Class A" + elif col2 < 1.0: + return "Class B" + elif col3 < 1.0: + return "Class C" + else: + return "GTCC" + + if table1_nuclides_present and table2_nuclides_present: + # Classification based on §61.55(a)(5) + if ratio1 < 0.1: + return classify_table2(*ratio2) + elif ratio1 < 1.0: + return "Class C" if ratio2[2] < 1.0 else "GTCC" + else: + return "GTCC" + + elif table1_nuclides_present: + # Classification based on §61.55(a)(3) + if ratio1 < 0.1: + return "Class A" + elif ratio1 < 1.0: + return "Class C" + else: + return "GTCC" + + elif table2_nuclides_present: + # Classification based on §61.55(a)(4) + return classify_table2(*ratio2) + + else: + # Classification based on §61.55(a)(6) + return "Class A" + + +def _waste_disposal_rating( + mat: openmc.Material, + limits: str | dict[str, float] = 'Fetter', + metal: bool = False, + ) -> float: + """Return the waste disposal rating for a material. + + This method returns a waste disposal rating for the material based on a set + of specific activity limits. The waste disposal rating is a single number + that represents the sum of the ratios of the specific activity for each + radionuclide in the material against a nuclide-specific limit. A value less + than 1.0 indicates that the material "meets" the limits whereas a value + greater than 1.0 exceeds the limits. + + Parameters + ---------- + mat : openmc.Material + The material to classify. + limits : str or dict, optional + The name of a predefined set of specific activity limits or a dictionary + that contains specific activity limits for radionuclides, where keys are + nuclide names and values are activities in units of [Ci/m3]. The + predefined options are: + + - 'Fetter': Uses limits from Fetter et al. (1990) + - 'NRC_long': Uses the 10 CFR 61.55 limits for long-lived radionuclides + - 'NRC_short_A': Uses the 10 CFR 61.55 class A limits for short-lived + radionuclides + - 'NRC_short_B': Uses the 10 CFR 61.55 class B limits for short-lived + radionuclides + - 'NRC_short_C': Uses the 10 CFR 61.55 class C limits for short-lived + radionuclides + metal : bool, optional + Whether or not the material is in metal form (only applicable for NRC + based limits) + + Returns + ------- + float + The waste disposal rating for the material. + + """ + if limits == 'Fetter': + # Specific activity limits for radionuclides with half-lives between 5 + # years and 1e12 years from Table 2 in Fetter + limits = { + "Be10": 5.0e3, + "C14": 6.0e2, + "Al26": 9.0e-2, + "Si32": 6.0e2, + "Cl36": 1.0e1, + "Ar39": 2.0e4, + "Ar42": 2.0e4, + "K40": 2.0e0, + "Ca41": 1.0e4, + "Ti44": 2.0e2, + "Fe60": 1.0e-1, + "Co60": 3.0e8, + "Ni59": 9.0e2, + "Ni63": 7.0e5, + "Se79": 5.0e1, + "Kr81": 3.0e1, + "Sr90": 8.0e5, + "Nb91": 2.0e2, + "Nb92": 2.0e-1, + "Nb94": 2.0e-1, + "Mo93": 4.0e3, + "Tc97": 4.0e-1, + "Tc98": 1.0e-2, + "Tc99": 6.0e-2, + "Pd107": 9.0e2, + "Ag108_m1": 3.0e0, + "Sn121_m1": 7.0e5, + "Sn126": 1.0e-1, + "I129": 2.0e0, + "Cs137": 5.0e4, + "Ba133": 2.0e8, + "La137": 2.0e2, + "Sm151": 5.0e7, + "Eu150_m1": 3.0e3, + "Eu152": 3.0e5, + "Eu154": 5.0e6, + "Gd148": 2.0e5, + "Gd150": 2.0e3, + "Tb157": 5.0e3, + "Tb158": 4.0e0, + "Dy154": 1.0e3, + "Ho166_m1": 2.0e-1, + "Hf178_m1": 9.0e3, + "Hf182": 2.0e-1, + "Re186_m1": 2.0e1, + "Ir192_m1": 1.0e0, + "Pt193": 2.0e8, + "Hg194": 5.0e-1, + "Pb202": 6.0e-1, + "Pb210": 3.0e7, + "Bi207": 9.0e3, + "Bi208": 8.0e-2, + "Bi210_m1": 1.0e0, + "Po209": 3.0e3, + "Ra226": 1.0e-1, + "Ra228": 3.0e7, + "Ac227": 5.0e5, + "Th229": 2.0e0, + "Th230": 3.0e-1, + "Th232": 1.0e-1, + "Pa231": 7.0e-1, + "U232": 3.0e1, + "U233": 2.0e1, + "U234": 9.0e1, + "U235": 2.0e0, + "Np236": 1.0e0, + "Np237": 1.0e0, + "Pu238": 7.0e4, + "Pu239": 1.0e3, + "Pu240": 1.0e3, + "Pu241": 2.0e3, + "Pu242": 1.0e3, + "Pu244": 9.0e-1, + "Am241": 5.0e1, + "Am242_m1": 3.0e2, + "Am243": 2.0e0, + "Cm243": 6.0e2, + "Cm244": 5.0e5, + "Cm245": 5.0e0, + "Cm246": 8.0e2, + "Cm248": 8.0e2, + } + + elif limits == 'NRC_long': + # Specific activity limits for long-lived radionuclides from Table 1 in + # 10 CFR 61.55 in Ci/m3. + limits = { + 'C14': 8.0, + 'Tc99': 3.0, + 'I129': 0.08, + } + if metal: + limits['C14'] = 80.0 + limits['Ni59'] = 220.0 + limits['Nb94'] = 0.2 + + # Convert values in nCi/g to Ci/m3 + factor = (1e6 * mat.get_mass_density()) / 1e9 + limits.update({ + 'Pu241': 3500.0 * factor, + 'Cm242': 20000.0 * factor, + 'Np237': 100.0 * factor, + 'Pu238': 100.0 * factor, + 'Pu239': 100.0 * factor, + 'Pu240': 100.0 * factor, + 'Pu242': 100.0 * factor, + 'Pu244': 100.0 * factor, + 'Am241': 100.0 * factor, + 'Am243': 100.0 * factor, + 'Cm243': 100.0 * factor, + 'Cm244': 100.0 * factor, + 'Cm245': 100.0 * factor, + 'Cm246': 100.0 * factor, + 'Cm247': 100.0 * factor, + 'Cm248': 100.0 * factor, + 'Bk247': 100.0 * factor, + 'Cf249': 100.0 * factor, + 'Cf250': 100.0 * factor, + 'Cf251': 100.0 * factor, + }) + + elif limits == 'NRC_short_A': + # Get Class A specific activity limits for short-lived radionuclides + # from Table 2 in 10 CFR 61.55 + limits = { + 'H3': 40.0, + 'Co60': 700.0, + 'Ni63': 35.0 if metal else 3.5, + 'Sr90': 0.04, + 'Cs137': 1.0 + } + + # Add radionuclides with half-lives < 5 years to limits for class A + five_years = 60.0 * 60.0 * 24.0 * 365.25 * 5.0 + for nuc in mat.get_nuclides(): + if half_life(nuc) is not None and half_life(nuc) < five_years: + limits[nuc] = 700.0 + + elif limits == 'NRC_short_B': + # Get Class B specific activity limits for short-lived radionuclides + # from Table 2 in 10 CFR 61.55 + limits = {'Ni63': 700.0 if metal else 70.0, 'Sr90': 150.0, 'Cs137': 44.0} + + elif limits == 'NRC_short_C': + # Get Class C specific activity limits for short-lived radionuclides + # from Table 2 in 10 CFR 61.55 + limits = {'Ni63': 7000.0 if metal else 700.0, 'Sr90': 7000.0, 'Cs137': 4600.0} + + # Calculate the sum of the fractions of the activity of each radionuclide + # compared to the specified limits + ratio = 0.0 + for nuc, ci_m3 in mat.get_activity(units="Ci/m3", by_nuclide=True).items(): + if nuc in limits: + ratio += ci_m3 / limits[nuc] + return ratio diff --git a/tests/unit_tests/test_material.py b/tests/unit_tests/test_material.py index cb71cd09bef..8ad57956381 100644 --- a/tests/unit_tests/test_material.py +++ b/tests/unit_tests/test_material.py @@ -590,6 +590,12 @@ def test_get_activity(): # Test with volume specified as argument assert pytest.approx(m4.get_activity(units='Bq', volume=1.0)) == 355978108155965.94*3/2 + # Test units based on Ci + bq = m4.get_activity(units='Bq') + m3 = m4.volume * 1e-6 + assert (ci := m4.get_activity(units='Ci')) == pytest.approx(bq/3.7e10) + assert m4.get_activity(units='Ci/m3') == pytest.approx(ci/m3) + def test_get_decay_heat(): # Set chain file for testing diff --git a/tests/unit_tests/test_waste_classification.py b/tests/unit_tests/test_waste_classification.py new file mode 100644 index 00000000000..dd692cc4b35 --- /dev/null +++ b/tests/unit_tests/test_waste_classification.py @@ -0,0 +1,98 @@ +import random + +import openmc +import pytest + + +@pytest.mark.parametrize("metal", [False, True]) +def test_waste_classification_long(metal): + """Test classification when determined by long-lived radionuclides""" + f = 10.0 if metal else 1.0 + limit = 8.0*f + mat = openmc.Material() + mat.add_nuclide('C14', 1e-9*f) + assert mat.get_activity('Ci/m3') < 0.1 * limit + assert mat.waste_classification(metal=metal) == 'Class A' + + mat = openmc.Material() + mat.add_nuclide('C14', 1e-8*f) + assert 0.1 * limit < mat.get_activity('Ci/m3') < limit + assert mat.waste_classification(metal=metal) == 'Class C' + + mat = openmc.Material() + mat.add_nuclide('C14', 1e-7*f) + assert mat.get_activity('Ci/m3') > limit + assert mat.waste_classification(metal=metal) == 'GTCC' + + +@pytest.mark.parametrize("metal", [False, True]) +def test_waste_classification_short(metal): + """Test classification when determined by short-lived radionuclides""" + f = 10.0 if metal else 1.0 + col1, col2, col3 = 3.5*f, 70.0*f, 700.0*f + + mat = openmc.Material() + mat.add_nuclide('Ni63', 1e-10*f) + assert mat.get_activity('Ci/m3') < col1 + assert mat.waste_classification(metal=metal) == 'Class A' + + mat = openmc.Material() + mat.add_nuclide('Ni63', 1e-10*10*f) + assert col1 < mat.get_activity('Ci/m3') < col2 + assert mat.waste_classification(metal=metal) == 'Class B' + + mat = openmc.Material() + mat.add_nuclide('Ni63', 1e-10*200*f) + assert col2 < mat.get_activity('Ci/m3') < col3 + assert mat.waste_classification(metal=metal) == 'Class C' + + mat = openmc.Material() + mat.add_nuclide('Ni63', 1e-10*2000*f) + assert mat.get_activity('Ci/m3') > col3 + assert mat.waste_classification(metal=metal) == 'GTCC' + + +def test_waste_classification_mix(): + """Test classification when determined by a mix of radionuclides""" + # Check example from 10 CFR 61.55 with mix of Sr90 and Cs137 + mat = openmc.Material() + mat.add_nuclide('Sr90', 2.425e-9) + mat.add_nuclide('Cs137', 1.115e-9) + + # In example, activity of Sr90 is 50.0 Ci/m3 and Cs137 is 22.0 Ci/m3 + activity = mat.get_activity(units='Ci/m3', by_nuclide=True) + assert activity['Sr90'] == pytest.approx(50.0, 0.01) + assert activity['Cs137'] == pytest.approx(22.0, 0.01) + + # According to example, the waste should be class B + assert mat.waste_classification() == 'Class B' + + +def test_waste_rating_fetter(): + """Test waste classification using the Fetter limits""" + # For Tc99, Fetter has a more strict limit. Here, we create a material with + # Tc99 at 1 Ci/m3 which exceeds Fetter but not NRC + density = 3.5561e-7 + mat = openmc.Material() + mat.add_nuclide('Tc99', density) + assert mat.get_activity('Ci/m3') == pytest.approx(1.0, 1e-3) + assert mat.waste_disposal_rating(limits='NRC_short_C') < 1.0 + assert mat.waste_disposal_rating(limits='Fetter') > 1.0 + + # With a lower density, it should be Class C under Fetter limits and Class A + # under NRC limits + mat = openmc.Material() + mat.add_nuclide('Tc99', 5.0e-2*density) + assert mat.waste_disposal_rating(limits='NRC_short_A') < 1.0 + assert mat.waste_disposal_rating(limits='Fetter') < 1.0 + + +def test_waste_disposal_rating(): + """Test waste_disposal_rating method""" + mat = openmc.Material() + mat.add_nuclide('K40', random.random()) + + # Check for correct classification based on actual activity + ci_m3 = mat.get_activity('Ci/m3') + assert mat.waste_disposal_rating(limits={'K40': 2*ci_m3}) < 1.0 + assert mat.waste_disposal_rating(limits={'K40': 0.5*ci_m3}) > 1.0 From 47ca2916aa85a2909a5f6e0a3442ab7c581894b1 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville <54550047+JoffreyDorville@users.noreply.github.com> Date: Mon, 14 Apr 2025 09:07:48 -0500 Subject: [PATCH 007/159] Kinetics parameters using Iterated Fission Probability (#3133) Co-authored-by: Paul Romano --- CMakeLists.txt | 1 + docs/source/usersguide/index.rst | 1 + docs/source/usersguide/kinetics.rst | 110 ++++++++++ docs/source/usersguide/tallies.rst | 18 ++ include/openmc/bank.h | 8 + include/openmc/constants.h | 8 +- include/openmc/ifp.h | 188 +++++++++++++++++ include/openmc/particle_data.h | 7 + include/openmc/settings.h | 15 +- openmc/lib/tally.py | 4 +- openmc/settings.py | 29 +++ src/bank.cpp | 28 +++ src/eigenvalue.cpp | 82 +++++++- src/finalize.cpp | 3 +- src/ifp.cpp | 216 ++++++++++++++++++++ src/output.cpp | 3 + src/particle.cpp | 6 +- src/physics.cpp | 5 + src/reaction.cpp | 3 + src/settings.cpp | 17 ++ src/simulation.cpp | 6 + src/tallies/tally.cpp | 63 ++++++ src/tallies/tally_scoring.cpp | 51 +++++ tests/regression_tests/ifp/__init__.py | 0 tests/regression_tests/ifp/inputs_true.dat | 33 +++ tests/regression_tests/ifp/results_true.dat | 9 + tests/regression_tests/ifp/test.py | 45 ++++ tests/unit_tests/test_ifp.py | 49 +++++ 28 files changed, 1002 insertions(+), 6 deletions(-) create mode 100644 docs/source/usersguide/kinetics.rst create mode 100644 include/openmc/ifp.h create mode 100644 src/ifp.cpp create mode 100644 tests/regression_tests/ifp/__init__.py create mode 100644 tests/regression_tests/ifp/inputs_true.dat create mode 100644 tests/regression_tests/ifp/results_true.dat create mode 100644 tests/regression_tests/ifp/test.py create mode 100644 tests/unit_tests/test_ifp.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 461abe508ab..8cb03a1a1da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -343,6 +343,7 @@ list(APPEND libopenmc_SOURCES src/geometry.cpp src/geometry_aux.cpp src/hdf5_interface.cpp + src/ifp.cpp src/initialize.cpp src/lattice.cpp src/material.cpp diff --git a/docs/source/usersguide/index.rst b/docs/source/usersguide/index.rst index aef9b1a1c1a..5f8e0197e70 100644 --- a/docs/source/usersguide/index.rst +++ b/docs/source/usersguide/index.rst @@ -22,6 +22,7 @@ essential aspects of using OpenMC to perform simulations. plots depletion decay_sources + kinetics scripts processing parallel diff --git a/docs/source/usersguide/kinetics.rst b/docs/source/usersguide/kinetics.rst new file mode 100644 index 00000000000..bdf26d341b5 --- /dev/null +++ b/docs/source/usersguide/kinetics.rst @@ -0,0 +1,110 @@ +.. _kinetics: + +=================== +Kinetics parameters +=================== + +OpenMC has the capability to estimate the following adjoint-weighted effective +generation time :math:`\Lambda_{\text{eff}}` and the effective delayed neutron +fraction :math:`\beta_{\text{eff}}`. These parameters are calculated using the +iterated fission probability (IFP) method [Hurwitz_1964]_ based on a similar +approach as in `Serpent 2 `_. The +implementation in OpenMC is limited to eigenvalue calculations and is described +in more details in [Dorville_2025]_. + +---------------------------------- +Iterated Fission Probability (IFP) +---------------------------------- + +With IFP, additional information needs to be recorded during the simulation +compared to a typical eigenvalue calculation. OpenMC stores an additional +set of values (neutron lifetime or delayed neutron group number for +:math:`\Lambda_{\text{eff}}` or :math:`\beta_{\text{eff}}`, respectively) +for every fission neutron simulated. Each set of values corresponds to +the values that are associated to the :math:`N_{\text{gen}}` direct ancestors +of any given fission neutron. + +:math:`N_{\text{gen}}` is referred to as the number of generations in the +IFP method and corresponds to the number of generations between the birth of +a fission neutron and the time its score is added to the IFP tally. By default, +OpenMC considers 10 generations but this value can be modified by the user via +the ``ifp_n_generation`` settings in the Python API:: + + settings.ifp_n_generation = 5 + +``ifp_n_generation`` should be greater than 0, but should also be lower than +or equal to the number of inactive batches declared for the calculation. +The respect of these constraints is verified by OpenMC before any calculation. + +OpenMC will automatically detect the type of data that needs to be stored based +on the tally scores selected by the user. This guarantees that only information +of interest are stored during a simulation and avoids using extra memory when +only one parameter is needed. The following table shows the tally scores that +are needed to compute kinetics parameters in OpenMC: + +.. table:: **OpenMC tally scores needed to calculate adjoint-weighted kinetics parameters** + :align: center + + =============================== ============================ ========================== ======== + OpenMC tally score \\ Parameter :math:`\Lambda_{\text{eff}}` :math:`\beta_{\text{eff}}` Both + =============================== ============================ ========================== ======== + ``ifp-time-numerator`` X X + ``ifp-beta-numerator`` X X + ``ifp-denominator`` X X X + =============================== ============================ ========================== ======== + +| + +.. note:: Because the memory footprint of additional data is generally non-negligible + with IFP, it is recommended to choose the value for ``ifp_n_generation`` carefully. + For example, using one generation for both kinetics parameters corresponds to store + one additional integer (for the delayed neutron group number used with + :math:`\beta_{\text{eff}}`) and one floating point value (for the neutron lifetime + used with :math:`\Lambda_{\text{eff}}`) for every fission neutron simulated once the + asymptotic regime is reached. + +----------------------------- +Obtaining kinetics parameters +----------------------------- + +Here is an example showing how to declare the three available IFP scores in a +single tally:: + + tally = openmc.Tally(name="ifp-scores") + tally.scores = [ + "ifp-time-numerator", + "ifp-beta-numerator", + "ifp-denominator" + ] + +The effective generation time :math:`\Lambda_{\text{eff}}` is calculated +by dividing the result of the ``ifp-time-numerator`` score by the one obtained +for ``ifp-denominator`` and by the :math:`k_{\text{eff}}` of the simulation: + +.. math:: + :label: lambda_eff + + \Lambda_{\text{eff}} = \frac{S_{\text{ifp-time-numerator}}}{S_{\text{ifp-denominator}} \times k_{\text{eff}}} + +The effective delayed neutron fraction :math:`\beta_{\text{eff}}` is calculated +by dividing the result of the ``ifp-beta-numerator`` score by the one obtained +for ``ifp-denominator``: + +.. math:: + :label: beta_eff + + \beta_{\text{eff}} = \frac{S_{\text{ifp-beta-numerator}}}{S_{\text{ifp-denominator}}} + +.. only:: html + + .. rubric:: References + +.. [Hurwitz_1964] H. Hurwitz Jr., "Naval Reactors Physics Handbook", volume 1, p. 864. + Radkowsky, A. (Ed.), Naval Reactors, Division of Reactor Development, U.S. + Atomic Energy Commission (1964). + +.. [Dorville_2025] J. Dorville, L. Labrie-Cleary, and P. K. Romano, "Implementation + of the Iterated Fission Probability Method in OpenMC to Compute Adjoint-Weighted + Kinetics Parameters", International Conference on Mathematics and Computational + Methods Applied to Nuclear Science and Engineering (M&C 2025), Denver, April 27-30, + 2025 (to be presented). diff --git a/docs/source/usersguide/tallies.rst b/docs/source/usersguide/tallies.rst index 02328af58ca..e3b4e508bc9 100644 --- a/docs/source/usersguide/tallies.rst +++ b/docs/source/usersguide/tallies.rst @@ -322,6 +322,24 @@ The following tables show all valid scores: | |particle. Note that this score can only be combined| | |with a cell filter and an energy filter. | +----------------------+---------------------------------------------------+ + |ifp-time-numerator |Adjoint-weighted lifetime of neutron produced by | + | |fission in units of seconds per source particle. | + | |This score is used to compute kinetics parameters | + | |using the iterated fission probability (IFP) | + | |method. | + +----------------------+---------------------------------------------------+ + |ifp-beta-numerator |Adjoint-weighted number of delayed fission events | + | |in units of number of delayed fission event per | + | |source particle. This score is used to compute | + | |kinetics parameters using the iterated fission | + | |probability (IFP) method. | + +----------------------+---------------------------------------------------+ + |ifp-denominator |Weights corresponding to the number of fission | + | |events in units of number of fission event per | + | |source particle. This score is used to compute | + | |kinetics parameters using the iterated fission | + | |probability (IFP) method. | + +----------------------+---------------------------------------------------+ .. _usersguide_tally_normalization: diff --git a/include/openmc/bank.h b/include/openmc/bank.h index 95386514d7b..fd8fbd73ee5 100644 --- a/include/openmc/bank.h +++ b/include/openmc/bank.h @@ -22,6 +22,14 @@ extern SharedArray surf_source_bank; extern SharedArray fission_bank; +extern vector> ifp_source_delayed_group_bank; + +extern vector> ifp_source_lifetime_bank; + +extern vector> ifp_fission_delayed_group_bank; + +extern vector> ifp_fission_lifetime_bank; + extern vector progeny_per_particle; } // namespace simulation diff --git a/include/openmc/constants.h b/include/openmc/constants.h index 2a7ce199046..252194528c7 100644 --- a/include/openmc/constants.h +++ b/include/openmc/constants.h @@ -312,7 +312,10 @@ enum TallyScore { SCORE_FISS_Q_PROMPT = -14, // prompt fission Q-value SCORE_FISS_Q_RECOV = -15, // recoverable fission Q-value SCORE_DECAY_RATE = -16, // delayed neutron precursor decay rate - SCORE_PULSE_HEIGHT = -17 // pulse-height + SCORE_PULSE_HEIGHT = -17, // pulse-height + SCORE_IFP_TIME_NUM = -18, // IFP lifetime numerator + SCORE_IFP_BETA_NUM = -19, // IFP delayed fraction numerator + SCORE_IFP_DENOM = -20 // IFP common denominator }; // Global tally parameters @@ -322,6 +325,9 @@ enum class GlobalTally { K_COLLISION, K_ABSORPTION, K_TRACKLENGTH, LEAKAGE }; // Miscellaneous constexpr int C_NONE {-1}; +// Default value of generation for IFP +constexpr int DEFAULT_IFP_N_GENERATION {10}; + // Interpolation rules enum class Interpolation { histogram = 1, diff --git a/include/openmc/ifp.h b/include/openmc/ifp.h new file mode 100644 index 00000000000..633a262d5f9 --- /dev/null +++ b/include/openmc/ifp.h @@ -0,0 +1,188 @@ +#ifndef OPENMC_IFP_H +#define OPENMC_IFP_H + +#include "openmc/message_passing.h" +#include "openmc/particle.h" +#include "openmc/particle_data.h" +#include "openmc/settings.h" + +namespace openmc { + +//! Check the value of the IFP parameter for beta effective or both. +//! +//! \return true if "BetaEffective" or "Both", false otherwise. +bool is_beta_effective_or_both(); + +//! Check the value of the IFP parameter for generation time or both. +//! +//! \return true if "GenerationTime" or "Both", false otherwise. +bool is_generation_time_or_both(); + +//! Resize IFP vectors +//! +//! \param[in,out] delayed_groups List of delayed group numbers +//! \param[in,out] lifetimes List of lifetimes +//! \param[in] n Dimension to resize vectors +template +void resize_ifp_data(vector& delayed_groups, vector& lifetimes, int64_t n) +{ + if (is_beta_effective_or_both()) { + delayed_groups.resize(n); + } + if (is_generation_time_or_both()) { + lifetimes.resize(n); + } +} + +//! Update a list of values by adding a new value if the size +//! of the list can accomodate the new value or by shifting all +//! values to the left (removing the first value of the list +//! and adding the new value at the end of the list). +//! +//! \param[in] value Value to add to the list +//! \param[in] data Initial version of the list +//! \return Updated list +template +vector _ifp(const T& value, const vector& data) +{ + vector updated; + size_t source_idx = data.size(); + + if (source_idx < settings::ifp_n_generation) { + updated.resize(source_idx + 1); + for (size_t i = 0; i < source_idx; i++) { + updated[i] = data[i]; + } + updated[source_idx] = value; + } else if (source_idx == settings::ifp_n_generation) { + updated.resize(source_idx); + for (size_t i = 0; i < source_idx - 1; i++) { + updated[i] = data[i + 1]; + } + updated[source_idx - 1] = value; + } + return updated; +} + +//! \brief Iterated Fission Probability (IFP) method. +//! +//! Add the IFP information in the IFP banks using the same index +//! as the one used to append the fission site to the fission bank. +//! Multithreading protection is guaranteed by the index returned by the +//! thread_safe_append call in physics.cpp. +//! +//! Needs to be done after the delayed group is found. +//! +//! \param[in] p Particle +//! \param[in] site Fission site +//! \param[in] idx Bank index from the thread_safe_append call in physics.cpp +void ifp(const Particle& p, const SourceSite& site, int64_t idx); + +//! Resize the IFP banks used in the simulation +void resize_simulation_ifp_banks(); + +//! Retrieve IFP data from the IFP fission banks. +//! +//! \param[in] i_bank Index in the fission banks +//! \param[in,out] delayed_groups Delayed group numbers +//! \param[in,out] lifetimes Lifetimes lists +void copy_ifp_data_from_fission_banks( + int i_bank, vector& delayed_groups, vector& lifetimes); + +#ifdef OPENMC_MPI + +//! Deserialization information for transfer of IFP data using MPI +struct DeserializationInfo { + int64_t index_local; //!< local index + int64_t n; //!< number of sites sent +}; + +//! Broadcast the number of generation determined by the size of the first +//! element on the first processor. +//! +//! \param[in] n_generation Number of generations +//! \param[in] delayed_groups List of delayed group numbers lists +//! \param[in] lifetimes List of lifetimes lists +void broadcast_ifp_n_generation(int& n_generation, + const vector>& delayed_groups, + const vector>& lifetimes); + +//! Send IFP data using MPI. +//! +//! \param[in] idx Index of the first site +//! \param[in] n Number of sites to send +//! \param[in] n_generation Number of generations +//! \param[in] neighbor Index of the neighboring processor +//! \param[in] requests MPI requests +//! \param[in] delayed_groups List of delayed group numbers lists +//! \param[out] send_delayed_groups Delayed group numbers buffer +//! \param[in] lifetimes List of lifetimes lists +//! \param[out] send_lifetimes Lifetimes buffer +void send_ifp_info(int64_t idx, int64_t n, int n_generation, int neighbor, + vector& requests, const vector>& delayed_groups, + vector& send_delayed_groups, const vector>& lifetimes, + vector& send_lifetimes); + +//! Receive IFP data using MPI. +//! +//! \param[in] idx Index of the first site +//! \param[in] n Number of sites to receive +//! \param[in] n_generation Number of generations +//! \param[in] neighbor Index of the neighboring processor +//! \param[in] requests MPI requests +//! \param[in] delayed_groups List of delayed group numbers +//! \param[in] lifetimes List of lifetimes +//! \param[out] deserialization Information to deserialize the received data +void receive_ifp_data(int64_t idx, int64_t n, int n_generation, int neighbor, + vector& requests, vector& delayed_groups, + vector& lifetimes, vector& deserialization); + +//! Copy partial IFP data from local lists to source banks. +//! +//! \param[in] idx Index of the first site +//! \param[in] n Number of sites to copy +//! \param[in] i_bank Index in the IFP source banks +//! \param[in] delayed_groups List of delayed group numbers lists +//! \param[in] lifetimes List of lifetimes lists +void copy_partial_ifp_data_to_source_banks(int64_t idx, int n, int64_t i_bank, + const vector>& delayed_groups, + const vector>& lifetimes); + +//! Deserialize IFP information received using MPI and store it in +//! the IFP source banks. +//! +//! \param[in] n_generation Number of generations +//! \param[out] deserialization Information to deserialize the received data +//! \param[in] delayed_groups List of delayed group numbers +//! \param[in] lifetimes List of lifetimes +void deserialize_ifp_info(int n_generation, + const vector& deserialization, + const vector& delayed_groups, const vector& lifetimes); + +#endif + +//! Copy IFP temporary vectors to source banks. +//! +//! \param[in] delayed_groups List of delayed group numbers lists +//! \param[in] lifetimes List of lifetimes lists +void copy_complete_ifp_data_to_source_banks( + const vector>& delayed_groups, + const vector>& lifetimes); + +//! Allocate temporary vectors for IFP data. +//! +//! \param[in,out] delayed_groups List of delayed group numbers lists +//! \param[in,out] lifetimes List of delayed group numbers lists +void allocate_temporary_vector_ifp( + vector>& delayed_groups, vector>& lifetimes); + +//! Copy local IFP data to IFP fission banks. +//! +//! \param[in] delayed_groups_ptr Pointer to delayed group numbers +//! \param[in] lifetimes_ptr Pointer to lifetimes +void copy_ifp_data_to_fission_banks( + const vector* delayed_groups_ptr, const vector* lifetimes_ptr); + +} // namespace openmc + +#endif // OPENMC_IFP_H diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index 15ae57893a7..1570b780bb9 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -454,6 +454,9 @@ class ParticleData : public GeometryState { int cell_born_ {-1}; + // Iterated Fission Probability + double lifetime_ {0.0}; //!< neutron lifetime [s] + int n_collision_ {0}; bool write_track_ {false}; @@ -560,6 +563,10 @@ class ParticleData : public GeometryState { double& time_last() { return time_last_; } const double& time_last() const { return time_last_; } + // Particle lifetime + double& lifetime() { return lifetime_; } + const double& lifetime() const { return lifetime_; } + // What event took place, described in greater detail below TallyEvent& event() { return event_; } const TallyEvent& event() const { return event_; } diff --git a/include/openmc/settings.h b/include/openmc/settings.h index f3aff08b0c0..9017b2d080b 100644 --- a/include/openmc/settings.h +++ b/include/openmc/settings.h @@ -24,6 +24,14 @@ enum class SSWCellType { To, }; +// Type of IFP parameters +enum class IFPParameter { + None, + Both, + BetaEffective, + GenerationTime, +}; + //============================================================================== // Global variable declarations //============================================================================== @@ -42,7 +50,8 @@ extern bool delayed_photon_scaling; //!< Scale fission photon yield to include delayed extern "C" bool entropy_on; //!< calculate Shannon entropy? extern "C" bool - event_based; //!< use event-based mode (instead of history-based) + event_based; //!< use event-based mode (instead of history-based) +extern bool ifp_on; //!< Use IFP for kinetics parameters? extern bool legendre_to_tabular; //!< convert Legendre distributions to tabular? extern bool material_cell_offsets; //!< create material cells offsets? extern "C" bool output_summary; //!< write summary.h5? @@ -112,6 +121,10 @@ extern array energy_cutoff; //!< Energy cutoff in [eV] for each particle type extern array time_cutoff; //!< Time cutoff in [s] for each particle type +extern int + ifp_n_generation; //!< Number of generation for Iterated Fission Probability +extern IFPParameter + ifp_parameter; //!< Parameter to calculate for Iterated Fission Probability extern int legendre_to_tabular_points; //!< number of points to convert Legendres extern int max_order; //!< Maximum Legendre order for multigroup data diff --git a/openmc/lib/tally.py b/openmc/lib/tally.py index d0b34aedc26..c17b16597f9 100644 --- a/openmc/lib/tally.py +++ b/openmc/lib/tally.py @@ -104,7 +104,9 @@ -5: 'absorption', -6: 'fission', -7: 'nu-fission', -8: 'kappa-fission', -9: 'current', -10: 'events', -11: 'delayed-nu-fission', -12: 'prompt-nu-fission', -13: 'inverse-velocity', -14: 'fission-q-prompt', - -15: 'fission-q-recoverable', -16: 'decay-rate', -17: 'pulse-height' + -15: 'fission-q-recoverable', -16: 'decay-rate', -17: 'pulse-height', + -18: 'ifp-time-numerator', -19: 'ifp-beta-numerator', + -20: 'ifp-denominator', } _ESTIMATORS = { 0: 'analog', 1: 'tracklength', 2: 'collision' diff --git a/openmc/settings.py b/openmc/settings.py index a2bf51a51e5..6fb36f3fc12 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -86,6 +86,9 @@ class Settings: .. versionadded:: 0.12 generations_per_batch : int Number of generations per batch + ifp_n_generation : int + Number of generations to consider for the Iterated Fission Probability + method. max_lost_particles : int Maximum number of lost particles @@ -375,6 +378,9 @@ def __init__(self, **kwargs): self._output = None + # Iterated Fission Probability + self._ifp_n_generation = None + # Output options self._statepoint = {} self._sourcepoint = {} @@ -826,6 +832,17 @@ def verbosity(self, verbosity: int): cv.check_less_than('verbosity', verbosity, 10, True) self._verbosity = verbosity + @property + def ifp_n_generation(self) -> int: + return self._ifp_n_generation + + @ifp_n_generation.setter + def ifp_n_generation(self, ifp_n_generation: int): + if ifp_n_generation is not None: + cv.check_type("number of generations", ifp_n_generation, Integral) + cv.check_greater_than("number of generations", ifp_n_generation, 0) + self._ifp_n_generation = ifp_n_generation + @property def tabular_legendre(self) -> dict: return self._tabular_legendre @@ -1455,6 +1472,11 @@ def _create_no_reduce_subelement(self, root): element = ET.SubElement(root, "no_reduce") element.text = str(self._no_reduce).lower() + def _create_ifp_n_generation_subelement(self, root): + if self._ifp_n_generation is not None: + element = ET.SubElement(root, "ifp_n_generation") + element.text = str(self._ifp_n_generation) + def _create_tabular_legendre_subelements(self, root): if self.tabular_legendre: element = ET.SubElement(root, "tabular_legendre") @@ -1888,6 +1910,11 @@ def _verbosity_from_xml_element(self, root): if text is not None: self.verbosity = int(text) + def _ifp_n_generation_from_xml_element(self, root): + text = get_text(root, 'ifp_n_generation') + if text is not None: + self.ifp_n_generation = int(text) + def _tabular_legendre_from_xml_element(self, root): elem = root.find('tabular_legendre') if elem is not None: @@ -2116,6 +2143,7 @@ def to_xml_element(self, mesh_memo=None): self._create_trigger_subelement(element) self._create_no_reduce_subelement(element) self._create_verbosity_subelement(element) + self._create_ifp_n_generation_subelement(element) self._create_tabular_legendre_subelements(element) self._create_temperature_subelements(element) self._create_trace_subelement(element) @@ -2225,6 +2253,7 @@ def from_xml_element(cls, elem, meshes=None): settings._trigger_from_xml_element(elem) settings._no_reduce_from_xml_element(elem) settings._verbosity_from_xml_element(elem) + settings._ifp_n_generation_from_xml_element(elem) settings._tabular_legendre_from_xml_element(elem) settings._temperature_from_xml_element(elem) settings._trace_from_xml_element(elem) diff --git a/src/bank.cpp b/src/bank.cpp index 8d00d544090..9955939f6e6 100644 --- a/src/bank.cpp +++ b/src/bank.cpp @@ -1,6 +1,7 @@ #include "openmc/bank.h" #include "openmc/capi.h" #include "openmc/error.h" +#include "openmc/ifp.h" #include "openmc/message_passing.h" #include "openmc/simulation.h" #include "openmc/vector.h" @@ -26,6 +27,14 @@ SharedArray surf_source_bank; // function. SharedArray fission_bank; +vector> ifp_source_delayed_group_bank; + +vector> ifp_source_lifetime_bank; + +vector> ifp_fission_delayed_group_bank; + +vector> ifp_fission_lifetime_bank; + // Each entry in this vector corresponds to the number of progeny produced // this generation for the particle located at that index. This vector is // used to efficiently sort the fission bank after each iteration. @@ -43,6 +52,10 @@ void free_memory_bank() simulation::surf_source_bank.clear(); simulation::fission_bank.clear(); simulation::progeny_per_particle.clear(); + simulation::ifp_source_delayed_group_bank.clear(); + simulation::ifp_source_lifetime_bank.clear(); + simulation::ifp_fission_delayed_group_bank.clear(); + simulation::ifp_fission_lifetime_bank.clear(); } void init_fission_bank(int64_t max) @@ -82,6 +95,8 @@ void sort_fission_bank() // over provisioned, so we can use that as scratch space. SourceSite* sorted_bank; vector sorted_bank_holder; + vector> sorted_ifp_delayed_group_bank; + vector> sorted_ifp_lifetime_bank; // If there is not enough space, allocate a temporary vector and point to it if (simulation::fission_bank.size() > @@ -92,6 +107,11 @@ void sort_fission_bank() sorted_bank = &simulation::fission_bank[simulation::fission_bank.size()]; } + if (settings::ifp_on) { + allocate_temporary_vector_ifp( + sorted_ifp_delayed_group_bank, sorted_ifp_lifetime_bank); + } + // Use parent and progeny indices to sort fission bank for (int64_t i = 0; i < simulation::fission_bank.size(); i++) { const auto& site = simulation::fission_bank[i]; @@ -102,11 +122,19 @@ void sort_fission_bank() "shared fission bank size."); } sorted_bank[idx] = site; + if (settings::ifp_on) { + copy_ifp_data_from_fission_banks( + i, sorted_ifp_delayed_group_bank[idx], sorted_ifp_lifetime_bank[idx]); + } } // Copy sorted bank into the fission bank std::copy(sorted_bank, sorted_bank + simulation::fission_bank.size(), simulation::fission_bank.data()); + if (settings::ifp_on) { + copy_ifp_data_to_fission_banks( + sorted_ifp_delayed_group_bank.data(), sorted_ifp_lifetime_bank.data()); + } } //============================================================================== diff --git a/src/eigenvalue.cpp b/src/eigenvalue.cpp index 8669d76f947..2685bbe98a4 100644 --- a/src/eigenvalue.cpp +++ b/src/eigenvalue.cpp @@ -11,6 +11,7 @@ #include "openmc/constants.h" #include "openmc/error.h" #include "openmc/hdf5_interface.h" +#include "openmc/ifp.h" #include "openmc/math_functions.h" #include "openmc/mesh.h" #include "openmc/message_passing.h" @@ -153,8 +154,17 @@ void synchronize_bank() // Allocate temporary source bank -- we don't really know how many fission // sites were created, so overallocate by a factor of 3 int64_t index_temp = 0; + vector temp_sites(3 * simulation::work_per_rank); + // Temporary banks for IFP + vector> temp_delayed_groups; + vector> temp_lifetimes; + if (settings::ifp_on) { + resize_ifp_data( + temp_delayed_groups, temp_lifetimes, 3 * simulation::work_per_rank); + } + for (int64_t i = 0; i < simulation::fission_bank.size(); i++) { const auto& site = simulation::fission_bank[i]; @@ -165,6 +175,10 @@ void synchronize_bank() if (total < settings::n_particles) { for (int64_t j = 1; j <= settings::n_particles / total; ++j) { temp_sites[index_temp] = site; + if (settings::ifp_on) { + copy_ifp_data_from_fission_banks( + i, temp_delayed_groups[index_temp], temp_lifetimes[index_temp]); + } ++index_temp; } } @@ -172,6 +186,10 @@ void synchronize_bank() // Randomly sample sites needed if (prn(&seed) < p_sample) { temp_sites[index_temp] = site; + if (settings::ifp_on) { + copy_ifp_data_from_fission_banks( + i, temp_delayed_groups[index_temp], temp_lifetimes[index_temp]); + } ++index_temp; } } @@ -188,6 +206,8 @@ void synchronize_bank() MPI_Exscan(&index_temp, &start, 1, MPI_INT64_T, MPI_SUM, mpi::intracomm); finish = start + index_temp; + // TODO: protect for MPI_Exscan at rank 0 + // Allocate space for bank_position if this hasn't been done yet int64_t bank_position[mpi::n_procs]; MPI_Allgather( @@ -211,9 +231,15 @@ void synchronize_bank() // If we have too few sites, repeat sites from the very end of the // fission bank sites_needed = settings::n_particles - finish; + // TODO: sites_needed > simulation::fission_bank.size() or other test to + // make sure we don't need info from other proc for (int i = 0; i < sites_needed; ++i) { int i_bank = simulation::fission_bank.size() - sites_needed + i; temp_sites[index_temp] = simulation::fission_bank[i_bank]; + if (settings::ifp_on) { + copy_ifp_data_from_fission_banks(i_bank, + temp_delayed_groups[index_temp], temp_lifetimes[index_temp]); + } ++index_temp; } } @@ -229,15 +255,32 @@ void synchronize_bank() // ========================================================================== // SEND BANK SITES TO NEIGHBORS + // IFP number of generation + int ifp_n_generation; + if (settings::ifp_on) { + broadcast_ifp_n_generation( + ifp_n_generation, temp_delayed_groups, temp_lifetimes); + } + int64_t index_local = 0; vector requests; + // IFP send buffers + vector send_delayed_groups; + vector send_lifetimes; + if (start < settings::n_particles) { // Determine the index of the processor which has the first part of the // source_bank for the local processor int neighbor = upper_bound_index( simulation::work_index.begin(), simulation::work_index.end(), start); + // Resize IFP send buffers + if (settings::ifp_on && mpi::n_procs > 1) { + resize_ifp_data(send_delayed_groups, send_lifetimes, + ifp_n_generation * 3 * simulation::work_per_rank); + } + while (start < finish) { // Determine the number of sites to send int64_t n = @@ -250,6 +293,13 @@ void synchronize_bank() MPI_Isend(&temp_sites[index_local], static_cast(n), mpi::source_site, neighbor, mpi::rank, mpi::intracomm, &requests.back()); + + if (settings::ifp_on) { + // Send IFP data + send_ifp_info(index_local, n, ifp_n_generation, neighbor, requests, + temp_delayed_groups, send_delayed_groups, temp_lifetimes, + send_lifetimes); + } } // Increment all indices @@ -271,6 +321,11 @@ void synchronize_bank() start = simulation::work_index[mpi::rank]; index_local = 0; + // IFP receive buffers + vector recv_delayed_groups; + vector recv_lifetimes; + vector deserialization_info; + // Determine what process has the source sites that will need to be stored at // the beginning of this processor's source bank. @@ -282,6 +337,12 @@ void synchronize_bank() upper_bound_index(bank_position, bank_position + mpi::n_procs, start); } + // Resize IFP receive buffers + if (settings::ifp_on && mpi::n_procs > 1) { + resize_ifp_data(recv_delayed_groups, recv_lifetimes, + ifp_n_generation * simulation::work_per_rank); + } + while (start < simulation::work_index[mpi::rank + 1]) { // Determine how many sites need to be received int64_t n; @@ -301,13 +362,24 @@ void synchronize_bank() MPI_Irecv(&simulation::source_bank[index_local], static_cast(n), mpi::source_site, neighbor, neighbor, mpi::intracomm, &requests.back()); + if (settings::ifp_on) { + // Receive IFP data + receive_ifp_data(index_local, n, ifp_n_generation, neighbor, requests, + recv_delayed_groups, recv_lifetimes, deserialization_info); + } + } else { - // If the source sites are on this procesor, we can simply copy them + // If the source sites are on this processor, we can simply copy them // from the temp_sites bank index_temp = start - bank_position[mpi::rank]; std::copy(&temp_sites[index_temp], &temp_sites[index_temp + n], &simulation::source_bank[index_local]); + + if (settings::ifp_on) { + copy_partial_ifp_data_to_source_banks( + index_temp, n, index_local, temp_delayed_groups, temp_lifetimes); + } } // Increment all indices @@ -323,9 +395,17 @@ void synchronize_bank() int n_request = requests.size(); MPI_Waitall(n_request, requests.data(), MPI_STATUSES_IGNORE); + if (settings::ifp_on) { + deserialize_ifp_info(ifp_n_generation, deserialization_info, + recv_delayed_groups, recv_lifetimes); + } + #else std::copy(temp_sites.data(), temp_sites.data() + settings::n_particles, simulation::source_bank.begin()); + if (settings::ifp_on) { + copy_complete_ifp_data_to_source_banks(temp_delayed_groups, temp_lifetimes); + } #endif simulation::time_bank_sendrecv.stop(); diff --git a/src/finalize.cpp b/src/finalize.cpp index ed3d0e7f464..54aa1d1d9ee 100644 --- a/src/finalize.cpp +++ b/src/finalize.cpp @@ -171,8 +171,9 @@ int openmc_finalize() // Free all MPI types #ifdef OPENMC_MPI - if (mpi::source_site != MPI_DATATYPE_NULL) + if (mpi::source_site != MPI_DATATYPE_NULL) { MPI_Type_free(&mpi::source_site); + } #endif openmc_reset_random_ray(); diff --git a/src/ifp.cpp b/src/ifp.cpp new file mode 100644 index 00000000000..1f81f26f6ea --- /dev/null +++ b/src/ifp.cpp @@ -0,0 +1,216 @@ +#include "openmc/ifp.h" + +#include "openmc/bank.h" +#include "openmc/message_passing.h" +#include "openmc/particle.h" +#include "openmc/particle_data.h" +#include "openmc/settings.h" +#include "openmc/simulation.h" +#include "openmc/vector.h" + +namespace openmc { + +bool is_beta_effective_or_both() +{ + if (settings::ifp_parameter == IFPParameter::BetaEffective || + settings::ifp_parameter == IFPParameter::Both) { + return true; + } + return false; +} + +bool is_generation_time_or_both() +{ + if (settings::ifp_parameter == IFPParameter::GenerationTime || + settings::ifp_parameter == IFPParameter::Both) { + return true; + } + return false; +} + +void ifp(const Particle& p, const SourceSite& site, int64_t idx) +{ + if (is_beta_effective_or_both()) { + const auto& delayed_groups = + simulation::ifp_source_delayed_group_bank[p.current_work() - 1]; + simulation::ifp_fission_delayed_group_bank[idx] = + _ifp(site.delayed_group, delayed_groups); + } + if (is_generation_time_or_both()) { + const auto& lifetimes = + simulation::ifp_source_lifetime_bank[p.current_work() - 1]; + simulation::ifp_fission_lifetime_bank[idx] = _ifp(p.lifetime(), lifetimes); + } +} + +void resize_simulation_ifp_banks() +{ + resize_ifp_data(simulation::ifp_source_delayed_group_bank, + simulation::ifp_source_lifetime_bank, simulation::work_per_rank); + resize_ifp_data(simulation::ifp_fission_delayed_group_bank, + simulation::ifp_fission_lifetime_bank, 3 * simulation::work_per_rank); +} + +void copy_ifp_data_from_fission_banks( + int i_bank, vector& delayed_groups, vector& lifetimes) +{ + if (is_beta_effective_or_both()) { + delayed_groups = simulation::ifp_fission_delayed_group_bank[i_bank]; + } + if (is_generation_time_or_both()) { + lifetimes = simulation::ifp_fission_lifetime_bank[i_bank]; + } +} + +#ifdef OPENMC_MPI + +void broadcast_ifp_n_generation(int& n_generation, + const vector>& delayed_groups, + const vector>& lifetimes) +{ + if (mpi::rank == 0) { + if (is_beta_effective_or_both()) { + n_generation = static_cast(delayed_groups[0].size()); + } else { + n_generation = static_cast(lifetimes[0].size()); + } + } + MPI_Bcast(&n_generation, 1, MPI_INT, 0, mpi::intracomm); +} + +void send_ifp_info(int64_t idx, int64_t n, int n_generation, int neighbor, + vector& requests, const vector>& delayed_groups, + vector& send_delayed_groups, const vector>& lifetimes, + vector& send_lifetimes) +{ + // Copy data in send buffers + for (int i = idx; i < idx + n; i++) { + if (is_beta_effective_or_both()) { + std::copy(delayed_groups[i].begin(), delayed_groups[i].end(), + send_delayed_groups.begin() + i * n_generation); + } + if (is_generation_time_or_both()) { + std::copy(lifetimes[i].begin(), lifetimes[i].end(), + send_lifetimes.begin() + i * n_generation); + } + } + // Send delayed groups + if (is_beta_effective_or_both()) { + requests.emplace_back(); + MPI_Isend(&send_delayed_groups[n_generation * idx], + n_generation * static_cast(n), MPI_INT, neighbor, mpi::rank, + mpi::intracomm, &requests.back()); + } + // Send lifetimes + if (is_generation_time_or_both()) { + requests.emplace_back(); + MPI_Isend(&send_lifetimes[n_generation * idx], + n_generation * static_cast(n), MPI_DOUBLE, neighbor, mpi::rank, + mpi::intracomm, &requests.back()); + } +} + +void receive_ifp_data(int64_t idx, int64_t n, int n_generation, int neighbor, + vector& requests, vector& delayed_groups, + vector& lifetimes, vector& deserialization) +{ + // Receive delayed groups + if (is_beta_effective_or_both()) { + requests.emplace_back(); + MPI_Irecv(&delayed_groups[n_generation * idx], + n_generation * static_cast(n), MPI_INT, neighbor, neighbor, + mpi::intracomm, &requests.back()); + } + // Receive lifetimes + if (is_generation_time_or_both()) { + requests.emplace_back(); + MPI_Irecv(&lifetimes[n_generation * idx], + n_generation * static_cast(n), MPI_DOUBLE, neighbor, neighbor, + mpi::intracomm, &requests.back()); + } + // Deserialization info to reconstruct data later + DeserializationInfo info = {idx, n}; + deserialization.push_back(info); +} + +void copy_partial_ifp_data_to_source_banks(int64_t idx, int n, int64_t i_bank, + const vector>& delayed_groups, + const vector>& lifetimes) +{ + if (is_beta_effective_or_both()) { + std::copy(&delayed_groups[idx], &delayed_groups[idx + n], + &simulation::ifp_source_delayed_group_bank[i_bank]); + } + if (is_generation_time_or_both()) { + std::copy(&lifetimes[idx], &lifetimes[idx + n], + &simulation::ifp_source_lifetime_bank[i_bank]); + } +} + +void deserialize_ifp_info(int n_generation, + const vector& deserialization, + const vector& delayed_groups, const vector& lifetimes) +{ + for (auto info : deserialization) { + int64_t index_local = info.index_local; + int64_t n = info.n; + + for (int i = index_local; i < index_local + n; i++) { + if (is_beta_effective_or_both()) { + vector delayed_groups_received( + delayed_groups.begin() + n_generation * i, + delayed_groups.begin() + n_generation * (i + 1)); + simulation::ifp_source_delayed_group_bank[i] = delayed_groups_received; + } + if (is_generation_time_or_both()) { + vector lifetimes_received(lifetimes.begin() + n_generation * i, + lifetimes.begin() + n_generation * (i + 1)); + simulation::ifp_source_lifetime_bank[i] = lifetimes_received; + } + } + } +} + +#endif + +void copy_complete_ifp_data_to_source_banks( + const vector>& delayed_groups, + const vector>& lifetimes) +{ + if (is_beta_effective_or_both()) { + std::copy(delayed_groups.data(), + delayed_groups.data() + settings::n_particles, + simulation::ifp_source_delayed_group_bank.begin()); + } + if (is_generation_time_or_both()) { + std::copy(lifetimes.data(), lifetimes.data() + settings::n_particles, + simulation::ifp_source_lifetime_bank.begin()); + } +} + +void allocate_temporary_vector_ifp( + vector>& delayed_groups, vector>& lifetimes) +{ + if (is_beta_effective_or_both()) { + delayed_groups.resize(simulation::fission_bank.size()); + } + if (is_generation_time_or_both()) { + lifetimes.resize(simulation::fission_bank.size()); + } +} + +void copy_ifp_data_to_fission_banks(const vector* const delayed_groups_ptr, + const vector* lifetimes_ptr) +{ + if (is_beta_effective_or_both()) { + std::copy(delayed_groups_ptr, + delayed_groups_ptr + simulation::fission_bank.size(), + simulation::ifp_fission_delayed_group_bank.data()); + } + if (is_generation_time_or_both()) { + std::copy(lifetimes_ptr, lifetimes_ptr + simulation::fission_bank.size(), + simulation::ifp_fission_lifetime_bank.data()); + } +} + +} // namespace openmc diff --git a/src/output.cpp b/src/output.cpp index e20868efbb1..baaf682a157 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -593,6 +593,9 @@ const std::unordered_map score_names = { {SCORE_FISS_Q_RECOV, "Recoverable fission power"}, {SCORE_CURRENT, "Current"}, {SCORE_PULSE_HEIGHT, "pulse-height"}, + {SCORE_IFP_TIME_NUM, "IFP lifetime numerator"}, + {SCORE_IFP_BETA_NUM, "IFP delayed fraction numerator"}, + {SCORE_IFP_DENOM, "IFP common denominator"}, }; //! Create an ASCII output file showing all tally results. diff --git a/src/particle.cpp b/src/particle.cpp index c51011d6b7f..324276d15f3 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -116,6 +116,7 @@ void Particle::from_source(const SourceSite* src) n_collision() = 0; fission() = false; zero_flux_derivs(); + lifetime() = 0.0; // Copy attributes from source bank site type() = src->particle; @@ -235,7 +236,9 @@ void Particle::event_advance() for (int j = 0; j < n_coord(); ++j) { coord(j).r += distance * coord(j).u; } - this->time() += distance / this->speed(); + double dt = distance / this->speed(); + this->time() += dt; + this->lifetime() += dt; // Kill particle if its time exceeds the cutoff bool hit_time_boundary = false; @@ -243,6 +246,7 @@ void Particle::event_advance() if (time() > time_cutoff) { double dt = time() - time_cutoff; time() = time_cutoff; + lifetime() = time_cutoff; double push_back_distance = speed() * dt; this->move_distance(-push_back_distance); diff --git a/src/physics.cpp b/src/physics.cpp index 72c04a5caf6..a8e5b9e8135 100644 --- a/src/physics.cpp +++ b/src/physics.cpp @@ -8,6 +8,7 @@ #include "openmc/eigenvalue.h" #include "openmc/endf.h" #include "openmc/error.h" +#include "openmc/ifp.h" #include "openmc/material.h" #include "openmc/math_functions.h" #include "openmc/message_passing.h" @@ -233,6 +234,10 @@ void create_fission_sites(Particle& p, int i_nuclide, const Reaction& rx) // Break out of loop as no more sites can be added to fission bank break; } + // Iterated Fission Probability (IFP) method + if (settings::ifp_on) { + ifp(p, site, idx); + } } else { p.secondary_bank().push_back(site); } diff --git a/src/reaction.cpp b/src/reaction.cpp index 97147343831..d96790c6d43 100644 --- a/src/reaction.cpp +++ b/src/reaction.cpp @@ -202,6 +202,9 @@ std::unordered_map REACTION_NAME_MAP { {SCORE_FISS_Q_PROMPT, "fission-q-prompt"}, {SCORE_FISS_Q_RECOV, "fission-q-recoverable"}, {SCORE_PULSE_HEIGHT, "pulse-height"}, + {SCORE_IFP_TIME_NUM, "ifp-time-numerator"}, + {SCORE_IFP_BETA_NUM, "ifp-beta-numerator"}, + {SCORE_IFP_DENOM, "ifp-denominator"}, // Normal ENDF-based reactions {TOTAL_XS, "(n,total)"}, {ELASTIC, "(n,elastic)"}, diff --git a/src/settings.cpp b/src/settings.cpp index 53fcdec38b3..c135fa3391e 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -52,6 +52,7 @@ bool create_fission_neutrons {true}; bool delayed_photon_scaling {true}; bool entropy_on {false}; bool event_based {false}; +bool ifp_on {false}; bool legendre_to_tabular {true}; bool material_cell_offsets {true}; bool output_summary {true}; @@ -106,6 +107,8 @@ int max_particle_events {1000000}; ElectronTreatment electron_treatment {ElectronTreatment::TTB}; array energy_cutoff {0.0, 1000.0, 0.0, 0.0}; array time_cutoff {INFTY, INFTY, INFTY, INFTY}; +int ifp_n_generation {-1}; +IFPParameter ifp_parameter {IFPParameter::None}; int legendre_to_tabular_points {C_NONE}; int max_order {0}; int n_log_bins {8000}; @@ -1059,6 +1062,20 @@ void read_settings_xml(pugi::xml_node root) temperature_range[1] = range.at(1); } + // Check for user value for the number of generation of the Iterated Fission + // Probability (IFP) method + if (check_for_node(root, "ifp_n_generation")) { + ifp_n_generation = std::stoi(get_node_value(root, "ifp_n_generation")); + if (ifp_n_generation <= 0) { + fatal_error("'ifp_n_generation' must be greater than 0."); + } + // Avoid tallying 0 if IFP logs are not complete when active cycles start + if (ifp_n_generation > n_inactive) { + fatal_error("'ifp_n_generation' must be lower than or equal to the " + "number of inactive cycles."); + } + } + // Check for tabular_legendre options if (check_for_node(root, "tabular_legendre")) { // Get pointer to tabular_legendre node diff --git a/src/simulation.cpp b/src/simulation.cpp index 74a7dbe6392..e4521c06dc9 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -7,6 +7,7 @@ #include "openmc/error.h" #include "openmc/event.h" #include "openmc/geometry_aux.h" +#include "openmc/ifp.h" #include "openmc/material.h" #include "openmc/mcpl_interface.h" #include "openmc/message_passing.h" @@ -335,6 +336,11 @@ void allocate_banks() // Allocate fission bank init_fission_bank(3 * simulation::work_per_rank); + + // Allocate IFP bank + if (settings::ifp_on) { + resize_simulation_ifp_banks(); + } } if (settings::surf_source_write) { diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 96d684f71a8..35805b20d7e 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -180,6 +180,64 @@ Tally::Tally(pugi::xml_node node) fatal_error(fmt::format("No scores specified on tally {}.", id_)); } + // Set IFP if needed + if (!settings::ifp_on) { + // Determine if this tally has an IFP score + bool has_ifp_score = false; + for (int score : scores_) { + if (score == SCORE_IFP_TIME_NUM || score == SCORE_IFP_BETA_NUM || + score == SCORE_IFP_DENOM) { + has_ifp_score = true; + break; + } + } + + // Check for errors + if (has_ifp_score) { + if (settings::run_mode == RunMode::EIGENVALUE) { + if (settings::ifp_n_generation < 0) { + settings::ifp_n_generation = DEFAULT_IFP_N_GENERATION; + warning(fmt::format( + "{} generations will be used for IFP (default value). It can be " + "changed using the 'ifp_n_generation' settings.", + settings::ifp_n_generation)); + } + if (settings::ifp_n_generation > settings::n_inactive) { + fatal_error("'ifp_n_generation' must be lower than or equal to the " + "number of inactive cycles."); + } + settings::ifp_on = true; + } else { + fatal_error( + "Iterated Fission Probability can only be used in an eigenvalue " + "calculation."); + } + } + } + + // Set IFP parameters if needed + if (settings::ifp_on) { + for (int score : scores_) { + switch (score) { + case SCORE_IFP_TIME_NUM: + if (settings::ifp_parameter == IFPParameter::None) { + settings::ifp_parameter = IFPParameter::GenerationTime; + } else if (settings::ifp_parameter == IFPParameter::BetaEffective) { + settings::ifp_parameter = IFPParameter::Both; + } + break; + case SCORE_IFP_BETA_NUM: + case SCORE_IFP_DENOM: + if (settings::ifp_parameter == IFPParameter::None) { + settings::ifp_parameter = IFPParameter::BetaEffective; + } else if (settings::ifp_parameter == IFPParameter::GenerationTime) { + settings::ifp_parameter = IFPParameter::Both; + } + break; + } + } + } + // Check if tally is compatible with particle type if (!settings::photon_transport) { for (int score : scores_) { @@ -585,7 +643,12 @@ void Tally::set_scores(const vector& scores) } } } + break; + case SCORE_IFP_TIME_NUM: + case SCORE_IFP_BETA_NUM: + case SCORE_IFP_DENOM: + estimator_ = TallyEstimator::COLLISION; break; } diff --git a/src/tallies/tally_scoring.cpp b/src/tallies/tally_scoring.cpp index 02cb4856719..5c6386fb423 100644 --- a/src/tallies/tally_scoring.cpp +++ b/src/tallies/tally_scoring.cpp @@ -4,6 +4,7 @@ #include "openmc/capi.h" #include "openmc/constants.h" #include "openmc/error.h" +#include "openmc/ifp.h" #include "openmc/material.h" #include "openmc/mgxs_interface.h" #include "openmc/nuclide.h" @@ -890,6 +891,56 @@ void score_general_ce_nonanalog(Particle& p, int i_tally, int start_index, score_fission_q(p, score_bin, tally, flux, i_nuclide, atom_density); break; + case SCORE_IFP_TIME_NUM: + if (settings::ifp_on) { + if ((p.type() == Type::neutron) && (p.fission())) { + if (is_generation_time_or_both()) { + const auto& lifetimes = + simulation::ifp_source_lifetime_bank[p.current_work() - 1]; + if (lifetimes.size() == settings::ifp_n_generation) { + score = lifetimes[0] * p.wgt_last(); + } + } + } + } + break; + + case SCORE_IFP_BETA_NUM: + if (settings::ifp_on) { + if ((p.type() == Type::neutron) && (p.fission())) { + if (is_beta_effective_or_both()) { + const auto& delayed_groups = + simulation::ifp_source_delayed_group_bank[p.current_work() - 1]; + if (delayed_groups.size() == settings::ifp_n_generation) { + if (delayed_groups[0] > 0) { + score = p.wgt_last(); + } + } + } + } + } + break; + + case SCORE_IFP_DENOM: + if (settings::ifp_on) { + if ((p.type() == Type::neutron) && (p.fission())) { + int ifp_data_size; + if (is_beta_effective_or_both()) { + ifp_data_size = static_cast( + simulation::ifp_source_delayed_group_bank[p.current_work() - 1] + .size()); + } else { + ifp_data_size = static_cast( + simulation::ifp_source_lifetime_bank[p.current_work() - 1] + .size()); + } + if (ifp_data_size == settings::ifp_n_generation) { + score = p.wgt_last(); + } + } + } + break; + case N_2N: case N_3N: case N_4N: diff --git a/tests/regression_tests/ifp/__init__.py b/tests/regression_tests/ifp/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/ifp/inputs_true.dat b/tests/regression_tests/ifp/inputs_true.dat new file mode 100644 index 00000000000..a3a3f1d77ea --- /dev/null +++ b/tests/regression_tests/ifp/inputs_true.dat @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + eigenvalue + 1000 + 20 + 5 + + + -10.0 -10.0 -10.0 10.0 10.0 10.0 + + + true + + + 5 + + + + ifp-time-numerator ifp-beta-numerator ifp-denominator + + + diff --git a/tests/regression_tests/ifp/results_true.dat b/tests/regression_tests/ifp/results_true.dat new file mode 100644 index 00000000000..a74e2bd78bc --- /dev/null +++ b/tests/regression_tests/ifp/results_true.dat @@ -0,0 +1,9 @@ +k-combined: +1.007452E+00 5.705278E-03 +tally 1: +8.996235E-08 +5.461421E-16 +4.800000E-02 +4.680000E-04 +1.512000E+01 +1.526063E+01 diff --git a/tests/regression_tests/ifp/test.py b/tests/regression_tests/ifp/test.py new file mode 100644 index 00000000000..6969a54c49d --- /dev/null +++ b/tests/regression_tests/ifp/test.py @@ -0,0 +1,45 @@ +"""Test the Iterated Fission Probability (IFP) method to compute adjoint-weighted +kinetics parameters using dedicated tallies.""" + +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + + +@pytest.fixture() +def ifp_model(): + model = openmc.Model() + + # Material + material = openmc.Material(name="core") + material.add_nuclide("U235", 1.0) + material.set_density('g/cm3', 16.0) + + # Geometry + radius = 10.0 + sphere = openmc.Sphere(r=radius, boundary_type="vacuum") + cell = openmc.Cell(region=-sphere, fill=material) + model.geometry = openmc.Geometry([cell]) + + # Settings + model.settings.particles = 1000 + model.settings.batches = 20 + model.settings.inactive = 5 + model.settings.ifp_n_generation = 5 + + space = openmc.stats.Box(*cell.bounding_box) + model.settings.source = openmc.IndependentSource( + space=space, constraints={'fissionable': True}) + + # Tally IFP scores + tally = openmc.Tally(name="ifp-scores") + tally.scores = ["ifp-time-numerator", "ifp-beta-numerator", "ifp-denominator"] + model.tallies = [tally] + + return model + + +def test_iterated_fission_probability(ifp_model): + harness = PyAPITestHarness("statepoint.20.h5", model=ifp_model) + harness.main() diff --git a/tests/unit_tests/test_ifp.py b/tests/unit_tests/test_ifp.py new file mode 100644 index 00000000000..8d0fd98010d --- /dev/null +++ b/tests/unit_tests/test_ifp.py @@ -0,0 +1,49 @@ +"""Test the Iterated Fission Probability (IFP) method to compute adjoint-weighted +kinetics parameters using dedicated tallies.""" + +import pytest +import openmc + + +def test_xml_serialization(run_in_tmpdir): + """Check that a simple use case can be written and read in XML.""" + parameter = 5 + settings = openmc.Settings() + settings.ifp_n_generation = parameter + settings.export_to_xml() + + read_settings = openmc.Settings.from_xml() + assert read_settings.ifp_n_generation == parameter + + +@pytest.fixture(scope="module") +def geometry(): + openmc.reset_auto_ids() + material = openmc.Material() + material.add_nuclide("U235", 1.0) + sphere = openmc.Sphere(r=1.0, boundary_type="vacuum") + cell = openmc.Cell(region=-sphere, fill=material) + return openmc.Geometry([cell]) + + +@pytest.mark.parametrize( + "options, error", + [ + ({"ifp_n_generation": 0}, ValueError), + ({"ifp_n_generation": -1}, ValueError), + ({"run_mode": "fixed source"}, RuntimeError), + ({"inactive": 5, "ifp_n_generation": 6}, RuntimeError), + ({"inactive": 9}, RuntimeError) + ], +) +def test_exceptions(options, error, run_in_tmpdir, geometry): + """Test settings configuration that should return an error.""" + with pytest.raises(error): + settings = openmc.Settings(**options) + settings.particles = 100 + settings.batches = 15 + tally = openmc.Tally(name="ifp-scores") + tally.scores = ["ifp-time-numerator", "ifp-beta-numerator", "ifp-denominator"] + tallies = openmc.Tallies([tally]) + model = openmc.Model(geometry=geometry, settings=settings, tallies=tallies) + model.run() From a1134409863361c553014150949695407dea8287 Mon Sep 17 00:00:00 2001 From: John Tramm Date: Tue, 22 Apr 2025 12:17:04 -0500 Subject: [PATCH 008/159] Small Fixes to Allow Random Ray to Work with DAGMC (#3374) Co-authored-by: Patrick Shriwise --- openmc/model/model.py | 123 +++++++++++++++-------- src/random_ray/random_ray.cpp | 2 +- src/random_ray/random_ray_simulation.cpp | 4 +- 3 files changed, 85 insertions(+), 44 deletions(-) diff --git a/openmc/model/model.py b/openmc/model/model.py index 260059f4a84..9ff574ec63c 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -263,7 +263,7 @@ def from_model_xml(cls, path='model.xml'): return model def init_lib(self, threads=None, geometry_debug=False, restart_file=None, - tracks=False, output=True, event_based=None, intracomm=None): + tracks=False, output=True, event_based=None, intracomm=None, directory=None): """Initializes the model in memory via the C API .. versionadded:: 0.13.0 @@ -291,6 +291,8 @@ def init_lib(self, threads=None, geometry_debug=False, restart_file=None, the Settings will be used. intracomm : mpi4py.MPI.Intracomm or None, optional MPI intracommunicator + directory : str or None, optional + Directory to write XML files to. Defaults to None. """ import openmc.lib @@ -304,7 +306,8 @@ def init_lib(self, threads=None, geometry_debug=False, restart_file=None, args = _process_CLI_arguments( volume=False, geometry_debug=geometry_debug, restart_file=restart_file, threads=threads, tracks=tracks, - event_based=event_based) + event_based=event_based, path_input=directory) + # Args adds the openmc_exec command in the first entry; remove it args = args[1:] @@ -318,7 +321,10 @@ def init_lib(self, threads=None, geometry_debug=False, restart_file=None, self._intracomm = DummyCommunicator() if self._intracomm.rank == 0: - self.export_to_xml() + if directory is not None: + self.export_to_xml(directory=directory) + else: + self.export_to_xml() self._intracomm.barrier() # We cannot pass DummyCommunicator to openmc.lib.init so pass instead @@ -1443,7 +1449,7 @@ def differentiate_mats(self, diff_volume_method: str = None, depletable_only: bo self.geometry.get_all_materials().values() ) - def _generate_infinite_medium_mgxs(self, groups, nparticles, mgxs_path, correction): + def _generate_infinite_medium_mgxs(self, groups, nparticles, mgxs_path, correction, directory): """Generate a MGXS library by running multiple OpenMC simulations, each representing an infinite medium simulation of a single isolated material. A discrete source is used to sample particles, with an equal @@ -1455,8 +1461,15 @@ def _generate_infinite_medium_mgxs(self, groups, nparticles, mgxs_path, correcti ---------- groups : openmc.mgxs.EnergyGroups Energy group structure for the MGXS. - mgxs_path : path-like + nparticles : int + Number of particles to simulate per batch when generating MGXS. + mgxs_path : str Filename for the MGXS HDF5 file. + correction : str + Transport correction to apply to the MGXS. Options are None and + "P0". + directory : str + Directory to run the simulation in, so as to contain XML files. """ warnings.warn("The infinite medium method of generating MGXS may hang " "if a material has a k-infinity > 1.0.") @@ -1537,7 +1550,7 @@ def _generate_infinite_medium_mgxs(self, groups, nparticles, mgxs_path, correcti mgxs_lib.add_to_tallies_file(model.tallies, merge=True) # Run - statepoint_filename = model.run() + statepoint_filename = model.run(cwd=directory) # Load MGXS with openmc.StatePoint(statepoint_filename) as sp: @@ -1623,7 +1636,7 @@ def _create_stochastic_slab_geometry(materials, cell_thickness=1.0, num_repeats= return geometry, box - def _generate_stochastic_slab_mgxs(self, groups, nparticles, mgxs_path, correction) -> None: + def _generate_stochastic_slab_mgxs(self, groups, nparticles, mgxs_path, correction, directory) -> None: """Generate MGXS assuming a stochastic "sandwich" of materials in a layered slab geometry. While geometry-specific spatial shielding effects are not captured, this method can be useful when the geometry has materials only @@ -1638,8 +1651,15 @@ def _generate_stochastic_slab_mgxs(self, groups, nparticles, mgxs_path, correcti ---------- groups : openmc.mgxs.EnergyGroups Energy group structure for the MGXS. - mgxs_path : path-like + nparticles : int + Number of particles to simulate per batch when generating MGXS. + mgxs_path : str Filename for the MGXS HDF5 file. + correction : str + Transport correction to apply to the MGXS. Options are None and + "P0". + directory : str + Directory to run the simulation in, so as to contain XML files. """ openmc.reset_auto_ids() model = openmc.Model() @@ -1709,7 +1729,7 @@ def _generate_stochastic_slab_mgxs(self, groups, nparticles, mgxs_path, correcti mgxs_lib.add_to_tallies_file(model.tallies, merge=True) # Run - statepoint_filename = model.run() + statepoint_filename = model.run(cwd=directory) # Load MGXS with openmc.StatePoint(statepoint_filename) as sp: @@ -1721,7 +1741,7 @@ def _generate_stochastic_slab_mgxs(self, groups, nparticles, mgxs_path, correcti mgxs_file = mgxs_lib.create_mg_library(xs_type='macro', xsdata_names=names) mgxs_file.export_to_hdf5(mgxs_path) - def _generate_material_wise_mgxs(self, groups, nparticles, mgxs_path, correction) -> None: + def _generate_material_wise_mgxs(self, groups, nparticles, mgxs_path, correction, directory) -> None: """Generate a material-wise MGXS library for the model by running the original continuous energy OpenMC simulation of the full material geometry and source, and tally MGXS data for each material. This method @@ -1736,8 +1756,15 @@ def _generate_material_wise_mgxs(self, groups, nparticles, mgxs_path, correction ---------- groups : openmc.mgxs.EnergyGroups Energy group structure for the MGXS. + nparticles : int + Number of particles to simulate per batch when generating MGXS. mgxs_path : str Filename for the MGXS HDF5 file. + correction : str + Transport correction to apply to the MGXS. Options are None and + "P0". + directory : str + Directory to run the simulation in, so as to contain XML files. """ openmc.reset_auto_ids() model = copy.deepcopy(self) @@ -1791,7 +1818,7 @@ def _generate_material_wise_mgxs(self, groups, nparticles, mgxs_path, correction mgxs_lib.add_to_tallies_file(model.tallies, merge=True) # Run - statepoint_filename = model.run() + statepoint_filename = model.run(cwd=directory) # Load MGXS with openmc.StatePoint(statepoint_filename) as sp: @@ -1828,39 +1855,53 @@ def convert_to_multigroup(self, method="material_wise", groups='CASMO-2', if isinstance(groups, str): groups = openmc.mgxs.EnergyGroups(groups) - # Make sure all materials have a name, and that the name is a valid HDF5 - # dataset name - for material in self.materials: - if material.name is None: - material.name = f"material {material.id}" - material.name = re.sub(r'[^a-zA-Z0-9]', '_', material.name) - - # If needed, generate the needed MGXS data library file - if not Path(mgxs_path).is_file() or overwrite_mgxs_library: - if method == "infinite_medium": - self._generate_infinite_medium_mgxs( - groups, nparticles, mgxs_path, correction) - elif method == "material_wise": - self._generate_material_wise_mgxs( - groups, nparticles, mgxs_path, correction) - elif method == "stochastic_slab": - self._generate_stochastic_slab_mgxs( - groups, nparticles, mgxs_path, correction) + # Do all work (including MGXS generation) in a temporary directory + # to avoid polluting the working directory with residual XML files + with TemporaryDirectory() as tmpdir: + + # Determine if there are DAGMC universes in the model. If so, we need to synchronize + # the dagmc materials with cells. + # TODO: Can this be done without having to init/finalize? + for univ in self.geometry.get_all_universes().values(): + if isinstance(univ, openmc.DAGMCUniverse): + self.init_lib(directory=tmpdir) + self.sync_dagmc_universes() + self.finalize_lib() + break + + # Make sure all materials have a name, and that the name is a valid HDF5 + # dataset name + for material in self.materials: + if material.name is None: + material.name = f"material {material.id}" + material.name = re.sub(r'[^a-zA-Z0-9]', '_', material.name) + + # If needed, generate the needed MGXS data library file + if not Path(mgxs_path).is_file() or overwrite_mgxs_library: + if method == "infinite_medium": + self._generate_infinite_medium_mgxs( + groups, nparticles, mgxs_path, correction, tmpdir) + elif method == "material_wise": + self._generate_material_wise_mgxs( + groups, nparticles, mgxs_path, correction, tmpdir) + elif method == "stochastic_slab": + self._generate_stochastic_slab_mgxs( + groups, nparticles, mgxs_path, correction, tmpdir) + else: + raise ValueError( + f'MGXS generation method "{method}" not recognized') else: - raise ValueError( - f'MGXS generation method "{method}" not recognized') - else: - print(f'Existing MGXS library file "{mgxs_path}" will be used') + print(f'Existing MGXS library file "{mgxs_path}" will be used') - # Convert all continuous energy materials to multigroup - self.materials.cross_sections = mgxs_path - for material in self.materials: - material.set_density('macro', 1.0) - material._nuclides = [] - material._sab = [] - material.add_macroscopic(material.name) + # Convert all continuous energy materials to multigroup + self.materials.cross_sections = mgxs_path + for material in self.materials: + material.set_density('macro', 1.0) + material._nuclides = [] + material._sab = [] + material.add_macroscopic(material.name) - self.settings.energy_mode = 'multi-group' + self.settings.energy_mode = 'multi-group' def convert_to_random_ray(self): """Convert a multigroup model to use random ray. diff --git a/src/random_ray/random_ray.cpp b/src/random_ray/random_ray.cpp index 9a9d1394aac..532840f1f4e 100644 --- a/src/random_ray/random_ray.cpp +++ b/src/random_ray/random_ray.cpp @@ -277,7 +277,7 @@ void RandomRay::event_advance_ray() boundary() = distance_to_boundary(*this); double distance = boundary().distance; - if (distance <= 0.0) { + if (distance < 0.0) { mark_as_lost("Negative transport distance detected for particle " + std::to_string(id())); return; diff --git a/src/random_ray/random_ray_simulation.cpp b/src/random_ray/random_ray_simulation.cpp index b831efcd99a..40a08c3af7a 100644 --- a/src/random_ray/random_ray_simulation.cpp +++ b/src/random_ray/random_ray_simulation.cpp @@ -196,8 +196,8 @@ void validate_random_ray_inputs() "supported in random ray mode."); } if (material.get_xsdata().size() > 1) { - fatal_error("Non-isothermal MGXS detected. Only isothermal XS data sets " - "supported in random ray mode."); + warning("Non-isothermal MGXS detected. Only isothermal XS data sets " + "supported in random ray mode. Using lowest temperature."); } for (int g = 0; g < data::mg.num_energy_groups_; g++) { if (material.exists_in_model) { From 5dd6ff6527b073aa214806e01f82f24447593fec Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 22 Apr 2025 23:00:44 -0500 Subject: [PATCH 009/159] Fix negative distances from bins_crossed for CylindricalMesh (#3370) --- include/openmc/mesh.h | 5 --- src/mesh.cpp | 37 +++++++++---------- .../filter_mesh/inputs_true.dat | 4 +- tests/regression_tests/filter_mesh/test.py | 3 +- tests/unit_tests/test_cylindrical_mesh.py | 11 ++++-- tests/unit_tests/test_spherical_mesh.py | 2 - 6 files changed, 29 insertions(+), 33 deletions(-) diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index 8faf45f1b99..5a727c2b6f1 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -138,9 +138,6 @@ class Mesh { //! Perform any preparation needed to support point location within the mesh virtual void prepare_for_point_location() {}; - //! Update a position to the local coordinates of the mesh - virtual void local_coords(Position& r) const {}; - //! Return a position in the local coordinates of the mesh virtual Position local_coords(const Position& r) const { return r; }; @@ -427,8 +424,6 @@ class PeriodicStructuredMesh : public StructuredMesh { PeriodicStructuredMesh() = default; PeriodicStructuredMesh(pugi::xml_node node) : StructuredMesh {node} {}; - void local_coords(Position& r) const override { r -= origin_; }; - Position local_coords(const Position& r) const override { return r - origin_; diff --git a/src/mesh.cpp b/src/mesh.cpp index 101da746849..8280177ac13 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -915,6 +915,11 @@ void StructuredMesh::raytrace_mesh( if (total_distance == 0.0 && settings::solver_type != SolverType::RANDOM_RAY) return; + // keep a copy of the original global position to pass to get_indices, + // which performs its own transformation to local coordinates + Position global_r = r0; + Position local_r = local_coords(r0); + const int n = n_dimension_; // Flag if position is inside the mesh @@ -925,7 +930,7 @@ void StructuredMesh::raytrace_mesh( // Calculate index of current cell. Offset the position a tiny bit in // direction of flight - MeshIndex ijk = get_indices(r0 + TINY_BIT * u, in_mesh); + MeshIndex ijk = get_indices(global_r + TINY_BIT * u, in_mesh); // if track is very short, assume that it is completely inside one cell. // Only the current cell will score and no surfaces @@ -936,16 +941,10 @@ void StructuredMesh::raytrace_mesh( return; } - // translate start and end positions, - // this needs to come after the get_indices call because it does its own - // translation - local_coords(r0); - local_coords(r1); - // Calculate initial distances to next surfaces in all three dimensions std::array distances; for (int k = 0; k < n; ++k) { - distances[k] = distance_to_grid_boundary(ijk, k, r0, u, 0.0); + distances[k] = distance_to_grid_boundary(ijk, k, local_r, u, 0.0); } // Loop until r = r1 is eventually reached @@ -975,7 +974,7 @@ void StructuredMesh::raytrace_mesh( // The two other directions are still valid! ijk[k] = distances[k].next_index; distances[k] = - distance_to_grid_boundary(ijk, k, r0, u, traveled_distance); + distance_to_grid_boundary(ijk, k, local_r, u, traveled_distance); // Check if we have left the interior of the mesh in_mesh = ((ijk[k] >= 1) && (ijk[k] <= shape_[k])); @@ -1005,10 +1004,10 @@ void StructuredMesh::raytrace_mesh( // Calculate the new cell index and update all distances to next // surfaces. - ijk = get_indices(r0 + (traveled_distance + TINY_BIT) * u, in_mesh); + ijk = get_indices(global_r + (traveled_distance + TINY_BIT) * u, in_mesh); for (int k = 0; k < n; ++k) { distances[k] = - distance_to_grid_boundary(ijk, k, r0, u, traveled_distance); + distance_to_grid_boundary(ijk, k, local_r, u, traveled_distance); } // If inside the mesh, Tally inward current @@ -1482,7 +1481,7 @@ std::string CylindricalMesh::get_mesh_type() const StructuredMesh::MeshIndex CylindricalMesh::get_indices( Position r, bool& in_mesh) const { - local_coords(r); + r = local_coords(r); Position mapped_r; mapped_r[0] = std::hypot(r.x, r.y); @@ -1630,23 +1629,21 @@ StructuredMesh::MeshDistance CylindricalMesh::distance_to_grid_boundary( const MeshIndex& ijk, int i, const Position& r0, const Direction& u, double l) const { - Position r = r0 - origin_; - if (i == 0) { return std::min( - MeshDistance(ijk[i] + 1, true, find_r_crossing(r, u, l, ijk[i])), - MeshDistance(ijk[i] - 1, false, find_r_crossing(r, u, l, ijk[i] - 1))); + MeshDistance(ijk[i] + 1, true, find_r_crossing(r0, u, l, ijk[i])), + MeshDistance(ijk[i] - 1, false, find_r_crossing(r0, u, l, ijk[i] - 1))); } else if (i == 1) { return std::min(MeshDistance(sanitize_phi(ijk[i] + 1), true, - find_phi_crossing(r, u, l, ijk[i])), + find_phi_crossing(r0, u, l, ijk[i])), MeshDistance(sanitize_phi(ijk[i] - 1), false, - find_phi_crossing(r, u, l, ijk[i] - 1))); + find_phi_crossing(r0, u, l, ijk[i] - 1))); } else { - return find_z_crossing(r, u, l, ijk[i]); + return find_z_crossing(r0, u, l, ijk[i]); } } @@ -1762,7 +1759,7 @@ std::string SphericalMesh::get_mesh_type() const StructuredMesh::MeshIndex SphericalMesh::get_indices( Position r, bool& in_mesh) const { - local_coords(r); + r = local_coords(r); Position mapped_r; mapped_r[0] = r.norm(); diff --git a/tests/regression_tests/filter_mesh/inputs_true.dat b/tests/regression_tests/filter_mesh/inputs_true.dat index 0667c034148..4995749bbd3 100644 --- a/tests/regression_tests/filter_mesh/inputs_true.dat +++ b/tests/regression_tests/filter_mesh/inputs_true.dat @@ -54,8 +54,8 @@ 0.0 0.4411764705882353 0.8823529411764706 1.3235294117647058 1.7647058823529411 2.2058823529411766 2.6470588235294117 3.0882352941176467 3.5294117647058822 3.9705882352941178 4.411764705882353 4.852941176470588 5.294117647058823 5.735294117647059 6.1764705882352935 6.617647058823529 7.0588235294117645 7.5 0.0 0.3490658503988659 0.6981317007977318 1.0471975511965976 1.3962634015954636 1.7453292519943295 2.0943951023931953 2.443460952792061 2.792526803190927 3.141592653589793 3.490658503988659 3.839724354387525 4.1887902047863905 4.537856055185257 4.886921905584122 5.235987755982989 5.585053606381854 5.93411945678072 6.283185307179586 - -7.5 -6.5625 -5.625 -4.6875 -3.75 -2.8125 -1.875 -0.9375 0.0 0.9375 1.875 2.8125 3.75 4.6875 5.625 6.5625 7.5 - 0.0 0.0 0.0 + 0.0 0.9375 1.875 2.8125 3.75 4.6875 5.625 6.5625 7.5 8.4375 9.375 10.3125 11.25 12.1875 13.125 14.0625 15.0 + 0.0 0.0 -7.5 0.0 0.4411764705882353 0.8823529411764706 1.3235294117647058 1.7647058823529411 2.2058823529411766 2.6470588235294117 3.0882352941176467 3.5294117647058822 3.9705882352941178 4.411764705882353 4.852941176470588 5.294117647058823 5.735294117647059 6.1764705882352935 6.617647058823529 7.0588235294117645 7.5 diff --git a/tests/regression_tests/filter_mesh/test.py b/tests/regression_tests/filter_mesh/test.py index 6214e0486c0..165ba2a0c09 100644 --- a/tests/regression_tests/filter_mesh/test.py +++ b/tests/regression_tests/filter_mesh/test.py @@ -61,9 +61,10 @@ def model(): np.testing.assert_allclose(recti_mesh.volumes, recti_mesh_exp_vols) cyl_mesh = openmc.CylindricalMesh( + origin=(0, 0, -7.5), r_grid=np.linspace(0, 7.5, 18), phi_grid=np.linspace(0, 2*pi, 19), - z_grid=np.linspace(-7.5, 7.5, 17), + z_grid=np.linspace(0, 15, 17), ) dr = 0.5 * np.diff(np.linspace(0, 7.5, 18)**2) dp = np.full(cyl_mesh.dimension[1], 2*pi / 18) diff --git a/tests/unit_tests/test_cylindrical_mesh.py b/tests/unit_tests/test_cylindrical_mesh.py index b408bc0e917..645269825a8 100644 --- a/tests/unit_tests/test_cylindrical_mesh.py +++ b/tests/unit_tests/test_cylindrical_mesh.py @@ -49,6 +49,7 @@ def model(): return openmc.Model(geometry=geom, settings=settings, tallies=tallies) + def test_origin_read_write_to_xml(run_in_tmpdir, model): """Tests that the origin attribute can be written and read back to XML """ @@ -63,8 +64,9 @@ def test_origin_read_write_to_xml(run_in_tmpdir, model): np.testing.assert_equal(new_mesh.origin, mesh.origin) estimators = ('tracklength', 'collision') -origins = set(permutations((-geom_size, 0, 0))) -origins |= set(permutations((geom_size, 0, 0))) +offset = geom_size + 0.001 +origins = set(permutations((-offset , 0, 0))) +origins |= set(permutations((offset, 0, 0))) test_cases = product(estimators, origins) @@ -74,8 +76,9 @@ def label(p): if isinstance(p, str): return f'estimator:{p}' + @pytest.mark.parametrize('estimator,origin', test_cases, ids=label) -def test_offset_mesh(run_in_tmpdir, model, estimator, origin): +def test_offset_mesh(model, estimator, origin): """Tests that the mesh has been moved based on tally results """ mesh = model.tallies[0].filters[0].mesh @@ -103,6 +106,7 @@ def test_offset_mesh(run_in_tmpdir, model, estimator, origin): else: mean[i, j, k] != 0.0 + @pytest.fixture() def void_coincident_geom_model(): """A model with many geometric boundaries coincident with mesh boundaries @@ -155,6 +159,7 @@ def _check_void_cylindrical_tally(statepoint_filename): d_r = mesh.r_grid[1] - mesh.r_grid[0] assert neutron_flux == pytest.approx(d_r) + def test_void_geom_pnt_src(run_in_tmpdir, void_coincident_geom_model): src = openmc.IndependentSource() src.space = openmc.stats.Point() diff --git a/tests/unit_tests/test_spherical_mesh.py b/tests/unit_tests/test_spherical_mesh.py index 0b579be35f7..a95a4151a2e 100644 --- a/tests/unit_tests/test_spherical_mesh.py +++ b/tests/unit_tests/test_spherical_mesh.py @@ -63,8 +63,6 @@ def test_origin_read_write_to_xml(run_in_tmpdir, model): np.testing.assert_equal(new_mesh.origin, mesh.origin) estimators = ('tracklength', 'collision') -# TODO: determine why this is needed for spherical mesh -# but not cylindrical mesh offset = geom_size + 0.001 origins = set(permutations((-offset, 0, 0))) From 820648daeee2d3323ce2125f7ad67c4e23a8cd2e Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Wed, 23 Apr 2025 08:44:46 +0200 Subject: [PATCH 010/159] using reduce chain level to remove need for reduce chain (#3377) Co-authored-by: Jon Shimwell Co-authored-by: Paul Romano --- openmc/deplete/coupled_operator.py | 13 +++---------- openmc/deplete/independent_operator.py | 20 ++++---------------- openmc/deplete/openmc_operator.py | 16 +++++++--------- tests/unit_tests/test_model.py | 4 ++-- 4 files changed, 16 insertions(+), 37 deletions(-) diff --git a/openmc/deplete/coupled_operator.py b/openmc/deplete/coupled_operator.py index 17af935f4a2..7ecfd5a0893 100644 --- a/openmc/deplete/coupled_operator.py +++ b/openmc/deplete/coupled_operator.py @@ -154,15 +154,9 @@ class CoupledOperator(OpenMCOperator): options. .. versionadded:: 0.12.1 - reduce_chain : bool, optional - If True, use :meth:`openmc.deplete.Chain.reduce` to reduce the - depletion chain up to ``reduce_chain_level``. - - .. versionadded:: 0.12 reduce_chain_level : int, optional - Depth of the search when reducing the depletion chain. Only used - if ``reduce_chain`` evaluates to true. The default value of - ``None`` implies no limit on the depth. + Depth of the search when reducing the depletion chain. The default + value of ``None`` implies no limit on the depth. .. versionadded:: 0.12 diff_volume_method : str @@ -214,7 +208,7 @@ def __init__(self, model, chain_file=None, prev_results=None, normalization_mode="fission-q", fission_q=None, fission_yield_mode="constant", fission_yield_opts=None, reaction_rate_mode="direct", reaction_rate_opts=None, - reduce_chain=False, reduce_chain_level=None): + reduce_chain_level=None): # check for old call to constructor if isinstance(model, openmc.Geometry): @@ -270,7 +264,6 @@ def __init__(self, model, chain_file=None, prev_results=None, diff_volume_method=diff_volume_method, fission_q=fission_q, helper_kwargs=helper_kwargs, - reduce_chain=reduce_chain, reduce_chain_level=reduce_chain_level) def _differentiate_burnable_mats(self): diff --git a/openmc/deplete/independent_operator.py b/openmc/deplete/independent_operator.py index 333ead0f8a0..b3fe9a78fcc 100644 --- a/openmc/deplete/independent_operator.py +++ b/openmc/deplete/independent_operator.py @@ -66,13 +66,9 @@ class IndependentOperator(OpenMCOperator): Dictionary of nuclides and their fission Q values [eV]. If not given, values will be pulled from the ``chain_file``. Only applicable if ``"normalization_mode" == "fission-q"``. - reduce_chain : bool, optional - If True, use :meth:`openmc.deplete.Chain.reduce` to reduce the depletion - chain up to ``reduce_chain_level``. reduce_chain_level : int, optional - Depth of the search when reducing the depletion chain. Only used if - ``reduce_chain`` evaluates to true. The default value of ``None`` - implies no limit on the depth. + Depth of the search when reducing the depletion chain. The default + value of ``None`` implies no limit on the depth. fission_yield_opts : dict of str to option, optional Optional arguments to pass to the :class:`openmc.deplete.helpers.FissionYieldHelper` object. Will be @@ -119,7 +115,6 @@ def __init__(self, normalization_mode='fission-q', fission_q=None, prev_results=None, - reduce_chain=False, reduce_chain_level=None, fission_yield_opts=None): # Validate micro-xs parameters @@ -157,7 +152,6 @@ def __init__(self, prev_results=prev_results, fission_q=fission_q, helper_kwargs=helper_kwargs, - reduce_chain=reduce_chain, reduce_chain_level=reduce_chain_level) @classmethod @@ -170,7 +164,6 @@ def from_nuclides(cls, volume, nuclides, normalization_mode='fission-q', fission_q=None, prev_results=None, - reduce_chain=False, reduce_chain_level=None, fission_yield_opts=None): """ @@ -206,13 +199,9 @@ def from_nuclides(cls, volume, nuclides, applicable if ``"normalization_mode" == "fission-q"``. prev_results : Results, optional Results from a previous depletion calculation. - reduce_chain : bool, optional - If True, use :meth:`openmc.deplete.Chain.reduce` to reduce the - depletion chain up to ``reduce_chain_level``. Default is False. reduce_chain_level : int, optional - Depth of the search when reducing the depletion chain. Only used - if ``reduce_chain`` evaluates to true. The default value of - ``None`` implies no limit on the depth. + Depth of the search when reducing the depletion chain. The default + value of ``None`` implies no limit on the depth. fission_yield_opts : dict of str to option, optional Optional arguments to pass to the :class:`openmc.deplete.helpers.FissionYieldHelper` class. Will be @@ -232,7 +221,6 @@ def from_nuclides(cls, volume, nuclides, normalization_mode=normalization_mode, fission_q=fission_q, prev_results=prev_results, - reduce_chain=reduce_chain, reduce_chain_level=reduce_chain_level, fission_yield_opts=fission_yield_opts) diff --git a/openmc/deplete/openmc_operator.py b/openmc/deplete/openmc_operator.py index 64cb0a7e5c4..4ef8673728e 100644 --- a/openmc/deplete/openmc_operator.py +++ b/openmc/deplete/openmc_operator.py @@ -11,7 +11,7 @@ import numpy as np import openmc -from openmc.checkvalue import check_value +from openmc.checkvalue import check_value, check_type, check_greater_than from openmc.exceptions import DataError from openmc.mpi import comm from .abc import TransportOperator, OperatorResult @@ -49,13 +49,9 @@ class OpenMCOperator(TransportOperator): Dictionary of nuclides and their fission Q values [eV]. helper_kwargs : dict Keyword arguments for helper classes - reduce_chain : bool, optional - If True, use :meth:`openmc.deplete.Chain.reduce()` to reduce the - depletion chain up to ``reduce_chain_level``. reduce_chain_level : int, optional - Depth of the search when reducing the depletion chain. Only used - if ``reduce_chain`` evaluates to true. The default value of - ``None`` implies no limit on the depth. + Depth of the search when reducing the depletion chain. The default + value of ``None`` implies no limit on the depth. diff_volume_method : str Specifies how the volumes of the new materials should be found. Default @@ -107,7 +103,6 @@ def __init__( diff_volume_method='divide equally', fission_q=None, helper_kwargs=None, - reduce_chain=False, reduce_chain_level=None): # If chain file was not specified, try to get it from global config @@ -126,10 +121,13 @@ def __init__( check_value('diff volume method', diff_volume_method, {'divide equally', 'match cell'}) + if reduce_chain_level: + check_type('reduce_chain_level', reduce_chain_level, int) + check_greater_than('reduce_chain_level', reduce_chain_level, 0) self.diff_volume_method = diff_volume_method # Reduce the chain to only those nuclides present - if reduce_chain: + if reduce_chain_level is not None: init_nuclides = set() for material in self.materials: if not material.depletable: diff --git a/tests/unit_tests/test_model.py b/tests/unit_tests/test_model.py index ee5d8895ca3..aa844e2f21e 100644 --- a/tests/unit_tests/test_model.py +++ b/tests/unit_tests/test_model.py @@ -448,7 +448,7 @@ def test_deplete(run_in_tmpdir, pin_model_attributes, mpi_intracomm): # In this test we first run without pre-initializing the shared library # data and then compare. Then we repeat with the C API already initialized # and make sure we get the same answer - test_model.deplete([1e6], 'predictor', final_step=False, + test_model.deplete(timesteps=[1e6], method='predictor', final_step=False, operator_kwargs=op_kwargs, power=1., output=False) # Get the new Xe136 and U235 atom densities @@ -482,7 +482,7 @@ def check_tally_output(): # Now we can re-run with the pre-initialized API test_model.init_lib(output=False, intracomm=mpi_intracomm) - test_model.deplete([1e6], 'predictor', final_step=False, + test_model.deplete(timesteps=[1e6], method='predictor', final_step=False, operator_kwargs=op_kwargs, power=1., output=False) # Get the new Xe136 and U235 atom densities From c17908f24ccc9ae3ae47686cacd8faa78f879d9c Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 2 May 2025 18:38:10 -0500 Subject: [PATCH 011/159] Install MCPL using same build type as OpenMC in CI (#3388) --- CMakeLists.txt | 7 +++++++ cmake/OpenMCConfig.cmake.in | 2 +- tools/ci/gha-install-mcpl.sh | 7 ------- tools/ci/gha-install.sh | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) delete mode 100755 tools/ci/gha-install-mcpl.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 8cb03a1a1da..d7974620970 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -194,6 +194,13 @@ endif() #=============================================================================== if (OPENMC_USE_MCPL) + if (NOT DEFINED MCPL_DIR) + execute_process( + COMMAND mcpl-config --show cmakedir + OUTPUT_VARIABLE MCPL_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + endif() find_package(MCPL REQUIRED) message(STATUS "Found MCPL: ${MCPL_DIR} (found version \"${MCPL_VERSION}\")") endif() diff --git a/cmake/OpenMCConfig.cmake.in b/cmake/OpenMCConfig.cmake.in index 5cab4790abc..7416013fbc6 100644 --- a/cmake/OpenMCConfig.cmake.in +++ b/cmake/OpenMCConfig.cmake.in @@ -30,7 +30,7 @@ if(@OPENMC_USE_OPENMP@) endif() if(@OPENMC_USE_MCPL@) - find_package(MCPL REQUIRED) + find_package(MCPL REQUIRED HINTS @MCPL_DIR@) endif() if(@OPENMC_USE_UWUW@ AND NOT ${DAGMC_BUILD_UWUW}) diff --git a/tools/ci/gha-install-mcpl.sh b/tools/ci/gha-install-mcpl.sh deleted file mode 100755 index 9b8609398a0..00000000000 --- a/tools/ci/gha-install-mcpl.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -set -ex -cd $HOME -git clone https://github.com/mctools/mcpl -cd mcpl -mkdir build && cd build -cmake .. && make 2>/dev/null && sudo make install diff --git a/tools/ci/gha-install.sh b/tools/ci/gha-install.sh index d8a3a7600e4..74c3947f183 100755 --- a/tools/ci/gha-install.sh +++ b/tools/ci/gha-install.sh @@ -29,7 +29,7 @@ if [[ $LIBMESH = 'y' ]]; then fi # Install MCPL -./tools/ci/gha-install-mcpl.sh +pip install mcpl # For MPI configurations, make sure mpi4py and h5py are built against the # correct version of MPI From 4138bc34e08777d2a0c7c91748c00aacf048e136 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 2 May 2025 19:07:43 -0500 Subject: [PATCH 012/159] Add option to determine waste disposal rating by nuclide (#3376) --- openmc/material.py | 22 ++++++++++------ openmc/waste.py | 25 ++++++++++++------- tests/unit_tests/test_waste_classification.py | 4 +++ 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/openmc/material.py b/openmc/material.py index 9677fbba32a..770722f748a 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -1348,10 +1348,11 @@ def waste_classification(self, metal: bool = False) -> str: return waste._waste_classification(self, metal=metal) def waste_disposal_rating( - self, - limits: str | dict[str, float] = 'Fetter', - metal: bool = False, - ) -> float: + self, + limits: str | dict[str, float] = 'Fetter', + metal: bool = False, + by_nuclide: bool = False, + ) -> float | dict[str, float]: """Return the waste disposal rating for the material. This method returns a waste disposal rating for the material based on a @@ -1387,18 +1388,25 @@ def waste_disposal_rating( metal : bool, optional Whether or not the material is in metal form (only applicable for NRC based limits) + by_nuclide : bool, optional + Whether to return the waste disposal rating for each nuclide in the + material. If True, a dictionary is returned where the keys are the + nuclide names and the values are the waste disposal ratings for each + nuclide. If False, a single float value is returned that represents + the overall waste disposal rating for the material. Returns ------- - float - The waste disposal rating for the material. + float or dict + The waste disposal rating for the material or its constituent + nuclides. See also -------- Material.waste_classification() """ - return waste._waste_disposal_rating(self, limits, metal) + return waste._waste_disposal_rating(self, limits, metal, by_nuclide) def clone(self, memo: dict | None = None) -> Material: """Create a copy of this material with a new unique ID. diff --git a/openmc/waste.py b/openmc/waste.py index 7dd45b8e3be..80cfc0adcba 100644 --- a/openmc/waste.py +++ b/openmc/waste.py @@ -77,10 +77,11 @@ def classify_table2(col1, col2, col3): def _waste_disposal_rating( - mat: openmc.Material, - limits: str | dict[str, float] = 'Fetter', - metal: bool = False, - ) -> float: + mat: openmc.Material, + limits: str | dict[str, float] = 'Fetter', + metal: bool = False, + by_nuclide: bool = False, +) -> float | dict[str, float]: """Return the waste disposal rating for a material. This method returns a waste disposal rating for the material based on a set @@ -111,11 +112,17 @@ def _waste_disposal_rating( metal : bool, optional Whether or not the material is in metal form (only applicable for NRC based limits) + by_nuclide : bool, optional + Whether to return the waste disposal rating for each nuclide in the + material. If True, a dictionary is returned where the keys are the + nuclide names and the values are the waste disposal ratings for each + nuclide. If False, a single float value is returned that represents the + overall waste disposal rating for the material. Returns ------- - float - The waste disposal rating for the material. + float or dict + The waste disposal rating for the material or its constituent nuclides. """ if limits == 'Fetter': @@ -272,8 +279,8 @@ def _waste_disposal_rating( # Calculate the sum of the fractions of the activity of each radionuclide # compared to the specified limits - ratio = 0.0 + ratio = {} for nuc, ci_m3 in mat.get_activity(units="Ci/m3", by_nuclide=True).items(): if nuc in limits: - ratio += ci_m3 / limits[nuc] - return ratio + ratio[nuc] = ci_m3 / limits[nuc] + return ratio if by_nuclide else sum(ratio.values()) diff --git a/tests/unit_tests/test_waste_classification.py b/tests/unit_tests/test_waste_classification.py index dd692cc4b35..072590df965 100644 --- a/tests/unit_tests/test_waste_classification.py +++ b/tests/unit_tests/test_waste_classification.py @@ -96,3 +96,7 @@ def test_waste_disposal_rating(): ci_m3 = mat.get_activity('Ci/m3') assert mat.waste_disposal_rating(limits={'K40': 2*ci_m3}) < 1.0 assert mat.waste_disposal_rating(limits={'K40': 0.5*ci_m3}) > 1.0 + + wdr = mat.waste_disposal_rating(limits={'K40': 4*ci_m3}, by_nuclide=True) + assert isinstance(wdr, dict) + assert wdr['K40'] == pytest.approx(1/4) From d0354dbd71531d9c6c88d2c613d571a270453e71 Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Sat, 3 May 2025 06:38:23 +0200 Subject: [PATCH 013/159] corrected tally name in D1S example (#3383) Co-authored-by: Paul Romano --- docs/source/usersguide/decay_sources.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/usersguide/decay_sources.rst b/docs/source/usersguide/decay_sources.rst index a228f8e66bb..d5a078135ba 100644 --- a/docs/source/usersguide/decay_sources.rst +++ b/docs/source/usersguide/decay_sources.rst @@ -88,5 +88,5 @@ relevant tallies. This can be done with the aid of the dose_tally = sp.get_tally(name='dose tally') # Apply time correction factors - tally = d1s.apply_time_correction(tally, factors, time_index) + tally = d1s.apply_time_correction(dose_tally, factors, time_index) From a921280fa9c3f63eb10132ad37bacd612ab82157 Mon Sep 17 00:00:00 2001 From: Hunter Belanger Date: Sat, 3 May 2025 05:10:18 +0000 Subject: [PATCH 014/159] Fix extremely large yields from Bremsstrahlung (#3386) Co-authored-by: Paul Romano --- src/material.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/material.cpp b/src/material.cpp index a1937aca459..2bd504e4dbf 100644 --- a/src/material.cpp +++ b/src/material.cpp @@ -640,7 +640,7 @@ void Material::init_bremsstrahlung() // Allocate arrays for TTB data ttb->pdf = xt::zeros({n_e, n_e}); ttb->cdf = xt::zeros({n_e, n_e}); - ttb->yield = xt::empty({n_e}); + ttb->yield = xt::zeros({n_e}); // Allocate temporary arrays xt::xtensor stopping_power_collision({n_e}, 0.0); From 512df2f4ffb4ad529af2f69b614685ab1ad4d372 Mon Sep 17 00:00:00 2001 From: Amanda Lund Date: Sat, 3 May 2025 00:46:14 -0500 Subject: [PATCH 015/159] Skip atomic relaxation if binding energy is larger than photon energy (#3391) Co-authored-by: Paul Romano --- src/photon.cpp | 5 +- .../results_true.dat | 58 +++++++++---------- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/src/photon.cpp b/src/photon.cpp index 4b2fb41978f..dae87ea4e25 100644 --- a/src/photon.cpp +++ b/src/photon.cpp @@ -770,8 +770,9 @@ void PhotonInteraction::pair_production(double alpha, double* E_electron, void PhotonInteraction::atomic_relaxation(int i_shell, Particle& p) const { - // Return if no atomic relaxation data is present - if (!has_atomic_relaxation_) + // Return if no atomic relaxation data is present or if the binding energy is + // larger than the incident particle energy + if (!has_atomic_relaxation_ || shells_[i_shell].binding_energy > p.E()) return; // Stack for unprocessed holes left by transitioning electrons diff --git a/tests/regression_tests/photon_production_fission/results_true.dat b/tests/regression_tests/photon_production_fission/results_true.dat index 1aa08b1476e..7e20342af51 100644 --- a/tests/regression_tests/photon_production_fission/results_true.dat +++ b/tests/regression_tests/photon_production_fission/results_true.dat @@ -1,12 +1,12 @@ k-combined: -2.272421E+00 4.418825E-02 +2.270911E+00 4.568134E-02 tally 1: -2.665301E+00 -2.369839E+00 +2.663476E+00 +2.366726E+00 0.000000E+00 0.000000E+00 -2.665301E+00 -2.369839E+00 +2.663476E+00 +2.366726E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -18,52 +18,52 @@ tally 1: 0.000000E+00 0.000000E+00 tally 2: -2.637532E+00 -2.320051E+00 -4.231377E+08 -5.971498E+16 +2.636709E+00 +2.318646E+00 +4.230052E+08 +5.967879E+16 0.000000E+00 0.000000E+00 -2.637532E+00 -2.320051E+00 -4.231377E+08 -5.971498E+16 +2.636709E+00 +2.318646E+00 +4.230052E+08 +5.967879E+16 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.448745E+06 -2.005297E+12 +2.455009E+06 +2.015314E+12 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.448745E+06 -2.005297E+12 +2.455009E+06 +2.015314E+12 0.000000E+00 0.000000E+00 tally 3: -2.660000E+00 -2.358558E+00 -4.231377E+08 -5.971498E+16 +2.660004E+00 +2.358564E+00 +4.230052E+08 +5.967879E+16 0.000000E+00 0.000000E+00 -2.660000E+00 -2.358558E+00 -4.231377E+08 -5.971498E+16 +2.660004E+00 +2.358564E+00 +4.230052E+08 +5.967879E+16 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.448745E+06 -2.005297E+12 +2.455009E+06 +2.015314E+12 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.448745E+06 -2.005297E+12 +2.455009E+06 +2.015314E+12 0.000000E+00 0.000000E+00 From 1e7d8324eefaa6b0995f16a40b99416fdf3cf179 Mon Sep 17 00:00:00 2001 From: Gregoire Biot Date: Sat, 3 May 2025 08:19:10 -0400 Subject: [PATCH 016/159] Figure of Merit implementation (#3363) Co-authored-by: Paul Romano --- docs/source/methods/tallies.rst | 27 +++++++++++++++++++++++++++ openmc/tallies.py | 18 ++++++++++++++++++ tests/unit_tests/test_tallies.py | 18 +++++++++++++++++- 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/docs/source/methods/tallies.rst b/docs/source/methods/tallies.rst index 57e05d84f85..79a63fbdd83 100644 --- a/docs/source/methods/tallies.rst +++ b/docs/source/methods/tallies.rst @@ -387,6 +387,33 @@ of this is that the longer you run a simulation, the better you know your results. Therefore, by running a simulation long enough, it is possible to reduce the stochastic uncertainty to arbitrarily low levels. +Figure of Merit ++++++++++++++++ + +The figure of merit (FOM) is an indicator that accounts for both the statistical +uncertainty and the execution time and represents how much information is +obtained per unit time in the simulation. The FOM is defined as + +.. math:: + :label: figure_of_merit + + FOM = \frac{1}{r^2 t}, + +where :math:`t` is the total execution time and :math:`r` is the relative error +defined as + +.. math:: + :label: relative_error + + r = \frac{s_\bar{X}}{\bar{x}}. + +Based on this definition, one can see that a higher FOM is desirable. The FOM is +useful as a comparative tool. For example, if a variance reduction technique is +being applied to a simulation, the FOM with variance reduction can be compared +to the FOM without variance reduction to ascertain whether the reduction in +variance outweighs the potential increase in execution time (e.g., due to +particle splitting). + Confidence Intervals ++++++++++++++++++++ diff --git a/openmc/tallies.py b/openmc/tallies.py index ebb313551ab..e990a95f742 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -95,6 +95,10 @@ class Tally(IDManagerMixin): An array containing the sample mean for each bin std_dev : numpy.ndarray An array containing the sample standard deviation for each bin + figure_of_merit : numpy.ndarray + An array containing the figure of merit for each bin + + .. versionadded:: 0.15.3 derived : bool Whether or not the tally is derived from one or more other tallies sparse : bool @@ -127,6 +131,7 @@ def __init__(self, tally_id=None, name=''): self._sum_sq = None self._mean = None self._std_dev = None + self._simulation_time = None self._with_batch_statistics = False self._derived = False self._sparse = False @@ -385,6 +390,9 @@ def _read_results(self): self._sum = sps.lil_matrix(self._sum.flatten(), self._sum.shape) self._sum_sq = sps.lil_matrix(self._sum_sq.flatten(), self._sum_sq.shape) + # Read simulation time (needed for figure of merit) + self._simulation_time = f["runtime"]["simulation"][()] + # Indicate that Tally results have been read self._results_read = True @@ -462,6 +470,16 @@ def std_dev(self): else: return self._std_dev + @property + def figure_of_merit(self): + mean = self.mean + std_dev = self.std_dev + fom = np.zeros_like(mean) + nonzero = np.abs(mean) > 0 + fom[nonzero] = 1.0 / ( + (std_dev[nonzero] / mean[nonzero])**2 * self._simulation_time) + return fom + @property def with_batch_statistics(self): return self._with_batch_statistics diff --git a/tests/unit_tests/test_tallies.py b/tests/unit_tests/test_tallies.py index ccc6ff34383..c38f067d58c 100644 --- a/tests/unit_tests/test_tallies.py +++ b/tests/unit_tests/test_tallies.py @@ -1,5 +1,5 @@ import numpy as np - +import pytest import openmc @@ -96,6 +96,22 @@ def test_tally_equivalence(): assert tally_a == tally_b +def test_figure_of_merit(sphere_model, run_in_tmpdir): + # Run model with a few simple tally scores + tally = openmc.Tally() + tally.scores = ['total', 'absorption', 'scatter'] + sphere_model.tallies = [tally] + sp_path = sphere_model.run(apply_tally_results=True) + + # Get execution time and relative error + with openmc.StatePoint(sp_path) as sp: + time = sp.runtime['simulation'] + rel_err = tally.std_dev / tally.mean + + # Check that figure of merit is calculated correctly + assert tally.figure_of_merit == pytest.approx(1 / (rel_err**2 * time)) + + def test_tally_application(sphere_model, run_in_tmpdir): # Create a tally with most possible gizmos tally = openmc.Tally(name='test tally') From 57dc71f5303a53fc74794384d2a211b1fab57810 Mon Sep 17 00:00:00 2001 From: Amanda Lund Date: Mon, 5 May 2025 13:05:48 -0500 Subject: [PATCH 017/159] Map Compton subshell data to atomic relaxation data (#3392) Co-authored-by: Paul Romano --- include/openmc/photon.h | 6 ++- src/photon.cpp | 30 ++++++++--- src/physics.cpp | 10 ++-- .../photon_production/results_true.dat | 52 +++++++++---------- .../weightwindows/results_true.dat | 2 +- 5 files changed, 59 insertions(+), 41 deletions(-) diff --git a/include/openmc/photon.h b/include/openmc/photon.h index 1fee6c9f5b6..f6f28a4df1d 100644 --- a/include/openmc/photon.h +++ b/include/openmc/photon.h @@ -33,7 +33,6 @@ class ElectronSubshell { int index_subshell; //!< index in SUBSHELLS int threshold; - double n_electrons; double binding_energy; vector transitions; }; @@ -90,6 +89,11 @@ class PhotonInteraction { xt::xtensor binding_energy_; xt::xtensor electron_pdf_; + // Map subshells from Compton profile data obtained from Biggs et al, + // "Hartree-Fock Compton profiles for the elements" to ENDF/B atomic + // relaxation data + xt::xtensor subshell_map_; + // Stopping power data double I_; // mean excitation energy xt::xtensor n_electrons_; diff --git a/src/photon.cpp b/src/photon.cpp index dae87ea4e25..4926e3eae6a 100644 --- a/src/photon.cpp +++ b/src/photon.cpp @@ -165,7 +165,6 @@ PhotonInteraction::PhotonInteraction(hid_t group) if (attribute_exists(tgroup, "binding_energy")) { has_atomic_relaxation_ = true; read_attribute(tgroup, "binding_energy", shell.binding_energy); - read_attribute(tgroup, "num_electrons", shell.n_electrons); } // Read subshell cross section @@ -233,6 +232,28 @@ PhotonInteraction::PhotonInteraction(hid_t group) } close_group(rgroup); + // Map Compton subshell data to atomic relaxation data by finding the + // subshell with the equivalent binding energy + if (has_atomic_relaxation_) { + auto is_close = [](double a, double b) { + return std::abs(a - b) / a < FP_REL_PRECISION; + }; + subshell_map_ = xt::full_like(binding_energy_, -1); + for (int i = 0; i < binding_energy_.size(); ++i) { + double E_b = binding_energy_[i]; + if (i < n_shell && is_close(E_b, shells_[i].binding_energy)) { + subshell_map_[i] = i; + } else { + for (int j = 0; j < n_shell; ++j) { + if (is_close(E_b, shells_[j].binding_energy)) { + subshell_map_[i] = j; + break; + } + } + } + } + } + // Create Compton profile CDF auto n_profile = data::compton_profile_pz.size(); auto n_shell_compton = profile_pdf_.shape(0); @@ -423,13 +444,6 @@ void PhotonInteraction::compton_scatter(double alpha, bool doppler, double E_out; this->compton_doppler(alpha, *mu, &E_out, i_shell, seed); *alpha_out = E_out / MASS_ELECTRON_EV; - - // It's possible for the Compton profile data to have more shells than - // there are in the ENDF data. Make sure the shell index doesn't end up - // out of bounds. - if (*i_shell >= shells_.size()) { - *i_shell = -1; - } } else { *i_shell = -1; } diff --git a/src/physics.cpp b/src/physics.cpp index a8e5b9e8135..3c06e543de1 100644 --- a/src/physics.cpp +++ b/src/physics.cpp @@ -340,11 +340,11 @@ void sample_photon_reaction(Particle& p) p.create_secondary(p.wgt(), u, E_electron, ParticleType::electron); } - // TODO: Compton subshell data does not match atomic relaxation data - // Allow electrons to fill orbital and produce auger electrons - // and fluorescent photons - if (i_shell >= 0) { - element.atomic_relaxation(i_shell, 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) { + element.atomic_relaxation(element.subshell_map_[i_shell], p); } phi += PI; diff --git a/tests/regression_tests/photon_production/results_true.dat b/tests/regression_tests/photon_production/results_true.dat index 398deeed8b0..a911fed3b8d 100644 --- a/tests/regression_tests/photon_production/results_true.dat +++ b/tests/regression_tests/photon_production/results_true.dat @@ -1,8 +1,8 @@ tally 1: 8.610000E-01 7.413210E-01 -9.493000E-01 -9.011705E-01 +9.491000E-01 +9.007908E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -16,12 +16,12 @@ tally 2: 1.573004E+00 4.296434E-04 1.845934E-07 -2.337049E-01 -5.461796E-02 +2.350021E-01 +5.522600E-02 0.000000E+00 0.000000E+00 -2.337049E-01 -5.461796E-02 +2.350021E-01 +5.522600E-02 0.000000E+00 0.000000E+00 0.000000E+00 @@ -53,16 +53,16 @@ tally 3: 4.104374E+12 4.296582E-04 1.846062E-07 -2.286000E-01 -5.225796E-02 -7.054033E+03 -4.975938E+07 +2.297000E-01 +5.276209E-02 +7.055982E+03 +4.978688E+07 0.000000E+00 0.000000E+00 -2.286000E-01 -5.225796E-02 -7.054033E+03 -4.975938E+07 +2.297000E-01 +5.276209E-02 +7.055982E+03 +4.978688E+07 0.000000E+00 0.000000E+00 0.000000E+00 @@ -73,8 +73,8 @@ tally 3: 0.000000E+00 0.000000E+00 0.000000E+00 -1.764573E+05 -3.113718E+10 +1.774451E+05 +3.148675E+10 0.000000E+00 0.000000E+00 0.000000E+00 @@ -102,16 +102,16 @@ tally 4: 4.104374E+12 0.000000E+00 0.000000E+00 -2.286000E-01 -5.225796E-02 -7.054033E+03 -4.975938E+07 +2.297000E-01 +5.276209E-02 +7.055982E+03 +4.978688E+07 0.000000E+00 0.000000E+00 -2.286000E-01 -5.225796E-02 -7.054033E+03 -4.975938E+07 +2.297000E-01 +5.276209E-02 +7.055982E+03 +4.978688E+07 0.000000E+00 0.000000E+00 0.000000E+00 @@ -122,8 +122,8 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -1.764573E+05 -3.113718E+10 +1.774451E+05 +3.148675E+10 0.000000E+00 0.000000E+00 0.000000E+00 diff --git a/tests/regression_tests/weightwindows/results_true.dat b/tests/regression_tests/weightwindows/results_true.dat index 6c05af31aa3..6bb992cd488 100644 --- a/tests/regression_tests/weightwindows/results_true.dat +++ b/tests/regression_tests/weightwindows/results_true.dat @@ -1 +1 @@ -ebc761815175b25fc95a226174928c226a3ab5dbf3b2a2abf09e079a0b87dee1dee74aa9b9eaec35acd58b8c481d264be7e9b3f052905bcc14ccd76f36b01549 \ No newline at end of file +952c20fefac374d3b9fd28627fda7d5ae262f8cd7c01a33f0381526204a2287c18e56259a599dc6fdc1b59a2d2866fdfeedea371154c8fa7a69dc5c445b08c2d \ No newline at end of file From 9942269a910cf2f02bd028656be9f3744e1bc7dd Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 5 May 2025 12:55:14 -0600 Subject: [PATCH 018/159] Updates to VTK data checks (#3371) Co-authored-by: Paul Romano --- docs/source/_images/sphere-mesh-vtk.png | Bin 0 -> 73644 bytes docs/source/pythonapi/base.rst | 19 ++- docs/source/usersguide/processing.rst | 79 +++++++++++ openmc/mesh.py | 124 +++++++++++++----- tests/unit_tests/mesh_to_vtk/test.py | 6 +- tests/unit_tests/mesh_to_vtk/test_vtk_dims.py | 2 +- tests/unit_tests/test_mesh.py | 3 + 7 files changed, 195 insertions(+), 38 deletions(-) create mode 100644 docs/source/_images/sphere-mesh-vtk.png diff --git a/docs/source/_images/sphere-mesh-vtk.png b/docs/source/_images/sphere-mesh-vtk.png new file mode 100644 index 0000000000000000000000000000000000000000..73b412bdd3779cff6573d81642b91e083fd7c784 GIT binary patch literal 73644 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

h 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

Xvgr!z$Pq0D}n~Bl-=;bz?MVH z)}T!QAdGS4=j!i#v<~PAMLzz<(D`ROiv9HP`I3Jt66nwxvr9N!R3#bUqQA4r^jbl+ z#rcx|`dIs}R-pj2`%>a>Xa6!&tWA@~o~KLWZyVRGwK1#kiQ7IceNmP~y);Y|(cinx z%2e#`w0C2BOXxgBuFKL~Fsd9}Ii=z((YDXRrs!j19^vcCC`uTKgCP@||7oFX&(RS} z2TF87S0C01<+fwf2faTiDh@Tq)sgo*Ja}e5c)kq~54iIMI@>=+i(WB4 zpLbke3lHy9_jXoEQmkI+#qfc%KNVY`9*+4nYu%+Tc-^{nP7W@-oN`dP;L)15=F)uj zZ)!z0d`)rKo>~U-v^oVYcE%0pA~d;9`%&!3dnf*zUkCXNk| zwHnx2*TM(ClYiju_!RQzO=;~jVK|ocx1~l(BAcV9nFxG&Rb1#zK@u`|(~o2-BB9g@KlV+0d80YaOE7AB3ry zRx$h`NHb@~O_4X#)5PZ;_JF1(7Knpjvjv5s0>(dp|s8}Yu?nBcMrWaQxCfd?Bq7{ zjTvWq1PG(H(J8opjUW(N-9ACfF1jkO(h7#4433W_hMx(AML6yvJZ1@wpA7?Y@(oQV9pn17g;^3>`wz#L?r|mtI}&uEW_9zmJUGeQ{q}dA^FHsm5qRi zq+PKD&w_QKRh&#=w^$ylfK(DiABqHbTsGy#k)C?DQbpK>4BI9mn$l~le<8Ve*!BHrvQTZb8trv!lRNhhpeoM1z zuinpQES@Ym%ja_cG+MSQM9~Ii-FOnTjuUTmA1g?^*%|jwQW9VNZk{B3y_?49P1@gX zz?uWC_k8gqtMJN}X1c?H$L(9Z+f9sNU9hZm-RKF|4)`$=+=6G}%hh9LeBrRuJOT9G zfGaI#3F&Q5-{Q_C}>^GQN9`M&T!4$_r{=CjH>UjF!6 zOYds5um+t5ph%q)wy>X+Uw-&LZ|j^8f+YEQO{|{$A`E>3c;o*3TomMJ7$FR}FnXHV z>X$PhfRpE}>rn)m4F5C`^=G-$rVS&gv94l!+1}xT{kcJ^DlaUyauV@74a9dk2{j3z zFquF?(@WU=)i=nsazx4p#JIDme=5YP-#PMi?p%1^Zvj1_9MJ=WMh%do9(P_Yojs5d z$2i($1RA4OH!vLw=^2PYGzKEr&UeYUwI~VZ1lGipb6ZwlDXP$y91_SlqCS;y(rn-Z zIER*jClz#rIYOdoz)+cdbDu`BV+IcP{PxW088%08OD&I#TuC~-1An}K?kFN@^KmY1 z0v(aPFyH5&gY0aQx;)0(n{j)U7Sr zB|fm#q!5;2EQ{B>D;K5R?hmU3S)QKXyk`&CW+~@IQLz6mUhj9;IEjp<;qerU8vPl z0$y>)>_{gHU(Q!~YMs~<(GD|s?LB>eHqxQcBE*)5g%DKXBE5b2e#dIiL5T{9tr49g z5jPT1u;5hA-kUD6$ClcZ_nOJVvT*xBXAI_@5O2f5d5nnAC_(v^W?2{ujDcf*lu)*C z!-iun80DE~G0B;4o)RV!1>-YU z9A}iw>-+Y`;Rq&OUKNTOL4ccNH)RRb1*eP-82WpL@PT=yg>%pUGMt6{iRrA|3T3S8 z$Dhm5_`p)*#?bxH!dVj{&TaK&_obblO>_BD+3G9dvzoZCTl-2YZR!?Rk_S4N_)1@Z zr7i8%+H+9LD54QXtb2ryVx-@CLu^Ros=g}$);ANt&fmGpez7HiR7Tt!cD!;8wY`o* z=e(|ml+*_~06t(tqw|8}xMPQPVTpdvt&!zA-xhxbyd&r%wbO-+ScCeG*Hl+8el={$ zhBkV#3Iqij?M3N^NC!m^NTpV)sDFr*QQyl9yQbT})U9u6{i;(2iR>gVYVAk+dgibf z$R8c8Gd~icsN4iP4WYSmWNu@yUp`^Jd6#J6atd`k(<{kc)-c(cg9(fxb@J{ML5Do^ zub$_0{#bBw68r~zz#d$|o#O>tB4eG2m&j)LS^F#0ze`;214Py5X>;7GE*?>USR#Hk zW=>kbd0TlnuHy1EbJ@d1eq%P}eeHWn9%p`qF(InlZsqiLsC|9?_ZZwvca@cI!K+^Xo9R09M0}CR6~ikhlFT^?;mYl z1kwRW9d+%9t1_R?ZQS>fN9m`g>-);tF@XR?SB?s;S@RS5J-*)ys$Z^W>ycG`6=|?{ z-`754-imek&1WgFpCUTXbAB))PR?zDKz`dSOSd;wT1^IIypMLb7oG(av6?xX;*{+v zMVv&Y+ZF^`34a@STl2^!2j>eWtkkV&R665v2P+kj4@vn7tu%ROw_;2Etr15fOX3H= ziAEhf_aY5~ucehPTsjC5yx%mQLH`}%HkV`=e|pTVxSaWT+uk$e5&{IRfJG#4W2i%T zn6>?9Uqdf$|A|MYA60GGl5VnWw4dV-c7Dd*#;VQUsrS67IUdXF)ZhyK8p){~ zVrf@9GT;> zPt<>IK&i*v@0Jgoo}P{heLS?6om+YFnL#qJ>6Z#lWH9YP2rXn}pRvZP_tK`F;(^0Lqfx@a>d3!zEnKg`H5Q^Ntny0A{0*WbWz`~ST9VzS z)PHEs7VZA>)ByXfK8kqrlW~@Wv6-*T=2*r1Qeq2h2FVsui7FJNz@Iiej@y-0p*15f z!pJ{O1p)ET#%N9)Hd@y}^H>MPJiS0kgTq%4zGP#Q#0WWPg>-pDVX2*%;eE(P(a;Fc zQ0>;Cd?-5hK(9p`1W+%d1vzT``j^J=w+gt;P7zOLzc$5tD;K5k8JtV!MK2M7&?oQ{l!ZQ*VhCD z+TIrvabMvbJAYXO7qa|K_sZnq-nQz#?wsA&NDYcFk`x%w4Hd?56j-&&9uE-FuFo=4 z-wEm*GXCo(=T&O~SJvo^-ipt?dT$jtsT>|kBK{9uEmwQ3(+IXiLu%lTT~%}(rCb4p zl6KeY`U4S+mf!rwM43vDo)TzrFPz!YlzjM9UPcS4w?xUI9EzZ zA*4&*Ta?S)=(5!`kM%_FGZ8F^Co2wZQL4|o#pry^2NS@@mTgHJJ(^ITZZD;tU^`Gb zZ7eUaUj`wC$otgUD(*VJR=rEUTE9Dq3*6K9InPVxFOdBh%{_yVzZ_cNwUGX7CZkGa z78pE1c=?SsjU1yK6(Gq_b;ox2`NF+Ne`SE3#|goR7KJ$@e0hKaiZTa)f*G4vJs z#+qr9@z=e}vpf$lCWyA>bb)L13iWq+j4$m3!V6J1T{uCVJn~ZbK$^u=|Gy7QZBr9L zo?6)PnbEeh6pd$@Y{~B@n4FLYttC}Np|G_wSxIR}tVRz$R50Ku7Ups`T%+b%UOgze zWHgcAQtt<%fZ3c<$DeQD&ry{h9KQvpjweJx4ctwlKFEAEE{_mTN;(&ut1K*?0lU~` zPWJ#;+}Ver!hxjaylzsuXk|U!&6j@WHp{&>NTa*CD$;Y=I5@aD_niJ@`ay~S z)0?!8kY7VI_T)Nm0^Rq3I8N~*Q|r3nJa>0W+5lBCR*J=!iSoQwPW%#V7O1)}_7 zMxFUh%U94riw}FT{K@alSlHp?zWRGA8!B%+C2M(WOAZNItNJC&C01kcL`mkmr~diF zdPlZk${Xt`g@*-nxHKw~mm_y?oY+%Y`Q@(Lk=m-&?M2>1TZe`)@dQX~c@9T0TFdn^ zWrVqV5PgK4OIKeJ_$as}Od139SaiV&95YQP2BI2Vun7K2sksi+XDeme%+yj-!whPG zNBQyNSP@I8!K)FC8t|!9@9Sl&hI(*)=1L!x`P>BD&ezq%l=mJoxqM9ymW3K)b5803 z%w@*+$Jl9N6+}>lm3j>tJ*z$bYwAxmxKv|if(Dk<@H(8KI7!(eDMx-OoRqOPBTCeN z`1-r)E$RMVp`>sh=y7|e6yr5}Q9rAI`Y0T=^osptq+FT4Bkk_Td$wAm_=Il3DTYSf z4%}-${kgN2@+AlJVEn@vgZ-3kPwfdLC*z1CT))7ZAJ>POR&Fk;EIIgPO&qDE9|I~! zM$0%{7RGq(7n+tua>srZR4%=nKkav!|Jm#~C(}}{Y2={}FL!6Y)XoNrvMjpZwB5=k zI+(p5MMm0BOP|9{y*?xp6MY&O@ZaQ;FtOV2Nx|v#ymb~$HQ6CP+38n4-)Pf4u`dys zpE*Jr|6F$QsCnI>Tg!G#fTfO~WpZoYuhn?&jI$vWO&i4u&)B?4SCYtUVR@j*W(H67 z=mciiPR?dQjg^Mng6Q6@&kK|=9sN!c;wf-GSblH)!2>E)L>478g~WNldCs%a$e2Eo z!{U1HlYCJ+l8%1FH|caWDMsW#e{3hkU4n_4Or(=>o^S30#OMEd^YM10V?GnJC^Z@^#{lfY zo_7IU*}W5UFnfg~Fuw6xK@1=Ph4_H1z`SIF#O&Df3hvV^eka^fJ9S}YjddqbD_=f< zi=ozwt2=^gD{N+Ivs~V)fAqir)=SI!6A2p|9f^M z_^711>UqW!ws>hXxMufPTXxx(vxxq!#R54T={*tLw9Tq5NoVc44RzSgT!)Q&Y~0pj zrCzh#o3;1cz?iR`eCVQ%H?VW z`~Hp<;v>|Lp;oIpd@nYx@&QU4>A*6ab^9Cg4Kn?Iw9W8vT-{6IgsgR!OjyOSbgSj< zMbFBqVEV>ug_U7AYRSFH%4{r15M%i$NYQMhk*!@$nFX(!WvkAM!3&`?Y?! zJMy`XsSctl|AO)s@-r9rquV`Veg=GWv#o2FDM7D>?{CBQ0M>c0Pv95?gw9Ctn> z6|1|Z8ANNrC^4S^@$_!!oR9&B*HrePTn@R&P^4n7X(Y9}`%w!zUUA$=B`Kx3Yby#R z6Y3uxXC~#CU#SnMdaM*|e;h*vpP%;+5Q4ZL=fU_7|45gMg3iL1?V_AxapX@9OA?9hjb|J| z;#a+#DB@8zhUSh`;@oNreEu4X*b{gOzxq20?G>=Cl#8Kk+3VWj4`Dq$(N^qLx@h+V ze$|=aIe4V&gQhi+U`4H$6O_>0OrI|ML~Murtv;J|(l zHBP2YN#!giUt>dD9Jb2Nq~EnKfjp5(o1+T6tqbmRfum4%z{*E#uE;K4;lhqw%J=p9QQ!M^g&D`2>w^h(#est2gzWm|xRo z8<8e+uT5g~hHXF=8gs7>9YUM0w6pd$E zSP>z_^bS|~tNnlty?kf_#x)Nrt?o^~MPS%ub*pX6o$bk9wCyeP{fVdMcf|AZ&2N`c0}cfO1xd3vI|#UG-e*i$`U0X?oUI8!_RjRaXnM{$%Tta9 z$ok{kZ3}!A#C5$DlnDeI$?r1gt81(}UqZ6f{L#UpG}tjDie&a=y)TeeV3B4hQtr%YF zBa6PfQn}pZrB04Y++k?6eB%MX^L;SzPH^Fn<#uvQhHS)VOwR94(w8qXWqbHPXiqC& zl~;wd_|q)TY_rW{EZxCIEa@ttnX94J^n*LJ(tU2Cx9P0b2qqu-t|iuRHtStekf61X zG8IQD?p2FSV;*WbYZ%iJQbb%mRIF*Lv2hpCx%>JvHa$JXe%T+Lok@iUO!!k=G!q>d zhh*)PX2r>!Z_;k4-?IRrEASV#4<-2A*t_&nby;NBU)+J`d&3gI|J)fQ;c;S*tu$@# zIsYg;pCzJpR0d!S)KctfPvG?*lz14Hh|+$2UM7JGFB=t71(3i#!s4ked#XtuJ0ekg zEAmN{0f00K7E@0&3m{m{7+`Y-QDbJdKGwNLTHhDqm*P0=56-FVIN(bl1#3@9j`^So zB#g8gj_TNH1#$~IvM5j&2>M#v-Qj?swPOqv2V>$5%HhBsZmq}9dqeHZU%*LnHmwm! zQw=iW6_4ETl_NFxUCkTck27t(tV!uLkz@`qJem`a>}5(Mt*t97v^lz=Od-N zd;#siqcmIu-x@t{X8o8QD5O&-FA61|7}b3<9JH_@uL2M*eY=OJE>qxdrbvG^yNr|b z-u&hM?=$AlQgKdOA}xGX{I);l#MKS9e2~w1y-V%gy_jy+{#MQmyUZ)2RtU&pmUar1 z=4P<F}?Kn zYrSGD23_#tKmM_sZtPC{gOVCL`1Anrv9fXpUoE1nB{0%#6og?c>#yfC4>!GRKb223 zKd0Mf=N7dB5sw*M%XiL>IsZd{x}g<*#pV;=k9CC?>U(OEBK=ocwD!_Z>@z-5u{1hh zAqNvA6i>Jv*5;w&c?LObOFEfge%vLE1h%t!tHQIYFwA3Ir|xS zhI&`OF>Y!Hu}8L@KjL?Q=-n-pw$^flhY=r)X-KfVLk;=s0F{(&a`n3b@+fa#e> zQQCXWO4M-umfYP}zv$h0QW6{`k-&E&6`qs@;np` zN9){!+hLYfexv+ghy3?ok63$QmwDlM*ajU37cr{F2UuJK1(F!>mWIw%zB(0s0X4}h zonL(=tpo9YiSs0lpM7<;gXm4R`TF7o`HGyRxF)w)^2P69v|QD{S64C)Rc>v6{U+%I zw5Vw_Mj(r(SDWtu_8o@k#k45q9v;_&;+r13wQO~x&DVJe79=r|Tff}gbaKgOOwZ!i z*uHA8aaZm%a5F!KV|2P+fPq&>Uvwn$TL&JSWbEkwLs<(+a_{H-?4m#sf{C|1Mt0;& z8P?*pw=oV^{=`RVx`B0}qFBQdT=@vG$1W3&s#M)6mWSI1sTIFX^v_)gnk%h4e<0XV z&FsW=BU&h@!{KSU7OfB*+-O*&$YD(yABa{d&JynI^$<6^^$2OnV8~ia%$XYL1og~^ zNQ3iTO(+)YyqGh7cgLVG99-21c6WZI8S2YeYA6+}`@l8PWxOKoum$vj^bEjLV}khy zXX=>mrUdRVzShDHpR1U3u-ci%jUcj#3@n}ZRyC$c%*Rfz)!*#;o@x-_EV60n6Y+d^ z3#!4z^0IUY(h)svX|rpaS(ZS#>u&=|);*B%T`8S$3zTaO+tl`0@%G(>JaR;E#K1+fM@#hgo!yVR z>w3P}g=jT6i&ok*|N0(hDNtmy}@^JaNzrK_Hjj# z&g@uU>&O||z30*N?e^dgz+=*?Pi0sO6=l0{3;>*VjMl;wr)#weO!Q%p^S@jn4FxcO zlAl)3HkL9~T=8$tTe#Xe@gAV`n_kzC%vbM5&pemnE&07)RvJO99DIUjgeLy2569dR zZQ)}?O-HE!ZH88x_32&sW6u(r^Eh&okf0H4G^`T9$Z@Me({~R^T%`!Rj?7V*51L+$ zed+|TJ}YAje<>^B0uGLRYIy=VOlv+;&ECck0M6wd1Xb;?m|72$OJTgJNbSh|!q|Ot zW7W2%+NF8Jg5zLlY(>$Sm$$EHTSTI0iQchRbanG`sr6BX-G5z(*ugAmOl8({WL|{sk;l^|NpPQGEp^ z%8!cJ2+wZ-x%;Z{}YlO%&%9{$~C=o|Sv8l^gG5-8@Arp4qby zCn{X?Hp-*(D>C3lZ)0TVJZi2uQ*+5EI`JDsJ2OwN0WH-EN%L*FtjFg{w8w$@u2LwG zF~N^%<(*AF$slp`iTqt4X16@bdBUNKItua){zr}uMmQ6sb%efY9A3D&RF@gaTDq_9s`sLMlj_D-JWYlRbiD9W8eF&Dg#9-*2HjH4hJhm>hoUyvx3EBB-Jq z#Mx2@y(^KL=FIWFBm$dn5$t^mv`*rBxS3#ow}JQOM;Ergz&cYv5q1e`uw?gdoaETD z#~ReoP zDqlClH-%+PLfteD^0)Jr%G2awD3{dH18_j z83+V`E*f}iUH23I(5J}1Fv7$_)60H2Ey)p*=jQ2SEy;AI0)2Mx4jYq~+A0A^BT@b( z0Cj)l&!bo|_=R)E(V-E=`p5setoETZlhol!aoE!3f&B@bQ^ z=EUo0AXLKioSzQ)m|8wBAoZWF32I#pST(CB9uh-=!zPg@#a)Q|Rv+o}?D0!{bhX8{ zPt*M@X$s=n1l{3@a-t0G1*M+mWt546ozLqNjA$*Xi~UCI52J@!KAOG2#B&#{?!uxM z4XTVGh3*L7uc9420j{^sXA<#g{S4km3GCIP7YGFL8<~r1VCtw?1gAg+mq0~b%}T48 zEiZH-4r6&VYhc7VbX__A0MVB&c7TwyZPVoTrSdYBd9vWT6UTG5H#PaqrIs10YI zd1jpc?T>7Kn);ZzfReAYePs{>@~F#&?TtKHDI=j5#<9$F!dtLK@YYP4vFUj7c`KTO z)m~LC=9WlYsz~7i*zdh`NEF5V6^|GqEz-)w51?BHOs8~!Wet>;{AY%W@K6@X!1>io zMw!t3Jtfm#_~g^UrxvI1f9vW`qhPSwZ699oD9eSoUUR7CfkJ3d>{>E9&FjO>Y$R#z zT*m2ANceD+q@F*Lv_f=+0qdNZ%I|E2uqRT|4)4P_UaBs#ZMFS2tv(QeRo&`y@8lmC zgWpmVgn6|YYl{TrUw>HrEP-gm6)sjOGq({kR{_fbYDG2HM(1X4r`dgB|IW#>0H{L* z%tXN5q>09V;OzH1-zn}7zqZX7Df-Ik%Y@*zz5$>mS$xLFc**~%;>zQpjJ|hCA!LiP zWDnVe#L$$jQYc|C_AP}OWG!a0Z&^y#u|$dNOV+`VeNCclW9%ejAG`6p`hI@f{Qb_o z=brbRd(L^zbKc<*C+-$|1$WN7TsP_Qh<+V$wSU0%RL}$YYg$6>x``u3aW&+XrlV7M zUaVx?l8Y|#??G(Ra#Pm(mnXd_WQd6E)#!|C2hR&22}6B`xg6okur*S|Z6CO;edRZS z7*=IsWg$LgF0thET0YeG9olctRrF=>NgsZ1xR~MWvENx%bFu2>6eITcWaebpz zGSx2EJ>!oLK@>qA+-(z*yWuA;{Y>az#xtMJ-+4a@qL5|AN;cQui&|;#p5E8FSKKjP z%OSv_P`waH7S7N&uzdQDow7{$J^k=cstSB64);|}aEZ7nU{zBngaEJW6(DD@I7W_B zG`vljYMoMQ-Z^{b>b~YpLqCEpSx3NjWP0Fg+VYb3lPNEbT7+PmocK>`SwhWP%o|uq zff>5ttT`nv0g(M>a=Oh}Q0;n4nssWjO1<>YJ;oFWk?UP^f-r*Cqfr{SB~>QHX(&V( zi#SGo8ts@E>53e>23Ux=2o+|U1GXa&V!i{g0e~T#wQUwX7PEyQ#_Z6anfpVZud_R{8^i!m)eiOQKeodpjrmNBz!x4HoG zOhvY|SCNJva@y_l3!#{6xpt>(TAxjrY(+ETajag7@qyY~Co3tpnvJshO!Sy*pYK>~!A z>kQT?#&BOaX6KwO$9_Ha5N7kEA=jQ;Dk-5Bq4t*6Lch7m?zY6Sv2Fm z3Yzx4-0gJ1;*>likmxj7Xz`{$Rea^nsMfo%qAFC6Yj#Vb1u6yYv*&t4JTj;4^X|k) z3y$;TgINE(Er}6CZOSABox=(OPHQkD1YGvOe&VkQV5bv)3Oa%&ik6#SIS9-1G=Oh; zfB+YEf1lBUmEj)9$qnN(j1z}N!<-5_9`@v=Yf4pZOr*3%U4Y-0u)h2O0vy-MSksYz z|0#fK*1L86?bhvm-z0{N6;ABXyHx{Zy5b(~zh|?ytV}u>xn!kA=oo z+EXmWD&0saGhtA#$|$zd#-PFnN@~#rS<5WsU76uMNIg@!{WpSWOb!NA*^xOgU}8fn zieL|KZ<@qNHh)l}{eLn9LnR_Ulyd-UNI!PHsQJgD5kxTS^! zBk9{UpVt0D_a!Y%s=JidoSrAZfMJBlSx%}GhSMhf@rP;L5x1&okFDg~t7ttRZa9>w z-jD!TW@yS=2CPly>FOi%H@mrgU8+GJN-SE`JNX3xU

Uh3_-mm;=L=cv@S4~E2# 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 Muc4z}q-q)PUuriayZ`_I literal 0 HcmV?d00001 diff --git a/docs/source/pythonapi/base.rst b/docs/source/pythonapi/base.rst index e5b5b17c317..f3eb7ce838a 100644 --- a/docs/source/pythonapi/base.rst +++ b/docs/source/pythonapi/base.rst @@ -147,17 +147,26 @@ Constructing Tallies openmc.ZernikeRadialFilter openmc.ParentNuclideFilter openmc.ParticleFilter - openmc.RegularMesh - openmc.RectilinearMesh - openmc.CylindricalMesh - openmc.SphericalMesh - openmc.UnstructuredMesh openmc.MeshMaterialVolumes openmc.Trigger openmc.TallyDerivative openmc.Tally openmc.Tallies +Meshes +------ + +.. autosummary:: + :toctree: generated + :nosignatures: + :template: myclassinherit.rst + + openmc.RegularMesh + openmc.RectilinearMesh + openmc.CylindricalMesh + openmc.SphericalMesh + openmc.UnstructuredMesh + Geometry Plotting ----------------- diff --git a/docs/source/usersguide/processing.rst b/docs/source/usersguide/processing.rst index 10944b5e265..8b5ae53fac6 100644 --- a/docs/source/usersguide/processing.rst +++ b/docs/source/usersguide/processing.rst @@ -91,3 +91,82 @@ from a statepoint file, the ``openmc.statepoint`` module can be used. An `example notebook`_ demontrates how to analyze and plot source information. .. _example notebook: https://nbviewer.jupyter.org/github/openmc-dev/openmc-notebooks/blob/main/post-processing.ipynb + +------------------------ +VTK Mesh File Generation +------------------------ + +VTK files of OpenMC meshes can be created using the +:meth:`openmc.Mesh.write_data_to_vtk` method. Data can be applied to the +elements of the resulting mesh from mesh filter objects. This data can be +provided either as a flat array or, in the case of structured meshes +(:class:`~openmc.RegularMesh`, :class:`~openmc.RectilinearMesh`, +:class:`~openmc.CylindricalMesh`, or :class:`SphericalMesh`), the data can be +shaped with dimensions that match the dimensions of the mesh itself. + + +.. image:: ../_images/sphere-mesh-vtk.png + :width: 400px + :align: center + :alt: OpenMC spherical mesh exported to VTK + + +For all mesh types, if a flat data array is provided to the mesh, it is expected +that the data is ordered in the same ordering as the :attr:`openmc.Mesh.indices` +for that mesh object. When providing data directly from a tally, as shown below, +a flat array for a given dataset can be passed directly to this method. + +:: + + # create model above + + # create a mesh tally + mesh = openmc.RegularMesh() + mesh.dimension = [10, 20, 30] + mesh.lower_left = [-5, -10, -15] + mesh.upper_right = [5, 10, 15] + mesh_filter = openmc.MeshFilter(mesh) + tally = openmc.Tally() + tally.filters = [mesh_filter] + tally.scores = ['flux'] + + model.tallies = [tally] + model.run(apply_tally_results=True) + + # provide the data as-is to the method + mesh.write_data_to_vtk('flux.vtk', {'flux-mean': tally.mean}) + +The :class:`~openmc.Tally` object also provides a way to expand the dimensions +of the mesh filter into a meaningful form where indexing the mesh filter +dimensions results in intuitive slicing of structured meshes by setting +``expand_dims=True`` when using :meth:`openmc.Tally.get_reshaped_data`. This +reshaping does cause flat indexing of the data to change, however. As noted +above, provided datasets are allowed to be shaped so long as such datasets have +shapes that match the mesh dimensions. The ability to pass datasets in this way +is useful when additional filters are applied to a tally. The example below +demonstrates such a case for tally with both a :class:`~openmc.MeshFilter` and +:class:`~openmc.EnergyFilter` applied. + +:: + + # create model above + + # create a mesh tally with energy filter + mesh = openmc.RegularMesh() + mesh.dimension = [10, 20, 30] + mesh.lower_left = [-5, -10, -15] + mesh.upper_right = [5, 10, 15] + mesh_filter = openmc.MeshFilter(mesh) + energy_filter = openmc.EnergyFilter([0.0, 1.0, 20.0e6]) + tally = openmc.Tally() + tally.filters = [mesh_filter, energy_filter] + tally.scores = ['flux'] + + model.tallies = [tally] + model.run(apply_tally_results=True) + + # get the data with mesh dimensions expanded, squeeze out length-one dimensions (nuclides, scores) + flux = tally.get_reshaped_data(expand_dims=True).squeeze() # shape: (10, 20, 30, 2) + + # write the lowest energy group to a VTK file + mesh.write_data_to_vtk('flux-group1.vtk', datasets={'flux-mean': flux[..., 0]}) diff --git a/openmc/mesh.py b/openmc/mesh.py index 08025f374ec..27187b5dd18 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -576,11 +576,16 @@ def write_data_to_vtk(self, filename : str Name of the VTK file to write. datasets : dict - Dictionary whose keys are the data labels - and values are the data sets. + Dictionary whose keys are the data labels and values are the data + sets. 1D datasets are expected to be extracted directly from + statepoint data without reordering/reshaping. Multidimensional + datasets are expected to have the same dimensions as the mesh itself + with structured indexing in "C" ordering. See the "expand_dims" flag + of :meth:`~openmc.Tally.get_reshaped_data` on reshaping tally data when using + :class:`~openmc.MeshFilter`'s. volume_normalization : bool, optional - Whether or not to normalize the data by - the volume of the mesh elements. + Whether or not to normalize the data by the volume of the mesh + elements. curvilinear : bool Whether or not to write curvilinear elements. Only applies to ``SphericalMesh`` and ``CylindricalMesh``. @@ -594,14 +599,27 @@ def write_data_to_vtk(self, ------- vtk.StructuredGrid or vtk.UnstructuredGrid a VTK grid object representing the mesh + + Examples + -------- + 1D data from a tally with only a mesh filter and heating score: + + # pass the tally mean property of shape (N, 1, 1) directly to this + # method; dimensions of size 1 will automatically removed + >>> heating = tally.mean + >>> mesh.write_data_to_vtk({'heating': heating}) + + Multidimensional data from a tally with only a mesh + + # retrieve a data array with the mesh filter expanded into three + # dimensions, ijk; additional dimensions of size one will + # automatically be removed + >>> heating = tally.get_reshaped_data(expand_dims=True) + >>> mesh.write_data_to_vtk({'heating': heating}) """ import vtk from vtk.util import numpy_support as nps - # check that the data sets are appropriately sized - if datasets is not None: - self._check_vtk_datasets(datasets) - # write linear elements using a structured grid if not curvilinear or isinstance(self, (RegularMesh, RectilinearMesh)): vtk_grid = self._create_vtk_structured_grid() @@ -612,22 +630,27 @@ def write_data_to_vtk(self, writer = vtk.vtkUnstructuredGridWriter() if datasets is not None: - # maintain a list of the datasets as added - # to the VTK arrays to ensure they persist - # in memory until the file is written + # maintain a list of the datasets as added to the VTK arrays to + # ensure they persist in memory until the file is written datasets_out = [] for label, dataset in datasets.items(): - dataset = np.asarray(dataset).flatten() + dataset = self._reshape_vtk_dataset(dataset) + self._check_vtk_dataset(label, dataset) + # If the array data is 3D, assume is in C ordering and transpose + # before flattening to match the ordering expected by the VTK + # array based on the way mesh indices are ordered in the Python + # API + # TODO: update to "C" ordering throughout + if dataset.ndim == 3: + dataset = dataset.T.ravel() datasets_out.append(dataset) if volume_normalization: - dataset /= self.volumes.T.flatten() + dataset /= self.volumes.T.ravel() dataset_array = vtk.vtkDoubleArray() dataset_array.SetName(label) - dataset_array.SetArray(nps.numpy_to_vtk(dataset), - dataset.size, - True) + dataset_array.SetArray(nps.numpy_to_vtk(dataset), dataset.size, True) vtk_grid.GetCellData().AddArray(dataset_array) writer.SetFileName(str(filename)) @@ -754,28 +777,69 @@ def _insert_point(pnt): return vtk_grid - def _check_vtk_datasets(self, datasets: dict): - """Perform some basic checks that the datasets are valid for this mesh + @staticmethod + def _reshape_vtk_dataset(dataset): + """Reshape a dataset to be compatible with VTK output + + This method performs the following operations on a dataset: + 1. Convert to numpy array if not already + 2. Remove any trailing dimensions of size 1 + 3. Squeeze out any extra dimensions of size 1 beyond the first 3 Parameters ---------- - datasets : dict - Dictionary whose keys are the data labels - and values are the data sets. + dataset : array-like + The dataset to reshape + + Returns + ------- + numpy.ndarray + The reshaped dataset + """ + reshaped_data = np.asarray(dataset) + + # detect flat array with extra dims + if all(d == 1 for d in reshaped_data.shape[1:]): + reshaped_data = reshaped_data.squeeze() + + # remove any higher dimensions with size 1 + if reshaped_data.ndim > 3 and all(d == 1 for d in reshaped_data.shape[3:]): + reshaped_data = reshaped_data.reshape(reshaped_data.shape[:3]) + + if np.shares_memory(reshaped_data, dataset): + return np.copy(reshaped_data) + else: + return reshaped_data + + def _check_vtk_dataset(self, label: str, dataset: np.ndarray): + """Perform some basic checks that a dataset is valid for this Mesh + + Parameters + ---------- + label : str + The label for the dataset being checked + dataset : numpy.ndarray + The dataset array to check against this mesh's dimensions """ - for label, dataset in datasets.items(): - errmsg = ( + cv.check_type('data label', label, str) + + if dataset.size != self.num_mesh_cells: + raise ValueError( f"The size of the dataset '{label}' ({dataset.size}) should be" f" equal to the number of mesh cells ({self.num_mesh_cells})" ) - if isinstance(dataset, np.ndarray): - if not dataset.size == self.num_mesh_cells: - raise ValueError(errmsg) - else: - if len(dataset) == self.num_mesh_cells: - raise ValueError(errmsg) - cv.check_type('data label', label, str) + + # accept a flat array as-is, assuming it is in the correct order + if dataset.ndim == 1: + return + + if dataset.shape != self.dimension: + raise ValueError( + f'Cannot apply multidimensional dataset "{label}" with ' + f"shape {dataset.shape} to mesh {self.id} " + f"with dimensions {self.dimension}" + ) class RegularMesh(StructuredMesh): diff --git a/tests/unit_tests/mesh_to_vtk/test.py b/tests/unit_tests/mesh_to_vtk/test.py index 3ee6c0298ce..f00aa462612 100644 --- a/tests/unit_tests/mesh_to_vtk/test.py +++ b/tests/unit_tests/mesh_to_vtk/test.py @@ -49,7 +49,10 @@ def diff_file(file1, file2): def mesh_data(mesh_dims): data = 100 * np.arange(np.prod(mesh_dims), dtype=float) - return data.reshape(*mesh_dims) + # data is returned reshaped with order 'F' to ensure that + # the resulting data is interpreted correctly by the + # write_data_to_vtk method + return data.reshape(*mesh_dims, order='F') test_data = ((reg_mesh, False, 'regular'), (rect_mesh, False, 'rectilinear'), @@ -83,7 +86,6 @@ def test_mesh_write_vtk(mesh_params, run_in_tmpdir): # check data writing def test_mesh_write_vtk_data(run_in_tmpdir): - data = {'ascending_data': mesh_data(cyl_mesh.dimension)} filename_expected = full_path('cyl-data.vtk') filename_actual = full_path('cyl-data-actual.vtk') diff --git a/tests/unit_tests/mesh_to_vtk/test_vtk_dims.py b/tests/unit_tests/mesh_to_vtk/test_vtk_dims.py index 0b6acf5aabf..8166ba68c80 100644 --- a/tests/unit_tests/mesh_to_vtk/test_vtk_dims.py +++ b/tests/unit_tests/mesh_to_vtk/test_vtk_dims.py @@ -90,7 +90,7 @@ def test_write_data_to_vtk(mesh, tmpdir): # kji (i changing fastest) orering is expected for input data # by using the volumes transposed as the data here, we can ensure the # normalization is happening correctly - data = mesh.volumes.T + data = mesh.volumes # RUN mesh.write_data_to_vtk(filename=filename, datasets={"label1": data, "label2": data}) diff --git a/tests/unit_tests/test_mesh.py b/tests/unit_tests/test_mesh.py index f1f5e9f6796..49556fce37c 100644 --- a/tests/unit_tests/test_mesh.py +++ b/tests/unit_tests/test_mesh.py @@ -480,6 +480,9 @@ def test_umesh(run_in_tmpdir, simple_umesh, export_type): mean = np.array([arr.GetTuple1(i) for i in range(ref_data.size)]) np.testing.assert_almost_equal(mean, ref_data) + # attempt to apply a dataset with an improper size to a VTK write + with pytest.raises(ValueError, match='Cannot apply dataset "mean"') as e: + simple_umesh.write_data_to_vtk(datasets={'mean': ref_data[:-2]}, filename=filename) def test_mesh_get_homogenized_materials(): """Test the get_homogenized_materials method""" From dc619eac17d45ae42361c973bd4e643b5bb31684 Mon Sep 17 00:00:00 2001 From: Gregoire Biot Date: Mon, 5 May 2025 16:33:12 -0400 Subject: [PATCH 019/159] Filter weight implementation (#3345) Co-authored-by: Grego01-biot Co-authored-by: Patrick Shriwise Co-authored-by: Paul Romano --- CMakeLists.txt | 1 + docs/source/pythonapi/base.rst | 1 + docs/source/pythonapi/capi.rst | 1 + include/openmc/tallies/filter.h | 1 + include/openmc/tallies/filter_weight.h | 51 +++++++++++++++++++++ openmc/filter.py | 25 +++++++++- openmc/lib/filter.py | 4 ++ src/hdf5_interface.cpp | 2 +- src/tallies/filter.cpp | 3 ++ src/tallies/filter_weight.cpp | 63 ++++++++++++++++++++++++++ tests/unit_tests/test_filter_weight.py | 44 ++++++++++++++++++ tests/unit_tests/test_filters.py | 21 +++++++++ 12 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 include/openmc/tallies/filter_weight.h create mode 100644 src/tallies/filter_weight.cpp create mode 100644 tests/unit_tests/test_filter_weight.py diff --git a/CMakeLists.txt b/CMakeLists.txt index d7974620970..4dff35418f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -425,6 +425,7 @@ list(APPEND libopenmc_SOURCES src/tallies/filter_surface.cpp src/tallies/filter_time.cpp src/tallies/filter_universe.cpp + src/tallies/filter_weight.cpp src/tallies/filter_zernike.cpp src/tallies/tally.cpp src/tallies/tally_scoring.cpp diff --git a/docs/source/pythonapi/base.rst b/docs/source/pythonapi/base.rst index f3eb7ce838a..1d8a93d47f1 100644 --- a/docs/source/pythonapi/base.rst +++ b/docs/source/pythonapi/base.rst @@ -143,6 +143,7 @@ Constructing Tallies openmc.SpatialLegendreFilter openmc.SphericalHarmonicsFilter openmc.TimeFilter + openmc.WeightFilter openmc.ZernikeFilter openmc.ZernikeRadialFilter openmc.ParentNuclideFilter diff --git a/docs/source/pythonapi/capi.rst b/docs/source/pythonapi/capi.rst index c8e0e874dbf..63fc5d11b84 100644 --- a/docs/source/pythonapi/capi.rst +++ b/docs/source/pythonapi/capi.rst @@ -91,6 +91,7 @@ Classes Tally UniverseFilter UnstructuredMesh + WeightFilter WeightWindows ZernikeFilter ZernikeRadialFilter diff --git a/include/openmc/tallies/filter.h b/include/openmc/tallies/filter.h index 3e982d0cf45..ee635b18327 100644 --- a/include/openmc/tallies/filter.h +++ b/include/openmc/tallies/filter.h @@ -44,6 +44,7 @@ enum class FilterType { SURFACE, TIME, UNIVERSE, + WEIGHT, ZERNIKE, ZERNIKE_RADIAL }; diff --git a/include/openmc/tallies/filter_weight.h b/include/openmc/tallies/filter_weight.h new file mode 100644 index 00000000000..1fe9d75d3e7 --- /dev/null +++ b/include/openmc/tallies/filter_weight.h @@ -0,0 +1,51 @@ +#ifndef OPENMC_TALLIES_FILTER_WEIGHT_H +#define OPENMC_TALLIES_FILTER_WEIGHT_H + +#include + +#include "openmc/span.h" +#include "openmc/tallies/filter.h" +#include "openmc/vector.h" + +namespace openmc { + +//============================================================================== +//! Bins the weights of the particles. +//============================================================================== + +class WeightFilter : public Filter { +public: + //---------------------------------------------------------------------------- + // Constructors, destructors + + ~WeightFilter() = default; + + //---------------------------------------------------------------------------- + // Methods + + std::string type_str() const override { return "weight"; } + FilterType type() const override { return FilterType::WEIGHT; } + + void from_xml(pugi::xml_node node) override; + + void get_all_bins(const Particle& p, TallyEstimator estimator, + FilterMatch& match) const override; + + void to_statepoint(hid_t filter_group) const override; + + std::string text_label(int bin) const override; + + //---------------------------------------------------------------------------- + // Accessors + + const vector& bins() const { return bins_; } + void set_bins(span bins); + +protected: + //---------------------------------------------------------------------------- + // Data members + vector bins_; +}; + +} // namespace openmc +#endif // OPENMC_TALLIES_FILTER_WEIGHT_H diff --git a/openmc/filter.py b/openmc/filter.py index 755777c5e7d..4d37f105538 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -25,7 +25,7 @@ 'energyout', 'mu', 'musurface', 'polar', 'azimuthal', 'distribcell', 'delayedgroup', 'energyfunction', 'cellfrom', 'materialfrom', 'legendre', 'spatiallegendre', 'sphericalharmonics', 'zernike', 'zernikeradial', 'particle', 'cellinstance', - 'collision', 'time', 'parentnuclide' + 'collision', 'time', 'parentnuclide', 'weight' ) _CURRENT_NAMES = ( @@ -2328,3 +2328,26 @@ def get_pandas_dataframe(self, data_size, stride, **kwargs): {self.short_name.lower(): filter_bins})]) return df + + +class WeightFilter(RealFilter): + """Bins tally events based on the incoming particle weight. + + Parameters + ---------- + Values : Iterable of float + A list or iterable of the weight boundaries, as float values. + filter_id : int + Unique identifier for the filter + + Attributes + ---------- + id : int + Unique identifier for the filter + bins : numpy.ndarray + An array of integer values representing the weights by which to filter + num_bins : int + The number of filter bins + values : numpy.ndarray + Array of weight boundaries + """ diff --git a/openmc/lib/filter.py b/openmc/lib/filter.py index b6086c56711..3a76b52a11b 100644 --- a/openmc/lib/filter.py +++ b/openmc/lib/filter.py @@ -610,6 +610,10 @@ class UniverseFilter(Filter): filter_type = 'universe' +class WeightFilter(Filter): + filter_type = 'weight' + + class ZernikeFilter(Filter): filter_type = 'zernike' diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index 27b750b931e..e90aa74901e 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -92,7 +92,7 @@ void get_shape_attr(hid_t obj_id, const char* name, hsize_t* dims) H5Aclose(attr); } -hid_t create_group(hid_t parent_id, char const* name) +hid_t create_group(hid_t parent_id, const char* name) { hid_t out = H5Gcreate(parent_id, name, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); if (out < 0) { diff --git a/src/tallies/filter.cpp b/src/tallies/filter.cpp index 6430d9ae100..17e57a987c7 100644 --- a/src/tallies/filter.cpp +++ b/src/tallies/filter.cpp @@ -36,6 +36,7 @@ #include "openmc/tallies/filter_surface.h" #include "openmc/tallies/filter_time.h" #include "openmc/tallies/filter_universe.h" +#include "openmc/tallies/filter_weight.h" #include "openmc/tallies/filter_zernike.h" #include "openmc/xml_interface.h" @@ -154,6 +155,8 @@ Filter* Filter::create(const std::string& type, int32_t id) return Filter::create(id); } else if (type == "universe") { return Filter::create(id); + } else if (type == "weight") { + return Filter::create(id); } else if (type == "zernike") { return Filter::create(id); } else if (type == "zernikeradial") { diff --git a/src/tallies/filter_weight.cpp b/src/tallies/filter_weight.cpp new file mode 100644 index 00000000000..31f4bd1bf70 --- /dev/null +++ b/src/tallies/filter_weight.cpp @@ -0,0 +1,63 @@ +#include "openmc/tallies/filter_weight.h" + +#include // for is_sorted +#include // for runtime_error + +#include + +#include "openmc/search.h" +#include "openmc/xml_interface.h" + +namespace openmc { + +//============================================================================== +// WeightFilter implementation +//============================================================================== + +void WeightFilter::from_xml(pugi::xml_node node) +{ + auto bins = get_node_array(node, "bins"); + this->set_bins(bins); +} + +void WeightFilter::set_bins(span bins) +{ + if (!std::is_sorted(bins.begin(), bins.end())) { + throw std::runtime_error {"Weight bins must be monotonically increasing."}; + } + + // Clear existing bins + bins_.clear(); + bins_.reserve(bins.size()); + + // Copy bins + bins_.insert(bins_.end(), bins.begin(), bins.end()); + n_bins_ = bins_.size() - 1; +} + +void WeightFilter::get_all_bins( + const Particle& p, TallyEstimator estimator, FilterMatch& match) const +{ + // Get particle weight + double wgt = p.wgt_last(); + + // Bin the weight + if (wgt >= bins_.front() && wgt <= bins_.back()) { + auto bin = lower_bound_index(bins_.begin(), bins_.end(), wgt); + match.bins_.push_back(bin); + match.weights_.push_back(1.0); + } +} + +void WeightFilter::to_statepoint(hid_t filter_group) const +{ + Filter::to_statepoint(filter_group); + write_dataset(filter_group, "bins", bins_); +} + +std::string WeightFilter::text_label(int bin) const +{ + return fmt::format("Weight [{}, {}]", bins_[bin], bins_[bin + 1]); +} + +} // namespace openmc diff --git a/tests/unit_tests/test_filter_weight.py b/tests/unit_tests/test_filter_weight.py new file mode 100644 index 00000000000..878929ee056 --- /dev/null +++ b/tests/unit_tests/test_filter_weight.py @@ -0,0 +1,44 @@ +import openmc +import numpy as np + + +def test_weightfilter(run_in_tmpdir): + steel = openmc.Material(name='Stainless Steel') + steel.set_density('g/cm3', 8.00) + steel.add_nuclide('Fe56', 1.0) + + sphere = openmc.Sphere(r=50.0, boundary_type='vacuum') + cell = openmc.Cell(region=-sphere, fill=steel) + model = openmc.Model() + model.geometry = openmc.Geometry([cell]) + model.settings.particles = 100 + model.settings.batches = 10 + + model.settings.source = openmc.IndependentSource( + energy=openmc.stats.delta_function(14e6), + ) + model.settings.run_mode = "fixed source" + + radius = list(range(1, 50)) + sphere_mesh = openmc.SphericalMesh(radius) + mesh_filter = openmc.MeshFilter(sphere_mesh) + weight_filter = openmc.WeightFilter( + [0.999, 0.9999, 0.99999, 0.999999, 1.0, 1.000001 ,1.00001, 1.0001, 1.001] + ) + + tally = openmc.Tally() + tally.filters = [mesh_filter, weight_filter] + tally.estimator = 'analog' + tally.scores = ['flux'] + model.tallies = openmc.Tallies([tally]) + + # Run OpenMC + model.run(apply_tally_results=True) + + # Get current binned by mu + neutron_flux = tally.mean.reshape(48, 8) + + # All contributions should show up in the fourth bin + assert np.all(neutron_flux[:, 3] != 0.0) + neutron_flux[:, 3] = 0.0 + assert np.all(neutron_flux == 0.0) diff --git a/tests/unit_tests/test_filters.py b/tests/unit_tests/test_filters.py index c3b73719232..60a60e3f56e 100644 --- a/tests/unit_tests/test_filters.py +++ b/tests/unit_tests/test_filters.py @@ -312,3 +312,24 @@ def test_energy_filter(): msg = 'Unable to set "filter value" to "-1.2" since it is less than "0.0"' with raises(ValueError, match=msg): openmc.EnergyFilter([-1.2, 0.25, 0.5]) + + +def test_weight(): + f = openmc.WeightFilter([0.01, 0.1, 1.0, 10.0]) + expected_bins = [[0.01, 0.1], [0.1, 1.0], [1.0, 10.0]] + + assert np.allclose(f.bins, expected_bins) + assert len(f.bins) == 3 + + # Make sure __repr__ works + repr(f) + + # to_xml_element() + elem = f.to_xml_element() + assert elem.tag == 'filter' + assert elem.attrib['type'] == 'weight' + + # from_xml_element() + new_f = openmc.Filter.from_xml_element(elem) + assert new_f.id == f.id + assert np.allclose(new_f.bins, f.bins) From e4f55a57b69c87f16e73710bcf51f7427b2cb6a7 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 5 May 2025 15:59:11 -0500 Subject: [PATCH 020/159] Fix weight modification for uniform source sampling (#3395) Co-authored-by: Patrick Shriwise --- openmc/deplete/d1s.py | 4 +++- openmc/source.py | 2 +- src/settings.cpp | 14 +++++++------- src/source.cpp | 15 ++++++++++----- src/tallies/tally.cpp | 7 ++----- tests/unit_tests/test_uniform_source_sampling.py | 15 +++++++++++---- 6 files changed, 34 insertions(+), 23 deletions(-) diff --git a/openmc/deplete/d1s.py b/openmc/deplete/d1s.py index 8f6bffa8656..0a1ed74b173 100644 --- a/openmc/deplete/d1s.py +++ b/openmc/deplete/d1s.py @@ -141,7 +141,9 @@ def apply_time_correction( time_correction_factors : dict Time correction factors as returned by :func:`time_correction_factors` index : int, optional - Index to use for the correction factors + Index of the time of interest. If N timesteps are provided in + :func:`time_correction_factors`, there are N + 1 times to select from. + The default is -1 which corresponds to the final time. sum_nuclides : bool Whether to sum over the parent nuclides diff --git a/openmc/source.py b/openmc/source.py index 878f52c3b22..87e734e9a25 100644 --- a/openmc/source.py +++ b/openmc/source.py @@ -110,7 +110,7 @@ def constraints(self, constraints: dict[str, Any] | None): cv.check_value('rejection strategy', value, ('resample', 'kill')) self._constraints['rejection_strategy'] = value else: - raise ValueError('Unknown key in constraints dictionary: {key}') + raise ValueError(f'Unknown key in constraints dictionary: {key}') @abstractmethod def populate_xml_element(self, element): diff --git a/src/settings.cpp b/src/settings.cpp index c135fa3391e..c8230a10f1d 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -620,13 +620,6 @@ void read_settings_xml(pugi::xml_node root) model::external_sources.push_back(make_unique(path)); } - // Build probability mass function for sampling external sources - vector source_strengths; - for (auto& s : model::external_sources) { - source_strengths.push_back(s->strength()); - } - model::external_sources_probability.assign(source_strengths); - // If no source specified, default to isotropic point source at origin with // Watt spectrum. No default source is needed in random ray mode. if (model::external_sources.empty() && @@ -639,6 +632,13 @@ void read_settings_xml(pugi::xml_node root) UPtrDist {new Discrete(T, p, 1)})); } + // Build probability mass function for sampling external sources + vector source_strengths; + for (auto& s : model::external_sources) { + source_strengths.push_back(s->strength()); + } + model::external_sources_probability.assign(source_strengths); + // Check if we want to write out source if (check_for_node(root, "write_initial_source")) { write_initial_source = get_node_value_bool(root, "write_initial_source"); diff --git a/src/source.cpp b/src/source.cpp index c1b4ce260a3..8e6eb4f11fd 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -613,9 +613,10 @@ SourceSite sample_external_source(uint64_t* seed) { // Sample from among multiple source distributions int i = 0; - if (model::external_sources.size() > 1) { + int n_sources = model::external_sources.size(); + if (n_sources > 1) { if (settings::uniform_source_sampling) { - i = prn(seed) * model::external_sources.size(); + i = prn(seed) * n_sources; } else { i = model::external_sources_probability.sample(seed); } @@ -624,9 +625,13 @@ SourceSite sample_external_source(uint64_t* seed) // Sample source site from i-th source distribution SourceSite site {model::external_sources[i]->sample_with_constraints(seed)}; - // Set particle creation weight - if (settings::uniform_source_sampling) { - site.wgt *= model::external_sources[i]->strength(); + // For uniform source sampling, multiply the weight by the ratio of the actual + // probability of sampling source i to the biased probability of sampling + // source i, which is (strength_i / total_strength) / (1 / n) + if (n_sources > 1 && settings::uniform_source_sampling) { + double total_strength = model::external_sources_probability.integral(); + site.wgt *= + model::external_sources[i]->strength() * n_sources / total_strength; } // If running in MG, convert site.E to group diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 35805b20d7e..54840b5188e 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -815,11 +815,8 @@ void Tally::accumulate() if (mpi::master || !settings::reduce_tallies) { // Calculate total source strength for normalization double total_source = 0.0; - if (settings::run_mode == RunMode::FIXED_SOURCE && - !settings::uniform_source_sampling) { - for (const auto& s : model::external_sources) { - total_source += s->strength(); - } + if (settings::run_mode == RunMode::FIXED_SOURCE) { + total_source = model::external_sources_probability.integral(); } else { total_source = 1.0; } diff --git a/tests/unit_tests/test_uniform_source_sampling.py b/tests/unit_tests/test_uniform_source_sampling.py index 7f805e37d27..0d1930328b9 100644 --- a/tests/unit_tests/test_uniform_source_sampling.py +++ b/tests/unit_tests/test_uniform_source_sampling.py @@ -14,10 +14,15 @@ def sphere_model(): model.settings.particles = 100 model.settings.batches = 1 - model.settings.source = openmc.IndependentSource( + src1 = openmc.IndependentSource( energy=openmc.stats.delta_function(1.0e3), - strength=100.0 + strength=75.0 ) + src2 = openmc.IndependentSource( + energy=openmc.stats.delta_function(1.0e3), + strength=25.0 + ) + model.settings.source = [src1, src2] model.settings.run_mode = "fixed source" model.settings.surf_source_write = { "max_particles": 100, @@ -42,11 +47,13 @@ def test_source_weight(run_in_tmpdir, sphere_model): sphere_model.settings.uniform_source_sampling = True sphere_model.run() particles = openmc.ParticleList.from_hdf5('surface_source.h5') - strength = sphere_model.settings.source[0].strength - assert set(p.wgt for p in particles) == {strength} + assert set(p.wgt for p in particles) == {0.5, 1.5} def test_tally_mean(run_in_tmpdir, sphere_model): + # Use only one source + sphere_model.settings.source.pop() + # Run without uniform source sampling sphere_model.settings.uniform_source_sampling = False sp_file = sphere_model.run() From f9dca9a458fec7ec8384b88d349170ddf581ce3d Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Tue, 6 May 2025 03:04:47 +0300 Subject: [PATCH 021/159] Fixing an incorrect computation of CDF of bremsstrahlung photons (#3396) Co-authored-by: Paul Romano --- src/material.cpp | 9 +-- .../photon_production/results_true.dat | 24 ++++---- .../results_true.dat | 58 +++++++++---------- .../photon_source/results_true.dat | 4 +- .../weightwindows/results_true.dat | 2 +- 5 files changed, 49 insertions(+), 48 deletions(-) diff --git a/src/material.cpp b/src/material.cpp index 2bd504e4dbf..b8846d7cbfa 100644 --- a/src/material.cpp +++ b/src/material.cpp @@ -777,14 +777,15 @@ void Material::init_bremsstrahlung() // Loop over photon energies double c = 0.0; for (int i = 0; i < j; ++i) { - // Integrate the CDF from the PDF using the trapezoidal rule in log-log - // space + // Integrate the CDF from the PDF using the fact that the PDF is linear + // in log-log space double w_l = std::log(data::ttb_e_grid(i)); double w_r = std::log(data::ttb_e_grid(i + 1)); double x_l = std::log(ttb->pdf(j, i)); double x_r = std::log(ttb->pdf(j, i + 1)); - - c += 0.5 * (w_r - w_l) * (std::exp(w_l + x_l) + std::exp(w_r + x_r)); + double beta = (x_r - x_l) / (w_r - w_l); + double a = beta + 1.0; + c += std::exp(w_l + x_l) / a * std::expm1(a * (w_r - w_l)); ttb->cdf(j, i + 1) = c; } diff --git a/tests/regression_tests/photon_production/results_true.dat b/tests/regression_tests/photon_production/results_true.dat index a911fed3b8d..874e32b85f7 100644 --- a/tests/regression_tests/photon_production/results_true.dat +++ b/tests/regression_tests/photon_production/results_true.dat @@ -16,12 +16,12 @@ tally 2: 1.573004E+00 4.296434E-04 1.845934E-07 -2.350021E-01 -5.522600E-02 +2.350047E-01 +5.522722E-02 0.000000E+00 0.000000E+00 -2.350021E-01 -5.522600E-02 +2.350047E-01 +5.522722E-02 0.000000E+00 0.000000E+00 0.000000E+00 @@ -73,8 +73,8 @@ tally 3: 0.000000E+00 0.000000E+00 0.000000E+00 -1.774451E+05 -3.148675E+10 +1.774484E+05 +3.148794E+10 0.000000E+00 0.000000E+00 0.000000E+00 @@ -85,8 +85,8 @@ tally 3: 0.000000E+00 0.000000E+00 0.000000E+00 -7.691658E+03 -5.916160E+07 +7.692488E+03 +5.917437E+07 0.000000E+00 0.000000E+00 tally 4: @@ -122,8 +122,8 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -1.774451E+05 -3.148675E+10 +1.774484E+05 +3.148794E+10 0.000000E+00 0.000000E+00 0.000000E+00 @@ -134,7 +134,7 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -7.691658E+03 -5.916160E+07 +7.692488E+03 +5.917437E+07 0.000000E+00 0.000000E+00 diff --git a/tests/regression_tests/photon_production_fission/results_true.dat b/tests/regression_tests/photon_production_fission/results_true.dat index 7e20342af51..cdbb1255c22 100644 --- a/tests/regression_tests/photon_production_fission/results_true.dat +++ b/tests/regression_tests/photon_production_fission/results_true.dat @@ -1,12 +1,12 @@ k-combined: -2.270911E+00 4.568134E-02 +2.278476E+00 6.220292E-02 tally 1: -2.663476E+00 -2.366726E+00 +2.664071E+00 +2.369250E+00 0.000000E+00 0.000000E+00 -2.663476E+00 -2.366726E+00 +2.664071E+00 +2.369250E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -18,52 +18,52 @@ tally 1: 0.000000E+00 0.000000E+00 tally 2: -2.636709E+00 -2.318646E+00 -4.230052E+08 -5.967879E+16 +2.640755E+00 +2.326201E+00 +4.245217E+08 +6.012128E+16 0.000000E+00 0.000000E+00 -2.636709E+00 -2.318646E+00 -4.230052E+08 -5.967879E+16 +2.640755E+00 +2.326201E+00 +4.245217E+08 +6.012128E+16 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.455009E+06 -2.015314E+12 +2.479234E+06 +2.052367E+12 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.455009E+06 -2.015314E+12 +2.479234E+06 +2.052367E+12 0.000000E+00 0.000000E+00 tally 3: -2.660004E+00 -2.358564E+00 -4.230052E+08 -5.967879E+16 +2.657846E+00 +2.354717E+00 +4.245217E+08 +6.012128E+16 0.000000E+00 0.000000E+00 -2.660004E+00 -2.358564E+00 -4.230052E+08 -5.967879E+16 +2.657846E+00 +2.354717E+00 +4.245217E+08 +6.012128E+16 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.455009E+06 -2.015314E+12 +2.479234E+06 +2.052367E+12 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.455009E+06 -2.015314E+12 +2.479234E+06 +2.052367E+12 0.000000E+00 0.000000E+00 diff --git a/tests/regression_tests/photon_source/results_true.dat b/tests/regression_tests/photon_source/results_true.dat index fcc75cbca84..8d934afc66a 100644 --- a/tests/regression_tests/photon_source/results_true.dat +++ b/tests/regression_tests/photon_source/results_true.dat @@ -1,5 +1,5 @@ tally 1: -2.263938E+02 -5.125417E+04 +2.263761E+02 +5.124615E+04 0.000000E+00 0.000000E+00 diff --git a/tests/regression_tests/weightwindows/results_true.dat b/tests/regression_tests/weightwindows/results_true.dat index 6bb992cd488..671be1d7b06 100644 --- a/tests/regression_tests/weightwindows/results_true.dat +++ b/tests/regression_tests/weightwindows/results_true.dat @@ -1 +1 @@ -952c20fefac374d3b9fd28627fda7d5ae262f8cd7c01a33f0381526204a2287c18e56259a599dc6fdc1b59a2d2866fdfeedea371154c8fa7a69dc5c445b08c2d \ No newline at end of file +a5880ad9262e8aba90801783891ee74618144101401f06fb46e954e851a3c517ab28d5f0d6e1b2b364844f08d363cba35ab23b63fc81012ea8a6a328755d56c4 \ No newline at end of file From c1c5c0b93ecc93815afb3554a2812fb22b50d9c4 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 7 May 2025 18:51:40 -0600 Subject: [PATCH 022/159] Apply resolve paths to path values in `config` (#3400) --- docs/source/usersguide/data.rst | 3 ++- openmc/config.py | 2 +- tests/unit_tests/test_config.py | 7 +++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/source/usersguide/data.rst b/docs/source/usersguide/data.rst index 2a9cd36dbb4..8b2938556be 100644 --- a/docs/source/usersguide/data.rst +++ b/docs/source/usersguide/data.rst @@ -30,7 +30,8 @@ responsible for specifying one or more of the following: Each of the above files can specified in several ways. In the Python API, a :ref:`runtime configuration variable ` :data:`openmc.config` can be used to specify any of the above and is initialized -using a set of environment variables. +using a set of environment variables. Data configuration paths set in +:data:`openmc.config` will be expanded to absolute paths. .. _usersguide_data_runtime: diff --git a/openmc/config.py b/openmc/config.py index ab53ab61b5f..56d8a4072dd 100644 --- a/openmc/config.py +++ b/openmc/config.py @@ -60,7 +60,7 @@ def __repr__(self): return repr(self._mapping) def _set_path(self, key, value): - self._mapping[key] = p = Path(value) + self._mapping[key] = p = Path(value).resolve() if not p.exists(): warnings.warn(f"'{value}' does not exist.") diff --git a/tests/unit_tests/test_config.py b/tests/unit_tests/test_config.py index 9d3f53a7403..4e87de4b7e9 100644 --- a/tests/unit_tests/test_config.py +++ b/tests/unit_tests/test_config.py @@ -1,5 +1,6 @@ from collections.abc import Mapping import os +from pathlib import Path import openmc import pytest @@ -34,6 +35,12 @@ def test_config_basics(): with pytest.raises(KeyError): openmc.config['🐖'] = '/like/to/eat/bacon' + # ensure relative paths are expanded into absolute + # paths + chain_path = Path('./chain.xml') + openmc.config['chain_file'] = chain_path + assert openmc.config['chain_file'] == chain_path.resolve() + def test_config_patch(): openmc.config['cross_sections'] = '/path/to/cross_sections.xml' From ba834be5c29f4e2a5218cf6377bf536b0358a12c Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Thu, 8 May 2025 08:10:33 +0200 Subject: [PATCH 023/159] added type hints to model file (#3399) Co-authored-by: Paul Romano --- openmc/model/model.py | 223 ++++++++++++++++++++++++++++++------------ 1 file changed, 162 insertions(+), 61 deletions(-) diff --git a/openmc/model/model.py b/openmc/model/model.py index 9ff574ec63c..c19c8ac9f15 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -67,8 +67,14 @@ class Model: """ - def __init__(self, geometry=None, materials=None, settings=None, - tallies=None, plots=None): + def __init__( + self, + geometry: openmc.Geometry | None = None, + materials: openmc.Materials = None, + settings: openmc.Settings | None = None, + tallies: openmc.Tallies | None = None, + plots: openmc.Plots | None = None, + ): self.geometry = openmc.Geometry() if geometry is None else geometry self.materials = openmc.Materials() if materials is None else materials self.settings = openmc.Settings() if settings is None else settings @@ -192,24 +198,29 @@ def _materials_by_name(self) -> dict[int, openmc.Material]: return result @classmethod - def from_xml(cls, geometry='geometry.xml', materials='materials.xml', - settings='settings.xml', tallies='tallies.xml', - plots='plots.xml') -> Model: + def from_xml( + cls, + geometry: PathLike = "geometry.xml", + materials: PathLike = "materials.xml", + settings: PathLike = "settings.xml", + tallies: PathLike = "tallies.xml", + plots: PathLike = "plots.xml", + ) -> Model: """Create model from existing XML files Parameters ---------- - geometry : str + geometry : PathLike Path to geometry.xml file - materials : str + materials : PathLike Path to materials.xml file - settings : str + settings : PathLike Path to settings.xml file - tallies : str + tallies : PathLike Path to tallies.xml file .. versionadded:: 0.13.0 - plots : str + plots : PathLike Path to plots.xml file .. versionadded:: 0.13.0 @@ -229,14 +240,14 @@ def from_xml(cls, geometry='geometry.xml', materials='materials.xml', return cls(geometry, materials, settings, tallies, plots) @classmethod - def from_model_xml(cls, path='model.xml'): + def from_model_xml(cls, path: PathLike = "model.xml") -> Model: """Create model from single XML file .. versionadded:: 0.13.3 Parameters ---------- - path : str or PathLike + path : PathLike Path to model.xml file """ parser = ET.XMLParser(huge_tree=True) @@ -262,8 +273,17 @@ def from_model_xml(cls, path='model.xml'): return model - def init_lib(self, threads=None, geometry_debug=False, restart_file=None, - tracks=False, output=True, event_based=None, intracomm=None, directory=None): + def init_lib( + self, + threads: int | None = None, + geometry_debug: bool = False, + restart_file: PathLike | None = None, + tracks: bool = False, + output: bool = True, + event_based: bool | None = None, + intracomm=None, + directory: PathLike | None = None, + ): """Initializes the model in memory via the C API .. versionadded:: 0.13.0 @@ -278,7 +298,7 @@ def init_lib(self, threads=None, geometry_debug=False, restart_file=None, variable). geometry_debug : bool, optional Turn on geometry debugging during simulation. Defaults to False. - restart_file : str, optional + restart_file : PathLike, optional Path to restart file to use tracks : bool, optional Enables the writing of particles tracks. The number of particle @@ -291,7 +311,7 @@ def init_lib(self, threads=None, geometry_debug=False, restart_file=None, the Settings will be used. intracomm : mpi4py.MPI.Intracomm or None, optional MPI intracommunicator - directory : str or None, optional + directory : PathLike or None, optional Directory to write XML files to. Defaults to None. """ @@ -365,9 +385,15 @@ def finalize_lib(self): openmc.lib.finalize() - def deplete(self, timesteps, method='cecm', final_step=True, - operator_kwargs=None, directory='.', output=True, - **integrator_kwargs): + def deplete( + self, + method: str = "cecm", + final_step: bool = True, + operator_kwargs: dict | None = None, + directory: PathLike = ".", + output: bool = True, + **integrator_kwargs, + ): """Deplete model using specified timesteps/power .. versionchanged:: 0.13.0 @@ -376,10 +402,12 @@ def deplete(self, timesteps, method='cecm', final_step=True, Parameters ---------- - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not - cumulative. - method : str, optional + timesteps : iterable of float or iterable of tuple + Array of timesteps. Note that values are not cumulative. The units are + specified by the `timestep_units` argument when `timesteps` is an + iterable of float. Alternatively, units can be specified for each step + by passing an iterable of (value, unit) tuples. + method : str Integration method used for depletion (e.g., 'cecm', 'predictor'). Defaults to 'cecm'. final_step : bool, optional @@ -388,14 +416,14 @@ def deplete(self, timesteps, method='cecm', final_step=True, operator_kwargs : dict Keyword arguments passed to the depletion operator initializer (e.g., :func:`openmc.deplete.Operator`) - directory : str, optional + directory : PathLike, optional Directory to write XML files to. If it doesn't exist already, it will be created. Defaults to the current working directory output : bool Capture OpenMC output from standard out integrator_kwargs : dict - Remaining keyword arguments passed to the depletion Integrator - initializer (e.g., :func:`openmc.deplete.integrator.cecm`). + Remaining keyword arguments passed to the depletion integrator + (e.g., :class:`openmc.deplete.CECMIntegrator`). """ @@ -426,8 +454,7 @@ def deplete(self, timesteps, method='cecm', final_step=True, check_value('method', method, dep.integrators.integrator_by_name.keys()) integrator_class = dep.integrators.integrator_by_name[method] - integrator = integrator_class(depletion_operator, timesteps, - **integrator_kwargs) + integrator = integrator_class(depletion_operator, **integrator_kwargs) # Now perform the depletion with openmc.lib.quiet_dll(output): @@ -456,7 +483,7 @@ def export_to_xml(self, directory: PathLike = '.', remove_surfs: bool = False, Parameters ---------- - directory : str + directory : PathLike Directory to write XML files to. If it doesn't exist already, it will be created. remove_surfs : bool @@ -570,7 +597,7 @@ def export_to_model_xml(self, path: PathLike = 'model.xml', remove_surfs: bool = fh.write(ET.tostring(plots_element, encoding="unicode")) fh.write("\n") - def import_properties(self, filename): + def import_properties(self, filename: PathLike): """Import physical properties .. versionchanged:: 0.13.0 @@ -578,7 +605,7 @@ def import_properties(self, filename): Parameters ---------- - filename : str + filename : PathLike Path to properties HDF5 file See Also @@ -631,11 +658,22 @@ def import_properties(self, filename): C_mat = openmc.lib.materials[mat_id] C_mat.set_density(atom_density, 'atom/b-cm') - def run(self, particles=None, threads=None, geometry_debug=False, - restart_file=None, tracks=False, output=True, cwd='.', - openmc_exec='openmc', mpi_args=None, event_based=None, - export_model_xml=True, apply_tally_results=False, - **export_kwargs): + def run( + self, + particles: int | None = None, + threads: int | None = None, + geometry_debug: bool = False, + restart_file: PathLike | None = None, + tracks: bool = False, + output: bool = True, + cwd: PathLike = ".", + openmc_exec: PathLike = "openmc", + mpi_args: Iterable[str] = None, + event_based: bool | None = None, + export_model_xml: bool = True, + apply_tally_results: bool = False, + **export_kwargs, + ) -> Path: """Run OpenMC If the C API has been initialized, then the C API is used, otherwise, @@ -767,10 +805,17 @@ def run(self, particles=None, threads=None, geometry_debug=False, return last_statepoint - def calculate_volumes(self, threads=None, output=True, cwd='.', - openmc_exec='openmc', mpi_args=None, - apply_volumes=True, export_model_xml=True, - **export_kwargs): + def calculate_volumes( + self, + threads: int | None = None, + output: bool = True, + cwd: PathLike = ".", + openmc_exec: PathLike = "openmc", + mpi_args: list[str] | None = None, + apply_volumes: bool = True, + export_model_xml: bool = True, + **export_kwargs, + ): """Runs an OpenMC stochastic volume calculation and, if requested, applies volumes to the model @@ -1116,8 +1161,14 @@ def apply_tally_results(self, statepoint: PathLike | openmc.StatePoint): """ self.tallies.add_results(statepoint) - def plot_geometry(self, output=True, cwd='.', openmc_exec='openmc', - export_model_xml=True, **export_kwargs): + def plot_geometry( + self, + output: bool = True, + cwd: PathLike = ".", + openmc_exec: PathLike = "openmc", + export_model_xml: bool = True, + **export_kwargs, + ): """Creates plot images as specified by the Model.plots attribute .. versionadded:: 0.13.0 @@ -1126,10 +1177,10 @@ def plot_geometry(self, output=True, cwd='.', openmc_exec='openmc', ---------- output : bool, optional Capture OpenMC output from standard out - cwd : str, optional + cwd : PathLike, optional Path to working directory to run in. Defaults to the current working directory. - openmc_exec : str, optional + openmc_exec : PathLike, optional Path to OpenMC executable. Defaults to 'openmc'. This only applies to the case when not using the C API. export_model_xml : bool, optional @@ -1159,8 +1210,14 @@ def plot_geometry(self, output=True, cwd='.', openmc_exec='openmc', openmc.plot_geometry(output=output, openmc_exec=openmc_exec, path_input=path_input) - def _change_py_lib_attribs(self, names_or_ids, value, obj_type, - attrib_name, density_units='atom/b-cm'): + def _change_py_lib_attribs( + self, + names_or_ids: Iterable[str] | Iterable[int], + value: float | Iterable[float], + obj_type: str, + attrib_name: str, + density_units: str = "atom/b-cm", + ): # Method to do the same work whether it is a cell or material and # a temperature or volume check_type('names_or_ids', names_or_ids, Iterable, (Integral, str)) @@ -1239,7 +1296,9 @@ def _change_py_lib_attribs(self, names_or_ids, value, obj_type, else: setattr(lib_obj, attrib_name, value) - def rotate_cells(self, names_or_ids, vector): + def rotate_cells( + self, names_or_ids: Iterable[str] | Iterable[int], vector: Iterable[float] + ): """Rotate the identified cell(s) by the specified rotation vector. The rotation is only applied to cells filled with a universe. @@ -1261,7 +1320,9 @@ def rotate_cells(self, names_or_ids, vector): self._change_py_lib_attribs(names_or_ids, vector, 'cell', 'rotation') - def translate_cells(self, names_or_ids, vector): + def translate_cells( + self, names_or_ids: Iterable[str] | Iterable[int], vector: Iterable[float] + ): """Translate the identified cell(s) by the specified translation vector. The translation is only applied to cells filled with a universe. @@ -1284,7 +1345,12 @@ def translate_cells(self, names_or_ids, vector): self._change_py_lib_attribs(names_or_ids, vector, 'cell', 'translation') - def update_densities(self, names_or_ids, density, density_units='atom/b-cm'): + def update_densities( + self, + names_or_ids: Iterable[str] | Iterable[int], + density: float, + density_units: str = "atom/b-cm", + ): """Update the density of a given set of materials to a new value .. note:: If applying this change to a name that is not unique, then @@ -1307,7 +1373,9 @@ def update_densities(self, names_or_ids, density, density_units='atom/b-cm'): self._change_py_lib_attribs(names_or_ids, density, 'material', 'density', density_units) - def update_cell_temperatures(self, names_or_ids, temperature): + def update_cell_temperatures( + self, names_or_ids: Iterable[str] | Iterable[int], temperature: float + ): """Update the temperature of a set of cells to the given value .. note:: If applying this change to a name that is not unique, then @@ -1328,7 +1396,9 @@ def update_cell_temperatures(self, names_or_ids, temperature): self._change_py_lib_attribs(names_or_ids, temperature, 'cell', 'temperature') - def update_material_volumes(self, names_or_ids, volume): + def update_material_volumes( + self, names_or_ids: Iterable[str] | Iterable[int], volume: float + ): """Update the volume of a set of materials to the given value .. note:: If applying this change to a name that is not unique, then @@ -1449,7 +1519,14 @@ def differentiate_mats(self, diff_volume_method: str = None, depletable_only: bo self.geometry.get_all_materials().values() ) - def _generate_infinite_medium_mgxs(self, groups, nparticles, mgxs_path, correction, directory): + def _generate_infinite_medium_mgxs( + self, + groups: openmc.mgxs.EnergyGroups, + nparticles: int, + mgxs_path: PathLike, + correction: str | None, + directory: PathLike, + ): """Generate a MGXS library by running multiple OpenMC simulations, each representing an infinite medium simulation of a single isolated material. A discrete source is used to sample particles, with an equal @@ -1567,7 +1644,11 @@ def _generate_infinite_medium_mgxs(self, groups, nparticles, mgxs_path, correcti mgxs_file.export_to_hdf5(mgxs_path) @staticmethod - def _create_stochastic_slab_geometry(materials, cell_thickness=1.0, num_repeats=100): + def _create_stochastic_slab_geometry( + materials: Sequence[openmc.Material], + cell_thickness: float = 1.0, + num_repeats: int = 100, + ) -> tuple[openmc.Geometry, openmc.stats.Box]: """Create a geometry representing a stochastic "sandwich" of materials in a layered slab geometry. To reduce the impact of the order of materials in the slab, the materials are applied to 'num_repeats' different randomly @@ -1636,7 +1717,14 @@ def _create_stochastic_slab_geometry(materials, cell_thickness=1.0, num_repeats= return geometry, box - def _generate_stochastic_slab_mgxs(self, groups, nparticles, mgxs_path, correction, directory) -> None: + def _generate_stochastic_slab_mgxs( + self, + groups: openmc.mgxs.EnergyGroups, + nparticles: int, + mgxs_path: PathLike, + correction: str | None, + directory: PathLike, + ) -> None: """Generate MGXS assuming a stochastic "sandwich" of materials in a layered slab geometry. While geometry-specific spatial shielding effects are not captured, this method can be useful when the geometry has materials only @@ -1741,7 +1829,14 @@ def _generate_stochastic_slab_mgxs(self, groups, nparticles, mgxs_path, correcti mgxs_file = mgxs_lib.create_mg_library(xs_type='macro', xsdata_names=names) mgxs_file.export_to_hdf5(mgxs_path) - def _generate_material_wise_mgxs(self, groups, nparticles, mgxs_path, correction, directory) -> None: + def _generate_material_wise_mgxs( + self, + groups: openmc.mgxs.EnergyGroups, + nparticles: int, + mgxs_path: PathLike, + correction: str | None, + directory: PathLike, + ) -> None: """Generate a material-wise MGXS library for the model by running the original continuous energy OpenMC simulation of the full material geometry and source, and tally MGXS data for each material. This method @@ -1758,12 +1853,12 @@ def _generate_material_wise_mgxs(self, groups, nparticles, mgxs_path, correction Energy group structure for the MGXS. nparticles : int Number of particles to simulate per batch when generating MGXS. - mgxs_path : str + mgxs_path : PathLike Filename for the MGXS HDF5 file. correction : str Transport correction to apply to the MGXS. Options are None and "P0". - directory : str + directory : PathLike Directory to run the simulation in, so as to contain XML files. """ openmc.reset_auto_ids() @@ -1831,9 +1926,15 @@ def _generate_material_wise_mgxs(self, groups, nparticles, mgxs_path, correction xs_type='macro', xsdata_names=names) mgxs_file.export_to_hdf5(mgxs_path) - def convert_to_multigroup(self, method="material_wise", groups='CASMO-2', - nparticles=2000, overwrite_mgxs_library=False, - mgxs_path: PathLike = "mgxs.h5", correction=None): + def convert_to_multigroup( + self, + method: str = "material_wise", + groups: str = "CASMO-2", + nparticles: int = 2000, + overwrite_mgxs_library: bool = False, + mgxs_path: PathLike = "mgxs.h5", + correction: str | None = None, + ): """Convert all materials from continuous energy to multigroup. If no MGXS data library file is found, generate one using one or more @@ -1868,7 +1969,7 @@ def convert_to_multigroup(self, method="material_wise", groups='CASMO-2', self.sync_dagmc_universes() self.finalize_lib() break - + # Make sure all materials have a name, and that the name is a valid HDF5 # dataset name for material in self.materials: From f615441f069cea5a835c1302fa84f7f5a9bdf538 Mon Sep 17 00:00:00 2001 From: John Tramm Date: Fri, 9 May 2025 22:10:25 -0500 Subject: [PATCH 024/159] Random Ray Misc Memory Error Fixes (#3405) Co-authored-by: Hunter Belanger --- src/random_ray/flat_source_domain.cpp | 8 +++++--- src/random_ray/random_ray.cpp | 21 +++++++++++++-------- src/random_ray/source_region.cpp | 6 +++++- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/random_ray/flat_source_domain.cpp b/src/random_ray/flat_source_domain.cpp index 8ab30abf207..cd073f35077 100644 --- a/src/random_ray/flat_source_domain.cpp +++ b/src/random_ray/flat_source_domain.cpp @@ -206,9 +206,11 @@ void FlatSourceDomain::set_flux_to_flux_plus_source( int material = source_regions_.material(sr); if (material == MATERIAL_VOID) { source_regions_.scalar_flux_new(sr, g) /= volume; - source_regions_.scalar_flux_new(sr, g) += - 0.5f * source_regions_.external_source(sr, g) * - source_regions_.volume_sq(sr); + if (settings::run_mode == RunMode::FIXED_SOURCE) { + source_regions_.scalar_flux_new(sr, g) += + 0.5f * source_regions_.external_source(sr, g) * + source_regions_.volume_sq(sr); + } } else { double sigma_t = sigma_t_[source_regions_.material(sr) * negroups_ + g]; source_regions_.scalar_flux_new(sr, g) /= (sigma_t * volume); diff --git a/src/random_ray/random_ray.cpp b/src/random_ray/random_ray.cpp index 532840f1f4e..27819591cdd 100644 --- a/src/random_ray/random_ray.cpp +++ b/src/random_ray/random_ray.cpp @@ -518,8 +518,10 @@ void RandomRay::attenuate_flux_flat_source_void( } // Add source to incoming angular flux, assuming void region - for (int g = 0; g < negroups_; g++) { - angular_flux_[g] += srh.external_source(g) * distance; + if (settings::run_mode == RunMode::FIXED_SOURCE) { + for (int g = 0; g < negroups_; g++) { + angular_flux_[g] += srh.external_source(g) * distance; + } } } @@ -688,7 +690,10 @@ void RandomRay::attenuate_flux_linear_source_void( // transport through a void region is greatly simplified. Here we // compute the updated flux moments. for (int g = 0; g < negroups_; g++) { - float spatial_source = srh.external_source(g); + float spatial_source = 0.f; + if (settings::run_mode == RunMode::FIXED_SOURCE) { + spatial_source = srh.external_source(g); + } float new_delta_psi = (angular_flux_[g] - spatial_source) * distance; float h1 = 0.5f; h1 = h1 * angular_flux_[g]; @@ -750,8 +755,10 @@ void RandomRay::attenuate_flux_linear_source_void( } // Add source to incoming angular flux, assuming void region - for (int g = 0; g < negroups_; g++) { - angular_flux_[g] += srh.external_source(g) * distance; + if (settings::run_mode == RunMode::FIXED_SOURCE) { + for (int g = 0; g < negroups_; g++) { + angular_flux_[g] += srh.external_source(g) * distance; + } } } @@ -782,9 +789,7 @@ void RandomRay::initialize_ray(uint64_t ray_id, FlatSourceDomain* domain) fatal_error("Unknown sample method for random ray transport."); } - site.E = lower_bound_index( - data::mg.rev_energy_bins_.begin(), data::mg.rev_energy_bins_.end(), site.E); - site.E = negroups_ - site.E - 1.; + site.E = 0.0; this->from_source(&site); // Locate ray diff --git a/src/random_ray/source_region.cpp b/src/random_ray/source_region.cpp index 6c0df0157f1..69541f7f8ee 100644 --- a/src/random_ray/source_region.cpp +++ b/src/random_ray/source_region.cpp @@ -217,7 +217,11 @@ SourceRegionHandle SourceRegionContainer::get_source_region_handle(int64_t sr) handle.scalar_flux_old_ = &scalar_flux_old(sr, 0); handle.scalar_flux_new_ = &scalar_flux_new(sr, 0); handle.source_ = &source(sr, 0); - handle.external_source_ = &external_source(sr, 0); + if (settings::run_mode == RunMode::FIXED_SOURCE) { + handle.external_source_ = &external_source(sr, 0); + } else { + handle.external_source_ = nullptr; + } handle.scalar_flux_final_ = &scalar_flux_final(sr, 0); handle.tally_task_ = &tally_task(sr, 0); From 7382b5d1c8b00342335b39159a00667ca8768b05 Mon Sep 17 00:00:00 2001 From: Lorenzo Chierici Date: Mon, 12 May 2025 22:48:21 +0200 Subject: [PATCH 025/159] External transfer rates source term (#3088) Co-authored-by: Paul Romano --- docs/source/pythonapi/deplete.rst | 5 +- openmc/deplete/abc.py | 110 ++++-- openmc/deplete/chain.py | 70 +++- openmc/deplete/integrators.py | 56 +-- openmc/deplete/pool.py | 59 ++- openmc/deplete/transfer_rates.py | 347 ++++++++++++++---- .../ref_depletion_with_ext_source.h5 | Bin 0 -> 37328 bytes .../ref_depletion_with_feed.h5 | Bin 37328 -> 37328 bytes .../ref_depletion_with_removal.h5 | Bin 37328 -> 37328 bytes .../ref_depletion_with_transfer.h5 | Bin 37328 -> 37328 bytes .../ref_no_depletion_only_feed.h5 | Bin 37328 -> 37328 bytes .../ref_no_depletion_only_removal.h5 | Bin 37328 -> 37328 bytes .../ref_no_depletion_with_ext_source.h5 | Bin 0 -> 37328 bytes .../ref_no_depletion_with_transfer.h5 | Bin 37328 -> 37328 bytes .../deplete_with_transfer_rates/test.py | 45 ++- .../test_deplete_external_source_rates.py | 170 +++++++++ .../unit_tests/test_deplete_transfer_rates.py | 65 ++-- 17 files changed, 747 insertions(+), 180 deletions(-) create mode 100644 tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_ext_source.h5 create mode 100644 tests/regression_tests/deplete_with_transfer_rates/ref_no_depletion_with_ext_source.h5 create mode 100644 tests/unit_tests/test_deplete_external_source_rates.py diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 3967f1a1810..f112cf8ccff 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -206,14 +206,15 @@ total system energy. The :class:`openmc.deplete.IndependentOperator` uses inner classes subclassed from those listed above to perform similar calculations. -The following classes are used to define transfer rates to model continuous -removal or feed of nuclides during depletion. +The following classes are used to define external source rates or transfer rates +to model continuous removal or feed of nuclides during depletion. .. autosummary:: :toctree: generated :nosignatures: :template: myclass.rst + transfer_rates.ExternalSourceRates transfer_rates.TransferRates Intermediate Classes diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index ab087b98e59..c304b8dc621 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -29,7 +29,7 @@ _SECONDS_PER_DAY, _SECONDS_PER_JULIAN_YEAR from .pool import deplete from .reaction_rates import ReactionRates -from .transfer_rates import TransferRates +from .transfer_rates import TransferRates, ExternalSourceRates __all__ = [ @@ -607,9 +607,14 @@ class Integrator(ABC): next time step. Expected to be of the same shape as ``n0`` transfer_rates : openmc.deplete.TransferRates - Instance of TransferRates class to perform continuous transfer during depletion + Transfer rates for the depletion system used to model continuous + removal/feed between materials. .. versionadded:: 0.14.0 + external_source_rates : openmc.deplete.ExternalSourceRates + External source rates for the depletion system. + + .. versionadded:: 0.15.3 """ @@ -686,6 +691,7 @@ def __init__( self.source_rates = np.asarray(source_rates) self.transfer_rates = None + self.external_source_rates = None if isinstance(solver, str): # Delay importing of cram module, which requires this file @@ -731,11 +737,11 @@ def solver(self, func): self._solver = func - def _timed_deplete(self, n, rates, dt, matrix_func=None): + def _timed_deplete(self, n, rates, dt, i=None, matrix_func=None): start = time.time() results = deplete( - self._solver, self.chain, n, rates, dt, matrix_func, - self.transfer_rates) + self._solver, self.chain, n, rates, dt, i, matrix_func, + self.transfer_rates, self.external_source_rates) return time.time() - start, results @abstractmethod @@ -885,13 +891,14 @@ def integrate( self.operator.finalize() def add_transfer_rate( - self, - material: Union[str, int, Material], - components: Sequence[str], - transfer_rate: float, - transfer_rate_units: str = '1/s', - destination_material: Optional[Union[str, int, Material]] = None - ): + self, + material: str | int | Material, + components: Sequence[str], + transfer_rate: float, + transfer_rate_units: str = '1/s', + timesteps: Sequence[int] | None = None, + destination_material: str | int | Material | None = None + ): """Add transfer rates to depletable material. Parameters @@ -905,18 +912,79 @@ def add_transfer_rate( transfer_rate : float Rate at which elements are transferred. A positive or negative values set removal of feed rates, respectively. - destination_material : openmc.Material or str or int, Optional - Destination material to where nuclides get fed. transfer_rate_units : {'1/s', '1/min', '1/h', '1/d', '1/a'} Units for values specified in the transfer_rate argument. 's' means seconds, 'min' means minutes, 'h' means hours, 'a' means Julian years. + timesteps : list of int, optional + List of timestep indices where to set external source rates. + Defaults to None, which means the external source rate is set for + all timesteps. + destination_material : openmc.Material or str or int, Optional + Destination material to where nuclides get fed. """ if self.transfer_rates is None: - self.transfer_rates = TransferRates(self.operator, self.operator.model) + if hasattr(self.operator, 'model'): + materials = self.operator.model.materials + elif hasattr(self.operator, 'materials'): + materials = self.operator.materials + self.transfer_rates = TransferRates( + self.operator, materials, len(self.timesteps)) + + if self.external_source_rates is not None and destination_material: + raise ValueError('Currently is not possible to set a transfer rate ' + 'with destination matrial in combination with ' + 'external source rates.') + + self.transfer_rates.set_transfer_rate( + material, components, transfer_rate, transfer_rate_units, + timesteps, destination_material) + + def add_external_source_rate( + self, + material: str | int | Material, + composition: dict[str, float], + rate: float, + rate_units: str = 'g/s', + timesteps: Sequence[int] | None = None + ): + """Add external source rates to depletable material. + + Parameters + ---------- + material : openmc.Material or str or int + Depletable material + composition : dict of str to float + External source rate composition vector, where key can be an element + or a nuclide and value the corresponding weight percent. + rate : float + External source rate in units of mass per time. A positive or + negative value corresponds to a feed or removal rate, respectively. + units : {'g/s', 'g/min', 'g/h', 'g/d', 'g/a'} + Units for values specified in the `rate` argument. 's' for seconds, + 'min' for minutes, 'h' for hours, 'a' for Julian years. + timesteps : list of int, optional + List of timestep indices where to set external source rates. + Defaults to None, which means the external source rate is set for + all timesteps. + + """ + if self.external_source_rates is None: + if hasattr(self.operator, 'model'): + materials = self.operator.model.materials + elif hasattr(self.operator, 'materials'): + materials = self.operator.materials + self.external_source_rates = ExternalSourceRates( + self.operator, materials, len(self.timesteps)) + + if self.transfer_rates is not None and self.transfer_rates.index_transfer: + raise ValueError('Currently is not possible to set an external ' + 'source rate in combination with transfer rates ' + 'with destination matrial.') + + self.external_source_rates.set_external_source_rate( + material, composition, rate, rate_units, timesteps) - self.transfer_rates.set_transfer_rate(material, components, transfer_rate, - transfer_rate_units, destination_material) @add_params class SIIntegrator(Integrator): @@ -1047,10 +1115,10 @@ def _get_bos_data_from_operator(self, step_index, step_power, n_bos): return inherited def integrate( - self, - output: bool = True, - path: PathLike = "depletion_results.h5" - ): + self, + output: bool = True, + path: PathLike = "depletion_results.h5" + ): """Perform the entire depletion process across all steps Parameters diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 4998a2c3d90..9f234683a90 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -703,7 +703,7 @@ def form_matrix(self, rates, fission_yields=None): # Return CSC representation instead of DOK return matrix.tocsc() - def form_rr_term(self, tr_rates, mats): + def form_rr_term(self, tr_rates, current_timestep, mats): """Function to form the transfer rate term matrices. .. versionadded:: 0.14.0 @@ -712,6 +712,8 @@ def form_rr_term(self, tr_rates, mats): ---------- tr_rates : openmc.deplete.TransferRates Instance of openmc.deplete.TransferRates + current_timestep : int + Current timestep index mats : string or two-tuple of strings Two cases are possible: @@ -740,32 +742,74 @@ def form_rr_term(self, tr_rates, mats): for i, nuc in enumerate(self.nuclides): elm = re.split(r'\d+', nuc.name)[0] - # Build transfer terms matrices + # Build transfer terms (nuclide transfer only) if isinstance(mats, str): mat = mats - components = tr_rates.get_components(mat) + components = tr_rates.get_components(mat, current_timestep) + if not components: + break if elm in components: - matrix[i, i] = sum(tr_rates.get_transfer_rate(mat, elm)) + matrix[i, i] = sum( + tr_rates.get_external_rate(mat, elm, current_timestep)) elif nuc.name in components: - matrix[i, i] = sum(tr_rates.get_transfer_rate(mat, nuc.name)) + matrix[i, i] = sum( + tr_rates.get_external_rate(mat, nuc.name, current_timestep)) else: matrix[i, i] = 0.0 - #Build transfer terms matrices + + # Build transfer terms (transfer from one material into another) elif isinstance(mats, tuple): dest_mat, mat = mats - if dest_mat in tr_rates.get_destination_material(mat, elm): - dest_mat_idx = tr_rates.get_destination_material(mat, elm).index(dest_mat) - matrix[i, i] = tr_rates.get_transfer_rate(mat, elm)[dest_mat_idx] - elif dest_mat in tr_rates.get_destination_material(mat, nuc.name): - dest_mat_idx = tr_rates.get_destination_material(mat, nuc.name).index(dest_mat) - matrix[i, i] = tr_rates.get_transfer_rate(mat, nuc.name)[dest_mat_idx] + components = tr_rates.get_components(mat, current_timestep, dest_mat) + if elm in components: + matrix[i, i] = tr_rates.get_external_rate( + mat, elm, current_timestep, dest_mat)[0] + elif nuc.name in components: + matrix[i, i] = tr_rates.get_external_rate( + mat, nuc.name, current_timestep, dest_mat)[0] else: matrix[i, i] = 0.0 - #Nothing else is allowed # Return CSC instead of DOK return matrix.tocsc() + def form_ext_source_term(self, ext_source_rates, current_timestep, mat): + """Function to form the external source rate term vectors. + + .. versionadded:: 0.15.3 + + Parameters + ---------- + ext_source_rates : openmc.deplete.ExternalSourceRates + Instance of openmc.deplete.ExternalSourceRates + current_timestep : int + Current timestep index + mat : string + Material id + + Returns + ------- + scipy.sparse.csc_matrix + Sparse vector representing external source term. + + """ + if not ext_source_rates.get_components(mat, current_timestep): + return + # Use DOK as intermediate representation + n = len(self) + vector = sp.dok_matrix((n, 1)) + + for i, nuc in enumerate(self.nuclides): + # Build source term vector + if nuc.name in ext_source_rates.get_components(mat, current_timestep): + vector[i] = sum(ext_source_rates.get_external_rate( + mat, nuc.name, current_timestep)) + else: + vector[i] = 0.0 + + # Return CSC instead of DOK + return vector.tocsc() + def get_branch_ratios(self, reaction="(n,gamma)"): """Return a dictionary with reaction branching ratios diff --git a/openmc/deplete/integrators.py b/openmc/deplete/integrators.py index 50810c88a36..000cb2c41c0 100644 --- a/openmc/deplete/integrators.py +++ b/openmc/deplete/integrators.py @@ -40,8 +40,8 @@ def __call__(self, n, rates, dt, source_rate, _i=None): Time in [s] for the entire depletion interval source_rate : float Power in [W] or source rate in [neutron/sec] - _i : int or None - Iteration index. Not used + _i : int, optional + Current iteration count. Not used Returns ------- @@ -54,7 +54,7 @@ def __call__(self, n, rates, dt, source_rate, _i=None): with predictor """ - proc_time, n_end = self._timed_deplete(n, rates, dt) + proc_time, n_end = self._timed_deplete(n, rates, dt, _i) return proc_time, [n_end], [] @@ -106,12 +106,12 @@ def __call__(self, n, rates, dt, source_rate, _i=None): Eigenvalue and reaction rates from transport simulations """ # deplete across first half of interval - time0, n_middle = self._timed_deplete(n, rates, dt / 2) + time0, n_middle = self._timed_deplete(n, rates, dt / 2, _i) res_middle = self.operator(n_middle, source_rate) # deplete across entire interval with BOS concentrations, # MOS reaction rates - time1, n_end = self._timed_deplete(n, res_middle.rates, dt) + time1, n_end = self._timed_deplete(n, res_middle.rates, dt, _i) return time0 + time1, [n_middle, n_end], [res_middle] @@ -172,26 +172,26 @@ def __call__(self, n_bos, bos_rates, dt, source_rate, _i=None): """ # Step 1: deplete with matrix 1/2*A(y0) time1, n_eos1 = self._timed_deplete( - n_bos, bos_rates, dt, matrix_func=cf4_f1) + n_bos, bos_rates, dt, _i, matrix_func=cf4_f1) res1 = self.operator(n_eos1, source_rate) # Step 2: deplete with matrix 1/2*A(y1) time2, n_eos2 = self._timed_deplete( - n_bos, res1.rates, dt, matrix_func=cf4_f1) + n_bos, res1.rates, dt, _i, matrix_func=cf4_f1) res2 = self.operator(n_eos2, source_rate) # Step 3: deplete with matrix -1/2*A(y0)+A(y2) list_rates = list(zip(bos_rates, res2.rates)) time3, n_eos3 = self._timed_deplete( - n_eos1, list_rates, dt, matrix_func=cf4_f2) + n_eos1, list_rates, dt, _i, matrix_func=cf4_f2) res3 = self.operator(n_eos3, source_rate) # Step 4: deplete with two matrix exponentials list_rates = list(zip(bos_rates, res1.rates, res2.rates, res3.rates)) time4, n_inter = self._timed_deplete( - n_bos, list_rates, dt, matrix_func=cf4_f3) + n_bos, list_rates, dt, _i, matrix_func=cf4_f3) time5, n_eos5 = self._timed_deplete( - n_inter, list_rates, dt, matrix_func=cf4_f4) + n_inter, list_rates, dt, _i, matrix_func=cf4_f4) return (time1 + time2 + time3 + time4 + time5, [n_eos1, n_eos2, n_eos3, n_eos5], @@ -249,17 +249,17 @@ def __call__(self, n_bos, rates, dt, source_rate, _i=None): simulation """ # deplete to end using BOS rates - proc_time, n_ce = self._timed_deplete(n_bos, rates, dt) + proc_time, n_ce = self._timed_deplete(n_bos, rates, dt, _i) res_ce = self.operator(n_ce, source_rate) # deplete using two matrix exponentials list_rates = list(zip(rates, res_ce.rates)) time_le1, n_inter = self._timed_deplete( - n_bos, list_rates, dt, matrix_func=celi_f1) + n_bos, list_rates, dt, _i, matrix_func=celi_f1) time_le2, n_end = self._timed_deplete( - n_inter, list_rates, dt, matrix_func=celi_f2) + n_inter, list_rates, dt, _i, matrix_func=celi_f2) return proc_time + time_le1 + time_le1, [n_ce, n_end], [res_ce] @@ -316,20 +316,20 @@ def __call__(self, n, rates, dt, source_rate, _i=None): """ # Step 1: deplete with matrix A(y0) / 2 - time1, n1 = self._timed_deplete(n, rates, dt, matrix_func=rk4_f1) + time1, n1 = self._timed_deplete(n, rates, dt, _i, matrix_func=rk4_f1) res1 = self.operator(n1, source_rate) # Step 2: deplete with matrix A(y1) / 2 - time2, n2 = self._timed_deplete(n, res1.rates, dt, matrix_func=rk4_f1) + time2, n2 = self._timed_deplete(n, res1.rates, dt, _i, matrix_func=rk4_f1) res2 = self.operator(n2, source_rate) # Step 3: deplete with matrix A(y2) - time3, n3 = self._timed_deplete(n, res2.rates, dt) + time3, n3 = self._timed_deplete(n, res2.rates, dt, _i) res3 = self.operator(n3, source_rate) # Step 4: deplete with matrix built from weighted rates list_rates = list(zip(rates, res1.rates, res2.rates, res3.rates)) - time4, n4 = self._timed_deplete(n, list_rates, dt, matrix_func=rk4_f4) + time4, n4 = self._timed_deplete(n, list_rates, dt, _i, matrix_func=rk4_f4) return (time1 + time2 + time3 + time4, [n1, n2, n3, n4], [res1, res2, res3]) @@ -414,9 +414,9 @@ def __call__(self, n_bos, bos_rates, dt, source_rate, i): self._prev_rates, bos_res.rates, repeat(prev_dt), repeat(dt))) time1, n_inter = self._timed_deplete( - n_bos, le_inputs, dt, matrix_func=leqi_f1) + n_bos, le_inputs, dt, i, matrix_func=leqi_f1) time2, n_eos0 = self._timed_deplete( - n_inter, le_inputs, dt, matrix_func=leqi_f2) + n_inter, le_inputs, dt, i, matrix_func=leqi_f2) res_inter = self.operator(n_eos0, source_rate) @@ -425,9 +425,9 @@ def __call__(self, n_bos, bos_rates, dt, source_rate, i): repeat(prev_dt), repeat(dt))) time3, n_inter = self._timed_deplete( - n_bos, qi_inputs, dt, matrix_func=leqi_f3) + n_bos, qi_inputs, dt, i, matrix_func=leqi_f3) time4, n_eos1 = self._timed_deplete( - n_inter, qi_inputs, dt, matrix_func=leqi_f4) + n_inter, qi_inputs, dt, i, matrix_func=leqi_f4) # store updated rates self._prev_rates = copy.deepcopy(bos_res.rates) @@ -478,7 +478,7 @@ def __call__(self, n_bos, bos_rates, dt, source_rate, _i=None): Eigenvalue and reaction rates from intermediate transport simulations """ - proc_time, n_eos = self._timed_deplete(n_bos, bos_rates, dt) + proc_time, n_eos = self._timed_deplete(n_bos, bos_rates, dt, _i) n_inter = copy.deepcopy(n_eos) # Begin iteration @@ -494,9 +494,9 @@ def __call__(self, n_bos, bos_rates, dt, source_rate, _i=None): list_rates = list(zip(bos_rates, res_bar.rates)) time1, n_inter = self._timed_deplete( - n_bos, list_rates, dt, matrix_func=celi_f1) + n_bos, list_rates, dt, _i, matrix_func=celi_f1) time2, n_inter = self._timed_deplete( - n_inter, list_rates, dt, matrix_func=celi_f2) + n_inter, list_rates, dt, _i, matrix_func=celi_f2) proc_time += time1 + time2 # end iteration @@ -560,9 +560,9 @@ def __call__(self, n_bos, bos_rates, dt, source_rate, i): inputs = list(zip(self._prev_rates, bos_rates, repeat(prev_dt), repeat(dt))) proc_time, n_inter = self._timed_deplete( - n_bos, inputs, dt, matrix_func=leqi_f1) + n_bos, inputs, dt, i, matrix_func=leqi_f1) time1, n_eos = self._timed_deplete( - n_inter, inputs, dt, matrix_func=leqi_f2) + n_inter, inputs, dt, i, matrix_func=leqi_f2) proc_time += time1 n_inter = copy.deepcopy(n_eos) @@ -580,9 +580,9 @@ def __call__(self, n_bos, bos_rates, dt, source_rate, i): inputs = list(zip(self._prev_rates, bos_rates, res_bar.rates, repeat(prev_dt), repeat(dt))) time1, n_inter = self._timed_deplete( - n_bos, inputs, dt, matrix_func=leqi_f3) + n_bos, inputs, dt, i, matrix_func=leqi_f3) time2, n_inter = self._timed_deplete( - n_inter, inputs, dt, matrix_func=leqi_f4) + n_inter, inputs, dt, i, matrix_func=leqi_f4) proc_time += time1 + time2 return proc_time, [n_eos, n_inter], [res_bar] diff --git a/openmc/deplete/pool.py b/openmc/deplete/pool.py index 27ecaa4dd8b..03b050af38f 100644 --- a/openmc/deplete/pool.py +++ b/openmc/deplete/pool.py @@ -5,7 +5,7 @@ from itertools import repeat, starmap from multiprocessing import Pool -from scipy.sparse import bmat +from scipy.sparse import bmat, hstack, vstack, csc_matrix import numpy as np from openmc.mpi import comm @@ -40,8 +40,8 @@ def _distribute(items): return items[j:j + chunk_size] j += chunk_size -def deplete(func, chain, n, rates, dt, matrix_func=None, transfer_rates=None, - *matrix_args): +def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, + transfer_rates=None, external_source_rates=None, *matrix_args): """Deplete materials using given reaction rates for a specified time Parameters @@ -58,15 +58,21 @@ def deplete(func, chain, n, rates, dt, matrix_func=None, transfer_rates=None, Reaction rates (from transport operator) dt : float Time in [s] to deplete for + current_timestep : int + Current timestep index maxtrix_func : callable, optional Function to form the depletion matrix after calling ``matrix_func(chain, rates, fission_yields)``, where ``fission_yields = {parent: {product: yield_frac}}`` Expected to return the depletion matrix required by ``func`` transfer_rates : openmc.deplete.TransferRates, Optional - Object to perform continuous reprocessing. + Transfer rates for continuous removal/feed. .. versionadded:: 0.14.0 + external_source_rates : openmc.deplete.ExternalSourceRates, Optional + External source rates for continuous removal/feed. + + .. versionadded:: 0.15.3 matrix_args: Any, optional Additional arguments passed to matrix_func @@ -93,15 +99,17 @@ def deplete(func, chain, n, rates, dt, matrix_func=None, transfer_rates=None, matrices = map(matrix_func, repeat(chain), rates, fission_yields, *matrix_args) - if transfer_rates is not None: + if (transfer_rates is not None and + current_timestep in transfer_rates.external_timesteps): # Calculate transfer rate terms as diagonal matrices transfers = map(chain.form_rr_term, repeat(transfer_rates), - transfer_rates.local_mats) + repeat(current_timestep), transfer_rates.local_mats) + # Subtract transfer rate terms from Bateman matrices matrices = [matrix - transfer for (matrix, transfer) in zip(matrices, transfers)] - if len(transfer_rates.index_transfer) > 0: + if current_timestep in transfer_rates.index_transfer: # Gather all on comm.rank 0 matrices = comm.gather(matrices) n = comm.gather(n) @@ -112,10 +120,12 @@ def deplete(func, chain, n, rates, dt, matrix_func=None, transfer_rates=None, n = [n_elm for n_mat in n for n_elm in n_mat] # Calculate transfer rate terms as diagonal matrices - transfer_pair = { - mat_pair: chain.form_rr_term(transfer_rates, mat_pair) - for mat_pair in transfer_rates.index_transfer - } + transfer_pair = {} + for mat_pair in transfer_rates.index_transfer[current_timestep]: + transfer_matrix = chain.form_rr_term(transfer_rates, + current_timestep, + mat_pair) + transfer_pair[mat_pair] = transfer_matrix # Combine all matrices together in a single matrix of matrices # to be solved in one go @@ -129,7 +139,7 @@ def deplete(func, chain, n, rates, dt, matrix_func=None, transfer_rates=None, if row == col: # Fill the diagonals with the Bateman matrices cols.append(matrices[row]) - elif mat_pair in transfer_rates.index_transfer: + elif mat_pair in transfer_rates.index_transfer[current_timestep]: # Fill the off-diagonals with the transfer pair matrices cols.append(transfer_pair[mat_pair]) else: @@ -155,6 +165,25 @@ def deplete(func, chain, n, rates, dt, matrix_func=None, transfer_rates=None, return n_result + if (external_source_rates is not None and + current_timestep in external_source_rates.external_timesteps): + # Calculate external source term vectors + sources = map(chain.form_ext_source_term, repeat(external_source_rates), + repeat(current_timestep), external_source_rates.local_mats) + + # stack vector column at the end of the matrix + matrices = [ + hstack([matrix, source]) + for matrix, source in zip(matrices, sources) + ] + + # Add a last row of zeroes to the matrices and append 1 to the last row + # of the nuclide vectors + for i, matrix in enumerate(matrices): + if not np.equal(*matrix.shape): + matrices[i] = vstack([matrix, csc_matrix([0]*matrix.shape[1])]) + n[i] = np.append(n[i], 1.0) + inputs = zip(matrices, n, repeat(dt)) if USE_MULTIPROCESSING: @@ -163,4 +192,10 @@ def deplete(func, chain, n, rates, dt, matrix_func=None, transfer_rates=None, else: n_result = list(starmap(func, inputs)) + # Remove extra value at the end of the nuclide vectors + if (external_source_rates is not None and + current_timestep in external_source_rates.external_timesteps): + external_source_rates.reformat_nuclide_vectors(n) + external_source_rates.reformat_nuclide_vectors(n_result) + return n_result diff --git a/openmc/deplete/transfer_rates.py b/openmc/deplete/transfer_rates.py index 01c9b2e3534..4c28c7d1506 100644 --- a/openmc/deplete/transfer_rates.py +++ b/openmc/deplete/transfer_rates.py @@ -1,31 +1,31 @@ +from collections import defaultdict from numbers import Real import re +from typing import Sequence + +import numpy as np from openmc.checkvalue import check_type, check_value from openmc import Material -from openmc.data import ELEMENT_SYMBOL +from openmc.data import ELEMENT_SYMBOL, isotopes, AVOGADRO, atomic_mass +from .results import _SECONDS_PER_MINUTE, _SECONDS_PER_HOUR, \ + _SECONDS_PER_DAY, _SECONDS_PER_JULIAN_YEAR -class TransferRates: - """Class for defining continuous removals and feeds. - Molten Salt Reactors (MSRs) benefit from continuous reprocessing, - which removes fission products and feeds fresh fuel into the system. MSRs - inspired the development of this class. - - An instance of this class can be passed directly to an instance of one of - the :class:`openmc.deplete.Integrator` classes. +class ExternalRates: + """External rates class for defining addition terms of depletion equation. - .. versionadded:: 0.14.0 + .. versionadded:: 0.15.3 Parameters ---------- operator : openmc.TransportOperator Depletion operator - model : openmc.Model - OpenMC model containing materials and geometry. If using - :class:`openmc.deplete.CoupledOperator`, the model must also contain - a :class:`opnemc.Settings` object. + materials : openmc.Materials + OpenMC materials. + number_of_timesteps : int + Total number of depletion timesteps Attributes ---------- @@ -33,22 +33,25 @@ class TransferRates: All burnable material IDs. local_mats : list of str All burnable material IDs being managed by a single process - transfer_rates : dict of str to dict - Container of transfer rates, components (elements and/or nuclides) and - destination material - index_transfer : Set of pair of str - Pair of strings needed to build final matrix (destination_material, mat) + number_of_timesteps : int + Total number of depletion timesteps + external_rates : dict of str to dict + Container of timesteps, external rates, components (elements and/or + nuclides) and optionally destination material + external_timesteps : list of int + Container of all timesteps indeces with an external rate defined. """ - def __init__(self, operator, model): + def __init__(self, operator, materials, number_of_timesteps): - self.materials = model.materials + self.materials = materials self.burnable_mats = operator.burnable_mats self.local_mats = operator.local_mats + self.number_of_timesteps = number_of_timesteps - #initialize transfer rates container dict - self.transfer_rates = {mat: {} for mat in self.burnable_mats} - self.index_transfer = set() + # initialize transfer rates container dict + self.external_rates = {mat: defaultdict(list) for mat in self.burnable_mats} + self.external_timesteps = [] def _get_material_id(self, val): """Helper method for getting material id from Material obj or name. @@ -71,7 +74,7 @@ def _get_material_id(self, val): check_value('Material ID', str(val), self.burnable_mats) else: check_value('Material name', val, - [mat.name for mat in self.materials if mat.depletable]) + [mat.name for mat in self.materials if mat.depletable]) val = [mat.id for mat in self.materials if mat.name == val][0] elif isinstance(val, int): @@ -79,7 +82,13 @@ def _get_material_id(self, val): return str(val) - def get_transfer_rate(self, material, component): + def get_external_rate( + self, + material: str | int | Material, + component: str, + timestep: int, + destination_material: str | int | Material | None = None + ): """Return transfer rate for given material and element. Parameters @@ -88,62 +97,114 @@ def get_transfer_rate(self, material, component): Depletable material component : str Element or nuclide to get transfer rate value + timestep : int + Current timestep index + destination_material : openmc.Material or str or int, Optional + Destination material to where nuclides get fed Returns ------- - transfer_rate : list of floats - Transfer rate values + external_rate : list of floats + External rate values """ material_id = self._get_material_id(material) check_type('component', component, str) - return [i[0] for i in self.transfer_rates[material_id][component]] + if destination_material is not None: + dest_mat_id = self._get_material_id(destination_material) + return [i[1] for i in self.external_rates[material_id][component] + if timestep in i[0] and dest_mat_id == i[2]] + else: + return [i[1] for i in self.external_rates[material_id][component] + if timestep in i[0]] - def get_destination_material(self, material, component): - """Return destination material for given material and - component, if defined. + def get_components(self, material, timestep, destination_material=None): + """Extract removing elements and/or nuclides for a given material at a + given timestep Parameters ---------- material : openmc.Material or str or int Depletable material - component : str - Element or nuclide that gets transferred to another material. + timestep : int + Current timestep index + destination_material : openmc.Material or str or int, Optional + Destination material to where nuclides get fed Returns ------- - destination_material_id : list of str - Depletable material ID to where the element or nuclide gets - transferred + components : list + List of elements or nuclides with external rates set at a given + timestep """ material_id = self._get_material_id(material) - check_type('component', component, str) - if component in self.transfer_rates[material_id]: - return [i[1] for i in self.transfer_rates[material_id][component]] + if destination_material is not None: + dest_mat_id = self._get_material_id(destination_material) else: - return [] + dest_mat_id = None + + all_components = [] + if material_id in self.external_rates: + mat_components = self.external_rates[material_id] + + for component in mat_components: + if dest_mat_id: + # check for both timestep and destination material ids + if np.isin(timestep, [val[0] for val in mat_components[component]]) and \ + np.isin(dest_mat_id, [val[2] for val in mat_components[component]]): + all_components.append(component) + else: + # check only for timesteps + if np.isin(timestep, [val[0] for val in mat_components[component]]): + all_components.append(component) + return all_components - def get_components(self, material): - """Extract removing elements and/or nuclides for a given material - Parameters - ---------- - material : openmc.Material or str or int - Depletable material +class TransferRates(ExternalRates): + """Class for defining continuous removals and feeds. - Returns - ------- - elements : list - List of elements and nuclides where transfer rates exist + Molten Salt Reactors (MSRs) benefit from continuous reprocessing, + which removes fission products and feeds fresh fuel into the system. MSRs + inspired the development of this class. - """ - material_id = self._get_material_id(material) - if material_id in self.transfer_rates: - return self.transfer_rates[material_id].keys() + An instance of this class can be passed directly to an instance of one of + the :class:`openmc.deplete.Integrator` classes. + + .. versionadded:: 0.14.0 + + Parameters + ---------- + operator : openmc.TransportOperator + Depletion operator + materials : openmc.Materials + OpenMC materials. + number_of_timesteps : int + Total number of depletion timesteps + + Attributes + ---------- + burnable_mats : list of str + All burnable material IDs. + local_mats : list of str + All burnable material IDs being managed by a single process + external_rates : dict of str to dict + Container of timesteps, transfer rates, components (elements and/or + nuclides) and destination material + external_timesteps : list of int + Container of all timesteps indeces with an external rate defined. + index_transfer : Set of pair of str + Pair of strings needed to build final matrix (destination_material, mat) + """ + + def __init__(self, operator, materials, number_of_timesteps): + super().__init__(operator, materials, number_of_timesteps) + self.index_transfer = defaultdict(list) + self.chain_nuclides = [nuc.name for nuc in operator.chain.nuclides] def set_transfer_rate(self, material, components, transfer_rate, - transfer_rate_units='1/s', destination_material=None): + transfer_rate_units='1/s', timesteps=None, + destination_material=None): """Set element and/or nuclide transfer rates in a depletable material. Parameters @@ -157,11 +218,14 @@ def set_transfer_rate(self, material, components, transfer_rate, transfer_rate : float Rate at which elements and/or nuclides are transferred. A positive or negative value corresponds to a removal or feed rate, respectively. - destination_material : openmc.Material or str or int, Optional - Destination material to where nuclides get fed. transfer_rate_units : {'1/s', '1/min', '1/h', '1/d', '1/a'} Units for values specified in the transfer_rate argument. 's' for seconds, 'min' for minutes, 'h' for hours, 'a' for Julian years. + timesteps : list of int, Optional + List of timestep indeces where to set transfer rates. + Default to None means the transfer rate is set for all timesteps. + destination_material : openmc.Material or str or int, Optional + Destination material to where nuclides get fed. """ material_id = self._get_material_id(material) @@ -183,19 +247,25 @@ def set_transfer_rate(self, material, components, transfer_rate, if transfer_rate_units in ('1/s', '1/sec'): unit_conv = 1 elif transfer_rate_units in ('1/min', '1/minute'): - unit_conv = 60 + unit_conv = _SECONDS_PER_MINUTE elif transfer_rate_units in ('1/h', '1/hr', '1/hour'): - unit_conv = 60*60 + unit_conv = _SECONDS_PER_HOUR elif transfer_rate_units in ('1/d', '1/day'): - unit_conv = 24*60*60 + unit_conv = _SECONDS_PER_DAY elif transfer_rate_units in ('1/a', '1/year'): - unit_conv = 365.25*24*60*60 + unit_conv = _SECONDS_PER_JULIAN_YEAR else: - raise ValueError('Invalid transfer rate unit ' - f'"{transfer_rate_units}"') + raise ValueError(f'Invalid transfer rate unit "{transfer_rate_units}"') + + if timesteps is not None: + for timestep in timesteps: + check_value('timestep', timestep, range(self.number_of_timesteps)) + timesteps = np.array(timesteps) + else: + timesteps = np.arange(self.number_of_timesteps) for component in components: - current_components = self.transfer_rates[material_id].keys() + current_components = self.external_rates[material_id].keys() split_component = re.split(r'\d+', component) element = split_component[0] if element not in ELEMENT_SYMBOL.values(): @@ -219,11 +289,144 @@ def set_transfer_rate(self, material, components, transfer_rate, f'where element {element} already has ' 'a transfer rate.') - if component in self.transfer_rates[material_id]: - self.transfer_rates[material_id][component].append( - (transfer_rate / unit_conv, destination_material_id)) - else: - self.transfer_rates[material_id][component] = [ - (transfer_rate / unit_conv, destination_material_id)] + self.external_rates[material_id][component].append( + (timesteps, transfer_rate/unit_conv, destination_material_id)) + if destination_material_id is not None: - self.index_transfer.add((destination_material_id, material_id)) + for timestep in timesteps: + self.index_transfer[timestep].append( + (destination_material_id, material_id)) + + self.external_timesteps = np.unique(np.concatenate( + [self.external_timesteps, timesteps])) + + +class ExternalSourceRates(ExternalRates): + """Class for defining external source rates. + + An instance of this class can be passed directly to an instance of one of + the :class:`openmc.deplete.Integrator` classes. + + .. versionadded:: 0.15.3 + + Parameters + ---------- + operator : openmc.TransportOperator + Depletion operator + materials : openmc.Materials + OpenMC materials. + number_of_timesteps : int + Total number of depletion timesteps + + Attributes + ---------- + burnable_mats : list of str + All burnable material IDs. + local_mats : list of str + All burnable material IDs being managed by a single process + external_timesteps : list of int + Container of all timesteps indeces with an external rate defined. + external_rates : dict of str to dict + Container of timesteps external source rates, and components + (elements and/or nuclides) + """ + + def reformat_nuclide_vectors(self, vectors): + """Remove last element of nuclide vector that was added for handling + external source rates by the depletion solver. + + Parameters + ---------- + vectors : list of array + List of nuclides vector to reformat + + """ + for mat_index, i in enumerate(self.local_mats): + if self.external_rates[i]: + vectors[mat_index] = vectors[mat_index][:-1] + + def set_external_source_rate( + self, + material: str | int | Material, + composition: dict[str, float], + rate: float, + rate_units: str = 'g/s', + timesteps: Sequence[int] | None = None + ): + """Set element and/or nuclide composition vector external source rates + to a depletable material. + + Parameters + ---------- + material : openmc.Material or str or int + Depletable material + composition : dict of str to float + External source rate composition vector, where key can be an element + or a nuclide and value the corresponding weight percent. + rate : float + External source rate in units of mass per time. A positive or + negative value corresponds to a feed or removal rate, respectively. + units : {'g/s', 'g/min', 'g/h', 'g/d', 'g/a'} + Units for values specified in the `rate` argument. 's' for seconds, + 'min' for minutes, 'h' for hours, 'a' for Julian years. + timesteps : list of int, optional + List of timestep indices where to set external source rates. Default + to None, which means the external source rate is set for all + timesteps. + + """ + + material_id = self._get_material_id(material) + check_type('rate', rate, Real) + check_type('composition', composition, dict, str) + + if rate_units in ('g/s', 'g/sec'): + unit_conv = 1 + elif rate_units in ('g/min', 'g/minute'): + unit_conv = _SECONDS_PER_MINUTE + elif rate_units in ('g/h', 'g/hr', 'g/hour'): + unit_conv = _SECONDS_PER_HOUR + elif rate_units in ('g/d', 'g/day'): + unit_conv = _SECONDS_PER_DAY + elif rate_units in ('g/a', 'g/year'): + unit_conv = _SECONDS_PER_JULIAN_YEAR + else: + raise ValueError(f'Invalid external source rate unit "{rate_units}"') + + if timesteps is not None: + for timestep in timesteps: + check_value('timestep', timestep, range(self.number_of_timesteps)) + timesteps = np.asarray(timesteps) + else: + timesteps = np.arange(self.number_of_timesteps) + + components = composition.keys() + percents = composition.values() + norm_percents = [float(i) / sum(percents) for i in percents] + + atoms_per_nuc = {} + for component, percent in zip(components, norm_percents): + split_component = re.split(r'\d+', component) + element = split_component[0] + if element not in ELEMENT_SYMBOL.values(): + raise ValueError(f'{component} is not a valid nuclide or element.') + + if len(split_component) == 1: + if not isotopes(component): + raise ValueError(f'Cannot add element {component} ' + 'as it is not naturally abundant. ' + 'Specify a nuclide vector instead.') + for nuc, frac in isotopes(component): + atoms_per_nuc[nuc] = (rate / atomic_mass(nuc) * AVOGADRO * + frac * percent / unit_conv) + + else: + atoms_per_nuc[component] = (rate / atomic_mass(component) * + AVOGADRO * percent / unit_conv) + + for nuc, val in atoms_per_nuc.items(): + self.external_rates[material_id][nuc].append((timesteps, val, None)) + + self.external_timesteps = np.unique(np.concatenate( + [self.external_timesteps, timesteps] + )) diff --git a/tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_ext_source.h5 b/tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_ext_source.h5 new file mode 100644 index 0000000000000000000000000000000000000000..aa09e1bcf4e352f94798f2cc266718b5f77fcec1 GIT binary patch literal 37328 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 delta 693 zcmcbxnCZe|rVXd;7#TL7wv%TBQm^gj1F6Z~j{kw2ZYO>aXSOqlGuvgUC!@?{$G-Ub zZIjMO#I5AFV}JsCzP}w+i{8Nbjt>ml!{a8{!sIPqYq9+MAOV+uv2vD|`KhIF{_f=q zAA~(rg!4UT6u4|LHndY)@AJ>@>mS>D=M3+!(^0qku~2fA_6Ns$yNhK9Vr3uxu@$sP zYJa%ys;$ZYd+`cA3UC8Xy`0Q)pp4lb=73vnE8JqT7r-4<#(!kp(aS1u^%MP%Z@v_? zAI{JI^StFlg&~~3)nlHyR>xUO(^j?3eJ$VqozFzLkET!FI6=OC`mEK9jrZulbv;gT zmHu|h#BN^BtSN^W|Jk0o(;8qOu3@()@niL@e~xzT+>dqI&iuD6=2-ghbN*G^DWCW6 zt(QTBMM8Z_Ra#Xa9#tDI9hB<|Z#b*vCdpUNupP1r&||wBXb{ diff --git a/tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_removal.h5 b/tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_removal.h5 index 4b24bed52ad30a270df65704cecf8aef59078557..271af24103cda8f8c4886da551422a824dcde360 100644 GIT binary patch delta 720 zcmcbxnCZe|rVXd;7@0Powv%TBQm^gj1F6Z~j{kw2ZYO>aXSOqlGuvgUC!_3S$G-Ub z`blRb;#Tt8F+hO|-`|d^MQ`AI!v_ZK;c*jeVe-PSwOIarkbukYUOCIl{M1r7fAR8# z55gWQ!ue)13S71r8`}L}=kw3*>mS=a=M3+!(^0qky-;$M_6Ns$yBTE%Vr3uxvHfSB z)c$bYRok%t_u>_L6yOFdcsZHnKpC?=%mF*xR=CAvFMvD9ng7VTqnB0S>QDF|-+U=( zKb-IX=XuMA3PU*m?j1EDPz77M!#20J_n|UnY3QEOd`t6%^;ry)v>$QDbKiW@R zHu+S)l5Tw95&LVOWXh8J4V)i-%?pV7q2jzS|7oOSos~0-Rkiu-Zz|4;nzpKKCa0XH zPnMjJUa#TK<#D4A5yZl)U3P48Lfz{@F?$ zH(nv%Fo^RFe%OS1TEZ2iO+31{_^BzJzm#RxO@U1x?VT4* LUNupP1r)>paXSOqlGuvgUC!@?{$G-Ub zU6am8#I5AFV}JrDzP}w+i{8NbE)NXa!{a8{!sM-AYq9+MAOV+uy>gb9`KhIF{=Vf4 zAA~(rg!8>-6u4|LHnh`P@AJ>@>mS?u=M3+!(^0qku~2fA_6Ns$yDMb}Vr3uxu@$jM zYJa%ys;$}od+`cA3UC9?yqwH(pp4lb=74){E8JqT7r-4<#eZbo(aS1u_0#;1Z@v_? zAI{JE^StFlg&~~3>ynn>^fG5CUx$IA>jm4vW*!T;f^%Xq{q{||aQ=FM_1eCzAMF#C zPCnJIq<^@FkP;p+E|1{FE&dT|ZWwrV2Zz|5pnzpKKCa0WM zOqQIGUT^5m<#D4A5yXP4U3P48Lfz{@Jn| zH(?*JrcAgLsoqrpqj?rSKpQy)n=1-F!rp9Q#pUwCd0i zxc=&VFURQ>MsWVhITbwGu>nv%Fo;VGe%OS1TEZ2yO+31{_^BzJ-^nuTrog6;_FYRR LubQaD0t#XPxH8vc diff --git a/tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_transfer.h5 b/tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_transfer.h5 index 51173f778b945f5c666bd9a8ed9ace75f47eb0ec..25365f6f3a486e7cc6ffa3ce844d4ed3fa17a95c 100644 GIT binary patch delta 747 zcmcbxnCZe|rVXd;7@0Powv%TBQm^gj1F6Z~j{kw2ZYO>aXSOqlGuvgUC!_3S$G-Ub znn`CQ;#Tt8F+hPC-`|d^MQ`AIod*W(;c*jeVe)dXwOIarkbujtT{+9k{M1r7fA#W( z55gWQ!ubv}3S71r8``n1_xWe{^^fg=bB6cV>8RWNS}3_n`-5Y>-Q2PRv9b^U*#0+9 zYJa%ys%^^ud+`cA3UCAVznsi+pp4lb=72SBE8JqT7r-54$A4tq(aS1u^=ti)Z@v_? zAI?ws^StFlg&~}O?UI(@^fG5CUx$HV!V9*8%{&%x1(U>L`t6%^;ruNE>$QDbKiW$y zn|!KYIYqL_{>mqrvgCdP=lfst0-}DXI4{V58tGVP<^0dG+I;pm6=y|FTh%s`Q%=*? zFN}Km^|q7b6BRc^s6l{FfSF{d#K&8>4(#Mfvfj7Ezoh^ zJvnfKzTxvU{%g7!d~kQHYY!LjzWo}`UoVu~79}td9uV%6H%^eRSKe)LTyU`-TvY;- zPRfT!JG-#7kbTw*|JmB}?}#v$(6rkf@axu!eU5h5#G9{8-1^UU!k!BcORitDz1ih? zCPfMne=FouU#G=p=3n$G%*2jC9K&N)0GkkJIr z-*Q~&^`-5uaQ?N71A)5=Ea7~~OTuLjZomy>@MwKGX-dW?`+quQC{jF delta 747 zcmcbxnCZe|rVXd;7#TL7wv%TBQm^gj1F6Z~j{kw2ZYO>aXSOqlGuvgUC!@?{$G-Ub zZIjMO#I5AFV}JsCzP}w+i{8NbP7e&)!{a8{!sLx#Yq9+MAOV+uwsMx2`KhIF{@&#a zAA~(rg!A2J6u4|LHndY;@AJ>@>mS>D=M3+!(^0qku~2fA_6Ns$yGvyUVr3uxu@$sP zYJa%ys;%+=d+`cA3UC8Xy`0Q)pp4lb=75`SE8JqT7r-4<#(!kp(aS1u_5J?GH(v_c z59eq8dEWA&!Vu2ia!E^YdYLnnufxF5@`CMPGmiya!DX?Se)}d}IDfssdTrmm zC!gw9PMKS1f8~=*S#rOD^WCp`0Z~6xoR{Z6jdZNDa%QrsHlO`X#aTad^^4A~US^?dzuUT8Iwe*<{c4K~%u6}<9%?si`r-6R;OhNN3v`^f zP7a))Z}=*W|C(+FAKV>V+QS9BZ@-4~4+-VAMF~uV2ZYt+jT7YS6?a=47hJ3dS5?fU zlky?b&Mqb`WS{lIf41iQJ0i>_H0`zq{JOPbpQGJ#@#bq2xBjzTwCBRZlIz!OuXK5y zNs&UtpTN-%vElm=!GBgNIa7VsLb$D*YDrrIjNzdb&5)j;rnCOy0k{KVat==jWHf>E zR~;96eQCQZoPRLmK;W(dOE~}SMd7jsH{b>`c(uNqG$rGc{af|Pt0pQl1A`v`ms{(o diff --git a/tests/regression_tests/deplete_with_transfer_rates/ref_no_depletion_only_feed.h5 b/tests/regression_tests/deplete_with_transfer_rates/ref_no_depletion_only_feed.h5 index d53adc7d8413c904289119fcb922be662b396f96..d080b447093018f62b3eced413db81fa606d3338 100644 GIT binary patch delta 77 zcmcbxnCZe|rVXd;7@0Powv%TBQm^gj1F6Z~j{kw2ZYO>aXSOqlGuvgUC!_3U$G+8! Z%msdjCnvhfPrM+sSz*E&MiwC79spTR99sYY delta 77 zcmcbxnCZe|rVXd;7#TL7wv%TBQm^gj1F6Z~j{kw2ZYO>aXSOqlGuvgUC!@?}$G+8! Z%zKV6nw;n=KkaXSOqlGuvgUC!_3U$G+8! Z%!S%VCnvhfPrM+sSz*E&MiwC79spL192)=t delta 77 zcmcbxnCZe|rVXd;7#TL7wv%TBQm^gj1F6Z~j{kw2ZYO>aXSOqlGuvgUC!@?}$G+8! Z%u{q0O-^)`pLjuNv%-Wmj4VLDJpfd>91s8i diff --git a/tests/regression_tests/deplete_with_transfer_rates/ref_no_depletion_with_ext_source.h5 b/tests/regression_tests/deplete_with_transfer_rates/ref_no_depletion_with_ext_source.h5 new file mode 100644 index 0000000000000000000000000000000000000000..f3b1b171ebcab9d99fb7b73f5b8273ac72187971 GIT binary patch literal 37328 zcmeHPO>7%Q6rQz{)@=i%rIb)nv=vA-|4y5>0o2x|PDw8`f`WWN;56Abmhwk_N)Q~X z3Q*tx=`AM|2_dBiP!2sHaq$7998)Bu9>Xoia;TI`Rc80SFB?xhcD#vG(>@|;Ck?^OOLkx&h)IXJ}Eef}(5rxw)FpW_3E%SkS0V|3tXjecJX zw>PtXH%<+6+2UNA29urKSB(F8G$OhaG{=j?-QK8sRVSvs}8i zE8orjiuvHzO{z1P*ii}9q`mu!H!_+Z8dS{*O{1WP409Y}2Gc=%IDNnulue4|)eb#A zp)Te7y|K@SJr-9_YQf#Q0{LEVSPSSG-4Sneynk$z1syHn%YbjNf0_B#!aO3rIaa<+ zQ$#SL0j!boVMhRb%ko0uPwnhKSbr-Y9Oe@KnT9}><@@iR&p4`4P4KvMro4h6C}@%9 z)8*xelS(Zx4`8Rs3#_y71nGm^64w)ZsJ)%1OP?%1?}O^KU3V9EkwsH%7{vi)jROzL zewnvE5HRBac$DVr1FQs3z(=%4Eh!Gf_*$ye?d_xq^Ps~uu2aKJ#d6bJ|LyUpmi)iR z;regl)ud9v!W^|t=PwDBAMEw~n7bx_F_zz?DDpXKyV|SAT^Z^hAJAFKZub;VkydTH zRNH+c&h~tH@!YN7|2bugODT@85|<3XrCJgJTU@$r@Eq}Cv*Hr?WX6?h`2@S7^69V1 zeIMPpcT@AJbiQxqE0y-w>8A*0m(iZ!gs!tB`qKEYApkzj@xs8Dc~08NmQ&9a%O$zq zxgr3;`Ydr>HSr4Xi|q;W)b6pN0e5hG;G#F;B4_niWXoGQ4b@}Q!NMJ#wfgz)h}Ydi zx6iuAyj7Yi>9NJDJAB?GUIj4AR|N!Y@oJj=2*VK{HY;9%XJ)(!`4Nq0jcjnJCmPS7 z$Huel0h&m71`m<490E3;DfT0btrgGA>vhPFXgq6RgF`*hcm_Q-o)ui0NO%Shk+K{D zHlFpl#iMG)Gc(@}`4Nq0usYNejc3qf<5}_rk`SH&DJjb#VB^`s^TnfT#j~k{G|wE* z7xE(-&+v0{s3#iFpvT6usY7J9@T}NRq*fXM8_(L=k1(oMJX_}LLJQ9q@*^70_~+9N z-v@yn8_$+sB4mVT@DM4>Azy40eWkqVWuRY&Az5qebIbWy-A3W!qWZiuS=>YSZ`^*%}EpT28IWteDr_+>bXT^{+uk&fvH^XrWa%P#vft&65#ygBJ&cDPJ zASPug1Z;6-+Tba848JO+tC}|O!Hf&l@&R`F@9G7|+~DB7*am7q{s(!~cd*fT&)e1? z*Ke}=eYW>Y8uhb$et#?;3sfYO8)%Nl8mG?&M?rTWqO1P29KtsXQJMtbYw^eiT~1?Q z-kSG+O*~MqgbU=V$A>mX@z}P-3uLr!*lK)y)ws;*!)h}sQfht==*KKa^4 zr$688p}O)sCK~7wPqotSj%WqC)5~A>2CsDYkMq#6y#Mx*QlmrTBR$?HzEm)E_P_py`r7kH>v!Vi&!6plReyfmz?gWfAw?@G^50eGB^Mf9!=tr$5P}ZTZN;DA zkH7)->et`G>yHnAO6!7e^4ceAxZ299*NX@I4S1lw0X$~E2=VaM)hQly{>=Y901naD zA6xu7T>mN_OjbT=TPT6|0n$8Y*QxT8C%HRmj=SBr%gfb!ub)3V zCH@a)s!GF~zo!8h2Od^ZbLWR*35-LD_=Y@Yl|vQ}l$szQ2nYg#fFK|U2m*qDARq_` z0)oJnAYj`^y4c87X~hihCo#TSHCM3_<#{n4rMa&}UG|q%o+uF)1Ox#=KoAfF1OY)n z5D)|e0YN|z5Ck@WfbD)l^E!1F_;+Q76itcqH&~i7uS~C6P9~J(M xYPB>SfAiT-KW9Gd%3SL^aQ?f03z=)javzW1ZS7bqDJ@%u0M08)p4dOX{{cmRix2<+ literal 0 HcmV?d00001 diff --git a/tests/regression_tests/deplete_with_transfer_rates/ref_no_depletion_with_transfer.h5 b/tests/regression_tests/deplete_with_transfer_rates/ref_no_depletion_with_transfer.h5 index 469149f6742736085bebd22499c4eb2a45f519ed..b0ba997407f2471adcb32897dd08378bfe312814 100644 GIT binary patch delta 77 zcmcbxnCZe|rVXd;7@0Powv%TBQm^gj1F6Z~j{kw2ZYO>aXSOqlGuvgUC!_3U$G+8! Z%nw!-O-^)`pLjuNv%-Wmj4VLDJpg559Z>)P delta 77 zcmcbxnCZe|rVXd;7#TL7wv%TBQm^gj1F6Z~j{kw2ZYO>aXSOqlGuvgUC!@?}$G+8! Z%nfS`CnvhfPrM+sSz*E&MiwC79spV!9Ebn_ diff --git a/tests/regression_tests/deplete_with_transfer_rates/test.py b/tests/regression_tests/deplete_with_transfer_rates/test.py index 10d60866a69..4ad009d22e0 100644 --- a/tests/regression_tests/deplete_with_transfer_rates/test.py +++ b/tests/regression_tests/deplete_with_transfer_rates/test.py @@ -1,8 +1,7 @@ -""" TransferRates depletion test suite """ +""" ExternalRates depletion test suite """ from pathlib import Path import shutil -import sys import numpy as np import pytest @@ -11,7 +10,7 @@ from openmc.deplete import CoupledOperator from tests.regression_tests import config, assert_reaction_rates_equal, \ - assert_atoms_equal, assert_same_mats + assert_atoms_equal @pytest.fixture @@ -44,9 +43,11 @@ def model(): settings.particles = 100 settings.inactive = 0 settings.batches = 10 + settings.seed = 1 return openmc.Model(geometry, materials, settings) + @pytest.mark.parametrize("rate, dest_mat, power, ref_result", [ (1e-5, None, 0.0, 'no_depletion_only_removal'), (-1e-5, None, 0.0, 'no_depletion_only_feed'), @@ -83,6 +84,40 @@ def test_transfer_rates(run_in_tmpdir, model, rate, dest_mat, power, ref_result) res_ref = openmc.deplete.Results(path_reference) res_test = openmc.deplete.Results(path_test) - assert_same_mats(res_ref, res_test) - assert_atoms_equal(res_ref, res_test, 1e-6) + assert_atoms_equal(res_ref, res_test) + assert_reaction_rates_equal(res_ref, res_test) + + +@pytest.mark.parametrize("rate, power, ref_result", [ + (1e-1, 0.0, 'no_depletion_with_ext_source'), + (1e-1, 174., 'depletion_with_ext_source'), +]) +def test_external_source_rates(run_in_tmpdir, model, rate, power, ref_result): + """Tests external_rates depletion class with external source rates""" + + chain_file = Path(__file__).parents[2] / 'chain_simple.xml' + + external_source_vector = {'U': 1} + + op = CoupledOperator(model, chain_file) + op.round_number = True + integrator = openmc.deplete.PredictorIntegrator( + op, [1], power, timestep_units='d') + integrator.add_external_source_rate('f', external_source_vector, rate) + integrator.integrate() + + # Get path to test and reference results + path_test = op.output_dir / 'depletion_results.h5' + path_reference = Path(__file__).with_name(f'ref_{ref_result}.h5') + + # If updating results, do so and return + if config['update']: + shutil.copyfile(str(path_test), str(path_reference)) + return + + # Load the reference/test results + res_ref = openmc.deplete.Results(path_reference) + res_test = openmc.deplete.Results(path_test) + + assert_atoms_equal(res_ref, res_test) assert_reaction_rates_equal(res_ref, res_test) diff --git a/tests/unit_tests/test_deplete_external_source_rates.py b/tests/unit_tests/test_deplete_external_source_rates.py new file mode 100644 index 00000000000..a8cf9dde536 --- /dev/null +++ b/tests/unit_tests/test_deplete_external_source_rates.py @@ -0,0 +1,170 @@ +""" Tests for ExternalSourceRates class """ + +from pathlib import Path + +import pytest +import numpy as np +import re + +import openmc +from openmc.data import AVOGADRO, atomic_mass +from openmc.deplete import CoupledOperator +from openmc.deplete.transfer_rates import ExternalSourceRates +from openmc.deplete.abc import (_SECONDS_PER_MINUTE, _SECONDS_PER_HOUR, + _SECONDS_PER_DAY, _SECONDS_PER_JULIAN_YEAR) + +CHAIN_PATH = Path(__file__).parents[1] / "chain_simple.xml" + +@pytest.fixture +def model(): + openmc.reset_auto_ids() + f = openmc.Material(name="f") + f.add_element("U", 1, enrichment=4.25) + f.add_element("O", 2) + f.set_density("g/cm3", 10.4) + + w = openmc.Material(name="w") + w.add_element("O", 1) + w.add_element("H", 2) + w.set_density("g/cm3", 1.0) + w.depletable = True + + # material just to test multiple destination material + h = openmc.Material(name="h") + h.add_element("He", 1) + h.set_density("g/cm3", 1.78e-4) + + radii = [0.42, 0.45] + f.volume = np.pi * radii[0] ** 2 + w.volume = np.pi * (radii[1]**2 - radii[0]**2) + h.volume = 1 + materials = openmc.Materials([f, w, h]) + + surf_f = openmc.Sphere(r=radii[0]) + surf_w = openmc.Sphere(r=radii[1], boundary_type='vacuum') + surf_h = openmc.Sphere(x0=10, r=1, boundary_type='vacuum') + cell_f = openmc.Cell(fill=f, region=-surf_f) + cell_w = openmc.Cell(fill=w, region=+surf_f & -surf_w) + cell_h = openmc.Cell(fill=h, region=-surf_h) + geometry = openmc.Geometry([cell_f, cell_w, cell_h]) + + settings = openmc.Settings() + settings.particles = 1000 + settings.inactive = 10 + settings.batches = 50 + + return openmc.Model(geometry, materials, settings) + + +@pytest.mark.parametrize( +"case_name, external_source_vectors, external_source_rate, timesteps", [ + ('elements', [{'U': 0.9, 'Xe': 0.1}], 1, None), + ('nuclides', [{'I135': 0.1, 'Gd156': 0.3, 'Gd157': 0.6}], 1, None), + ('nuclides_elements', [{'I135': 0.01, 'Gd156': 0.1, 'Gd157': 0.01, 'U': 0.8, + 'Xe': 0.08}], 1, None), + ('elements_nuclides', [{'U': 0.78, 'Xe': 0.1, 'I135': 0.01, 'Gd156': 0.1, + 'Gd157': 0.01}], 1, None), + ('multiple_vectors', [{'U': 1.}, {'Xe': 1}], 1, None), + ('timesteps', [{'U': 0.9, 'Xe': 0.1}], 1, [1]), + ('rates_invalid_1', [{'Gb': 1.}], 1, None), + ('rates_invalid_2', [{'Pu': 1.}], 1, None) + ]) +def test_get_set(model, case_name, external_source_vectors, external_source_rate, + timesteps): + """Tests the get/set methods""" + + op = CoupledOperator(model, CHAIN_PATH) + number_of_timesteps = 2 + transfer = ExternalSourceRates(op, model.materials, number_of_timesteps) + + if timesteps is None: + timesteps = np.arange(number_of_timesteps) + + # Test by Openmc material, material name and material id + material= [m for m in model.materials if m.depletable][0] + + for material_input in [material, material.name, material.id]: + for external_source_vector in external_source_vectors: + if case_name == 'rates_invalid_1': + with pytest.raises(ValueError, match='Gb is not a valid ' + 'nuclide or element.'): + transfer.set_external_source_rate(material_input, + external_source_vector, + external_source_rate) + elif case_name == 'rates_invalid_2': + with pytest.raises(ValueError, match='Cannot add element Pu'): + transfer.set_external_source_rate(material_input, + external_source_vector, + external_source_rate) + else: + transfer.set_external_source_rate(material_input, + external_source_vector, + external_source_rate, + timesteps=timesteps) + for component, percent in external_source_vector.items(): + split_component = re.split(r'\d+', component) + if len(split_component) == 1: + for nuc, frac in openmc.data.isotopes(component): + val = external_source_rate * percent * frac * \ + AVOGADRO / atomic_mass(nuc) + assert transfer.get_external_rate( + material_input, nuc, timesteps)[0] == pytest.approx(val) + else: + val = external_source_rate * percent * AVOGADRO / atomic_mass(component) + assert transfer.get_external_rate( + material_input, component, timesteps)[0] == pytest.approx(val) + + assert np.all(transfer.external_timesteps == timesteps) + + +@pytest.mark.parametrize("units, unit_conv", [ + ('g/s', 1), + ('g/sec', 1), + ('g/min', _SECONDS_PER_MINUTE), + ('g/minute', _SECONDS_PER_MINUTE), + ('g/h', _SECONDS_PER_HOUR), + ('g/hr', _SECONDS_PER_HOUR), + ('g/hour', _SECONDS_PER_HOUR), + ('g/d', _SECONDS_PER_DAY), + ('g/day', _SECONDS_PER_DAY), + ('g/a', _SECONDS_PER_JULIAN_YEAR), + ('g/year', _SECONDS_PER_JULIAN_YEAR), + ]) +def test_units(units, unit_conv, model): + """ Units testing""" + # create external rate Xe + components = ['Xe135', 'U235'] + external_source_rate = 1.0 + number_of_timesteps = 2 + op = CoupledOperator(model, CHAIN_PATH) + transfer = ExternalSourceRates(op, model.materials, number_of_timesteps) + timesteps = np.arange(number_of_timesteps) + + for component in components: + rate = external_source_rate * unit_conv * atomic_mass(component) / AVOGADRO + transfer.set_external_source_rate('f', {component: 1}, rate, rate_units=units) + assert transfer.get_external_rate( + 'f', component, timesteps)[0] == pytest.approx(external_source_rate) + + +def test_external_source(run_in_tmpdir, model): + """Tests external source depletion class without neither reaction rates nor + decay but only external source rates""" + # create transfer rate for U + vector = {'U235': 1} + external_source = 10 # grams + op = CoupledOperator(model, CHAIN_PATH) + integrator = openmc.deplete.PredictorIntegrator( + op, [1, 1], 0.0, timestep_units = 'd') + integrator.add_external_source_rate('f', vector, external_source/(24*3600)) + integrator.integrate() + + # Get number of U238 atoms from results + results = openmc.deplete.Results('depletion_results.h5') + _, atoms = results.get_atoms(model.materials[0], "U235") + + # Ensure number of atoms equal external source + assert atoms[1] - atoms[0] == pytest.approx( + external_source * AVOGADRO / atomic_mass('U235')) + assert atoms[2] - atoms[1] == pytest.approx( + external_source * AVOGADRO / atomic_mass('U235')) diff --git a/tests/unit_tests/test_deplete_transfer_rates.py b/tests/unit_tests/test_deplete_transfer_rates.py index 140777cd6f9..a3228e9fb7d 100644 --- a/tests/unit_tests/test_deplete_transfer_rates.py +++ b/tests/unit_tests/test_deplete_transfer_rates.py @@ -16,6 +16,7 @@ @pytest.fixture def model(): + openmc.reset_auto_ids() f = openmc.Material(name="f") f.add_element("U", 1, percent_type="ao", enrichment=4.25) f.add_element("O", 2) @@ -54,32 +55,35 @@ def model(): return openmc.Model(geometry, materials, settings) -@pytest.mark.parametrize("case_name, transfer_rates", [ - ('elements', {'U': 0.01, 'Xe': 0.1}), - ('nuclides', {'I135': 0.01, 'Gd156': 0.1, 'Gd157': 0.01}), +@pytest.mark.parametrize("case_name, transfer_rates, timesteps", [ + ('elements', {'U': 0.01, 'Xe': 0.1}, None), + ('nuclides', {'I135': 0.01, 'Gd156': 0.1, 'Gd157': 0.01}, None), ('nuclides_elements', {'I135': 0.01, 'Gd156': 0.1, 'Gd157': 0.01, 'U': 0.01, - 'Xe': 0.1}), + 'Xe': 0.1}, None), ('elements_nuclides', {'U': 0.01, 'Xe': 0.1, 'I135': 0.01, 'Gd156': 0.1, - 'Gd157': 0.01}), + 'Gd157': 0.01}, None), ('multiple_transfer', {'U': 0.01, 'Xe': 0.1, 'I135': 0.01, 'Gd156': 0.1, - 'Gd157': 0.01}), - ('rates_invalid_1', {'Gd': 0.01, 'Gd157': 0.01, 'Gd156': 0.01}), - ('rates_invalid_2', {'Gd156': 0.01, 'Gd157': 0.01, 'Gd': 0.01}), - ('rates_invalid_3', {'Gb156': 0.01}), - ('rates_invalid_4', {'Gb': 0.01}) + 'Gd157': 0.01}, None), + ('timesteps', {'U': 0.01, 'Xe': 0.1}, [1]), + ('rates_invalid_1', {'Gd': 0.01, 'Gd157': 0.01, 'Gd156': 0.01}, None), + ('rates_invalid_2', {'Gd156': 0.01, 'Gd157': 0.01, 'Gd': 0.01}, None), + ('rates_invalid_3', {'Gb156': 0.01}, None), + ('rates_invalid_4', {'Gb': 0.01}, None) ]) -def test_get_set(model, case_name, transfer_rates): +def test_get_set(model, case_name, transfer_rates, timesteps): """Tests the get/set methods""" - - openmc.reset_auto_ids() op = CoupledOperator(model, CHAIN_PATH) - transfer = TransferRates(op, model) + number_of_timesteps = 2 + transfer = TransferRates(op, model.materials, number_of_timesteps) + + if timesteps is None: + timesteps = np.arange(number_of_timesteps) # Test by Openmc material, material name and material id material, dest_material, dest_material2 = [m for m in model.materials if m.depletable] for material_input in [material, material.name, material.id]: - for dest_material_input in [dest_material, dest_material.name, + for dest_material_input in [None, dest_material, dest_material.name, dest_material.id]: if case_name == 'rates_invalid_1': with pytest.raises(ValueError, match='Cannot add transfer ' @@ -117,14 +121,21 @@ def test_get_set(model, case_name, transfer_rates): for component, transfer_rate in transfer_rates.items(): transfer.set_transfer_rate(material_input, [component], transfer_rate, + timesteps=timesteps, destination_material=\ dest_material_input) - assert transfer.get_transfer_rate( - material_input, component)[0] == transfer_rate - assert transfer.get_destination_material( - material_input, component)[0] == str(dest_material.id) - assert transfer.get_components(material_input) == \ - transfer_rates.keys() + assert transfer.get_external_rate( + material_input, component, timesteps, + dest_material_input)[0] == transfer_rate + assert np.all(transfer.external_timesteps == timesteps) + + if timesteps is not None: + for timestep in timesteps: + assert transfer.get_components(material_input, timestep, + dest_material_input) == list(transfer_rates.keys()) + else: + assert transfer.get_components(material_input, timesteps, + dest_material_input) == list(transfer_rates.keys()) if case_name == 'multiple_transfer': for dest2_material_input in [dest_material2, dest_material2.name, @@ -135,10 +146,8 @@ def test_get_set(model, case_name, transfer_rates): destination_material=\ dest2_material_input) for id, dest_mat in zip([0,1],[dest_material,dest_material2]): - assert transfer.get_transfer_rate( - material_input, component)[id] == transfer_rate - assert transfer.get_destination_material( - material_input, component)[id] == str(dest_mat.id) + assert transfer.get_external_rate( + material_input, component, timesteps)[0] == transfer_rate @pytest.mark.parametrize("transfer_rate_units, unit_conv", [ ('1/s', 1), @@ -158,13 +167,15 @@ def test_units(transfer_rate_units, unit_conv, model): # create transfer rate Xe components = ['Xe', 'U235'] transfer_rate = 1e-5 + number_of_timesteps = 2 op = CoupledOperator(model, CHAIN_PATH) - transfer = TransferRates(op, model) + transfer = TransferRates(op, model.materials, number_of_timesteps) for component in components: transfer.set_transfer_rate('f', [component], transfer_rate * unit_conv, transfer_rate_units=transfer_rate_units) - assert transfer.get_transfer_rate('f', component)[0] == transfer_rate + for timestep in range(transfer.number_of_timesteps): + assert transfer.get_external_rate('f', component, timestep)[0] == transfer_rate def test_transfer(run_in_tmpdir, model): From 757617be50acb2a92120fb06991b5491b79bf4d4 Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Fri, 16 May 2025 16:55:41 +0300 Subject: [PATCH 026/159] added test for dagmc geometry plot (#3375) Co-authored-by: Paul Romano --- tests/unit_tests/dagmc/test_model.py | 1 + tests/unit_tests/dagmc/test_plot.py | 32 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 tests/unit_tests/dagmc/test_plot.py diff --git a/tests/unit_tests/dagmc/test_model.py b/tests/unit_tests/dagmc/test_model.py index 9fdfbcebc68..0de4f6092db 100644 --- a/tests/unit_tests/dagmc/test_model.py +++ b/tests/unit_tests/dagmc/test_model.py @@ -4,6 +4,7 @@ import numpy as np import pytest import openmc +import openmc.lib from openmc.utility_funcs import change_directory pytestmark = pytest.mark.skipif( diff --git a/tests/unit_tests/dagmc/test_plot.py b/tests/unit_tests/dagmc/test_plot.py new file mode 100644 index 00000000000..83f6df7676b --- /dev/null +++ b/tests/unit_tests/dagmc/test_plot.py @@ -0,0 +1,32 @@ +import pytest +import openmc +import openmc.lib + + +pytestmark = pytest.mark.skipif( + not openmc.lib._dagmc_enabled(), reason="DAGMC CAD geometry is not enabled." +) + +def test_plotting_dagmc_geometry(request): + """Test plotting a DAGMC geometry with OpenMC. This is different to CSG + geometry plotting as the path to the DAGMC file needs handling.""" + + dag_universe = openmc.DAGMCUniverse(request.path.parent / 'dagmc.h5m') + csg_with_dag_inside = dag_universe.bounded_universe() + model = openmc.Model() + model.geometry = openmc.Geometry(csg_with_dag_inside) + + for mat_name in dag_universe.material_names: + material = openmc.Material(name=mat_name) + material.add_nuclide("Fe56", 1.0) + material.set_density("g/cm3", 7.0) + model.materials.append(material) + + # putting the source at the center of the bounding box of the DAGMC + model.settings.source = openmc.IndependentSource( + space=openmc.stats.Point(dag_universe.bounding_box.center) + ) + model.settings.batches = 10 + model.settings.particles = 50 + + model.plot() From a7d1ceba3f9fe5e394a49cdf10b724094f39af11 Mon Sep 17 00:00:00 2001 From: Alberto P <4104972+alberto743@users.noreply.github.com> Date: Sun, 18 May 2025 19:28:00 +0200 Subject: [PATCH 027/159] small typo - spelling of Debian (#3411) --- docs/source/usersguide/install.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index d294770e0e0..7a8cb80204e 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -221,7 +221,7 @@ Prerequisites OpenMC's built-in plotting capabilities use the libpng library to produce compressed PNG files. In the absence of this library, OpenMC will fallback to writing PPM files, which are uncompressed and only supported by select - image viewers. libpng can be installed on Ddebian derivates with:: + image viewers. libpng can be installed on Debian derivates with:: sudo apt install libpng-dev From cb95c784b755719d9e5897bb550611eb0a8e542a Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Tue, 27 May 2025 19:04:16 +0300 Subject: [PATCH 028/159] Fix bug where the same mesh is written multiple times to settings.xml (#3418) --- openmc/settings.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/openmc/settings.py b/openmc/settings.py index 6fb36f3fc12..4cacefbedba 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -1626,9 +1626,12 @@ def _create_weight_window_generators_subelement(self, root, mesh_memo=None): if mesh_memo is not None and wwg.mesh.id in mesh_memo: continue - root.append(wwg.mesh.to_xml_element()) - if mesh_memo is not None: - mesh_memo.add(wwg.mesh.id) + # See if a element already exists -- if not, add it + path = f"./mesh[@id='{wwg.mesh.id}']" + if root.find(path) is None: + root.append(wwg.mesh.to_xml_element()) + if mesh_memo is not None: + mesh_memo.add(wwg.mesh.id) def _create_weight_windows_file_element(self, root): if self.weight_windows_file is not None: From ace73ab5de619d33241fcc778551c843b682ced1 Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Tue, 3 Jun 2025 21:02:20 +0300 Subject: [PATCH 029/159] Fixed a bug in charged particle energy deposition. (#3416) Co-authored-by: Jonathan Shimwell Co-authored-by: Paul Romano --- docs/source/methods/energy_deposition.rst | 79 ++++++++++++++++--- include/openmc/material.h | 5 ++ src/material.cpp | 11 ++- src/tallies/tally_scoring.cpp | 67 +++++++++------- .../photon_production/results_true.dat | 8 +- tests/unit_tests/test_data_multipole.py | 4 +- tests/unit_tests/test_nuclide_heating.py | 39 +++++++++ 7 files changed, 166 insertions(+), 47 deletions(-) create mode 100644 tests/unit_tests/test_nuclide_heating.py diff --git a/docs/source/methods/energy_deposition.rst b/docs/source/methods/energy_deposition.rst index 4675aee3fb2..0babd71246b 100644 --- a/docs/source/methods/energy_deposition.rst +++ b/docs/source/methods/energy_deposition.rst @@ -25,19 +25,36 @@ KERMA (Kinetic Energy Release in Materials) [Mack97]_ coefficients for reaction :math:`\times` cross-section (e.g., eV-barn) and can be used much like a reaction cross section for the purpose of tallying energy deposition. -KERMA coefficients can be computed using the energy-balance method with -a nuclear data processing code like NJOY, which performs the following -iteration over all reactions :math:`r` for all isotopes :math:`i` -requested +KERMA coefficients can be computed using the energy-balance method with a +nuclear data processing code like NJOY, which estimates the KERMA coefficients +using the following equation: .. math:: - k_{i, r}(E) = \left(E + Q_{i, r} - \bar{E}_{i, r, n} + k_{i, r}(E) = \left(E + Q_{i, r} - \sum\limits_x \bar{E}_{i, r, x} + \right)\sigma_{i, r}(E), + +where the summation is over each secondary particle type :math:`x`. This +equation states that the energy deposited is equal to the energy of the incident +particle plus the reaction :math:`Q` value less the energy of secondary +particles that are transported away from the reaction site. For neutron +interactions, the energy-balance KERMA coefficient is + +.. math:: + + k_{i, r}(E) = \left(E + Q_{i, r} - \sum\limits_x \bar{E}_{i, r, n} - \bar{E}_{i, r, \gamma}\right)\sigma_{i, r}(E), -removing the energy of neutral particles (neutrons and photons) that are -transported away from the reaction site :math:`\bar{E}`, and the reaction -:math:`Q` value. +where :math:`\bar{E}_{i, r, n}` is the average energy of secondary neutrons and +:math:`\bar{E}_{i, r, \gamma}` is the average energy of secondary photons. For +photon and charged particle interactions, the :math:`Q` value is zero and thus +the KERMA coefficient is + +.. math:: + :label: energy-balance-photon + + k_{i, r}(E) = \left(E - \sum\limits_x \bar{E}_{i, r, x} + \right)\sigma_{i, r}(E). ------- Fission @@ -120,7 +137,7 @@ run with :math:`N918` reflecting fission heating computed from NJOY. This modified heating data is stored as the MT=901 reaction and will be scored if ``heating-local`` is included in :attr:`openmc.Tally.scores`. -Coupled neutron-photon transport +Coupled Neutron-Photon Transport -------------------------------- Here, OpenMC instructs ``heatr`` to assume that energy from photons is not @@ -138,6 +155,50 @@ Let :math:`N301` represent the total heating number returned from this This modified heating data is stored as the MT=301 reaction and will be scored if ``heating`` is included in :attr:`openmc.Tally.scores`. +Photons and Charged Particles +----------------------------- + +In OpenMC, energy deposition from photons or charged particles is scored using +the energy balance method based on Equation :eq:`energy-balance-photon`. Special +consideration is given to electrons and positrons as described below. + ++++++++++++++++++ +Charged Particles ++++++++++++++++++ + +OpenMC tracks photons interaction by interaction so the energy deposited in each +collision is easily attributed back to the nuclide and reaction for which the +photon interacted with. Charged particles (electrons and photons) aren't tracked +in the same way. For charged particles, OpenMC assumes that all their energy +(less the energy of bremsstrahlung radiation) is deposited in the material in +which they were born. In this way it is harder to trace how much energy should +be attributed in each nuclide. + +According to the CSDA approximation (see :ref:`ttb`) the energy deposited by a +charged particle with kinetic energy :math:`T` in the :math:`i`-th element can +be calculated as: + +.. math:: + + E_{i} = \int_{0}^{R(T)} w_{i}S_{\text{col,i}} dx + +where :math:`R(T)` is the CSDA range of the charged particle, +:math:`S_{\text{col},i}` is the collision stopping power of the charged particle +in the :math:`i`-th element and :math:`w_i` is the mass fraction of the +:math:`i`-th element. According to the Bethe formula the collision stopping +power of the :math:`i`-th element is proportional to :math:`Z_i/A_i`, so the +fractional collision stopping power from the :math:`i`-th element is: + +.. math:: + + \frac{w_{i}S_{\text{col},i}(T)}{S_{\text{col}}(T)} = + \frac{\frac{w_{i}Z_{i}}{A_{i}}}{\sum_{i}\frac{w_{i}Z_{i}}{A_{i}}} = + \frac{\gamma_i Z_{i}}{\sum_{i}\gamma_i Z_{i}}. + +where :math:`\gamma_i` is the atomic fraction of the :math:`i`-th element. +Therefore, the energy deposited by charged particles should be attributed to +a given element according to its fractional charge density. + ---------- References ---------- diff --git a/include/openmc/material.h b/include/openmc/material.h index 31fab2ae273..fe587a86f9e 100644 --- a/include/openmc/material.h +++ b/include/openmc/material.h @@ -107,6 +107,10 @@ class Material { //! \return Density in [g/cm^3] double density_gpcc() const { return density_gpcc_; } + //! Get charge density in [e/b-cm] + //! \return Charge density in [e/b-cm] + double charge_density() const { return charge_density_; }; + //! Get name //! \return Material name const std::string& name() const { return name_; } @@ -177,6 +181,7 @@ class Material { xt::xtensor atom_density_; //!< Nuclide atom density in [atom/b-cm] double density_; //!< Total atom density in [atom/b-cm] double density_gpcc_; //!< Total atom density in [g/cm^3] + double charge_density_; //!< Total charge density in [e/b-cm] double volume_ {-1.0}; //!< Volume in [cm^3] vector p0_; //!< Indicate which nuclides are to be treated with //!< iso-in-lab scattering diff --git a/src/material.cpp b/src/material.cpp index b8846d7cbfa..32384ddf12b 100644 --- a/src/material.cpp +++ b/src/material.cpp @@ -454,12 +454,15 @@ void Material::normalize_density() // Calculate nuclide atom densities atom_density_ *= density_; - // Calculate density in g/cm^3. + // Calculate density in [g/cm^3] and charge density in [e/b-cm] density_gpcc_ = 0.0; + charge_density_ = 0.0; for (int i = 0; i < nuclide_.size(); ++i) { int i_nuc = nuclide_[i]; double awr = settings::run_CE ? data::nuclides[i_nuc]->awr_ : 1.0; + int z = settings::run_CE ? data::nuclides[i_nuc]->Z_ : 0.0; density_gpcc_ += atom_density_(i) * awr * MASS_NEUTRON / N_AVOGADRO; + charge_density_ += atom_density_(i) * z; } } @@ -982,12 +985,15 @@ void Material::set_density(double density, const std::string& units) // Recalculate nuclide atom densities based on given density atom_density_ *= density; - // Calculate density in g/cm^3. + // Calculate density in g/cm^3 and charge density in [e/b-cm] density_gpcc_ = 0.0; + charge_density_ = 0.0; for (int i = 0; i < nuclide_.size(); ++i) { int i_nuc = nuclide_[i]; double awr = data::nuclides[i_nuc]->awr_; + int z = settings::run_CE ? data::nuclides[i_nuc]->Z_ : 0.0; density_gpcc_ += atom_density_(i) * awr * MASS_NEUTRON / N_AVOGADRO; + charge_density_ += atom_density_(i) * z; } } else if (units == "g/cm3" || units == "g/cc") { // Determine factor by which to change densities @@ -998,6 +1004,7 @@ void Material::set_density(double density, const std::string& units) density_gpcc_ = density; density_ *= f; atom_density_ *= f; + charge_density_ *= f; } else { throw std::invalid_argument { "Invalid units '" + std::string(units.data()) + "' specified."}; diff --git a/src/tallies/tally_scoring.cpp b/src/tallies/tally_scoring.cpp index 5c6386fb423..62b0029074b 100644 --- a/src/tallies/tally_scoring.cpp +++ b/src/tallies/tally_scoring.cpp @@ -325,6 +325,39 @@ double score_neutron_heating(const Particle& p, const Tally& tally, double flux, return score; } +//! Helper function to obtain particle heating [eV] + +double score_particle_heating(const Particle& p, const Tally& tally, + double flux, int rxn_bin, int i_nuclide, double atom_density) +{ + if (p.type() == ParticleType::neutron) + return score_neutron_heating( + p, tally, flux, rxn_bin, i_nuclide, atom_density); + if (i_nuclide == -1 || i_nuclide == p.event_nuclide() || + p.event_nuclide() == -1) { + // Get the pre-collision energy of the particle. + auto E = p.E_last(); + // The energy deposited is the difference between the pre-collision + // and post-collision energy... + double score = E - p.E(); + // ...less the energy of any secondary particles since they will be + // transported individually later + score -= p.bank_second_E(); + score *= p.wgt_last(); + + // if no event_nuclide (charged particle) scale energy deposition by + // fractional charge density + if (i_nuclide != -1 && p.event_nuclide() == -1) { + const auto& mat {model::materials[p.material()]}; + int z = data::nuclides[i_nuclide]->Z_; + auto i = mat->mat_nuclide_index_[i_nuclide]; + score *= (z * mat->atom_density_[i] / mat->charge_density()); + } + return score; + } + return 0.0; +} + //! Helper function for nu-fission tallies with energyout filters. // //! In this case, we may need to score to multiple bins if there were multiple @@ -1007,23 +1040,8 @@ void score_general_ce_nonanalog(Particle& p, int i_tally, int start_index, break; case HEATING: - if (p.type() == Type::neutron) { - score = score_neutron_heating( - p, tally, flux, HEATING, i_nuclide, atom_density); - } else { - if (i_nuclide == -1 || i_nuclide == p.event_nuclide()) { - // The energy deposited is the difference between the pre-collision - // and post-collision energy... - score = E - p.E(); - // ...less the energy of any secondary particles since they will be - // transported individually later - score -= p.bank_second_E(); - - score *= p.wgt_last(); - } else { - score = 0.0; - } - } + score = score_particle_heating( + p, tally, flux, HEATING, i_nuclide, atom_density); break; default: @@ -1539,19 +1557,8 @@ void score_general_ce_analog(Particle& p, int i_tally, int start_index, break; case HEATING: - if (p.type() == Type::neutron) { - score = score_neutron_heating( - p, tally, flux, HEATING, i_nuclide, atom_density); - } else { - // The energy deposited is the difference between the pre-collision and - // post-collision energy... - score = E - p.E(); - // ...less the energy of any secondary particles since they will be - // transported individually later - score -= p.bank_second_E(); - - score *= p.wgt_last(); - } + score = score_particle_heating( + p, tally, flux, HEATING, i_nuclide, atom_density); break; default: diff --git a/tests/regression_tests/photon_production/results_true.dat b/tests/regression_tests/photon_production/results_true.dat index 874e32b85f7..c16524689e8 100644 --- a/tests/regression_tests/photon_production/results_true.dat +++ b/tests/regression_tests/photon_production/results_true.dat @@ -67,8 +67,8 @@ tally 3: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 +1.774484E+05 +3.148794E+10 0.000000E+00 0.000000E+00 0.000000E+00 @@ -79,8 +79,8 @@ tally 3: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 +7.692488E+03 +5.917437E+07 0.000000E+00 0.000000E+00 0.000000E+00 diff --git a/tests/unit_tests/test_data_multipole.py b/tests/unit_tests/test_data_multipole.py index 4c2ba96d620..bc7136a97c8 100644 --- a/tests/unit_tests/test_data_multipole.py +++ b/tests/unit_tests/test_data_multipole.py @@ -52,7 +52,7 @@ def test_from_endf(): pytest.importorskip('vectfit') endf_data = os.environ['OPENMC_ENDF_DATA'] endf_file = os.path.join(endf_data, 'neutrons', 'n-001_H_001.endf') - return openmc.data.WindowedMultipole.from_endf( + assert openmc.data.WindowedMultipole.from_endf( endf_file, log=True, wmp_options={"n_win": 400, "n_cf": 3}) @@ -60,5 +60,5 @@ def test_from_endf_search(): pytest.importorskip('vectfit') endf_data = os.environ['OPENMC_ENDF_DATA'] endf_file = os.path.join(endf_data, 'neutrons', 'n-095_Am_244.endf') - return openmc.data.WindowedMultipole.from_endf( + assert openmc.data.WindowedMultipole.from_endf( endf_file, log=True, wmp_options={"search": True, 'rtol':1e-2}) diff --git a/tests/unit_tests/test_nuclide_heating.py b/tests/unit_tests/test_nuclide_heating.py new file mode 100644 index 00000000000..e00d57b4ff4 --- /dev/null +++ b/tests/unit_tests/test_nuclide_heating.py @@ -0,0 +1,39 @@ +import openmc +from pytest import approx + + +def test_nuclide_heating(run_in_tmpdir): + mat = openmc.Material() + mat.add_nuclide("Li6", 0.5) + mat.add_nuclide("Li7", 0.5) + mat.set_density("g/cm3", 1.0) + + sphere = openmc.Sphere(r=20, boundary_type="reflective") + inside_sphere = openmc.Cell(fill=mat, region=-sphere) + model = openmc.Model() + model.geometry = openmc.Geometry([inside_sphere]) + + model.settings.particles = 1000 + model.settings.batches = 1 + model.settings.photon_transport = True + model.settings.electron_treatment = "ttb" + model.settings.cutoff = {"energy_photon": 1000} + model.settings.run_mode = "fixed source" + model.settings.source = openmc.IndependentSource( + energy=openmc.stats.delta_function(10.0e6), + particle="photon" + ) + + # Create two tallies, one with heating by nuclide and one with total heating + tally1 = openmc.Tally() + tally1.scores = ["heating"] + tally1.nuclides = mat.get_nuclides() + tally2 = openmc.Tally() + tally2.scores = ["heating"] + model.tallies = [tally1, tally2] + + # Run the model + model.run(apply_tally_results=True) + + # Make sure the heating results are consistent + assert tally1.mean.sum() == approx(tally2.mean.sum()) From e14bb884eb97f80fecaa3c509dce10964c10247a Mon Sep 17 00:00:00 2001 From: Lewis Gross <43077972+lewisgross1296@users.noreply.github.com> Date: Wed, 4 Jun 2025 18:40:55 -0500 Subject: [PATCH 030/159] Update _get_start_data to always grab the beginning of timestep time (#3414) Co-authored-by: Paul Wilson Co-authored-by: Paul Romano --- openmc/deplete/abc.py | 29 +++++++++++++++-- tests/unit_tests/test_deplete_continue.py | 38 +++++++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index c304b8dc621..4b43dd22149 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -818,10 +818,35 @@ def _get_bos_data_from_restart(self, source_rate, bos_conc): rates *= source_rate / res.source_rate return bos_conc, OperatorResult(k, rates) - def _get_start_data(self): + def _get_start_data(self) -> tuple[float, int]: + """ + This function fetches the starting state of a depletion simulation in + terms of the simulation physical time at which to start and the index at + which the depletion simulation should start. When no previous results + exist, the time and index are both zero. When previous results do exist, + it returns the time corresponding to beginning the previous results last + timestep and the index as N-1 where N is the number of previous + StepResults found in the previous Results (as expected from 0-based + indexing). + + Note that the openmc.deplete.Results.time object is a list of float with + [t,t+dt] where t is the beginning of timestep time and t+dt is the end + of timestep time. If the previous results correspond to a simulation + that finished to completeion, it will contain a results in the form of + [t,t], but if a simulation doesn't finish all the given timesteps, it is + the t that is the desired start time, not t+dt. Thus, it is always safe + to take time[0]. + + Returns + ------- + start_time : float + Time at which depletion simulation should start in [s] + index : int + Index at which depletion simulation should start + """ if self.operator.prev_res is None: return 0.0, 0 - return (self.operator.prev_res[-1].time[-1], + return (self.operator.prev_res[-1].time[0], len(self.operator.prev_res) - 1) def integrate( diff --git a/tests/unit_tests/test_deplete_continue.py b/tests/unit_tests/test_deplete_continue.py index 53c4c56d29f..637c9d5e440 100644 --- a/tests/unit_tests/test_deplete_continue.py +++ b/tests/unit_tests/test_deplete_continue.py @@ -4,6 +4,7 @@ """ import pytest +import numpy as np import openmc.deplete from tests import dummy_operator @@ -26,6 +27,43 @@ def test_continue(run_in_tmpdir): bundle.solver(operator, [1.0, 2.0, 3.0, 4.0], [1.0, 2.0, 3.0, 4.0], continue_timesteps=True).integrate() + final_res = openmc.deplete.Results(operator.output_dir / "depletion_results.h5") + + assert np.array_equal( + np.diff(final_res.get_times(time_units="s")), + [1.0, 2.0, 3.0, 4.0] + ) + + +def test_continue_continue(run_in_tmpdir): + """Test to ensure that a continue run can be continued""" + # set up the problem + bundle = dummy_operator.SCHEMES['predictor'] + operator = dummy_operator.DummyOperator() + + # initial depletion + bundle.solver(operator, [1.0, 2.0], [1.0, 2.0]).integrate() + + # set up continue run + prev_res = openmc.deplete.Results(operator.output_dir / "depletion_results.h5") + operator = dummy_operator.DummyOperator(prev_res) + + # first continue run + bundle.solver(operator, [1.0, 2.0, 3.0, 4.0], [1.0, 2.0, 3.0, 4.0], + continue_timesteps=True).integrate() + + prev_res = openmc.deplete.Results(operator.output_dir / "depletion_results.h5") + # second continue run + bundle.solver(operator, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [1.0, 2.0, 3.0, 4.0, 5.0, 6.0], + continue_timesteps=True).integrate() + + final_res = openmc.deplete.Results(operator.output_dir / "depletion_results.h5") + + assert np.array_equal( + np.diff(final_res.get_times(time_units="s")), + [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] + ) + def test_mismatched_initial_times(run_in_tmpdir): """Test to ensure that a continue run with different initial steps is properly caught""" From dadc4fe4184e66848a60a052c00d0b4cfeebb247 Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Thu, 5 Jun 2025 03:07:59 +0300 Subject: [PATCH 031/159] Fix no serialization of periodic_surface_id bug (#3421) Co-authored-by: Paul Romano --- include/openmc/boundary_condition.h | 4 +++ openmc/summary.py | 12 +++++++++ src/surface.cpp | 13 +++++++++ tests/unit_tests/test_summary.py | 41 +++++++++++++++++++++++++++++ 4 files changed, 70 insertions(+) create mode 100644 tests/unit_tests/test_summary.py diff --git a/include/openmc/boundary_condition.h b/include/openmc/boundary_condition.h index cd8988162f2..af40131f1c8 100644 --- a/include/openmc/boundary_condition.h +++ b/include/openmc/boundary_condition.h @@ -111,6 +111,10 @@ class PeriodicBC : public BoundaryCondition { std::string type() const override { return "periodic"; } + int i_surf() const { return i_surf_; } + + int j_surf() const { return j_surf_; } + protected: int i_surf_; int j_surf_; diff --git a/openmc/summary.py b/openmc/summary.py index 6423f8ce1d6..ca5cfadd738 100644 --- a/openmc/summary.py +++ b/openmc/summary.py @@ -127,11 +127,23 @@ def _read_materials(self): self._fast_materials[material.id] = material def _read_surfaces(self): + periodic_surface_ids = set() for group in self._f['geometry/surfaces'].values(): surface = openmc.Surface.from_hdf5(group) # surface may be None for DAGMC surfaces if surface: self._fast_surfaces[surface.id] = surface + if surface.boundary_type == "periodic": + periodic_surface_ids.add(surface.id) + + # Assign periodic surfaces when information is in file + for surface_id in periodic_surface_ids: + group = self._f[f'geometry/surfaces/surface {surface_id}'] + surface = self._fast_surfaces[surface_id] + if 'periodic_surface_id' in group: + periodic_surface_id = int(group['periodic_surface_id'][()]) + surface.periodic_surface = self._fast_surfaces[periodic_surface_id] + def _read_cells(self): diff --git a/src/surface.cpp b/src/surface.cpp index 0002275c393..ea19514a311 100644 --- a/src/surface.cpp +++ b/src/surface.cpp @@ -172,6 +172,19 @@ void Surface::to_hdf5(hid_t group_id) const if (bc_) { write_string(surf_group, "boundary_type", bc_->type(), false); bc_->to_hdf5(surf_group); + + // write periodic surface ID + if (bc_->type() == "periodic") { + auto pbc = dynamic_cast(bc_.get()); + Surface& surf1 {*model::surfaces[pbc->i_surf()]}; + Surface& surf2 {*model::surfaces[pbc->j_surf()]}; + + if (id_ == surf1.id_) { + write_dataset(surf_group, "periodic_surface_id", surf2.id_); + } else { + write_dataset(surf_group, "periodic_surface_id", surf1.id_); + } + } } else { write_string(surf_group, "boundary_type", "transmission", false); } diff --git a/tests/unit_tests/test_summary.py b/tests/unit_tests/test_summary.py new file mode 100644 index 00000000000..315ac0ba59e --- /dev/null +++ b/tests/unit_tests/test_summary.py @@ -0,0 +1,41 @@ +import openmc + + +def test_periodic_surface_roundtrip(run_in_tmpdir): + # Create a simple model with periodic surfaces + mat = openmc.Material() + mat.add_nuclide('H1', 1.0) + mat.set_density('g/cm3', 1.0) + cyl = openmc.ZCylinder(r=1.0) + x0 = openmc.XPlane(-5.0, boundary_type='periodic') + y0 = openmc.YPlane(-5.0, boundary_type='periodic') + z0 = openmc.ZPlane(-5.0, boundary_type='periodic') + x1 = openmc.XPlane(5.0, boundary_type='periodic') + y1 = openmc.YPlane(5.0, boundary_type='periodic') + z1 = openmc.ZPlane(5.0, boundary_type='periodic') + x0.periodic_surface = x1 + y0.periodic_surface = y1 + z0.periodic_surface = z1 + cell1 = openmc.Cell(fill=mat, region=-cyl) + cell2 = openmc.Cell(fill=mat, region=+cyl & +x0 & -x1 & +y0 & -y1 & +z0 & -z1) + model = openmc.Model() + model.geometry = openmc.Geometry([cell1, cell2]) + model.settings.particles = 100 + model.settings.batches = 1 + model.settings.run_mode = 'fixed source' + model.settings.source = openmc.IndependentSource( + energy=openmc.stats.delta_function(1.0e4) + ) + + # Run model + model.run() + + # Load summary data and check periodic surfaces + summary = openmc.Summary('summary.h5') + surfs = summary.geometry.get_all_surfaces() + for s in [x0, y0, z0, x1, y1, z1]: + assert surfs[s.id].boundary_type == 'periodic' + pairs = [(x0, x1), (y0, y1), (z0, z1)] + for s0, s1 in pairs: + assert surfs[s0.id].periodic_surface == surfs[s1.id] + assert surfs[s1.id].periodic_surface == surfs[s0.id] From 4943fa363074e511cd0fa797a8d4c02af92c2443 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 5 Jun 2025 10:45:49 -0500 Subject: [PATCH 032/159] Avoid negative heating values during pair production and bremsstrahlung (#3426) Co-authored-by: GuySten --- docs/source/methods/energy_deposition.rst | 8 +++-- include/openmc/particle_data.h | 3 +- src/bremsstrahlung.cpp | 6 ++++ src/tallies/tally_scoring.cpp | 29 ++++++++++++++---- .../photon_production/results_true.dat | 28 ++++++++--------- .../results_true.dat | 16 +++++----- tests/unit_tests/test_photon_heating.py | 30 +++++++++++++++++++ 7 files changed, 88 insertions(+), 32 deletions(-) create mode 100644 tests/unit_tests/test_photon_heating.py diff --git a/docs/source/methods/energy_deposition.rst b/docs/source/methods/energy_deposition.rst index 0babd71246b..c43ee64ac4e 100644 --- a/docs/source/methods/energy_deposition.rst +++ b/docs/source/methods/energy_deposition.rst @@ -47,15 +47,17 @@ interactions, the energy-balance KERMA coefficient is where :math:`\bar{E}_{i, r, n}` is the average energy of secondary neutrons and :math:`\bar{E}_{i, r, \gamma}` is the average energy of secondary photons. For -photon and charged particle interactions, the :math:`Q` value is zero and thus -the KERMA coefficient is +photon and charged particle interactions the KERMA coefficient is .. math:: :label: energy-balance-photon - k_{i, r}(E) = \left(E - \sum\limits_x \bar{E}_{i, r, x} + k_{i, r}(E) = \left(E + Q_{i, r} - \sum\limits_x \bar{E}_{i, r, x} \right)\sigma_{i, r}(E). +where the :math:`Q` value is zero for all interactions except for pair +production and positron annihilation. + ------- Fission ------- diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index 1570b780bb9..a07a99ffbed 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -573,7 +573,8 @@ class ParticleData : public GeometryState { bool& fission() { return fission_; } // true if implicit fission int& event_nuclide() { return event_nuclide_; } // index of collision nuclide const int& event_nuclide() const { return event_nuclide_; } - int& event_mt() { return event_mt_; } // MT number of collision + int& event_mt() { return event_mt_; } // MT number of collision + const int& event_mt() const { return event_mt_; } int& delayed_group() { return delayed_group_; } // delayed group const int& parent_nuclide() const { return parent_nuclide_; } int& parent_nuclide() { return parent_nuclide_; } // Parent nuclide diff --git a/src/bremsstrahlung.cpp b/src/bremsstrahlung.cpp index d22d6392ab8..a2320e0b469 100644 --- a/src/bremsstrahlung.cpp +++ b/src/bremsstrahlung.cpp @@ -112,6 +112,12 @@ void thick_target_bremsstrahlung(Particle& p, double* E_lost) std::pow(a * (c - c_l) / (std::exp(w_l) * p_l) + 1.0, 1.0 / a); if (w > settings::energy_cutoff[photon]) { + // If the energy of the secondary photon is larger than the remaining + // energy of the primary particle, adjust it to the remaining energy + if (*E_lost + w > p.E()) { + w = p.E() - *E_lost; + } + // Create secondary photon p.create_secondary(p.wgt(), p.u(), w, ParticleType::photon); *E_lost += w; diff --git a/src/tallies/tally_scoring.cpp b/src/tallies/tally_scoring.cpp index 62b0029074b..71b103cfbfc 100644 --- a/src/tallies/tally_scoring.cpp +++ b/src/tallies/tally_scoring.cpp @@ -325,6 +325,20 @@ double score_neutron_heating(const Particle& p, const Tally& tally, double flux, return score; } +//! Helper function to obtain reaction Q value for photons and charged particles +double get_reaction_q_value(const Particle& p) +{ + if (p.type() == ParticleType::photon && p.event_mt() == PAIR_PROD) { + // pair production + return -2 * MASS_ELECTRON_EV; + } else if (p.type() == ParticleType::positron) { + // positron annihilation + return 2 * MASS_ELECTRON_EV; + } else { + return 0.0; + } +} + //! Helper function to obtain particle heating [eV] double score_particle_heating(const Particle& p, const Tally& tally, @@ -335,14 +349,17 @@ double score_particle_heating(const Particle& p, const Tally& tally, p, tally, flux, rxn_bin, i_nuclide, atom_density); if (i_nuclide == -1 || i_nuclide == p.event_nuclide() || p.event_nuclide() == -1) { + // For pair production and positron annihilation, we need to account for the + // reaction Q value + double Q = get_reaction_q_value(p); + // Get the pre-collision energy of the particle. auto E = p.E_last(); - // The energy deposited is the difference between the pre-collision - // and post-collision energy... - double score = E - p.E(); - // ...less the energy of any secondary particles since they will be - // transported individually later - score -= p.bank_second_E(); + + // The energy deposited is the sum of the incident energy and the reaction + // Q-value less the energy of any outgoing particles + double score = E + Q - p.E() - p.bank_second_E(); + score *= p.wgt_last(); // if no event_nuclide (charged particle) scale energy deposition by diff --git a/tests/regression_tests/photon_production/results_true.dat b/tests/regression_tests/photon_production/results_true.dat index c16524689e8..413f6f0ca45 100644 --- a/tests/regression_tests/photon_production/results_true.dat +++ b/tests/regression_tests/photon_production/results_true.dat @@ -55,14 +55,14 @@ tally 3: 1.846062E-07 2.297000E-01 5.276209E-02 -7.055982E+03 -4.978688E+07 +4.196651E+00 +1.761188E+01 0.000000E+00 0.000000E+00 2.297000E-01 5.276209E-02 -7.055982E+03 -4.978688E+07 +4.196651E+00 +1.761188E+01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -79,14 +79,14 @@ tally 3: 0.000000E+00 0.000000E+00 0.000000E+00 -7.692488E+03 -5.917437E+07 +1.474427E+04 +2.173936E+08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -7.692488E+03 -5.917437E+07 +1.474427E+04 +2.173936E+08 0.000000E+00 0.000000E+00 tally 4: @@ -104,14 +104,14 @@ tally 4: 0.000000E+00 2.297000E-01 5.276209E-02 -7.055982E+03 -4.978688E+07 +4.196651E+00 +1.761188E+01 0.000000E+00 0.000000E+00 2.297000E-01 5.276209E-02 -7.055982E+03 -4.978688E+07 +4.196651E+00 +1.761188E+01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -134,7 +134,7 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -7.692488E+03 -5.917437E+07 +1.474427E+04 +2.173936E+08 0.000000E+00 0.000000E+00 diff --git a/tests/regression_tests/photon_production_fission/results_true.dat b/tests/regression_tests/photon_production_fission/results_true.dat index cdbb1255c22..b17b3621a04 100644 --- a/tests/regression_tests/photon_production_fission/results_true.dat +++ b/tests/regression_tests/photon_production_fission/results_true.dat @@ -32,14 +32,14 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -2.479234E+06 -2.052367E+12 +1.675918E+05 +9.365733E+09 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.479234E+06 -2.052367E+12 +1.675918E+05 +9.365733E+09 0.000000E+00 0.000000E+00 tally 3: @@ -57,13 +57,13 @@ tally 3: 0.000000E+00 0.000000E+00 0.000000E+00 -2.479234E+06 -2.052367E+12 +1.675918E+05 +9.365733E+09 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.479234E+06 -2.052367E+12 +1.675918E+05 +9.365733E+09 0.000000E+00 0.000000E+00 diff --git a/tests/unit_tests/test_photon_heating.py b/tests/unit_tests/test_photon_heating.py new file mode 100644 index 00000000000..05473f5c6fc --- /dev/null +++ b/tests/unit_tests/test_photon_heating.py @@ -0,0 +1,30 @@ +import openmc + + +def test_negative_positron_heating(): + m = openmc.Material() + m.add_element('Li', 1.0) + m.set_density('g/cm3', 10.0) + + surf = openmc.Sphere(r=100.0, boundary_type='reflective') + cell = openmc.Cell(fill=m, region=-surf) + model = openmc.Model() + model.geometry = openmc.Geometry([cell]) + model.settings.run_mode = 'fixed source' + model.settings.source = openmc.IndependentSource( + space=openmc.stats.Point(), + energy=openmc.stats.Discrete([5.0e6], [1.0]), + particle='photon', + ) + model.settings.particles = 7 + model.settings.batches = 1 + model.settings.electron_treatment = 'led' + model.settings.seed = 513836 + + tally = openmc.Tally() + tally.filters = [openmc.ParticleFilter(['photon', 'electron', 'positron'])] + tally.scores = ['heating'] + model.tallies = openmc.Tallies([tally]) + model.run(apply_tally_results=True) + + assert (tally.mean >= 0.0).all(), "Negative heating detected" From 67aa0def9deabae1cc012214d5b96367c5743dcf Mon Sep 17 00:00:00 2001 From: John Tramm Date: Fri, 6 Jun 2025 14:51:39 -0500 Subject: [PATCH 033/159] Random Ray External Source Plotting Fix (#3430) --- src/random_ray/flat_source_domain.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/random_ray/flat_source_domain.cpp b/src/random_ray/flat_source_domain.cpp index cd073f35077..367c63af4bc 100644 --- a/src/random_ray/flat_source_domain.cpp +++ b/src/random_ray/flat_source_domain.cpp @@ -919,10 +919,17 @@ void FlatSourceDomain::output_to_vtk() const std::fprintf(plot, "LOOKUP_TABLE default\n"); for (int i = 0; i < Nx * Ny * Nz; i++) { int64_t fsr = voxel_indices[i]; + int mat = source_regions_.material(fsr); float total_external = 0.0f; if (fsr >= 0) { for (int g = 0; g < negroups_; g++) { - total_external += source_regions_.external_source(fsr, g); + // External sources are already divided by sigma_t, so we need to + // multiply it back to get the true external source. + double sigma_t = 1.0; + if (mat != MATERIAL_VOID) { + sigma_t = sigma_t_[mat * negroups_ + g]; + } + total_external += source_regions_.external_source(fsr, g) * sigma_t; } } total_external = convert_to_big_endian(total_external); From 7fda3eb84695f6b147705e3b0fd4a65a49e0735b Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 6 Jun 2025 16:30:50 -0500 Subject: [PATCH 034/159] Implement a new MeshMaterialFilter (#3406) Co-authored-by: Jonathan Shimwell Co-authored-by: Patrick Shriwise --- CMakeLists.txt | 1 + docs/source/pythonapi/base.rst | 1 + include/openmc/tallies/filter.h | 1 + include/openmc/tallies/filter_mesh.h | 6 +- include/openmc/tallies/filter_meshmaterial.h | 114 +++++++++++ openmc/deplete/microxs.py | 9 +- openmc/filter.py | 190 ++++++++++++++++++- openmc/lib/filter.py | 20 +- openmc/tallies.py | 2 +- src/tallies/filter.cpp | 3 + src/tallies/filter_meshmaterial.cpp | 185 ++++++++++++++++++ tests/unit_tests/test_filter_meshmaterial.py | 65 +++++++ tests/unit_tests/test_filters.py | 33 ++++ 13 files changed, 614 insertions(+), 16 deletions(-) create mode 100644 include/openmc/tallies/filter_meshmaterial.h create mode 100644 src/tallies/filter_meshmaterial.cpp create mode 100644 tests/unit_tests/test_filter_meshmaterial.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 4dff35418f0..6665682dd05 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -414,6 +414,7 @@ list(APPEND libopenmc_SOURCES src/tallies/filter_materialfrom.cpp src/tallies/filter_mesh.cpp src/tallies/filter_meshborn.cpp + src/tallies/filter_meshmaterial.cpp src/tallies/filter_meshsurface.cpp src/tallies/filter_mu.cpp src/tallies/filter_musurface.cpp diff --git a/docs/source/pythonapi/base.rst b/docs/source/pythonapi/base.rst index 1d8a93d47f1..d0e7d2439b8 100644 --- a/docs/source/pythonapi/base.rst +++ b/docs/source/pythonapi/base.rst @@ -129,6 +129,7 @@ Constructing Tallies openmc.SurfaceFilter openmc.MeshFilter openmc.MeshBornFilter + openmc.MeshMaterialFilter openmc.MeshSurfaceFilter openmc.EnergyFilter openmc.EnergyoutFilter diff --git a/include/openmc/tallies/filter.h b/include/openmc/tallies/filter.h index ee635b18327..65098597a5d 100644 --- a/include/openmc/tallies/filter.h +++ b/include/openmc/tallies/filter.h @@ -33,6 +33,7 @@ enum class FilterType { MATERIALFROM, MESH, MESHBORN, + MESH_MATERIAL, MESH_SURFACE, MU, MUSURFACE, diff --git a/include/openmc/tallies/filter_mesh.h b/include/openmc/tallies/filter_mesh.h index 2f48b6d4d48..4c9460d2eb4 100644 --- a/include/openmc/tallies/filter_mesh.h +++ b/include/openmc/tallies/filter_mesh.h @@ -9,9 +9,9 @@ namespace openmc { //============================================================================== -//! Indexes the location of particle events to a regular mesh. For tracklength -//! tallies, it will produce multiple valid bins and the bin weight will -//! correspond to the fraction of the track length that lies in that bin. +//! Indexes the location of particle events to a mesh. For tracklength tallies, +//! it will produce multiple valid bins and the bin weight will correspond to +//! the fraction of the track length that lies in that bin. //============================================================================== class MeshFilter : public Filter { diff --git a/include/openmc/tallies/filter_meshmaterial.h b/include/openmc/tallies/filter_meshmaterial.h new file mode 100644 index 00000000000..42a4edcf084 --- /dev/null +++ b/include/openmc/tallies/filter_meshmaterial.h @@ -0,0 +1,114 @@ +#ifndef OPENMC_TALLIES_FILTER_MESHMATERIAL_H +#define OPENMC_TALLIES_FILTER_MESHMATERIAL_H + +#include +#include +#include +#include + +#include "openmc/position.h" +#include "openmc/random_ray/source_region.h" +#include "openmc/span.h" +#include "openmc/tallies/filter.h" +#include "openmc/vector.h" + +namespace openmc { + +//============================================================================== +//! Helper structs that define a combination of a mesh element index and a +//! material index and a functor for hashing to place in an unordered_map +//============================================================================== + +struct ElementMat { + //! Check for equality + bool operator==(const ElementMat& other) const + { + return index_element == other.index_element && index_mat == other.index_mat; + } + + int32_t index_element; + int32_t index_mat; +}; + +struct ElementMatHash { + std::size_t operator()(const ElementMat& k) const + { + size_t seed = 0; + hash_combine(seed, k.index_element); + hash_combine(seed, k.index_mat); + return seed; + } +}; + +//============================================================================== +//! Indexes the location of particle events to combinations of mesh element +//! index and material. For tracklength tallies, it will produce multiple valid +//! bins and the bin weight will correspond to the fraction of the track length +//! that lies in that bin. +//============================================================================== + +class MeshMaterialFilter : public Filter { +public: + //---------------------------------------------------------------------------- + // Constructors, destructors + + ~MeshMaterialFilter() = default; + + //---------------------------------------------------------------------------- + // Methods + + std::string type_str() const override { return "meshmaterial"; } + FilterType type() const override { return FilterType::MESH_MATERIAL; } + + void from_xml(pugi::xml_node node) override; + + void get_all_bins(const Particle& p, TallyEstimator estimator, + FilterMatch& match) const override; + + void to_statepoint(hid_t filter_group) const override; + + std::string text_label(int bin) const override; + + //---------------------------------------------------------------------------- + // Accessors + + int32_t mesh() const { return mesh_; } + + void set_mesh(int32_t mesh); + + //! Set the bins based on a flat vector of alternating element index and + //! material IDs + void set_bins(span bins); + + //! Set the bins based on a vector of (element, material index) pairs + void set_bins(vector&& bins); + + virtual void set_translation(const Position& translation); + + virtual void set_translation(const double translation[3]); + + virtual const Position& translation() const { return translation_; } + + virtual bool translated() const { return translated_; } + +private: + //---------------------------------------------------------------------------- + // Data members + + int32_t mesh_; //!< Index of the mesh + bool translated_ {false}; //!< Whether or not the filter is translated + Position translation_ {0.0, 0.0, 0.0}; //!< Filter translation + + //! The indices of the mesh element-material combinations binned by this + //! filter. + vector bins_; + + //! The set of materials used in this filter + std::unordered_set materials_; + + //! A map from mesh element-material indices to filter bin indices. + std::unordered_map map_; +}; + +} // namespace openmc +#endif // OPENMC_TALLIES_FILTER_MESHMATERIAL_H diff --git a/openmc/deplete/microxs.py b/openmc/deplete/microxs.py index bbe9b2a4322..686d3bc3823 100644 --- a/openmc/deplete/microxs.py +++ b/openmc/deplete/microxs.py @@ -56,8 +56,8 @@ def get_microxs_and_flux( ---------- model : openmc.Model OpenMC model object. Must contain geometry, materials, and settings. - domains : list of openmc.Material or openmc.Cell or openmc.Universe, or openmc.MeshBase - Domains in which to tally reaction rates. + domains : list of openmc.Material or openmc.Cell or openmc.Universe, or openmc.MeshBase, or openmc.Filter + Domains in which to tally reaction rates, or a spatial tally filter. nuclides : list of str Nuclides to get cross sections for. If not specified, all burnable nuclides from the depletion chain file are used. @@ -103,7 +103,10 @@ def get_microxs_and_flux( energy_filter = openmc.EnergyFilter.from_group_structure(energies) else: energy_filter = openmc.EnergyFilter(energies) - if isinstance(domains, openmc.MeshBase): + + if isinstance(domains, openmc.Filter): + domain_filter = domains + elif isinstance(domains, openmc.MeshBase): domain_filter = openmc.MeshFilter(domains) elif isinstance(domains[0], openmc.Material): domain_filter = openmc.MaterialFilter(domains) diff --git a/openmc/filter.py b/openmc/filter.py index 4d37f105538..f29c540857c 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -25,7 +25,8 @@ 'energyout', 'mu', 'musurface', 'polar', 'azimuthal', 'distribcell', 'delayedgroup', 'energyfunction', 'cellfrom', 'materialfrom', 'legendre', 'spatiallegendre', 'sphericalharmonics', 'zernike', 'zernikeradial', 'particle', 'cellinstance', - 'collision', 'time', 'parentnuclide', 'weight' + 'collision', 'time', 'parentnuclide', 'weight', 'meshborn', 'meshsurface', + 'meshmaterial', ) _CURRENT_NAMES = ( @@ -900,8 +901,6 @@ def mesh(self, mesh): @property def shape(self): - if isinstance(self, MeshSurfaceFilter): - return (self.num_bins,) return self.mesh.dimension @property @@ -992,8 +991,11 @@ def to_xml_element(self): XML element containing filter data """ - element = super().to_xml_element() - element[0].text = str(self.mesh.id) + element = ET.Element('filter') + element.set('id', str(self.id)) + element.set('type', self.short_name.lower()) + subelement = ET.SubElement(element, 'bins') + subelement.text = str(self.mesh.id) if self.translation is not None: element.set('translation', ' '.join(map(str, self.translation))) return element @@ -1039,6 +1041,181 @@ class MeshBornFilter(MeshFilter): """ +class MeshMaterialFilter(MeshFilter): + """Filter events by combinations of mesh elements and materials. + + .. versionadded:: 0.15.3 + + Parameters + ---------- + mesh : openmc.MeshBase + The mesh object that events will be tallied onto + bins : iterable of 2-tuples or numpy.ndarray + Combinations of (mesh element, material) to tally, given as 2-tuples. + The first value in the tuple represents the index of the mesh element, + and the second value indicates the material (either a + :class:`openmc.Material` instance of the ID). + filter_id : int + Unique identifier for the filter + + """ + def __init__(self, mesh: openmc.MeshBase, bins, filter_id=None): + self.mesh = mesh + self.bins = bins + self.id = filter_id + self._translation = None + + @classmethod + def from_volumes(cls, mesh: openmc.MeshBase, volumes: openmc.MeshMaterialVolumes): + """Construct a MeshMaterialFilter from a MeshMaterialVolumes object. + + Parameters + ---------- + mesh : openmc.MeshBase + The mesh object that events will be tallied onto + volumes : openmc.MeshMaterialVolumes + The mesh material volumes to use for the filter + + Returns + ------- + MeshMaterialFilter + A new MeshMaterialFilter instance + + """ + bins = list(zip(*np.where(volumes._materials > -1))) + return cls(mesh, bins) + + def __hash__(self): + data = (type(self).__name__, self.mesh.id, tuple(self.bins.ravel())) + return hash(data) + + def __repr__(self): + string = type(self).__name__ + '\n' + string += '{: <16}=\t{}\n'.format('\tID', self.id) + string += '{: <16}=\t{}\n'.format('\tMesh ID', self.mesh.id) + string += '{: <16}=\n{}\n'.format('\tBins', self.bins) + string += '{: <16}=\t{}\n'.format('\tTranslation', self.translation) + return string + + @property + def shape(self): + return (self.num_bins,) + + @property + def mesh(self): + return self._mesh + + @mesh.setter + def mesh(self, mesh): + cv.check_type('filter mesh', mesh, openmc.MeshBase) + self._mesh = mesh + + @Filter.bins.setter + def bins(self, bins): + pairs = np.empty((len(bins), 2), dtype=int) + for i, (elem, mat) in enumerate(bins): + cv.check_type('element', elem, Integral) + cv.check_type('material', mat, (Integral, openmc.Material)) + pairs[i, 0] = elem + pairs[i, 1] = mat if isinstance(mat, Integral) else mat.id + self._bins = pairs + + def to_xml_element(self): + """Return XML element representing the filter. + + Returns + ------- + element : lxml.etree._Element + XML element containing filter data + + """ + element = ET.Element('filter') + element.set('id', str(self.id)) + element.set('type', self.short_name.lower()) + element.set('mesh', str(self.mesh.id)) + + if self.translation is not None: + element.set('translation', ' '.join(map(str, self.translation))) + + subelement = ET.SubElement(element, 'bins') + subelement.text = ' '.join(str(i) for i in self.bins.ravel()) + + return element + + @classmethod + def from_xml_element(cls, elem: ET.Element, **kwargs) -> MeshMaterialFilter: + filter_id = int(elem.get('id')) + mesh_id = int(elem.get('mesh')) + mesh_obj = kwargs['meshes'][mesh_id] + bins = [int(x) for x in get_text(elem, 'bins').split()] + bins = list(zip(bins[::2], bins[1::2])) + out = cls(mesh_obj, bins, filter_id=filter_id) + + translation = elem.get('translation') + if translation: + out.translation = [float(x) for x in translation.split()] + return out + + @classmethod + def from_hdf5(cls, group, **kwargs): + if group['type'][()].decode() != cls.short_name.lower(): + raise ValueError("Expected HDF5 data for filter type '" + + cls.short_name.lower() + "' but got '" + + group['type'][()].decode() + " instead") + + if 'meshes' not in kwargs: + raise ValueError(cls.__name__ + " requires a 'meshes' keyword " + "argument.") + + mesh_id = group['mesh'][()] + mesh_obj = kwargs['meshes'][mesh_id] + bins = group['bins'][()] + filter_id = int(group.name.split('/')[-1].lstrip('filter ')) + out = cls(mesh_obj, bins, filter_id=filter_id) + + translation = group.get('translation') + if translation: + out.translation = translation[()] + + return out + + def get_pandas_dataframe(self, data_size, stride, **kwargs): + """Builds a Pandas DataFrame for the Filter's bins. + + This method constructs a Pandas DataFrame object for the filter with + columns annotated by filter bin information. This is a helper method for + :meth:`Tally.get_pandas_dataframe`. + + Parameters + ---------- + data_size : int + The total number of bins in the tally corresponding to this filter + stride : int + Stride in memory for the filter + + Returns + ------- + pandas.DataFrame + A Pandas DataFrame with a multi-index column for the cell instance. + The number of rows in the DataFrame is the same as the total number + of bins in the corresponding tally, with the filter bin appropriately + tiled to map to the corresponding tally bins. + + See also + -------- + Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe() + + """ + # Repeat and tile bins as necessary to account for other filters. + bins = np.repeat(self.bins, stride, axis=0) + tile_factor = data_size // len(bins) + bins = np.tile(bins, (tile_factor, 1)) + + columns = pd.MultiIndex.from_product([[self.short_name.lower()], + ['element', 'material']]) + return pd.DataFrame(bins, columns=columns) + + class MeshSurfaceFilter(MeshFilter): """Filter events by surface crossings on a mesh. @@ -1065,6 +1242,9 @@ class MeshSurfaceFilter(MeshFilter): The number of filter bins """ + @property + def shape(self): + return (self.num_bins,) @MeshFilter.mesh.setter def mesh(self, mesh): diff --git a/openmc/lib/filter.py b/openmc/lib/filter.py index 3a76b52a11b..55b681d89ad 100644 --- a/openmc/lib/filter.py +++ b/openmc/lib/filter.py @@ -21,10 +21,10 @@ 'CellInstanceFilter', 'CollisionFilter', 'DistribcellFilter', 'DelayedGroupFilter', 'EnergyFilter', 'EnergyoutFilter', 'EnergyFunctionFilter', 'LegendreFilter', 'MaterialFilter', 'MaterialFromFilter', 'MeshFilter', 'MeshBornFilter', - 'MeshSurfaceFilter', 'MuFilter', 'MuSurfaceFilter', 'ParentNuclideFilter', - 'ParticleFilter', 'PolarFilter', 'SphericalHarmonicsFilter', - 'SpatialLegendreFilter', 'SurfaceFilter', 'UniverseFilter', 'ZernikeFilter', - 'ZernikeRadialFilter', 'filters' + 'MeshMaterialFilter', 'MeshSurfaceFilter', 'MuFilter', 'MuSurfaceFilter', + 'ParentNuclideFilter', 'ParticleFilter', 'PolarFilter', 'SphericalHarmonicsFilter', + 'SpatialLegendreFilter', 'SurfaceFilter', 'TimeFilter', 'UniverseFilter', + 'WeightFilter', 'ZernikeFilter', 'ZernikeRadialFilter', 'filters' ] # Tally functions @@ -480,6 +480,10 @@ def translation(self, translation): _dll.openmc_meshborn_filter_set_translation(self._index, (c_double*3)(*translation)) +class MeshMaterialFilter(Filter): + filter_type = 'meshmaterial' + + class MeshSurfaceFilter(Filter): """MeshSurface filter stored internally. @@ -606,6 +610,10 @@ class SurfaceFilter(Filter): filter_type = 'surface' +class TimeFilter(Filter): + filter_type = 'time' + + class UniverseFilter(Filter): filter_type = 'universe' @@ -643,6 +651,7 @@ class ZernikeRadialFilter(ZernikeFilter): 'cellborn': CellbornFilter, 'cellfrom': CellfromFilter, 'cellinstance': CellInstanceFilter, + 'collision': CollisionFilter, 'delayedgroup': DelayedGroupFilter, 'distribcell': DistribcellFilter, 'energy': EnergyFilter, @@ -653,6 +662,7 @@ class ZernikeRadialFilter(ZernikeFilter): 'materialfrom': MaterialFromFilter, 'mesh': MeshFilter, 'meshborn': MeshBornFilter, + 'meshmaterial': MeshMaterialFilter, 'meshsurface': MeshSurfaceFilter, 'mu': MuFilter, 'musurface': MuSurfaceFilter, @@ -662,7 +672,9 @@ class ZernikeRadialFilter(ZernikeFilter): 'sphericalharmonics': SphericalHarmonicsFilter, 'spatiallegendre': SpatialLegendreFilter, 'surface': SurfaceFilter, + 'time': TimeFilter, 'universe': UniverseFilter, + 'weight': WeightFilter, 'zernike': ZernikeFilter, 'zernikeradial': ZernikeRadialFilter } diff --git a/openmc/tallies.py b/openmc/tallies.py index e990a95f742..06de0205c9e 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -1575,7 +1575,7 @@ def get_reshaped_data(self, value='mean', expand_dims=False): for i, f in enumerate(self.filters): if expand_dims: # Mesh filter indices are backwards so we need to flip them - if isinstance(f, openmc.MeshFilter): + if type(f) in {openmc.MeshFilter, openmc.MeshBornFilter}: fshape = f.shape[::-1] new_shape += fshape idx0, idx1 = i, i + len(fshape) - 1 diff --git a/src/tallies/filter.cpp b/src/tallies/filter.cpp index 17e57a987c7..79817981db0 100644 --- a/src/tallies/filter.cpp +++ b/src/tallies/filter.cpp @@ -25,6 +25,7 @@ #include "openmc/tallies/filter_materialfrom.h" #include "openmc/tallies/filter_mesh.h" #include "openmc/tallies/filter_meshborn.h" +#include "openmc/tallies/filter_meshmaterial.h" #include "openmc/tallies/filter_meshsurface.h" #include "openmc/tallies/filter_mu.h" #include "openmc/tallies/filter_musurface.h" @@ -133,6 +134,8 @@ Filter* Filter::create(const std::string& type, int32_t id) return Filter::create(id); } else if (type == "meshborn") { return Filter::create(id); + } else if (type == "meshmaterial") { + return Filter::create(id); } else if (type == "meshsurface") { return Filter::create(id); } else if (type == "mu") { diff --git a/src/tallies/filter_meshmaterial.cpp b/src/tallies/filter_meshmaterial.cpp new file mode 100644 index 00000000000..6e1f30380f6 --- /dev/null +++ b/src/tallies/filter_meshmaterial.cpp @@ -0,0 +1,185 @@ +#include "openmc/tallies/filter_meshmaterial.h" + +#include // for move + +#include + +#include "openmc/capi.h" +#include "openmc/constants.h" +#include "openmc/container_util.h" +#include "openmc/error.h" +#include "openmc/material.h" +#include "openmc/mesh.h" +#include "openmc/xml_interface.h" + +namespace openmc { + +void MeshMaterialFilter::from_xml(pugi::xml_node node) +{ + // Get mesh ID + auto mesh = get_node_array(node, "mesh"); + if (mesh.size() != 1) { + fatal_error( + "Only one mesh can be specified per " + type_str() + " mesh filter."); + } + + auto id = mesh[0]; + auto search = model::mesh_map.find(id); + if (search == model::mesh_map.end()) { + fatal_error( + fmt::format("Could not find mesh {} specified on tally filter.", id)); + } + set_mesh(search->second); + + // Get pairs of (element index, material) and set the bins + auto bins = get_node_array(node, "bins"); + this->set_bins(bins); + + if (check_for_node(node, "translation")) { + set_translation(get_node_array(node, "translation")); + } +} + +void MeshMaterialFilter::set_bins(span bins) +{ + if (bins.size() % 2 != 0) { + fatal_error( + fmt::format("Size of mesh material bins is not even: {}", bins.size())); + } + + // Create a vector of ElementMat pairs from the flat vector of bins + vector element_mats; + for (int64_t i = 0; i < bins.size() / 2; ++i) { + int32_t element = bins[2 * i]; + int32_t mat_id = bins[2 * i + 1]; + auto search = model::material_map.find(mat_id); + if (search == model::material_map.end()) { + fatal_error(fmt::format( + "Could not find material {} specified on tally filter.", mat_id)); + } + int32_t mat_index = search->second; + element_mats.push_back({element, mat_index}); + } + + this->set_bins(std::move(element_mats)); +} + +void MeshMaterialFilter::set_bins(vector&& bins) +{ + // Swap internal bins_ with the provided vector to avoid copying + bins_.swap(bins); + + // Clear and update the mapping and vector of materials + materials_.clear(); + map_.clear(); + for (std::size_t i = 0; i < bins_.size(); ++i) { + const auto& x = bins_[i]; + assert(x.index_mat >= 0); + assert(x.index_mat < model::materials.size()); + materials_.insert(x.index_mat); + map_[x] = i; + } + + n_bins_ = bins_.size(); +} + +void MeshMaterialFilter::set_mesh(int32_t mesh) +{ + // perform any additional perparation for mesh tallies here + mesh_ = mesh; + model::meshes[mesh_]->prepare_for_point_location(); +} + +void MeshMaterialFilter::set_translation(const Position& translation) +{ + translated_ = true; + translation_ = translation; +} + +void MeshMaterialFilter::set_translation(const double translation[3]) +{ + this->set_translation({translation[0], translation[1], translation[2]}); +} + +void MeshMaterialFilter::get_all_bins( + const Particle& p, TallyEstimator estimator, FilterMatch& match) const +{ + // If current material is not in any bins, don't bother checking + if (!contains(materials_, p.material())) { + return; + } + + Position last_r = p.r_last(); + Position r = p.r(); + Position u = p.u(); + + // apply translation if present + if (translated_) { + last_r -= translation(); + r -= translation(); + } + + if (estimator != TallyEstimator::TRACKLENGTH) { + int32_t index_element = model::meshes[mesh_]->get_bin(r); + if (index_element >= 0) { + auto search = map_.find({index_element, p.material()}); + if (search != map_.end()) { + match.bins_.push_back(search->second); + match.weights_.push_back(1.0); + } + } + } else { + // First determine which elements the particle crosses (may or may not + // actually match bins so we have to adjust bins_/weight_ after) + int32_t n_start = match.bins_.size(); + model::meshes[mesh_]->bins_crossed( + last_r, r, u, match.bins_, match.weights_); + int32_t n_end = match.bins_.size(); + + // Go through bins and weights and check which ones are actually a match + // based on the (element, material) pair. For matches, overwrite the bin. + int i = 0; + for (int j = n_start; j < n_end; ++j) { + int32_t index_element = match.bins_[j]; + double weight = match.weights_[j]; + auto search = map_.find({index_element, p.material()}); + if (search != map_.end()) { + match.bins_[n_start + i] = search->second; + match.weights_[n_start + i] = weight; + ++i; + } + } + + // Resize the vectors to remove the unmatched bins + match.bins_.resize(n_start + i); + } +} + +void MeshMaterialFilter::to_statepoint(hid_t filter_group) const +{ + Filter::to_statepoint(filter_group); + write_dataset(filter_group, "mesh", model::meshes[mesh_]->id_); + + size_t n = bins_.size(); + xt::xtensor data({n, 2}); + for (int64_t i = 0; i < n; ++i) { + const auto& x = bins_[i]; + data(i, 0) = x.index_element; + data(i, 1) = model::materials[x.index_mat]->id_; + } + write_dataset(filter_group, "bins", data); + + if (translated_) { + write_dataset(filter_group, "translation", translation_); + } +} + +std::string MeshMaterialFilter::text_label(int bin) const +{ + auto& x = bins_[bin]; + auto& mesh = *model::meshes.at(mesh_); + return fmt::format("Mesh {}, {}, Material {}", mesh.id(), + mesh.bin_label(x.index_element), model::materials[x.index_mat]->id_); +} + +} // namespace openmc diff --git a/tests/unit_tests/test_filter_meshmaterial.py b/tests/unit_tests/test_filter_meshmaterial.py new file mode 100644 index 00000000000..12053320976 --- /dev/null +++ b/tests/unit_tests/test_filter_meshmaterial.py @@ -0,0 +1,65 @@ +import numpy as np +import openmc +from pytest import approx + + +def test_filter_mesh_material(run_in_tmpdir): + # Create four identical materials + openmc.reset_auto_ids() + materials = [] + for i in range(4): + mat = openmc.Material() + mat.add_nuclide('Fe56', 1.0) + materials.append(mat) + + # Create a slab model with four cells + z_values = [-10., -5., 0., 5., 10.] + planes = [openmc.ZPlane(z) for z in z_values] + planes[0].boundary_type = 'vacuum' + planes[-1].boundary_type = 'vacuum' + regions = [+left & -right for left, right in zip(planes[:-1], planes[1:])] + cells = [openmc.Cell(fill=m, region=r) for r, m in zip(regions, materials)] + model = openmc.Model() + model.geometry = openmc.Geometry(cells) + model.settings.particles = 1_000 + model.settings.batches = 5 + model.settings.run_mode = 'fixed source' + + # Create a mesh that does not align with all planar surfaces + mesh = openmc.RegularMesh() + mesh.lower_left = (-1., -1., -10.) + mesh.upper_right = (1., 1., 10.) + mesh.dimension = (1, 1, 5) + + # Determine material volumes in each mesh element and use result to create a + # MeshMaterialFilter with corresponding bins + vols = mesh.material_volumes(model) + mmf = openmc.MeshMaterialFilter.from_volumes(mesh, vols) + expected_bins = [(0, 1), (1, 1), (1, 2), (2, 2), (2, 3), (3, 3), (3, 4), (4, 4)] + np.testing.assert_equal(mmf.bins, expected_bins) + + # Create two tallies, one with a mesh filter and one with mesh-material + mesh_tally = openmc.Tally() + mesh_tally.filters = [openmc.MeshFilter(mesh)] + mesh_tally.scores = ['flux'] + mesh_material_tally = openmc.Tally() + mesh_material_tally.filters = [mmf] + mesh_material_tally.scores = ['flux'] + model.tallies = [mesh_tally, mesh_material_tally] + + # Run model to get results on the two tallies + model.run(apply_tally_results=True) + + # The sum of the flux in each mesh-material combination within a single mesh + # element should be equal to the flux in that mesh element + mesh_mean = mesh_tally.mean.ravel() + meshmat_mean = mesh_material_tally.mean.ravel() + assert mesh_mean[0] == approx(meshmat_mean[0]) + assert mesh_mean[1] == approx(meshmat_mean[1] + meshmat_mean[2]) + assert mesh_mean[2] == approx(meshmat_mean[3] + meshmat_mean[4]) + assert mesh_mean[3] == approx(meshmat_mean[5] + meshmat_mean[6]) + assert mesh_mean[4] == approx(meshmat_mean[7]) + assert mesh_tally.mean.sum() == approx(mesh_material_tally.mean.sum()) + + # Make sure get_pandas_dataframe method works + mesh_material_tally.get_pandas_dataframe() diff --git a/tests/unit_tests/test_filters.py b/tests/unit_tests/test_filters.py index 60a60e3f56e..8c56a310e17 100644 --- a/tests/unit_tests/test_filters.py +++ b/tests/unit_tests/test_filters.py @@ -333,3 +333,36 @@ def test_weight(): new_f = openmc.Filter.from_xml_element(elem) assert new_f.id == f.id assert np.allclose(new_f.bins, f.bins) + + +def test_mesh_material(): + mat1 = openmc.Material() + mat2 = openmc.Material() + + mesh = openmc.RegularMesh() + mesh.lower_left = (-1., -1., -1.) + mesh.upper_right = (1., 1., 1.) + mesh.dimension = (2, 4, 1) + bins = [(0, mat1), (0, mat2), (6, mat1), (7, mat2)] + f = openmc.MeshMaterialFilter(mesh, bins) + + expected_bins = [(0, mat1.id), (0, mat2.id), (6, mat1.id), (7, mat2.id)] + assert np.allclose(f.bins, expected_bins) + assert f.mesh == mesh + assert f.shape == (4,) + + # to_xml_element() + elem = f.to_xml_element() + assert elem.tag == 'filter' + assert elem.attrib['type'] == 'meshmaterial' + + # from_xml_element() + new_f = openmc.Filter.from_xml_element(elem, meshes={mesh.id: mesh}) + assert isinstance(new_f, openmc.MeshMaterialFilter) + assert new_f.id == f.id + assert new_f.mesh == f.mesh + assert np.allclose(new_f.bins, expected_bins) + + # Test hash and str + hash(f) + str(f) From 56b44aa4e8ed8e8c55a8f4a7ec0c347fe9390449 Mon Sep 17 00:00:00 2001 From: John Tramm Date: Tue, 10 Jun 2025 09:13:08 -0500 Subject: [PATCH 035/159] Random Ray Missed Cell Policy Change for Adjoint Mode (#3434) --- src/random_ray/flat_source_domain.cpp | 2 +- .../flat/results_true.dat | 340 +++++++++--------- .../linear/results_true.dat | 254 ++++++------- 3 files changed, 298 insertions(+), 298 deletions(-) diff --git a/src/random_ray/flat_source_domain.cpp b/src/random_ray/flat_source_domain.cpp index 367c63af4bc..9df6513aee8 100644 --- a/src/random_ray/flat_source_domain.cpp +++ b/src/random_ray/flat_source_domain.cpp @@ -298,7 +298,7 @@ int64_t FlatSourceDomain::add_source_to_scalar_flux() // in the cell we will use the previous iteration's flux estimate. This // injects a small degree of correlation into the simulation, but this // is going to be trivial when the miss rate is a few percent or less. - if (source_regions_.external_source_present(sr) && !adjoint_) { + if (source_regions_.external_source_present(sr)) { set_flux_to_old_flux(sr, g); } else { set_flux_to_source(sr, g); diff --git a/tests/regression_tests/weightwindows_fw_cadis_mesh/flat/results_true.dat b/tests/regression_tests/weightwindows_fw_cadis_mesh/flat/results_true.dat index e14b7e19989..0354501d15d 100644 --- a/tests/regression_tests/weightwindows_fw_cadis_mesh/flat/results_true.dat +++ b/tests/regression_tests/weightwindows_fw_cadis_mesh/flat/results_true.dat @@ -36,15 +36,15 @@ Lower Bounds 1.73e-01 2.44e-01 3.63e-01 -1.23e-01 +1.22e-01 1.71e-01 1.66e-01 1.69e-01 -1.71e-01 +1.70e-01 1.67e-01 1.71e-01 1.64e-01 -1.56e-01 +1.55e-01 1.67e-01 1.64e-01 1.68e-01 @@ -96,7 +96,7 @@ Lower Bounds 1.80e-01 2.59e-01 2.97e-01 -1.07e-01 +1.06e-01 1.68e-01 1.60e-01 1.58e-01 @@ -156,7 +156,7 @@ Lower Bounds 1.49e-01 2.14e-01 1.76e-01 -6.71e-02 +6.70e-02 1.72e-01 1.66e-01 1.74e-01 @@ -192,7 +192,7 @@ Lower Bounds 2.37e-01 2.51e-01 2.64e-01 -2.43e-01 +2.42e-01 2.39e-01 2.32e-01 2.32e-01 @@ -209,12 +209,12 @@ Lower Bounds 2.86e-01 3.17e-01 2.60e-01 -2.39e-01 +2.38e-01 1.88e-01 1.97e-01 1.84e-01 1.50e-01 -9.99e-02 +9.98e-02 3.88e-02 1.90e-02 2.18e-01 @@ -340,7 +340,7 @@ Lower Bounds 1.65e-01 1.63e-01 1.59e-01 -1.58e-01 +1.57e-01 1.59e-01 1.54e-01 1.54e-01 @@ -416,7 +416,7 @@ Lower Bounds 2.65e-01 2.54e-01 2.58e-01 -2.81e-01 +2.80e-01 2.53e-01 2.33e-01 2.40e-01 @@ -503,7 +503,7 @@ Lower Bounds 3.36e-01 1.29e-01 1.57e-01 -1.68e-01 +1.67e-01 1.66e-01 1.63e-01 1.63e-01 @@ -513,7 +513,7 @@ Lower Bounds 1.56e-01 1.59e-01 1.65e-01 -1.60e-01 +1.59e-01 2.43e-01 3.30e-01 1.08e-01 @@ -543,7 +543,7 @@ Lower Bounds 1.54e-01 1.56e-01 1.67e-01 -1.70e-01 +1.69e-01 2.42e-01 3.48e-01 1.32e-01 @@ -661,7 +661,7 @@ Lower Bounds 2.34e-01 2.20e-01 1.88e-01 -1.78e-01 +1.77e-01 1.34e-01 1.25e-01 1.07e-01 @@ -676,7 +676,7 @@ Lower Bounds 8.09e-02 8.37e-02 5.69e-02 -6.46e-02 +6.45e-02 3.15e-02 3.86e-02 3.51e-02 @@ -770,14 +770,14 @@ Lower Bounds 1.53e-01 1.64e-01 2.46e-01 -3.03e-01 +3.02e-01 1.11e-01 1.63e-01 1.59e-01 1.68e-01 -1.56e-01 +1.59e-01 1.52e-01 -1.55e-01 +1.54e-01 1.56e-01 1.49e-01 1.49e-01 @@ -850,7 +850,7 @@ Lower Bounds 1.73e-01 1.65e-01 1.70e-01 -7.83e-02 +1.66e-01 1.73e-01 1.78e-01 1.65e-01 @@ -877,7 +877,7 @@ Lower Bounds 2.14e-01 8.26e-02 2.65e-02 -3.07e-01 +3.06e-01 2.51e-01 2.10e-01 2.95e-01 @@ -1091,7 +1091,7 @@ Lower Bounds 2.30e-01 2.33e-01 2.43e-01 -2.46e-01 +2.45e-01 2.48e-01 2.46e-01 2.21e-01 @@ -1124,7 +1124,7 @@ Lower Bounds 1.07e-01 8.75e-02 1.17e-01 -5.68e-02 +5.67e-02 7.16e-02 5.22e-02 3.31e-02 @@ -1317,7 +1317,7 @@ Lower Bounds 2.20e-01 2.27e-01 2.41e-01 -2.35e-01 +2.34e-01 2.13e-01 2.04e-01 2.13e-01 @@ -1359,14 +1359,14 @@ Lower Bounds 8.92e-03 1.61e-01 1.59e-01 -1.57e-01 +1.56e-01 1.59e-01 1.51e-01 1.49e-01 1.54e-01 1.51e-01 1.53e-01 -1.49e-01 +1.48e-01 1.52e-01 1.48e-01 2.27e-01 @@ -1478,12 +1478,12 @@ Lower Bounds 1.68e-01 5.02e-02 1.53e-01 -7.09e-02 +1.48e-01 1.47e-01 1.48e-01 1.43e-01 1.46e-01 -6.84e-02 +1.42e-01 1.27e-01 1.24e-01 1.31e-01 @@ -1523,7 +1523,7 @@ Lower Bounds 7.50e-02 2.64e-02 1.58e-01 -1.56e-01 +1.55e-01 1.50e-01 1.45e-01 1.50e-01 @@ -1575,7 +1575,7 @@ Lower Bounds 6.95e-02 7.10e-02 4.92e-02 -4.74e-02 +4.73e-02 3.52e-02 4.69e-02 4.03e-02 @@ -1669,7 +1669,7 @@ Lower Bounds 1.34e-01 1.36e-01 1.50e-01 -1.96e-01 +1.95e-01 1.29e-01 4.48e-02 1.49e-01 @@ -1784,7 +1784,7 @@ Lower Bounds 1.53e-01 1.76e-01 1.13e-01 -9.77e-02 +9.76e-02 8.62e-02 1.49e-01 1.29e-01 @@ -1898,7 +1898,7 @@ Lower Bounds 1.53e-01 5.25e-02 1.45e-01 -1.41e-01 +1.44e-01 1.44e-01 1.36e-01 1.36e-01 @@ -1928,13 +1928,13 @@ Lower Bounds 7.71e-02 3.01e-02 1.38e-01 -1.12e-01 +1.35e-01 1.38e-01 1.41e-01 1.44e-01 1.46e-01 1.34e-01 -1.11e-01 +1.28e-01 1.24e-01 1.20e-01 1.22e-01 @@ -1955,7 +1955,7 @@ Lower Bounds 1.21e-01 1.25e-01 1.84e-01 -9.19e-02 +9.18e-02 2.31e-02 1.48e-01 1.32e-01 @@ -2039,7 +2039,7 @@ Lower Bounds 1.55e-01 1.46e-01 1.44e-01 -1.48e-01 +1.47e-01 1.42e-01 1.44e-01 1.40e-01 @@ -2067,7 +2067,7 @@ Lower Bounds 1.43e-01 1.44e-01 1.46e-01 -1.54e-01 +1.53e-01 1.50e-01 1.51e-01 1.54e-01 @@ -2214,7 +2214,7 @@ Lower Bounds 1.83e-02 2.39e-01 1.91e-01 -1.79e-01 +1.80e-01 2.07e-01 1.97e-01 2.03e-01 @@ -2238,7 +2238,7 @@ Lower Bounds 1.30e-01 1.06e-01 9.60e-02 -7.07e-02 +7.06e-02 4.00e-02 1.71e-02 7.45e-03 @@ -2343,7 +2343,7 @@ Lower Bounds 1.32e-01 1.41e-01 1.41e-01 -1.40e-01 +1.39e-01 2.04e-01 1.15e-01 3.10e-02 @@ -2471,7 +2471,7 @@ Lower Bounds 3.06e-02 3.68e-02 4.09e-02 -5.27e-02 +5.26e-02 4.71e-02 3.30e-02 4.67e-02 @@ -2493,7 +2493,7 @@ Lower Bounds 1.53e-01 1.51e-01 1.54e-01 -1.45e-01 +1.44e-01 1.43e-01 3.50e-02 1.38e-02 @@ -2533,7 +2533,7 @@ Lower Bounds 1.54e-01 1.59e-01 1.65e-01 -5.07e-02 +1.63e-01 1.43e-01 1.52e-01 1.45e-01 @@ -2548,7 +2548,7 @@ Lower Bounds 1.55e-01 1.59e-01 1.59e-01 -1.53e-01 +1.54e-01 1.47e-01 1.53e-01 1.51e-01 @@ -2648,7 +2648,7 @@ Lower Bounds 5.02e-02 1.48e-02 1.33e-01 -5.34e-02 +1.31e-01 1.34e-01 1.36e-01 1.35e-01 @@ -2678,7 +2678,7 @@ Lower Bounds 2.21e-02 7.42e-03 1.30e-01 -7.03e-02 +7.02e-02 8.47e-02 1.05e-01 8.11e-02 @@ -2726,7 +2726,7 @@ Lower Bounds 2.10e-01 2.47e-01 2.20e-01 -2.21e-01 +2.20e-01 2.29e-01 2.22e-01 2.06e-01 @@ -2776,7 +2776,7 @@ Lower Bounds 2.54e-01 2.18e-01 2.16e-01 -2.24e-01 +2.23e-01 1.99e-01 1.77e-01 1.57e-01 @@ -3026,7 +3026,7 @@ Lower Bounds 1.80e-01 2.09e-01 2.09e-01 -1.83e-01 +1.82e-01 2.00e-01 1.82e-01 1.57e-01 @@ -3120,7 +3120,7 @@ Lower Bounds 8.59e-02 7.51e-02 7.34e-02 -6.11e-02 +6.10e-02 6.19e-02 4.42e-02 3.60e-02 @@ -3164,7 +3164,7 @@ Lower Bounds 1.09e-01 8.20e-02 7.49e-02 -7.94e-02 +7.93e-02 7.32e-02 5.71e-02 3.93e-02 @@ -3188,7 +3188,7 @@ Lower Bounds 1.79e-02 8.68e-03 8.76e-02 -6.94e-02 +6.93e-02 1.01e-01 9.79e-02 5.28e-02 @@ -3236,7 +3236,7 @@ Lower Bounds 5.48e-02 7.05e-02 7.64e-02 -6.19e-02 +6.18e-02 5.28e-02 6.84e-02 5.17e-02 @@ -3404,7 +3404,7 @@ Upper Bounds 7.61e-01 8.32e-01 8.60e-01 -8.42e-01 +8.41e-01 8.42e-01 8.32e-01 8.34e-01 @@ -3412,15 +3412,15 @@ Upper Bounds 8.65e-01 1.22e+00 1.82e+00 -6.13e-01 +6.12e-01 8.56e-01 -8.32e-01 +8.31e-01 8.43e-01 -8.53e-01 +8.52e-01 8.33e-01 8.57e-01 8.19e-01 -7.78e-01 +7.77e-01 8.33e-01 8.19e-01 8.41e-01 @@ -3472,8 +3472,8 @@ Upper Bounds 8.98e-01 1.29e+00 1.49e+00 -5.33e-01 -8.40e-01 +5.32e-01 +8.39e-01 7.98e-01 7.91e-01 8.03e-01 @@ -3526,7 +3526,7 @@ Upper Bounds 7.71e-01 7.75e-01 7.58e-01 -7.64e-01 +7.63e-01 7.06e-01 7.64e-01 7.43e-01 @@ -3556,7 +3556,7 @@ Upper Bounds 8.72e-01 7.91e-01 8.39e-01 -7.95e-01 +7.94e-01 7.87e-01 7.88e-01 7.38e-01 @@ -3589,7 +3589,7 @@ Upper Bounds 9.40e-01 9.83e-01 9.19e-01 -7.52e-01 +7.51e-01 4.99e-01 1.94e-01 9.48e-02 @@ -3615,7 +3615,7 @@ Upper Bounds 8.13e-01 7.97e-01 8.76e-01 -8.45e-01 +8.44e-01 7.95e-01 8.18e-01 8.84e-01 @@ -3649,7 +3649,7 @@ Upper Bounds 8.01e-01 8.48e-01 8.52e-01 -8.71e-01 +8.70e-01 1.31e+00 1.65e+00 5.73e-01 @@ -3713,10 +3713,10 @@ Upper Bounds 1.17e+00 1.50e+00 5.74e-01 -8.24e-01 +8.23e-01 8.15e-01 7.95e-01 -7.88e-01 +7.87e-01 7.97e-01 7.70e-01 7.69e-01 @@ -3761,7 +3761,7 @@ Upper Bounds 8.45e-01 8.73e-01 8.56e-01 -8.16e-01 +8.15e-01 8.44e-01 8.36e-01 7.99e-01 @@ -3774,11 +3774,11 @@ Upper Bounds 6.73e-01 2.04e-01 8.93e-01 -8.59e-01 +8.60e-01 8.64e-01 8.51e-01 8.70e-01 -8.91e-01 +8.90e-01 8.41e-01 7.94e-01 7.85e-01 @@ -3856,10 +3856,10 @@ Upper Bounds 8.14e-01 7.91e-01 8.00e-01 -7.95e-01 +7.94e-01 8.24e-01 8.16e-01 -8.87e-01 +8.86e-01 1.22e+00 1.96e+00 8.18e-01 @@ -3879,7 +3879,7 @@ Upper Bounds 1.68e+00 6.47e-01 7.87e-01 -8.38e-01 +8.37e-01 8.29e-01 8.15e-01 8.16e-01 @@ -3889,13 +3889,13 @@ Upper Bounds 7.82e-01 7.95e-01 8.24e-01 -7.98e-01 +7.97e-01 1.22e+00 1.65e+00 5.42e-01 8.09e-01 8.09e-01 -8.05e-01 +8.04e-01 7.72e-01 8.14e-01 8.20e-01 @@ -3919,7 +3919,7 @@ Upper Bounds 7.72e-01 7.80e-01 8.33e-01 -8.48e-01 +8.47e-01 1.21e+00 1.74e+00 6.61e-01 @@ -3934,7 +3934,7 @@ Upper Bounds 7.56e-01 7.70e-01 7.83e-01 -7.97e-01 +7.96e-01 1.13e+00 1.26e+00 4.66e-01 @@ -3943,7 +3943,7 @@ Upper Bounds 7.98e-01 7.75e-01 7.87e-01 -7.92e-01 +7.91e-01 7.70e-01 7.35e-01 7.31e-01 @@ -3959,7 +3959,7 @@ Upper Bounds 8.01e-01 7.99e-01 7.90e-01 -7.89e-01 +7.88e-01 7.83e-01 7.33e-01 7.48e-01 @@ -3975,7 +3975,7 @@ Upper Bounds 8.32e-01 7.85e-01 7.86e-01 -7.51e-01 +7.50e-01 7.01e-01 7.38e-01 7.30e-01 @@ -4037,13 +4037,13 @@ Upper Bounds 1.17e+00 1.10e+00 9.39e-01 -8.88e-01 +8.87e-01 6.70e-01 6.23e-01 5.35e-01 2.34e-01 6.47e-02 -6.45e-01 +6.44e-01 4.31e-01 7.11e-01 6.80e-01 @@ -4063,7 +4063,7 @@ Upper Bounds 8.31e-01 8.52e-01 8.43e-01 -8.06e-01 +8.05e-01 7.88e-01 7.57e-01 7.58e-01 @@ -4083,7 +4083,7 @@ Upper Bounds 7.76e-01 7.95e-01 8.30e-01 -7.96e-01 +7.95e-01 8.37e-01 1.18e+00 1.62e+00 @@ -4105,7 +4105,7 @@ Upper Bounds 5.36e-01 8.08e-01 8.15e-01 -8.00e-01 +7.99e-01 8.03e-01 7.68e-01 7.86e-01 @@ -4122,7 +4122,7 @@ Upper Bounds 7.72e-01 8.04e-01 8.28e-01 -7.75e-01 +7.74e-01 7.64e-01 7.80e-01 7.54e-01 @@ -4151,12 +4151,12 @@ Upper Bounds 8.13e-01 7.95e-01 8.40e-01 -7.80e-01 +7.97e-01 7.59e-01 -7.73e-01 +7.72e-01 7.80e-01 7.46e-01 -7.45e-01 +7.44e-01 7.55e-01 7.27e-01 7.44e-01 @@ -4226,8 +4226,8 @@ Upper Bounds 8.65e-01 8.27e-01 8.48e-01 -3.91e-01 -8.66e-01 +8.31e-01 +8.65e-01 8.92e-01 8.23e-01 7.50e-01 @@ -4265,7 +4265,7 @@ Upper Bounds 9.09e-01 6.43e-01 6.18e-01 -5.19e-01 +5.18e-01 2.26e-01 8.71e-02 5.78e-01 @@ -4367,7 +4367,7 @@ Upper Bounds 7.08e-01 7.78e-01 7.45e-01 -7.62e-01 +7.61e-01 7.55e-01 7.92e-01 1.16e+00 @@ -4376,7 +4376,7 @@ Upper Bounds 7.40e-01 7.79e-01 7.94e-01 -7.60e-01 +7.59e-01 7.99e-01 7.16e-01 7.14e-01 @@ -4420,7 +4420,7 @@ Upper Bounds 2.76e-01 8.13e-01 8.09e-01 -7.87e-01 +7.86e-01 7.66e-01 7.26e-01 7.62e-01 @@ -4531,7 +4531,7 @@ Upper Bounds 7.86e-01 7.98e-01 7.43e-01 -7.69e-01 +7.68e-01 7.51e-01 7.70e-01 8.08e-01 @@ -4583,7 +4583,7 @@ Upper Bounds 1.19e+00 1.33e+00 5.12e-01 -7.76e-01 +7.75e-01 7.68e-01 7.84e-01 7.62e-01 @@ -4631,14 +4631,14 @@ Upper Bounds 7.78e-01 7.65e-01 7.86e-01 -7.10e-01 +7.09e-01 7.60e-01 7.43e-01 7.27e-01 6.62e-01 6.51e-01 6.35e-01 -7.04e-01 +7.03e-01 7.07e-01 1.06e+00 7.73e-01 @@ -4648,7 +4648,7 @@ Upper Bounds 7.63e-01 7.80e-01 7.34e-01 -7.23e-01 +7.24e-01 7.02e-01 6.87e-01 7.13e-01 @@ -4724,7 +4724,7 @@ Upper Bounds 4.81e-01 4.96e-01 3.78e-01 -5.26e-01 +5.25e-01 3.10e-01 2.83e-01 1.99e-01 @@ -4735,14 +4735,14 @@ Upper Bounds 4.46e-02 8.04e-01 7.95e-01 -7.83e-01 +7.82e-01 7.95e-01 7.54e-01 7.47e-01 7.72e-01 7.56e-01 7.67e-01 -7.43e-01 +7.42e-01 7.58e-01 7.40e-01 1.14e+00 @@ -4779,7 +4779,7 @@ Upper Bounds 1.24e+00 5.06e-01 7.87e-01 -7.41e-01 +7.40e-01 7.55e-01 7.36e-01 7.30e-01 @@ -4821,7 +4821,7 @@ Upper Bounds 7.08e-01 7.57e-01 1.07e+00 -7.06e-01 +7.05e-01 2.16e-01 7.82e-01 7.85e-01 @@ -4854,12 +4854,12 @@ Upper Bounds 8.40e-01 2.51e-01 7.67e-01 -3.55e-01 +7.41e-01 7.36e-01 7.41e-01 7.16e-01 -7.29e-01 -3.42e-01 +7.28e-01 +7.11e-01 6.33e-01 6.18e-01 6.57e-01 @@ -4874,7 +4874,7 @@ Upper Bounds 7.36e-01 7.09e-01 6.77e-01 -6.93e-01 +6.94e-01 6.91e-01 6.86e-01 6.77e-01 @@ -4899,8 +4899,8 @@ Upper Bounds 3.75e-01 1.32e-01 7.90e-01 -7.78e-01 -7.50e-01 +7.77e-01 +7.49e-01 7.25e-01 7.49e-01 7.50e-01 @@ -4964,7 +4964,7 @@ Upper Bounds 7.66e-01 7.81e-01 7.83e-01 -7.80e-01 +7.79e-01 7.68e-01 7.20e-01 7.09e-01 @@ -5045,7 +5045,7 @@ Upper Bounds 6.71e-01 6.81e-01 7.49e-01 -9.78e-01 +9.77e-01 6.44e-01 2.24e-01 7.44e-01 @@ -5148,7 +5148,7 @@ Upper Bounds 8.35e-01 8.39e-01 9.59e-01 -9.14e-01 +9.15e-01 7.74e-01 5.71e-01 1.72e-01 @@ -5191,7 +5191,7 @@ Upper Bounds 7.60e-01 7.46e-01 7.71e-01 -7.12e-01 +7.11e-01 6.80e-01 6.82e-01 7.51e-01 @@ -5229,7 +5229,7 @@ Upper Bounds 8.27e-01 2.75e-01 7.59e-01 -7.20e-01 +7.19e-01 7.47e-01 7.32e-01 7.40e-01 @@ -5256,7 +5256,7 @@ Upper Bounds 7.13e-01 7.47e-01 9.68e-01 -6.86e-01 +6.85e-01 2.05e-01 7.59e-01 7.40e-01 @@ -5266,22 +5266,22 @@ Upper Bounds 6.98e-01 7.04e-01 6.61e-01 -6.80e-01 -6.80e-01 +6.79e-01 +6.79e-01 6.86e-01 7.42e-01 1.01e+00 7.65e-01 2.63e-01 7.27e-01 -7.07e-01 +7.22e-01 7.21e-01 6.82e-01 6.82e-01 6.79e-01 6.66e-01 6.58e-01 -6.32e-01 +6.31e-01 6.61e-01 6.87e-01 6.88e-01 @@ -5304,13 +5304,13 @@ Upper Bounds 3.86e-01 1.50e-01 6.91e-01 -5.58e-01 +6.77e-01 6.91e-01 7.07e-01 7.18e-01 7.31e-01 6.70e-01 -5.57e-01 +6.39e-01 6.18e-01 6.02e-01 6.11e-01 @@ -5349,7 +5349,7 @@ Upper Bounds 4.84e-01 1.41e-01 7.40e-01 -6.74e-01 +6.73e-01 6.74e-01 7.15e-01 7.22e-01 @@ -5415,16 +5415,16 @@ Upper Bounds 7.76e-01 7.30e-01 7.18e-01 -7.38e-01 +7.37e-01 7.09e-01 -7.21e-01 +7.20e-01 7.00e-01 7.72e-01 9.68e-01 6.90e-01 2.52e-01 7.57e-01 -7.65e-01 +7.64e-01 7.45e-01 7.47e-01 7.46e-01 @@ -5433,9 +5433,9 @@ Upper Bounds 7.52e-01 7.26e-01 6.82e-01 -7.10e-01 +7.09e-01 7.86e-01 -1.05e+00 +1.04e+00 7.77e-01 2.28e-01 7.74e-01 @@ -5443,7 +5443,7 @@ Upper Bounds 7.17e-01 7.21e-01 7.29e-01 -7.68e-01 +7.67e-01 7.52e-01 7.57e-01 7.68e-01 @@ -5504,7 +5504,7 @@ Upper Bounds 7.08e-01 7.09e-01 7.14e-01 -6.80e-01 +6.79e-01 6.85e-01 6.58e-01 6.83e-01 @@ -5519,7 +5519,7 @@ Upper Bounds 7.21e-01 7.00e-01 6.83e-01 -6.44e-01 +6.43e-01 6.62e-01 6.42e-01 6.10e-01 @@ -5566,7 +5566,7 @@ Upper Bounds 6.70e-01 6.87e-01 6.53e-01 -6.04e-01 +6.03e-01 5.84e-01 5.54e-01 5.65e-01 @@ -5574,7 +5574,7 @@ Upper Bounds 4.40e-01 1.48e-01 7.67e-01 -6.96e-01 +6.97e-01 7.26e-01 7.26e-01 6.91e-01 @@ -5590,7 +5590,7 @@ Upper Bounds 9.16e-02 1.20e+00 9.57e-01 -8.97e-01 +8.98e-01 1.03e+00 9.83e-01 1.01e+00 @@ -5655,7 +5655,7 @@ Upper Bounds 7.63e-01 7.61e-01 7.53e-01 -7.70e-01 +7.69e-01 7.59e-01 7.54e-01 7.39e-01 @@ -5667,18 +5667,18 @@ Upper Bounds 7.39e-01 7.32e-01 7.64e-01 -7.36e-01 +7.35e-01 7.69e-01 7.88e-01 7.27e-01 7.35e-01 7.32e-01 7.27e-01 -7.50e-01 +7.49e-01 9.97e-01 7.08e-01 2.42e-01 -7.31e-01 +7.30e-01 7.41e-01 7.80e-01 7.20e-01 @@ -5719,7 +5719,7 @@ Upper Bounds 6.58e-01 7.03e-01 7.03e-01 -6.98e-01 +6.97e-01 1.02e+00 5.77e-01 1.55e-01 @@ -5766,12 +5766,12 @@ Upper Bounds 6.04e-01 6.27e-01 8.72e-01 -4.59e-01 +4.58e-01 1.30e-01 6.94e-01 6.76e-01 6.98e-01 -7.17e-01 +7.16e-01 7.24e-01 6.61e-01 6.58e-01 @@ -5815,7 +5815,7 @@ Upper Bounds 7.13e-02 9.78e-01 8.94e-01 -9.01e-01 +9.02e-01 9.75e-01 9.93e-01 9.72e-01 @@ -5869,7 +5869,7 @@ Upper Bounds 7.65e-01 7.57e-01 7.69e-01 -7.23e-01 +7.22e-01 7.14e-01 1.75e-01 6.92e-02 @@ -5901,7 +5901,7 @@ Upper Bounds 7.12e-01 8.00e-01 1.03e+00 -5.17e-01 +5.16e-01 1.38e-01 7.80e-01 7.93e-01 @@ -5909,7 +5909,7 @@ Upper Bounds 7.70e-01 7.94e-01 8.27e-01 -2.53e-01 +8.15e-01 7.14e-01 7.62e-01 7.24e-01 @@ -5924,7 +5924,7 @@ Upper Bounds 7.75e-01 7.94e-01 7.94e-01 -7.67e-01 +7.68e-01 7.35e-01 7.64e-01 7.55e-01 @@ -5940,7 +5940,7 @@ Upper Bounds 7.62e-01 7.55e-01 7.49e-01 -7.14e-01 +7.15e-01 7.33e-01 7.29e-01 6.88e-01 @@ -5953,10 +5953,10 @@ Upper Bounds 7.63e-01 7.78e-01 7.67e-01 -7.39e-01 +7.38e-01 7.39e-01 7.04e-01 -6.89e-01 +6.90e-01 7.02e-01 7.00e-01 6.50e-01 @@ -5967,7 +5967,7 @@ Upper Bounds 7.31e-01 7.02e-01 7.48e-01 -7.26e-01 +7.25e-01 7.23e-01 7.48e-01 7.26e-01 @@ -6005,7 +6005,7 @@ Upper Bounds 5.90e-01 6.17e-01 5.70e-01 -8.00e-01 +7.99e-01 3.80e-01 1.34e-01 6.68e-01 @@ -6024,7 +6024,7 @@ Upper Bounds 2.51e-01 7.41e-02 6.65e-01 -2.67e-01 +6.54e-01 6.72e-01 6.79e-01 6.77e-01 @@ -6036,11 +6036,11 @@ Upper Bounds 5.40e-01 5.07e-01 5.77e-01 -2.23e-01 +2.24e-01 6.42e-02 8.66e-01 8.00e-01 -8.00e-01 +8.01e-01 8.48e-01 8.27e-01 8.32e-01 @@ -6048,13 +6048,13 @@ Upper Bounds 9.02e-01 9.05e-01 7.29e-01 -6.76e-01 +6.75e-01 5.88e-01 3.48e-01 1.10e-01 3.71e-02 6.49e-01 -3.52e-01 +3.51e-01 4.24e-01 5.24e-01 4.05e-01 @@ -6103,7 +6103,7 @@ Upper Bounds 1.23e+00 1.10e+00 1.10e+00 -1.15e+00 +1.14e+00 1.11e+00 1.03e+00 9.91e-01 @@ -6147,7 +6147,7 @@ Upper Bounds 1.03e+00 1.06e+00 1.10e+00 -1.04e+00 +1.05e+00 1.13e+00 1.27e+00 1.09e+00 @@ -6184,7 +6184,7 @@ Upper Bounds 8.41e-01 9.51e-01 9.08e-01 -7.46e-01 +7.45e-01 7.77e-01 3.05e-01 9.78e-02 @@ -6218,7 +6218,7 @@ Upper Bounds 8.06e-01 2.24e-01 7.49e-02 -9.70e-01 +9.69e-01 1.03e+00 1.10e+00 1.01e+00 @@ -6229,7 +6229,7 @@ Upper Bounds 8.30e-01 8.02e-01 8.09e-01 -8.10e-01 +8.11e-01 7.80e-01 2.46e-01 7.63e-02 @@ -6250,7 +6250,7 @@ Upper Bounds 7.72e-02 7.36e-01 7.62e-01 -8.87e-01 +8.86e-01 8.71e-01 9.21e-01 9.57e-01 @@ -6287,7 +6287,7 @@ Upper Bounds 2.20e-01 2.69e-01 4.39e-01 -2.89e-01 +2.88e-01 1.35e-01 1.30e-01 7.47e-02 @@ -6335,7 +6335,7 @@ Upper Bounds 7.01e-01 6.46e-01 5.40e-01 -4.50e-01 +4.49e-01 2.04e-01 7.38e-02 1.18e+00 @@ -6345,7 +6345,7 @@ Upper Bounds 9.38e-01 1.19e+00 9.74e-01 -1.02e+00 +1.01e+00 9.23e-01 8.23e-01 8.89e-01 @@ -6362,7 +6362,7 @@ Upper Bounds 1.36e+00 1.18e+00 1.09e+00 -8.67e-01 +8.66e-01 6.32e-01 4.57e-01 3.13e-01 @@ -6402,7 +6402,7 @@ Upper Bounds 9.01e-01 1.04e+00 1.05e+00 -9.13e-01 +9.12e-01 1.00e+00 9.12e-01 7.83e-01 @@ -6515,7 +6515,7 @@ Upper Bounds 1.43e-01 8.60e-02 5.91e-02 -4.19e-02 +4.18e-02 2.09e-02 1.01e-02 6.92e-02 @@ -6639,7 +6639,7 @@ Upper Bounds 3.97e-02 3.67e-02 1.54e-01 -2.67e-01 +2.66e-01 2.58e-01 2.66e-01 2.93e-01 diff --git a/tests/regression_tests/weightwindows_fw_cadis_mesh/linear/results_true.dat b/tests/regression_tests/weightwindows_fw_cadis_mesh/linear/results_true.dat index 3afdc56ca66..c1066ae8285 100644 --- a/tests/regression_tests/weightwindows_fw_cadis_mesh/linear/results_true.dat +++ b/tests/regression_tests/weightwindows_fw_cadis_mesh/linear/results_true.dat @@ -60,7 +60,7 @@ Lower Bounds 1.68e-01 1.70e-01 1.75e-01 -1.73e-01 +1.72e-01 1.73e-01 1.65e-01 1.84e-01 @@ -281,7 +281,7 @@ Lower Bounds 1.86e-01 1.81e-01 1.83e-01 -1.73e-01 +1.74e-01 1.67e-01 1.72e-01 1.75e-01 @@ -722,7 +722,7 @@ Lower Bounds 1.63e-01 1.58e-01 1.70e-01 -1.70e-01 +1.69e-01 1.84e-01 2.44e-01 2.71e-01 @@ -775,7 +775,7 @@ Lower Bounds 1.69e-01 1.67e-01 1.76e-01 -1.59e-01 +1.64e-01 1.59e-01 1.58e-01 1.64e-01 @@ -835,7 +835,7 @@ Lower Bounds 1.76e-01 1.78e-01 1.68e-01 -1.78e-01 +1.79e-01 1.73e-01 1.69e-01 1.62e-01 @@ -850,11 +850,11 @@ Lower Bounds 1.88e-01 1.77e-01 1.78e-01 -8.59e-02 +1.76e-01 1.82e-01 1.90e-01 1.76e-01 -1.63e-01 +1.62e-01 1.69e-01 1.69e-01 1.69e-01 @@ -1201,7 +1201,7 @@ Lower Bounds 1.57e-01 1.58e-01 1.58e-01 -1.57e-01 +1.56e-01 1.58e-01 1.66e-01 2.50e-01 @@ -1223,7 +1223,7 @@ Lower Bounds 2.18e-01 9.03e-02 1.68e-01 -1.70e-01 +1.69e-01 1.64e-01 1.56e-01 1.58e-01 @@ -1382,7 +1382,7 @@ Lower Bounds 1.52e-01 1.59e-01 1.52e-01 -1.49e-01 +1.48e-01 1.55e-01 2.25e-01 2.01e-01 @@ -1478,12 +1478,12 @@ Lower Bounds 1.63e-01 4.89e-02 1.64e-01 -6.61e-02 +1.55e-01 1.57e-01 1.53e-01 1.48e-01 1.53e-01 -7.58e-02 +1.52e-01 1.40e-01 1.38e-01 1.40e-01 @@ -1539,7 +1539,7 @@ Lower Bounds 2.68e-02 2.40e-01 2.25e-01 -2.21e-01 +2.22e-01 2.27e-01 2.31e-01 2.20e-01 @@ -1572,7 +1572,7 @@ Lower Bounds 8.97e-02 4.80e-02 4.87e-02 -6.44e-02 +6.43e-02 6.38e-02 4.70e-02 4.40e-02 @@ -1581,7 +1581,7 @@ Lower Bounds 4.03e-02 1.52e-02 8.66e-03 -6.15e-03 +6.14e-03 1.56e-01 1.57e-01 1.54e-01 @@ -1637,7 +1637,7 @@ Lower Bounds 1.53e-01 1.47e-01 1.42e-01 -1.57e-01 +1.56e-01 1.66e-01 2.10e-01 1.46e-01 @@ -1792,7 +1792,7 @@ Lower Bounds 4.29e-02 1.08e-02 4.27e-03 -7.46e-02 +7.45e-02 7.65e-02 5.07e-02 4.76e-02 @@ -1898,7 +1898,7 @@ Lower Bounds 1.50e-01 5.21e-02 1.53e-01 -1.49e-01 +1.53e-01 1.52e-01 1.45e-01 1.45e-01 @@ -1928,13 +1928,13 @@ Lower Bounds 7.19e-02 2.90e-02 1.48e-01 -1.19e-01 +1.44e-01 1.48e-01 1.48e-01 1.49e-01 1.54e-01 1.45e-01 -1.21e-01 +1.38e-01 1.29e-01 1.30e-01 1.31e-01 @@ -1970,7 +1970,7 @@ Lower Bounds 1.18e-01 1.31e-01 1.83e-01 -9.56e-02 +9.55e-02 2.75e-02 1.63e-01 1.47e-01 @@ -2058,7 +2058,7 @@ Lower Bounds 1.58e-01 1.43e-01 1.49e-01 -1.60e-01 +1.59e-01 2.21e-01 1.56e-01 4.57e-02 @@ -2208,7 +2208,7 @@ Lower Bounds 1.36e-01 1.33e-01 1.26e-01 -1.10e-01 +1.11e-01 1.31e-01 5.72e-02 1.80e-02 @@ -2245,7 +2245,7 @@ Lower Bounds 7.21e-02 6.08e-02 6.40e-02 -5.54e-02 +5.53e-02 6.28e-02 5.59e-02 4.39e-02 @@ -2405,7 +2405,7 @@ Lower Bounds 1.23e-01 1.24e-01 1.55e-01 -8.14e-02 +8.15e-02 2.87e-02 1.44e-01 1.47e-01 @@ -2416,7 +2416,7 @@ Lower Bounds 1.42e-01 1.37e-01 1.27e-01 -1.28e-01 +1.29e-01 1.17e-01 1.21e-01 1.63e-01 @@ -2479,12 +2479,12 @@ Lower Bounds 2.61e-02 2.32e-02 1.25e-02 -9.63e-03 +9.62e-03 6.12e-03 3.33e-03 1.57e-01 1.68e-01 -1.77e-01 +1.76e-01 1.71e-01 1.70e-01 1.76e-01 @@ -2533,7 +2533,7 @@ Lower Bounds 1.65e-01 1.69e-01 1.74e-01 -6.32e-02 +1.78e-01 1.61e-01 1.64e-01 1.57e-01 @@ -2547,8 +2547,8 @@ Lower Bounds 1.61e-01 1.66e-01 1.68e-01 -1.70e-01 -1.63e-01 +1.71e-01 +1.64e-01 1.61e-01 1.62e-01 1.60e-01 @@ -2647,8 +2647,8 @@ Lower Bounds 1.45e-01 4.24e-02 1.27e-02 -1.42e-01 -6.20e-02 +1.43e-01 +1.40e-01 1.44e-01 1.45e-01 1.42e-01 @@ -2660,7 +2660,7 @@ Lower Bounds 1.15e-01 1.07e-01 1.24e-01 -3.83e-02 +3.91e-02 1.19e-02 1.85e-01 1.70e-01 @@ -2678,7 +2678,7 @@ Lower Bounds 2.05e-02 7.17e-03 1.27e-01 -6.47e-02 +6.46e-02 8.27e-02 1.04e-01 7.86e-02 @@ -3043,7 +3043,7 @@ Lower Bounds 1.57e-01 1.80e-01 1.80e-01 -1.24e-01 +1.23e-01 1.22e-01 8.18e-02 7.30e-02 @@ -3055,7 +3055,7 @@ Lower Bounds 1.13e-01 1.37e-01 1.61e-01 -2.21e-01 +2.20e-01 1.71e-01 1.51e-01 1.16e-01 @@ -3187,14 +3187,14 @@ Lower Bounds 2.64e-02 1.76e-02 8.34e-03 -8.72e-02 +8.71e-02 6.82e-02 1.03e-01 9.93e-02 4.68e-02 8.00e-02 6.54e-02 -5.74e-02 +5.73e-02 5.88e-02 4.17e-02 6.05e-02 @@ -3252,7 +3252,7 @@ Lower Bounds 7.34e-02 7.91e-02 4.60e-02 -5.73e-02 +5.72e-02 6.01e-02 5.26e-02 5.01e-02 @@ -3399,7 +3399,7 @@ Upper Bounds 2.14e+00 7.89e-01 8.70e-01 -9.12e-01 +9.11e-01 9.06e-01 8.15e-01 8.92e-01 @@ -3436,7 +3436,7 @@ Upper Bounds 8.38e-01 8.52e-01 8.74e-01 -8.63e-01 +8.62e-01 8.63e-01 8.27e-01 9.20e-01 @@ -3509,8 +3509,8 @@ Upper Bounds 9.09e-01 8.56e-01 8.36e-01 -8.27e-01 -8.09e-01 +8.26e-01 +8.10e-01 7.69e-01 7.73e-01 7.71e-01 @@ -3642,14 +3642,14 @@ Upper Bounds 8.80e-01 8.68e-01 8.83e-01 -8.81e-01 +8.82e-01 8.83e-01 8.68e-01 8.89e-01 8.61e-01 9.00e-01 9.04e-01 -9.07e-01 +9.06e-01 1.33e+00 1.66e+00 5.75e-01 @@ -3657,7 +3657,7 @@ Upper Bounds 9.28e-01 9.04e-01 9.15e-01 -8.67e-01 +8.68e-01 8.37e-01 8.58e-01 8.74e-01 @@ -3673,7 +3673,7 @@ Upper Bounds 8.97e-01 8.66e-01 8.29e-01 -8.49e-01 +8.48e-01 8.85e-01 8.47e-01 8.39e-01 @@ -3734,7 +3734,7 @@ Upper Bounds 8.72e-01 8.86e-01 8.25e-01 -8.06e-01 +8.05e-01 7.70e-01 7.66e-01 7.77e-01 @@ -4084,7 +4084,7 @@ Upper Bounds 8.23e-01 8.70e-01 8.45e-01 -8.81e-01 +8.80e-01 1.24e+00 1.66e+00 6.51e-01 @@ -4098,7 +4098,7 @@ Upper Bounds 8.14e-01 7.91e-01 8.49e-01 -8.48e-01 +8.47e-01 9.21e-01 1.22e+00 1.35e+00 @@ -4124,7 +4124,7 @@ Upper Bounds 8.56e-01 8.12e-01 7.90e-01 -8.10e-01 +8.09e-01 7.88e-01 8.08e-01 8.07e-01 @@ -4141,7 +4141,7 @@ Upper Bounds 7.98e-01 8.23e-01 8.40e-01 -8.12e-01 +8.11e-01 8.01e-01 8.15e-01 8.78e-01 @@ -4149,14 +4149,14 @@ Upper Bounds 1.49e+00 5.48e-01 8.43e-01 -8.37e-01 +8.36e-01 8.81e-01 -7.97e-01 +8.18e-01 7.95e-01 7.90e-01 8.20e-01 7.94e-01 -7.75e-01 +7.74e-01 7.96e-01 7.56e-01 8.00e-01 @@ -4176,10 +4176,10 @@ Upper Bounds 7.46e-01 7.89e-01 1.17e+00 -9.25e-01 +9.24e-01 3.31e-01 8.47e-01 -8.26e-01 +8.25e-01 8.14e-01 8.34e-01 8.09e-01 @@ -4211,7 +4211,7 @@ Upper Bounds 8.80e-01 8.88e-01 8.39e-01 -8.92e-01 +8.93e-01 8.67e-01 8.43e-01 8.12e-01 @@ -4226,11 +4226,11 @@ Upper Bounds 9.42e-01 8.84e-01 8.89e-01 -4.30e-01 +8.81e-01 9.12e-01 9.52e-01 8.80e-01 -8.13e-01 +8.12e-01 8.44e-01 8.47e-01 8.47e-01 @@ -4284,7 +4284,7 @@ Upper Bounds 1.28e-01 6.06e-02 8.53e-01 -8.42e-01 +8.41e-01 8.16e-01 8.39e-01 8.62e-01 @@ -4293,7 +4293,7 @@ Upper Bounds 7.57e-01 7.84e-01 8.17e-01 -7.86e-01 +7.85e-01 8.91e-01 1.29e+00 1.58e+00 @@ -4384,7 +4384,7 @@ Upper Bounds 7.51e-01 7.69e-01 7.83e-01 -7.95e-01 +7.94e-01 1.09e+00 1.03e+00 3.79e-01 @@ -4439,7 +4439,7 @@ Upper Bounds 8.29e-01 8.34e-01 8.10e-01 -8.16e-01 +8.15e-01 8.13e-01 7.91e-01 7.58e-01 @@ -4500,7 +4500,7 @@ Upper Bounds 5.38e-01 4.35e-01 5.87e-01 -2.74e-01 +2.73e-01 3.52e-01 2.53e-01 1.54e-01 @@ -4577,7 +4577,7 @@ Upper Bounds 7.84e-01 7.91e-01 7.91e-01 -7.83e-01 +7.82e-01 7.90e-01 8.30e-01 1.25e+00 @@ -4590,7 +4590,7 @@ Upper Bounds 7.98e-01 7.82e-01 7.55e-01 -7.52e-01 +7.51e-01 8.14e-01 8.19e-01 7.83e-01 @@ -4599,7 +4599,7 @@ Upper Bounds 1.09e+00 4.51e-01 8.40e-01 -8.48e-01 +8.47e-01 8.18e-01 7.81e-01 7.89e-01 @@ -4623,7 +4623,7 @@ Upper Bounds 7.25e-01 7.18e-01 7.55e-01 -7.87e-01 +7.86e-01 8.22e-01 1.16e+00 5.88e-01 @@ -4658,7 +4658,7 @@ Upper Bounds 1.05e+00 6.00e-01 1.91e-01 -8.27e-01 +8.26e-01 7.94e-01 8.07e-01 8.05e-01 @@ -4758,7 +4758,7 @@ Upper Bounds 7.61e-01 7.94e-01 7.58e-01 -7.43e-01 +7.42e-01 7.76e-01 1.12e+00 1.01e+00 @@ -4784,12 +4784,12 @@ Upper Bounds 7.60e-01 7.73e-01 8.04e-01 -7.87e-01 +7.86e-01 7.68e-01 7.69e-01 7.72e-01 7.79e-01 -8.20e-01 +8.19e-01 1.12e+00 7.80e-01 3.07e-01 @@ -4817,7 +4817,7 @@ Upper Bounds 7.52e-01 7.32e-01 7.17e-01 -7.61e-01 +7.60e-01 7.74e-01 8.04e-01 1.13e+00 @@ -4854,12 +4854,12 @@ Upper Bounds 8.15e-01 2.44e-01 8.21e-01 -3.30e-01 +7.75e-01 7.87e-01 7.66e-01 7.39e-01 7.65e-01 -3.79e-01 +7.62e-01 6.99e-01 6.88e-01 6.98e-01 @@ -4962,10 +4962,10 @@ Upper Bounds 7.87e-01 7.72e-01 8.00e-01 -8.25e-01 -8.31e-01 +8.24e-01 +8.30e-01 8.27e-01 -7.89e-01 +7.88e-01 7.35e-01 7.02e-01 6.79e-01 @@ -4991,13 +4991,13 @@ Upper Bounds 7.91e-01 7.84e-01 8.12e-01 -8.14e-01 +8.13e-01 7.93e-01 7.86e-01 7.80e-01 7.88e-01 7.62e-01 -7.26e-01 +7.25e-01 7.48e-01 8.00e-01 1.08e+00 @@ -5013,7 +5013,7 @@ Upper Bounds 7.67e-01 7.34e-01 7.08e-01 -7.83e-01 +7.82e-01 8.28e-01 1.05e+00 7.28e-01 @@ -5043,7 +5043,7 @@ Upper Bounds 7.26e-01 7.36e-01 7.31e-01 -7.41e-01 +7.40e-01 7.92e-01 1.07e+00 6.18e-01 @@ -5089,7 +5089,7 @@ Upper Bounds 6.75e-01 6.66e-01 7.38e-01 -7.32e-01 +7.31e-01 9.88e-01 5.41e-01 1.68e-01 @@ -5219,7 +5219,7 @@ Upper Bounds 7.96e-01 8.02e-01 7.57e-01 -7.80e-01 +7.79e-01 7.99e-01 7.43e-01 7.26e-01 @@ -5251,7 +5251,7 @@ Upper Bounds 7.54e-01 7.53e-01 7.66e-01 -7.41e-01 +7.40e-01 7.01e-01 7.50e-01 8.07e-01 @@ -5274,9 +5274,9 @@ Upper Bounds 7.50e-01 2.61e-01 7.64e-01 -7.45e-01 +7.65e-01 7.60e-01 -7.26e-01 +7.25e-01 7.24e-01 7.17e-01 6.99e-01 @@ -5294,7 +5294,7 @@ Upper Bounds 7.14e-01 7.38e-01 7.21e-01 -6.81e-01 +6.80e-01 6.69e-01 6.84e-01 6.84e-01 @@ -5304,13 +5304,13 @@ Upper Bounds 3.59e-01 1.45e-01 7.38e-01 -5.93e-01 +7.18e-01 7.38e-01 7.39e-01 7.46e-01 7.70e-01 7.25e-01 -6.07e-01 +6.90e-01 6.46e-01 6.49e-01 6.56e-01 @@ -5318,7 +5318,7 @@ Upper Bounds 9.65e-01 3.98e-01 1.02e-01 -7.63e-01 +7.64e-01 7.31e-01 7.32e-01 7.05e-01 @@ -5369,7 +5369,7 @@ Upper Bounds 1.11e+00 1.11e+00 1.08e+00 -9.96e-01 +9.95e-01 1.02e+00 9.59e-01 1.01e+00 @@ -5434,7 +5434,7 @@ Upper Bounds 7.89e-01 7.16e-01 7.47e-01 -7.98e-01 +7.97e-01 1.11e+00 7.82e-01 2.29e-01 @@ -5447,7 +5447,7 @@ Upper Bounds 7.75e-01 7.99e-01 8.00e-01 -7.27e-01 +7.26e-01 7.00e-01 7.95e-01 1.12e+00 @@ -5584,7 +5584,7 @@ Upper Bounds 6.82e-01 6.66e-01 6.30e-01 -5.52e-01 +5.53e-01 6.56e-01 2.86e-01 9.02e-02 @@ -5603,7 +5603,7 @@ Upper Bounds 5.90e-01 1.70e-01 6.04e-02 -9.35e-01 +9.34e-01 7.67e-01 7.48e-01 8.36e-01 @@ -5688,7 +5688,7 @@ Upper Bounds 7.74e-01 7.69e-01 7.48e-01 -7.27e-01 +7.26e-01 7.70e-01 1.04e+00 5.38e-01 @@ -5698,7 +5698,7 @@ Upper Bounds 7.98e-01 7.72e-01 8.10e-01 -7.95e-01 +7.96e-01 7.80e-01 7.69e-01 7.86e-01 @@ -5776,7 +5776,7 @@ Upper Bounds 7.07e-01 6.87e-01 6.74e-01 -6.34e-01 +6.35e-01 6.34e-01 6.14e-01 6.22e-01 @@ -5792,10 +5792,10 @@ Upper Bounds 7.08e-01 6.86e-01 6.37e-01 -6.42e-01 +6.43e-01 5.84e-01 6.05e-01 -8.13e-01 +8.14e-01 2.50e-01 7.65e-02 7.35e-01 @@ -5805,7 +5805,7 @@ Upper Bounds 7.24e-01 7.52e-01 7.60e-01 -7.36e-01 +7.35e-01 6.72e-01 6.44e-01 6.12e-01 @@ -5860,7 +5860,7 @@ Upper Bounds 1.67e-02 7.86e-01 8.39e-01 -8.83e-01 +8.82e-01 8.55e-01 8.50e-01 8.79e-01 @@ -5901,7 +5901,7 @@ Upper Bounds 7.49e-01 8.31e-01 1.07e+00 -4.89e-01 +4.88e-01 1.30e-01 8.26e-01 8.49e-01 @@ -5909,7 +5909,7 @@ Upper Bounds 8.27e-01 8.44e-01 8.70e-01 -3.16e-01 +8.88e-01 8.04e-01 8.19e-01 7.85e-01 @@ -5923,8 +5923,8 @@ Upper Bounds 8.03e-01 8.28e-01 8.39e-01 -8.52e-01 -8.17e-01 +8.53e-01 +8.18e-01 8.07e-01 8.12e-01 8.02e-01 @@ -6023,8 +6023,8 @@ Upper Bounds 7.24e-01 2.12e-01 6.37e-02 -7.12e-01 -3.10e-01 +7.13e-01 +6.99e-01 7.21e-01 7.24e-01 7.11e-01 @@ -6036,7 +6036,7 @@ Upper Bounds 5.74e-01 5.37e-01 6.18e-01 -1.92e-01 +1.95e-01 5.93e-02 9.23e-01 8.49e-01 @@ -6053,8 +6053,8 @@ Upper Bounds 3.58e-01 1.02e-01 3.58e-02 -6.34e-01 -3.24e-01 +6.33e-01 +3.23e-01 4.14e-01 5.18e-01 3.93e-01 @@ -6067,7 +6067,7 @@ Upper Bounds 2.08e-01 1.27e-01 3.00e-02 -9.84e-03 +9.83e-03 2.53e-01 1.24e-01 1.72e-01 @@ -6211,7 +6211,7 @@ Upper Bounds 1.03e+00 1.15e+00 1.11e+00 -9.64e-01 +9.63e-01 1.01e+00 9.05e-01 8.67e-01 @@ -6229,8 +6229,8 @@ Upper Bounds 8.93e-01 8.70e-01 8.58e-01 -8.56e-01 -8.00e-01 +8.57e-01 +8.01e-01 2.31e-01 7.13e-02 9.12e-01 @@ -6249,7 +6249,7 @@ Upper Bounds 2.01e-01 7.22e-02 7.80e-01 -8.00e-01 +7.99e-01 9.64e-01 9.33e-01 9.70e-01 @@ -6277,7 +6277,7 @@ Upper Bounds 5.08e-01 3.32e-01 9.24e-02 -4.15e-02 +4.14e-02 3.42e-01 2.04e-01 2.56e-01 @@ -6414,12 +6414,12 @@ Upper Bounds 1.17e-01 7.24e-02 5.31e-01 -7.17e-01 +7.16e-01 8.03e-01 7.87e-01 9.01e-01 9.00e-01 -6.18e-01 +6.17e-01 6.08e-01 4.09e-01 3.65e-01 @@ -6545,7 +6545,7 @@ Upper Bounds 2.87e-01 1.88e-01 1.85e-01 -1.80e-01 +1.79e-01 1.07e-01 3.77e-02 5.65e-01 @@ -6578,13 +6578,13 @@ Upper Bounds 1.41e-01 5.88e-02 6.26e-02 -4.47e-01 +4.46e-01 3.47e-01 5.27e-01 6.11e-01 3.91e-01 4.22e-01 -4.77e-01 +4.76e-01 4.54e-01 4.31e-01 3.19e-01 @@ -6727,14 +6727,14 @@ Upper Bounds 5.73e-02 5.81e-02 1.92e-02 -6.10e-03 +6.09e-03 2.72e-02 4.77e-02 4.74e-02 3.34e-02 5.41e-02 7.93e-02 -6.70e-02 +6.69e-02 7.67e-02 9.31e-02 5.17e-02 From f796fa04e06a074163f9071436724a06fd691931 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 10 Jun 2025 09:01:28 -0600 Subject: [PATCH 036/159] Adding fix and tests for spherical mesh as spatial distribution (#3428) Co-authored-by: Paul Wilson Co-authored-by: Paul Romano --- openmc/mesh.py | 126 +++++++++++++++++----- src/mesh.cpp | 3 +- tests/unit_tests/test_mesh_from_domain.py | 32 +++++- tests/unit_tests/test_source_mesh.py | 45 ++++++++ 4 files changed, 176 insertions(+), 30 deletions(-) diff --git a/openmc/mesh.py b/openmc/mesh.py index 27187b5dd18..b046769fd0f 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -5,6 +5,7 @@ from functools import wraps from math import pi, sqrt, atan2 from numbers import Integral, Real +from typing import Protocol import h5py import lxml.etree as ET @@ -842,6 +843,11 @@ def _check_vtk_dataset(self, label: str, dataset: np.ndarray): ) +class HasBoundingBox(Protocol): + """Object that has a ``bounding_box`` attribute.""" + bounding_box: openmc.BoundingBox + + class RegularMesh(StructuredMesh): """A regular Cartesian mesh in one, two, or three dimensions @@ -1088,17 +1094,16 @@ def from_rect_lattice( @classmethod def from_domain( cls, - domain: 'openmc.Cell' | 'openmc.Region' | 'openmc.Universe' | 'openmc.Geometry', + domain: HasBoundingBox, dimension: Sequence[int] = (10, 10, 10), mesh_id: int | None = None, name: str = '' ): - """Create mesh from an existing openmc cell, region, universe or - geometry by making use of the objects bounding box property. + """Create RegularMesh from a domain using its bounding box. Parameters ---------- - domain : {openmc.Cell, openmc.Region, openmc.Universe, openmc.Geometry} + domain : HasBoundingBox The object passed in will be used as a template for this mesh. The bounding box of the property of the object passed will be used to set the lower_left and upper_right and of the mesh instance @@ -1115,11 +1120,8 @@ def from_domain( RegularMesh instance """ - cv.check_type( - "domain", - domain, - (openmc.Cell, openmc.Region, openmc.Universe, openmc.Geometry), - ) + if not hasattr(domain, 'bounding_box'): + raise TypeError("Domain must have a bounding_box property") mesh = cls(mesh_id=mesh_id, name=name) mesh.lower_left = domain.bounding_box[0] @@ -1787,17 +1789,18 @@ def from_hdf5(cls, group: h5py.Group, mesh_id: int, name: str): @classmethod def from_domain( cls, - domain: 'openmc.Cell' | 'openmc.Region' | 'openmc.Universe' | 'openmc.Geometry', + domain: HasBoundingBox, dimension: Sequence[int] = (10, 10, 10), mesh_id: int | None = None, phi_grid_bounds: Sequence[float] = (0.0, 2*pi), - name: str = '' + name: str = '', + enclose_domain: bool = False ): - """Creates a regular CylindricalMesh from an existing openmc domain. + """Create CylindricalMesh from a domain using its bounding box. Parameters ---------- - domain : openmc.Cell or openmc.Region or openmc.Universe or openmc.Geometry + domain : HasBoundingBox The object passed in will be used as a template for this mesh. The bounding box of the property of the object passed will be used to set the r_grid, z_grid ranges. @@ -1811,6 +1814,9 @@ def from_domain( is (0, 2π), i.e., the full phi range. name : str Name of the mesh + enclose_domain : bool + If True, the mesh will encompass the bounding box of the domain. If + False, the mesh will be inscribed within the domain's bounding box. Returns ------- @@ -1818,25 +1824,20 @@ def from_domain( CylindricalMesh instance """ - cv.check_type( - "domain", - domain, - (openmc.Cell, openmc.Region, openmc.Universe, openmc.Geometry), - ) + if not hasattr(domain, 'bounding_box'): + raise TypeError("Domain must have a bounding_box property") # loaded once to avoid recalculating bounding box cached_bb = domain.bounding_box - max_bounding_box_radius = max( - [ - cached_bb[0][0], - cached_bb[0][1], - cached_bb[1][0], - cached_bb[1][1], - ] - ) + + if enclose_domain: + outer_radius = 0.5 * np.linalg.norm(cached_bb.width[:2]) + else: + outer_radius = 0.5 * min(cached_bb.width[:2]) + r_grid = np.linspace( 0, - max_bounding_box_radius, + outer_radius, num=dimension[0]+1 ) phi_grid = np.linspace( @@ -2167,6 +2168,77 @@ def from_hdf5(cls, group: h5py.Group, mesh_id: int, name: str): return mesh + @classmethod + def from_domain( + cls, + domain: HasBoundingBox, + dimension: Sequence[int] = (10, 10, 10), + mesh_id: int | None = None, + phi_grid_bounds: Sequence[float] = (0.0, 2*pi), + theta_grid_bounds: Sequence[float] = (0.0, pi), + name: str = '', + enclose_domain: bool = False + ): + """Create SphericalMesh from a domain using its bounding box. + + Parameters + ---------- + domain : HasBoundingBox + The object passed in will be used as a template for this mesh. The + bounding box of the property of the object passed will be used to + set the r_grid, phi_grid, and theta_grid ranges. + dimension : Iterable of int + The number of equally spaced mesh cells in each direction (r_grid, + phi_grid, theta_grid). Spacing is in angular space (radians) for + phi and theta, and in absolute space for r. + mesh_id : int + Unique identifier for the mesh + phi_grid_bounds : numpy.ndarray + Mesh bounds points along the phi-axis in radians. The default value + is (0, 2π), i.e., the full phi range. + theta_grid_bounds : numpy.ndarray + Mesh bounds points along the theta-axis in radians. The default value + is (0, π), i.e., the full theta range. + name : str + Name of the mesh + enclose_domain : bool + If True, the mesh will encompass the bounding box of the domain. If + False, the mesh will be inscribed within the domain's bounding box. + + Returns + ------- + openmc.SphericalMesh + SphericalMesh instance + + """ + if not hasattr(domain, 'bounding_box'): + raise TypeError("Domain must have a bounding_box property") + + # loaded once to avoid recalculating bounding box + cached_bb = domain.bounding_box + + if enclose_domain: + outer_radius = 0.5 * np.linalg.norm(cached_bb.width) + else: + outer_radius = 0.5 * min(cached_bb.width) + + r_grid = np.linspace(0, outer_radius, num=dimension[0] + 1) + theta_grid = np.linspace( + theta_grid_bounds[0], + theta_grid_bounds[1], + num=dimension[1]+1 + ) + phi_grid = np.linspace( + phi_grid_bounds[0], + phi_grid_bounds[1], + num=dimension[2]+1 + ) + origin = np.array([ + cached_bb.center[0], cached_bb.center[1], cached_bb.center[2]]) + + return cls(r_grid=r_grid, phi_grid=phi_grid, theta_grid=theta_grid, + origin=origin, mesh_id=mesh_id, name=name) + def to_xml_element(self): """Return XML representation of the mesh diff --git a/src/mesh.cpp b/src/mesh.cpp index 8280177ac13..d610585259c 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -1794,7 +1794,8 @@ Position SphericalMesh::sample_element( double phi_min = this->phi(ijk[2] - 1); double phi_max = this->phi(ijk[2]); - double cos_theta = uniform_distribution(theta_min, theta_max, seed); + double cos_theta = + uniform_distribution(std::cos(theta_min), std::cos(theta_max), seed); double sin_theta = std::sin(std::acos(cos_theta)); double phi = uniform_distribution(phi_min, phi_max, seed); double r_min_cub = std::pow(r_min, 3); diff --git a/tests/unit_tests/test_mesh_from_domain.py b/tests/unit_tests/test_mesh_from_domain.py index 0cbe413e86a..0e385891306 100644 --- a/tests/unit_tests/test_mesh_from_domain.py +++ b/tests/unit_tests/test_mesh_from_domain.py @@ -40,7 +40,7 @@ def test_cylindrical_mesh_from_cell(): assert isinstance(mesh, openmc.CylindricalMesh) assert np.array_equal(mesh.dimension, (1, 1, 1)) - assert np.array_equal(mesh.r_grid, [0., 150.]) + assert np.array_equal(mesh.r_grid, [0., 50.]) assert np.array_equal(mesh.origin, [100., 0., 10.]) # Cell is not centralized on Z, X or Y axis @@ -49,7 +49,7 @@ def test_cylindrical_mesh_from_cell(): mesh = openmc.CylindricalMesh.from_domain(domain=cell, dimension=[1, 1, 1]) assert isinstance(mesh, openmc.CylindricalMesh) - assert np.array_equal(mesh.r_grid, [0., 220.]) + assert np.array_equal(mesh.r_grid, [0., 50.]) assert np.array_equal(mesh.origin, [100., 170., 10.]) @@ -87,6 +87,34 @@ def test_cylindrical_mesh_from_region(): assert np.array_equal(mesh.origin, (0.0, 0.0, -30.)) +def test_spherical_mesh_from_domain(): + """Tests a SphericalMesh can be made from a Region and the specified + dimensions are propagated through. Cell is not centralized""" + sphere = openmc.Sphere(r=5, x0=2, y0=3, z0=4) + region = -sphere + + geometry = openmc.Geometry(openmc.Universe(cells=[openmc.Cell(region=region)])) + + region_mesh = openmc.SphericalMesh.from_domain( + domain=region, dimension=(4, 3, 4)) + universe_mesh = openmc.SphericalMesh.from_domain( + domain=geometry.root_universe, dimension=(4, 3, 4)) + geometry_mesh = openmc.SphericalMesh.from_domain( + domain=geometry, dimension=(4, 3, 4)) + + + for mesh in (region_mesh, universe_mesh, geometry_mesh): + assert isinstance(mesh, openmc.SphericalMesh) + assert np.array_equal(mesh.dimension, (4, 3, 4)) + assert np.array_equal(mesh.r_grid, [0., 1.25, 2.5, 3.75, 5.0]) + assert np.array_equal(mesh.theta_grid, [0., np.pi/3., 2*np.pi/3., np.pi]) + assert np.array_equal(mesh.phi_grid, [0., np.pi/2., np.pi, 3*np.pi/2., 2*np.pi]) + assert np.array_equal(mesh.origin, (2.0, 3.0, 4.0)) + + for p in mesh.centroids.reshape(-1, 3): + assert p in mesh.bounding_box + + def test_reg_mesh_from_universe(): """Tests a RegularMesh can be made from a Universe and the default dimensions are propagated through. Universe is centralized""" diff --git a/tests/unit_tests/test_source_mesh.py b/tests/unit_tests/test_source_mesh.py index 43bb1678c40..d023c1335c0 100644 --- a/tests/unit_tests/test_source_mesh.py +++ b/tests/unit_tests/test_source_mesh.py @@ -396,3 +396,48 @@ def test_mesh_source_file(run_in_tmpdir): assert site.u == source_particle.u assert site.time == source_particle.time assert site.r in bbox + + +@pytest.mark.parametrize("mesh_type", ('rectangular', 'cylindrical', 'spherical')) +def test_mesh_spatial(run_in_tmpdir, mesh_type): + """Test that a spherical mesh source works as expected.""" + model = openmc.Model() + + # Set up geometry, a box that is shifted in x, y, and z + box = openmc.model.RectangularParallelepiped(5.0, 25.0, -20.0, 20.0, -30.0, 30.0, boundary_type='vacuum') + mat = openmc.Material() + mat.add_nuclide('H1', 1.0) + model.geometry = openmc.Geometry([openmc.Cell(fill=mat, region=-box)]) + + # Create a mesh of each type in turn + if mesh_type == 'rectangular': + mesh = openmc.RegularMesh.from_domain(model.geometry, (10, 2, 2)) + elif mesh_type == 'cylindrical': + mesh = openmc.CylindricalMesh.from_domain(model.geometry, (10, 2, 2)) + assert max(mesh.r_grid) == 10.0, "Cylindrical mesh radius exceeds geometry bounds" + assert mesh.origin[0] == 15.0, "Cylindrical mesh origin x-coordinate is incorrect" + elif mesh_type == 'spherical': + mesh = openmc.SphericalMesh.from_domain(model.geometry, (10, 2, 2)) + assert max(mesh.r_grid) == 10.0, "Spherical mesh radius exceeds geometry bounds" + assert mesh.origin[0] == 15.0, "Spherical mesh origin x-coordinate is incorrect" + + # Create a mesh source with a single particle + ind_source = openmc.IndependentSource(space=openmc.stats.MeshSpatial(mesh, np.prod(mesh.dimension)*[1.0])) + model.settings.source = ind_source + + model.settings.particles = 100 + model.settings.batches = 10 + model.settings.run_mode = 'fixed source' + + model.export_to_model_xml() + + openmc.lib.init() + openmc.lib.simulation_init() + sites = openmc.lib.sample_external_source(10) + openmc.lib.simulation_finalize() + openmc.lib.finalize() + + # Check that the sites are within the spherical mesh bounds + bbox = mesh.bounding_box + for site in sites: + assert site.r in bbox From f571be87c5e46587ce898dd5fa944512283b6ea3 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 10 Jun 2025 22:51:51 -0500 Subject: [PATCH 037/159] Add user setting for source rejection fraction (#3433) --- docs/source/io_formats/settings.rst | 12 ++++++- include/openmc/settings.h | 2 ++ include/openmc/source.h | 5 ++- openmc/settings.py | 28 ++++++++++++++++ src/error.cpp | 25 ++++++++------- src/finalize.cpp | 1 + src/settings.cpp | 7 ++++ src/source.cpp | 50 +++++++++++++++-------------- tests/unit_tests/test_settings.py | 5 +-- tests/unit_tests/test_source.py | 33 +++++++++++++------ 10 files changed, 118 insertions(+), 50 deletions(-) diff --git a/docs/source/io_formats/settings.rst b/docs/source/io_formats/settings.rst index cef9bf911f9..26673faac2e 100644 --- a/docs/source/io_formats/settings.rst +++ b/docs/source/io_formats/settings.rst @@ -487,7 +487,7 @@ found in the :ref:`random ray user guide `. :type: The type of the domain. Can be ``material``, ``cell``, or ``universe``. - + :diagonal_stabilization_rho: The rho factor for use with diagonal stabilization. This technique is applied when negative diagonal (in-group) elements are detected in @@ -917,6 +917,16 @@ variable and whose sub-elements/attributes are as follows: :dist: This sub-element of a ``pair`` element provides information on the corresponding univariate distribution. +--------------------------------------- +```` Element +--------------------------------------- + +The ```` element specifies the minimum fraction of +external source sites that must be accepted when applying rejection sampling +based on constraints. + + *Default*: 0.05 + ------------------------- ```` Element ------------------------- diff --git a/include/openmc/settings.h b/include/openmc/settings.h index 9017b2d080b..069d94c3b28 100644 --- a/include/openmc/settings.h +++ b/include/openmc/settings.h @@ -145,6 +145,8 @@ extern std::unordered_set statepoint_batch; //!< Batches when state should be written extern std::unordered_set source_write_surf_id; //!< Surface ids where sources will be written +extern double source_rejection_fraction; //!< Minimum fraction of source sites + //!< that must be accepted extern int max_history_splits; //!< maximum number of particle splits for weight windows diff --git a/include/openmc/source.h b/include/openmc/source.h index 6733eaeffca..e82c42269f2 100644 --- a/include/openmc/source.h +++ b/include/openmc/source.h @@ -21,10 +21,9 @@ namespace openmc { // Constants //============================================================================== -// Maximum number of external source spatial resamples to encounter before an -// error is thrown. +// Minimum number of external source sites rejected before checking againts the +// source_rejection_fraction constexpr int EXTSRC_REJECT_THRESHOLD {10000}; -constexpr double EXTSRC_REJECT_FRACTION {0.05}; //============================================================================== // Global variables diff --git a/openmc/settings.py b/openmc/settings.py index 4cacefbedba..bd3a89e11cc 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -222,6 +222,10 @@ class Settings: Number of random numbers allocated for each source particle history source : Iterable of openmc.SourceBase Distribution of source sites in space, angle, and energy + source_rejection_fraction : float + Minimum fraction of source sites that must be accepted when applying + rejection sampling based on constraints. If not specified, the default + value is 0.05. sourcepoint : dict Options for writing source points. Acceptable keys are: @@ -357,6 +361,7 @@ def __init__(self, **kwargs): # Source subelement self._source = cv.CheckedList(SourceBase, 'source distributions') + self._source_rejection_fraction = None self._confidence_intervals = None self._electron_treatment = None @@ -1224,6 +1229,17 @@ def use_decay_photons(self, value): cv.check_type('use decay photons', value, bool) self._use_decay_photons = value + @property + def source_rejection_fraction(self) -> float: + return self._source_rejection_fraction + + @source_rejection_fraction.setter + def source_rejection_fraction(self, source_rejection_fraction: float): + cv.check_type('source_rejection_fraction', source_rejection_fraction, Real) + cv.check_greater_than('source_rejection_fraction', source_rejection_fraction, 0) + cv.check_less_than('source_rejection_fraction', source_rejection_fraction, 1) + self._source_rejection_fraction = source_rejection_fraction + def _create_run_mode_subelement(self, root): elem = ET.SubElement(root, "run_mode") elem.text = self._run_mode.value @@ -1685,6 +1701,11 @@ def _create_random_ray_subelement(self, root, mesh_memo=None): subelement = ET.SubElement(element, key) subelement.text = str(value) + def _create_source_rejection_fraction_subelement(self, root): + if self._source_rejection_fraction is not None: + element = ET.SubElement(root, "source_rejection_fraction") + element.text = str(self._source_rejection_fraction) + def _eigenvalue_from_xml_element(self, root): elem = root.find('eigenvalue') if elem is not None: @@ -2104,6 +2125,11 @@ def _use_decay_photons_from_xml_element(self, root): if text is not None: self.use_decay_photons = text in ('true', '1') + def _source_rejection_fraction_from_xml_element(self, root): + text = get_text(root, 'source_rejection_fraction') + if text is not None: + self.source_rejection_fraction = float(text) + def to_xml_element(self, mesh_memo=None): """Create a 'settings' element to be written to an XML file. @@ -2171,6 +2197,7 @@ def to_xml_element(self, mesh_memo=None): self._create_max_tracks_subelement(element) self._create_random_ray_subelement(element, mesh_memo) self._create_use_decay_photons_subelement(element) + self._create_source_rejection_fraction_subelement(element) # Clean the indentation in the file to be user-readable clean_indentation(element) @@ -2279,6 +2306,7 @@ def from_xml_element(cls, elem, meshes=None): settings._max_tracks_from_xml_element(elem) settings._random_ray_from_xml_element(elem) settings._use_decay_photons_from_xml_element(elem) + settings._source_rejection_fraction_from_xml_element(elem) return settings diff --git a/src/error.cpp b/src/error.cpp index 566950a973c..f99f5935f0f 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -110,23 +110,26 @@ void write_message(const std::string& message, int level) void fatal_error(const std::string& message, int err) { +#pragma omp critical(FatalError) + { #ifdef _POSIX_VERSION - // Make output red if user is in a terminal - if (isatty(STDERR_FILENO)) { - std::cerr << "\033[0;31m"; - } + // Make output red if user is in a terminal + if (isatty(STDERR_FILENO)) { + std::cerr << "\033[0;31m"; + } #endif - // Write error message - std::cerr << " ERROR: "; - output(message, std::cerr, 8); + // Write error message + std::cerr << " ERROR: "; + output(message, std::cerr, 8); #ifdef _POSIX_VERSION - // Reset color for terminal - if (isatty(STDERR_FILENO)) { - std::cerr << "\033[0m"; - } + // Reset color for terminal + if (isatty(STDERR_FILENO)) { + std::cerr << "\033[0m"; + } #endif + } #ifdef OPENMC_MPI MPI_Abort(mpi::intracomm, err); diff --git a/src/finalize.cpp b/src/finalize.cpp index 54aa1d1d9ee..9cbebc878fd 100644 --- a/src/finalize.cpp +++ b/src/finalize.cpp @@ -119,6 +119,7 @@ int openmc_finalize() settings::run_CE = true; settings::run_mode = RunMode::UNSET; settings::source_latest = false; + settings::source_rejection_fraction = 0.05; settings::source_separate = false; settings::source_write = true; settings::ssw_cell_id = C_NONE; diff --git a/src/settings.cpp b/src/settings.cpp index c8230a10f1d..9afe743614e 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -124,6 +124,7 @@ RunMode run_mode {RunMode::UNSET}; SolverType solver_type {SolverType::MONTE_CARLO}; std::unordered_set sourcepoint_batch; std::unordered_set statepoint_batch; +double source_rejection_fraction {0.05}; std::unordered_set source_write_surf_id; int64_t ssw_max_particles; int64_t ssw_max_files; @@ -644,6 +645,12 @@ void read_settings_xml(pugi::xml_node root) write_initial_source = get_node_value_bool(root, "write_initial_source"); } + // Get relative number of lost particles + if (check_for_node(root, "source_rejection_fraction")) { + source_rejection_fraction = + std::stod(get_node_value(root, "source_rejection_fraction")); + } + // Survival biasing if (check_for_node(root, "survival_biasing")) { survival_biasing = get_node_value_bool(root, "survival_biasing"); diff --git a/src/source.cpp b/src/source.cpp index 8e6eb4f11fd..6ee0b560359 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -157,11 +157,28 @@ void Source::read_constraints(pugi::xml_node node) } } +void check_rejection_fraction(int64_t n_reject, int64_t n_accept) +{ + // Don't check unless we've hit a minimum number of total sites rejected + if (n_reject < EXTSRC_REJECT_THRESHOLD) + return; + + // Compute fraction of accepted sites and compare against minimum + double fraction = static_cast(n_accept) / n_reject; + if (fraction <= settings::source_rejection_fraction) { + fatal_error(fmt::format( + "Too few source sites satisfied the constraints (minimum source " + "rejection fraction = {}). Please check your source definition or " + "set a lower value of Settings.source_rejection_fraction.", + settings::source_rejection_fraction)); + } +} + SourceSite Source::sample_with_constraints(uint64_t* seed) const { bool accepted = false; - static int n_reject = 0; - static int n_accept = 0; + static int64_t n_reject = 0; + static int64_t n_accept = 0; SourceSite site; while (!accepted) { @@ -176,13 +193,9 @@ SourceSite Source::sample_with_constraints(uint64_t* seed) const satisfies_energy_constraints(site.E) && satisfies_time_constraints(site.time); if (!accepted) { + // Increment number of rejections and check against minimum fraction ++n_reject; - if (n_reject >= EXTSRC_REJECT_THRESHOLD && - static_cast(n_accept) / n_reject <= - EXTSRC_REJECT_FRACTION) { - fatal_error("More than 95% of external source sites sampled were " - "rejected. Please check your source definition."); - } + check_rejection_fraction(n_reject, n_accept); // For the "kill" strategy, accept particle but set weight to 0 so that // it is terminated immediately @@ -337,8 +350,8 @@ SourceSite IndependentSource::sample(uint64_t* seed) const // Repeat sampling source location until a good site has been accepted bool accepted = false; - static int n_reject = 0; - static int n_accept = 0; + static int64_t n_reject = 0; + static int64_t n_accept = 0; while (!accepted) { @@ -351,12 +364,7 @@ SourceSite IndependentSource::sample(uint64_t* seed) const // Check for rejection if (!accepted) { ++n_reject; - if (n_reject >= EXTSRC_REJECT_THRESHOLD && - static_cast(n_accept) / n_reject <= EXTSRC_REJECT_FRACTION) { - fatal_error("More than 95% of external source sites sampled were " - "rejected. Please check your external source's spatial " - "definition."); - } + check_rejection_fraction(n_reject, n_accept); } } @@ -381,18 +389,12 @@ SourceSite IndependentSource::sample(uint64_t* seed) const site.E = energy_->sample(seed); // Resample if energy falls above maximum particle energy - if (site.E < data::energy_max[p] and + if (site.E < data::energy_max[p] && (satisfies_energy_constraints(site.E))) break; n_reject++; - if (n_reject >= EXTSRC_REJECT_THRESHOLD && - static_cast(n_accept) / n_reject <= EXTSRC_REJECT_FRACTION) { - fatal_error( - "More than 95% of external source sites sampled were " - "rejected. Please check your external source energy spectrum " - "definition."); - } + check_rejection_fraction(n_reject, n_accept); } // Sample particle creation time diff --git a/tests/unit_tests/test_settings.py b/tests/unit_tests/test_settings.py index 397d7043427..7f202bcdc73 100644 --- a/tests/unit_tests/test_settings.py +++ b/tests/unit_tests/test_settings.py @@ -66,8 +66,8 @@ def test_export_to_xml(run_in_tmpdir): space=openmc.stats.Box((-1., -1., -1.), (1., 1., 1.)) ) } - s.max_particle_events = 100 + s.source_rejection_fraction = 0.01 # Make sure exporting XML works s.export_to_xml() @@ -130,7 +130,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 s.write_initial_source == True + assert s.write_initial_source assert len(s.volume_calculations) == 1 vol = s.volume_calculations[0] assert vol.domain_type == 'cell' @@ -144,3 +144,4 @@ def test_export_to_xml(run_in_tmpdir): assert s.random_ray['distance_active'] == 100.0 assert s.random_ray['ray_source'].space.lower_left == [-1., -1., -1.] assert s.random_ray['ray_source'].space.upper_right == [1., 1., 1.] + assert s.source_rejection_fraction == 0.01 diff --git a/tests/unit_tests/test_source.py b/tests/unit_tests/test_source.py index c88fbcbe62d..bb8a1b78528 100644 --- a/tests/unit_tests/test_source.py +++ b/tests/unit_tests/test_source.py @@ -264,21 +264,36 @@ def test_constraints_file(sphere_box_model, run_in_tmpdir): @pytest.mark.skipif(config['mpi'], reason='Not compatible with MPI') -def test_rejection_limit(sphere_box_model, run_in_tmpdir): - model, cell1 = sphere_box_model[:2] +def test_rejection_fraction(run_in_tmpdir): + mat = openmc.Material() + mat.add_nuclide('H1', 1.0) + w = 0.25 + rpp1 = openmc.model.RectangularParallelepiped( + -w/2, w/2, -w/2, w/2, -w/2, w/2) + rpp2 = openmc.model.RectangularParallelepiped( + -0.5, 0.5, -0.5, 0.5, -0.5, 0.5, boundary_type='vacuum') + cell1 = openmc.Cell(fill=mat, region=-rpp1) + cell2 = openmc.Cell(region=+rpp1 & -rpp2) + model = openmc.Model() + model.geometry = openmc.Geometry([cell1, cell2]) - # Define a point source that will get rejected 100% of the time + # Create a box source over a 1 cm³ volume that is constrained to the source + # cell of volume (0.25 cm)³ = 0.0125 cm³, which means the default rejection + # fraction of 0.05 won't work + model.settings.particles = 1000 + model.settings.batches = 1 + model.settings.run_mode = 'fixed source' model.settings.source = openmc.IndependentSource( - space=openmc.stats.Point((-3., 0., 0.)), + space=openmc.stats.Box(*(-rpp2).bounding_box), constraints={'domains': [cell1]} ) - - # Confirm that OpenMC doesn't run in an infinite loop. Note that this may - # work when running with MPI since it won't necessarily capture the error - # message correctly - with pytest.raises(RuntimeError, match="rejected"): + with pytest.raises(RuntimeError, match='Too few source sites'): model.run(openmc_exec=config['exe']) + # With a source rejection fraction below 0.0125, the simulation should run + model.settings.source_rejection_fraction = 0.005 + model.run(openmc_exec=config['exe']) + def test_exceptions(): From 2eeba89992ee8832147957ec0e330aa363bf189e Mon Sep 17 00:00:00 2001 From: John Tramm Date: Wed, 11 Jun 2025 12:41:38 -0500 Subject: [PATCH 038/159] Apply Max Number of Events Check to Random Rays (#3438) Co-authored-by: Jonathan Shimwell --- src/random_ray/random_ray.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/random_ray/random_ray.cpp b/src/random_ray/random_ray.cpp index 27819591cdd..36eab062846 100644 --- a/src/random_ray/random_ray.cpp +++ b/src/random_ray/random_ray.cpp @@ -265,6 +265,12 @@ uint64_t RandomRay::transport_history_based_single_ray() if (!alive()) break; event_cross_surface(); + // If ray has too many events, display warning and kill it + if (n_event() >= settings::max_particle_events) { + warning("Ray " + std::to_string(id()) + + " underwent maximum number of events, terminating ray."); + wgt() = 0.0; + } } return n_event(); From e678b1a057e4dfe079ee27fd73886a8b2249f0c5 Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Thu, 12 Jun 2025 01:54:04 +0300 Subject: [PATCH 039/159] Fix raytrace infinite loop. (#3423) Co-authored-by: Paul Romano --- src/mesh.cpp | 9 +++++++-- tests/unit_tests/test_mesh.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/mesh.cpp b/src/mesh.cpp index d610585259c..becbbd3fd42 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -989,7 +989,7 @@ void StructuredMesh::raytrace_mesh( // For all directions outside the mesh, find the distance that we need // to travel to reach the next surface. Use the largest distance, as // only this will cross all outer surfaces. - int k_max {0}; + int k_max {-1}; for (int k = 0; k < n; ++k) { if ((ijk[k] < 1 || ijk[k] > shape_[k]) && (distances[k].distance > traveled_distance)) { @@ -997,6 +997,10 @@ void StructuredMesh::raytrace_mesh( k_max = k; } } + // Assure some distance is traveled + if (k_max == -1) { + traveled_distance += TINY_BIT; + } // If r1 is not inside the mesh, exit here if (traveled_distance >= total_distance) @@ -1011,7 +1015,7 @@ void StructuredMesh::raytrace_mesh( } // If inside the mesh, Tally inward current - if (in_mesh) + if (in_mesh && k_max >= 0) tally.surface(ijk, k_max, !distances[k_max].max_surface, true); } } @@ -1207,6 +1211,7 @@ StructuredMesh::MeshDistance RegularMesh::distance_to_grid_boundary( d.next_index--; d.distance = (negative_grid_boundary(ijk, i) - r0[i]) / u[i]; } + return d; } diff --git a/tests/unit_tests/test_mesh.py b/tests/unit_tests/test_mesh.py index 49556fce37c..3c8e988c032 100644 --- a/tests/unit_tests/test_mesh.py +++ b/tests/unit_tests/test_mesh.py @@ -618,3 +618,36 @@ def test_mesh_material_volumes_serialize(): assert new_volumes.by_element(1) == [(None, 1.0)] assert new_volumes.by_element(2) == [(2, 0.5), (1, 0.5)] assert new_volumes.by_element(3) == [(2, 1.0)] + + +def test_raytrace_mesh_infinite_loop(): + # Create a model with one large spherical cell + sphere = openmc.Sphere(r=100, boundary_type='vacuum') + cell = openmc.Cell(region=-sphere) + model = openmc.Model() + model.geometry = openmc.Geometry([cell]) + + # Create a regular mesh and associated tally + mesh_surface = openmc.RegularMesh() + mesh_surface.lower_left = (-30, -30, 30) + mesh_surface.upper_right = (30, 30, 60) + mesh_surface.dimension = (1, 1, 1) + reg_filter = openmc.MeshSurfaceFilter(mesh_surface) + mesh_surface_tally = openmc.Tally() + mesh_surface_tally.filters = [reg_filter] + mesh_surface_tally.scores = ['current'] + model.tallies = [mesh_surface_tally] + + # Define a source such that the z position is on a mesh boundary with a very + # small directional cosine in the z direction + polar = openmc.stats.delta_function(1.75e-7) + azimuthal = openmc.stats.Uniform(0.0, 2.0*pi) + model.settings.source = openmc.IndependentSource( + angle=openmc.stats.PolarAzimuthal(polar, azimuthal) + ) + model.settings.run_mode = 'fixed source' + model.settings.particles = 10 + model.settings.batches = 1 + + # Run the model; this should not cause an infinite loop + model.run() From 6c9c69628cbab69ecd46dc50c2585ab9fd5251f6 Mon Sep 17 00:00:00 2001 From: April Novak Date: Thu, 12 Jun 2025 11:49:59 -0500 Subject: [PATCH 040/159] update units for flux (#3441) --- docs/source/methods/random_ray.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/source/methods/random_ray.rst b/docs/source/methods/random_ray.rst index 41f95f0a9ea..5e17316aa1c 100644 --- a/docs/source/methods/random_ray.rst +++ b/docs/source/methods/random_ray.rst @@ -109,9 +109,7 @@ terms on the right hand side. In Equation :eq:`transport`, :math:`\psi` is the angular neutron flux. This parameter represents the total distance traveled by all neutrons in a particular direction inside of a control volume per second, and is often given in units of -:math:`1/(\text{cm}^{2} \text{s})`. As OpenMC does not support time dependence -in the random ray solver mode, we consider the steady state equation, where the -units of flux become :math:`1/\text{cm}^{2}`. The angular direction unit vector, +:math:`1/(\text{cm}^{2} \text{s})`. The angular direction unit vector, :math:`\mathbf{\Omega}`, represents the direction of travel for the neutron. The spatial position vector, :math:`\mathbf{r}`, represents the location within the simulation. The neutron energy, :math:`E`, or speed in continuous space, is From f81962c960929edc055198527d544db36f150747 Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Thu, 12 Jun 2025 23:02:35 +0200 Subject: [PATCH 041/159] Allowing chain_file to be chain object to save reloading time (#3436) Co-authored-by: Paul Romano --- openmc/deplete/abc.py | 11 +++--- openmc/deplete/chain.py | 38 ++++++++++++++++++- openmc/deplete/coupled_operator.py | 6 +-- openmc/deplete/d1s.py | 15 ++++---- openmc/deplete/independent_operator.py | 12 +++--- openmc/deplete/microxs.py | 35 +++++------------ openmc/deplete/openmc_operator.py | 6 +-- tests/unit_tests/test_d1s.py | 3 +- .../test_deplete_coupled_operator.py | 6 ++- .../test_deplete_independent_operator.py | 5 ++- 10 files changed, 81 insertions(+), 56 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 4b43dd22149..e6d4c151273 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -24,7 +24,7 @@ from openmc.utility_funcs import change_directory from openmc import Material from .stepresult import StepResult -from .chain import Chain +from .chain import _get_chain from .results import Results, _SECONDS_PER_MINUTE, _SECONDS_PER_HOUR, \ _SECONDS_PER_DAY, _SECONDS_PER_JULIAN_YEAR from .pool import deplete @@ -126,8 +126,8 @@ class TransportOperator(ABC): Parameters ---------- - chain_file : str - Path to the depletion chain XML file + chain_file : PathLike or Chain + Path to the depletion chain XML file or instance of openmc.deplete.Chain. fission_q : dict, optional Dictionary of nuclides and their fission Q values [eV]. If not given, values will be pulled from the ``chain_file``. @@ -145,11 +145,12 @@ class TransportOperator(ABC): The depletion chain information necessary to form matrices and tallies. """ - def __init__(self, chain_file, fission_q=None, prev_results=None): + def __init__(self, chain_file=None, fission_q=None, prev_results=None): self.output_dir = '.' # Read depletion chain - self.chain = Chain.from_xml(chain_file, fission_q) + self.chain = _get_chain(chain_file, fission_q) + if prev_results is None: self.prev_res = None else: diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 9f234683a90..07db487995b 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -17,8 +17,9 @@ import lxml.etree as ET import scipy.sparse as sp -from openmc.checkvalue import check_type, check_greater_than +from openmc.checkvalue import check_type, check_greater_than, PathLike from openmc.data import gnds_name, zam +from openmc.exceptions import DataError from .nuclide import FissionYieldDistribution, Nuclide import openmc.data @@ -1246,3 +1247,38 @@ def _follow(self, isotopes, level): found.update(isotopes) return found + + +def _get_chain( + chain_file: PathLike | Chain | None = None, + fission_q: dict | None = None +) -> Chain: + """Get a depletion chain from a file or the runtime configuration. + + Parameters + ---------- + chain_file : PathLike or Chain, optional + Path to depletion chain XML file, a Chain instance, or None to use + the file specified in ``openmc.config['chain_file']``. + fission_q : dict, optional + Dictionary of nuclides and their fission Q values [eV]. If not given, + values will be pulled from the ``chain_file``. + + Returns + ------- + Chain + Depletion chain instance. + """ + if isinstance(chain_file, Chain): + return chain_file + elif isinstance(chain_file, PathLike | None): + if chain_file is None: + chain_file = openmc.config.get('chain_file') + if 'chain_file' not in openmc.config: + raise DataError( + "No depletion chain specified and could not find depletion " + "chain in openmc.config['chain_file']" + ) + return Chain.from_xml(chain_file, fission_q) + else: + raise TypeError("chain_file must be path-like, a Chain, or None") diff --git a/openmc/deplete/coupled_operator.py b/openmc/deplete/coupled_operator.py index 7ecfd5a0893..34bb28b491b 100644 --- a/openmc/deplete/coupled_operator.py +++ b/openmc/deplete/coupled_operator.py @@ -102,9 +102,9 @@ class CoupledOperator(OpenMCOperator): ---------- model : openmc.model.Model OpenMC model object - chain_file : str, optional - Path to the depletion chain XML file. Defaults to - ``openmc.config['chain_file']``. + chain_file : PathLike or Chain, optional + Path to the depletion chain XML file or instance of openmc.deplete.Chain. + Defaults to ``openmc.config['chain_file']``. prev_results : Results, optional Results from a previous depletion calculation. If this argument is specified, the depletion calculation will start from the latest state diff --git a/openmc/deplete/d1s.py b/openmc/deplete/d1s.py index 0a1ed74b173..e9f8bfe79d6 100644 --- a/openmc/deplete/d1s.py +++ b/openmc/deplete/d1s.py @@ -14,19 +14,20 @@ import openmc from openmc.data import half_life from .abc import _normalize_timesteps -from .chain import Chain +from .chain import Chain, _get_chain +from ..checkvalue import PathLike -def get_radionuclides(model: openmc.Model, chain_file: str | None = None) -> list[str]: +def get_radionuclides(model: openmc.Model, chain_file: PathLike | Chain | None = None) -> list[str]: """Determine all radionuclides that can be produced during D1S. Parameters ---------- model : openmc.Model Model that should be used for determining what nuclides are present - chain_file : str, optional - Which chain file to use for inspecting decay data. If None is passed, - defaults to ``openmc.config['chain_file']`` + chain_file : PathLike | Chain + Path to the depletion chain XML file or instance of openmc.deplete.Chain. + Used for inspecting decay data. Defaults to ``openmc.config['chain_file']`` Returns ------- @@ -39,9 +40,7 @@ def get_radionuclides(model: openmc.Model, chain_file: str | None = None) -> lis for nuc in mat.get_nuclides()} # Load chain file - if chain_file is None: - chain_file = openmc.config['chain_file'] - chain = Chain.from_xml(chain_file) + chain = _get_chain(chain_file) radionuclides = set() for nuclide in chain.nuclides: diff --git a/openmc/deplete/independent_operator.py b/openmc/deplete/independent_operator.py index b3fe9a78fcc..3eb50b05ec1 100644 --- a/openmc/deplete/independent_operator.py +++ b/openmc/deplete/independent_operator.py @@ -50,9 +50,9 @@ class IndependentOperator(OpenMCOperator): Cross sections in [b] for each domain. If the :class:`~openmc.deplete.MicroXS` object is empty, a decay-only calculation will be run. - chain_file : str - Path to the depletion chain XML file. Defaults to - ``openmc.config['chain_file']``. + chain_file : PathLike or Chain, optional + Path to the depletion chain XML file or instance of openmc.deplete.Chain. + Defaults to ``openmc.config['chain_file']``. keff : 2-tuple of float, optional keff eigenvalue and uncertainty from transport calculation. prev_results : Results, optional @@ -179,9 +179,9 @@ def from_nuclides(cls, volume, nuclides, micro_xs : MicroXS Cross sections in [b]. If the :class:`~openmc.deplete.MicroXS` object is empty, a decay-only calculation will be run. - chain_file : str, optional - Path to the depletion chain XML file. Defaults to - ``openmc.config['chain_file']``. + chain_file : PathLike or Chain, optional + Path to the depletion chain XML file or instance of + openmc.deplete.Chain. Defaults to ``openmc.config['chain_file']``. nuc_units : {'atom/cm3', 'atom/b-cm'}, optional Units for nuclide concentration. keff : 2-tuple of float, optional diff --git a/openmc/deplete/microxs.py b/openmc/deplete/microxs.py index 686d3bc3823..d478db11ba7 100644 --- a/openmc/deplete/microxs.py +++ b/openmc/deplete/microxs.py @@ -12,13 +12,12 @@ import numpy as np from openmc.checkvalue import check_type, check_value, check_iterable_type, PathLike -from openmc.exceptions import DataError from openmc.utility_funcs import change_directory from openmc import StatePoint from openmc.mgxs import GROUP_STRUCTURES from openmc.data import REACTION_MT import openmc -from .chain import Chain, REACTIONS +from .chain import Chain, REACTIONS, _get_chain from .coupled_operator import _find_cross_sections, _get_nuclides_with_data import openmc.lib from openmc.mpi import comm @@ -28,24 +27,13 @@ _valid_rxns.append('damage-energy') -def _resolve_chain_file_path(chain_file: str | None): - if chain_file is None: - chain_file = openmc.config.get('chain_file') - if 'chain_file' not in openmc.config: - raise DataError( - "No depletion chain specified and could not find depletion " - "chain in openmc.config['chain_file']" - ) - return chain_file - - def get_microxs_and_flux( model: openmc.Model, domains, nuclides: Iterable[str] | None = None, reactions: Iterable[str] | None = None, energies: Iterable[float] | str | None = None, - chain_file: PathLike | None = None, + chain_file: PathLike | Chain | None = None, run_kwargs=None ) -> tuple[list[np.ndarray], list[MicroXS]]: """Generate a microscopic cross sections and flux from a Model @@ -66,9 +54,9 @@ def get_microxs_and_flux( reactions listed in the depletion chain file are used. energies : iterable of float or str Energy group boundaries in [eV] or the name of the group structure - chain_file : str, optional - Path to the depletion chain XML file that will be used in depletion - simulation. Used to determine cross sections for materials not + chain_file : PathLike or Chain, optional + Path to the depletion chain XML file or an instance of + openmc.deplete.Chain. Used to determine cross sections for materials not present in the inital composition. Defaults to ``openmc.config['chain_file']``. run_kwargs : dict, optional @@ -86,8 +74,7 @@ def get_microxs_and_flux( original_tallies = model.tallies # Determine what reactions and nuclides are available in chain - chain_file = _resolve_chain_file_path(chain_file) - chain = Chain.from_xml(chain_file) + chain = _get_chain(chain_file) if reactions is None: reactions = chain.reactions if not nuclides: @@ -245,9 +232,9 @@ def from_multigroup_flux( Energy group boundaries in [eV] or the name of the group structure multi_group_flux : iterable of float Energy-dependent multigroup flux values - chain_file : str, optional - Path to the depletion chain XML file that will be used in depletion - simulation. Defaults to ``openmc.config['chain_file']``. + chain_file : PathLike or Chain, optional + Path to the depletion chain XML file or an instance of + openmc.deplete.Chain. Defaults to ``openmc.config['chain_file']``. temperature : int, optional Temperature for cross section evaluation in [K]. nuclides : list of str, optional @@ -278,9 +265,7 @@ def from_multigroup_flux( if len(multigroup_flux) != len(energies) - 1: raise ValueError('Length of flux array should be len(energies)-1') - chain_file_path = _resolve_chain_file_path(chain_file) - chain = Chain.from_xml(chain_file_path) - + chain = _get_chain(chain_file) cross_sections = _find_cross_sections(model=None) nuclides_with_data = _get_nuclides_with_data(cross_sections) diff --git a/openmc/deplete/openmc_operator.py b/openmc/deplete/openmc_operator.py index 4ef8673728e..2d7694093bd 100644 --- a/openmc/deplete/openmc_operator.py +++ b/openmc/deplete/openmc_operator.py @@ -36,9 +36,9 @@ class OpenMCOperator(TransportOperator): cross_sections : str or list of MicroXS Path to continuous energy cross section library, or list of objects containing cross sections. - chain_file : str, optional - Path to the depletion chain XML file. Defaults to - openmc.config['chain_file']. + chain_file : PathLike or Chain, optional + Path to the depletion chain XML file or instance of openmc.deplete.Chain. + Defaults to ``openmc.config['chain_file']``. prev_results : Results, optional Results from a previous depletion calculation. If this argument is specified, the depletion calculation will start from the latest state diff --git a/tests/unit_tests/test_d1s.py b/tests/unit_tests/test_d1s.py index 7c92dd6d9b8..115759489aa 100644 --- a/tests/unit_tests/test_d1s.py +++ b/tests/unit_tests/test_d1s.py @@ -22,7 +22,8 @@ def model(): def test_get_radionuclides(model): # Check that radionuclides are correct and are unstable - nuclides = d1s.get_radionuclides(model, CHAIN_PATH) + chain = openmc.deplete.Chain.from_xml(CHAIN_PATH) + nuclides = d1s.get_radionuclides(model, chain) assert sorted(nuclides) == [ 'Co58', 'Co60', 'Co61', 'Co62', 'Co64', 'Fe55', 'Fe59', 'Fe61', 'Ni57', 'Ni59', 'Ni63', 'Ni65' diff --git a/tests/unit_tests/test_deplete_coupled_operator.py b/tests/unit_tests/test_deplete_coupled_operator.py index ba291e07dd5..119a15923f2 100644 --- a/tests/unit_tests/test_deplete_coupled_operator.py +++ b/tests/unit_tests/test_deplete_coupled_operator.py @@ -5,7 +5,7 @@ from pathlib import Path import pytest -from openmc.deplete import CoupledOperator +from openmc.deplete import CoupledOperator, Chain import openmc import numpy as np @@ -106,11 +106,13 @@ def test_diff_volume_method_match_cell(model_with_volumes): def test_diff_volume_method_divide_equally(model_with_volumes): """Tests the volumes assigned to the materials are divided equally""" + chain = Chain.from_xml(CHAIN_PATH) + operator = openmc.deplete.CoupledOperator( model=model_with_volumes, diff_burnable_mats=True, diff_volume_method='divide equally', - chain_file=CHAIN_PATH + chain_file=chain ) all_cells = list(operator.model.geometry.get_all_cells().values()) diff --git a/tests/unit_tests/test_deplete_independent_operator.py b/tests/unit_tests/test_deplete_independent_operator.py index c765d065009..aca83399a08 100644 --- a/tests/unit_tests/test_deplete_independent_operator.py +++ b/tests/unit_tests/test_deplete_independent_operator.py @@ -7,7 +7,7 @@ import pytest from openmc import Material -from openmc.deplete import IndependentOperator, MicroXS +from openmc.deplete import IndependentOperator, MicroXS, Chain CHAIN_PATH = Path(__file__).parents[1] / "chain_simple.xml" ONE_GROUP_XS = Path(__file__).parents[1] / "micro_xs_simple.csv" @@ -25,8 +25,9 @@ def test_operator_init(): 'O17': 1.7588724018066158e+19} flux = 1.0 micro_xs = MicroXS.from_csv(ONE_GROUP_XS) + chain = Chain.from_xml(CHAIN_PATH) IndependentOperator.from_nuclides( - volume, nuclides, flux, micro_xs, CHAIN_PATH, nuc_units='atom/cm3') + volume, nuclides, flux, micro_xs, chain, nuc_units='atom/cm3') fuel = Material(name="uo2") fuel.add_element("U", 1, percent_type="ao", enrichment=4.25) From aeb1052c1907e4d12890fcbbc2bb53ee3c737aa3 Mon Sep 17 00:00:00 2001 From: John Tramm Date: Thu, 12 Jun 2025 19:54:35 -0500 Subject: [PATCH 042/159] Fix Resetting of Auto IDs When Generating MGXS (#3437) --- openmc/model/model.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openmc/model/model.py b/openmc/model/model.py index c19c8ac9f15..7e679d10aa0 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -1552,7 +1552,6 @@ def _generate_infinite_medium_mgxs( "if a material has a k-infinity > 1.0.") mgxs_sets = [] for material in self.materials: - openmc.reset_auto_ids() model = openmc.Model() # Set materials on the model @@ -1749,7 +1748,6 @@ def _generate_stochastic_slab_mgxs( directory : str Directory to run the simulation in, so as to contain XML files. """ - openmc.reset_auto_ids() model = openmc.Model() model.materials = self.materials @@ -1861,7 +1859,6 @@ def _generate_material_wise_mgxs( directory : PathLike Directory to run the simulation in, so as to contain XML files. """ - openmc.reset_auto_ids() model = copy.deepcopy(self) model.tallies = openmc.Tallies() @@ -1973,7 +1970,7 @@ def convert_to_multigroup( # Make sure all materials have a name, and that the name is a valid HDF5 # dataset name for material in self.materials: - if material.name is None: + if not material.name or not material.name.strip(): material.name = f"material {material.id}" material.name = re.sub(r'[^a-zA-Z0-9]', '_', material.name) From b11eb02655ab0112ce6001546c9311692d08c9b2 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 13 Jun 2025 15:38:16 -0500 Subject: [PATCH 043/159] Change Dockerfile from debian:bookworm-slim to ubuntu:24.04 (#3442) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 1ae615a50f8..9745f15123c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,7 +24,7 @@ ARG compile_cores=1 ARG build_dagmc=off ARG build_libmesh=off -FROM debian:bookworm-slim AS dependencies +FROM ubuntu:24.04 AS dependencies ARG compile_cores ARG build_dagmc From 23eab2c89bb6537cf3bf264215b38c6013a518e0 Mon Sep 17 00:00:00 2001 From: Jan Malec Date: Sat, 14 Jun 2025 08:39:31 +0200 Subject: [PATCH 044/159] Allow specifying number of equiprobable angles for thermal scattering data generation (#3346) Co-authored-by: Paul Romano --- openmc/data/njoy.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openmc/data/njoy.py b/openmc/data/njoy.py index e025cff0d98..4c538bd6e6f 100644 --- a/openmc/data/njoy.py +++ b/openmc/data/njoy.py @@ -201,12 +201,12 @@ _THERMAL_TEMPLATE_THERMR = """ thermr / %%%%%%%%%%%%%%%% Add thermal scattering data (free gas) %%%%%%%%%%%%%%% 0 {nthermr1_in} {nthermr1} -0 {mat} 12 {num_temp} 1 0 {iform} 1 221 1/ +0 {mat} {nbin} {num_temp} 1 0 {iform} 1 221 1/ {temps} {error} {energy_max} thermr / %%%%%%%%%%%%%%%% Add thermal scattering data (bound) %%%%%%%%%%%%%%%%%% {nthermal_endf} {nthermr2_in} {nthermr2} -{mat_thermal} {mat} 16 {num_temp} {inelastic} {elastic} {iform} {natom} 222 1/ +{mat_thermal} {mat} {nbin} {num_temp} {inelastic} {elastic} {iform} {natom} 222 1/ {temps} {error} {energy_max} """ @@ -495,7 +495,7 @@ def make_ace(filename, temperatures=None, acer=True, xsdir=None, def make_ace_thermal(filename, filename_thermal, temperatures=None, ace=None, xsdir=None, output_dir=None, error=0.001, iwt=2, evaluation=None, evaluation_thermal=None, - table_name=None, zaids=None, nmix=None, **kwargs): + table_name=None, zaids=None, nmix=None, nbin=16, **kwargs): """Generate thermal scattering ACE file from ENDF files Parameters @@ -532,6 +532,8 @@ def make_ace_thermal(filename, filename_thermal, temperatures=None, ZAIDs that the thermal scattering data applies to nmix : int, optional Number of atom types in mixed moderator + nbin : int, optional + Number of equi-probable angles **kwargs Keyword arguments passed to :func:`openmc.data.njoy.run` From 7a1cafa6c17d456dabdd4a2aa33fd5e586e98794 Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Wed, 18 Jun 2025 00:36:35 +0200 Subject: [PATCH 045/159] adding plot function to DAGMCUnvierse (#3451) Co-authored-by: Paul Romano --- openmc/dagmc.py | 16 ++++++++++++++++ tests/unit_tests/dagmc/test_plot.py | 14 +++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/openmc/dagmc.py b/openmc/dagmc.py index 8ab0aaf69e7..3f20fb0ac82 100644 --- a/openmc/dagmc.py +++ b/openmc/dagmc.py @@ -13,6 +13,7 @@ from .surface import _BOUNDARY_TYPES from .bounding_box import BoundingBox from .utility_funcs import input_path +from .plots import add_plot_params class DAGMCUniverse(openmc.UniverseBase): @@ -566,6 +567,21 @@ def sync_dagmc_cells(self, mats: Iterable[openmc.Material]): fill = mats_per_id[dag_cell.fill.id] if dag_cell.fill else None self.add_cell(openmc.DAGMCCell(cell_id=dag_cell_id, fill=fill)) + @add_plot_params + def plot(self, *args, **kwargs): + """Display a slice plot of the DAGMCUniverse. + """ + model = openmc.Model() + model.geometry = openmc.Geometry(self) + + for mat_name in self.material_names: + material = openmc.Material(name=mat_name) + # Placeholder nuclide to ensure material is not empty + material.add_nuclide('H1', 1.0) + model.materials.append(material) + + return model.plot(*args, **kwargs) + class DAGMCCell(openmc.Cell): """A cell class for DAGMC-based geometries. diff --git a/tests/unit_tests/dagmc/test_plot.py b/tests/unit_tests/dagmc/test_plot.py index 83f6df7676b..d768ba50810 100644 --- a/tests/unit_tests/dagmc/test_plot.py +++ b/tests/unit_tests/dagmc/test_plot.py @@ -7,9 +7,9 @@ not openmc.lib._dagmc_enabled(), reason="DAGMC CAD geometry is not enabled." ) -def test_plotting_dagmc_geometry(request): - """Test plotting a DAGMC geometry with OpenMC. This is different to CSG - geometry plotting as the path to the DAGMC file needs handling.""" +def test_plotting_dagmc_model(request): + """Test plotting a DAGMC model with OpenMC. This is different to CSG + model plotting as the path to the DAGMC file needs handling.""" dag_universe = openmc.DAGMCUniverse(request.path.parent / 'dagmc.h5m') csg_with_dag_inside = dag_universe.bounded_universe() @@ -30,3 +30,11 @@ def test_plotting_dagmc_geometry(request): model.settings.particles = 50 model.plot() + + +def test_plotting_dagmc_universe(request): + """Test plotting a DAGMCUniverse with OpenMC. This is different to plotting + UniverseBase as the materials are not defined withing the DAGMCUniverse.""" + + dag_universe = openmc.DAGMCUniverse(request.path.parent / 'dagmc.h5m') + dag_universe.plot() From f31130d36798aef0ca7bed6a73e24a9281c6c51c Mon Sep 17 00:00:00 2001 From: John Tramm Date: Wed, 18 Jun 2025 06:34:56 -0500 Subject: [PATCH 046/159] Prevent Adjoint Sources from Trending towards Infinity (#3449) --- include/openmc/constants.h | 5 ++++ src/random_ray/flat_source_domain.cpp | 43 +++++++++++++++++++++------ 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/include/openmc/constants.h b/include/openmc/constants.h index 252194528c7..ae705607958 100644 --- a/include/openmc/constants.h +++ b/include/openmc/constants.h @@ -63,6 +63,11 @@ constexpr int MAX_SAMPLE {100000}; // source region in the random ray solver constexpr double MIN_HITS_PER_BATCH {1.5}; +// The minimum flux value to be considered non-zero when computing adjoint +// sources. Positive values below this cutoff will be treated as zero, so as to +// prevent extremely large adjoint source terms from being generated. +constexpr double ZERO_FLUX_CUTOFF {1e-22}; + // ============================================================================ // MATH AND PHYSICAL CONSTANTS diff --git a/src/random_ray/flat_source_domain.cpp b/src/random_ray/flat_source_domain.cpp index 9df6513aee8..7404a0b29b6 100644 --- a/src/random_ray/flat_source_domain.cpp +++ b/src/random_ray/flat_source_domain.cpp @@ -304,6 +304,13 @@ int64_t FlatSourceDomain::add_source_to_scalar_flux() set_flux_to_source(sr, g); } } + // Halt if NaN implosion is detected + if (!std::isfinite(source_regions_.scalar_flux_new(sr, g))) { + fatal_error("A source region scalar flux is not finite. " + "This indicates a numerical instability in the " + "simulation. Consider increasing ray density or adjusting " + "the source region mesh."); + } } } @@ -1154,9 +1161,9 @@ void FlatSourceDomain::flatten_xs() m.get_xs(MgxsType::TOTAL, g_out, NULL, NULL, NULL, t, a); sigma_t_.push_back(sigma_t); - double nu_Sigma_f = + double nu_sigma_f = m.get_xs(MgxsType::NU_FISSION, g_out, NULL, NULL, NULL, t, a); - nu_sigma_f_.push_back(nu_Sigma_f); + nu_sigma_f_.push_back(nu_sigma_f); double sigma_f = m.get_xs(MgxsType::FISSION, g_out, NULL, NULL, NULL, t, a); @@ -1164,6 +1171,11 @@ void FlatSourceDomain::flatten_xs() double chi = m.get_xs(MgxsType::CHI_PROMPT, g_out, &g_out, NULL, NULL, t, a); + if (!std::isfinite(chi)) { + // MGXS interface may return NaN in some cases, such as when material + // is fissionable but has very small sigma_f. + chi = 0.0; + } chi_.push_back(chi); for (int g_in = 0; g_in < negroups_; g_in++) { @@ -1191,17 +1203,30 @@ void FlatSourceDomain::flatten_xs() void FlatSourceDomain::set_adjoint_sources(const vector& forward_flux) { - // Set the external source to 1/forward_flux. If the forward flux is negative - // or zero, set the adjoint source to zero, as this is likely a very small - // source region that we don't need to bother trying to vector particles - // towards. Flux negativity in random ray is not related to the flux being - // small in magnitude, but rather due to the source region being physically - // small in volume and thus having a noisy flux estimate. + // Set the adjoint external source to 1/forward_flux. If the forward flux is + // negative, zero, or extremely close to zero, set the adjoint source to zero, + // as this is likely a very small source region that we don't need to bother + // trying to vector particles towards. In the case of flux "being extremely + // close to zero", we define this as being a fixed fraction of the maximum + // forward flux, below which we assume the flux would be physically + // undetectable. + + // First, find the maximum forward flux value + double max_flux = 0.0; +#pragma omp parallel for reduction(max : max_flux) + for (int64_t se = 0; se < n_source_elements(); se++) { + double flux = forward_flux[se]; + if (flux > max_flux) { + max_flux = flux; + } + } + + // Then, compute the adjoint source for each source region #pragma omp parallel for for (int64_t sr = 0; sr < n_source_regions(); sr++) { for (int g = 0; g < negroups_; g++) { double flux = forward_flux[sr * negroups_ + g]; - if (flux <= 0.0) { + if (flux <= ZERO_FLUX_CUTOFF * max_flux) { source_regions_.external_source(sr, g) = 0.0; } else { source_regions_.external_source(sr, g) = 1.0 / flux; From 1c5aa6559be235abeb1c7a4c5d596d5ec617947d Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Wed, 18 Jun 2025 18:10:03 +0300 Subject: [PATCH 047/159] fixing expansion of elemental Ta bug (#3443) Co-authored-by: Paul Romano --- openmc/data/data.py | 2 +- openmc/element.py | 9 +++++---- tests/unit_tests/test_element.py | 7 +++++++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/openmc/data/data.py b/openmc/data/data.py index 408adf2e429..8ccc8d26355 100644 --- a/openmc/data/data.py +++ b/openmc/data/data.py @@ -94,7 +94,7 @@ 'Yb174': 0.32025, 'Yb176': 0.12995, 'Lu175': 0.97401, 'Lu176': 0.02599, 'Hf174': 0.0016, 'Hf176': 0.0526, 'Hf177': 0.186, 'Hf178': 0.2728, 'Hf179': 0.1362, - 'Hf180': 0.3508, 'Ta180': 0.0001201, 'Ta181': 0.9998799, + 'Hf180': 0.3508, 'Ta180_m1': 0.0001201, 'Ta181': 0.9998799, 'W180': 0.0012, 'W182': 0.265, 'W183': 0.1431, 'W184': 0.3064, 'W186': 0.2843, 'Re185': 0.374, 'Re187': 0.626, 'Os184': 0.0002, 'Os186': 0.0159, diff --git a/openmc/element.py b/openmc/element.py index 082bee5226d..f1d8f5f24d5 100644 --- a/openmc/element.py +++ b/openmc/element.py @@ -144,8 +144,7 @@ def expand(self, percent, percent_type, enrichment=None, root = tree.getroot() for child in root.findall('library'): nuclide = child.attrib['materials'] - if re.match(r'{}\d+'.format(self), nuclide) and \ - '_m' not in nuclide: + if re.match(r'{}\d+'.format(self), nuclide): library_nuclides.add(nuclide) # Get a set of the mutual and absent nuclides. Convert to lists @@ -185,8 +184,10 @@ def expand(self, percent, percent_type, enrichment=None, for nuclide in absent_nuclides: if nuclide in ['O17', 'O18'] and 'O16' in mutual_nuclides: abundances['O16'] += NATURAL_ABUNDANCE[nuclide] - elif nuclide == 'Ta180' and 'Ta181' in mutual_nuclides: - abundances['Ta181'] += NATURAL_ABUNDANCE[nuclide] + elif nuclide == 'Ta180_m1' and 'Ta180' in library_nuclides: + abundances['Ta180'] = NATURAL_ABUNDANCE[nuclide] + elif nuclide == 'Ta180_m1' and 'Ta181' in mutual_nuclides: + abundances['Ta181'] += NATURAL_ABUNDANCE[nuclide] elif nuclide == 'W180' and 'W182' in mutual_nuclides: abundances['W182'] += NATURAL_ABUNDANCE[nuclide] else: diff --git a/tests/unit_tests/test_element.py b/tests/unit_tests/test_element.py index d3555701e2c..d91cfaf6e24 100644 --- a/tests/unit_tests/test_element.py +++ b/tests/unit_tests/test_element.py @@ -44,6 +44,13 @@ def test_expand_no_isotopes(): element.expand(100.0, 'ao') +def test_expand_ta(): + ref = {'Ta180': 0.01201, 'Ta181': 99.98799} + element = openmc.Element('Ta') + for isotope in element.expand(100.0, 'ao'): + assert isotope[1] == approx(ref[isotope[0]]) + + def test_expand_exceptions(): """ Test that correct exceptions are raised for invalid input """ From 3e32aed2da312a04fb4db2c22648d66d78a0e745 Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Wed, 18 Jun 2025 20:34:07 +0200 Subject: [PATCH 048/159] Fixing crash when calling Geometry.plot when DAGMCUniverse in geometry (#3455) Co-authored-by: Paul Romano --- openmc/dagmc.py | 11 +---------- openmc/geometry.py | 15 ++++++++++++++- openmc/model/model.py | 14 ++++++++++++++ openmc/universe.py | 8 -------- tests/unit_tests/dagmc/test_plot.py | 21 +++++++++++++++++++++ 5 files changed, 50 insertions(+), 19 deletions(-) diff --git a/openmc/dagmc.py b/openmc/dagmc.py index 3f20fb0ac82..2486f7516f0 100644 --- a/openmc/dagmc.py +++ b/openmc/dagmc.py @@ -571,16 +571,7 @@ def sync_dagmc_cells(self, mats: Iterable[openmc.Material]): def plot(self, *args, **kwargs): """Display a slice plot of the DAGMCUniverse. """ - model = openmc.Model() - model.geometry = openmc.Geometry(self) - - for mat_name in self.material_names: - material = openmc.Material(name=mat_name) - # Placeholder nuclide to ensure material is not empty - material.add_nuclide('H1', 1.0) - model.materials.append(material) - - return model.plot(*args, **kwargs) + return openmc.Geometry(self).plot(*args, **kwargs) class DAGMCCell(openmc.Cell): diff --git a/openmc/geometry.py b/openmc/geometry.py index c069d579695..9b2af7b4a03 100644 --- a/openmc/geometry.py +++ b/openmc/geometry.py @@ -754,4 +754,17 @@ def plot(self, *args, **kwargs): .. versionadded:: 0.14.0 """ - return self.root_universe.plot(*args, **kwargs) + model = openmc.Model() + model.geometry = self + model.materials = self.get_all_materials().values() + + # Add placeholder materials for DAGMCUniverses + universes = self.get_all_universes() + for universe in universes.values(): + if isinstance(universe, openmc.DAGMCUniverse): + for name in universe.material_names: + mat_dag = openmc.Material(name=name) + mat_dag.add_nuclide('H1', 1.0) + model.materials.append(mat_dag) + + return model.plot(*args, **kwargs) diff --git a/openmc/model/model.py b/openmc/model/model.py index 7e679d10aa0..55f309bfbd3 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -986,7 +986,16 @@ def plot( y_min = (origin[y] - 0.5*width[1]) * axis_scaling_factor[axis_units] y_max = (origin[y] + 0.5*width[1]) * axis_scaling_factor[axis_units] + # Determine whether any materials contains macroscopic data and if so, + # set energy mode accordingly + _energy_mode = self.settings._energy_mode + for mat in self.geometry.get_all_materials().values(): + if mat._macroscopic is not None: + self.settings.energy_mode = 'multi-group' + break + with TemporaryDirectory() as tmpdir: + _plot_seed = self.settings.plot_seed if seed is not None: self.settings.plot_seed = seed @@ -1007,6 +1016,11 @@ def plot( # Run OpenMC in geometry plotting mode self.plot_geometry(False, cwd=tmpdir, openmc_exec=openmc_exec) + # Undo changes to model + self.plots.pop() + self.settings._plot_seed = _plot_seed + self.settings._energy_mode = _energy_mode + # Read image from file img_path = Path(tmpdir) / f'plot_{plot.id}.png' if not img_path.is_file(): diff --git a/openmc/universe.py b/openmc/universe.py index 02b794deed4..0e64693ba8e 100644 --- a/openmc/universe.py +++ b/openmc/universe.py @@ -337,14 +337,6 @@ def plot(self, *args, **kwargs): """ model = openmc.Model() model.geometry = openmc.Geometry(self) - - # Determine whether any materials contains macroscopic data and if - # so, set energy mode accordingly - for mat in self.get_all_materials().values(): - if mat._macroscopic is not None: - model.settings.energy_mode = 'multi-group' - break - return model.plot(*args, **kwargs) def get_nuclides(self): diff --git a/tests/unit_tests/dagmc/test_plot.py b/tests/unit_tests/dagmc/test_plot.py index d768ba50810..afc2c395e4f 100644 --- a/tests/unit_tests/dagmc/test_plot.py +++ b/tests/unit_tests/dagmc/test_plot.py @@ -38,3 +38,24 @@ def test_plotting_dagmc_universe(request): dag_universe = openmc.DAGMCUniverse(request.path.parent / 'dagmc.h5m') dag_universe.plot() + + +def test_plotting_geometry_filled_with_dagmc_universe(request): + """Test plotting a geometry with OpenMC. This is an edge case when plotting + geometry as often geometry objects don't include a DAGMCUniverse. The + inclusion of a DAGMCUniverse requires special handling for the materials.""" + + dag_universe = openmc.DAGMCUniverse(request.path.parent / 'dagmc.h5m', auto_geom_ids=True) + + sphere1 = openmc.Sphere(r=50.0) + sphere2 = openmc.Sphere(r=60.0, boundary_type='vacuum') + + # Adding a material to the CSG Universe to check all materials are accounted for + csg_material = openmc.Material(name='csg_material') + csg_material.add_nuclide("H1", 1.0) + + cell1 = openmc.Cell(fill=dag_universe, region=-sphere1) + cell2 = openmc.Cell(fill=csg_material, region=+sphere1 & -sphere2) + + geometry = openmc.Geometry([cell1, cell2]) + geometry.plot() From 3d16d4b1007fbee2f48e823d08f2ae0ab29aea73 Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Thu, 19 Jun 2025 22:43:45 +0200 Subject: [PATCH 049/159] Adding checks to geometry.plot to avoid material name overlaps (#3458) Co-authored-by: Patrick Shriwise --- openmc/geometry.py | 30 +++++++++++++++++++++-------- tests/unit_tests/dagmc/test_plot.py | 11 +++++++++-- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/openmc/geometry.py b/openmc/geometry.py index 9b2af7b4a03..f56b9b6fe37 100644 --- a/openmc/geometry.py +++ b/openmc/geometry.py @@ -758,13 +758,27 @@ def plot(self, *args, **kwargs): model.geometry = self model.materials = self.get_all_materials().values() - # Add placeholder materials for DAGMCUniverses - universes = self.get_all_universes() - for universe in universes.values(): - if isinstance(universe, openmc.DAGMCUniverse): - for name in universe.material_names: - mat_dag = openmc.Material(name=name) - mat_dag.add_nuclide('H1', 1.0) - model.materials.append(mat_dag) + # collect all the material names from the geometry + all_material_names = {m.name for m in model.materials if m.name is not None} + + # makes a placeholder material for each material name if it isn't + # already present on the model. These materials are otherwise missing + # from the geometry and are needed for plotting. + for universe in model.geometry.get_all_universes().values(): + if not isinstance(universe, openmc.DAGMCUniverse): + continue + for name in universe.material_names: + # if this name is already present in the model, skip it + # (this can happen if the same material name is used in multiple + # universes) + if name in all_material_names: + continue + # if the material name is not present on the model, + # create a placeholder material with the same name + # and add it to the model + mat_dag = openmc.Material(name=name) + mat_dag.add_nuclide('H1', 1.0) + model.materials.append(mat_dag) + all_material_names.add(name) return model.plot(*args, **kwargs) diff --git a/tests/unit_tests/dagmc/test_plot.py b/tests/unit_tests/dagmc/test_plot.py index afc2c395e4f..62022b25837 100644 --- a/tests/unit_tests/dagmc/test_plot.py +++ b/tests/unit_tests/dagmc/test_plot.py @@ -48,14 +48,21 @@ def test_plotting_geometry_filled_with_dagmc_universe(request): dag_universe = openmc.DAGMCUniverse(request.path.parent / 'dagmc.h5m', auto_geom_ids=True) sphere1 = openmc.Sphere(r=50.0) - sphere2 = openmc.Sphere(r=60.0, boundary_type='vacuum') + sphere2 = openmc.Sphere(r=60.0) + sphere2 = openmc.Sphere(r=70.0, boundary_type='vacuum') - # Adding a material to the CSG Universe to check all materials are accounted for + # Adding a material to the CSG Universe to check universe materials are accounted for csg_material = openmc.Material(name='csg_material') csg_material.add_nuclide("H1", 1.0) + # Adding a material with the same name as a dagmc material to check that + # the plot can handel two materials with the same name from different universes + csg_material = openmc.Material(name=dag_universe.material_names[0]) + csg_material.add_nuclide("H1", 1.0) + cell1 = openmc.Cell(fill=dag_universe, region=-sphere1) cell2 = openmc.Cell(fill=csg_material, region=+sphere1 & -sphere2) geometry = openmc.Geometry([cell1, cell2]) + geometry.plot() From eaef13b9bc4606e9f70253b5a044064975d98b09 Mon Sep 17 00:00:00 2001 From: John Tramm Date: Mon, 23 Jun 2025 15:53:27 -0500 Subject: [PATCH 050/159] Weight Window Birth Scaling (#3459) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Paul Romano --- include/openmc/particle_data.h | 5 +++++ src/simulation.cpp | 4 ++++ src/weight_windows.cpp | 16 ++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index a07a99ffbed..82b101c227b 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -435,6 +435,7 @@ class ParticleData : public GeometryState { double wgt_ {1.0}; double wgt_born_ {1.0}; + double wgt_ww_born_ {-1.0}; double mu_; double time_ {0.0}; double time_last_ {0.0}; @@ -545,6 +546,10 @@ class ParticleData : public GeometryState { double& wgt_born() { return wgt_born_; } double wgt_born() const { return wgt_born_; } + // Weight window value at birth + double& wgt_ww_born() { return wgt_ww_born_; } + const double& wgt_ww_born() const { return wgt_ww_born_; } + // Statistic weight of particle at last collision double& wgt_last() { return wgt_last_; } const double& wgt_last() const { return wgt_last_; } diff --git a/src/simulation.cpp b/src/simulation.cpp index e4521c06dc9..f8d4e547049 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -614,6 +614,10 @@ void initialize_history(Particle& p, int64_t index_source) // Set particle track. p.write_track() = check_track_criteria(p); + // Set the particle's initial weight window value. + p.wgt_ww_born() = -1.0; + apply_weight_windows(p); + // Display message if high verbosity or trace is on if (settings::verbosity >= 9 || p.trace()) { write_message("Simulating Particle {}", p.id()); diff --git a/src/weight_windows.cpp b/src/weight_windows.cpp index 0efa5a6aeee..50f3fb2edb6 100644 --- a/src/weight_windows.cpp +++ b/src/weight_windows.cpp @@ -73,10 +73,26 @@ void apply_weight_windows(Particle& p) if (weight_window.is_valid()) break; } + + // If particle has not yet had its birth weight window value set, set it to + // the current weight window (or 1.0 if not born in a weight window). + if (p.wgt_ww_born() == -1.0) { + if (weight_window.is_valid()) { + p.wgt_ww_born() = + (weight_window.lower_weight + weight_window.upper_weight) / 2; + } else { + p.wgt_ww_born() = 1.0; + } + } + // particle is not in any of the ww domains, do nothing if (!weight_window.is_valid()) return; + // Normalize weight windows based on particle's starting weight + // and the value of the weight window the particle was born in. + weight_window.scale(p.wgt_born() / p.wgt_ww_born()); + // get the paramters double weight = p.wgt(); From 7c6aaf7204163464d13e14a4547b48269a70c3d1 Mon Sep 17 00:00:00 2001 From: John Tramm Date: Mon, 23 Jun 2025 17:20:36 -0500 Subject: [PATCH 051/159] Fix Weight Window Infinite Loop Bug (#3457) Co-authored-by: Paul Romano --- include/openmc/particle_data.h | 2 +- src/particle.cpp | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index 82b101c227b..be7fa74837e 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -47,7 +47,7 @@ struct SourceSite { double time {0.0}; double wgt {1.0}; int delayed_group {0}; - int surf_id {0}; + int surf_id {SURFACE_NONE}; ParticleType particle; // Extra attributes that don't show up in source written to file diff --git a/src/particle.cpp b/src/particle.cpp index 324276d15f3..6cfdf2175af 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -104,6 +104,14 @@ void Particle::split(double wgt) bank.u = u(); bank.E = settings::run_CE ? E() : g(); bank.time = time(); + + // Convert signed index to a singed surface ID + if (surface() == SURFACE_NONE) { + bank.surf_id = SURFACE_NONE; + } else { + int surf_id = model::surfaces[surface_index()]->id_; + bank.surf_id = (surface() > 0) ? surf_id : -surf_id; + } } void Particle::from_source(const SourceSite* src) @@ -140,6 +148,12 @@ void Particle::from_source(const SourceSite* src) time() = src->time; time_last() = src->time; parent_nuclide() = src->parent_nuclide; + + // Convert signed surface ID to signed index + if (src->surf_id != SURFACE_NONE) { + int index_plus_one = model::surface_map[std::abs(src->surf_id)] + 1; + surface() = (src->surf_id > 0) ? index_plus_one : -index_plus_one; + } } void Particle::event_calculate_xs() From 3a8894c3f86a4e7c013eb5bd6e795aadead8ef7d Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 24 Jun 2025 04:29:16 -0500 Subject: [PATCH 052/159] Fix Dockerfile DAGMC build (#3463) --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 9745f15123c..a163a281029 100644 --- a/Dockerfile +++ b/Dockerfile @@ -96,6 +96,7 @@ RUN if [ "$build_dagmc" = "on" ]; then \ # Install addition packages required for DAGMC apt-get -y install libeigen3-dev libnetcdf-dev libtbb-dev libglfw3-dev \ && pip install --upgrade numpy \ + && pip install --no-cache-dir setuptools cython \ # Clone and install EMBREE && mkdir -p $HOME/EMBREE && cd $HOME/EMBREE \ && git clone --single-branch -b ${EMBREE_TAG} --depth 1 ${EMBREE_REPO} \ From 2a0299aec7e83fd978cb584ba8051c7ec6fb8866 Mon Sep 17 00:00:00 2001 From: John Tramm Date: Tue, 24 Jun 2025 15:20:26 -0500 Subject: [PATCH 053/159] Limit Random Ray Weight Window Generation to Final Batch (#3464) --- src/simulation.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/simulation.cpp b/src/simulation.cpp index f8d4e547049..d9a4fd131dc 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -400,8 +400,11 @@ void finalize_batch() simulation::time_tallies.stop(); // update weight windows if needed - for (const auto& wwg : variance_reduction::weight_windows_generators) { - wwg->update(); + if (settings::solver_type != SolverType::RANDOM_RAY || + simulation::current_batch == settings::n_batches) { + for (const auto& wwg : variance_reduction::weight_windows_generators) { + wwg->update(); + } } // Reset global tally results From 15dfe7e8b96a762086a0f4cf3c90d7f2cc31e833 Mon Sep 17 00:00:00 2001 From: John Tramm Date: Wed, 25 Jun 2025 08:46:40 -0500 Subject: [PATCH 054/159] Parallelization of Weight Window Update (#3467) --- src/weight_windows.cpp | 175 ++++++++++++++++++++++++++--------------- 1 file changed, 113 insertions(+), 62 deletions(-) diff --git a/src/weight_windows.cpp b/src/weight_windows.cpp index 50f3fb2edb6..26762ad18a6 100644 --- a/src/weight_windows.cpp +++ b/src/weight_windows.cpp @@ -6,11 +6,11 @@ #include #include +#include "xtensor/xdynamic_view.hpp" #include "xtensor/xindex_view.hpp" #include "xtensor/xio.hpp" #include "xtensor/xmasked_view.hpp" #include "xtensor/xnoalias.hpp" -#include "xtensor/xstrided_view.hpp" #include "xtensor/xview.hpp" #include "openmc/error.h" @@ -506,8 +506,18 @@ void WeightWindows::update_weights(const Tally* tally, const std::string& value, /////////////////////////// this->check_tally_update_compatibility(tally); - lower_ww_.fill(-1); - upper_ww_.fill(-1); + // Dimensions of weight window arrays + int e_bins = lower_ww_.shape()[0]; + int64_t mesh_bins = lower_ww_.shape()[1]; + + // Initialize weight window arrays to -1.0 by default +#pragma omp parallel for collapse(2) schedule(static) + for (int e = 0; e < e_bins; e++) { + for (int64_t m = 0; m < mesh_bins; m++) { + lower_ww_(e, m) = -1.0; + upper_ww_(e, m) = -1.0; + } + } // determine which value to use const std::set allowed_values = {"mean", "rel_err"}; @@ -605,10 +615,12 @@ void WeightWindows::update_weights(const Tally* tally, const std::string& value, } // down-select data based on particle and score - auto sum = xt::view(transposed_view, particle_idx, xt::all(), xt::all(), - score_index, static_cast(TallyResult::SUM)); - auto sum_sq = xt::view(transposed_view, particle_idx, xt::all(), xt::all(), - score_index, static_cast(TallyResult::SUM_SQ)); + auto sum = xt::dynamic_view( + transposed_view, {particle_idx, xt::all(), xt::all(), score_index, + static_cast(TallyResult::SUM)}); + auto sum_sq = xt::dynamic_view( + transposed_view, {particle_idx, xt::all(), xt::all(), score_index, + static_cast(TallyResult::SUM_SQ)}); int n = tally->n_realizations_; ////////////////////////////////////////////// @@ -626,78 +638,117 @@ void WeightWindows::update_weights(const Tally* tally, const std::string& value, auto& new_bounds = this->lower_ww_; auto& rel_err = this->upper_ww_; - // noalias avoids memory allocation here - xt::noalias(new_bounds) = sum / n; - - xt::noalias(rel_err) = - xt::sqrt(((sum_sq / n) - xt::square(new_bounds)) / (n - 1)) / new_bounds; - xt::filter(rel_err, sum <= 0.0).fill(INFTY); - - if (value == "rel_err") - xt::noalias(new_bounds) = 1 / rel_err; - // get mesh volumes auto mesh_vols = this->mesh()->volumes(); - int e_bins = new_bounds.shape()[0]; + // Calculate mean (new_bounds) and relative error +#pragma omp parallel for collapse(2) schedule(static) + for (int e = 0; e < e_bins; e++) { + for (int64_t m = 0; m < mesh_bins; m++) { + // Calculate mean + new_bounds(e, m) = sum(e, m) / n; + // Calculate relative error + if (sum(e, m) > 0.0) { + double mean_val = new_bounds(e, m); + double variance = (sum_sq(e, m) / n - mean_val * mean_val) / (n - 1); + rel_err(e, m) = std::sqrt(variance) / mean_val; + } else { + rel_err(e, m) = INFTY; + } + if (value == "rel_err") { + new_bounds(e, m) = 1.0 / rel_err(e, m); + } + } + } + + // Divide by volume of mesh elements +#pragma omp parallel for collapse(2) schedule(static) + for (int e = 0; e < e_bins; e++) { + for (int64_t m = 0; m < mesh_bins; m++) { + new_bounds(e, m) /= mesh_vols[m]; + } + } if (method == WeightWindowUpdateMethod::MAGIC) { - // If we are computing weight windows with forward fluxes derived from a - // Monte Carlo or forward random ray solve, we use the MAGIC algorithm. - for (int e = 0; e < e_bins; e++) { - // select all - auto group_view = xt::view(new_bounds, e); + // For MAGIC, weight windows are proportional to the forward fluxes. + // We normalize weight windows independently for each energy group. - // divide by volume of mesh elements - for (int i = 0; i < group_view.size(); i++) { - group_view[i] /= mesh_vols[i]; + // Find group maximum and normalize (per energy group) + for (int e = 0; e < e_bins; e++) { + double group_max = 0.0; + + // Find maximum value across all elements in this energy group +#pragma omp parallel for schedule(static) reduction(max : group_max) + for (int64_t m = 0; m < mesh_bins; m++) { + if (new_bounds(e, m) > group_max) { + group_max = new_bounds(e, m); + } } - double group_max = - *std::max_element(group_view.begin(), group_view.end()); - // normalize values in this energy group by the maximum value for this - // group - if (group_max > 0.0) - group_view /= 2.0 * group_max; + // Normalize values in this energy group by the maximum value + if (group_max > 0.0) { + double norm_factor = 1.0 / (2.0 * group_max); +#pragma omp parallel for schedule(static) + for (int64_t m = 0; m < mesh_bins; m++) { + new_bounds(e, m) *= norm_factor; + } + } } } else { - // If we are computing weight windows with adjoint fluxes derived from an - // adjoint random ray solve, we use the FW-CADIS algorithm. + // For FW-CADIS, weight windows are inversely proportional to the adjoint + // fluxes. We normalize the weight windows across all energy groups. +#pragma omp parallel for collapse(2) schedule(static) for (int e = 0; e < e_bins; e++) { - // select all - auto group_view = xt::view(new_bounds, e); + for (int64_t m = 0; m < mesh_bins; m++) { + // Take the inverse, but are careful not to divide by zero + if (new_bounds(e, m) != 0.0) { + new_bounds(e, m) = 1.0 / new_bounds(e, m); + } else { + new_bounds(e, m) = 0.0; + } + } + } - // divide by volume of mesh elements - for (int i = 0; i < group_view.size(); i++) { - group_view[i] /= mesh_vols[i]; + // Find the maximum value across all elements + double max_val = 0.0; +#pragma omp parallel for collapse(2) schedule(static) reduction(max : max_val) + for (int e = 0; e < e_bins; e++) { + for (int64_t m = 0; m < mesh_bins; m++) { + if (new_bounds(e, m) > max_val) { + max_val = new_bounds(e, m); + } } } - // We take the inverse, but are careful not to divide by zero e.g. if some - // mesh bins are not reachable in the physical geometry. - xt::noalias(new_bounds) = - xt::where(xt::not_equal(new_bounds, 0.0), 1.0 / new_bounds, 0.0); - auto max_val = xt::amax(new_bounds)(); - xt::noalias(new_bounds) = new_bounds / (2.0 * max_val); - - // For bins that were missed, we use the minimum weight window value. This - // shouldn't matter except for plotting. - auto min_val = xt::amin(new_bounds)(); - xt::noalias(new_bounds) = - xt::where(xt::not_equal(new_bounds, 0.0), new_bounds, min_val); + // Parallel normalization + if (max_val > 0.0) { + double norm_factor = 1.0 / (2.0 * max_val); +#pragma omp parallel for collapse(2) schedule(static) + for (int e = 0; e < e_bins; e++) { + for (int64_t m = 0; m < mesh_bins; m++) { + new_bounds(e, m) *= norm_factor; + } + } + } } - // make sure that values where the mean is zero are set s.t. the weight window - // value will be ignored - xt::filter(new_bounds, sum <= 0.0).fill(-1.0); - - // make sure the weight windows are ignored for any locations where the - // relative error is higher than the specified relative error threshold - xt::filter(new_bounds, rel_err > threshold).fill(-1.0); - - // update the bounds of this weight window class - // noalias avoids additional memory allocation - xt::noalias(upper_ww_) = ratio * lower_ww_; + // Final processing +#pragma omp parallel for collapse(2) schedule(static) + for (int e = 0; e < e_bins; e++) { + for (int64_t m = 0; m < mesh_bins; m++) { + // Values where the mean is zero should be ignored + if (sum(e, m) <= 0.0) { + new_bounds(e, m) = -1.0; + } + // Values where the relative error is higher than the threshold should be + // ignored + else if (rel_err(e, m) > threshold) { + new_bounds(e, m) = -1.0; + } + // Set the upper bounds + upper_ww_(e, m) = ratio * lower_ww_(e, m); + } + } } void WeightWindows::check_tally_update_compatibility(const Tally* tally) From a6db05ac8b5f250e41d858d1f803fae811bf125f Mon Sep 17 00:00:00 2001 From: John Tramm Date: Wed, 25 Jun 2025 08:47:23 -0500 Subject: [PATCH 055/159] Optimize Mapping of Random Ray Source Regions to Tallies (#3465) --- .../openmc/random_ray/flat_source_domain.h | 2 +- src/random_ray/flat_source_domain.cpp | 19 +++++++++++++------ src/random_ray/random_ray_simulation.cpp | 5 +++-- src/random_ray/source_region.cpp | 8 -------- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/include/openmc/random_ray/flat_source_domain.h b/include/openmc/random_ray/flat_source_domain.h index 9fdd5587508..78351fcc5f5 100644 --- a/include/openmc/random_ray/flat_source_domain.h +++ b/include/openmc/random_ray/flat_source_domain.h @@ -34,7 +34,7 @@ class FlatSourceDomain { int64_t add_source_to_scalar_flux(); virtual void batch_reset(); - void convert_source_regions_to_tallies(); + void convert_source_regions_to_tallies(int64_t start_sr_id); void reset_tally_volumes(); void random_ray_tally(); virtual void accumulate_iteration_flux(); diff --git a/src/random_ray/flat_source_domain.cpp b/src/random_ray/flat_source_domain.cpp index 7404a0b29b6..41e93ec945d 100644 --- a/src/random_ray/flat_source_domain.cpp +++ b/src/random_ray/flat_source_domain.cpp @@ -421,7 +421,12 @@ double FlatSourceDomain::compute_k_eff(double k_eff_old) const // be passed back to the caller to alert them that this function doesn't // need to be called for the remainder of the simulation. -void FlatSourceDomain::convert_source_regions_to_tallies() +// It takes as an argument the starting index in the source region array, +// and it will operate from that index until the end of the array. This +// is useful as it can be called for both explicit user source regions or +// when a source region mesh is overlaid. + +void FlatSourceDomain::convert_source_regions_to_tallies(int64_t start_sr_id) { openmc::simulation::time_tallies.start(); @@ -430,7 +435,7 @@ void FlatSourceDomain::convert_source_regions_to_tallies() // Attempt to generate mapping for all source regions #pragma omp parallel for - for (int64_t sr = 0; sr < n_source_regions(); sr++) { + for (int64_t sr = start_sr_id; sr < n_source_regions(); sr++) { // If this source region has not been hit by a ray yet, then // we aren't going to be able to map it, so skip it. @@ -468,7 +473,7 @@ void FlatSourceDomain::convert_source_regions_to_tallies() // Loop over all active tallies. This logic is essentially identical // to what happens when scanning for applicable tallies during // MC transport. - for (auto i_tally : model::active_tallies) { + for (int i_tally = 0; i_tally < model::tallies.size(); i_tally++) { Tally& tally {*model::tallies[i_tally]}; // Initialize an iterator over valid filter bin combinations. @@ -1525,6 +1530,9 @@ void FlatSourceDomain::finalize_discovered_source_regions() // order due to shared memory threading. std::sort(keys.begin(), keys.end()); + // Remember the index of the first new source region + int64_t start_sr_id = source_regions_.n_source_regions(); + // Append the source regions in the sorted key order. for (const auto& key : keys) { const SourceRegion& sr = discovered_source_regions_[key]; @@ -1532,9 +1540,8 @@ void FlatSourceDomain::finalize_discovered_source_regions() source_regions_.push_back(sr); } - // If any new source regions were discovered, we need to update the - // tally mapping between source regions and tally bins. - mapped_all_tallies_ = false; + // Map all new source regions to tallies + convert_source_regions_to_tallies(start_sr_id); } discovered_source_regions_.clear(); diff --git a/src/random_ray/random_ray_simulation.cpp b/src/random_ray/random_ray_simulation.cpp index 40a08c3af7a..388a778b840 100644 --- a/src/random_ray/random_ray_simulation.cpp +++ b/src/random_ray/random_ray_simulation.cpp @@ -508,8 +508,9 @@ void RandomRaySimulation::simulate() domain_->accumulate_iteration_flux(); // Generate mapping between source regions and tallies - if (!domain_->mapped_all_tallies_) { - domain_->convert_source_regions_to_tallies(); + if (!domain_->mapped_all_tallies_ && + !RandomRay::mesh_subdivision_enabled_) { + domain_->convert_source_regions_to_tallies(0); } // Use above mapping to contribute FSR flux data to appropriate diff --git a/src/random_ray/source_region.cpp b/src/random_ray/source_region.cpp index 69541f7f8ee..1205b995a6f 100644 --- a/src/random_ray/source_region.cpp +++ b/src/random_ray/source_region.cpp @@ -259,15 +259,11 @@ void SourceRegionContainer::adjoint_reset() MomentMatrix {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}); std::fill(mom_matrix_t_.begin(), mom_matrix_t_.end(), MomentMatrix {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}); - for (auto& task_set : volume_task_) { - task_set.clear(); - } std::fill(scalar_flux_old_.begin(), scalar_flux_old_.end(), 0.0); std::fill(scalar_flux_new_.begin(), scalar_flux_new_.end(), 0.0); std::fill(scalar_flux_final_.begin(), scalar_flux_final_.end(), 0.0); std::fill(source_.begin(), source_.end(), 0.0f); std::fill(external_source_.begin(), external_source_.end(), 0.0f); - std::fill(source_gradients_.begin(), source_gradients_.end(), MomentArray {0.0, 0.0, 0.0}); std::fill(flux_moments_old_.begin(), flux_moments_old_.end(), @@ -276,10 +272,6 @@ void SourceRegionContainer::adjoint_reset() MomentArray {0.0, 0.0, 0.0}); std::fill(flux_moments_t_.begin(), flux_moments_t_.end(), MomentArray {0.0, 0.0, 0.0}); - - for (auto& task_set : tally_task_) { - task_set.clear(); - } } } // namespace openmc From 01fa8056d130d0f0271d4c5c25867dae5f91b3c1 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 25 Jun 2025 13:44:53 -0500 Subject: [PATCH 056/159] Introduce WeightWindowsList class that enables export to HDF5 (#3456) --- docs/source/pythonapi/base.rst | 9 +- docs/source/usersguide/variance_reduction.rst | 2 +- openmc/settings.py | 14 +- openmc/weight_windows.py | 495 +++++++++++------- .../weightwindows/inputs_true.dat | 4 +- .../unit_tests/weightwindows/test_ww_list.py | 23 + 6 files changed, 337 insertions(+), 210 deletions(-) create mode 100644 tests/unit_tests/weightwindows/test_ww_list.py diff --git a/docs/source/pythonapi/base.rst b/docs/source/pythonapi/base.rst index d0e7d2439b8..2a9d0876cd2 100644 --- a/docs/source/pythonapi/base.rst +++ b/docs/source/pythonapi/base.rst @@ -37,7 +37,6 @@ Simulation Settings openmc.read_source_file openmc.write_source_file - openmc.wwinp_to_wws Material Specification ---------------------- @@ -259,8 +258,16 @@ Variance Reduction :template: myclass openmc.WeightWindows + openmc.WeightWindowsList openmc.WeightWindowGenerator + +.. autosummary:: + :toctree: generated + :nosignatures: + :template: myfunction.rst + openmc.hdf5_to_wws + openmc.wwinp_to_wws Coarse Mesh Finite Difference Acceleration diff --git a/docs/source/usersguide/variance_reduction.rst b/docs/source/usersguide/variance_reduction.rst index 3256fa35f36..369e33e2d80 100644 --- a/docs/source/usersguide/variance_reduction.rst +++ b/docs/source/usersguide/variance_reduction.rst @@ -162,7 +162,7 @@ solver, the Python input just needs to load the h5 file:: settings.weight_window_checkpoints = {'collision': True, 'surface': True} settings.survival_biasing = False - settings.weight_windows = openmc.hdf5_to_wws('weight_windows.h5') + settings.weight_windows = openmc.WeightWindowsList.from_hdf5('weight_windows.h5') settings.weight_windows_on = True The :class:`~openmc.WeightWindowGenerator` instance is not needed to load an diff --git a/openmc/settings.py b/openmc/settings.py index bd3a89e11cc..36eaec6cb45 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -16,7 +16,7 @@ from .source import SourceBase, MeshSource, IndependentSource from .utility_funcs import input_path from .volume import VolumeCalculation -from .weight_windows import WeightWindows, WeightWindowGenerator +from .weight_windows import WeightWindows, WeightWindowGenerator, WeightWindowsList class RunMode(Enum): @@ -313,7 +313,7 @@ class Settings: described in :ref:`verbosity`. volume_calculations : VolumeCalculation or iterable of VolumeCalculation Stochastic volume calculation specifications - weight_windows : WeightWindows or iterable of WeightWindows + weight_windows : WeightWindowsList Weight windows to use for variance reduction .. versionadded:: 0.13 @@ -424,7 +424,7 @@ def __init__(self, **kwargs): self._max_particles_in_flight = None self._max_particle_events = None self._write_initial_source = None - self._weight_windows = cv.CheckedList(WeightWindows, 'weight windows') + self._weight_windows = WeightWindowsList() self._weight_window_generators = cv.CheckedList(WeightWindowGenerator, 'weight window generators') self._weight_windows_on = None self._weight_windows_file = None @@ -1095,14 +1095,14 @@ def write_initial_source(self, value: bool): self._write_initial_source = value @property - def weight_windows(self) -> list[WeightWindows]: + def weight_windows(self) -> WeightWindowsList: return self._weight_windows @weight_windows.setter - def weight_windows(self, value: WeightWindows | Iterable[WeightWindows]): - if not isinstance(value, MutableSequence): + def weight_windows(self, value: WeightWindows | Sequence[WeightWindows]): + if not isinstance(value, Sequence): value = [value] - self._weight_windows = cv.CheckedList(WeightWindows, 'weight windows', value) + self._weight_windows = WeightWindowsList(value) @property def weight_windows_on(self) -> bool: diff --git a/openmc/weight_windows.py b/openmc/weight_windows.py index d52b81925aa..1b44747d574 100644 --- a/openmc/weight_windows.py +++ b/openmc/weight_windows.py @@ -1,6 +1,8 @@ from __future__ import annotations from numbers import Real, Integral from collections.abc import Iterable, Sequence +from pathlib import Path +from typing import Self import warnings import lxml.etree as ET @@ -14,6 +16,7 @@ from openmc.checkvalue import PathLike from ._xml import get_text, clean_indentation from .mixin import IDManagerMixin +from .utility_funcs import change_directory class WeightWindows(IDManagerMixin): @@ -90,7 +93,7 @@ class WeightWindows(IDManagerMixin): survival_ratio : float Ratio of the survival weight to the lower weight window bound for rouletting - max_lower_bound_ratio: float + max_lower_bound_ratio : float Maximum allowed ratio of a particle's weight to the weight window's lower bound. (Default: 1.0) max_split : int @@ -114,7 +117,7 @@ def __init__( upper_bound_ratio: float | None = None, energy_bounds: Iterable[Real] | None = None, particle_type: str = 'neutron', - survival_ratio: float = 3, + survival_ratio: float = 3.0, max_lower_bound_ratio: float | None = None, max_split: int = 10, weight_cutoff: float = 1.e-38, @@ -354,7 +357,7 @@ def to_xml_element(self) -> ET.Element: return element @classmethod - def from_xml_element(cls, elem: ET.Element, meshes: dict[int, MeshBase]) -> WeightWindows: + def from_xml_element(cls, elem: ET.Element, meshes: dict[int, MeshBase]) -> Self: """Generate weight window settings from an XML element Parameters @@ -408,7 +411,7 @@ def from_xml_element(cls, elem: ET.Element, meshes: dict[int, MeshBase]) -> Weig ) @classmethod - def from_hdf5(cls, group: h5py.Group, meshes: dict[int, MeshBase]) -> WeightWindows: + def from_hdf5(cls, group: h5py.Group, meshes: dict[int, MeshBase]) -> Self: """Create weight windows from HDF5 group Parameters @@ -458,7 +461,7 @@ def from_hdf5(cls, group: h5py.Group, meshes: dict[int, MeshBase]) -> WeightWind ) -def wwinp_to_wws(path: PathLike) -> list[WeightWindows]: +def wwinp_to_wws(path: PathLike) -> WeightWindowsList: """Create WeightWindows instances from a wwinp file .. versionadded:: 0.13.1 @@ -470,190 +473,13 @@ def wwinp_to_wws(path: PathLike) -> list[WeightWindows]: Returns ------- - list of openmc.WeightWindows + WeightWindowsList """ - - with open(path) as wwinp: - # BLOCK 1 - header = wwinp.readline().split(None, 4) - # read file type, time-dependence, number of - # particles, mesh type and problem identifier - _if, iv, ni, nr = [int(x) for x in header[:4]] - - # header value checks - if _if != 1: - raise ValueError(f'Found incorrect file type, if: {_if}') - - if iv > 1: - # read number of time bins for each particle, 'nt(1...ni)' - nt = np.fromstring(wwinp.readline(), sep=' ', dtype=int) - - # raise error if time bins are present for now - raise ValueError('Time-dependent weight windows ' - 'are not yet supported') - else: - nt = ni * [1] - - # read number of energy bins for each particle, 'ne(1...ni)' - ne = np.fromstring(wwinp.readline(), sep=' ', dtype=int) - - # read coarse mesh dimensions and lower left corner - mesh_description = np.fromstring(wwinp.readline(), sep=' ') - nfx, nfy, nfz = mesh_description[:3].astype(int) - xyz0 = mesh_description[3:] - - # read cylindrical and spherical mesh vectors if present - if nr == 16: - # read number of coarse bins - line_arr = np.fromstring(wwinp.readline(), sep=' ') - ncx, ncy, ncz = line_arr[:3].astype(int) - # read polar vector (x1, y1, z1) - xyz1 = line_arr[3:] - # read azimuthal vector (x2, y2, z2) - line_arr = np.fromstring(wwinp.readline(), sep=' ') - xyz2 = line_arr[:3] - - # Get polar and azimuthal axes - polar_axis = xyz1 - xyz0 - azimuthal_axis = xyz2 - xyz0 - - # Check for polar axis other than (0, 0, 1) - norm = np.linalg.norm(polar_axis) - if not np.isclose(polar_axis[2]/norm, 1.0): - raise NotImplementedError('Polar axis not aligned to z-axis not supported') - - # Check for azimuthal axis other than (1, 0, 0) - norm = np.linalg.norm(azimuthal_axis) - if not np.isclose(azimuthal_axis[0]/norm, 1.0): - raise NotImplementedError('Azimuthal axis not aligned to x-axis not supported') - - # read geometry type - nwg = int(line_arr[-1]) - - elif nr == 10: - # read rectilinear data: - # number of coarse mesh bins and mesh type - ncx, ncy, ncz, nwg = \ - np.fromstring(wwinp.readline(), sep=' ').astype(int) - else: - raise RuntimeError(f'Invalid mesh description (nr) found: {nr}') - - # read BLOCK 2 and BLOCK 3 data into a single array - ww_data = np.fromstring(wwinp.read(), sep=' ') - - # extract mesh data from the ww_data array - start_idx = 0 - - # first values in the mesh definition arrays are the first - # coordinate of the grid - end_idx = start_idx + 1 + 3 * ncx - i0, i_vals = ww_data[start_idx], ww_data[start_idx+1:end_idx] - start_idx = end_idx - - end_idx = start_idx + 1 + 3 * ncy - j0, j_vals = ww_data[start_idx], ww_data[start_idx+1:end_idx] - start_idx = end_idx - - end_idx = start_idx + 1 + 3 * ncz - k0, k_vals = ww_data[start_idx], ww_data[start_idx+1:end_idx] - start_idx = end_idx - - # mesh consistency checks - if nr == 16 and nwg == 1 or nr == 10 and nwg != 1: - raise ValueError(f'Mesh description in header ({nr}) ' - f'does not match the mesh type ({nwg})') - - if nr == 10 and (xyz0 != (i0, j0, k0)).any(): - raise ValueError(f'Mesh origin in the header ({xyz0}) ' - f' does not match the origin in the mesh ' - f' description ({i0, j0, k0})') - - # create openmc mesh object - grids = [] - mesh_definition = [(i0, i_vals, nfx), (j0, j_vals, nfy), (k0, k_vals, nfz)] - for grid0, grid_vals, n_pnts in mesh_definition: - # file spec checks for the mesh definition - if (grid_vals[2::3] != 1.0).any(): - raise ValueError('One or more mesh ratio value, qx, ' - 'is not equal to one') - - s = int(grid_vals[::3].sum()) - if s != n_pnts: - raise ValueError(f'Sum of the fine bin entries, {s}, does ' - f'not match the number of fine bins, {n_pnts}') - - # extend the grid based on the next coarse bin endpoint, px - # and the number of fine bins in the coarse bin, sx - intervals = grid_vals.reshape(-1, 3) - coords = [grid0] - for sx, px, qx in intervals: - coords += np.linspace(coords[-1], px, int(sx + 1)).tolist()[1:] - - grids.append(np.array(coords)) - - if nwg == 1: - mesh = RectilinearMesh() - mesh.x_grid, mesh.y_grid, mesh.z_grid = grids - elif nwg == 2: - mesh = CylindricalMesh( - r_grid=grids[0], - z_grid=grids[1], - phi_grid=grids[2], - origin = xyz0, - ) - elif nwg == 3: - mesh = SphericalMesh( - r_grid=grids[0], - theta_grid=grids[1], - phi_grid=grids[2], - origin = xyz0 - ) - - # extract weight window values from array - wws = [] - for ne_i, nt_i, particle_type in zip(ne, nt, ('neutron', 'photon')): - # no information to read for this particle if - # either the energy bins or time bins are empty - if ne_i == 0 or nt_i == 0: - continue - - if iv > 1: - # time bins are parsed but unused for now - end_idx = start_idx + nt_i - time_bounds = ww_data[start_idx:end_idx] - np.insert(time_bounds, (0,), (0.0,)) - start_idx = end_idx - - # read energy boundaries - end_idx = start_idx + ne_i - energy_bounds = np.insert(ww_data[start_idx:end_idx], (0,), (0.0,)) - # convert from MeV to eV - energy_bounds *= 1e6 - start_idx = end_idx - - # read weight window values - end_idx = start_idx + (nfx * nfy * nfz) * nt_i * ne_i - - # read values and reshape according to ordering - # slowest to fastest: t, e, z, y, x - # reorder with transpose since our ordering is x, y, z, e, t - ww_shape = (nt_i, ne_i, nfz, nfy, nfx) - ww_values = ww_data[start_idx:end_idx].reshape(ww_shape).T - # Only use first time bin since we don't support time dependent weight - # windows yet. - ww_values = ww_values[:, :, :, :, 0] - start_idx = end_idx - - # create a weight window object - ww = WeightWindows(id=None, - mesh=mesh, - lower_ww_bounds=ww_values, - upper_bound_ratio=5.0, - energy_bounds=energy_bounds, - particle_type=particle_type) - wws.append(ww) - - return wws + warnings.warn( + "This function is deprecated in favor of 'WeightWindowsList.from_wwinp'", + FutureWarning + ) + return WeightWindowsList.from_wwinp(path) class WeightWindowGenerator: @@ -886,7 +712,7 @@ def to_xml_element(self): return element @classmethod - def from_xml_element(cls, elem: ET.Element, meshes: dict) -> WeightWindowGenerator: + def from_xml_element(cls, elem: ET.Element, meshes: dict) -> Self: """ Create a weight window generation object from an XML element @@ -929,8 +755,8 @@ def from_xml_element(cls, elem: ET.Element, meshes: dict) -> WeightWindowGenerat return wwg -def hdf5_to_wws(path='weight_windows.h5'): - """Create WeightWindows instances from a weight windows HDF5 file +def hdf5_to_wws(path='weight_windows.h5') -> WeightWindowsList: + """Create a WeightWindowsList from a weight windows HDF5 file .. versionadded:: 0.14.0 @@ -941,13 +767,284 @@ def hdf5_to_wws(path='weight_windows.h5'): Returns ------- - list of openmc.WeightWindows + WeightWindowsList + """ + warnings.warn( + "This function is deprecated in favor of 'WeightWindowsList.from_hdf5'", + FutureWarning + ) + return WeightWindowsList.from_hdf5(path) + + +class WeightWindowsList(list): + """A list of WeightWindows objects. + + .. versionadded:: 0.15.3 + + Parameters + ---------- + iterable : iterable of openmc.WeightWindows + An iterable of WeightWindows objects to initialize the list with + """ + def __init__(self, iterable: Iterable[WeightWindows] = ()): + super().__init__(iterable) + + @classmethod + def from_hdf5(cls, path: PathLike = 'weight_windows.h5') -> Self: + """Create WeightWindowsList from a weight windows HDF5 file. + + Parameters + ---------- + path : PathLike + Path to the weight windows hdf5 file - with h5py.File(path) as h5_file: - # read in all of the meshes in the mesh node - meshes = {} - for mesh_group in h5_file['meshes']: - mesh = MeshBase.from_hdf5(h5_file['meshes'][mesh_group]) - meshes[mesh.id] = mesh - return [WeightWindows.from_hdf5(ww, meshes) for ww in h5_file['weight_windows'].values()] + Returns + ------- + WeightWindowsList + A list of WeightWindows objects read from the file + """ + + with h5py.File(path) as h5_file: + # read in all of the meshes in the mesh node + meshes = {} + for mesh_group in h5_file['meshes']: + mesh = MeshBase.from_hdf5(h5_file['meshes'][mesh_group]) + meshes[mesh.id] = mesh + wws = [ + WeightWindows.from_hdf5(ww, meshes) + for ww in h5_file['weight_windows'].values() + ] + + return cls(wws) + + @classmethod + def from_wwinp(cls, path: PathLike) -> Self: + """Create WeightWindowsList from a wwinp file. + + Parameters + ---------- + path : PathLike + Path to the wwinp file + + Returns + ------- + WeightWindowsList + A list of WeightWindows objects read from the file + """ + + with open(path) as wwinp: + # BLOCK 1 + header = wwinp.readline().split(None, 4) + # read file type, time-dependence, number of + # particles, mesh type and problem identifier + _if, iv, ni, nr = [int(x) for x in header[:4]] + + # header value checks + if _if != 1: + raise ValueError(f'Found incorrect file type, if: {_if}') + + if iv > 1: + # read number of time bins for each particle, 'nt(1...ni)' + nt = np.fromstring(wwinp.readline(), sep=' ', dtype=int) + + # raise error if time bins are present for now + raise ValueError('Time-dependent weight windows ' + 'are not yet supported') + else: + nt = ni * [1] + + # read number of energy bins for each particle, 'ne(1...ni)' + ne = np.fromstring(wwinp.readline(), sep=' ', dtype=int) + + # read coarse mesh dimensions and lower left corner + mesh_description = np.fromstring(wwinp.readline(), sep=' ') + nfx, nfy, nfz = mesh_description[:3].astype(int) + xyz0 = mesh_description[3:] + + # read cylindrical and spherical mesh vectors if present + if nr == 16: + # read number of coarse bins + line_arr = np.fromstring(wwinp.readline(), sep=' ') + ncx, ncy, ncz = line_arr[:3].astype(int) + # read polar vector (x1, y1, z1) + xyz1 = line_arr[3:] + # read azimuthal vector (x2, y2, z2) + line_arr = np.fromstring(wwinp.readline(), sep=' ') + xyz2 = line_arr[:3] + + # Get polar and azimuthal axes + polar_axis = xyz1 - xyz0 + azimuthal_axis = xyz2 - xyz0 + + # Check for polar axis other than (0, 0, 1) + norm = np.linalg.norm(polar_axis) + if not np.isclose(polar_axis[2]/norm, 1.0): + raise NotImplementedError('Polar axis not aligned to z-axis not supported') + + # Check for azimuthal axis other than (1, 0, 0) + norm = np.linalg.norm(azimuthal_axis) + if not np.isclose(azimuthal_axis[0]/norm, 1.0): + raise NotImplementedError('Azimuthal axis not aligned to x-axis not supported') + + # read geometry type + nwg = int(line_arr[-1]) + + elif nr == 10: + # read rectilinear data: + # number of coarse mesh bins and mesh type + ncx, ncy, ncz, nwg = \ + np.fromstring(wwinp.readline(), sep=' ').astype(int) + else: + raise RuntimeError(f'Invalid mesh description (nr) found: {nr}') + + # read BLOCK 2 and BLOCK 3 data into a single array + ww_data = np.fromstring(wwinp.read(), sep=' ') + + # extract mesh data from the ww_data array + start_idx = 0 + + # first values in the mesh definition arrays are the first + # coordinate of the grid + end_idx = start_idx + 1 + 3 * ncx + i0, i_vals = ww_data[start_idx], ww_data[start_idx+1:end_idx] + start_idx = end_idx + + end_idx = start_idx + 1 + 3 * ncy + j0, j_vals = ww_data[start_idx], ww_data[start_idx+1:end_idx] + start_idx = end_idx + + end_idx = start_idx + 1 + 3 * ncz + k0, k_vals = ww_data[start_idx], ww_data[start_idx+1:end_idx] + start_idx = end_idx + + # mesh consistency checks + if nr == 16 and nwg == 1 or nr == 10 and nwg != 1: + raise ValueError(f'Mesh description in header ({nr}) ' + f'does not match the mesh type ({nwg})') + + if nr == 10 and (xyz0 != (i0, j0, k0)).any(): + raise ValueError(f'Mesh origin in the header ({xyz0}) ' + f' does not match the origin in the mesh ' + f' description ({i0, j0, k0})') + + # create openmc mesh object + grids = [] + mesh_definition = [(i0, i_vals, nfx), (j0, j_vals, nfy), (k0, k_vals, nfz)] + for grid0, grid_vals, n_pnts in mesh_definition: + # file spec checks for the mesh definition + if (grid_vals[2::3] != 1.0).any(): + raise ValueError('One or more mesh ratio value, qx, ' + 'is not equal to one') + + s = int(grid_vals[::3].sum()) + if s != n_pnts: + raise ValueError(f'Sum of the fine bin entries, {s}, does ' + f'not match the number of fine bins, {n_pnts}') + + # extend the grid based on the next coarse bin endpoint, px + # and the number of fine bins in the coarse bin, sx + intervals = grid_vals.reshape(-1, 3) + coords = [grid0] + for sx, px, qx in intervals: + coords += np.linspace(coords[-1], px, int(sx + 1)).tolist()[1:] + + grids.append(np.array(coords)) + + if nwg == 1: + mesh = RectilinearMesh() + mesh.x_grid, mesh.y_grid, mesh.z_grid = grids + elif nwg == 2: + mesh = CylindricalMesh( + r_grid=grids[0], + z_grid=grids[1], + phi_grid=grids[2], + origin = xyz0, + ) + elif nwg == 3: + mesh = SphericalMesh( + r_grid=grids[0], + theta_grid=grids[1], + phi_grid=grids[2], + origin = xyz0 + ) + + # extract weight window values from array + wws = cls() + for ne_i, nt_i, particle_type in zip(ne, nt, ('neutron', 'photon')): + # no information to read for this particle if + # either the energy bins or time bins are empty + if ne_i == 0 or nt_i == 0: + continue + + if iv > 1: + # time bins are parsed but unused for now + end_idx = start_idx + nt_i + time_bounds = ww_data[start_idx:end_idx] + np.insert(time_bounds, (0,), (0.0,)) + start_idx = end_idx + + # read energy boundaries + end_idx = start_idx + ne_i + energy_bounds = np.insert(ww_data[start_idx:end_idx], (0,), (0.0,)) + # convert from MeV to eV + energy_bounds *= 1e6 + start_idx = end_idx + + # read weight window values + end_idx = start_idx + (nfx * nfy * nfz) * nt_i * ne_i + + # read values and reshape according to ordering + # slowest to fastest: t, e, z, y, x + # reorder with transpose since our ordering is x, y, z, e, t + ww_shape = (nt_i, ne_i, nfz, nfy, nfx) + ww_values = ww_data[start_idx:end_idx].reshape(ww_shape).T + # Only use first time bin since we don't support time dependent weight + # windows yet. + ww_values = ww_values[:, :, :, :, 0] + start_idx = end_idx + + # create a weight window object + ww = WeightWindows(id=None, + mesh=mesh, + lower_ww_bounds=ww_values, + upper_bound_ratio=5.0, + energy_bounds=energy_bounds, + particle_type=particle_type) + wws.append(ww) + + return wws + + def export_to_hdf5(self, path: PathLike = 'weight_windows.h5', **init_kwargs): + """Write weight windows to an HDF5 file. + + Parameters + ---------- + path : PathLike + Path to the file to write weight windows to + **init_kwargs + Keyword arguments passed to :func:`openmc.lib.init` + + """ + import openmc.lib + cv.check_type('path', path, PathLike) + + # Create a temporary model with the weight windows + model = openmc.Model() + sph = openmc.Sphere(boundary_type='vacuum') + cell = openmc.Cell(region=-sph) + model.geometry = openmc.Geometry([cell]) + model.settings.weight_windows = self + model.settings.particles = 100 + model.settings.batches = 1 + + # Get absolute path before moving to temporary directory + path = Path(path).resolve() + + with change_directory(tmpdir=True): + # Write the model to an XML file + model.export_to_model_xml() + + # Load the model with openmc.lib and then export it to an HDF5 file + with openmc.lib.run_in_memory(**init_kwargs): + openmc.lib.export_weight_windows(path) diff --git a/tests/regression_tests/weightwindows/inputs_true.dat b/tests/regression_tests/weightwindows/inputs_true.dat index dcc41ae8417..91fec4af92d 100644 --- a/tests/regression_tests/weightwindows/inputs_true.dat +++ b/tests/regression_tests/weightwindows/inputs_true.dat @@ -49,7 +49,7 @@ 0.0 0.5 20000000.0 -1.0 0.007673145137236567 6.542700644645627e-07 0.0017541380096893788 0.0007245451610090619 7.476545089278482e-06 2.253382683081525e-06 2.1040609012865134e-05 0.08285899874014539 -1.0 1.0262431550731535e-13 3.3164825349503764e-16 0.0064803314120165335 3.5981031766416143e-06 -1.0 1.6205064883026195e-11 1.7770136411255912e-05 0.0001670700632870335 4.323138781337963e-05 -1.0 -1.0 6.922231352729155e-05 0.06550426960512012 -1.0 2.5639656866250453e-06 1.4508262073256659e-13 -1.0 -1.0 0.0009127050763987511 3.8523247550378815e-12 -1.0 0.0008175033526488423 -1.0 0.015148978428271613 2.5157355790201773e-07 2.1612150425221703e-06 1.3181562352445092e-10 2.9282097947816224e-05 2.762955748645507e-05 2.936268747135548e-14 1.6859219614058024e-19 3.937160538176268e-07 2.6011787393916806e-06 0.001570829313580841 -1.0 1.7041669224797384e-09 1.6421491993751715e-11 0.4899999304038166 6.948292975998466e-07 -1.0 -1.0 0.08657573566388353 0.006639379668946672 1.681305446488884e-11 2.2487617828938326e-05 4.109066205029878e-05 0.08288806458368345 -1.0 0.007385384405891666 -1.0 -1.0 1.008394964750947e-06 6.655926357459275e-08 0.0016279794552427574 1.4825219199133353e-12 -1.0 1.93847081892941e-05 1.284759166582202e-05 5.1161740516205715e-05 2.5447281241730366e-07 3.853097455417877e-06 -1.0 8.887234042637684e-05 0.01621821782442112 -1.0 1.8747824667478637e-14 5.97653814110781e-06 -1.0 -1.0 1.2296747260635405e-06 -1.0 -1.0 7.754700849337902e-06 7.091263906557291e-05 3.2799045883372075e-06 0.00020573181450240786 5.6949597066784976e-05 1.271128669411295e-07 0.0010019432401508087 -1.0 -1.0 6.848642863465585e-07 5.783974359937857e-06 2.380955631371226e-06 4.756872959630257e-05 2.1360401784344975e-18 0.00046029617115127556 1.0092905083158804e-11 0.08095280568305474 3.89841667138598e-08 4.354946981329799e-05 -1.0 0.5 0.0015447823995470042 -1.0 -1.0 0.001959411999677113 0.48573842213878143 -1.0 7.962899789920809e-06 5.619835883512353e-14 0.0835648710074336 2.7207433165796702e-11 0.00027464244062887683 4.076762579823379e-17 2.124999182004655e-05 8.037131813528501e-07 8.846493399850663e-06 1.080711295768551e-05 -1.0 -1.0 0.0010737081832502356 2.679340942769232e-06 2.1648918040467363e-05 0.0002668579837809081 1.3455341306162382e-05 2.5629068330446215e-05 1.3228013765525015e-05 -1.0 -1.0 7.002861620919232e-08 1.5172912057312398e-14 -1.0 7.989579285134734e-06 2.639268952045579e-10 -1.0 0.013330619853379314 0.00021534568699157805 2.4469456008493614e-09 8.211982683649991e-09 1.951356547084204e-15 0.00016419031150495913 5.985108617124989e-06 1.28584557092134e-05 1.5476769237571295e-14 5.5379154134462336e-08 0.0016637126543321873 2.499931357628383e-07 8.020767115181574e-07 2.7697233171399416e-13 -1.0 0.007197662162700777 -1.0 0.0833931797381589 2.8226276944532696e-05 3.353295403842037e-05 5.504813693036933e-12 0.007136750029575816 0.08636569925236791 -1.0 -1.0 6.315154082213375e-07 0.4877856021170283 -1.0 3.7051518220083886e-06 -1.0 0.0017950613169359904 7.84289689494925e-06 9.041149893307806e-07 -1.0 8.48176145671274e-10 9.03108551826469e-05 3.634644995026692e-05 2.0295213621716706e-09 1.0305672698745657e-11 1.5550455959178486e-06 0.01664109232689093 8.533030345699353e-17 0.0009252532821720597 -1.0 7.695031155172816e-14 0.0007151459162818186 -1.0 -1.0 5.182269672663266e-10 3.0160758454323657e-07 -1.0 0.06539005235506369 3.452537179033895e-05 -1.0 -1.0 3.855884234344845e-06 0.00017699858357744463 2.594951085377588e-06 1.1333627604823673e-09 -1.0 2.4306375693722205e-06 0.0063790654507599135 2.2060992580622125e-11 -1.0 -1.0 0.0848872967381878 3.932389299316285e-06 1.2911827198500372e-08 1.957092814065688e-05 0.0007213722022489493 0.0018327593437608 1.6699254277734109e-06 0.007188410694198128 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -10.0 0.07673145137236567 6.5427006446456266e-06 0.017541380096893787 0.0072454516100906195 7.476545089278483e-05 2.253382683081525e-05 0.00021040609012865135 0.828589987401454 -10.0 1.0262431550731535e-12 3.3164825349503764e-15 0.06480331412016534 3.5981031766416144e-05 -10.0 1.6205064883026195e-10 0.0001777013641125591 0.001670700632870335 0.0004323138781337963 -10.0 -10.0 0.0006922231352729155 0.6550426960512012 -10.0 2.5639656866250452e-05 1.4508262073256658e-12 -10.0 -10.0 0.009127050763987512 3.852324755037882e-11 -10.0 0.008175033526488424 -10.0 0.15148978428271614 2.515735579020177e-06 2.1612150425221703e-05 1.3181562352445092e-09 0.00029282097947816227 0.0002762955748645507 2.9362687471355483e-13 1.6859219614058023e-18 3.937160538176268e-06 2.6011787393916804e-05 0.015708293135808408 -10.0 1.7041669224797384e-08 1.6421491993751715e-10 4.899999304038166 6.948292975998466e-06 -10.0 -10.0 0.8657573566388354 0.06639379668946672 1.681305446488884e-10 0.00022487617828938325 0.0004109066205029878 0.8288806458368345 -10.0 0.07385384405891665 -10.0 -10.0 1.0083949647509469e-05 6.655926357459275e-07 0.016279794552427576 1.4825219199133352e-11 -10.0 0.000193847081892941 0.0001284759166582202 0.0005116174051620571 2.5447281241730365e-06 3.853097455417877e-05 -10.0 0.0008887234042637684 0.16218217824421122 -10.0 1.8747824667478637e-13 5.97653814110781e-05 -10.0 -10.0 1.2296747260635405e-05 -10.0 -10.0 7.754700849337902e-05 0.0007091263906557291 3.2799045883372076e-05 0.0020573181450240785 0.0005694959706678497 1.271128669411295e-06 0.010019432401508087 -10.0 -10.0 6.848642863465585e-06 5.7839743599378564e-05 2.380955631371226e-05 0.0004756872959630257 2.1360401784344976e-17 0.004602961711512756 1.0092905083158804e-10 0.8095280568305474 3.89841667138598e-07 0.00043549469813297995 -10.0 5.0 0.015447823995470043 -10.0 -10.0 0.01959411999677113 4.8573842213878144 -10.0 7.96289978992081e-05 5.619835883512353e-13 0.8356487100743359 2.7207433165796704e-10 0.002746424406288768 4.076762579823379e-16 0.00021249991820046548 8.037131813528501e-06 8.846493399850663e-05 0.0001080711295768551 -10.0 -10.0 0.010737081832502356 2.679340942769232e-05 0.00021648918040467362 0.0026685798378090807 0.00013455341306162382 0.00025629068330446217 0.00013228013765525016 -10.0 -10.0 7.002861620919232e-07 1.51729120573124e-13 -10.0 7.989579285134734e-05 2.639268952045579e-09 -10.0 0.13330619853379314 0.0021534568699157807 2.4469456008493615e-08 8.211982683649991e-08 1.951356547084204e-14 0.0016419031150495913 5.985108617124989e-05 0.000128584557092134 1.5476769237571296e-13 5.537915413446234e-07 0.016637126543321872 2.499931357628383e-06 8.020767115181574e-06 2.7697233171399415e-12 -10.0 0.07197662162700777 -10.0 0.8339317973815891 0.000282262769445327 0.00033532954038420373 5.504813693036933e-11 0.07136750029575815 0.8636569925236791 -10.0 -10.0 6.3151540822133754e-06 4.877856021170283 -10.0 3.7051518220083885e-05 -10.0 0.017950613169359905 7.84289689494925e-05 9.041149893307805e-06 -10.0 8.48176145671274e-09 0.000903108551826469 0.0003634644995026692 2.0295213621716707e-08 1.0305672698745658e-10 1.5550455959178486e-05 0.1664109232689093 8.533030345699353e-16 0.009252532821720597 -10.0 7.695031155172816e-13 0.007151459162818186 -10.0 -10.0 5.182269672663266e-09 3.0160758454323656e-06 -10.0 0.6539005235506369 0.0003452537179033895 -10.0 -10.0 3.855884234344845e-05 0.0017699858357744464 2.594951085377588e-05 1.1333627604823672e-08 -10.0 2.4306375693722206e-05 0.06379065450759913 2.2060992580622125e-10 -10.0 -10.0 0.848872967381878 3.9323892993162844e-05 1.2911827198500372e-07 0.0001957092814065688 0.0072137220224894934 0.018327593437608 1.669925427773411e-05 0.07188410694198127 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 - 3 + 3.0 1.5 10 1e-38 @@ -65,7 +65,7 @@ 0.0 0.5 20000000.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 0.06827568359008944 0.00017329528628152923 0.018541112907970003 0.011113024641218336 8.76041935303527e-05 8.168081568291806e-05 0.00046693784586018913 0.4952432605379829 2.2512327083641865e-10 4.6258592060517866e-07 2.7044817176109556e-05 0.054902816619122045 0.00014126153603265097 8.304845400451923e-08 2.2950716170641463e-05 0.0001335621430311088 0.003432919201293737 0.0005197081842709918 5.726152701849347e-16 -1.0 0.0013332554437480327 0.4346984808472855 7.691527781829036e-17 0.00011042047857307253 3.0571050855707084e-06 -1.0 4.986698316296507e-06 0.011128424196387618 1.3106335518133802e-05 5.447493368067179e-10 0.011440381012864086 4.670673837764874e-08 0.12364069326350118 0.00012558879474108074 3.136829920007009e-05 2.34750482787598e-05 0.001537590300149361 0.0017693279949094218 6.717625881826167e-05 4.587143797469485e-08 2.6211239246796104e-05 0.00030002326388383244 0.01729816946709781 4.5757238372058234e-11 6.92296299242814e-05 3.792209399652396e-06 -1.0 9.256616392487858e-05 8.541606727879512e-07 -1.0 0.49317081643076643 0.05500108683629957 1.298062961617927e-05 0.0006870228272606287 0.0008231482263927564 0.5 3.419181071650816e-06 0.06433819434431046 6.574706543646946e-16 4.0404626147344e-13 0.0003782322077390033 0.00012637534695610975 0.017602971074583987 1.0044390728858277e-05 -1.0 0.0008787411098754915 0.00023686818807101741 0.0009304497896205121 6.949587369646497e-05 0.00012918433677593975 2.0707648529746894e-06 0.0023295401596005313 0.11819165666519223 -1.0 2.1606550732170693e-05 0.00022057970761624768 -1.0 1.0325207105027347e-08 0.0002029396545382813 2.1234980618763222e-13 -1.0 0.00024175998138228048 0.001178616843705956 0.0001399807456616496 0.0038811025614225057 0.0011076796092102186 0.00010814452764860558 0.010955637485319249 -1.0 9.382800796426366e-17 5.098747893974248e-05 0.00027052054298385255 0.00021305826462238593 0.000808100901275472 4.233410073487772e-06 0.0051513292132259495 8.616027294555462e-07 0.4873897581604287 2.9093345975075226e-05 0.000469589568463265 -1.0 -1.0 0.017802624844035688 1.3116807133030035e-15 2.617878055106949e-16 0.01884234453311008 -1.0 2.027381220364608e-16 0.0004299195570308035 1.4628847959717225e-05 0.4899952354672477 2.774067049392004e-07 0.004441328419270369 4.279460329782799e-09 0.0012297303081805393 0.0002792896409558745 0.0006934466049804711 8.370896020975689e-05 3.8270536025799805e-15 -1.0 0.01208261392046878 0.0001497958445994315 0.0015016500045374082 0.005384625561469684 0.00025519632243117644 0.0011028020390507682 0.0002362355168421952 -1.0 -1.0 8.862760180332873e-05 1.3806577785135177e-06 -1.0 0.0001319677450153934 2.6866894852481906e-05 -1.0 0.11510120013926417 0.002672923773363389 1.2257360246712472e-05 8.544159282151438e-05 2.777791741571174e-05 0.0014957033281168012 0.0003257646134837451 0.0008919466185572302 1.2907171695294846e-13 4.6842350882367666e-05 0.01721806295736377 0.00025106741008066317 0.00022234471595443313 2.101790478097517e-09 5.752120832330886e-11 0.06428782790571179 3.2478301680021854e-08 0.4903495510314233 0.001146714816952061 0.0004777967757842694 1.0882011018684852e-06 0.05708498338352203 0.4981497862127628 -1.0 3.2790914744011534e-06 0.00010077009726691265 -1.0 2.210707325798542e-07 4.3166052799897375e-05 -1.0 0.01825795827020865 0.0003182108781955478 3.8561696337086434e-05 1.0995322456277382e-06 9.53380658669839e-06 0.001550036616828061 0.0012648333883989995 5.272021975060514e-06 1.9257248009114363e-05 0.00016757997590048827 0.11891251244906934 5.515522196186723e-06 0.01102706140784316 9.904481358675478e-13 3.520674728719278e-05 0.011294708566804167 7.239087311622819e-08 -1.0 3.5740607788414942e-06 0.00035983547999740665 1.0588257623722672e-07 0.42372713683725016 0.0011273340972061078 8.582011716859219e-15 1.1514579117272292e-10 0.00043130977711323417 0.0027847329307350423 0.00017287873387910357 6.401055165502384e-06 1.722191123879782e-08 0.00020848309220162036 0.05639071613735579 1.8064550990294753e-05 1.289814511028015e-06 1.078687446476881e-14 0.4897735047310072 0.0002981891705231209 0.00016079768514218546 9.332371598523186e-05 0.011724714376848187 0.019040707071165303 0.00014242053195529864 0.06684484191345574 -1.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 -10.0 0.6827568359008944 0.0017329528628152924 0.18541112907970003 0.11113024641218336 0.0008760419353035269 0.0008168081568291806 0.004669378458601891 4.952432605379829 2.2512327083641864e-09 4.625859206051786e-06 0.00027044817176109554 0.5490281661912204 0.0014126153603265096 8.304845400451923e-07 0.00022950716170641464 0.001335621430311088 0.03432919201293737 0.0051970818427099184 5.726152701849347e-15 -10.0 0.013332554437480326 4.346984808472855 7.6915277818290365e-16 0.0011042047857307254 3.0571050855707086e-05 -10.0 4.986698316296507e-05 0.11128424196387618 0.000131063355181338 5.447493368067179e-09 0.11440381012864086 4.670673837764874e-07 1.2364069326350118 0.0012558879474108074 0.0003136829920007009 0.000234750482787598 0.01537590300149361 0.01769327994909422 0.0006717625881826168 4.587143797469485e-07 0.000262112392467961 0.0030002326388383245 0.1729816946709781 4.5757238372058235e-10 0.0006922962992428139 3.792209399652396e-05 -10.0 0.0009256616392487858 8.541606727879512e-06 -10.0 4.931708164307665 0.5500108683629957 0.00012980629616179268 0.006870228272606287 0.008231482263927564 5.0 3.419181071650816e-05 0.6433819434431045 6.574706543646946e-15 4.0404626147344005e-12 0.003782322077390033 0.0012637534695610975 0.17602971074583987 0.00010044390728858278 -10.0 0.008787411098754914 0.002368681880710174 0.00930449789620512 0.0006949587369646498 0.0012918433677593976 2.0707648529746893e-05 0.023295401596005315 1.1819165666519222 -10.0 0.00021606550732170693 0.0022057970761624767 -10.0 1.0325207105027347e-07 0.0020293965453828133 2.123498061876322e-12 -10.0 0.0024175998138228046 0.01178616843705956 0.0013998074566164962 0.03881102561422506 0.011076796092102187 0.0010814452764860557 0.10955637485319249 -10.0 9.382800796426367e-16 0.0005098747893974248 0.0027052054298385255 0.0021305826462238594 0.00808100901275472 4.233410073487772e-05 0.0515132921322595 8.61602729455546e-06 4.873897581604287 0.0002909334597507523 0.0046958956846326495 -10.0 -10.0 0.1780262484403569 1.3116807133030034e-14 2.6178780551069492e-15 0.1884234453311008 -10.0 2.027381220364608e-15 0.004299195570308035 0.00014628847959717226 4.899952354672477 2.774067049392004e-06 0.04441328419270369 4.279460329782799e-08 0.012297303081805393 0.002792896409558745 0.006934466049804711 0.0008370896020975689 3.8270536025799805e-14 -10.0 0.1208261392046878 0.001497958445994315 0.015016500045374082 0.05384625561469684 0.0025519632243117644 0.011028020390507681 0.002362355168421952 -10.0 -10.0 0.0008862760180332873 1.3806577785135176e-05 -10.0 0.001319677450153934 0.00026866894852481903 -10.0 1.1510120013926417 0.026729237733633893 0.00012257360246712473 0.0008544159282151438 0.0002777791741571174 0.014957033281168012 0.0032576461348374514 0.008919466185572301 1.2907171695294846e-12 0.00046842350882367664 0.17218062957363772 0.0025106741008066318 0.0022234471595443312 2.101790478097517e-08 5.752120832330886e-10 0.6428782790571179 3.2478301680021856e-07 4.903495510314233 0.01146714816952061 0.004777967757842694 1.0882011018684853e-05 0.5708498338352204 4.981497862127628 -10.0 3.279091474401153e-05 0.0010077009726691265 -10.0 2.210707325798542e-06 0.00043166052799897375 -10.0 0.1825795827020865 0.0031821087819554777 0.00038561696337086434 1.0995322456277382e-05 9.53380658669839e-05 0.01550036616828061 0.012648333883989995 5.272021975060514e-05 0.00019257248009114364 0.0016757997590048828 1.1891251244906933 5.515522196186723e-05 0.1102706140784316 9.904481358675478e-12 0.0003520674728719278 0.11294708566804167 7.239087311622819e-07 -10.0 3.574060778841494e-05 0.0035983547999740664 1.0588257623722672e-06 4.237271368372502 0.011273340972061077 8.582011716859219e-14 1.151457911727229e-09 0.004313097771132342 0.027847329307350423 0.0017287873387910357 6.401055165502385e-05 1.722191123879782e-07 0.0020848309220162036 0.5639071613735579 0.00018064550990294753 1.289814511028015e-05 1.078687446476881e-13 4.897735047310072 0.002981891705231209 0.0016079768514218546 0.0009332371598523186 0.11724714376848187 0.19040707071165303 0.0014242053195529865 0.6684484191345574 -10.0 - 3 + 3.0 1.5 10 1e-38 diff --git a/tests/unit_tests/weightwindows/test_ww_list.py b/tests/unit_tests/weightwindows/test_ww_list.py new file mode 100644 index 00000000000..d148f382a53 --- /dev/null +++ b/tests/unit_tests/weightwindows/test_ww_list.py @@ -0,0 +1,23 @@ +import openmc + + +def test_ww_roundtrip(request, run_in_tmpdir): + # Load weight windows from a wwinp file + wwinp_file = request.path.with_name('wwinp_n') + wws = openmc.WeightWindowsList.from_wwinp(wwinp_file) + + # Roundtrip them, writing to HDF5 and reading back in + wws.export_to_hdf5('ww.h5') + wws_new = openmc.WeightWindowsList.from_hdf5('ww.h5') + + # Check that the new weight windows are the same as the original + assert len(wws) == len(wws_new) + for ww, ww_new in zip(wws, wws_new): + assert ww.particle_type == ww_new.particle_type + assert (ww.lower_ww_bounds == ww_new.lower_ww_bounds).all() + assert (ww.upper_ww_bounds == ww_new.upper_ww_bounds).all() + assert ww.survival_ratio == ww_new.survival_ratio + assert ww.num_energy_bins == ww_new.num_energy_bins + assert ww.max_split == ww_new.max_split + assert ww.weight_cutoff == ww_new.weight_cutoff + assert ww.mesh.id == ww_new.mesh.id From 5c10214465107277b4f046b58ff9d00e34ba4ce4 Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Thu, 26 Jun 2025 03:29:45 +0200 Subject: [PATCH 057/159] added openmc.Material.mean_free_path function (#3469) --- openmc/material.py | 30 ++++++++++++++++++++++++++++++ tests/unit_tests/test_material.py | 13 +++++++++++++ 2 files changed, 43 insertions(+) diff --git a/openmc/material.py b/openmc/material.py index 770722f748a..c6765178e9d 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -1718,6 +1718,36 @@ def from_xml_element(cls, elem: ET.Element) -> Material: return mat + def mean_free_path(self, energy: float) -> float: + """Calculate the mean free path of neutrons in the material at a given + energy. + + .. versionadded:: 0.15.3 + + Parameters + ---------- + energy : float + Neutron energy in eV + + Returns + ------- + float + Mean free path in cm + + """ + from openmc.plotter import _calculate_cexs_elem_mat + + energy_grid, cexs = _calculate_cexs_elem_mat( + this=self, + types=["total"], + ) + total_cexs = cexs[0] + + interpolated_cexs = float(np.interp(energy, energy_grid, total_cexs)) + + return 1.0 / interpolated_cexs + + class Materials(cv.CheckedList): """Collection of Materials used for an OpenMC simulation. diff --git a/tests/unit_tests/test_material.py b/tests/unit_tests/test_material.py index 8ad57956381..db5f4ce32d5 100644 --- a/tests/unit_tests/test_material.py +++ b/tests/unit_tests/test_material.py @@ -710,3 +710,16 @@ def test_avoid_subnormal(run_in_tmpdir): # When read back in, the density should be zero mats = openmc.Materials.from_xml() assert mats[0].get_nuclide_atom_densities()['H2'] == 0.0 + + +def test_mean_free_path(): + + mat1 = openmc.Material() + mat1.add_nuclide('Si28', 1.0) + mat1.set_density('g/cm3', 2.32) + assert mat1.mean_free_path(energy=14e6) == pytest.approx(11.41, abs=1e-2) + + mat2 = openmc.Material() + mat2.add_nuclide('Pb208', 1.0) + mat2.set_density('g/cm3', 11.34) + assert mat2.mean_free_path(energy=14e6) == pytest.approx(5.65, abs=1e-2) From c116288ea389f01420600fe31f000ff7276990dc Mon Sep 17 00:00:00 2001 From: John Tramm Date: Thu, 26 Jun 2025 18:50:26 -0500 Subject: [PATCH 058/159] Updated Docs to Not Give Specific Python Version Requirement (#3473) --- docs/source/usersguide/install.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index 7a8cb80204e..2621109f7c8 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -515,10 +515,13 @@ to install the Python package in :ref:`"editable" mode `. Prerequisites ------------- -The Python API works with Python 3.8+. In addition to Python itself, the API -relies on a number of third-party packages. All prerequisites can be installed -using Conda_ (recommended), pip_, or through the package manager in most Linux -distributions. +In addition to Python itself, the OpenMC Python API relies on a number of +third-party packages. All prerequisites can be installed using Conda_ +(recommended), pip_, or through the package manager in most Linux distributions. +The current required Python version and up-to-date list of package dependencies +can be found in the `pyproject.toml `_ +file in the root directory of the OpenMC repository. An overview of these +dependencies is provided below. .. admonition:: Required :class: error From 25d64c9b26b0bf4d4b0aad4829ea8ec225226905 Mon Sep 17 00:00:00 2001 From: Ahnaf Tahmid Chowdhury Date: Fri, 27 Jun 2025 06:49:55 +0600 Subject: [PATCH 059/159] Refactor and Harden Configuration Management (#3461) Co-authored-by: Paul Romano --- openmc/config.py | 230 +++++++++++++++++++++++--------- tests/unit_tests/test_config.py | 111 ++++++++++----- 2 files changed, 245 insertions(+), 96 deletions(-) diff --git a/openmc/config.py b/openmc/config.py index 56d8a4072dd..d61fb2d31e6 100644 --- a/openmc/config.py +++ b/openmc/config.py @@ -1,8 +1,23 @@ +"""Module for handling global configuration in OpenMC. + +This module exports a single object, `config`, that can be used to control +various settings, primarily paths to data files. It acts like a dictionary but +with special behaviors. + +Examples +-------- +>>> import openmc +>>> openmc.config['cross_sections'] = '/path/to/my/cross_sections.xml' +>>> print(openmc.config) +{'resolve_paths': True, 'cross_sections': PosixPath('/path/to/my/cross_sections.xml')} + +""" from collections.abc import MutableMapping from contextlib import contextmanager import os from pathlib import Path import warnings +from typing import Any, Dict, Iterator from openmc.data import DataLibrary from openmc.data.decay import _DECAY_ENERGY, _DECAY_PHOTON_ENERGY @@ -11,104 +26,195 @@ class _Config(MutableMapping): - def __init__(self, data=()): - self._mapping = {'resolve_paths': True} + """A configuration dictionary for OpenMC with special handling for path-like values. + + This class enforces valid configuration keys and synchronizes path-related + settings with their corresponding environment variables. + + Attributes + ---------- + cross_sections : pathlib.Path + Path to a cross_sections.xml file. Also sets/unsets the + OPENMC_CROSS_SECTIONS environment variable. + mg_cross_sections : pathlib.Path + Path to a multi-group cross_sections.h5 file. Also sets/unsets + the OPENMC_MG_CROSS_SECTIONS environment variable. + chain_file : pathlib.Path + Path to a depletion chain XML file. Also sets/unsets the + OPENMC_CHAIN_FILE environment variable. Setting or deleting this + clears internal decay data caches. + resolve_paths : bool + If True (default), all paths assigned are resolved to absolute + paths. If False, paths are stored as they are provided. + + """ + _PATH_KEYS: Dict[str, str] = { + 'cross_sections': 'OPENMC_CROSS_SECTIONS', + 'mg_cross_sections': 'OPENMC_MG_CROSS_SECTIONS', + 'chain_file': 'OPENMC_CHAIN_FILE' + } + + def __init__(self, data: dict = ()): + self._mapping: Dict[str, Any] = {'resolve_paths': True} self.update(data) - def __getitem__(self, key): + def __getitem__(self, key: str) -> Any: return self._mapping[key] - def __delitem__(self, key): - del self._mapping[key] - if key == 'cross_sections': - del os.environ['OPENMC_CROSS_SECTIONS'] - elif key == 'mg_cross_sections': - del os.environ['OPENMC_MG_CROSS_SECTIONS'] - elif key == 'chain_file': - del os.environ['OPENMC_CHAIN_FILE'] - # Reset photon source data since it relies on chain file - _DECAY_PHOTON_ENERGY.clear() + def __delitem__(self, key: str): + """Delete a configuration key. + + This also deletes the corresponding environment variable if the key is a + path-like key, and clears decay data caches if 'chain_file' is deleted. + 'resolve_paths' cannot be deleted. - def __setitem__(self, key, value): - if key == 'cross_sections': - # Force environment variable to match - self._set_path(key, value) - os.environ['OPENMC_CROSS_SECTIONS'] = str(value) - elif key == 'mg_cross_sections': - self._set_path(key, value) - os.environ['OPENMC_MG_CROSS_SECTIONS'] = str(value) - elif key == 'chain_file': - self._set_path(key, value) - os.environ['OPENMC_CHAIN_FILE'] = str(value) - # Reset photon source data since it relies on chain file + """ + if key == 'resolve_paths': + raise KeyError("'resolve_paths' cannot be deleted.") + del self._mapping[key] + if key in self._PATH_KEYS: + env_var = self._PATH_KEYS[key] + if env_var in os.environ: + del os.environ[env_var] + if key == 'chain_file': _DECAY_PHOTON_ENERGY.clear() _DECAY_ENERGY.clear() + + def __setitem__(self, key: str, value: Any): + """Set a configuration key and its corresponding value. + + For path-like keys, this method performs several actions: + 1. Resolves the path to an absolute path if `resolve_paths` is True. + 2. Stores the `pathlib.Path` object. + 3. Sets the corresponding environment variable (e.g., OPENMC_CROSS_SECTIONS). + 4. For 'chain_file', clears internal decay data caches. + 5. Issues a `UserWarning` if the final path does not exist. + + """ + if key in self._PATH_KEYS: + p = Path(value) + # Use .get() for robustness, defaulting to True + if self._mapping.get('resolve_paths', True): + stored_path = p.resolve(strict=False) + else: + stored_path = p + + self._mapping[key] = stored_path + os.environ[self._PATH_KEYS[key]] = str(stored_path) + + if key == 'chain_file': + _DECAY_PHOTON_ENERGY.clear() + _DECAY_ENERGY.clear() + + if not stored_path.exists(): + warnings.warn(f"Path '{stored_path}' does not exist.", UserWarning) + elif key == 'resolve_paths': + if not isinstance(value, bool): + raise TypeError("'resolve_paths' must be a boolean.") self._mapping[key] = value else: - raise KeyError(f'Unrecognized config key: {key}. Acceptable keys ' - 'are "cross_sections", "mg_cross_sections", ' - '"chain_file", and "resolve_paths".') + valid_keys = list(self._PATH_KEYS.keys()) + ['resolve_paths'] + raise KeyError( + f"Unrecognized config key: {key}. Acceptable keys are: " + f"{', '.join(repr(k) for k in valid_keys)}." + ) - def __iter__(self): + def __iter__(self) -> Iterator[str]: return iter(self._mapping) - def __len__(self): + def __len__(self) -> int: return len(self._mapping) - def __repr__(self): + def __repr__(self) -> str: return repr(self._mapping) - def _set_path(self, key, value): - self._mapping[key] = p = Path(value).resolve() - if not p.exists(): - warnings.warn(f"'{value}' does not exist.") + def clear(self): + """Clear all configuration keys except for 'resolve_paths'. + + This ensures that the path resolution behavior is not accidentally reset + when clearing the configuration. + + """ + # Create a copy of keys to iterate over for safe deletion + keys_to_delete = [k for k in self._mapping if k != 'resolve_paths'] + for key in keys_to_delete: + del self[key] @contextmanager - def patch(self, key, value): - """Temporarily change a value in the configuration. + def patch(self, key: str, value: Any): + """Context manager to temporarily change a configuration value. + + After the `with` block, the configuration is restored to its original + state. Parameters ---------- key : str - Key to change - value : object - New value + The key of the configuration value to change. + value + The new temporary value. + + Examples + -------- + >>> openmc.config['cross_sections'] = 'endf71.xml' + >>> with openmc.config.patch('cross_sections', 'fendl32.xml'): + ... # Code in this block sees the new value + ... print(f"Inside with block: {openmc.config['cross_sections']}") + >>> # Outside the block, the value is restored + >>> print(f"Outside with block: {openmc.config['cross_sections']}") + Inside with block: fendl32.xml + Outside with block: endf71.xml + """ previous_value = self.get(key) self[key] = value - yield - if previous_value is None: - del self[key] - else: - self[key] = previous_value + try: + yield + finally: + if previous_value is None: + del self[key] + else: + self[key] = previous_value -def _default_config(): - """Return default configuration""" - config = _Config() - # Set cross sections using environment variable +def _default_config() -> _Config: + """Create a configuration initialized from environment variables. + + This function checks for OPENMC_CROSS_SECTIONS, OPENMC_MG_CROSS_SECTIONS, + and OPENMC_CHAIN_FILE environment variables. It also has logic to find + a chain file within a `cross_sections.xml` file if one is not + explicitly set. + + Returns + ------- + _Config + A new configuration object. + + """ + config = _Config() if "OPENMC_CROSS_SECTIONS" in os.environ: config['cross_sections'] = os.environ["OPENMC_CROSS_SECTIONS"] if "OPENMC_MG_CROSS_SECTIONS" in os.environ: config['mg_cross_sections'] = os.environ["OPENMC_MG_CROSS_SECTIONS"] - - # Set depletion chain chain_file = os.environ.get("OPENMC_CHAIN_FILE") - if (chain_file is None and - config.get('cross_sections') is not None and - config['cross_sections'].exists() - ): - # Check for depletion chain in cross_sections.xml - data = DataLibrary.from_xml(config['cross_sections']) - for lib in reversed(data.libraries): - if lib['type'] == 'depletion_chain': - chain_file = lib['path'] - break + xs_path = config.get('cross_sections') + if chain_file is None and xs_path is not None and xs_path.exists(): + try: + data = DataLibrary.from_xml(xs_path) + except Exception: + # Let this pass silently if cross_sections.xml can't be parsed + # or if a dependency like lxml is not available. + pass + else: + for lib in reversed(data.libraries): + if lib['type'] == 'depletion_chain': + chain_file = xs_path.parent / lib['path'] + break if chain_file is not None: config['chain_file'] = chain_file - return config +# Global configuration dictionary for OpenMC settings. config = _default_config() diff --git a/tests/unit_tests/test_config.py b/tests/unit_tests/test_config.py index 4e87de4b7e9..242c59b5e23 100644 --- a/tests/unit_tests/test_config.py +++ b/tests/unit_tests/test_config.py @@ -3,58 +3,101 @@ from pathlib import Path import openmc +from openmc.config import _default_config +from openmc.data import decay import pytest -@pytest.fixture(autouse=True, scope='module') -def reset_config(): - config = dict(openmc.config) +@pytest.fixture(autouse=True, scope='function') +def reset_config_and_env(): + """A fixture to ensure each test has a clean config, env, and CWD.""" + original_env = dict(os.environ) + original_cwd = os.getcwd() + + # Reset environment variables that affect config + for key in ['OPENMC_CROSS_SECTIONS', 'OPENMC_MG_CROSS_SECTIONS', 'OPENMC_CHAIN_FILE']: + if key in os.environ: + del os.environ[key] + + # Re-initialize the global config object + openmc.config = _default_config() + try: yield finally: - openmc.config.clear() - openmc.config.update(config) + # Restore environment and CWD + os.environ.clear() + os.environ.update(original_env) + os.chdir(original_cwd) + + # Restore config one last time for safety between modules + openmc.config = _default_config() def test_config_basics(): assert isinstance(openmc.config, Mapping) - for key, value in openmc.config.items(): - assert isinstance(key, str) - if key == 'resolve_paths': - assert isinstance(value, bool) - else: - assert isinstance(value, os.PathLike) - - # Set and delete - openmc.config['cross_sections'] = '/path/to/cross_sections.xml' + with pytest.warns(UserWarning): + openmc.config['cross_sections'] = '/path/to/cross_sections.xml' del openmc.config['cross_sections'] assert 'cross_sections' not in openmc.config assert 'OPENMC_CROSS_SECTIONS' not in os.environ + with pytest.raises(KeyError, match="Unrecognized config key: nuke"): + openmc.config['nuke'] = '/like/to/eat/bacon' + with pytest.raises(TypeError): + openmc.config['resolve_paths'] = 'not a bool' + + +def test_config_path_resolution(tmp_path): + """Test path resolution logic.""" + os.chdir(tmp_path) + relative_path = Path("some/file.xml") + absolute_path = relative_path.resolve() + + # Test with resolve_paths = True (default) + with pytest.warns(UserWarning): + openmc.config['cross_sections'] = relative_path + assert openmc.config['cross_sections'] == absolute_path + assert openmc.config['cross_sections'].is_absolute() + + # Test with resolve_paths = False + with openmc.config.patch('resolve_paths', False): + with pytest.warns(UserWarning): + openmc.config['chain_file'] = relative_path + assert openmc.config['chain_file'] == relative_path + assert not openmc.config['chain_file'].is_absolute() - # Can't use any key - with pytest.raises(KeyError): - openmc.config['🐖'] = '/like/to/eat/bacon' + assert openmc.config['resolve_paths'] is True - # ensure relative paths are expanded into absolute - # paths - chain_path = Path('./chain.xml') - openmc.config['chain_file'] = chain_path - assert openmc.config['chain_file'] == chain_path.resolve() +def test_config_patch(tmp_path): + file_a = tmp_path / "a.xml"; file_a.touch() + file_b = tmp_path / "b.xml"; file_b.touch() + openmc.config['cross_sections'] = file_a + with openmc.config.patch('cross_sections', file_b): + assert openmc.config['cross_sections'] == file_b.resolve() + assert openmc.config['cross_sections'] == file_a.resolve() -def test_config_patch(): - openmc.config['cross_sections'] = '/path/to/cross_sections.xml' - with openmc.config.patch('cross_sections', '/path/to/other.xml'): - assert str(openmc.config['cross_sections']) == '/path/to/other.xml' - assert str(openmc.config['cross_sections']) == '/path/to/cross_sections.xml' +def test_config_set_envvar(tmp_path): + """Test that setting config also sets environment variables correctly.""" + os.chdir(tmp_path) + relative_path = Path("relative.xml") + with pytest.warns(UserWarning): + openmc.config['cross_sections'] = relative_path + expected_path = str(relative_path.resolve()) + assert os.environ['OPENMC_CROSS_SECTIONS'] == expected_path -def test_config_set_envvar(): - openmc.config['cross_sections'] = '/path/to/cross_sections.xml' - assert os.environ['OPENMC_CROSS_SECTIONS'] == '/path/to/cross_sections.xml' +def test_config_warning_nonexistent_path(tmp_path): + """Test that a warning is issued for a path that does not exist.""" + bad_path = tmp_path / "a/path/that/does/not/exist.xml" + with pytest.warns(UserWarning, match=f"Path '{bad_path}' does not exist."): + openmc.config['chain_file'] = bad_path - openmc.config['mg_cross_sections'] = '/path/to/mg_cross_sections.h5' - assert os.environ['OPENMC_MG_CROSS_SECTIONS'] == '/path/to/mg_cross_sections.h5' - openmc.config['chain_file'] = '/path/to/chain_file.xml' - assert os.environ['OPENMC_CHAIN_FILE'] == '/path/to/chain_file.xml' +def test_config_chain_side_effect(tmp_path): + """Test that modifying chain_file clears decay data caches.""" + chain_file = tmp_path / "chain.xml"; chain_file.touch() + decay._DECAY_ENERGY['U235'] = (1.0, 2.0) + decay._DECAY_PHOTON_ENERGY['PU239'] = {} + openmc.config['chain_file'] = chain_file + assert not decay._DECAY_ENERGY and not decay._DECAY_PHOTON_ENERGY From b939f9003b65b8b7c1070256f51638c25e8d8c30 Mon Sep 17 00:00:00 2001 From: John Tramm Date: Mon, 30 Jun 2025 03:33:22 -0500 Subject: [PATCH 060/159] Stabilize Adjoint Source (#3476) --- src/random_ray/flat_source_domain.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/random_ray/flat_source_domain.cpp b/src/random_ray/flat_source_domain.cpp index 41e93ec945d..4a5b430362d 100644 --- a/src/random_ray/flat_source_domain.cpp +++ b/src/random_ray/flat_source_domain.cpp @@ -1242,6 +1242,30 @@ void FlatSourceDomain::set_adjoint_sources(const vector& forward_flux) } } + // "Small" source regions in OpenMC are defined as those that are hit by + // MIN_HITS_PER_BATCH rays or fewer each batch. These regions typically have + // very small volumes combined with a low aspect ratio, and are often + // generated when applying a source region mesh that clips the edge of a + // curved surface. As perhaps only a few rays will visit these regions over + // the entire forward simulation, the forward flux estimates are extremely + // noisy and unreliable. In some cases, the noise may make the forward fluxes + // extremely low, leading to unphysically large adjoint source terms, + // resulting in weight windows that aggressively try to drive particles + // towards these regions. To fix this, we simply filter out any "small" source + // regions from consideration. If a source region is "small", we + // set its adjoint source to zero. This adds negligible bias to the adjoint + // flux solution, as the true total adjoint source contribution from small + // regions is likely to be negligible. +#pragma omp parallel for + for (int64_t sr = 0; sr < n_source_regions(); sr++) { + if (source_regions_.is_small(sr)) { + for (int g = 0; g < negroups_; g++) { + source_regions_.external_source(sr, g) = 0.0; + } + source_regions_.external_source_present(sr) = 0; + } + } + // Divide the fixed source term by sigma t (to save time when applying each // iteration) #pragma omp parallel for From dab8af5672d7367996fd78a40b0c75d852efe0db Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 1 Jul 2025 05:43:28 -0500 Subject: [PATCH 061/159] Support flux collapse method in `get_microxs_and_flux` (#3466) Co-authored-by: Jonathan Shimwell --- openmc/deplete/microxs.py | 134 +++++++++++++----- openmc/filter_expansion.py | 2 +- tests/regression_tests/microxs/test.py | 71 +++++----- .../microxs/test_reference_materials.csv | 25 ---- .../test_reference_materials_direct.csv | 7 + .../microxs/test_reference_materials_flux.csv | 7 + .../microxs/test_reference_mesh.csv | 25 ---- .../microxs/test_reference_mesh_direct.csv | 7 + .../microxs/test_reference_mesh_flux.csv | 7 + 9 files changed, 163 insertions(+), 122 deletions(-) delete mode 100644 tests/regression_tests/microxs/test_reference_materials.csv create mode 100644 tests/regression_tests/microxs/test_reference_materials_direct.csv create mode 100644 tests/regression_tests/microxs/test_reference_materials_flux.csv delete mode 100644 tests/regression_tests/microxs/test_reference_mesh.csv create mode 100644 tests/regression_tests/microxs/test_reference_mesh_direct.csv create mode 100644 tests/regression_tests/microxs/test_reference_mesh_flux.csv diff --git a/openmc/deplete/microxs.py b/openmc/deplete/microxs.py index d478db11ba7..cbc8b1c8894 100644 --- a/openmc/deplete/microxs.py +++ b/openmc/deplete/microxs.py @@ -5,8 +5,10 @@ """ from __future__ import annotations -from collections.abc import Iterable, Sequence +from collections.abc import Sequence +import shutil from tempfile import TemporaryDirectory +from typing import Union, TypeAlias import pandas as pd import numpy as np @@ -27,19 +29,39 @@ _valid_rxns.append('damage-energy') +# TODO: Replace with type statement when support is Python 3.12+ +DomainTypes: TypeAlias = Union[ + Sequence[openmc.Material], + Sequence[openmc.Cell], + Sequence[openmc.Universe], + openmc.MeshBase, + openmc.Filter +] + + def get_microxs_and_flux( - model: openmc.Model, - domains, - nuclides: Iterable[str] | None = None, - reactions: Iterable[str] | None = None, - energies: Iterable[float] | str | None = None, - chain_file: PathLike | Chain | None = None, - run_kwargs=None - ) -> tuple[list[np.ndarray], list[MicroXS]]: - """Generate a microscopic cross sections and flux from a Model + model: openmc.Model, + domains: DomainTypes, + nuclides: Sequence[str] | None = None, + reactions: Sequence[str] | None = None, + energies: Sequence[float] | str | None = None, + reaction_rate_mode: str = 'direct', + chain_file: PathLike | Chain | None = None, + path_statepoint: PathLike | None = None, + run_kwargs=None +) -> tuple[list[np.ndarray], list[MicroXS]]: + """Generate microscopic cross sections and fluxes for multiple domains. + + This function runs a neutron transport solve to obtain the flux and reaction + rates in the specified domains and computes multigroup microscopic cross + sections that can be used in depletion calculations with the + :class:`~openmc.deplete.IndependentOperator` class. .. versionadded:: 0.14.0 + .. versionchanged:: 0.15.3 + Added `reaction_rate_mode` and `path_statepoint` arguments. + Parameters ---------- model : openmc.Model @@ -53,12 +75,22 @@ def get_microxs_and_flux( Reactions to get cross sections for. If not specified, all neutron reactions listed in the depletion chain file are used. energies : iterable of float or str - Energy group boundaries in [eV] or the name of the group structure + Energy group boundaries in [eV] or the name of the group structure. + If left as None energies will default to [0.0, 100e6] + reaction_rate_mode : {"direct", "flux"}, optional + Indicate how reaction rates should be calculated. The "direct" method + tallies reaction rates directly. The "flux" method tallies a multigroup + flux spectrum and then collapses multigroup reaction rates after a + transport solve (with an option to tally some reaction rates directly). chain_file : PathLike or Chain, optional Path to the depletion chain XML file or an instance of openmc.deplete.Chain. Used to determine cross sections for materials not present in the inital composition. Defaults to ``openmc.config['chain_file']``. + path_statepoint : path-like, optional + Path to write the statepoint file from the neutron transport solve to. + By default, The statepoint file is written to a temporary directory and + is not kept. run_kwargs : dict, optional Keyword arguments passed to :meth:`openmc.Model.run` @@ -69,7 +101,13 @@ def get_microxs_and_flux( list of MicroXS Cross section data in [b] for each domain + See Also + -------- + openmc.deplete.IndependentOperator + """ + check_value('reaction_rate_mode', reaction_rate_mode, {'direct', 'flux'}) + # Save any original tallies on the model original_tallies = model.tallies @@ -85,8 +123,8 @@ def get_microxs_and_flux( # Set up the reaction rate and flux tallies if energies is None: - energy_filter = openmc.EnergyFilter([0.0, 100.0e6]) - elif isinstance(energies, str): + energies = [0.0, 100.0e6] + if isinstance(energies, str): energy_filter = openmc.EnergyFilter.from_group_structure(energies) else: energy_filter = openmc.EnergyFilter(energies) @@ -104,16 +142,18 @@ def get_microxs_and_flux( else: raise ValueError(f"Unsupported domain type: {type(domains[0])}") - rr_tally = openmc.Tally(name='MicroXS RR') - rr_tally.filters = [domain_filter, energy_filter] - rr_tally.nuclides = nuclides - rr_tally.multiply_density = False - rr_tally.scores = reactions - flux_tally = openmc.Tally(name='MicroXS flux') flux_tally.filters = [domain_filter, energy_filter] flux_tally.scores = ['flux'] - model.tallies = openmc.Tallies([rr_tally, flux_tally]) + model.tallies = [flux_tally] + + if reaction_rate_mode == 'direct': + rr_tally = openmc.Tally(name='MicroXS RR') + rr_tally.filters = [domain_filter, energy_filter] + rr_tally.nuclides = nuclides + rr_tally.multiply_density = False + rr_tally.scores = reactions + model.tallies.append(rr_tally) if openmc.lib.is_initialized: openmc.lib.finalize() @@ -134,33 +174,55 @@ def get_microxs_and_flux( statepoint_path = model.run(**run_kwargs) if comm.rank == 0: + # Move the statepoint file if it is being saved to a specific path + if path_statepoint is not None: + shutil.move(statepoint_path, path_statepoint) + statepoint_path = path_statepoint + with StatePoint(statepoint_path) as sp: - rr_tally = sp.tallies[rr_tally.id] - rr_tally._read_results() + if reaction_rate_mode == 'direct': + rr_tally = sp.tallies[rr_tally.id] + rr_tally._read_results() flux_tally = sp.tallies[flux_tally.id] flux_tally._read_results() - rr_tally = comm.bcast(rr_tally) + # Get flux values and make energy groups last dimension flux_tally = comm.bcast(flux_tally) - # Get reaction rates and flux values - reaction_rates = rr_tally.get_reshaped_data() # (domains, groups, nuclides, reactions) flux = flux_tally.get_reshaped_data() # (domains, groups, 1, 1) - - # Make energy groups last dimension - reaction_rates = np.moveaxis(reaction_rates, 1, -1) # (domains, nuclides, reactions, groups) flux = np.moveaxis(flux, 1, -1) # (domains, 1, 1, groups) - # Divide RR by flux to get microscopic cross sections - xs = np.empty_like(reaction_rates) # (domains, nuclides, reactions, groups) - d, _, _, g = np.nonzero(flux) - xs[d, ..., g] = reaction_rates[d, ..., g] / flux[d, :, :, g] + # Create list where each item corresponds to one domain + fluxes = list(flux.squeeze((1, 2))) + + if reaction_rate_mode == 'direct': + # Get reaction rates + rr_tally = comm.bcast(rr_tally) + reaction_rates = rr_tally.get_reshaped_data() # (domains, groups, nuclides, reactions) + + # Make energy groups last dimension + reaction_rates = np.moveaxis(reaction_rates, 1, -1) # (domains, nuclides, reactions, groups) + + # Divide RR by flux to get microscopic cross sections. The indexing + # ensures that only non-zero flux values are used, and broadcasting is + # applied to align the shapes of reaction_rates and flux for division. + xs = np.empty_like(reaction_rates) # (domains, nuclides, reactions, groups) + d, _, _, g = np.nonzero(flux) + xs[d, ..., g] = reaction_rates[d, ..., g] / flux[d, :, :, g] + + # Create lists where each item corresponds to one domain + micros = [MicroXS(xs_i, nuclides, reactions) for xs_i in xs] + else: + micros = [MicroXS.from_multigroup_flux( + energies=energies, + multigroup_flux=flux_i, + chain_file=chain_file, + nuclides=nuclides, + reactions=reactions + ) for flux_i in fluxes] # Reset tallies model.tallies = original_tallies - # Create lists where each item corresponds to one domain - fluxes = list(flux.squeeze((1, 2))) - micros = [MicroXS(xs_i, nuclides, reactions) for xs_i in xs] return fluxes, micros @@ -230,7 +292,7 @@ def from_multigroup_flux( ---------- energies : iterable of float or str Energy group boundaries in [eV] or the name of the group structure - multi_group_flux : iterable of float + multigroup_flux : iterable of float Energy-dependent multigroup flux values chain_file : PathLike or Chain, optional Path to the depletion chain XML file or an instance of diff --git a/openmc/filter_expansion.py b/openmc/filter_expansion.py index f8e677578f1..cdb2f20e06b 100644 --- a/openmc/filter_expansion.py +++ b/openmc/filter_expansion.py @@ -3,7 +3,7 @@ import lxml.etree as ET import openmc.checkvalue as cv -from . import Filter +from .filter import Filter class ExpansionFilter(Filter): diff --git a/tests/regression_tests/microxs/test.py b/tests/regression_tests/microxs/test.py index a35150a1b80..70833bb39c1 100644 --- a/tests/regression_tests/microxs/test.py +++ b/tests/regression_tests/microxs/test.py @@ -13,55 +13,56 @@ @pytest.fixture(scope="module") def model(): fuel = openmc.Material(name="uo2") - fuel.add_element("U", 1, percent_type="ao", enrichment=4.25) - fuel.add_element("O", 2) + fuel.add_nuclide("U235", 1.0) + fuel.add_nuclide("O16", 2.0) fuel.set_density("g/cc", 10.4) - clad = openmc.Material(name="clad") - clad.add_element("Zr", 1) - clad.set_density("g/cc", 6) - - water = openmc.Material(name="water") - water.add_element("O", 1) - water.add_element("H", 2) - water.set_density("g/cc", 1.0) - water.add_s_alpha_beta("c_H_in_H2O") - - radii = [0.42, 0.45] - fuel.volume = np.pi * radii[0] ** 2 - - materials = openmc.Materials([fuel, clad, water]) - - pin_surfaces = [openmc.ZCylinder(r=r) for r in radii] - pin_univ = openmc.model.pin(pin_surfaces, materials) - bound_box = openmc.model.RectangularPrism(1.24, 1.24, boundary_type="reflective") - root_cell = openmc.Cell(fill=pin_univ, region=-bound_box) - geometry = openmc.Geometry([root_cell]) + sphere = openmc.Sphere(r=10.0, boundary_type='vacuum') + cell = openmc.Cell(region=-sphere, fill=fuel) + geometry = openmc.Geometry([cell]) settings = openmc.Settings() settings.particles = 1000 settings.inactive = 5 settings.batches = 10 - return openmc.Model(geometry, materials, settings) + return openmc.Model(geometry, settings=settings) -@pytest.mark.parametrize("domain_type", ["materials", "mesh"]) -def test_from_model(model, domain_type): +@pytest.mark.parametrize( + "domain_type, rr_mode", + [ + ("materials", "direct"), + ("materials", "flux"), + ("mesh", "direct"), + ("mesh", "flux"), + ] +) +def test_from_model(model, domain_type, rr_mode): if domain_type == 'materials': - domains = model.materials[:1] + domains = list(model.geometry.get_all_materials().values()) elif domain_type == 'mesh': mesh = openmc.RegularMesh() - mesh.lower_left = (-0.62, -0.62) - mesh.upper_right = (0.62, 0.62) - mesh.dimension = (3, 3) + mesh.lower_left = (-10., -10.) + mesh.upper_right = (10., 10.) + mesh.dimension = (1, 1) domains = mesh - nuclides = ['U234', 'U235', 'U238', 'U236', 'O16', 'O17', 'I135', 'Xe135', - 'Xe136', 'Cs135', 'Gd157', 'Gd156'] - _, test_xs = get_microxs_and_flux(model, domains, nuclides, chain_file=CHAIN_FILE) + nuclides = ['U235', 'O16', 'Xe135'] + kwargs = { + 'reaction_rate_mode': rr_mode, + 'chain_file': CHAIN_FILE, + 'path_statepoint': 'neutron_transport.h5', + } + if rr_mode == 'flux': + kwargs['energies'] = 'CASMO-40' + _, test_xs = get_microxs_and_flux(model, domains, nuclides, **kwargs) if config['update']: - test_xs[0].to_csv(f'test_reference_{domain_type}.csv') - - ref_xs = MicroXS.from_csv(f'test_reference_{domain_type}.csv') + test_xs[0].to_csv(f'test_reference_{domain_type}_{rr_mode}.csv') + # Make sure results match reference results + ref_xs = MicroXS.from_csv(f'test_reference_{domain_type}_{rr_mode}.csv') np.testing.assert_allclose(test_xs[0].data, ref_xs.data, rtol=1e-11) + + # Make sure statepoint file was saved + assert Path('neutron_transport.h5').exists() + Path('neutron_transport.h5').unlink() diff --git a/tests/regression_tests/microxs/test_reference_materials.csv b/tests/regression_tests/microxs/test_reference_materials.csv deleted file mode 100644 index 3e679f98071..00000000000 --- a/tests/regression_tests/microxs/test_reference_materials.csv +++ /dev/null @@ -1,25 +0,0 @@ -nuclides,reactions,groups,xs -U234,"(n,gamma)",1,21.418670317831076 -U234,fission,1,0.5014588470882162 -U235,"(n,gamma)",1,10.343944102483215 -U235,fission,1,47.46718472611895 -U238,"(n,gamma)",1,0.8741166723597229 -U238,fission,1,0.10829568455139067 -U236,"(n,gamma)",1,9.08348678468935 -U236,fission,1,0.3325287927011424 -O16,"(n,gamma)",1,7.548646353912426e-05 -O16,fission,1,0.0 -O17,"(n,gamma)",1,0.00040184862213103105 -O17,fission,1,0.0 -I135,"(n,gamma)",1,6.691256508942912 -I135,fission,1,0.0 -Xe135,"(n,gamma)",1,223998.6418566729 -Xe135,fission,1,0.0 -Xe136,"(n,gamma)",1,0.022934362666193503 -Xe136,fission,1,0.0 -Cs135,"(n,gamma)",1,2.2845395222353204 -Cs135,fission,1,0.0 -Gd157,"(n,gamma)",1,12582.07962003624 -Gd157,fission,1,0.0 -Gd156,"(n,gamma)",1,2.942112751533234 -Gd156,fission,1,0.0 diff --git a/tests/regression_tests/microxs/test_reference_materials_direct.csv b/tests/regression_tests/microxs/test_reference_materials_direct.csv new file mode 100644 index 00000000000..3259d0151a9 --- /dev/null +++ b/tests/regression_tests/microxs/test_reference_materials_direct.csv @@ -0,0 +1,7 @@ +nuclides,reactions,groups,xs +U235,"(n,gamma)",1,0.14765501510184456 +U235,fission,1,1.2517200956290817 +O16,"(n,gamma)",1,0.00010872314985710938 +O16,fission,1,0.0 +Xe135,"(n,gamma)",1,0.014333667335215764 +Xe135,fission,1,0.0 diff --git a/tests/regression_tests/microxs/test_reference_materials_flux.csv b/tests/regression_tests/microxs/test_reference_materials_flux.csv new file mode 100644 index 00000000000..5023e0d0e63 --- /dev/null +++ b/tests/regression_tests/microxs/test_reference_materials_flux.csv @@ -0,0 +1,7 @@ +nuclides,reactions,groups,xs +U235,"(n,gamma)",1,0.15018326758942505 +U235,fission,1,1.2603151141390958 +O16,"(n,gamma)",1,0.00012159621938463765 +O16,fission,1,0.0 +Xe135,"(n,gamma)",1,0.015180177095633546 +Xe135,fission,1,0.0 diff --git a/tests/regression_tests/microxs/test_reference_mesh.csv b/tests/regression_tests/microxs/test_reference_mesh.csv deleted file mode 100644 index 1882372b11a..00000000000 --- a/tests/regression_tests/microxs/test_reference_mesh.csv +++ /dev/null @@ -1,25 +0,0 @@ -nuclides,reactions,groups,xs -U234,"(n,gamma)",1,27.027171724208227 -U234,fission,1,0.04333740860093498 -U235,"(n,gamma)",1,12.683875995776193 -U235,fission,1,4.2596665957162605 -U238,"(n,gamma)",1,4.479719141496804 -U238,fission,1,0.009460665924409056 -U236,"(n,gamma)",1,8.469286849810802 -U236,fission,1,0.027373590840715795 -O16,"(n,gamma)",1,7.478160204479271e-05 -O16,fission,1,0.0 -O17,"(n,gamma)",1,0.0004743959164164789 -O17,fission,1,0.0 -I135,"(n,gamma)",1,8.33959524761822 -I135,fission,1,0.0 -Xe135,"(n,gamma)",1,282068.447252079 -Xe135,fission,1,0.0 -Xe136,"(n,gamma)",1,0.02888928065916194 -Xe136,fission,1,0.0 -Cs135,"(n,gamma)",1,2.5863526577468408 -Cs135,fission,1,0.0 -Gd157,"(n,gamma)",1,16518.24083307153 -Gd157,fission,1,0.0 -Gd156,"(n,gamma)",1,2.838514589417232 -Gd156,fission,1,0.0 diff --git a/tests/regression_tests/microxs/test_reference_mesh_direct.csv b/tests/regression_tests/microxs/test_reference_mesh_direct.csv new file mode 100644 index 00000000000..bd07a3237ae --- /dev/null +++ b/tests/regression_tests/microxs/test_reference_mesh_direct.csv @@ -0,0 +1,7 @@ +nuclides,reactions,groups,xs +U235,"(n,gamma)",1,0.14765501510184456 +U235,fission,1,1.2517200956290815 +O16,"(n,gamma)",1,0.00010872314985710936 +O16,fission,1,0.0 +Xe135,"(n,gamma)",1,0.014333667335215761 +Xe135,fission,1,0.0 diff --git a/tests/regression_tests/microxs/test_reference_mesh_flux.csv b/tests/regression_tests/microxs/test_reference_mesh_flux.csv new file mode 100644 index 00000000000..d946ada802a --- /dev/null +++ b/tests/regression_tests/microxs/test_reference_mesh_flux.csv @@ -0,0 +1,7 @@ +nuclides,reactions,groups,xs +U235,"(n,gamma)",1,0.15018326758942507 +U235,fission,1,1.2603151141390958 +O16,"(n,gamma)",1,0.00012159621938463766 +O16,fission,1,0.0 +Xe135,"(n,gamma)",1,0.015180177095633546 +Xe135,fission,1,0.0 From ecfb666db735544dd6a76b88c02f57049111cbf2 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 1 Jul 2025 09:03:28 -0500 Subject: [PATCH 062/159] Allow spatial constraints on element sources within MeshSource (#3431) --- include/openmc/source.h | 29 +++++++--- src/source.cpp | 53 ++++++++++-------- tests/unit_tests/test_source_mesh.py | 81 ++++++++++++++-------------- 3 files changed, 94 insertions(+), 69 deletions(-) diff --git a/include/openmc/source.h b/include/openmc/source.h index e82c42269f2..d58195f1089 100644 --- a/include/openmc/source.h +++ b/include/openmc/source.h @@ -139,6 +139,9 @@ class IndependentSource : public Source { DomainType domain_type() const { return domain_type_; } const std::unordered_set& domain_ids() const { return domain_ids_; } + // Setter for spatial distribution + void set_space(UPtrSpace space) { space_ = std::move(space); } + protected: // Indicates whether derived class already handles constraints bool constraints_applied() const override { return true; } @@ -205,6 +208,23 @@ typedef unique_ptr create_compiled_source_t(std::string parameters); //! Mesh-based source with different distributions for each element //============================================================================== +// Helper class to sample spatial position on a single mesh element +class MeshElementSpatial : public SpatialDistribution { +public: + MeshElementSpatial(int32_t mesh_index, int elem_index) + : mesh_index_(mesh_index), elem_index_(elem_index) + {} + + //! Sample a position from the distribution + //! \param seed Pseudorandom number seed pointer + //! \return Sampled position + Position sample(uint64_t* seed) const override; + +private: + int32_t mesh_index_ {C_NONE}; //!< Index in global meshes array + int elem_index_; //! Index of mesh element +}; + class MeshSource : public Source { public: // Constructors @@ -219,18 +239,15 @@ class MeshSource : public Source { double strength() const override { return space_->total_strength(); } // Accessors - const std::unique_ptr& source(int32_t i) const + const unique_ptr& source(int32_t i) const { return sources_.size() == 1 ? sources_[0] : sources_[i]; } -protected: - bool constraints_applied() const override { return true; } - private: // Data members - unique_ptr space_; //!< Mesh spatial - vector> sources_; //!< Source distributions + unique_ptr space_; //!< Mesh spatial + vector> sources_; //!< Source distributions }; //============================================================================== diff --git a/src/source.cpp b/src/source.cpp index 6ee0b560359..65873f5a797 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -239,7 +239,9 @@ bool Source::satisfies_spatial_constraints(Position r) const if (!domain_ids_.empty()) { if (domain_type_ == DomainType::MATERIAL) { auto mat_index = geom_state.material(); - if (mat_index != MATERIAL_VOID) { + if (mat_index == MATERIAL_VOID) { + accepted = false; + } else { accepted = contains(domain_ids_, model::materials[mat_index]->id()); } } else { @@ -525,6 +527,15 @@ CompiledSourceWrapper::~CompiledSourceWrapper() #endif } +//============================================================================== +// MeshElementSpatial implementation +//============================================================================== + +Position MeshElementSpatial::sample(uint64_t* seed) const +{ + return model::meshes[mesh_index_]->sample_element(elem_index_, seed); +} + //============================================================================== // MeshSource implementation //============================================================================== @@ -539,10 +550,23 @@ MeshSource::MeshSource(pugi::xml_node node) : Source(node) // read all source distributions and populate strengths vector for MeshSpatial // object for (auto source_node : node.children("source")) { - sources_.emplace_back(Source::create(source_node)); + auto src = Source::create(source_node); + if (auto ptr = dynamic_cast(src.get())) { + src.release(); + sources_.emplace_back(ptr); + } else { + fatal_error( + "The source assigned to each element must be an IndependentSource."); + } strengths.push_back(sources_.back()->strength()); } + // Set spatial distributions for each mesh element + for (int elem_index = 0; elem_index < sources_.size(); ++elem_index) { + sources_[elem_index]->set_space( + std::make_unique(mesh_idx, elem_index)); + } + // the number of source distributions should either be one or equal to the // number of mesh elements if (sources_.size() > 1 && sources_.size() != mesh->n_bins()) { @@ -556,29 +580,12 @@ MeshSource::MeshSource(pugi::xml_node node) : Source(node) SourceSite MeshSource::sample(uint64_t* seed) const { - // Sample the CDF defined in initialization above + // Sample a mesh element based on the relative strengths int32_t element = space_->sample_element_index(seed); - // Sample position and apply rejection on spatial domains - Position r; - do { - r = space_->mesh()->sample_element(element, seed); - } while (!this->satisfies_spatial_constraints(r)); - - SourceSite site; - while (true) { - // Sample source for the chosen element and replace the position - site = source(element)->sample_with_constraints(seed); - site.r = r; - - // Apply other rejections - if (satisfies_energy_constraints(site.E) && - satisfies_time_constraints(site.time)) { - break; - } - } - - return site; + // Sample the distribution for the specific mesh element; note that the + // spatial distribution has been set for each element using MeshElementSpatial + return source(element)->sample_with_constraints(seed); } //============================================================================== diff --git a/tests/unit_tests/test_source_mesh.py b/tests/unit_tests/test_source_mesh.py index d023c1335c0..2550cb87ebb 100644 --- a/tests/unit_tests/test_source_mesh.py +++ b/tests/unit_tests/test_source_mesh.py @@ -1,5 +1,7 @@ from itertools import product from pathlib import Path +from math import sqrt +import random import pytest import numpy as np @@ -334,13 +336,10 @@ def test_umesh_source_independent(run_in_tmpdir, request, void_model, library): n_elements = 12_000 model.settings.source = openmc.MeshSource(uscd_mesh, n_elements*[ind_source]) model.export_to_model_xml() - try: - openmc.lib.init() + with openmc.lib.run_in_memory(): openmc.lib.simulation_init() sites = openmc.lib.sample_external_source(10) openmc.lib.statepoint_write('statepoint.h5') - finally: - openmc.lib.finalize() with openmc.StatePoint('statepoint.h5') as sp: uscd_mesh = sp.meshes[uscd_mesh.id] @@ -351,51 +350,53 @@ def test_umesh_source_independent(run_in_tmpdir, request, void_model, library): assert site.r in bounding_box -def test_mesh_source_file(run_in_tmpdir): - # Creating a source file with a single particle - source_particle = openmc.SourceParticle(time=10.0) - openmc.write_source_file([source_particle], 'source.h5') - file_source = openmc.FileSource('source.h5') +def test_mesh_source_constraints(run_in_tmpdir): + """Test application of constraints to underlying mesh element sources""" + # Create simple model with two cells + m1 = openmc.Material() + m1.add_nuclide('H1', 1.0) + m2 = m1.clone() + sph = openmc.Sphere(r=100, boundary_type='vacuum') + box1 = openmc.model.RectangularParallelepiped(-1, 0, -1, 1, -1, 1) + box2 = openmc.model.RectangularParallelepiped(0, 2, -1, 1, -1, 1) + cell1 = openmc.Cell(fill=m1, region=-box1) + cell2 = openmc.Cell(fill=m2, region=-box2) + outer = openmc.Cell(region=-sph & (+box1 | +box2)) model = openmc.Model() + model.geometry = openmc.Geometry([cell1, cell2, outer]) - rect_prism = openmc.model.RectangularParallelepiped( - -5.0, 5.0, -5.0, 5.0, -5.0, 5.0, boundary_type='vacuum') - - mat = openmc.Material() - mat.add_nuclide('H1', 1.0) - - model.geometry = openmc.Geometry([openmc.Cell(fill=mat, region=-rect_prism)]) - model.settings.particles = 1000 - model.settings.batches = 10 - model.settings.run_mode = 'fixed source' - + # Define a mesh covering the two cells: the first mesh element contains + # cell1 (-1 < x < 0) and the second element contains cells2 (0 < x < 2) mesh = openmc.RegularMesh() - mesh.lower_left = (-1, -2, -3) - mesh.upper_right = (2, 3, 4) - mesh.dimension = (1, 1, 1) + mesh.lower_left = (-3., -1., -1.) + mesh.upper_right = (3., 1., 1.) + mesh.dimension = (2, 1, 1) - model.settings.source = openmc.MeshSource(mesh, [file_source]) + # Define a mesh source with a randomly chosen probability + p = random.random() + src1 = openmc.IndependentSource(strength=p, constraints={'domains': [cell1]}) + src2 = openmc.IndependentSource(strength=1 - p, constraints={'domains': [cell2]}) + model.settings.source = openmc.MeshSource(mesh, [src1, src2]) + # Finish settings and export + model.settings.particles = 100 + model.settings.batches = 1 model.export_to_model_xml() - openmc.lib.init() - openmc.lib.simulation_init() - sites = openmc.lib.sample_external_source(10) - openmc.lib.simulation_finalize() - openmc.lib.finalize() + with openmc.lib.run_in_memory(): + # Sample sites from the source + sites = openmc.lib.sample_external_source(N := 1000) - # The mesh bounds do not contain the point of the lone source site in the - # file source, so it should not appear in the set of source sites produced - # from the mesh source. Additionally, the source should be located within - # the mesh - bbox = mesh.bounding_box - for site in sites: - assert site.r != (0, 0, 0) - assert site.E == source_particle.E - assert site.u == source_particle.u - assert site.time == source_particle.time - assert site.r in bbox + # Check that all sites are either in cell1 or cell2 + xs = np.array([s.r[0] for s in sites]) + assert (xs >= -1.0).all() + assert (xs <= 2.0).all() + + # Check that the correct percentage of the sites are in cell1 + sigma = sqrt(p*(1- p)/N) + frac = xs[(-1.0 <= xs) & (xs <= 0.0)].size / N + assert frac == pytest.approx(p, abs=5*sigma) @pytest.mark.parametrize("mesh_type", ('rectangular', 'cylindrical', 'spherical')) From eb74d497d2cfa784d9d628a79fda9f4c509b87d4 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 1 Jul 2025 12:43:45 -0500 Subject: [PATCH 063/159] Introduce `openmc.lib.TemporarySession` context manager (#3475) --- docs/source/pythonapi/capi.rst | 1 + openmc/deplete/chain.py | 57 ++++++++++++++++------ openmc/deplete/microxs.py | 53 +++++++++------------ openmc/lib/core.py | 65 ++++++++++++++++++++++++++ openmc/lib/nuclide.py | 6 +++ openmc/mesh.py | 41 ++++++++-------- openmc/weight_windows.py | 10 ++-- src/nuclide.cpp | 3 +- tests/unit_tests/test_deplete_chain.py | 6 +-- 9 files changed, 163 insertions(+), 79 deletions(-) diff --git a/docs/source/pythonapi/capi.rst b/docs/source/pythonapi/capi.rst index 63fc5d11b84..67eca009471 100644 --- a/docs/source/pythonapi/capi.rst +++ b/docs/source/pythonapi/capi.rst @@ -89,6 +89,7 @@ Classes SphericalMesh SurfaceFilter Tally + TemporarySession UniverseFilter UnstructuredMesh WeightFilter diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 07db487995b..8e24f716bc2 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -11,6 +11,7 @@ from collections import defaultdict, namedtuple from collections.abc import Mapping, Iterable from numbers import Real, Integral +from pathlib import Path from warnings import warn from typing import List @@ -278,7 +279,6 @@ def __len__(self): """Number of nuclides in chain.""" return len(self.nuclides) - @property def stable_nuclides(self) -> List[Nuclide]: """List of stable nuclides available in the chain""" @@ -298,6 +298,7 @@ def add_nuclide(self, nuclide: Nuclide): Nuclide to add """ + _invalidate_chain_cache(self) self.nuclide_dict[nuclide.name] = len(self.nuclides) self.nuclides.append(nuclide) @@ -463,7 +464,6 @@ def from_endf(cls, decay_files, fpy_files, neutron_files, nuclide.add_reaction('fission', None, q_value, 1.0) fissionable = True - if fissionable: if parent in fpy_data: fpy = fpy_data[parent] @@ -558,6 +558,9 @@ def from_xml(cls, filename, fission_q=None): nuc = Nuclide.from_xml(nuclide_elem, root, this_q) chain.add_nuclide(nuc) + # Store path of XML file (used for handling cache invalidation) + chain._xml_path = str(Path(filename).resolve()) + return chain def export_to_xml(self, filename): @@ -888,7 +891,7 @@ def set_branch_ratios(self, branch_ratios, reaction="(n,gamma)", -------- :meth:`get_branch_ratios` """ - + _invalidate_chain_cache(self) # Store some useful information through the validation stage sums = {} @@ -1027,6 +1030,7 @@ def fission_yields(self): @fission_yields.setter def fission_yields(self, yields): + _invalidate_chain_cache(self) if yields is not None: if isinstance(yields, Mapping): yields = [yields] @@ -1249,6 +1253,10 @@ def _follow(self, isotopes, level): return found +# A global cache for Chain objects +_CHAIN_CACHE = {} + + def _get_chain( chain_file: PathLike | Chain | None = None, fission_q: dict | None = None @@ -1269,16 +1277,39 @@ def _get_chain( Chain Depletion chain instance. """ + # If chain_file is already a Chain, return it directly if isinstance(chain_file, Chain): return chain_file - elif isinstance(chain_file, PathLike | None): - if chain_file is None: - chain_file = openmc.config.get('chain_file') - if 'chain_file' not in openmc.config: - raise DataError( - "No depletion chain specified and could not find depletion " - "chain in openmc.config['chain_file']" - ) - return Chain.from_xml(chain_file, fission_q) - else: + + # Resolve chain_file based on config if None + if chain_file is None: + chain_file = openmc.config.get('chain_file') + if 'chain_file' not in openmc.config: + raise DataError( + "No depletion chain specified and could not find depletion " + "chain in openmc.config['chain_file']" + ) + elif not isinstance(chain_file, PathLike): raise TypeError("chain_file must be path-like, a Chain, or None") + + # Determine the key for the cache, which consists of the absolute path, the + # file modification time, the file size, and the fission Q values. + chain_path = Path(chain_file).resolve() + stat_result = chain_path.stat() + fq_tuple = tuple(sorted(fission_q.items())) if fission_q else () + key = (chain_path, stat_result.st_mtime, stat_result.st_size, fq_tuple) + + # Check the global cache. If not cached, load the chain from XML and store + global _CHAIN_CACHE + if key not in _CHAIN_CACHE: + _CHAIN_CACHE[key] = Chain.from_xml(chain_path, fission_q) + return _CHAIN_CACHE[key] + + +def _invalidate_chain_cache(chain): + """Invalidate the cache for a specific Chain (when it is modifed).""" + if hasattr(chain, '_xml_path'): + # Remove all entries with the same path as self._xml_path + for key in list(_CHAIN_CACHE.keys()): + if str(key[0]) == chain._xml_path: + del _CHAIN_CACHE[key] diff --git a/openmc/deplete/microxs.py b/openmc/deplete/microxs.py index cbc8b1c8894..df78f8a267c 100644 --- a/openmc/deplete/microxs.py +++ b/openmc/deplete/microxs.py @@ -14,7 +14,6 @@ import numpy as np from openmc.checkvalue import check_type, check_value, check_iterable_type, PathLike -from openmc.utility_funcs import change_directory from openmc import StatePoint from openmc.mgxs import GROUP_STRUCTURES from openmc.data import REACTION_MT @@ -286,6 +285,10 @@ def from_multigroup_flux( sections available. MicroXS entry will be 0 if the nuclide cross section is not found. + It is recommended to make repeated calls to this method within a context + manager using the :class:`openmc.lib.TemporarySession` class to avoid + re-initializing OpenMC and loading cross sections each time. + .. versionadded:: 0.15.0 Parameters @@ -349,37 +352,23 @@ def from_multigroup_flux( # Create 3D array for microscopic cross sections microxs_arr = np.zeros((len(nuclides), len(mts), 1)) - # Create a material with all nuclides - mat_all_nucs = openmc.Material() - for nuc in nuclides: - if nuc in nuclides_with_data: - mat_all_nucs.add_nuclide(nuc, 1.0) - mat_all_nucs.set_density("atom/b-cm", 1.0) - - # Create simple model containing the above material - surf1 = openmc.Sphere(boundary_type="vacuum") - surf1_cell = openmc.Cell(fill=mat_all_nucs, region=-surf1) - model = openmc.Model() - model.geometry = openmc.Geometry([surf1_cell]) - model.settings = openmc.Settings( - particles=1, batches=1, output={'summary': False}) - - with change_directory(tmpdir=True): - # Export model within temporary directory - model.export_to_model_xml() - - with openmc.lib.run_in_memory(**init_kwargs): - # For each nuclide and reaction, compute the flux-averaged - # cross section - for nuc_index, nuc in enumerate(nuclides): - if nuc not in nuclides_with_data: - continue - lib_nuc = openmc.lib.nuclides[nuc] - for mt_index, mt in enumerate(mts): - xs = lib_nuc.collapse_rate( - mt, temperature, energies, multigroup_flux - ) - microxs_arr[nuc_index, mt_index, 0] = xs + def compute_microxs(): + # For each nuclide and reaction, compute the flux-averaged xs + for nuc_index, nuc in enumerate(nuclides): + if nuc not in nuclides_with_data: + continue + lib_nuc = openmc.lib.load_nuclide(nuc) + for mt_index, mt in enumerate(mts): + microxs_arr[nuc_index, mt_index, 0] = lib_nuc.collapse_rate( + mt, temperature, energies, multigroup_flux + ) + + # Compute microscopic cross sections within a temporary session + if not openmc.lib.is_initialized: + with openmc.lib.TemporarySession(**init_kwargs): + compute_microxs() + else: + compute_microxs() return cls(microxs_arr, nuclides, reactions) diff --git a/openmc/lib/core.py b/openmc/lib/core.py index 577913dcb8f..eac0e1784c6 100644 --- a/openmc/lib/core.py +++ b/openmc/lib/core.py @@ -4,7 +4,9 @@ c_uint64, c_size_t) import sys import os +from pathlib import Path from random import getrandbits +from tempfile import TemporaryDirectory import numpy as np from numpy.ctypeslib import as_array @@ -617,6 +619,69 @@ def run_in_memory(**kwargs): finalize() +class TemporarySession: + """Context manager for running via openmc.lib in a temporary directory. + + This class is useful for accessing functionality from openmc.lib without + polluting your current working directory with OpenMC files. It is used + internally as a persistent session to avoid loading cross sections multiple + times. + + Parameters + ---------- + model : openmc.Model, optional + OpenMC model to use for the session. If None, a minimal working model is + created. + **init_kwargs + Keyword arguments to pass to :func:`openmc.lib.init`. + + Attributes + ---------- + model : openmc.Model + The OpenMC model used for the session. + + """ + def __init__(self, model=None, **init_kwargs): + self.init_kwargs = init_kwargs + if model is None: + surf = openmc.Sphere(boundary_type="vacuum") + cell = openmc.Cell(region=-surf) + model = openmc.Model() + model.geometry = openmc.Geometry([cell]) + model.settings = openmc.Settings( + particles=1, batches=1, output={'summary': False}) + self.model = model + + def __enter__(self): + """Initialize the OpenMC library in a temporary directory.""" + # Make sure OpenMC is not already initialized + if openmc.lib.is_initialized: + raise RuntimeError("openmc.lib is already initialized.") + + # Store original working directory + self.orig_dir = Path.cwd() + + # Set up temporary directory + self.tmp_dir = TemporaryDirectory() + working_dir = Path(self.tmp_dir.name) + working_dir.mkdir(parents=True, exist_ok=True) + os.chdir(working_dir) + + # Export model and initialize OpenMC + self.model.export_to_model_xml() + openmc.lib.init(**self.init_kwargs) + + return self + + def __exit__(self, exc_type, exc_value, traceback): + """Finalize the OpenMC library and clean up temporary directory.""" + try: + openmc.lib.finalize() + finally: + os.chdir(self.orig_dir) + self.tmp_dir.cleanup() + + class _DLLGlobal: """Data descriptor that exposes global variables from libopenmc.""" def __init__(self, ctype, name): diff --git a/openmc/lib/nuclide.py b/openmc/lib/nuclide.py index 8078882cf35..ef1287cf34a 100644 --- a/openmc/lib/nuclide.py +++ b/openmc/lib/nuclide.py @@ -40,8 +40,14 @@ def load_nuclide(name): name : str Name of the nuclide, e.g. 'U235' + Returns + ------- + Nuclide + The class:`Nuclide` that was just loaded. + """ _dll.openmc_load_nuclide(name.encode(), None, 0) + return nuclides[name] class Nuclide(_FortranObject): diff --git a/openmc/mesh.py b/openmc/mesh.py index b046769fd0f..56fe2bd1c91 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -400,31 +400,30 @@ def material_volumes( """ import openmc.lib - with change_directory(tmpdir=True): - # In order to get mesh into model, we temporarily replace the - # tallies with a single mesh tally using the current mesh - original_tallies = model.tallies - new_tally = openmc.Tally() - new_tally.filters = [openmc.MeshFilter(self)] - new_tally.scores = ['flux'] - model.tallies = [new_tally] - - # Export model to XML - model.export_to_model_xml() - - # Get material volume fractions - kwargs.setdefault('output', True) - if 'args' in kwargs: - kwargs['args'] = ['-c'] + kwargs['args'] - kwargs.setdefault('args', ['-c']) - openmc.lib.init(**kwargs) + # In order to get mesh into model, we temporarily replace the + # tallies with a single mesh tally using the current mesh + original_tallies = model.tallies + new_tally = openmc.Tally() + new_tally.filters = [openmc.MeshFilter(self)] + new_tally.scores = ['flux'] + model.tallies = [new_tally] + + # Set default arguments + kwargs.setdefault('output', True) + if 'args' in kwargs: + kwargs['args'] = ['-c'] + kwargs['args'] + kwargs.setdefault('args', ['-c']) + + with openmc.lib.TemporarySession(model, **kwargs): + # Get mesh from single tally mesh = openmc.lib.tallies[new_tally.id].filters[0].mesh + + # Compute material volumes volumes = mesh.material_volumes( n_samples, max_materials, output=kwargs['output']) - openmc.lib.finalize() - # Restore original tallies - model.tallies = original_tallies + # Restore original tallies + model.tallies = original_tallies return volumes diff --git a/openmc/weight_windows.py b/openmc/weight_windows.py index 1b44747d574..90856ebab7f 100644 --- a/openmc/weight_windows.py +++ b/openmc/weight_windows.py @@ -1041,10 +1041,6 @@ def export_to_hdf5(self, path: PathLike = 'weight_windows.h5', **init_kwargs): # Get absolute path before moving to temporary directory path = Path(path).resolve() - with change_directory(tmpdir=True): - # Write the model to an XML file - model.export_to_model_xml() - - # Load the model with openmc.lib and then export it to an HDF5 file - with openmc.lib.run_in_memory(**init_kwargs): - openmc.lib.export_weight_windows(path) + # Load the model with openmc.lib and then export it to an HDF5 file + with openmc.lib.TemporarySession(model, **init_kwargs): + openmc.lib.export_weight_windows(path) diff --git a/src/nuclide.cpp b/src/nuclide.cpp index eaac2f756e7..7cb84640d62 100644 --- a/src/nuclide.cpp +++ b/src/nuclide.cpp @@ -1114,7 +1114,7 @@ extern "C" size_t nuclides_size() extern "C" int openmc_load_nuclide(const char* name, const double* temps, int n) { if (data::nuclide_map.find(name) == data::nuclide_map.end() || - data::nuclide_map.at(name) >= data::elements.size()) { + data::nuclide_map.at(name) >= data::nuclides.size()) { LibraryKey key {Library::Type::neutron, name}; const auto& it = data::library_map.find(key); if (it == data::library_map.end()) { @@ -1215,7 +1215,6 @@ extern "C" int openmc_nuclide_collapse_rate(int index, int MT, *xs = data::nuclides[index]->collapse_rate( MT, temperature, {energy, energy + n + 1}, {flux, flux + n}); } catch (const std::out_of_range& e) { - fmt::print("Caught error\n"); set_errmsg(e.what()); return OPENMC_E_OUT_OF_BOUNDS; } diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index 13267cb051e..bd5cc226352 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -310,8 +310,7 @@ def test_capture_branch_infer_ground(): # Create nuclide to be added into the chain xe136m = nuclide.Nuclide("Xe136_m1") - chain.nuclides.append(xe136m) - chain.nuclide_dict[xe136m.name] = len(chain.nuclides) - 1 + chain.add_nuclide(xe136m) chain.set_branch_ratios(infer_br, "(n,gamma)") @@ -327,8 +326,7 @@ def test_capture_branch_no_rxn(): u5m = nuclide.Nuclide("U235_m1") - chain.nuclides.append(u5m) - chain.nuclide_dict[u5m.name] = len(chain.nuclides) - 1 + chain.add_nuclide(u5m) with pytest.raises(AttributeError, match="U234"): chain.set_branch_ratios(u4br) From 6636b7e32b3347c5846aa26c434138eda6f0ee84 Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Thu, 3 Jul 2025 06:52:39 +0300 Subject: [PATCH 064/159] fix zam parsing (#3484) --- openmc/data/data.py | 2 +- tests/unit_tests/test_data_misc.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/openmc/data/data.py b/openmc/data/data.py index 8ccc8d26355..2142a5dc905 100644 --- a/openmc/data/data.py +++ b/openmc/data/data.py @@ -607,7 +607,7 @@ def zam(name): """ try: - symbol, A, state = _GNDS_NAME_RE.match(name).groups() + symbol, A, state = _GNDS_NAME_RE.fullmatch(name).groups() except AttributeError: raise ValueError(f"'{name}' does not appear to be a nuclide name in " "GNDS format") diff --git a/tests/unit_tests/test_data_misc.py b/tests/unit_tests/test_data_misc.py index 226d848a502..b65c704af8e 100644 --- a/tests/unit_tests/test_data_misc.py +++ b/tests/unit_tests/test_data_misc.py @@ -131,7 +131,8 @@ def test_zam(): assert openmc.data.zam('Am242_m10') == (95, 242, 10) with pytest.raises(ValueError): openmc.data.zam('garbage') - + with pytest.raises(ValueError): + openmc.data.zam('Am242-m1') def test_half_life(): assert openmc.data.half_life('H2') is None From efcf8f649e7be1d709a308c587d67c8bd58db6b8 Mon Sep 17 00:00:00 2001 From: Micah Gale Date: Thu, 3 Jul 2025 03:32:43 -0500 Subject: [PATCH 065/159] Added citation metadata file (#3409) --- CITATION.cff | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 CITATION.cff diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 00000000000..c1bc1443fb3 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,33 @@ +preferred-citation: + authors: + - family-names: Romano + given-names: Paul K. + orcid: "https://orcid.org/0000-0002-1147-045X" + - final-names: Horelik + given-names: Nicholas E. + - family-names: Herman + given-names: Bryan R. + - family-names: Nelson + given-names: Adam G. + - family-names: Forget + given-names: Benoit + orcid: "https://orcid.org/0000-0003-1459-7672" + - family-names: Smith + given-names: Kord + contact: + - family-names: Romano + given-names: Paul K. + orcid: "https://orcid.org/0000-0002-1147-045X" + date-published: 2015-08 + doi: 10.1016/j.anucene.2014.07.048 + issn: 1873-2100 + volume: 82 + journal: Annals of Nuclear Energy + publisher: + name: Elsevier + start: 90 + title: "OpenMC: A state-of-the-art Monte Carlo code for research and development" + type: article + url: "https://www.sciencedirect.com/science/article/pii/S030645491400379X" + volume: 10 + From b6c6ac078b391b3647510360529dcac0ac72c93a Mon Sep 17 00:00:00 2001 From: Nathan Glaser <123763273+nglaser3@users.noreply.github.com> Date: Thu, 3 Jul 2025 09:49:25 -0500 Subject: [PATCH 066/159] Add flag to CMakeLists to use submodules instead of searching (#3480) Co-authored-by: Paul Romano --- CMakeLists.txt | 38 +++++++++++++++++++++++------- docs/source/usersguide/install.rst | 5 ++++ 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6665682dd05..48f3bfc398b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,6 +38,7 @@ option(OPENMC_USE_LIBMESH "Enable support for libMesh unstructured mesh tall option(OPENMC_USE_MPI "Enable MPI" OFF) option(OPENMC_USE_MCPL "Enable MCPL" OFF) option(OPENMC_USE_UWUW "Enable UWUW" OFF) +option(OPENMC_FORCE_VENDORED_LIBS "Explicitly use submodules defined in 'vendor'" OFF) message(STATUS "OPENMC_USE_OPENMP ${OPENMC_USE_OPENMP}") message(STATUS "OPENMC_BUILD_TESTS ${OPENMC_BUILD_TESTS}") @@ -48,6 +49,7 @@ message(STATUS "OPENMC_USE_LIBMESH ${OPENMC_USE_LIBMESH}") message(STATUS "OPENMC_USE_MPI ${OPENMC_USE_MPI}") message(STATUS "OPENMC_USE_MCPL ${OPENMC_USE_MCPL}") message(STATUS "OPENMC_USE_UWUW ${OPENMC_USE_UWUW}") +message(STATUS "OPENMC_FORCE_VENDORED_LIBS ${OPENMC_FORCE_VENDORED_LIBS}") # Warnings for deprecated options foreach(OLD_OPT IN ITEMS "openmp" "profile" "coverage" "dagmc" "libmesh") @@ -256,31 +258,47 @@ endif() # pugixml library #=============================================================================== -find_package_write_status(pugixml) -if (NOT pugixml_FOUND) +if(OPENMC_FORCE_VENDORED_LIBS) add_subdirectory(vendor/pugixml) set_target_properties(pugixml PROPERTIES CXX_STANDARD 14 CXX_EXTENSIONS OFF) +else() + find_package_write_status(pugixml) + if (NOT pugixml_FOUND) + add_subdirectory(vendor/pugixml) + set_target_properties(pugixml PROPERTIES CXX_STANDARD 14 CXX_EXTENSIONS OFF) + endif() endif() #=============================================================================== # {fmt} library #=============================================================================== -find_package_write_status(fmt) -if (NOT fmt_FOUND) +if(OPENMC_FORCE_VENDORED_LIBS) set(FMT_INSTALL ON CACHE BOOL "Generate the install target.") add_subdirectory(vendor/fmt) +else() + find_package_write_status(fmt) + if (NOT fmt_FOUND) + set(FMT_INSTALL ON CACHE BOOL "Generate the install target.") + add_subdirectory(vendor/fmt) + endif() endif() #=============================================================================== # xtensor header-only library #=============================================================================== -find_package_write_status(xtensor) -if (NOT xtensor_FOUND) +if(OPENMC_FORCE_VENDORED_LIBS) add_subdirectory(vendor/xtl) set(xtl_DIR ${CMAKE_CURRENT_BINARY_DIR}/vendor/xtl) add_subdirectory(vendor/xtensor) +else() + find_package_write_status(xtensor) + if (NOT xtensor_FOUND) + add_subdirectory(vendor/xtl) + set(xtl_DIR ${CMAKE_CURRENT_BINARY_DIR}/vendor/xtl) + add_subdirectory(vendor/xtensor) + endif() endif() #=============================================================================== @@ -288,9 +306,13 @@ endif() #=============================================================================== if(OPENMC_BUILD_TESTS) - find_package_write_status(Catch2) - if (NOT Catch2_FOUND) + if (OPENMC_FORCE_VENDORED_LIBS) add_subdirectory(vendor/Catch2) + else() + find_package_write_status(Catch2) + if (NOT Catch2_FOUND) + add_subdirectory(vendor/Catch2) + endif() endif() endif() diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index 2621109f7c8..063c985f409 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -380,6 +380,11 @@ OPENMC_USE_MPI options, please see the `FindMPI.cmake documentation `_. +OPENMC_FORCE_VENDORED_LIBS + Forces OpenMC to use the submodules located in the vendor directory, as + opposed to searching the system for already installed versions of those + modules. + To set any of these options (e.g., turning on profiling), the following form should be used: From dd8d621f0b7d99db02e7b9dc855e514bac756096 Mon Sep 17 00:00:00 2001 From: April Novak Date: Thu, 3 Jul 2025 09:10:12 -0600 Subject: [PATCH 067/159] Only show warning if in restart mode. Refs #3477 (#3478) --- src/state_point.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/state_point.cpp b/src/state_point.cpp index 3b822715cff..12dfeb82ac6 100644 --- a/src/state_point.cpp +++ b/src/state_point.cpp @@ -436,7 +436,8 @@ extern "C" int openmc_statepoint_load(const char* filename) // Read batch number to restart at read_dataset(file_id, "current_batch", simulation::restart_batch); - if (simulation::restart_batch >= settings::n_max_batches) { + if (settings::restart_run && + simulation::restart_batch >= settings::n_max_batches) { warning(fmt::format( "The number of batches specified for simulation ({}) is smaller " "than or equal to the number of batches in the restart statepoint file " From d700d395de02d95190eab35514f25f9ea29d516f Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 3 Jul 2025 17:59:40 -0500 Subject: [PATCH 068/159] Update conda install instructions for macOS Apple silicon (#3488) --- CITATION.cff | 11 ++++++----- docs/source/quickinstall.rst | 7 +++++++ docs/source/usersguide/install.rst | 7 +++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index c1bc1443fb3..19b4213a15e 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -9,6 +9,7 @@ preferred-citation: given-names: Bryan R. - family-names: Nelson given-names: Adam G. + orcid: "https://orcid.org/0000-0002-3614-0676" - family-names: Forget given-names: Benoit orcid: "https://orcid.org/0000-0003-1459-7672" @@ -18,16 +19,16 @@ preferred-citation: - family-names: Romano given-names: Paul K. orcid: "https://orcid.org/0000-0002-1147-045X" - date-published: 2015-08 doi: 10.1016/j.anucene.2014.07.048 - issn: 1873-2100 + issn: 0306-4549 volume: 82 journal: Annals of Nuclear Energy publisher: name: Elsevier start: 90 + end: 97 + year: 2015 + month: 8 title: "OpenMC: A state-of-the-art Monte Carlo code for research and development" type: article - url: "https://www.sciencedirect.com/science/article/pii/S030645491400379X" - volume: 10 - + url: "https://doi.org/10.1016/j.anucene.2014.07.048" diff --git a/docs/source/quickinstall.rst b/docs/source/quickinstall.rst index 21526242f18..47c257dfe95 100644 --- a/docs/source/quickinstall.rst +++ b/docs/source/quickinstall.rst @@ -35,6 +35,13 @@ you wish) with OpenMC installed. conda create --name openmc-env openmc conda activate openmc-env +If you are installing on macOS with an Apple silicon ARM-based processor, you +will also need to specify the `--platform` option: + +.. code-block:: sh + + conda create --name openmc-env --platform osx-arm64 openmc + You are now in a conda environment called `openmc-env` that has OpenMC installed. diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index 063c985f409..775e3810914 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -35,6 +35,13 @@ you wish) with OpenMC installed. conda create --name openmc-env openmc conda activate openmc-env +If you are installing on macOS with an Apple silicon ARM-based processor, you +will also need to specify the `--platform` option: + +.. code-block:: sh + + conda create --name openmc-env --platform osx-arm64 openmc + You are now in a conda environment called `openmc-env` that has OpenMC installed. From 58ee8d825dfc6d5bcb98a0156a46e023a84e9aea Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Tue, 15 Jul 2025 16:53:52 -0400 Subject: [PATCH 069/159] Update OSX install instructions to point to x64 platform (#3501) --- docs/source/quickinstall.rst | 2 +- docs/source/usersguide/install.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/quickinstall.rst b/docs/source/quickinstall.rst index 47c257dfe95..0f887940ea5 100644 --- a/docs/source/quickinstall.rst +++ b/docs/source/quickinstall.rst @@ -40,7 +40,7 @@ will also need to specify the `--platform` option: .. code-block:: sh - conda create --name openmc-env --platform osx-arm64 openmc + conda create --name openmc-env --platform osx-64 openmc You are now in a conda environment called `openmc-env` that has OpenMC installed. diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index 775e3810914..cec3a541db1 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -40,7 +40,7 @@ will also need to specify the `--platform` option: .. code-block:: sh - conda create --name openmc-env --platform osx-arm64 openmc + conda create --name openmc-env --platform osx-64 openmc You are now in a conda environment called `openmc-env` that has OpenMC installed. From 6372c29cfad804ce49ac79179659eff9d9cddfcd Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 16 Jul 2025 08:31:49 -0500 Subject: [PATCH 070/159] Provide a way to get ID maps from plot parameters on the Model class (#3481) Co-authored-by: Jonathan Shimwell Co-authored-by: Paul Romano --- openmc/lib/plot.py | 1 + openmc/model/model.py | 173 ++++++++++++++++------- openmc/plots.py | 13 +- tests/unit_tests/test_model.py | 243 +++++++++++++++++++++++++++++++++ 4 files changed, 378 insertions(+), 52 deletions(-) diff --git a/openmc/lib/plot.py b/openmc/lib/plot.py index d9863667641..f97348b20bb 100644 --- a/openmc/lib/plot.py +++ b/openmc/lib/plot.py @@ -90,6 +90,7 @@ class _PlotBase(Structure): def __init__(self): self.level_ = -1 + self.basis_ = 1 self.color_overlaps_ = False @property diff --git a/openmc/model/model.py b/openmc/model/model.py index 55f309bfbd3..93e029a335e 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -20,7 +20,7 @@ from openmc.executor import _process_CLI_arguments from openmc.checkvalue import check_type, check_value, PathLike from openmc.exceptions import InvalidIDError -from openmc.plots import add_plot_params +from openmc.plots import add_plot_params, _BASIS_INDICES from openmc.utility_funcs import change_directory @@ -902,6 +902,111 @@ def calculate_volumes( openmc.lib.materials[domain_id].volume = \ vol_calc.volumes[domain_id].n + + def _set_plot_defaults( + self, + origin: Sequence[float] | None, + width: Sequence[float] | None, + pixels: int | Sequence[int], + basis: str + ): + x, y, _ = _BASIS_INDICES[basis] + + bb = self.bounding_box + # checks to see if bounding box contains -inf or inf values + if np.isinf(bb.extent[basis]).any(): + if origin is None: + origin = (0, 0, 0) + if width is None: + width = (10, 10) + else: + if origin is None: + # if nan values in the bb.center they get replaced with 0.0 + # this happens when the bounding_box contains inf values + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + origin = np.nan_to_num(bb.center) + if width is None: + bb_width = bb.width + width = (bb_width[x], bb_width[y]) + + if isinstance(pixels, int): + aspect_ratio = width[0] / width[1] + pixels_y = math.sqrt(pixels / aspect_ratio) + pixels = (int(pixels / pixels_y), int(pixels_y)) + + return origin, width, pixels + + def id_map( + self, + origin: Sequence[float] | None = None, + width: Sequence[float] | None = None, + pixels: int | Sequence[int] = 40000, + basis: str = 'xy', + **init_kwargs + ) -> np.ndarray: + """Generate an ID map for domains based on the plot parameters + + If the model is not yet initialized, it will be initialized with + openmc.lib. If the model is initialized, the model will remain + initialized after this method call exits. + + .. versionadded:: 0.15.3 + + Parameters + ---------- + origin : Sequence[float], optional + Origin of the plot. If unspecified, this argument defaults to the + center of the bounding box if the bounding box does not contain inf + values for the provided basis, otherwise (0.0, 0.0, 0.0). + width : Sequence[float], optional + Width of the plot. If unspecified, this argument defaults to the + width of the bounding box if the bounding box does not contain inf + values for the provided basis, otherwise (10.0, 10.0). + pixels : int | Sequence[int], optional + If an iterable of ints is provided then this directly sets the + number of pixels to use in each basis direction. If a single int is + provided then this sets the total number of pixels in the plot and + the number of pixels in each basis direction is calculated from this + total and the image aspect ratio based on the width argument. + basis : {'xy', 'yz', 'xz'}, optional + Basis of the plot. + **init_kwargs + Keyword arguments passed to :meth:`Model.init_lib`. + + Returns + ------- + id_map : numpy.ndarray + A NumPy array with shape (vertical pixels, horizontal pixels, 3) of + OpenMC property IDs with dtype int32. The last dimension of the + array contains cell IDs, cell instances, and material IDs (in that + order). + """ + import openmc.lib + + origin, width, pixels = self._set_plot_defaults( + origin, width, pixels, basis) + + # initialize the openmc.lib.plot._PlotBase object + plot_obj = openmc.lib.plot._PlotBase() + plot_obj.origin = origin + plot_obj.width = width[0] + plot_obj.height = width[1] + plot_obj.h_res = pixels[0] + plot_obj.v_res = pixels[1] + plot_obj.basis = basis + + if self.is_initialized: + return openmc.lib.id_map(plot_obj) + else: + # Silence output by default. Also set arguments to start in volume + # calculation mode to avoid loading cross sections + init_kwargs.setdefault('output', False) + init_kwargs.setdefault('args', ['-c']) + + with openmc.lib.TemporarySession(self, **init_kwargs): + return openmc.lib.id_map(plot_obj) + @add_plot_params def plot( self, @@ -945,39 +1050,13 @@ def plot( source_kwargs = {} source_kwargs.setdefault('marker', 'x') - # Determine extents of plot - if basis == 'xy': - x, y, z = 0, 1, 2 - xlabel, ylabel = f'x [{axis_units}]', f'y [{axis_units}]' - elif basis == 'yz': - x, y, z = 1, 2, 0 - xlabel, ylabel = f'y [{axis_units}]', f'z [{axis_units}]' - elif basis == 'xz': - x, y, z = 0, 2, 1 - xlabel, ylabel = f'x [{axis_units}]', f'z [{axis_units}]' + # Set indices using basis and create axis labels + x, y, z = _BASIS_INDICES[basis] + xlabel, ylabel = f'{basis[0]} [{axis_units}]', f'{basis[1]} [{axis_units}]' - bb = self.bounding_box - # checks to see if bounding box contains -inf or inf values - if np.isinf(bb.extent[basis]).any(): - if origin is None: - origin = (0, 0, 0) - if width is None: - width = (10, 10) - else: - if origin is None: - # if nan values in the bb.center they get replaced with 0.0 - # this happens when the bounding_box contains inf values - with warnings.catch_warnings(): - warnings.simplefilter("ignore", RuntimeWarning) - origin = np.nan_to_num(bb.center) - if width is None: - bb_width = bb.width - width = (bb_width[x], bb_width[y]) - - if isinstance(pixels, int): - aspect_ratio = width[0] / width[1] - pixels_y = math.sqrt(pixels / aspect_ratio) - pixels = (int(pixels / pixels_y), int(pixels_y)) + # Determine extents of plot + origin, width, pixels = self._set_plot_defaults( + origin, width, pixels, basis) axis_scaling_factor = {'km': 0.00001, 'm': 0.01, 'cm': 1, 'mm': 10} @@ -1124,10 +1203,10 @@ def plot( return axes def sample_external_source( - self, - n_samples: int = 1000, - prn_seed: int | None = None, - **init_kwargs + self, + n_samples: int = 1000, + prn_seed: int | None = None, + **init_kwargs ) -> openmc.ParticleList: """Sample external source and return source particles. @@ -1150,17 +1229,17 @@ def sample_external_source( """ import openmc.lib - # Silence output by default. Also set arguments to start in volume - # calculation mode to avoid loading cross sections - init_kwargs.setdefault('output', False) - init_kwargs.setdefault('args', ['-c']) - - with change_directory(tmpdir=True): - # Export model within temporary directory - self.export_to_model_xml() + if self.is_initialized: + return openmc.lib.sample_external_source( + n_samples=n_samples, prn_seed=prn_seed + ) + else: + # Silence output by default. Also set arguments to start in volume + # calculation mode to avoid loading cross sections + init_kwargs.setdefault('output', False) + init_kwargs.setdefault('args', ['-c']) - # Sample external source sites - with openmc.lib.run_in_memory(**init_kwargs): + with openmc.lib.TemporarySession(self, **init_kwargs): return openmc.lib.sample_external_source( n_samples=n_samples, prn_seed=prn_seed ) diff --git a/openmc/plots.py b/openmc/plots.py index 40fcf0c78da..9e097e2b9bf 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -15,6 +15,8 @@ _BASES = {'xy', 'xz', 'yz'} +_BASIS_INDICES = {'xy': (0, 1, 2), 'xz': (0, 2, 1), 'yz': (1, 2, 0)} + _SVG_COLORS = { 'aliceblue': (240, 248, 255), 'antiquewhite': (250, 235, 215), @@ -178,11 +180,12 @@ ascertain the plot width. Defaults to (10, 10) if the bounding box contains inf values. pixels : Iterable of int or int - If iterable of ints provided then this directly sets the number of - pixels to use in each basis direction. If int provided then this - sets the total number of pixels in the plot and the number of - pixels in each basis direction is calculated from this total and - the image aspect ratio. + If an iterable of ints is provided then this directly sets the + number of pixels to use in each basis direction. If a single int + is provided then this sets the total number of pixels in the plot + and the number of pixels in each basis direction is calculated + from this total and the image aspect ratio based on the width + argument. basis : {'xy', 'xz', 'yz'} The basis directions for the plot color_by : {'cell', 'material'} diff --git a/tests/unit_tests/test_model.py b/tests/unit_tests/test_model.py index aa844e2f21e..3797188ba6d 100644 --- a/tests/unit_tests/test_model.py +++ b/tests/unit_tests/test_model.py @@ -649,3 +649,246 @@ def test_model_plot(): # ensure that all of the data in the image data is either white or red test_mask = (image_data == white) | (image_data == red) assert np.all(test_mask), "Colors other than white or red found in overlap plot image" + + +def test_model_id_map_initialization(run_in_tmpdir): + model = openmc.examples.pwr_assembly() + model.init_lib(output=False) + + id_map = model.id_map( + pixels=(100, 100), + basis='xy', + origin=(0, 0, 0), + width=(10, 10), + ) + + assert id_map.shape == (100, 100, 3) + assert id_map.dtype == np.int32 + + max_cell_id = max(model.geometry.get_all_cells().keys()) + max_material_id = max(model.geometry.get_all_materials().keys()) + + # add some spot checks for the id_map + # Check that the array contains valid cell/material IDs (not all -2) + # The -2 values indicate outside the geometry + assert not np.all(id_map == -2), "All values are -2, indicating no valid geometry found" + + # Check that we have valid cell IDs (first dimension) + valid_cell_ids = id_map[:, :, 0] + assert np.any(valid_cell_ids >= 0), "No valid cell IDs found in the id_map" + + # Check that we have valid material IDs (third dimension) + valid_material_ids = id_map[:, :, 2] + assert np.any(valid_material_ids >= 0), "No valid material IDs found in the id_map" + + # Check that the middle dimension (cell instances) is consistent + # Cell instances should be >= 0 when cell IDs are valid + cell_instances = id_map[:, :, 1] + valid_cells = valid_cell_ids >= 0 + if np.any(valid_cells): + assert np.all(cell_instances[valid_cells] >= 0), "Invalid cell instances found for valid cells" + + # Check that the array contains reasonable ranges of values + # Cell IDs should be within the expected range for the assembly + if np.any(valid_cell_ids >= 0): + max_map_cell_id = np.max(valid_cell_ids) + assert max_map_cell_id <= max_cell_id, \ + f"Cell ID {max_map_cell_id} in the map is greater than the maximum cell ID {max_cell_id}" + + # Material IDs should be within the expected range + if np.any(valid_material_ids >= 0): + max_map_material_id = np.max(valid_material_ids) + assert max_map_material_id <= max_material_id, \ + f"Material ID {max_map_material_id} in the map is greater than the maximum material ID {max_material_id}" + + # Test id_map with pixels outside the model geometry + # Use a plot that's far from the model center to ensure we get -2 values + outside_id_map = model.id_map( + pixels=(50, 50), + basis='xy', + origin=(1000, 1000, 0), # Far from the model center + width=(10, 10), + ) + + assert outside_id_map.shape == (50, 50, 3) + assert outside_id_map.dtype == np.int32 + + # All values should be -2 (outside geometry) for this plot + assert np.all(outside_id_map == -2), "Expected all values to be -2 for plot outside model geometry" + + # Verify that the outside plot has the correct structure + assert np.all(outside_id_map[:, :, 0] == -2), "Cell IDs should all be -2 outside geometry" + assert np.all(outside_id_map[:, :, 1] == -2), "Cell instances should all be -2 outside geometry" + assert np.all(outside_id_map[:, :, 2] == -2), "Material IDs should all be -2 outside geometry" + + # if the model is already initialized, it should not be finalized + # after calling this method + model.id_map( + pixels=(100, 100), + basis='xy', + origin=(0, 0, 0), + width=(10, 10), + ) + assert model.is_initialized + + # if the model is not initialized, it should be finalized + # before exiting this method + model.finalize_lib() + model.id_map( + pixels=(100, 100), + basis='xy', + origin=(0, 0, 0), + width=(10, 10), + ) + assert not model.is_initialized + + +def test_id_map_aligned_model(): + """Test id_map with a 2x2 lattice where pixel boundaries align to cell boundaries""" + # Create materials -- identical compositions, different IDs + mat1 = openmc.Material(material_id=1, name='Material 1') + mat1.set_density('g/cm3', 1.0) + mat1.add_element('H', 1.0) + + mat2 = openmc.Material(material_id=2, name='Material 2') + mat2.set_density('g/cm3', 1.0) + mat2.add_element('H', 1.0) + + mat3 = openmc.Material(material_id=3, name='Material 3') + mat3.set_density('g/cm3', 1.0) + mat3.add_element('H', 1.0) + + mat4 = openmc.Material(material_id=4, name='Material 4') + mat4.set_density('g/cm3', 1.0) + mat4.add_element('H', 1.0) + + outer_mat = openmc.Material(material_id=5, name='Material 5') + outer_mat.set_density('g/cm3', 1.0) + outer_mat.add_element('H', 1.0) + + inner_materials = [mat1, mat2, mat3, mat4] + + # Create square surface that fits inside the lattice cell + # Lattice cell is 1 cm x 1 cm, so square will be 0.6 cm x 0.6 cm centered on the origin + square = openmc.model.RectangularPrism(0.6, 0.6, boundary_type='transmission') + + # Create cells for this universe + inner_cell = openmc.Cell(cell_id=10, region=-square, name='inner_cell') + inner_cell.fill = inner_materials + + outer_cell = openmc.Cell(cell_id=20, region=+square, name='outer_cell') + outer_cell.fill = outer_mat + + # Create universe + universe = openmc.Universe(universe_id=100, cells=[inner_cell, outer_cell]) + + # Create 2x2 lattice + lattice = openmc.RectLattice(lattice_id=1) + lattice.lower_left = [-1.0, -1.0] + lattice.pitch = [1.0, 1.0] + lattice.universes = [[universe, universe], [universe, universe]] + + # Create outer boundary + outer_boundary = openmc.model.RectangularPrism(2.0, 2.0, boundary_type='vacuum') + + # Create root cell + root_cell = openmc.Cell(cell_id=1, name='root', fill=lattice, region=-outer_boundary) + + # Create geometry + geometry = openmc.Geometry([root_cell]) + + # Create settings + settings = openmc.Settings() + settings.particles = 1000 + settings.batches = 10 + + # Create model + model = openmc.Model(settings=settings, geometry=geometry) + + # Generate id_map with pixel boundaries aligned to cell boundaries + # The model is 2 cm x 2 cm, so we'll use 200x200 pixels to get 0.01 cm resolution + # This allows us to align pixels with the squares inside each lattice cell + id_map = model.id_map( + pixels=(200, 200), + basis='xy', + origin=(0.0, 0.0, 0.0), # Align with lattice lower_left + width=(2.0, 2.0), # Align with lattice size + ) + + # Verify id_map properties + assert id_map.shape == (200, 200, 3) + assert id_map.dtype == np.int32 + + cell_id_map = id_map[:, :, 0] + material_ids_map = id_map[:, :, 2] + + # Check that we have valid cell IDs (not all -2) + assert np.any(cell_id_map >= 0), "No valid cell IDs found in the id_map" + + # Check that we have valid material IDs + assert np.any(material_ids_map >= 0), "No valid material IDs found in the id_map" + + # Check that the expected cell IDs are present + expected_cell_ids = [10, 20] # Root cell, inner cell, outer cell + found_cell_ids = np.unique(cell_id_map[cell_id_map >= 0]) + for cell_id in expected_cell_ids: + assert cell_id in found_cell_ids, f"Expected cell ID {cell_id} not found in id_map" + + # Check that the expected material IDs are present + expected_material_ids = [1, 2, 3, 4, 5] # All materials defined above + found_material_ids = np.unique(material_ids_map[material_ids_map >= 0]) + for mat_id in expected_material_ids: + assert mat_id in found_material_ids, f"Expected material ID {mat_id} not found in id_map" + + # Test specific regions to verify lattice structure + # Check center of each lattice cell (should be inner cells) + # Lattice cell centers are at (-0.5, -0.5), (0.5, -0.5), (-0.5, 0.5), (0.5, 0.5) + # With 200x200 pixels over 2x2 units, each pixel is 0.01 units + + # Bottom-left lattice cell center (should be inner cell 10) + bl_cell, bl_instance, bl_material = id_map[-50, 50] + assert bl_cell == 10, f"Expected cell ID 10 at bottom-left center, got {bl_cell}" + assert bl_instance == 0, f"Expected cell instance 0 at bottom-left center, got {bl_instance}" + assert bl_material == 1, f"Expected material ID 1 at bottom-left center, got {bl_material}" + + # Bottom-right lattice cell center (should be inner cell 10) + br_cell, br_instance, br_material = id_map[-50, 150] + assert br_cell == 10, f"Expected cell ID 10 at bottom-right center, got {br_cell}" + assert br_instance == 1, f"Expected cell instance 1 at bottom-right center, got {br_instance}" + assert br_material == 2, f"Expected material ID 2 at bottom-right center, got {br_material}" + + # Top-left lattice cell center (should be inner cell 10) + tl_cell, tl_instance, tl_material = id_map[-150, 50] + assert tl_cell == 10, f"Expected cell ID 10 at top-left center, got {tl_cell}" + assert tl_instance == 2, f"Expected cell instance 2 at top-left center, got {tl_instance}" + assert tl_material == 3, f"Expected material ID 3 at top-left center, got {tl_material}" + + # Top-right lattice cell center (should be inner cell 10) + tr_cell, tr_instance, tr_material = id_map[-150, 150] + assert tr_cell == 10, f"Expected cell ID 10 at top-right center, got {tr_cell}" + assert tr_instance == 3, f"Expected cell instance 3 at top-right center, got {tr_instance}" + assert tr_material == 4, f"Expected material ID 4 at top-right center, got {tr_material}" + + # Check that the model is properly finalized after id_map call + assert not model.is_initialized, "Model should be finalized after id_map call" + + # Check that the values at the corners are correctly set as the outer cell and material + bl_cell, bl_instance, bl_material = id_map[-1, 0] + assert bl_cell == 20, f"Expected cell ID 20 at bottom-left corner, got {bl_cell}" + assert bl_instance == 0, f"Expected cell instance 0 at bottom-left corner, got {bl_instance}" + assert bl_material == 5, f"Expected material ID 5 at bottom-left corner, got {bl_material}" + + br_cell, br_instance, br_material = id_map[-1, -1] + assert br_cell == 20, f"Expected cell ID 20 at bottom-right corner, got {br_cell}" + assert br_instance == 1, f"Expected cell instance 1 at bottom-right corner, got {br_instance}" + assert br_material == 5, f"Expected material ID 5 at bottom-right corner, got {br_material}" + + tl_cell, tl_instance, tl_material = id_map[0, 0] + assert tl_cell == 20, f"Expected cell ID 20 at top-left corner, got {tl_cell}" + assert tl_instance == 2, f"Expected cell instance 2 at top-left corner, got {tl_instance}" + assert tl_material == 5, f"Expected material ID 5 at top-left corner, got {tl_material}" + + tr_cell, tr_instance, tr_material = id_map[0, -1] + assert tr_cell == 20, f"Expected cell ID 20 at top-right corner, got {tr_cell}" + assert tr_instance == 3, f"Expected cell instance 3 at top-right corner, got {tr_instance}" + assert tr_material == 5, f"Expected material ID 5 at top-right corner, got {tr_material}" From 24f78e6e924f5163e5e45dd307f8819d78073bfe Mon Sep 17 00:00:00 2001 From: Perry <100789850+yrrepy@users.noreply.github.com> Date: Wed, 16 Jul 2025 17:40:02 -0700 Subject: [PATCH 071/159] Use auto-chunking for StepResult HDF5 writing (#3498) --- openmc/deplete/stepresult.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openmc/deplete/stepresult.py b/openmc/deplete/stepresult.py index 9cf33898f3b..7c86b86458c 100644 --- a/openmc/deplete/stepresult.py +++ b/openmc/deplete/stepresult.py @@ -328,13 +328,13 @@ def _write_hdf5_metadata(self, handle): handle.create_dataset("number", (1, n_stages, n_mats, n_nuc_number), maxshape=(None, n_stages, n_mats, n_nuc_number), - chunks=(1, 1, n_mats, n_nuc_number), + chunks=True, dtype='float64') if n_nuc_rxn > 0 and n_rxn > 0: handle.create_dataset("reaction rates", (1, n_stages, n_mats, n_nuc_rxn, n_rxn), maxshape=(None, n_stages, n_mats, n_nuc_rxn, n_rxn), - chunks=(1, 1, n_mats, n_nuc_rxn, n_rxn), + chunks=True, dtype='float64') handle.create_dataset("eigenvalues", (1, n_stages, 2), From 5318ea6e2b46000f3805d29edfbae1530694951d Mon Sep 17 00:00:00 2001 From: Ahnaf Tahmid Chowdhury Date: Thu, 17 Jul 2025 08:46:39 +0600 Subject: [PATCH 072/159] Make MCPL a Runtime Optional Dependency (#3429) Co-authored-by: Paul Romano --- CMakeLists.txt | 23 - cmake/OpenMCConfig.cmake.in | 4 - docs/source/usersguide/install.rst | 4 - include/openmc/mcpl_interface.h | 22 +- openmc/lib/__init__.py | 3 - src/mcpl_interface.cpp | 533 ++++++++++++++---- src/settings.cpp | 12 - .../source_mcpl_file/settings.xml | 9 +- .../regression_tests/source_mcpl_file/test.py | 27 +- 9 files changed, 436 insertions(+), 201 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 48f3bfc398b..2b863eb0871 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,7 +36,6 @@ option(OPENMC_ENABLE_COVERAGE "Compile with coverage analysis flags" option(OPENMC_USE_DAGMC "Enable support for DAGMC (CAD) geometry" OFF) option(OPENMC_USE_LIBMESH "Enable support for libMesh unstructured mesh tallies" OFF) option(OPENMC_USE_MPI "Enable MPI" OFF) -option(OPENMC_USE_MCPL "Enable MCPL" OFF) option(OPENMC_USE_UWUW "Enable UWUW" OFF) option(OPENMC_FORCE_VENDORED_LIBS "Explicitly use submodules defined in 'vendor'" OFF) @@ -47,7 +46,6 @@ message(STATUS "OPENMC_ENABLE_COVERAGE ${OPENMC_ENABLE_COVERAGE}") message(STATUS "OPENMC_USE_DAGMC ${OPENMC_USE_DAGMC}") message(STATUS "OPENMC_USE_LIBMESH ${OPENMC_USE_LIBMESH}") message(STATUS "OPENMC_USE_MPI ${OPENMC_USE_MPI}") -message(STATUS "OPENMC_USE_MCPL ${OPENMC_USE_MCPL}") message(STATUS "OPENMC_USE_UWUW ${OPENMC_USE_UWUW}") message(STATUS "OPENMC_FORCE_VENDORED_LIBS ${OPENMC_FORCE_VENDORED_LIBS}") @@ -191,22 +189,6 @@ if(${HDF5_VERSION} VERSION_GREATER_EQUAL 1.12.0) list(APPEND cxxflags -DH5Oget_info_by_idx_vers=1 -DH5O_info_t_vers=1) endif() -#=============================================================================== -# MCPL -#=============================================================================== - -if (OPENMC_USE_MCPL) - if (NOT DEFINED MCPL_DIR) - execute_process( - COMMAND mcpl-config --show cmakedir - OUTPUT_VARIABLE MCPL_DIR - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - endif() - find_package(MCPL REQUIRED) - message(STATUS "Found MCPL: ${MCPL_DIR} (found version \"${MCPL_VERSION}\")") -endif() - #=============================================================================== # Set compile/link flags based on which compiler is being used #=============================================================================== @@ -557,11 +539,6 @@ if (OPENMC_BUILD_TESTS) add_subdirectory(tests/cpp_unit_tests) endif() -if (OPENMC_USE_MCPL) - target_compile_definitions(libopenmc PUBLIC OPENMC_MCPL) - target_link_libraries(libopenmc MCPL::mcpl) -endif() - #=============================================================================== # Log build info that this executable can report later #=============================================================================== diff --git a/cmake/OpenMCConfig.cmake.in b/cmake/OpenMCConfig.cmake.in index 7416013fbc6..3fe0c1bcd16 100644 --- a/cmake/OpenMCConfig.cmake.in +++ b/cmake/OpenMCConfig.cmake.in @@ -29,10 +29,6 @@ if(@OPENMC_USE_OPENMP@) find_package(OpenMP REQUIRED) endif() -if(@OPENMC_USE_MCPL@) - find_package(MCPL REQUIRED HINTS @MCPL_DIR@) -endif() - if(@OPENMC_USE_UWUW@ AND NOT ${DAGMC_BUILD_UWUW}) message(FATAL_ERROR "UWUW is enabled in OpenMC but the DAGMC installation discovered was not configured with UWUW.") endif() diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index cec3a541db1..64d48017993 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -375,10 +375,6 @@ OPENMC_USE_DAGMC should also be defined as `DAGMC_ROOT` in the CMake configuration command. (Default: off) -OPENMC_USE_MCPL - Turns on support for reading MCPL_ source files and writing MCPL source points - and surface sources. (Default: off) - OPENMC_USE_LIBMESH Enables the use of unstructured mesh tallies with libMesh_. (Default: off) diff --git a/include/openmc/mcpl_interface.h b/include/openmc/mcpl_interface.h index e5c182280c8..f7323e10a67 100644 --- a/include/openmc/mcpl_interface.h +++ b/include/openmc/mcpl_interface.h @@ -9,12 +9,6 @@ namespace openmc { -//============================================================================== -// Constants -//============================================================================== - -extern "C" const bool MCPL_ENABLED; - //============================================================================== // Functions //============================================================================== @@ -29,14 +23,18 @@ vector mcpl_source_sites(std::string path); // //! \param[in] filename Path to MCPL file //! \param[in] source_bank Vector of SourceSites to write to file for this -//! MPI rank. Note that this can't be const due to -//! it being used as work space by MPI. -//! \param[in] bank_indx Pointer to vector of site index ranges over all -//! MPI ranks. This can be computed by calling -//! calculate_parallel_index_vector on -//! source_bank.size(). +//! MPI rank. +//! \param[in] bank_index Pointer to vector of site index ranges over all +//! MPI ranks. void write_mcpl_source_point(const char* filename, span source_bank, const vector& bank_index); + +//! Check if MCPL functionality is available +bool is_mcpl_interface_available(); + +//! Initialize the MCPL interface +void initialize_mcpl_interface_if_needed(); + } // namespace openmc #endif // OPENMC_MCPL_INTERFACE_H diff --git a/openmc/lib/__init__.py b/openmc/lib/__init__.py index 15642b42be3..d2a794eb158 100644 --- a/openmc/lib/__init__.py +++ b/openmc/lib/__init__.py @@ -46,9 +46,6 @@ def _coord_levels(): def _libmesh_enabled(): return c_bool.in_dll(_dll, "LIBMESH_ENABLED").value -def _mcpl_enabled(): - return c_bool.in_dll(_dll, "MCPL_ENABLED").value - def _uwuw_enabled(): return c_bool.in_dll(_dll, "UWUW_ENABLED").value diff --git a/src/mcpl_interface.cpp b/src/mcpl_interface.cpp index 83ef6332097..13915c3b8e9 100644 --- a/src/mcpl_interface.cpp +++ b/src/mcpl_interface.cpp @@ -11,32 +11,292 @@ #include -#ifdef OPENMC_MCPL -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else +#include #endif +// WARNING: These declarations MUST EXACTLY MATCH the structure and function +// signatures of the libmcpl being loaded at runtime. Any discrepancy will +// likely lead to crashes or incorrect behavior. This is a maintenance risk. +// MCPL 2.2.0 + +#pragma pack(push, 1) +struct mcpl_particle_repr_t { + double ekin; + double polarisation[3]; + double position[3]; + double direction[3]; + double time; + double weight; + int32_t pdgcode; + uint32_t userflags; +}; +#pragma pack(pop) + +// Opaque struct definitions replicating the MCPL C-API to ensure ABI +// compatibility without including mcpl.h. These must be kept in sync. +struct mcpl_file_t { + void* internal; +}; +struct mcpl_outfile_t { + void* internal; +}; + +// Function pointer types for the dynamically loaded MCPL library +using mcpl_open_file_fpt = mcpl_file_t* (*)(const char* filename); +using mcpl_hdr_nparticles_fpt = uint64_t (*)(mcpl_file_t* file_handle); +using mcpl_read_fpt = const mcpl_particle_repr_t* (*)(mcpl_file_t* file_handle); +using mcpl_close_file_fpt = void (*)(mcpl_file_t* file_handle); + +using mcpl_create_outfile_fpt = mcpl_outfile_t* (*)(const char* filename); +using mcpl_hdr_set_srcname_fpt = void (*)( + mcpl_outfile_t* outfile_handle, const char* srcname); +using mcpl_add_particle_fpt = void (*)( + mcpl_outfile_t* outfile_handle, const mcpl_particle_repr_t* particle); +using mcpl_close_outfile_fpt = void (*)(mcpl_outfile_t* outfile_handle); + namespace openmc { -//============================================================================== -// Constants -//============================================================================== +#ifdef _WIN32 +using LibraryHandleType = HMODULE; +#else +using LibraryHandleType = void*; +#endif -#ifdef OPENMC_MCPL -const bool MCPL_ENABLED = true; +std::string get_last_library_error() +{ +#ifdef _WIN32 + DWORD error_code = GetLastError(); + if (error_code == 0) + return "No error reported by system."; // More accurate than "No error." + LPSTR message_buffer = nullptr; + size_t size = + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&message_buffer, 0, NULL); + std::string message(message_buffer, size); + LocalFree(message_buffer); + while ( + !message.empty() && (message.back() == '\n' || message.back() == '\r')) { + message.pop_back(); + } + return message; #else -const bool MCPL_ENABLED = false; + const char* err = dlerror(); + return err ? std::string(err) : "No error reported by dlerror."; #endif +} -//============================================================================== -// Functions -//============================================================================== +struct McplApi { + mcpl_open_file_fpt open_file; + mcpl_hdr_nparticles_fpt hdr_nparticles; + mcpl_read_fpt read; + mcpl_close_file_fpt close_file; + mcpl_create_outfile_fpt create_outfile; + mcpl_hdr_set_srcname_fpt hdr_set_srcname; + mcpl_add_particle_fpt add_particle; + mcpl_close_outfile_fpt close_outfile; + + explicit McplApi(LibraryHandleType lib_handle) + { + if (!lib_handle) + throw std::runtime_error( + "MCPL library handle is null during API binding."); + + auto load_symbol_platform = [lib_handle](const char* name) { + void* sym = nullptr; +#ifdef _WIN32 + sym = (void*)GetProcAddress(lib_handle, name); +#else + sym = dlsym(lib_handle, name); +#endif + if (!sym) { + throw std::runtime_error( + fmt::format("Failed to load MCPL symbol '{}': {}", name, + get_last_library_error())); + } + return sym; + }; + + open_file = reinterpret_cast( + load_symbol_platform("mcpl_open_file")); + hdr_nparticles = reinterpret_cast( + load_symbol_platform("mcpl_hdr_nparticles")); + read = reinterpret_cast(load_symbol_platform("mcpl_read")); + close_file = reinterpret_cast( + load_symbol_platform("mcpl_close_file")); + create_outfile = reinterpret_cast( + load_symbol_platform("mcpl_create_outfile")); + hdr_set_srcname = reinterpret_cast( + load_symbol_platform("mcpl_hdr_set_srcname")); + add_particle = reinterpret_cast( + load_symbol_platform("mcpl_add_particle")); + close_outfile = reinterpret_cast( + load_symbol_platform("mcpl_close_outfile")); + } +}; + +static LibraryHandleType g_mcpl_lib_handle = nullptr; +static std::unique_ptr g_mcpl_api; +static bool g_mcpl_init_attempted = false; +static bool g_mcpl_successfully_loaded = false; +static std::string g_mcpl_load_error_msg; +static std::once_flag g_mcpl_init_flag; -#ifdef OPENMC_MCPL -SourceSite mcpl_particle_to_site(const mcpl_particle_t* particle) +void append_error(std::string& existing_msg, const std::string& new_error) { - SourceSite site; + if (!existing_msg.empty()) { + existing_msg += "; "; + } + existing_msg += new_error; +} + +void initialize_mcpl_interface_impl() +{ + g_mcpl_init_attempted = true; + g_mcpl_load_error_msg.clear(); + + // Try mcpl-config + if (!g_mcpl_lib_handle) { + FILE* pipe = nullptr; +#ifdef _WIN32 + pipe = _popen("mcpl-config --show libpath", "r"); +#else + pipe = popen("mcpl-config --show libpath 2>/dev/null", "r"); +#endif + if (pipe) { + char buffer[512]; + if (fgets(buffer, sizeof(buffer), pipe) != nullptr) { + std::string shlibpath = buffer; + // Remove trailing whitespace + while (!shlibpath.empty() && + std::isspace(static_cast(shlibpath.back()))) { + shlibpath.pop_back(); + } + + if (!shlibpath.empty()) { +#ifdef _WIN32 + g_mcpl_lib_handle = LoadLibraryA(shlibpath.c_str()); +#else + g_mcpl_lib_handle = dlopen(shlibpath.c_str(), RTLD_LAZY); +#endif + if (!g_mcpl_lib_handle) { + append_error( + g_mcpl_load_error_msg, fmt::format("From mcpl-config ({}): {}", + shlibpath, get_last_library_error())); + } + } + } +#ifdef _WIN32 + _pclose(pipe); +#else + pclose(pipe); +#endif + } else { // pipe failed to open + append_error(g_mcpl_load_error_msg, + "mcpl-config command not found or failed to execute"); + } + } + + // Try standard library names + if (!g_mcpl_lib_handle) { +#ifdef _WIN32 + const char* standard_names[] = {"mcpl.dll", "libmcpl.dll"}; +#else + const char* standard_names[] = {"libmcpl.so", "libmcpl.dylib"}; +#endif + for (const char* name : standard_names) { +#ifdef _WIN32 + g_mcpl_lib_handle = LoadLibraryA(name); +#else + g_mcpl_lib_handle = dlopen(name, RTLD_LAZY); +#endif + if (g_mcpl_lib_handle) + break; + } + if (!g_mcpl_lib_handle) { + append_error( + g_mcpl_load_error_msg, fmt::format("Using standard names (e.g. {}): {}", + standard_names[0], get_last_library_error())); + } + } + + if (!g_mcpl_lib_handle) { + if (mpi::master) { + warning(fmt::format("MCPL library could not be loaded. MCPL-dependent " + "features will be unavailable. Load attempts: {}", + g_mcpl_load_error_msg.empty() + ? "No specific error during load attempts." + : g_mcpl_load_error_msg)); + } + g_mcpl_successfully_loaded = false; + return; + } + + try { + g_mcpl_api = std::make_unique(g_mcpl_lib_handle); + g_mcpl_successfully_loaded = true; + // Do not call dlclose/FreeLibrary at exit. Leaking the handle is safer + // and standard practice for libraries used for the application's lifetime. + } catch (const std::runtime_error& e) { + append_error(g_mcpl_load_error_msg, + fmt::format( + "MCPL library loaded, but failed to bind symbols: {}", e.what())); + if (mpi::master) { + warning(g_mcpl_load_error_msg); + } +#ifdef _WIN32 + FreeLibrary(g_mcpl_lib_handle); +#else + dlclose(g_mcpl_lib_handle); +#endif + g_mcpl_lib_handle = nullptr; + g_mcpl_successfully_loaded = false; + } +} + +void initialize_mcpl_interface_if_needed() +{ + std::call_once(g_mcpl_init_flag, initialize_mcpl_interface_impl); +} - switch (particle->pdgcode) { +bool is_mcpl_interface_available() +{ + initialize_mcpl_interface_if_needed(); + return g_mcpl_successfully_loaded; +} + +inline void ensure_mcpl_ready_or_fatal() +{ + initialize_mcpl_interface_if_needed(); + if (!g_mcpl_successfully_loaded) { + fatal_error("MCPL functionality is required, but the MCPL library is not " + "available or failed to initialize. Please ensure MCPL is " + "installed and its library can be found (e.g., via PATH on " + "Windows, LD_LIBRARY_PATH on Linux, or DYLD_LIBRARY_PATH on " + "macOS). You can often install MCPL with 'pip install mcpl' or " + "'conda install mcpl'."); + } +} + +SourceSite mcpl_particle_to_site(const mcpl_particle_repr_t* particle_repr) +{ + SourceSite site; + switch (particle_repr->pdgcode) { case 2112: site.particle = ParticleType::neutron; break; @@ -49,179 +309,204 @@ SourceSite mcpl_particle_to_site(const mcpl_particle_t* particle) case -11: site.particle = ParticleType::positron; break; + default: + fatal_error(fmt::format( + "MCPL: Encountered unexpected PDG code {} when converting to SourceSite.", + particle_repr->pdgcode)); + break; } // Copy position and direction - site.r.x = particle->position[0]; - site.r.y = particle->position[1]; - site.r.z = particle->position[2]; - site.u.x = particle->direction[0]; - site.u.y = particle->direction[1]; - site.u.z = particle->direction[2]; - + site.r.x = particle_repr->position[0]; + site.r.y = particle_repr->position[1]; + site.r.z = particle_repr->position[2]; + site.u.x = particle_repr->direction[0]; + site.u.y = particle_repr->direction[1]; + site.u.z = particle_repr->direction[2]; // MCPL stores kinetic energy in [MeV], time in [ms] - site.E = particle->ekin * 1e6; - site.time = particle->time * 1e-3; - site.wgt = particle->weight; - + site.E = particle_repr->ekin * 1e6; + site.time = particle_repr->time * 1e-3; + site.wgt = particle_repr->weight; return site; } -#endif - -//============================================================================== vector mcpl_source_sites(std::string path) { + ensure_mcpl_ready_or_fatal(); vector sites; -#ifdef OPENMC_MCPL - // Open MCPL file and determine number of particles - auto mcpl_file = mcpl_open_file(path.c_str()); - size_t n_sites = mcpl_hdr_nparticles(mcpl_file); - - for (int i = 0; i < n_sites; i++) { - // Extract particle from mcpl-file, checking if it is a neutron, photon, - // electron, or positron. Otherwise skip. - const mcpl_particle_t* particle; - int pdg = 0; - while (pdg != 2112 && pdg != 22 && pdg != 11 && pdg != -11) { - particle = mcpl_read(mcpl_file); - pdg = particle->pdgcode; - } + mcpl_file_t* mcpl_file = g_mcpl_api->open_file(path.c_str()); + if (!mcpl_file) { + fatal_error(fmt::format("MCPL: Could not open file '{}'. It might be " + "missing, inaccessible, or not a valid MCPL file.", + path)); + } - // Convert to source site and add to vector - sites.push_back(mcpl_particle_to_site(particle)); + size_t n_particles_in_file = g_mcpl_api->hdr_nparticles(mcpl_file); + size_t n_skipped = 0; + if (n_particles_in_file > 0) { + sites.reserve(n_particles_in_file); } - // Check that some sites were read - if (sites.empty()) { - fatal_error("MCPL file contained no neutron, photon, electron, or positron " - "source particles."); + for (size_t i = 0; i < n_particles_in_file; ++i) { + const mcpl_particle_repr_t* p_repr = g_mcpl_api->read(mcpl_file); + if (!p_repr) { + warning(fmt::format("MCPL: Read error or unexpected end of file '{}' " + "after reading {} of {} expected particles.", + path, sites.size(), n_particles_in_file)); + break; + } + if (p_repr->pdgcode == 2112 || p_repr->pdgcode == 22 || + p_repr->pdgcode == 11 || p_repr->pdgcode == -11) { + sites.push_back(mcpl_particle_to_site(p_repr)); + } else { + n_skipped++; + } } - mcpl_close_file(mcpl_file); -#else - fatal_error( - "Your build of OpenMC does not support reading MCPL source files."); -#endif + g_mcpl_api->close_file(mcpl_file); + + if (n_skipped > 0 && n_particles_in_file > 0) { + double percent_skipped = + 100.0 * static_cast(n_skipped) / n_particles_in_file; + warning(fmt::format( + "MCPL: Skipped {} of {} total particles ({:.1f}%) in file '{}' because " + "their type is not supported by OpenMC.", + n_skipped, n_particles_in_file, percent_skipped, path)); + } + if (sites.empty()) { + if (n_particles_in_file > 0) { + fatal_error(fmt::format( + "MCPL file '{}' contained {} particles, but none were of the supported " + "types (neutron, photon, electron, positron). OpenMC cannot proceed " + "without source particles.", + path, n_particles_in_file)); + } else { + fatal_error(fmt::format( + "MCPL file '{}' is empty or contains no particle data.", path)); + } + } return sites; } -//============================================================================== - -#ifdef OPENMC_MCPL -void write_mcpl_source_bank(mcpl_outfile_t file_id, - span source_bank, const vector& bank_index) +void write_mcpl_source_bank_internal(mcpl_outfile_t* file_id, + span local_source_bank, + const vector& bank_index_all_ranks) { - int64_t dims_size = settings::n_particles; - int64_t count_size = simulation::work_per_rank; - if (mpi::master) { - // Particles are writeen to disk from the master node only - - // Save source bank sites since the array is overwritten below -#ifdef OPENMC_MPI - vector temp_source {source_bank.begin(), source_bank.end()}; -#endif + if (!file_id) { + fatal_error("MCPL: Internal error - master rank called " + "write_mcpl_source_bank_internal with null file_id."); + } + vector receive_buffer; - // loop over the other nodes and receive data - then write those. - for (int i = 0; i < mpi::n_procs; ++i) { - // number of particles for node node i - size_t count[] {static_cast(bank_index[i + 1] - bank_index[i])}; + for (int rank_idx = 0; rank_idx < mpi::n_procs; ++rank_idx) { + size_t num_sites_on_rank = static_cast( + bank_index_all_ranks[rank_idx + 1] - bank_index_all_ranks[rank_idx]); + if (num_sites_on_rank == 0) + continue; + span sites_to_write; #ifdef OPENMC_MPI - if (i > 0) - MPI_Recv(source_bank.data(), count[0], mpi::source_site, i, i, - mpi::intracomm, MPI_STATUS_IGNORE); + if (rank_idx == mpi::rank) { + sites_to_write = openmc::span( + local_source_bank.data(), num_sites_on_rank); + } else { + if (receive_buffer.size() < num_sites_on_rank) { + receive_buffer.resize(num_sites_on_rank); + } + MPI_Recv(receive_buffer.data(), num_sites_on_rank, mpi::source_site, + rank_idx, rank_idx, mpi::intracomm, MPI_STATUS_IGNORE); + sites_to_write = openmc::span( + receive_buffer.data(), num_sites_on_rank); + } +#else + sites_to_write = openmc::span( + local_source_bank.data(), num_sites_on_rank); #endif - // now write the source_bank data again. - for (const auto& site : source_bank) { - // particle is now at the iterator - // write it to the mcpl-file - mcpl_particle_t p; - p.position[0] = site.r.x; - p.position[1] = site.r.y; - p.position[2] = site.r.z; - - // mcpl requires that the direction vector is unit length - // which is also the case in openmc - p.direction[0] = site.u.x; - p.direction[1] = site.u.y; - p.direction[2] = site.u.z; - - // MCPL stores kinetic energy in [MeV], time in [ms] - p.ekin = site.E * 1e-6; - p.time = site.time * 1e3; - p.weight = site.wgt; - + for (const auto& site : sites_to_write) { + mcpl_particle_repr_t p_repr {}; + p_repr.position[0] = site.r.x; + p_repr.position[1] = site.r.y; + p_repr.position[2] = site.r.z; + p_repr.direction[0] = site.u.x; + p_repr.direction[1] = site.u.y; + p_repr.direction[2] = site.u.z; + p_repr.ekin = site.E * 1e-6; + p_repr.time = site.time * 1e3; + p_repr.weight = site.wgt; switch (site.particle) { case ParticleType::neutron: - p.pdgcode = 2112; + p_repr.pdgcode = 2112; break; case ParticleType::photon: - p.pdgcode = 22; + p_repr.pdgcode = 22; break; case ParticleType::electron: - p.pdgcode = 11; + p_repr.pdgcode = 11; break; case ParticleType::positron: - p.pdgcode = -11; + p_repr.pdgcode = -11; break; + default: + continue; } - - mcpl_add_particle(file_id, &p); + g_mcpl_api->add_particle(file_id, &p_repr); } } -#ifdef OPENMC_MPI - // Restore state of source bank - std::copy(temp_source.begin(), temp_source.end(), source_bank.begin()); -#endif } else { #ifdef OPENMC_MPI - MPI_Send(source_bank.data(), count_size, mpi::source_site, 0, mpi::rank, - mpi::intracomm); + if (!local_source_bank.empty()) { + MPI_Send(local_source_bank.data(), local_source_bank.size(), + mpi::source_site, 0, mpi::rank, mpi::intracomm); + } #endif } } -#endif - -//============================================================================== void write_mcpl_source_point(const char* filename, span source_bank, const vector& bank_index) { + ensure_mcpl_ready_or_fatal(); + std::string filename_(filename); const auto extension = get_file_extension(filename_); - if (extension == "") { + if (extension.empty()) { filename_.append(".mcpl"); } else if (extension != "mcpl") { - warning("write_mcpl_source_point was passed a file extension differing " - "from .mcpl, but an mcpl file will be written."); + warning(fmt::format("Specified filename '{}' has an extension '.{}', but " + "an MCPL file (.mcpl) will be written using this name.", + filename, extension)); } -#ifdef OPENMC_MCPL - mcpl_outfile_t file_id; + mcpl_outfile_t* file_id = nullptr; - std::string line; if (mpi::master) { - file_id = mcpl_create_outfile(filename_.c_str()); + file_id = g_mcpl_api->create_outfile(filename_.c_str()); + if (!file_id) { + fatal_error(fmt::format( + "MCPL: Failed to create output file '{}'. Check permissions and path.", + filename_)); + } + std::string src_line; if (VERSION_DEV) { - line = fmt::format("OpenMC {0}.{1}.{2}-dev{3}", VERSION_MAJOR, + src_line = fmt::format("OpenMC {}.{}.{}-dev{}", VERSION_MAJOR, VERSION_MINOR, VERSION_RELEASE, VERSION_COMMIT_COUNT); } else { - line = fmt::format( - "OpenMC {0}.{1}.{2}", VERSION_MAJOR, VERSION_MINOR, VERSION_RELEASE); + src_line = fmt::format( + "OpenMC {}.{}.{}", VERSION_MAJOR, VERSION_MINOR, VERSION_RELEASE); } - mcpl_hdr_set_srcname(file_id, line.c_str()); + g_mcpl_api->hdr_set_srcname(file_id, src_line.c_str()); } - write_mcpl_source_bank(file_id, source_bank, bank_index); + write_mcpl_source_bank_internal(file_id, source_bank, bank_index); if (mpi::master) { - mcpl_close_outfile(file_id); + if (file_id) { + g_mcpl_api->close_outfile(file_id); + } } -#endif } } // namespace openmc diff --git a/src/settings.cpp b/src/settings.cpp index 9afe743614e..afa42a3c5e6 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -844,12 +844,6 @@ void read_settings_xml(pugi::xml_node root) } if (check_for_node(node_sp, "mcpl")) { source_mcpl_write = get_node_value_bool(node_sp, "mcpl"); - - // Make sure MCPL support is enabled - if (source_mcpl_write && !MCPL_ENABLED) { - fatal_error( - "Your build of OpenMC does not support writing MCPL source files."); - } } if (check_for_node(node_sp, "overwrite_latest")) { source_latest = get_node_value_bool(node_sp, "overwrite_latest"); @@ -902,12 +896,6 @@ void read_settings_xml(pugi::xml_node root) if (check_for_node(node_ssw, "mcpl")) { surf_mcpl_write = get_node_value_bool(node_ssw, "mcpl"); - - // Make sure MCPL support is enabled - if (surf_mcpl_write && !MCPL_ENABLED) { - fatal_error("Your build of OpenMC does not support writing MCPL " - "surface source files."); - } } // Get cell information if (check_for_node(node_ssw, "cell")) { diff --git a/tests/regression_tests/source_mcpl_file/settings.xml b/tests/regression_tests/source_mcpl_file/settings.xml index 47b010bffea..bb08e6fd77d 100644 --- a/tests/regression_tests/source_mcpl_file/settings.xml +++ b/tests/regression_tests/source_mcpl_file/settings.xml @@ -1,12 +1,11 @@ + eigenvalue - - 10 - 5 - 1000 - + 10 + 5 + 1000 -4 -4 -4 4 4 4 diff --git a/tests/regression_tests/source_mcpl_file/test.py b/tests/regression_tests/source_mcpl_file/test.py index 84ec005ae16..6e668ef1919 100644 --- a/tests/regression_tests/source_mcpl_file/test.py +++ b/tests/regression_tests/source_mcpl_file/test.py @@ -1,23 +1,23 @@ #!/usr/bin/env python -import openmc.lib import pytest import glob import os - +import shutil from tests.testing_harness import * + pytestmark = pytest.mark.skipif( - not openmc.lib._mcpl_enabled(), - reason="MCPL is not enabled.") + shutil.which("mcpl-config") is None, + reason="mcpl-config command not found in PATH; MCPL is likely not available." +) settings1=""" + eigenvalue - - 10 - 5 - 1000 - + 10 + 5 + 1000 -4 -4 -4 4 4 4 @@ -28,11 +28,10 @@ settings2 = """ - - 10 - 5 - 1000 - + eigenvalue + 10 + 5 + 1000 source.10.{} From bca818ced6c7c0a7fd1b331cb8057b682ecc854c Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 16 Jul 2025 22:29:41 -0500 Subject: [PATCH 073/159] Add accessor methods for LocalCoord (#3494) Co-authored-by: Paul Romano --- include/openmc/particle_data.h | 54 +++++++++---- include/openmc/plot.h | 2 +- src/cell.cpp | 10 +-- src/dagmc.cpp | 4 +- src/geometry.cpp | 104 +++++++++++++------------- src/mesh.cpp | 6 +- src/output.cpp | 21 +++--- src/particle.cpp | 36 ++++----- src/particle_data.cpp | 26 +++---- src/plot.cpp | 16 ++-- src/random_ray/flat_source_domain.cpp | 6 +- src/random_ray/random_ray.cpp | 10 +-- src/source.cpp | 7 +- src/tallies/filter_cell.cpp | 2 +- src/tallies/filter_cell_instance.cpp | 4 +- src/tallies/filter_distribcell.cpp | 8 +- src/tallies/filter_universe.cpp | 2 +- src/tallies/tally_scoring.cpp | 6 +- src/universe.cpp | 4 +- src/volume_calc.cpp | 4 +- 20 files changed, 179 insertions(+), 153 deletions(-) diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index be7fa74837e..8dd814a2965 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -88,13 +88,37 @@ class LocalCoord { //! clear data from a single coordinate level void reset(); - Position r; //!< particle position - Direction u; //!< particle direction - int cell {-1}; - int universe {-1}; - int lattice {-1}; - array lattice_i {{-1, -1, -1}}; - bool rotated {false}; //!< Is the level rotated? + // accessors + Position& r() { return r_; } + const Position& r() const { return r_; } + + Direction& u() { return u_; } + const Direction& u() const { return u_; } + + int& cell() { return cell_; } + const int& cell() const { return cell_; } + + int& universe() { return universe_; } + const int& universe() const { return universe_; } + + int& lattice() { return lattice_; } + int lattice() const { return lattice_; } + + array& lattice_index() { return lattice_index_; } + const array& lattice_index() const { return lattice_index_; } + + bool& rotated() { return rotated_; } + const bool& rotated() const { return rotated_; } + +private: + // Data members + Position r_; //!< particle position + Direction u_; //!< particle direction + int cell_ {-1}; + int universe_ {-1}; + int lattice_ {-1}; + array lattice_index_ {{-1, -1, -1}}; + bool rotated_ {false}; //!< Is the level rotated? }; //============================================================================== @@ -301,20 +325,20 @@ class GeometryState { const Position& u_last() const { return u_last_; } // Accessors for position in global coordinates - Position& r() { return coord_[0].r; } - const Position& r() const { return coord_[0].r; } + Position& r() { return coord_[0].r(); } + const Position& r() const { return coord_[0].r(); } // Accessors for position in local coordinates - Position& r_local() { return coord_[n_coord_ - 1].r; } - const Position& r_local() const { return coord_[n_coord_ - 1].r; } + Position& r_local() { return coord_[n_coord_ - 1].r(); } + const Position& r_local() const { return coord_[n_coord_ - 1].r(); } // Accessors for direction in global coordinates - Direction& u() { return coord_[0].u; } - const Direction& u() const { return coord_[0].u; } + Direction& u() { return coord_[0].u(); } + const Direction& u() const { return coord_[0].u(); } // Accessors for direction in local coordinates - Direction& u_local() { return coord_[n_coord_ - 1].u; } - const Direction& u_local() const { return coord_[n_coord_ - 1].u; } + Direction& u_local() { return coord_[n_coord_ - 1].u(); } + const Direction& u_local() const { return coord_[n_coord_ - 1].u(); } // Surface token for the surface that the particle is currently on int& surface() { return surface_; } diff --git a/include/openmc/plot.h b/include/openmc/plot.h index 69e801c5fc3..7e27679eabb 100644 --- a/include/openmc/plot.h +++ b/include/openmc/plot.h @@ -223,7 +223,7 @@ T SlicePlotBase::get_map() const GeometryState p; p.r() = xyz; p.u() = dir; - p.coord(0).universe = model::root_universe; + p.coord(0).universe() = model::root_universe; int level = slice_level_; int j {}; diff --git a/src/cell.cpp b/src/cell.cpp index 4b26992299e..eb5c228bc11 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -1314,10 +1314,10 @@ vector Cell::find_parent_cells( bool cell_found = false; for (auto it = coords.begin(); it != coords.end(); it++) { const auto& coord = *it; - const auto& cell = model::cells[coord.cell]; + const auto& cell = model::cells[coord.cell()]; // if the cell at this level matches the current cell, stop adding to the // stack - if (coord.cell == model::cell_map[this->id_]) { + if (coord.cell() == model::cell_map[this->id_]) { cell_found = true; break; } @@ -1327,10 +1327,10 @@ vector Cell::find_parent_cells( int lattice_idx = C_NONE; if (cell->type_ == Fill::LATTICE) { const auto& next_coord = *(it + 1); - lattice_idx = model::lattices[next_coord.lattice]->get_flat_index( - next_coord.lattice_i); + lattice_idx = model::lattices[next_coord.lattice()]->get_flat_index( + next_coord.lattice_index()); } - stack.push(coord.universe, {coord.cell, lattice_idx}); + stack.push(coord.universe(), {coord.cell(), lattice_idx}); } // if this loop finished because the cell was found and diff --git a/src/dagmc.cpp b/src/dagmc.cpp index 13436088652..1d92d9d5703 100644 --- a/src/dagmc.cpp +++ b/src/dagmc.cpp @@ -430,7 +430,7 @@ bool DAGUniverse::find_cell(GeometryState& p) const // cells, place it in the implicit complement bool found = Universe::find_cell(p); if (!found && model::universe_map[this->id_] != model::root_universe) { - p.lowest_coord().cell = implicit_complement_idx(); + p.lowest_coord().cell() = implicit_complement_idx(); found = true; } return found; @@ -676,7 +676,7 @@ std::pair DAGCell::distance( p->history().reset(); } - const auto& univ = model::universes[p->lowest_coord().universe]; + const auto& univ = model::universes[p->lowest_coord().universe()]; DAGUniverse* dag_univ = static_cast(univ.get()); if (!dag_univ) diff --git a/src/geometry.cpp b/src/geometry.cpp index 9e975c00dee..cefe7d3649c 100644 --- a/src/geometry.cpp +++ b/src/geometry.cpp @@ -38,17 +38,17 @@ bool check_cell_overlap(GeometryState& p, bool error) // Loop through each coordinate level for (int j = 0; j < n_coord; j++) { - Universe& univ = *model::universes[p.coord(j).universe]; + Universe& univ = *model::universes[p.coord(j).universe()]; // Loop through each cell on this level for (auto index_cell : univ.cells_) { Cell& c = *model::cells[index_cell]; - if (c.contains(p.coord(j).r, p.coord(j).u, p.surface())) { - if (index_cell != p.coord(j).cell) { + if (c.contains(p.coord(j).r(), p.coord(j).u(), p.surface())) { + if (index_cell != p.coord(j).cell()) { if (error) { fatal_error( fmt::format("Overlapping cells detected: {}, {} on universe {}", - c.id_, model::cells[p.coord(j).cell]->id_, univ.id_)); + c.id_, model::cells[p.coord(j).cell()]->id_, univ.id_)); } return true; } @@ -73,7 +73,7 @@ int cell_instance_at_level(const GeometryState& p, int level) } // determine the cell instance - Cell& c {*model::cells[p.coord(level).cell]}; + Cell& c {*model::cells[p.coord(level).cell()]}; // quick exit if this cell doesn't have distribcell instances if (c.distribcell_index_ == C_NONE) @@ -82,13 +82,13 @@ int cell_instance_at_level(const GeometryState& p, int level) // compute the cell's instance int instance = 0; for (int i = 0; i < level; i++) { - const auto& c_i {*model::cells[p.coord(i).cell]}; + const auto& c_i {*model::cells[p.coord(i).cell()]}; if (c_i.type_ == Fill::UNIVERSE) { instance += c_i.offset_[c.distribcell_index_]; } else if (c_i.type_ == Fill::LATTICE) { instance += c_i.offset_[c.distribcell_index_]; - auto& lat {*model::lattices[p.coord(i + 1).lattice]}; - const auto& i_xyz {p.coord(i + 1).lattice_i}; + auto& lat {*model::lattices[p.coord(i + 1).lattice()]}; + const auto& i_xyz {p.coord(i + 1).lattice_index()}; if (lat.are_valid_indices(i_xyz)) { instance += lat.offset(c.distribcell_index_, i_xyz); } @@ -111,7 +111,7 @@ bool find_cell_inner( i_cell = *it; // Make sure the search cell is in the same universe. - int i_universe = p.lowest_coord().universe; + int i_universe = p.lowest_coord().universe(); if (model::cells[i_cell]->universe_ != i_universe) continue; @@ -120,7 +120,7 @@ bool find_cell_inner( Direction u {p.u_local()}; auto surf = p.surface(); if (model::cells[i_cell]->contains(r, u, surf)) { - p.lowest_coord().cell = i_cell; + p.lowest_coord().cell() = i_cell; found = true; break; } @@ -146,7 +146,7 @@ bool find_cell_inner( // code below this conditional, we set i_cell back to C_NONE to indicate // that. if (i_cell == C_NONE) { - int i_universe = p.lowest_coord().universe; + int i_universe = p.lowest_coord().universe(); const auto& univ {model::universes[i_universe]}; found = univ->find_cell(p); } @@ -154,7 +154,7 @@ bool find_cell_inner( if (!found) { return found; } - i_cell = p.lowest_coord().cell; + i_cell = p.lowest_coord().cell(); // Announce the cell that the particle is entering. if (found && verbose) { @@ -186,14 +186,14 @@ bool find_cell_inner( // Set the lower coordinate level universe. auto& coord {p.coord(p.n_coord())}; - coord.universe = c.fill_; + coord.universe() = c.fill_; // Set the position and direction. - coord.r = p.r_local(); - coord.u = p.u_local(); + coord.r() = p.r_local(); + coord.u() = p.u_local(); // Apply translation. - coord.r -= c.translation_; + coord.r() -= c.translation_; // Apply rotation. if (!c.rotation_.empty()) { @@ -208,11 +208,11 @@ bool find_cell_inner( // Set the position and direction. auto& coord {p.coord(p.n_coord())}; - coord.r = p.r_local(); - coord.u = p.u_local(); + coord.r() = p.r_local(); + coord.u() = p.u_local(); // Apply translation. - coord.r -= c.translation_; + coord.r() -= c.translation_; // Apply rotation. if (!c.rotation_.empty()) { @@ -220,21 +220,21 @@ bool find_cell_inner( } // Determine lattice indices. - auto& i_xyz {coord.lattice_i}; - lat.get_indices(coord.r, coord.u, i_xyz); + auto& i_xyz {coord.lattice_index()}; + lat.get_indices(coord.r(), coord.u(), i_xyz); // Get local position in appropriate lattice cell - coord.r = lat.get_local_position(coord.r, i_xyz); + coord.r() = lat.get_local_position(coord.r(), i_xyz); // Set lattice indices. - coord.lattice = c.fill_; + coord.lattice() = c.fill_; // Set the lower coordinate level universe. if (lat.are_valid_indices(i_xyz)) { - coord.universe = lat[i_xyz]; + coord.universe() = lat[i_xyz]; } else { if (lat.outer_ != NO_OUTER_UNIVERSE) { - coord.universe = lat.outer_; + coord.universe() = lat.outer_; } else { p.mark_as_lost(fmt::format( "Particle {} left lattice {}, but it has no outer definition.", @@ -261,7 +261,7 @@ bool neighbor_list_find_cell(GeometryState& p, bool verbose) // Get the cell this particle was in previously. auto coord_lvl = p.n_coord() - 1; - auto i_cell = p.coord(coord_lvl).cell; + auto i_cell = p.coord(coord_lvl).cell(); Cell& c {*model::cells[i_cell]}; // Search for the particle in that cell's neighbor list. Return if we @@ -275,15 +275,15 @@ bool neighbor_list_find_cell(GeometryState& p, bool verbose) // neighboring cell. found = find_cell_inner(p, nullptr, verbose); if (found) - c.neighbors_.push_back(p.coord(coord_lvl).cell); + c.neighbors_.push_back(p.coord(coord_lvl).cell()); return found; } bool exhaustive_find_cell(GeometryState& p, bool verbose) { - int i_universe = p.lowest_coord().universe; + int i_universe = p.lowest_coord().universe(); if (i_universe == C_NONE) { - p.coord(0).universe = model::root_universe; + p.coord(0).universe() = model::root_universe; p.n_coord() = 1; i_universe = model::root_universe; } @@ -299,32 +299,32 @@ bool exhaustive_find_cell(GeometryState& p, bool verbose) void cross_lattice(GeometryState& p, const BoundaryInfo& boundary, bool verbose) { auto& coord {p.lowest_coord()}; - auto& lat {*model::lattices[coord.lattice]}; + auto& lat {*model::lattices[coord.lattice()]}; if (verbose) { write_message( fmt::format(" Crossing lattice {}. Current position ({},{},{}). r={}", - lat.id_, coord.lattice_i[0], coord.lattice_i[1], coord.lattice_i[2], - p.r()), + lat.id_, coord.lattice_index()[0], coord.lattice_index()[1], + coord.lattice_index()[2], p.r()), 1); } // Set the lattice indices. - coord.lattice_i[0] += boundary.lattice_translation[0]; - coord.lattice_i[1] += boundary.lattice_translation[1]; - coord.lattice_i[2] += boundary.lattice_translation[2]; + coord.lattice_index()[0] += boundary.lattice_translation[0]; + coord.lattice_index()[1] += boundary.lattice_translation[1]; + coord.lattice_index()[2] += boundary.lattice_translation[2]; // Set the new coordinate position. const auto& upper_coord {p.coord(p.n_coord() - 2)}; - const auto& cell {model::cells[upper_coord.cell]}; - Position r = upper_coord.r; + const auto& cell {model::cells[upper_coord.cell()]}; + Position r = upper_coord.r(); r -= cell->translation_; if (!cell->rotation_.empty()) { r = r.rotate(cell->rotation_); } - p.r_local() = lat.get_local_position(r, coord.lattice_i); + p.r_local() = lat.get_local_position(r, coord.lattice_index()); - if (!lat.are_valid_indices(coord.lattice_i)) { + if (!lat.are_valid_indices(coord.lattice_index())) { // The particle is outside the lattice. Search for it from the base coords. p.n_coord() = 1; bool found = exhaustive_find_cell(p); @@ -337,7 +337,7 @@ void cross_lattice(GeometryState& p, const BoundaryInfo& boundary, bool verbose) } else { // Find cell in next lattice element. - p.lowest_coord().universe = lat[coord.lattice_i]; + p.lowest_coord().universe() = lat[coord.lattice_index()]; bool found = exhaustive_find_cell(p); if (!found) { @@ -367,9 +367,9 @@ BoundaryInfo distance_to_boundary(GeometryState& p) // Loop over each coordinate level. for (int i = 0; i < p.n_coord(); i++) { const auto& coord {p.coord(i)}; - const Position& r {coord.r}; - const Direction& u {coord.u}; - Cell& c {*model::cells[coord.cell]}; + const Position& r {coord.r()}; + const Direction& u {coord.u()}; + Cell& c {*model::cells[coord.cell()]}; // Find the oncoming surface in this cell and the distance to it. auto surface_distance = c.distance(r, u, p.surface(), &p); @@ -377,24 +377,24 @@ BoundaryInfo distance_to_boundary(GeometryState& p) level_surf_cross = surface_distance.second; // Find the distance to the next lattice tile crossing. - if (coord.lattice != C_NONE) { - auto& lat {*model::lattices[coord.lattice]}; + if (coord.lattice() != C_NONE) { + auto& lat {*model::lattices[coord.lattice()]}; // TODO: refactor so both lattice use the same position argument (which // also means the lat.type attribute can be removed) std::pair> lattice_distance; switch (lat.type_) { case LatticeType::rect: - lattice_distance = lat.distance(r, u, coord.lattice_i); + lattice_distance = lat.distance(r, u, coord.lattice_index()); break; case LatticeType::hex: - auto& cell_above {model::cells[p.coord(i - 1).cell]}; - Position r_hex {p.coord(i - 1).r}; + auto& cell_above {model::cells[p.coord(i - 1).cell()]}; + Position r_hex {p.coord(i - 1).r()}; r_hex -= cell_above->translation_; - if (coord.rotated) { + if (coord.rotated()) { r_hex = r_hex.rotate(cell_above->rotation_); } - r_hex.z = coord.r.z; - lattice_distance = lat.distance(r_hex, u, coord.lattice_i); + r_hex.z = coord.r().z; + lattice_distance = lat.distance(r_hex, u, coord.lattice_index()); break; } d_lat = lattice_distance.first; @@ -468,7 +468,7 @@ extern "C" int openmc_find_cell( return OPENMC_E_GEOMETRY; } - *index = geom_state.lowest_coord().cell; + *index = geom_state.lowest_coord().cell(); *instance = geom_state.cell_instance(); return 0; } diff --git a/src/mesh.cpp b/src/mesh.cpp index becbbd3fd42..1b2c71eb226 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -368,11 +368,11 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size, // Set birth cell attribute if (p.cell_born() == C_NONE) - p.cell_born() = p.lowest_coord().cell; + p.cell_born() = p.lowest_coord().cell(); // Initialize last cells from current cell for (int j = 0; j < p.n_coord(); ++j) { - p.cell_last(j) = p.coord(j).cell; + p.cell_last(j) = p.coord(j).cell(); } p.n_coord_last() = p.n_coord(); @@ -411,7 +411,7 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size, // cross next geometric surface for (int j = 0; j < p.n_coord(); ++j) { - p.cell_last(j) = p.coord(j).cell; + p.cell_last(j) = p.coord(j).cell(); } p.n_coord_last() = p.n_coord(); diff --git a/src/output.cpp b/src/output.cpp index baaf682a157..0316e313ed0 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -177,25 +177,26 @@ void print_particle(Particle& p) for (auto i = 0; i < p.n_coord(); i++) { fmt::print(" Level {}\n", i); - if (p.coord(i).cell != C_NONE) { - const Cell& c {*model::cells[p.coord(i).cell]}; + if (p.coord(i).cell() != C_NONE) { + const Cell& c {*model::cells[p.coord(i).cell()]}; fmt::print(" Cell = {}\n", c.id_); } - if (p.coord(i).universe != C_NONE) { - const Universe& u {*model::universes[p.coord(i).universe]}; + if (p.coord(i).universe() != C_NONE) { + const Universe& u {*model::universes[p.coord(i).universe()]}; fmt::print(" Universe = {}\n", u.id_); } - if (p.coord(i).lattice != C_NONE) { - const Lattice& lat {*model::lattices[p.coord(i).lattice]}; + if (p.coord(i).lattice() != C_NONE) { + const Lattice& lat {*model::lattices[p.coord(i).lattice()]}; fmt::print(" Lattice = {}\n", lat.id_); - fmt::print(" Lattice position = ({},{},{})\n", p.coord(i).lattice_i[0], - p.coord(i).lattice_i[1], p.coord(i).lattice_i[2]); + fmt::print(" Lattice position = ({},{},{})\n", + p.coord(i).lattice_index()[0], p.coord(i).lattice_index()[1], + p.coord(i).lattice_index()[2]); } - fmt::print(" r = {}\n", p.coord(i).r); - fmt::print(" u = {}\n", p.coord(i).u); + fmt::print(" r = {}\n", p.coord(i).r()); + fmt::print(" u = {}\n", p.coord(i).u()); } // Display miscellaneous info. diff --git a/src/particle.cpp b/src/particle.cpp index 6cfdf2175af..4758cded591 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -176,7 +176,7 @@ void Particle::event_calculate_xs() // If the cell hasn't been determined based on the particle's location, // initiate a search for the current cell. This generally happens at the // beginning of the history and again for any secondary particles - if (lowest_coord().cell == C_NONE) { + if (lowest_coord().cell() == C_NONE) { if (!exhaustive_find_cell(*this)) { mark_as_lost( "Could not find the cell containing particle " + std::to_string(id())); @@ -185,11 +185,11 @@ void Particle::event_calculate_xs() // Set birth cell attribute if (cell_born() == C_NONE) - cell_born() = lowest_coord().cell; + cell_born() = lowest_coord().cell(); // Initialize last cells from current cell for (int j = 0; j < n_coord(); ++j) { - cell_last(j) = coord(j).cell; + cell_last(j) = coord(j).cell(); } n_coord_last() = n_coord(); } @@ -248,7 +248,7 @@ void Particle::event_advance() // Short-term solution until the surface source is revised and we can use // this->move_distance(distance) for (int j = 0; j < n_coord(); ++j) { - coord(j).r += distance * coord(j).u; + coord(j).r() += distance * coord(j).u(); } double dt = distance / this->speed(); this->time() += dt; @@ -293,7 +293,7 @@ void Particle::event_cross_surface() { // Saving previous cell data for (int j = 0; j < n_coord(); ++j) { - cell_last(j) = coord(j).cell; + cell_last(j) = coord(j).cell(); } n_coord_last() = n_coord(); @@ -393,14 +393,14 @@ void Particle::event_collide() // Set all directions to base level -- right now, after a collision, only // the base level directions are changed for (int j = 0; j < n_coord() - 1; ++j) { - if (coord(j + 1).rotated) { + if (coord(j + 1).rotated()) { // If next level is rotated, apply rotation matrix - const auto& m {model::cells[coord(j).cell]->rotation_}; - const auto& u {coord(j).u}; - coord(j + 1).u = u.rotate(m); + const auto& m {model::cells[coord(j).cell()]->rotation_}; + const auto& u {coord(j).u()}; + coord(j + 1).u() = u.rotate(m); } else { // Otherwise, copy this level's direction - coord(j + 1).u = coord(j).u; + coord(j + 1).u() = coord(j).u(); } } @@ -445,7 +445,7 @@ void Particle::event_revive_from_secondary() // Since the birth cell of the particle has not been set we // have to determine it before the energy of the secondary particle can be // removed from the pulse-height of this cell. - if (lowest_coord().cell == C_NONE) { + if (lowest_coord().cell() == C_NONE) { bool verbose = settings::verbosity >= 10 || trace(); if (!exhaustive_find_cell(*this, verbose)) { mark_as_lost("Could not find the cell containing particle " + @@ -454,11 +454,11 @@ void Particle::event_revive_from_secondary() } // Set birth cell attribute if (cell_born() == C_NONE) - cell_born() = lowest_coord().cell; + cell_born() = lowest_coord().cell(); // Initialize last cells from current cell for (int j = 0; j < n_coord(); ++j) { - cell_last(j) = coord(j).cell; + cell_last(j) = coord(j).cell(); } n_coord_last() = n_coord(); } @@ -516,7 +516,7 @@ void Particle::pht_collision_energy() // determine index of cell in pulse_height_cells auto it = std::find(model::pulse_height_cells.begin(), - model::pulse_height_cells.end(), lowest_coord().cell); + model::pulse_height_cells.end(), lowest_coord().cell()); if (it != model::pulse_height_cells.end()) { int index = std::distance(model::pulse_height_cells.begin(), it); @@ -571,13 +571,13 @@ void Particle::cross_surface(const Surface& surf) // in DAGMC, we know what the next cell should be if (surf.geom_type() == GeometryType::DAG) { int32_t i_cell = next_cell(surface_index(), cell_last(n_coord() - 1), - lowest_coord().universe) - + lowest_coord().universe()) - 1; // save material and temp material_last() = material(); sqrtkT_last() = sqrtkT(); // set new cell value - lowest_coord().cell = i_cell; + lowest_coord().cell() = i_cell; auto& cell = model::cells[i_cell]; cell_instance() = 0; @@ -681,7 +681,7 @@ void Particle::cross_reflective_bc(const Surface& surf, Direction new_u) u() = new_u; // Reassign particle's cell and surface - coord(0).cell = cell_last(0); + coord(0).cell() = cell_last(0); surface() = -surface(); // If a reflective surface is coincident with a lattice or universe @@ -942,7 +942,7 @@ void add_surf_source_to_bank(Particle& p, const Surface& surf) // Check if the cell of interest has been entered bool entered = false; for (int i = 0; i < p.n_coord(); ++i) { - if (p.coord(i).cell == cell_idx) { + if (p.coord(i).cell() == cell_idx) { entered = true; } } diff --git a/src/particle_data.cpp b/src/particle_data.cpp index fef8359a356..aff2b68bada 100644 --- a/src/particle_data.cpp +++ b/src/particle_data.cpp @@ -32,20 +32,20 @@ void GeometryState::mark_as_lost(const std::stringstream& message) void LocalCoord::rotate(const vector& rotation) { - r = r.rotate(rotation); - u = u.rotate(rotation); - rotated = true; + r_ = r_.rotate(rotation); + u_ = u_.rotate(rotation); + rotated_ = true; } void LocalCoord::reset() { - cell = C_NONE; - universe = C_NONE; - lattice = C_NONE; - lattice_i[0] = 0; - lattice_i[1] = 0; - lattice_i[2] = 0; - rotated = false; + cell_ = C_NONE; + universe_ = C_NONE; + lattice_ = C_NONE; + lattice_index_[0] = 0; + lattice_index_[1] = 0; + lattice_index_[2] = 0; + rotated_ = false; } GeometryState::GeometryState() @@ -64,7 +64,7 @@ void GeometryState::advance_to_boundary_from_void() for (auto c_i : root_universe->cells_) { auto dist = - model::cells.at(c_i)->distance(root_coord.r, root_coord.u, 0, this); + model::cells.at(c_i)->distance(root_coord.r(), root_coord.u(), 0, this); if (dist.first < boundary().distance) { boundary().distance = dist.first; boundary().surface = dist.second; @@ -86,7 +86,7 @@ void GeometryState::advance_to_boundary_from_void() void GeometryState::move_distance(double length) { for (int j = 0; j < n_coord(); ++j) { - coord(j).r += length * coord(j).u; + coord(j).r() += length * coord(j).u(); } } @@ -123,7 +123,7 @@ TrackState ParticleData::get_track_state() const state.E = this->E(); state.time = this->time(); state.wgt = this->wgt(); - state.cell_id = model::cells[this->lowest_coord().cell]->id_; + state.cell_id = model::cells[this->lowest_coord().cell()]->id_; state.cell_instance = this->cell_instance(); if (this->material() != MATERIAL_VOID) { state.material_id = model::materials[material()]->id_; diff --git a/src/plot.cpp b/src/plot.cpp index dbc25b21e4d..db10c76006e 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -54,14 +54,14 @@ void IdData::set_value(size_t y, size_t x, const GeometryState& p, int level) data_(y, x, 0) = NOT_FOUND; data_(y, x, 1) = NOT_FOUND; } else { - data_(y, x, 0) = model::cells.at(p.coord(level).cell)->id_; + data_(y, x, 0) = model::cells.at(p.coord(level).cell())->id_; data_(y, x, 1) = level == p.n_coord() - 1 ? p.cell_instance() : cell_instance_at_level(p, level); } // set material data - Cell* c = model::cells.at(p.lowest_coord().cell).get(); + Cell* c = model::cells.at(p.lowest_coord().cell()).get(); if (p.material() == MATERIAL_VOID) { data_(y, x, 2) = MATERIAL_VOID; return; @@ -83,7 +83,7 @@ PropertyData::PropertyData(size_t h_res, size_t v_res) void PropertyData::set_value( size_t y, size_t x, const GeometryState& p, int level) { - Cell* c = model::cells.at(p.lowest_coord().cell).get(); + Cell* c = model::cells.at(p.lowest_coord().cell()).get(); data_(y, x, 0) = (p.sqrtkT() * p.sqrtkT()) / K_BOLTZMANN; if (c->type_ != Fill::UNIVERSE && p.material() != MATERIAL_VOID) { Material* m = model::materials.at(p.material()).get(); @@ -1692,7 +1692,7 @@ void Ray::trace() // Advance particle, prepare for next intersection for (int lev = 0; lev < n_coord(); ++lev) { - coord(lev).r += boundary().distance * coord(lev).u; + coord(lev).r() += boundary().distance * coord(lev).u(); } surface() = boundary().surface; n_coord_last() = n_coord(); @@ -1743,7 +1743,7 @@ void ProjectionRay::on_intersection() line_segments_.emplace_back( plot_.color_by_ == PlottableInterface::PlotColorBy::mats ? material() - : lowest_coord().cell, + : lowest_coord().cell(), traversal_distance_, boundary().surface_index()); } @@ -1752,7 +1752,7 @@ void PhongRay::on_intersection() // Check if we hit an opaque material or cell int hit_id = plot_.color_by_ == PlottableInterface::PlotColorBy::mats ? material() - : lowest_coord().cell; + : lowest_coord().cell(); // If we are reflected and have advanced beyond the camera, // the ray is done. This is checked here because we should @@ -1798,8 +1798,8 @@ void PhongRay::on_intersection() // Need to apply translations to find the normal vector in // the base level universe's coordinate system. for (int lev = n_coord() - 2; lev >= 0; --lev) { - if (coord(lev + 1).rotated) { - const Cell& c {*model::cells[coord(lev).cell]}; + if (coord(lev + 1).rotated()) { + const Cell& c {*model::cells[coord(lev).cell()]}; normal = normal.inverse_rotate(c.rotation_); } } diff --git a/src/random_ray/flat_source_domain.cpp b/src/random_ray/flat_source_domain.cpp index 4a5b430362d..b04b72981f8 100644 --- a/src/random_ray/flat_source_domain.cpp +++ b/src/random_ray/flat_source_domain.cpp @@ -794,7 +794,7 @@ void FlatSourceDomain::output_to_vtk() const continue; } - int i_cell = p.lowest_coord().cell; + int i_cell = p.lowest_coord().cell(); int64_t sr = source_region_offsets_[i_cell] + p.cell_instance(); if (RandomRay::mesh_subdivision_enabled_) { int mesh_idx = base_source_regions_.mesh(sr); @@ -1070,7 +1070,7 @@ void FlatSourceDomain::convert_external_sources() "point source at {}", sp->r())); } - int i_cell = gs.lowest_coord().cell; + int i_cell = gs.lowest_coord().cell(); int64_t sr = source_region_offsets_[i_cell] + gs.cell_instance(); if (RandomRay::mesh_subdivision_enabled_) { @@ -1475,7 +1475,7 @@ SourceRegionHandle FlatSourceDomain::get_subdivided_source_region_handle( gs.r() = r + TINY_BIT * u; gs.u() = {1.0, 0.0, 0.0}; exhaustive_find_cell(gs); - int gs_i_cell = gs.lowest_coord().cell; + int gs_i_cell = gs.lowest_coord().cell(); int64_t sr_found = source_region_offsets_[gs_i_cell] + gs.cell_instance(); if (sr_found != sr) { discovered_source_regions_.unlock(sr_key); diff --git a/src/random_ray/random_ray.cpp b/src/random_ray/random_ray.cpp index 36eab062846..2eca5b0ab6a 100644 --- a/src/random_ray/random_ray.cpp +++ b/src/random_ray/random_ray.cpp @@ -330,14 +330,14 @@ void RandomRay::event_advance_ray() // Advance particle for (int j = 0; j < n_coord(); ++j) { - coord(j).r += distance * coord(j).u; + coord(j).r() += distance * coord(j).u(); } } void RandomRay::attenuate_flux(double distance, bool is_active, double offset) { // Determine source region index etc. - int i_cell = lowest_coord().cell; + int i_cell = lowest_coord().cell(); // The base source region is the spatial region index int64_t sr = domain_->source_region_offsets_[i_cell] + cell_instance(); @@ -799,7 +799,7 @@ void RandomRay::initialize_ray(uint64_t ray_id, FlatSourceDomain* domain) this->from_source(&site); // Locate ray - if (lowest_coord().cell == C_NONE) { + if (lowest_coord().cell() == C_NONE) { if (!exhaustive_find_cell(*this)) { this->mark_as_lost( "Could not find the cell containing particle " + std::to_string(id())); @@ -807,12 +807,12 @@ void RandomRay::initialize_ray(uint64_t ray_id, FlatSourceDomain* domain) // Set birth cell attribute if (cell_born() == C_NONE) - cell_born() = lowest_coord().cell; + cell_born() = lowest_coord().cell(); } // Initialize ray's starting angular flux to starting location's isotropic // source - int i_cell = lowest_coord().cell; + int i_cell = lowest_coord().cell(); int64_t sr = domain_->source_region_offsets_[i_cell] + cell_instance(); SourceRegionHandle srh; diff --git a/src/source.cpp b/src/source.cpp index 65873f5a797..ae3daa404b7 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -246,9 +246,10 @@ bool Source::satisfies_spatial_constraints(Position r) const } } else { for (int i = 0; i < geom_state.n_coord(); i++) { - auto id = (domain_type_ == DomainType::CELL) - ? model::cells[geom_state.coord(i).cell]->id_ - : model::universes[geom_state.coord(i).universe]->id_; + auto id = + (domain_type_ == DomainType::CELL) + ? model::cells[geom_state.coord(i).cell()].get()->id_ + : model::universes[geom_state.coord(i).universe()].get()->id_; if ((accepted = contains(domain_ids_, id))) break; } diff --git a/src/tallies/filter_cell.cpp b/src/tallies/filter_cell.cpp index b545801ea8e..7a639495698 100644 --- a/src/tallies/filter_cell.cpp +++ b/src/tallies/filter_cell.cpp @@ -49,7 +49,7 @@ void CellFilter::get_all_bins( const Particle& p, TallyEstimator estimator, FilterMatch& match) const { for (int i = 0; i < p.n_coord(); i++) { - auto search = map_.find(p.coord(i).cell); + auto search = map_.find(p.coord(i).cell()); if (search != map_.end()) { match.bins_.push_back(search->second); match.weights_.push_back(1.0); diff --git a/src/tallies/filter_cell_instance.cpp b/src/tallies/filter_cell_instance.cpp index 0634175dd84..316a758d11a 100644 --- a/src/tallies/filter_cell_instance.cpp +++ b/src/tallies/filter_cell_instance.cpp @@ -73,7 +73,7 @@ void CellInstanceFilter::set_cell_instances(span instances) void CellInstanceFilter::get_all_bins( const Particle& p, TallyEstimator estimator, FilterMatch& match) const { - int64_t index_cell = p.lowest_coord().cell; + int64_t index_cell = p.lowest_coord().cell(); int64_t instance = p.cell_instance(); if (cells_.count(index_cell) > 0) { @@ -89,7 +89,7 @@ void CellInstanceFilter::get_all_bins( return; for (int i = 0; i < p.n_coord() - 1; i++) { - int64_t index_cell = p.coord(i).cell; + int64_t index_cell = p.coord(i).cell(); // if this cell isn't used on the filter, move on if (cells_.count(index_cell) == 0) continue; diff --git a/src/tallies/filter_distribcell.cpp b/src/tallies/filter_distribcell.cpp index 821e843daeb..02e88dde283 100644 --- a/src/tallies/filter_distribcell.cpp +++ b/src/tallies/filter_distribcell.cpp @@ -43,18 +43,18 @@ void DistribcellFilter::get_all_bins( int offset = 0; auto distribcell_index = model::cells[cell_]->distribcell_index_; for (int i = 0; i < p.n_coord(); i++) { - auto& c {*model::cells[p.coord(i).cell]}; + auto& c {*model::cells[p.coord(i).cell()]}; if (c.type_ == Fill::UNIVERSE) { offset += c.offset_[distribcell_index]; } else if (c.type_ == Fill::LATTICE) { - auto& lat {*model::lattices[p.coord(i + 1).lattice]}; - const auto& i_xyz {p.coord(i + 1).lattice_i}; + auto& lat {*model::lattices[p.coord(i + 1).lattice()]}; + const auto& i_xyz {p.coord(i + 1).lattice_index()}; if (lat.are_valid_indices(i_xyz)) { offset += lat.offset(distribcell_index, i_xyz) + c.offset_[distribcell_index]; } } - if (cell_ == p.coord(i).cell) { + if (cell_ == p.coord(i).cell()) { match.bins_.push_back(offset); match.weights_.push_back(1.0); return; diff --git a/src/tallies/filter_universe.cpp b/src/tallies/filter_universe.cpp index 48114cfad59..f4b22decd58 100644 --- a/src/tallies/filter_universe.cpp +++ b/src/tallies/filter_universe.cpp @@ -48,7 +48,7 @@ void UniverseFilter::get_all_bins( const Particle& p, TallyEstimator estimator, FilterMatch& match) const { for (int i = 0; i < p.n_coord(); i++) { - auto search = map_.find(p.coord(i).universe); + auto search = map_.find(p.coord(i).universe()); if (search != map_.end()) { match.bins_.push_back(search->second); match.weights_.push_back(1.0); diff --git a/src/tallies/tally_scoring.cpp b/src/tallies/tally_scoring.cpp index 71b103cfbfc..e73fb90f311 100644 --- a/src/tallies/tally_scoring.cpp +++ b/src/tallies/tally_scoring.cpp @@ -2615,7 +2615,7 @@ void score_pulse_height_tally(Particle& p, const vector& tallies) // Save original cell/energy information int orig_n_coord = p.n_coord(); - int orig_cell = p.coord(0).cell; + int orig_cell = p.coord(0).cell(); double orig_E_last = p.E_last(); for (auto i_tally : tallies) { @@ -2634,7 +2634,7 @@ void score_pulse_height_tally(Particle& p, const vector& tallies) // Temporarily change cell of particle p.n_coord() = 1; - p.coord(0).cell = cell_id; + p.coord(0).cell() = cell_id; // Determine index of cell in model::pulse_height_cells auto it = std::find(model::pulse_height_cells.begin(), @@ -2674,7 +2674,7 @@ void score_pulse_height_tally(Particle& p, const vector& tallies) } // Restore cell/energy p.n_coord() = orig_n_coord; - p.coord(0).cell = orig_cell; + p.coord(0).cell() = orig_cell; p.E_last() = orig_E_last; } } diff --git a/src/universe.cpp b/src/universe.cpp index b4ef6b4b265..78ddd54d4ff 100644 --- a/src/universe.cpp +++ b/src/universe.cpp @@ -45,14 +45,14 @@ bool Universe::find_cell(GeometryState& p) const Position r {p.r_local()}; Position u {p.u_local()}; auto surf = p.surface(); - int32_t i_univ = p.lowest_coord().universe; + int32_t i_univ = p.lowest_coord().universe(); for (auto i_cell : cells) { if (model::cells[i_cell]->universe_ != i_univ) continue; // Check if this cell contains the particle if (model::cells[i_cell]->contains(r, u, surf)) { - p.lowest_coord().cell = i_cell; + p.lowest_coord().cell() = i_cell; return true; } } diff --git a/src/volume_calc.cpp b/src/volume_calc.cpp index 8b5c27f14eb..1deffb80488 100644 --- a/src/volume_calc.cpp +++ b/src/volume_calc.cpp @@ -179,7 +179,7 @@ vector VolumeCalculation::execute() const } else if (domain_type_ == TallyDomain::CELL) { for (int level = 0; level < p.n_coord(); ++level) { for (int i_domain = 0; i_domain < n; i_domain++) { - if (model::cells[p.coord(level).cell]->id_ == + if (model::cells[p.coord(level).cell()]->id_ == domain_ids_[i_domain]) { this->check_hit( p.material(), indices[i_domain], hits[i_domain]); @@ -190,7 +190,7 @@ vector VolumeCalculation::execute() const } else if (domain_type_ == TallyDomain::UNIVERSE) { for (int level = 0; level < p.n_coord(); ++level) { for (int i_domain = 0; i_domain < n; ++i_domain) { - if (model::universes[p.coord(level).universe]->id_ == + if (model::universes[p.coord(level).universe()]->id_ == domain_ids_[i_domain]) { check_hit(p.material(), indices[i_domain], hits[i_domain]); break; From 4483583dddf291e6c324399511fbbccfd582e908 Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Fri, 18 Jul 2025 08:46:13 +0200 Subject: [PATCH 074/159] automatically finding appropriate dimension when making regular mesh from domain (#3468) --- openmc/mesh.py | 19 +++++++++++++++--- tests/unit_tests/test_mesh_from_domain.py | 24 +++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/openmc/mesh.py b/openmc/mesh.py index 56fe2bd1c91..33970288440 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -1094,7 +1094,7 @@ def from_rect_lattice( def from_domain( cls, domain: HasBoundingBox, - dimension: Sequence[int] = (10, 10, 10), + dimension: Sequence[int] | int = 1000, mesh_id: int | None = None, name: str = '' ): @@ -1106,8 +1106,11 @@ def from_domain( The object passed in will be used as a template for this mesh. The bounding box of the property of the object passed will be used to set the lower_left and upper_right and of the mesh instance - dimension : Iterable of int - The number of mesh cells in each direction (x, y, z). + dimension : Iterable of int | int + The number of mesh cells in total or number of mesh cells in each + direction (x, y, z). If a single integer is provided, the domain + will will be divided into that many mesh cells with roughly equal + lengths in each direction (cubes). mesh_id : int Unique identifier for the mesh name : str @@ -1125,6 +1128,16 @@ def from_domain( mesh = cls(mesh_id=mesh_id, name=name) mesh.lower_left = domain.bounding_box[0] mesh.upper_right = domain.bounding_box[1] + if isinstance(dimension, int): + cv.check_greater_than("dimension", dimension, 1, equality=True) + # If a single integer is provided, divide the domain into that many + # mesh cells with roughly equal lengths in each direction + ideal_cube_volume = domain.bounding_box.volume / dimension + ideal_cube_size = ideal_cube_volume ** (1 / 3) + dimension = [ + max(1, int(round(side / ideal_cube_size))) + for side in domain.bounding_box.width + ] mesh.dimension = dimension return mesh diff --git a/tests/unit_tests/test_mesh_from_domain.py b/tests/unit_tests/test_mesh_from_domain.py index 0e385891306..5b1173126ff 100644 --- a/tests/unit_tests/test_mesh_from_domain.py +++ b/tests/unit_tests/test_mesh_from_domain.py @@ -147,3 +147,27 @@ def test_reg_mesh_from_geometry(): def test_error_from_unsupported_object(): with pytest.raises(TypeError): openmc.RegularMesh.from_domain("vacuum energy") + + +def test_regularmesh_from_domain_error_from_small_dimensions(): + surface = openmc.Sphere(r=20) + cell = openmc.Cell(region=-surface) + with pytest.raises( + ValueError, match='Unable to set "dimension" to "-2" since it is less than "1"' + ): + openmc.RegularMesh.from_domain(domain=cell, dimension=-2) + + +def test_dimensions_from_domain_dimensions_from_int(): + region = openmc.model.RectangularParallelepiped( + xmin=-100, + xmax=150, + ymin=-50, + ymax=200, + zmin=300, + zmax=400, + boundary_type="vacuum", + ) + cell = openmc.Cell(region=-region) + mesh = openmc.RegularMesh.from_domain(domain=cell, dimension=1000) + assert mesh.dimension == (14, 14, 5) From 637e04a9ba9ceae8cb09eb66203e1581d465fa56 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 18 Jul 2025 02:51:30 -0500 Subject: [PATCH 075/159] Boundary info accessors (#3496) Co-authored-by: Paul Romano --- include/openmc/particle_data.h | 44 ++++++++++++++++++++++++---------- src/event.cpp | 2 +- src/geometry.cpp | 28 +++++++++++----------- src/mesh.cpp | 12 +++++----- src/particle.cpp | 12 +++++----- src/particle_data.cpp | 14 +++++------ src/plot.cpp | 28 +++++++++++----------- src/random_ray/random_ray.cpp | 2 +- src/simulation.cpp | 2 +- 9 files changed, 81 insertions(+), 63 deletions(-) diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index 8dd814a2965..1c8f1215a4f 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -210,23 +210,41 @@ struct CacheDataMG { // Information about nearest boundary crossing //============================================================================== -struct BoundaryInfo { - double distance {INFINITY}; //!< distance to nearest boundary - int surface { - SURFACE_NONE}; //!< surface token, non-zero if boundary is surface - int coord_level; //!< coordinate level after crossing boundary - array - lattice_translation {}; //!< which way lattice indices will change - +class BoundaryInfo { +public: void reset() { - distance = INFINITY; - surface = SURFACE_NONE; - coord_level = 0; - lattice_translation = {0, 0, 0}; + distance_ = INFINITY; + surface_ = SURFACE_NONE; + coord_level_ = 0; + lattice_translation_ = {0, 0, 0}; + } + double& distance() { return distance_; } + const double& distance() const { return distance_; } + + int& surface() { return surface_; } + const int& surface() const { return surface_; } + + int coord_level() const { return coord_level_; } + int& coord_level() { return coord_level_; } + + array& lattice_translation() { return lattice_translation_; } + const array& lattice_translation() const + { + return lattice_translation_; } + // TODO: off-by-one - int surface_index() const { return std::abs(surface) - 1; } + int surface_index() const { return std::abs(surface()) - 1; } + +private: + // Data members + double distance_ {INFINITY}; //!< distance to nearest boundary + int surface_ { + SURFACE_NONE}; //!< surface token, non-zero if boundary is surface + int coord_level_ {0}; //!< coordinate level after crossing boundary + array lattice_translation_ { + 0, 0, 0}; //!< which way lattice indices will change }; /* diff --git a/src/event.cpp b/src/event.cpp index ae914c8be34..aa3987504b6 100644 --- a/src/event.cpp +++ b/src/event.cpp @@ -114,7 +114,7 @@ void process_advance_particle_events() p.event_advance(); if (!p.alive()) continue; - if (p.collision_distance() > p.boundary().distance) { + if (p.collision_distance() > p.boundary().distance()) { simulation::surface_crossing_queue.thread_safe_append({p, buffer_idx}); } else { simulation::collision_queue.thread_safe_append({p, buffer_idx}); diff --git a/src/geometry.cpp b/src/geometry.cpp index cefe7d3649c..df485089163 100644 --- a/src/geometry.cpp +++ b/src/geometry.cpp @@ -310,9 +310,9 @@ void cross_lattice(GeometryState& p, const BoundaryInfo& boundary, bool verbose) } // Set the lattice indices. - coord.lattice_index()[0] += boundary.lattice_translation[0]; - coord.lattice_index()[1] += boundary.lattice_translation[1]; - coord.lattice_index()[2] += boundary.lattice_translation[2]; + coord.lattice_index()[0] += boundary.lattice_translation()[0]; + coord.lattice_index()[1] += boundary.lattice_translation()[1]; + coord.lattice_index()[2] += boundary.lattice_translation()[2]; // Set the new coordinate position. const auto& upper_coord {p.coord(p.n_coord() - 2)}; @@ -410,7 +410,7 @@ BoundaryInfo distance_to_boundary(GeometryState& p) // If the boundary on this coordinate level is coincident with a boundary on // a higher level then we need to make sure that the higher level boundary // is selected. This logic must consider floating point precision. - double& d = info.distance; + double& d = info.distance(); if (d_surf < d_lat - FP_COINCIDENT) { if (d == INFINITY || (d - d_surf) / d >= FP_REL_PRECISION) { // Update closest distance @@ -421,29 +421,29 @@ BoundaryInfo distance_to_boundary(GeometryState& p) // have to explicitly check which half-space the particle would be // traveling into if the surface is crossed if (c.is_simple() || d == INFTY) { - info.surface = level_surf_cross; + info.surface() = level_surf_cross; } else { Position r_hit = r + d_surf * u; Surface& surf {*model::surfaces[std::abs(level_surf_cross) - 1]}; Direction norm = surf.normal(r_hit); if (u.dot(norm) > 0) { - info.surface = std::abs(level_surf_cross); + info.surface() = std::abs(level_surf_cross); } else { - info.surface = -std::abs(level_surf_cross); + info.surface() = -std::abs(level_surf_cross); } } - info.lattice_translation[0] = 0; - info.lattice_translation[1] = 0; - info.lattice_translation[2] = 0; - info.coord_level = i + 1; + info.lattice_translation()[0] = 0; + info.lattice_translation()[1] = 0; + info.lattice_translation()[2] = 0; + info.coord_level() = i + 1; } } else { if (d == INFINITY || (d - d_lat) / d >= FP_REL_PRECISION) { d = d_lat; - info.surface = SURFACE_NONE; - info.lattice_translation = level_lat_trans; - info.coord_level = i + 1; + info.surface() = SURFACE_NONE; + info.lattice_translation() = level_lat_trans; + info.coord_level() = i + 1; } } } diff --git a/src/mesh.cpp b/src/mesh.cpp index 1b2c71eb226..9147efb950c 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -385,7 +385,7 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size, BoundaryInfo boundary = distance_to_boundary(p); // Advance particle forward - double distance = std::min(boundary.distance, max_distance); + double distance = std::min(boundary.distance(), max_distance); p.move_distance(distance); // Determine what mesh elements were crossed by particle @@ -416,12 +416,12 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size, p.n_coord_last() = p.n_coord(); // Set surface that particle is on and adjust coordinate levels - p.surface() = boundary.surface; - p.n_coord() = boundary.coord_level; + p.surface() = boundary.surface(); + p.n_coord() = boundary.coord_level(); - if (boundary.lattice_translation[0] != 0 || - boundary.lattice_translation[1] != 0 || - boundary.lattice_translation[2] != 0) { + if (boundary.lattice_translation()[0] != 0 || + boundary.lattice_translation()[1] != 0 || + boundary.lattice_translation()[2] != 0) { // Particle crosses lattice boundary cross_lattice(p, boundary); } else { diff --git a/src/particle.cpp b/src/particle.cpp index 4758cded591..f9044b2d7ca 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -242,7 +242,7 @@ void Particle::event_advance() } // Select smaller of the two distances - double distance = std::min(boundary().distance, collision_distance()); + double distance = std::min(boundary().distance(), collision_distance()); // Advance particle in space and time // Short-term solution until the surface source is revised and we can use @@ -298,12 +298,12 @@ void Particle::event_cross_surface() n_coord_last() = n_coord(); // Set surface that particle is on and adjust coordinate levels - surface() = boundary().surface; - n_coord() = boundary().coord_level; + surface() = boundary().surface(); + n_coord() = boundary().coord_level(); - if (boundary().lattice_translation[0] != 0 || - boundary().lattice_translation[1] != 0 || - boundary().lattice_translation[2] != 0) { + if (boundary().lattice_translation()[0] != 0 || + boundary().lattice_translation()[1] != 0 || + boundary().lattice_translation()[2] != 0) { // Particle crosses lattice boundary bool verbose = settings::verbosity >= 10 || trace(); diff --git a/src/particle_data.cpp b/src/particle_data.cpp index aff2b68bada..370ca12e469 100644 --- a/src/particle_data.cpp +++ b/src/particle_data.cpp @@ -65,22 +65,22 @@ void GeometryState::advance_to_boundary_from_void() for (auto c_i : root_universe->cells_) { auto dist = model::cells.at(c_i)->distance(root_coord.r(), root_coord.u(), 0, this); - if (dist.first < boundary().distance) { - boundary().distance = dist.first; - boundary().surface = dist.second; + if (dist.first < boundary().distance()) { + boundary().distance() = dist.first; + boundary().surface() = dist.second; } } // if no intersection or near-infinite intersection, reset // boundary information - if (boundary().distance > 1e300) { - boundary().distance = INFTY; - boundary().surface = SURFACE_NONE; + if (boundary().distance() > 1e300) { + boundary().distance() = INFTY; + boundary().surface() = SURFACE_NONE; return; } // move the particle up to (and just past) the boundary - move_distance(boundary().distance + TINY_BIT); + move_distance(boundary().distance() + TINY_BIT); } void GeometryState::move_distance(double length) diff --git a/src/plot.cpp b/src/plot.cpp index db10c76006e..2cadc48cefe 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -1634,7 +1634,7 @@ void Ray::trace() break; // if there is no intersection with the model, we're done - if (boundary().surface == SURFACE_NONE) + if (boundary().surface() == SURFACE_NONE) return; event_counter_++; @@ -1646,10 +1646,10 @@ void Ray::trace() // Call the specialized logic for this type of ray. This is for the // intersection for the first intersection if we had one. - if (boundary().surface != SURFACE_NONE) { + if (boundary().surface() != SURFACE_NONE) { // set the geometry state's surface attribute to be used for // surface normal computation - surface() = boundary().surface; + surface() = boundary().surface(); on_intersection(); if (stop_) return; @@ -1674,37 +1674,37 @@ void Ray::trace() // if we hit the edge of the model, so stop // the particle in that case. Also, just exit // if a negative distance was somehow computed. - if (boundary().distance == INFTY || boundary().distance == INFINITY || - boundary().distance < 0) { + if (boundary().distance() == INFTY || boundary().distance() == INFINITY || + boundary().distance() < 0) { return; } // See below comment where call_on_intersection is checked in an // if statement for an explanation of this. bool call_on_intersection {true}; - if (boundary().distance < 10 * TINY_BIT) { + if (boundary().distance() < 10 * TINY_BIT) { call_on_intersection = false; } // DAGMC surfaces expect us to go a little bit further than the advance // distance to properly check cell inclusion. - boundary().distance += TINY_BIT; + boundary().distance() += TINY_BIT; // Advance particle, prepare for next intersection for (int lev = 0; lev < n_coord(); ++lev) { - coord(lev).r() += boundary().distance * coord(lev).u(); + coord(lev).r() += boundary().distance() * coord(lev).u(); } - surface() = boundary().surface; + surface() = boundary().surface(); n_coord_last() = n_coord(); - n_coord() = boundary().coord_level; - if (boundary().lattice_translation[0] != 0 || - boundary().lattice_translation[1] != 0 || - boundary().lattice_translation[2] != 0) { + n_coord() = boundary().coord_level(); + if (boundary().lattice_translation()[0] != 0 || + boundary().lattice_translation()[1] != 0 || + boundary().lattice_translation()[2] != 0) { cross_lattice(*this, boundary(), settings::verbosity >= 10); } // Record how far the ray has traveled - traversal_distance_ += boundary().distance; + traversal_distance_ += boundary().distance(); inside_cell = neighbor_list_find_cell(*this, settings::verbosity >= 10); // Call the specialized logic for this type of ray. Note that we do not diff --git a/src/random_ray/random_ray.cpp b/src/random_ray/random_ray.cpp index 2eca5b0ab6a..27f674c4278 100644 --- a/src/random_ray/random_ray.cpp +++ b/src/random_ray/random_ray.cpp @@ -281,7 +281,7 @@ void RandomRay::event_advance_ray() { // Find the distance to the nearest boundary boundary() = distance_to_boundary(*this); - double distance = boundary().distance; + double distance = boundary().distance(); if (distance < 0.0) { mark_as_lost("Negative transport distance detected for particle " + diff --git a/src/simulation.cpp b/src/simulation.cpp index d9a4fd131dc..b9986f47375 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -790,7 +790,7 @@ void transport_history_based_single_particle(Particle& p) p.event_advance(); } if (p.alive()) { - if (p.collision_distance() > p.boundary().distance) { + if (p.collision_distance() > p.boundary().distance()) { p.event_cross_surface(); } else if (p.alive()) { p.event_collide(); From 659e43af7de9030ef3fbfef32f0992c39a00820d Mon Sep 17 00:00:00 2001 From: Joffrey Dorville <54550047+JoffreyDorville@users.noreply.github.com> Date: Fri, 18 Jul 2025 03:47:08 -0500 Subject: [PATCH 076/159] Enabling MCPL source files to be read when using surf_source_read (#3472) Co-authored-by: Paul Romano --- include/openmc/source.h | 2 +- src/source.cpp | 47 ++++--- ..._true_read.dat => inputs_true_read_h5.dat} | 0 .../surface_source/inputs_true_read_mcpl.dat | 33 +++++ ...rue_write.dat => inputs_true_write_h5.dat} | 0 .../surface_source/inputs_true_write_mcpl.dat | 40 ++++++ .../surface_source/surface_source_true.h5 | Bin 106144 -> 106144 bytes .../surface_source/surface_source_true.mcpl | Bin 0 -> 36071 bytes tests/regression_tests/surface_source/test.py | 116 +++++++++++++----- 9 files changed, 183 insertions(+), 55 deletions(-) rename tests/regression_tests/surface_source/{inputs_true_read.dat => inputs_true_read_h5.dat} (100%) create mode 100644 tests/regression_tests/surface_source/inputs_true_read_mcpl.dat rename tests/regression_tests/surface_source/{inputs_true_write.dat => inputs_true_write_h5.dat} (100%) create mode 100644 tests/regression_tests/surface_source/inputs_true_write_mcpl.dat create mode 100644 tests/regression_tests/surface_source/surface_source_true.mcpl diff --git a/include/openmc/source.h b/include/openmc/source.h index d58195f1089..1fbd319048d 100644 --- a/include/openmc/source.h +++ b/include/openmc/source.h @@ -173,7 +173,7 @@ class FileSource : public Source { SourceSite sample(uint64_t* seed) const override; private: - vector sites_; //!< Source sites from a file + vector sites_; //!< Source sites }; //============================================================================== diff --git a/src/source.cpp b/src/source.cpp index ae3daa404b7..12323f7bd7e 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -417,11 +417,7 @@ SourceSite IndependentSource::sample(uint64_t* seed) const FileSource::FileSource(pugi::xml_node node) : Source(node) { auto path = get_node_value(node, "file", false, true); - if (ends_with(path, ".mcpl") || ends_with(path, ".mcpl.gz")) { - sites_ = mcpl_source_sites(path); - } else { - this->load_sites_from_file(path); - } + load_sites_from_file(path); } FileSource::FileSource(const std::string& path) @@ -431,30 +427,33 @@ FileSource::FileSource(const std::string& path) void FileSource::load_sites_from_file(const std::string& path) { - // Check if source file exists - if (!file_exists(path)) { - fatal_error(fmt::format("Source file '{}' does not exist.", path)); - } + // If MCPL file, use the dedicated file reader + if (ends_with(path, ".mcpl") || ends_with(path, ".mcpl.gz")) { + sites_ = mcpl_source_sites(path); + } else { + // Check if source file exists + if (!file_exists(path)) { + fatal_error(fmt::format("Source file '{}' does not exist.", path)); + } - // Read the source from a binary file instead of sampling from some - // assumed source distribution - write_message(6, "Reading source file from {}...", path); + write_message(6, "Reading source file from {}...", path); - // Open the binary file - hid_t file_id = file_open(path, 'r', true); + // Open the binary file + hid_t file_id = file_open(path, 'r', true); - // Check to make sure this is a source file - std::string filetype; - read_attribute(file_id, "filetype", filetype); - if (filetype != "source" && filetype != "statepoint") { - fatal_error("Specified starting source file not a source file type."); - } + // Check to make sure this is a source file + std::string filetype; + read_attribute(file_id, "filetype", filetype); + if (filetype != "source" && filetype != "statepoint") { + fatal_error("Specified starting source file not a source file type."); + } - // Read in the source particles - read_source_bank(file_id, sites_, false); + // Read in the source particles + read_source_bank(file_id, sites_, false); - // Close file - file_close(file_id); + // Close file + file_close(file_id); + } } SourceSite FileSource::sample(uint64_t* seed) const diff --git a/tests/regression_tests/surface_source/inputs_true_read.dat b/tests/regression_tests/surface_source/inputs_true_read_h5.dat similarity index 100% rename from tests/regression_tests/surface_source/inputs_true_read.dat rename to tests/regression_tests/surface_source/inputs_true_read_h5.dat diff --git a/tests/regression_tests/surface_source/inputs_true_read_mcpl.dat b/tests/regression_tests/surface_source/inputs_true_read_mcpl.dat new file mode 100644 index 00000000000..b8d8c575aac --- /dev/null +++ b/tests/regression_tests/surface_source/inputs_true_read_mcpl.dat @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + + surface_source_true.mcpl + + 1 + + + + 3 + + + 1 + flux + + + diff --git a/tests/regression_tests/surface_source/inputs_true_write.dat b/tests/regression_tests/surface_source/inputs_true_write_h5.dat similarity index 100% rename from tests/regression_tests/surface_source/inputs_true_write.dat rename to tests/regression_tests/surface_source/inputs_true_write_h5.dat diff --git a/tests/regression_tests/surface_source/inputs_true_write_mcpl.dat b/tests/regression_tests/surface_source/inputs_true_write_mcpl.dat new file mode 100644 index 00000000000..c55223324e0 --- /dev/null +++ b/tests/regression_tests/surface_source/inputs_true_write_mcpl.dat @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + + + 0 0 0 + + + + 1 + true + 1000 + + 1 + + + + 3 + + + 1 + flux + + + diff --git a/tests/regression_tests/surface_source/surface_source_true.h5 b/tests/regression_tests/surface_source/surface_source_true.h5 index f2055d09dc3b668cc2fe85057cd999779788e9d7..d056d7328405bedf704f8690fd3b8056ec8aa101 100644 GIT binary patch delta 11203 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 NZmvQvPV5ll`9IHZo-P0Y literal 0 HcmV?d00001 diff --git a/tests/regression_tests/surface_source/test.py b/tests/regression_tests/surface_source/test.py index 83b78e61deb..13966b26d98 100644 --- a/tests/regression_tests/surface_source/test.py +++ b/tests/regression_tests/surface_source/test.py @@ -10,6 +10,25 @@ from tests.regression_tests import config +def mcpl_to_array(filepath): + import mcpl + + source = [] + with mcpl.MCPLFile(filepath) as f: + for p in f.particles: + source.append( + [ + *tuple(p.position), + *tuple(p.direction), + 1.0e6 * p.ekin, + 1.0e-3 * p.time, + p.weight, + p.pdgcode, + ] + ) + return np.sort(np.array(source), axis=0) + + def assert_structured_arrays_close(arr1, arr2, rtol=1e-5, atol=1e-8): assert arr1.dtype == arr2.dtype @@ -24,8 +43,7 @@ def assert_structured_arrays_close(arr1, arr2, rtol=1e-5, atol=1e-8): @pytest.fixture def model(request): openmc.reset_auto_ids() - marker = request.node.get_closest_marker("surf_source_op") - surf_source_op = marker.args[0] + operation, file_format = request.node.get_closest_marker("params").args openmc_model = openmc.model.Model() @@ -54,15 +72,19 @@ def model(request): openmc_model.settings.batches = 10 openmc_model.settings.seed = 1 - if surf_source_op == 'write': + if operation == 'write': point = openmc.stats.Point((0, 0, 0)) pt_src = openmc.IndependentSource(space=point) openmc_model.settings.source = pt_src - openmc_model.settings.surf_source_write = {'surface_ids': [1], - 'max_particles': 1000} - elif surf_source_op == 'read': - openmc_model.settings.surf_source_read = {'path': 'surface_source_true.h5'} + surf_source_write_settings = {'surface_ids': [1], + 'max_particles': 1000} + if file_format == "mcpl": + surf_source_write_settings["mcpl"] = True + + openmc_model.settings.surf_source_write = surf_source_write_settings + elif operation == 'read': + openmc_model.settings.surf_source_read = {'path': f"surface_source_true.{file_format}"} # Tallies tal = openmc.Tally() @@ -75,22 +97,30 @@ def model(request): class SurfaceSourceTestHarness(PyAPITestHarness): + def __init__(self, statepoint_name, model=None, inputs_true=None, file_format="h5"): + super().__init__(statepoint_name, model, inputs_true) + self.file_format = file_format + def _test_output_created(self): - """Make sure surface_source.h5 has also been created.""" + """Make sure the surface_source file has also been created.""" super()._test_output_created() - # Check if 'surface_source.h5' has been created. if self._model.settings.surf_source_write: - assert os.path.exists('surface_source.h5'), \ + assert os.path.exists(f"surface_source.{self.file_format}"), \ 'Surface source file does not exist.' def _compare_output(self): """Make sure the current surface_source.h5 agree with the reference.""" if self._model.settings.surf_source_write: - with h5py.File("surface_source_true.h5", 'r') as f: - source_true = np.sort(f['source_bank'][()]) - with h5py.File("surface_source.h5", 'r') as f: - source_test = np.sort(f['source_bank'][()]) - assert_structured_arrays_close(source_true, source_test, atol=1e-07) + if self.file_format == "h5": + with h5py.File("surface_source_true.h5", 'r') as f: + source_true = np.sort(f['source_bank'][()]) + with h5py.File("surface_source.h5", 'r') as f: + source_test = np.sort(f['source_bank'][()]) + assert_structured_arrays_close(source_true, source_test, atol=1e-07) + elif self.file_format == "mcpl": + source_true = mcpl_to_array("surface_source_true.mcpl") + source_test = mcpl_to_array("surface_source.mcpl") + np.testing.assert_allclose(source_true, source_test, rtol=1e-5, atol=1e-7) def execute_test(self): """Build input XMLs, run OpenMC, check output and results.""" @@ -111,30 +141,56 @@ def execute_test(self): def _overwrite_results(self): """Overwrite the results_true with the results_test.""" shutil.copyfile('results_test.dat', 'results_true.dat') - if os.path.exists('surface_source.h5'): - shutil.copyfile('surface_source.h5', 'surface_source_true.h5') + if os.path.exists(f"surface_source.{self.file_format}"): + shutil.copyfile(f"surface_source.{self.file_format}", f"surface_source_true.{self.file_format}") def _cleanup(self): """Delete statepoints, tally, and test files.""" super()._cleanup() - fs = 'surface_source.h5' + fs = f"surface_source.{self.file_format}" if os.path.exists(fs): os.remove(fs) -@pytest.mark.surf_source_op('write') -def test_surface_source_write(model, monkeypatch): - # Test result is based on 1 MPI process - monkeypatch.setitem(config, "mpi_np", "1") - harness = SurfaceSourceTestHarness('statepoint.10.h5', - model, - 'inputs_true_write.dat') +@pytest.mark.params('write', 'h5') +def test_surface_source_write(model, monkeypatch, request): + monkeypatch.setitem(config, "mpi_np", "1") # Results generated with 1 MPI process + operation, file_format = request.node.get_closest_marker("params").args + harness = SurfaceSourceTestHarness( + "statepoint.10.h5", model, f"inputs_true_{operation}_{file_format}.dat", + file_format=file_format + ) + harness.main() + + +@pytest.mark.params('read', 'h5') +def test_surface_source_read(model, request): + operation, file_format = request.node.get_closest_marker("params").args + harness = SurfaceSourceTestHarness( + "statepoint.10.h5", model, f"inputs_true_{operation}_{file_format}.dat", + file_format=file_format + ) + harness.main() + + +@pytest.mark.skipif(shutil.which("mcpl-config") is None, reason="MCPL is not available.") +@pytest.mark.params('write', 'mcpl') +def test_surface_source_write_mcpl(model, monkeypatch, request): + monkeypatch.setitem(config, "mpi_np", "1") # Results generated with 1 MPI process + operation, file_format = request.node.get_closest_marker("params").args + harness = SurfaceSourceTestHarness( + "statepoint.10.h5", model, f"inputs_true_{operation}_{file_format}.dat", + file_format=file_format + ) harness.main() -@pytest.mark.surf_source_op('read') -def test_surface_source_read(model): - harness = SurfaceSourceTestHarness('statepoint.10.h5', - model, - 'inputs_true_read.dat') +@pytest.mark.skipif(shutil.which("mcpl-config") is None, reason="MCPL is not available.") +@pytest.mark.params('read', 'mcpl') +def test_surface_source_read_mcpl(model, request): + operation, file_format = request.node.get_closest_marker("params").args + harness = SurfaceSourceTestHarness( + "statepoint.10.h5", model, f"inputs_true_{operation}_{file_format}.dat", + file_format=file_format + ) harness.main() From 8be65513b77c08e79e1dc5f4ef921d411ff990a9 Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Fri, 18 Jul 2025 16:37:57 +0200 Subject: [PATCH 077/159] Adding material depletion function (#3420) Co-authored-by: Jon Shimwell Co-authored-by: Micah Gale Co-authored-by: Paul Romano --- openmc/deplete/independent_operator.py | 2 +- openmc/material.py | 174 +++++++++++++++++++++++++ tests/unit_tests/test_material.py | 43 ++++++ tests/unit_tests/test_materials.py | 63 +++++++++ 4 files changed, 281 insertions(+), 1 deletion(-) create mode 100644 tests/unit_tests/test_materials.py diff --git a/openmc/deplete/independent_operator.py b/openmc/deplete/independent_operator.py index 3eb50b05ec1..c192907cf20 100644 --- a/openmc/deplete/independent_operator.py +++ b/openmc/deplete/independent_operator.py @@ -273,7 +273,7 @@ def _get_nuclides_with_data(self, cross_sections: list[MicroXS]) -> set[str]: Returns ------- nuclides : set of str - Set of nuclide names that have cross secton data + Set of nuclide names that have cross section data """ return set(cross_sections[0].nuclides) diff --git a/openmc/material.py b/openmc/material.py index c6765178e9d..cec74af6270 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -6,6 +6,8 @@ from pathlib import Path import re import sys +import tempfile +from typing import Sequence, Dict import warnings import lxml.etree as ET @@ -1717,6 +1719,67 @@ def from_xml_element(cls, elem: ET.Element) -> Material: return mat + def deplete( + self, + multigroup_flux: Sequence[float], + energy_group_structure: Sequence[float] | str, + timesteps: Sequence[float] | Sequence[tuple[float, str]], + source_rates: float | Sequence[float], + timestep_units: str = 's', + chain_file: cv.PathLike | "openmc.deplete.Chain" | None = None, + reactions: Sequence[str] | None = None, + ) -> list[openmc.Material]: + """Depletes that material, evolving the nuclide densities + + .. versionadded:: 0.15.3 + + Parameters + ---------- + multigroup_flux: Sequence[float] + Energy-dependent multigroup flux values, where each sublist corresponds + to a specific material. Will be normalized so that it sums to 1. + energy_group_structure : Sequence[float] | str + Energy group boundaries in [eV] or the name of the group structure. + timesteps : iterable of float or iterable of tuple + Array of timesteps. Note that values are not cumulative. The units are + specified by the `timestep_units` argument when `timesteps` is an + iterable of float. Alternatively, units can be specified for each step + by passing an iterable of (value, unit) tuples. + source_rates : float or iterable of float, optional + Source rate in [neutron/sec] or neutron flux in [neutron/s-cm^2] for + each interval in :attr:`timesteps` + timestep_units : {'s', 'min', 'h', 'd', 'a', 'MWd/kg'} + Units for values specified in the `timesteps` argument. 's' means + seconds, 'min' means minutes, 'h' means hours, 'a' means Julian years + and 'MWd/kg' indicates that the values are given in burnup (MW-d of + energy deposited per kilogram of initial heavy metal). + chain_file : PathLike or Chain + Path to the depletion chain XML file or instance of openmc.deplete.Chain. + Defaults to ``openmc.config['chain_file']``. + reactions : list of str, optional + Reactions to get cross sections for. If not specified, all neutron + reactions listed in the depletion chain file are used. + + Returns + ------- + list of openmc.Material, one for each timestep + + """ + + materials = openmc.Materials([self]) + + depleted_materials_dict = materials.deplete( + multigroup_fluxes=[multigroup_flux], + energy_group_structures=[energy_group_structure], + timesteps=timesteps, + source_rates=source_rates, + timestep_units=timestep_units, + chain_file=chain_file, + reactions=reactions, + ) + + return depleted_materials_dict[self.id] + def mean_free_path(self, energy: float) -> float: """Calculate the mean free path of neutrons in the material at a given @@ -1947,3 +2010,114 @@ def from_xml(cls, path: PathLike = 'materials.xml') -> Materials: root = tree.getroot() return cls.from_xml_element(root) + + + def deplete( + self, + multigroup_fluxes: Sequence[Sequence[float]], + energy_group_structures: Sequence[Sequence[float] | str], + timesteps: Sequence[float] | Sequence[tuple[float, str]], + source_rates: float | Sequence[float], + timestep_units: str = 's', + chain_file: cv.PathLike | "openmc.deplete.Chain" | None = None, + reactions: Sequence[str] | None = None, + ) -> Dict[int, list[openmc.Material]]: + """Depletes that material, evolving the nuclide densities + + .. versionadded:: 0.15.3 + + Parameters + ---------- + multigroup_fluxes: Sequence[Sequence[float]] + Energy-dependent multigroup flux values, where each sublist corresponds + to a specific material. Will be normalized so that it sums to 1. + energy_group_structures': Sequence[Sequence[float] | str] + Energy group boundaries in [eV] or the name of the group structure. + timesteps : iterable of float or iterable of tuple + Array of timesteps. Note that values are not cumulative. The units are + specified by the `timestep_units` argument when `timesteps` is an + iterable of float. Alternatively, units can be specified for each step + by passing an iterable of (value, unit) tuples. + source_rates : float or iterable of float, optional + Source rate in [neutron/sec] or neutron flux in [neutron/s-cm^2] for + each interval in :attr:`timesteps` + timestep_units : {'s', 'min', 'h', 'd', 'a', 'MWd/kg'} + Units for values specified in the `timesteps` argument. 's' means + seconds, 'min' means minutes, 'h' means hours, 'a' means Julian years + and 'MWd/kg' indicates that the values are given in burnup (MW-d of + energy deposited per kilogram of initial heavy metal). + chain_file : PathLike or Chain + Path to the depletion chain XML file or instance of openmc.deplete.Chain. + Defaults to ``openmc.config['chain_file']``. + reactions : list of str, optional + Reactions to get cross sections for. If not specified, all neutron + reactions listed in the depletion chain file are used. + + Returns + ------- + list of openmc.Material, one for each timestep + + """ + + import openmc.deplete + from .deplete.chain import _get_chain + + # setting all materials to be depletable + for mat in self: + mat.depletable = True + + chain = _get_chain(chain_file) + + # Create MicroXS objects for all materials + micros = [] + fluxes = [] + + with openmc.lib.TemporarySession(): + for material, flux, energy in zip( + self, multigroup_fluxes, energy_group_structures + ): + temperature = material.temperature or 293.6 + micro_xs = openmc.deplete.MicroXS.from_multigroup_flux( + energies=energy, + multigroup_flux=flux, + chain_file=chain, + temperature=temperature, + reactions=reactions, + ) + micros.append(micro_xs) + fluxes.append(material.volume) + + # Create a single operator for all materials + operator = openmc.deplete.IndependentOperator( + materials=self, + fluxes=fluxes, + micros=micros, + normalization_mode="source-rate", + chain_file=chain, + ) + + integrator = openmc.deplete.PredictorIntegrator( + operator=operator, + timesteps=timesteps, + source_rates=source_rates, + timestep_units=timestep_units, + ) + + with tempfile.TemporaryDirectory() as tmpdir: + # Run integrator + results_path = Path(tmpdir) / "depletion_results.h5" + integrator.integrate(path=results_path) + + # Load depletion results + results = openmc.deplete.Results(results_path) + + # For each material, get activated composition at each timestep + all_depleted_materials = { + material.id: [ + result.get_material(str(material.id)) + for result in results + ] + for material in self + } + + return all_depleted_materials diff --git a/tests/unit_tests/test_material.py b/tests/unit_tests/test_material.py index db5f4ce32d5..eae814a755b 100644 --- a/tests/unit_tests/test_material.py +++ b/tests/unit_tests/test_material.py @@ -3,8 +3,11 @@ import pytest +import numpy as np + import openmc from openmc.data import decay_photon_energy +from openmc.deplete import Chain import openmc.examples import openmc.model import openmc.stats @@ -712,6 +715,46 @@ def test_avoid_subnormal(run_in_tmpdir): assert mats[0].get_nuclide_atom_densities()['H2'] == 0.0 +def test_material_deplete(): + pristine_material = openmc.Material() + pristine_material.add_nuclide("Ni58", 1.0) + pristine_material.set_density("g/cm3", 7.87) + pristine_material.depletable = True + pristine_material.temperature = 293.6 + pristine_material.volume = 1. + + mg_flux = [0.5e11] * 42 + + chain = Chain.from_xml( + Path(__file__).parents[1] / "chain_ni.xml" + ) + + depleted_material = pristine_material.deplete( + multigroup_flux=mg_flux, + energy_group_structure="VITAMIN-J-42", + timesteps=[10, 70.86], + source_rates=[1e19, 0.0], + timestep_units="d", + chain_file=chain, + ) + + for material in depleted_material: + assert isinstance(material, openmc.Material) + assert len(material.get_nuclides()) > len(pristine_material.get_nuclides()) + + Co58_mat_1_step_0 = depleted_material[0].get_nuclide_atom_densities("Co58")["Co58"] + Co58_mat_1_step_1 = depleted_material[1].get_nuclide_atom_densities("Co58")["Co58"] + Co58_mat_1_step_2 = depleted_material[2].get_nuclide_atom_densities("Co58")["Co58"] + + assert Co58_mat_1_step_0 == 0.0 + + # Check that Co58 is produced in the first step + assert Co58_mat_1_step_1 > 0.0 + + # Check that Co58 is halved in the second step which is one halflife later + assert np.allclose(Co58_mat_1_step_1 * 0.5, Co58_mat_1_step_2) + + def test_mean_free_path(): mat1 = openmc.Material() diff --git a/tests/unit_tests/test_materials.py b/tests/unit_tests/test_materials.py new file mode 100644 index 00000000000..6c8c59fc925 --- /dev/null +++ b/tests/unit_tests/test_materials.py @@ -0,0 +1,63 @@ +from pathlib import Path + +import openmc +from openmc.deplete import Chain + + +def test_materials_deplete(): + pristine_material_1 = openmc.Material() + pristine_material_1.add_nuclide("Ni58", 1.) + pristine_material_1.set_density("g/cm3", 7.87) + pristine_material_1.depletable = True + pristine_material_1.temperature = 293.6 + pristine_material_1.volume = 1. + + pristine_material_2 = openmc.Material() + pristine_material_2.add_nuclide("Ni60", 1.) + pristine_material_2.set_density("g/cm3", 7.87) + pristine_material_2.depletable = True + pristine_material_2.temperature = 293.6 + pristine_material_2.volume = 1. + + pristine_materials = openmc.Materials([pristine_material_1, pristine_material_2]) + + mg_flux = [0.5e11] * 42 + + chain = Chain.from_xml( + Path(__file__).parents[1] / "chain_ni.xml" + ) + + depleted_material = pristine_materials.deplete( + multigroup_fluxes=[mg_flux, mg_flux], + energy_group_structures=["VITAMIN-J-42", "VITAMIN-J-42"], + timesteps=[100, 100], + source_rates=[1e19, 0.0], + timestep_units="d", + chain_file=chain, + ) + + assert list(depleted_material.keys()) == [pristine_material_1.id, pristine_material_2.id] + for mat_id, materials in depleted_material.items(): + for material in materials: + assert isinstance(material, openmc.Material) + assert len(material.get_nuclides()) > 1 + assert mat_id == material.id + + mats = depleted_material[pristine_material_1.id] + Co58_mat_1_step_0 = mats[0].get_nuclide_atom_densities("Co58")["Co58"] + Co58_mat_1_step_1 = mats[1].get_nuclide_atom_densities("Co58")["Co58"] + Co58_mat_1_step_2 = mats[2].get_nuclide_atom_densities("Co58")["Co58"] + + assert Co58_mat_1_step_0 == 0.0 + # Co58 is the main activation product of Ni58 in the first irradiation step. + # It then decays in the second cooling step (flux = 0) + assert Co58_mat_1_step_1 > 0.0 and Co58_mat_1_step_1 > Co58_mat_1_step_2 + + Ni59_mat_1_step_0 = mats[0].get_nuclide_atom_densities("Ni59")["Ni59"] + Ni59_mat_1_step_1 = mats[1].get_nuclide_atom_densities("Ni59")["Ni59"] + Ni59_mat_1_step_2 = mats[2].get_nuclide_atom_densities("Ni59")["Ni59"] + + assert Ni59_mat_1_step_0 == 0.0 + # Ni59 is one of the main activation product of Ni60 in the first irradiation + # step. It then decays in the second cooling step (flux = 0) + assert Ni59_mat_1_step_1 > 0.0 and Ni59_mat_1_step_1 > Ni59_mat_1_step_2 From a649d7bc69c47d5f04dbca50b106b605598ffcf7 Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Fri, 18 Jul 2025 18:23:02 +0200 Subject: [PATCH 078/159] Avoid adding ParentNuclideFilter twice when calling prepare_tallies (#3506) Co-authored-by: Paul Romano --- openmc/deplete/d1s.py | 4 ++-- tests/unit_tests/test_d1s.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/openmc/deplete/d1s.py b/openmc/deplete/d1s.py index e9f8bfe79d6..311bc2d69cd 100644 --- a/openmc/deplete/d1s.py +++ b/openmc/deplete/d1s.py @@ -243,7 +243,7 @@ def prepare_tallies( for f in tally.filters: if isinstance(f, openmc.ParticleFilter): if list(f.bins) == ['photon']: - tally.filters.append(filter) + if not tally.contains_filter(openmc.ParentNuclideFilter): + tally.filters.append(filter) break - return nuclides diff --git a/tests/unit_tests/test_d1s.py b/tests/unit_tests/test_d1s.py index 115759489aa..9410f2da2e0 100644 --- a/tests/unit_tests/test_d1s.py +++ b/tests/unit_tests/test_d1s.py @@ -84,6 +84,11 @@ def test_prepare_tallies(model): assert tally.contains_filter(openmc.ParentNuclideFilter) assert sorted(tally.filters[-1].bins) == sorted(radionuclides) + assert len(tally.filters) == 2 + # calling prepare_tallies twice should not add another ParentNuclideFilter + d1s.prepare_tallies(model, chain_file=CHAIN_PATH) + assert len(tally.filters) == 2 + def test_apply_time_correction(run_in_tmpdir): # Make simple sphere model with elemental Ni From 9d9dcc26c5a6f7b8e06335cfe7a88d296db48710 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 21 Jul 2025 23:10:08 -0500 Subject: [PATCH 079/159] Update DAGMC and libMesh precompiler definitions (#3510) --- CMakeLists.txt | 6 ++-- include/openmc/dagmc.h | 4 +-- include/openmc/mesh.h | 10 +++--- include/openmc/particle_data.h | 6 ++-- include/openmc/universe.h | 2 +- src/cell.cpp | 2 +- src/dagmc.cpp | 32 +++++++++---------- src/finalize.cpp | 2 +- src/initialize.cpp | 4 +-- src/mesh.cpp | 22 ++++++------- src/output.cpp | 6 ++-- src/particle.cpp | 10 +++--- src/state_point.cpp | 2 +- src/tallies/tally.cpp | 2 +- tests/regression_tests/dagmc/external/test.py | 2 +- tests/regression_tests/external_moab/test.py | 2 +- 16 files changed, 57 insertions(+), 57 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b863eb0871..474451c8aeb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -503,11 +503,11 @@ else() endif() if(OPENMC_USE_DAGMC) - target_compile_definitions(libopenmc PRIVATE DAGMC) + target_compile_definitions(libopenmc PRIVATE OPENMC_DAGMC_ENABLED) target_link_libraries(libopenmc dagmc-shared) if(OPENMC_USE_UWUW) - target_compile_definitions(libopenmc PRIVATE OPENMC_UWUW) + target_compile_definitions(libopenmc PRIVATE OPENMC_UWUW_ENABLED) target_link_libraries(libopenmc uwuw-shared) endif() elseif(OPENMC_USE_UWUW) @@ -516,7 +516,7 @@ elseif(OPENMC_USE_UWUW) endif() if(OPENMC_USE_LIBMESH) - target_compile_definitions(libopenmc PRIVATE LIBMESH) + target_compile_definitions(libopenmc PRIVATE OPENMC_LIBMESH_ENABLED) target_link_libraries(libopenmc PkgConfig::LIBMESH) endif() diff --git a/include/openmc/dagmc.h b/include/openmc/dagmc.h index 82ef1b644f3..0e27402a159 100644 --- a/include/openmc/dagmc.h +++ b/include/openmc/dagmc.h @@ -20,7 +20,7 @@ void check_dagmc_root_univ(); } // namespace openmc -#ifdef DAGMC +#ifdef OPENMC_DAGMC_ENABLED #include "DagMC.hpp" #include "dagmcmetadata.hpp" @@ -216,6 +216,6 @@ int32_t next_cell(int32_t surf, int32_t curr_cell, int32_t univ); } // namespace openmc -#endif // DAGMC +#endif // OPENMC_DAGMC_ENABLED #endif // OPENMC_DAGMC_H diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index 5a727c2b6f1..c15e2569777 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -19,14 +19,14 @@ #include "openmc/vector.h" #include "openmc/xml_interface.h" -#ifdef DAGMC +#ifdef OPENMC_DAGMC_ENABLED #include "moab/AdaptiveKDTree.hpp" #include "moab/Core.hpp" #include "moab/GeomUtil.hpp" #include "moab/Matrix3.hpp" #endif -#ifdef LIBMESH +#ifdef OPENMC_LIBMESH_ENABLED #include "libmesh/bounding_box.h" #include "libmesh/dof_map.h" #include "libmesh/elem.h" @@ -61,7 +61,7 @@ extern vector> meshes; } // namespace model -#ifdef LIBMESH +#ifdef OPENMC_LIBMESH_ENABLED namespace settings { // used when creating new libMesh::MeshBase instances extern unique_ptr libmesh_init; @@ -768,7 +768,7 @@ class UnstructuredMesh : public Mesh { virtual void initialize() = 0; }; -#ifdef DAGMC +#ifdef OPENMC_DAGMC_ENABLED class MOABMesh : public UnstructuredMesh { public: @@ -938,7 +938,7 @@ class MOABMesh : public UnstructuredMesh { #endif -#ifdef LIBMESH +#ifdef OPENMC_LIBMESH_ENABLED class LibMesh : public UnstructuredMesh { public: diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index 1c8f1215a4f..ec393845f81 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -8,7 +8,7 @@ #include "openmc/tallies/filter_match.h" #include "openmc/vector.h" -#ifdef DAGMC +#ifdef OPENMC_DAGMC_ENABLED #include "DagMC.hpp" #endif @@ -372,7 +372,7 @@ class GeometryState { // Boundary information BoundaryInfo& boundary() { return boundary_; } -#ifdef DAGMC +#ifdef OPENMC_DAGMC_ENABLED // DagMC state variables moab::DagMC::RayHistory& history() { return history_; } Direction& last_dir() { return last_dir_; } @@ -417,7 +417,7 @@ class GeometryState { double sqrtkT_ {-1.0}; //!< sqrt(k_Boltzmann * temperature) in eV double sqrtkT_last_ {0.0}; //!< last temperature -#ifdef DAGMC +#ifdef OPENMC_DAGMC_ENABLED moab::DagMC::RayHistory history_; Direction last_dir_; #endif diff --git a/include/openmc/universe.h b/include/openmc/universe.h index e8fbacfdc7b..52df432b838 100644 --- a/include/openmc/universe.h +++ b/include/openmc/universe.h @@ -6,7 +6,7 @@ namespace openmc { -#ifdef DAGMC +#ifdef OPENMC_DAGMC_ENABLED class DAGUniverse; #endif diff --git a/src/cell.cpp b/src/cell.cpp index eb5c228bc11..b6862ba177f 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -1025,7 +1025,7 @@ void populate_universes() model::universes.back()->cells_.push_back(index_cell); model::universe_map[uid] = model::universes.size() - 1; } else { -#ifdef DAGMC +#ifdef OPENMC_DAGMC_ENABLED // Skip implicit complement cells for now Universe* univ = model::universes[it->second].get(); DAGUniverse* dag_univ = dynamic_cast(univ); diff --git a/src/dagmc.cpp b/src/dagmc.cpp index 1d92d9d5703..8c81952a7b7 100644 --- a/src/dagmc.cpp +++ b/src/dagmc.cpp @@ -13,7 +13,7 @@ #include "openmc/settings.h" #include "openmc/string_utils.h" -#ifdef OPENMC_UWUW +#ifdef OPENMC_UWUW_ENABLED #include "uwuw.hpp" #endif #include @@ -26,13 +26,13 @@ namespace openmc { -#ifdef DAGMC +#ifdef OPENMC_DAGMC_ENABLED const bool DAGMC_ENABLED = true; #else const bool DAGMC_ENABLED = false; #endif -#ifdef OPENMC_UWUW +#ifdef OPENMC_UWUW_ENABLED const bool UWUW_ENABLED = true; #else const bool UWUW_ENABLED = false; @@ -40,7 +40,7 @@ const bool UWUW_ENABLED = false; } // namespace openmc -#ifdef DAGMC +#ifdef OPENMC_DAGMC_ENABLED namespace openmc { @@ -131,7 +131,7 @@ void DAGUniverse::set_id() void DAGUniverse::initialize() { -#ifdef OPENMC_UWUW +#ifdef OPENMC_UWUW_ENABLED // read uwuw materials from the .h5m file if present read_uwuw_materials(); #endif @@ -456,16 +456,16 @@ void DAGUniverse::to_hdf5(hid_t universes_group) const bool DAGUniverse::uses_uwuw() const { -#ifdef OPENMC_UWUW +#ifdef OPENMC_UWUW_ENABLED return uwuw_ && !uwuw_->material_library.empty(); #else return false; -#endif // OPENMC_UWUW +#endif // OPENMC_UWUW_ENABLED } std::string DAGUniverse::get_uwuw_materials_xml() const { -#ifdef OPENMC_UWUW +#ifdef OPENMC_UWUW_ENABLED if (!uses_uwuw()) { throw std::runtime_error("This DAGMC Universe does not use UWUW materials"); } @@ -485,12 +485,12 @@ std::string DAGUniverse::get_uwuw_materials_xml() const return ss.str(); #else fatal_error("DAGMC was not configured with UWUW."); -#endif // OPENMC_UWUW +#endif // OPENMC_UWUW_ENABLED } void DAGUniverse::write_uwuw_materials_xml(const std::string& outfile) const { -#ifdef OPENMC_UWUW +#ifdef OPENMC_UWUW_ENABLED if (!uses_uwuw()) { throw std::runtime_error( "This DAGMC universe does not use UWUW materials."); @@ -503,7 +503,7 @@ void DAGUniverse::write_uwuw_materials_xml(const std::string& outfile) const mats_xml.close(); #else fatal_error("DAGMC was not configured with UWUW."); -#endif // OPENMC_UWUW +#endif // OPENMC_UWUW_ENABLED } void DAGUniverse::legacy_assign_material( @@ -565,7 +565,7 @@ void DAGUniverse::legacy_assign_material( void DAGUniverse::read_uwuw_materials() { -#ifdef OPENMC_UWUW +#ifdef OPENMC_UWUW_ENABLED // If no filename was provided, don't read UWUW materials if (filename_ == "") return; @@ -605,13 +605,13 @@ void DAGUniverse::read_uwuw_materials() } #else fatal_error("DAGMC was not configured with UWUW."); -#endif // OPENMC_UWUW +#endif // OPENMC_UWUW_ENABLED } void DAGUniverse::uwuw_assign_material( moab::EntityHandle vol_handle, std::unique_ptr& c) const { -#ifdef OPENMC_UWUW +#ifdef OPENMC_UWUW_ENABLED // lookup material in uwuw if present std::string uwuw_mat = dmd_ptr->volume_material_property_data_eh[vol_handle]; if (uwuw_->material_library.count(uwuw_mat) != 0) { @@ -627,7 +627,7 @@ void DAGUniverse::uwuw_assign_material( } #else fatal_error("DAGMC was not configured with UWUW."); -#endif // OPENMC_UWUW +#endif // OPENMC_UWUW_ENABLED } void DAGUniverse::override_assign_material(std::unique_ptr& c) const @@ -926,4 +926,4 @@ int32_t next_cell(int32_t surf, int32_t curr_cell, int32_t univ); } // namespace openmc -#endif // DAGMC +#endif // OPENMC_DAGMC_ENABLED diff --git a/src/finalize.cpp b/src/finalize.cpp index 9cbebc878fd..2d14cdec803 100644 --- a/src/finalize.cpp +++ b/src/finalize.cpp @@ -166,7 +166,7 @@ int openmc_finalize() // Deallocate arrays free_memory(); -#ifdef LIBMESH +#ifdef OPENMC_LIBMESH_ENABLED settings::libmesh_init.reset(); #endif diff --git a/src/initialize.cpp b/src/initialize.cpp index c5803976bdf..2da78137d10 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -38,7 +38,7 @@ #include "openmc/vector.h" #include "openmc/weight_windows.h" -#ifdef LIBMESH +#ifdef OPENMC_LIBMESH_ENABLED #include "libmesh/libmesh.h" #endif @@ -64,7 +64,7 @@ int openmc_init(int argc, char* argv[], const void* intracomm) if (err) return err; -#ifdef LIBMESH +#ifdef OPENMC_LIBMESH_ENABLED const int n_threads = num_threads(); // initialize libMesh if it hasn't been initialized already // (if initialized externally, the libmesh_init object needs to be provided diff --git a/src/mesh.cpp b/src/mesh.cpp index 9147efb950c..3e4ff1a3eca 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -47,13 +47,13 @@ #include "openmc/volume_calc.h" #include "openmc/xml_interface.h" -#ifdef LIBMESH +#ifdef OPENMC_LIBMESH_ENABLED #include "libmesh/mesh_modification.h" #include "libmesh/mesh_tools.h" #include "libmesh/numeric_vector.h" #endif -#ifdef DAGMC +#ifdef OPENMC_DAGMC_ENABLED #include "moab/FileOptions.hpp" #endif @@ -63,7 +63,7 @@ namespace openmc { // Global variables //============================================================================== -#ifdef LIBMESH +#ifdef OPENMC_LIBMESH_ENABLED const bool LIBMESH_ENABLED = true; #else const bool LIBMESH_ENABLED = false; @@ -80,7 +80,7 @@ vector> meshes; } // namespace model -#ifdef LIBMESH +#ifdef OPENMC_LIBMESH_ENABLED namespace settings { unique_ptr libmesh_init; const libMesh::Parallel::Communicator* libmesh_comm {nullptr}; @@ -2134,14 +2134,14 @@ extern "C" int openmc_add_unstructured_mesh( std::string mesh_file(filename); bool valid_lib = false; -#ifdef DAGMC +#ifdef OPENMC_DAGMC_ENABLED if (lib_name == MOABMesh::mesh_lib_type) { model::meshes.push_back(std::move(make_unique(mesh_file))); valid_lib = true; } #endif -#ifdef LIBMESH +#ifdef OPENMC_LIBMESH_ENABLED if (lib_name == LibMesh::mesh_lib_type) { model::meshes.push_back(std::move(make_unique(mesh_file))); valid_lib = true; @@ -2509,7 +2509,7 @@ extern "C" int openmc_spherical_mesh_set_grid(int32_t index, index, grid_x, nx, grid_y, ny, grid_z, nz); } -#ifdef DAGMC +#ifdef OPENMC_DAGMC_ENABLED const std::string MOABMesh::mesh_lib_type = "moab"; @@ -3211,7 +3211,7 @@ void MOABMesh::write(const std::string& base_filename) const #endif -#ifdef LIBMESH +#ifdef OPENMC_LIBMESH_ENABLED const std::string LibMesh::mesh_lib_type = "libmesh"; @@ -3556,7 +3556,7 @@ double LibMesh::volume(int bin) const return this->get_element_from_bin(bin).volume(); } -#endif // LIBMESH +#endif // OPENMC_LIBMESH_ENABLED //============================================================================== // Non-member functions @@ -3605,12 +3605,12 @@ void read_meshes(pugi::xml_node root) model::meshes.push_back(make_unique(node)); } else if (mesh_type == SphericalMesh::mesh_type) { model::meshes.push_back(make_unique(node)); -#ifdef DAGMC +#ifdef OPENMC_DAGMC_ENABLED } else if (mesh_type == UnstructuredMesh::mesh_type && mesh_lib == MOABMesh::mesh_lib_type) { model::meshes.push_back(make_unique(node)); #endif -#ifdef LIBMESH +#ifdef OPENMC_LIBMESH_ENABLED } else if (mesh_type == UnstructuredMesh::mesh_type && mesh_lib == LibMesh::mesh_lib_type) { model::meshes.push_back(make_unique(node)); diff --git a/src/output.cpp b/src/output.cpp index 0316e313ed0..0a14e8843d8 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -323,10 +323,10 @@ void print_build_info() #ifdef OPENMC_MPI mpi = y; #endif -#ifdef DAGMC +#ifdef OPENMC_DAGMC_ENABLED dagmc = y; #endif -#ifdef LIBMESH +#ifdef OPENMC_LIBMESH_ENABLED libmesh = y; #endif #ifdef OPENMC_MCPL @@ -341,7 +341,7 @@ void print_build_info() #ifdef COVERAGEBUILD coverage = y; #endif -#ifdef OPENMC_UWUW +#ifdef OPENMC_UWUW_ENABLED uwuw = y; #endif diff --git a/src/particle.cpp b/src/particle.cpp index f9044b2d7ca..118b084b04e 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -32,7 +32,7 @@ #include "openmc/track_output.h" #include "openmc/weight_windows.h" -#ifdef DAGMC +#ifdef OPENMC_DAGMC_ENABLED #include "DagMC.hpp" #endif @@ -408,7 +408,7 @@ void Particle::event_collide() if (!model::active_tallies.empty()) score_collision_derivative(*this); -#ifdef DAGMC +#ifdef OPENMC_DAGMC_ENABLED history().reset(); #endif } @@ -473,7 +473,7 @@ void Particle::event_revive_from_secondary() void Particle::event_death() { -#ifdef DAGMC +#ifdef OPENMC_DAGMC_ENABLED history().reset(); #endif @@ -553,7 +553,7 @@ void Particle::cross_surface(const Surface& surf) } // if we're crossing a CSG surface, make sure the DAG history is reset -#ifdef DAGMC +#ifdef OPENMC_DAGMC_ENABLED if (surf.geom_type() == GeometryType::CSG) history().reset(); #endif @@ -567,7 +567,7 @@ void Particle::cross_surface(const Surface& surf) // ========================================================================== // SEARCH NEIGHBOR LISTS FOR NEXT CELL -#ifdef DAGMC +#ifdef OPENMC_DAGMC_ENABLED // in DAGMC, we know what the next cell should be if (surf.geom_type() == GeometryType::DAG) { int32_t i_cell = next_cell(surface_index(), cell_last(n_coord() - 1), diff --git a/src/state_point.cpp b/src/state_point.cpp index 12dfeb82ac6..8195c486507 100644 --- a/src/state_point.cpp +++ b/src/state_point.cpp @@ -340,7 +340,7 @@ extern "C" int openmc_statepoint_write(const char* filename, bool* write_source) file_close(file_id); } -#if defined(LIBMESH) || defined(DAGMC) +#if defined(OPENMC_LIBMESH_ENABLED) || defined(OPENMC_DAGMC_ENABLED) // write unstructured mesh tally files write_unstructured_mesh_results(); #endif diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 54840b5188e..e737f79d354 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -376,7 +376,7 @@ Tally::Tally(pugi::xml_node node) } } -#ifdef LIBMESH +#ifdef OPENMC_LIBMESH_ENABLED // ensure a tracklength tally isn't used with a libMesh filter for (auto i : this->filters_) { auto df = dynamic_cast(model::tally_filters[i].get()); diff --git a/tests/regression_tests/dagmc/external/test.py b/tests/regression_tests/dagmc/external/test.py index 57bc9ea7fd6..3580bfa11e6 100644 --- a/tests/regression_tests/dagmc/external/test.py +++ b/tests/regression_tests/dagmc/external/test.py @@ -32,7 +32,7 @@ def cpp_driver(request): target_link_libraries(main OpenMC::libopenmc) target_compile_features(main PUBLIC cxx_std_14) set(CMAKE_CXX_FLAGS "-pedantic-errors") - add_compile_definitions(DAGMC=1) + add_compile_definitions(OPENMC_DAGMC_ENABLED=1) """.format(openmc_dir))) # Create temporary build directory and change to there diff --git a/tests/regression_tests/external_moab/test.py b/tests/regression_tests/external_moab/test.py index ce4e78a2c25..2d64eb14bb9 100644 --- a/tests/regression_tests/external_moab/test.py +++ b/tests/regression_tests/external_moab/test.py @@ -39,7 +39,7 @@ def cpp_driver(request): target_link_libraries(main OpenMC::libopenmc) target_compile_features(main PUBLIC cxx_std_14) set(CMAKE_CXX_FLAGS "-pedantic-errors") - add_compile_definitions(DAGMC=1) + add_compile_definitions(OPENMC_DAGMC_ENABLED=1) """.format(openmc_dir))) # Create temporary build directory and change to there From 6b672f772f34c804cb729eb84aedb0a1b93576ea Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sun, 27 Jul 2025 04:42:33 +0700 Subject: [PATCH 080/159] Allow already-initialized openmc.lib in TemporarySession (#3505) --- openmc/deplete/microxs.py | 10 ++-------- openmc/lib/core.py | 12 ++++++++---- openmc/model/model.py | 32 ++++++++++++-------------------- 3 files changed, 22 insertions(+), 32 deletions(-) diff --git a/openmc/deplete/microxs.py b/openmc/deplete/microxs.py index df78f8a267c..4ce199f0cc1 100644 --- a/openmc/deplete/microxs.py +++ b/openmc/deplete/microxs.py @@ -352,7 +352,8 @@ def from_multigroup_flux( # Create 3D array for microscopic cross sections microxs_arr = np.zeros((len(nuclides), len(mts), 1)) - def compute_microxs(): + # Compute microscopic cross sections within a temporary session + with openmc.lib.TemporarySession(**init_kwargs): # For each nuclide and reaction, compute the flux-averaged xs for nuc_index, nuc in enumerate(nuclides): if nuc not in nuclides_with_data: @@ -363,13 +364,6 @@ def compute_microxs(): mt, temperature, energies, multigroup_flux ) - # Compute microscopic cross sections within a temporary session - if not openmc.lib.is_initialized: - with openmc.lib.TemporarySession(**init_kwargs): - compute_microxs() - else: - compute_microxs() - return cls(microxs_arr, nuclides, reactions) @classmethod diff --git a/openmc/lib/core.py b/openmc/lib/core.py index eac0e1784c6..9f8db69d577 100644 --- a/openmc/lib/core.py +++ b/openmc/lib/core.py @@ -654,9 +654,10 @@ def __init__(self, model=None, **init_kwargs): def __enter__(self): """Initialize the OpenMC library in a temporary directory.""" - # Make sure OpenMC is not already initialized - if openmc.lib.is_initialized: - raise RuntimeError("openmc.lib is already initialized.") + # If already initialized, the context manager is a no-op + self.already_initialized = openmc.lib.is_initialized + if self.already_initialized: + return self # Store original working directory self.orig_dir = Path.cwd() @@ -675,8 +676,11 @@ def __enter__(self): def __exit__(self, exc_type, exc_value, traceback): """Finalize the OpenMC library and clean up temporary directory.""" + if self.already_initialized: + return + try: - openmc.lib.finalize() + finalize() finally: os.chdir(self.orig_dir) self.tmp_dir.cleanup() diff --git a/openmc/model/model.py b/openmc/model/model.py index 93e029a335e..b1eff5b7c2e 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -996,16 +996,13 @@ def id_map( plot_obj.v_res = pixels[1] plot_obj.basis = basis - if self.is_initialized: - return openmc.lib.id_map(plot_obj) - else: - # Silence output by default. Also set arguments to start in volume - # calculation mode to avoid loading cross sections - init_kwargs.setdefault('output', False) - init_kwargs.setdefault('args', ['-c']) + # Silence output by default. Also set arguments to start in volume + # calculation mode to avoid loading cross sections + init_kwargs.setdefault('output', False) + init_kwargs.setdefault('args', ['-c']) - with openmc.lib.TemporarySession(self, **init_kwargs): - return openmc.lib.id_map(plot_obj) + with openmc.lib.TemporarySession(self, **init_kwargs): + return openmc.lib.id_map(plot_obj) @add_plot_params def plot( @@ -1229,20 +1226,15 @@ def sample_external_source( """ import openmc.lib - if self.is_initialized: + # Silence output by default. Also set arguments to start in volume + # calculation mode to avoid loading cross sections + init_kwargs.setdefault('output', False) + init_kwargs.setdefault('args', ['-c']) + + with openmc.lib.TemporarySession(self, **init_kwargs): return openmc.lib.sample_external_source( n_samples=n_samples, prn_seed=prn_seed ) - else: - # Silence output by default. Also set arguments to start in volume - # calculation mode to avoid loading cross sections - init_kwargs.setdefault('output', False) - init_kwargs.setdefault('args', ['-c']) - - with openmc.lib.TemporarySession(self, **init_kwargs): - return openmc.lib.sample_external_source( - n_samples=n_samples, prn_seed=prn_seed - ) def apply_tally_results(self, statepoint: PathLike | openmc.StatePoint): """Apply results from a statepoint to tally objects on the Model From 836bc487cf13e69a9d2d9ef3f79d3828609df4e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Delaporte-Mathurin?= <40028739+RemDelaporteMathurin@users.noreply.github.com> Date: Tue, 29 Jul 2025 05:34:03 -0400 Subject: [PATCH 081/159] Fix: `materials`, `plots`, and `tallies` cannot be passed as lists (#3513) Co-authored-by: Paul Romano --- openmc/model/model.py | 18 ++++++++++++------ tests/unit_tests/test_model.py | 13 +++++++++++++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/openmc/model/model.py b/openmc/model/model.py index b1eff5b7c2e..03fdcda1fc8 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -70,7 +70,7 @@ class Model: def __init__( self, geometry: openmc.Geometry | None = None, - materials: openmc.Materials = None, + materials: openmc.Materials | None = None, settings: openmc.Settings | None = None, tallies: openmc.Tallies | None = None, plots: openmc.Plots | None = None, @@ -82,7 +82,7 @@ def __init__( self.plots = openmc.Plots() if plots is None else plots @property - def geometry(self) -> openmc.Geometry | None: + def geometry(self) -> openmc.Geometry: return self._geometry @geometry.setter @@ -91,7 +91,7 @@ def geometry(self, geometry): self._geometry = geometry @property - def materials(self) -> openmc.Materials | None: + def materials(self) -> openmc.Materials: return self._materials @materials.setter @@ -100,12 +100,14 @@ def materials(self, materials): if isinstance(materials, openmc.Materials): self._materials = materials else: + if not hasattr(self, '_materials'): + self._materials = openmc.Materials() del self._materials[:] for mat in materials: self._materials.append(mat) @property - def settings(self) -> openmc.Settings | None: + def settings(self) -> openmc.Settings: return self._settings @settings.setter @@ -114,7 +116,7 @@ def settings(self, settings): self._settings = settings @property - def tallies(self) -> openmc.Tallies | None: + def tallies(self) -> openmc.Tallies: return self._tallies @tallies.setter @@ -123,12 +125,14 @@ def tallies(self, tallies): if isinstance(tallies, openmc.Tallies): self._tallies = tallies else: + if not hasattr(self, '_tallies'): + self._tallies = openmc.Tallies() del self._tallies[:] for tally in tallies: self._tallies.append(tally) @property - def plots(self) -> openmc.Plots | None: + def plots(self) -> openmc.Plots: return self._plots @plots.setter @@ -137,6 +141,8 @@ def plots(self, plots): if isinstance(plots, openmc.Plots): self._plots = plots else: + if not hasattr(self, '_plots'): + self._plots = openmc.Plots() del self._plots[:] for plot in plots: self._plots.append(plot) diff --git a/tests/unit_tests/test_model.py b/tests/unit_tests/test_model.py index 3797188ba6d..6e4dec00fff 100644 --- a/tests/unit_tests/test_model.py +++ b/tests/unit_tests/test_model.py @@ -892,3 +892,16 @@ def test_id_map_aligned_model(): assert tr_cell == 20, f"Expected cell ID 20 at top-right corner, got {tr_cell}" assert tr_instance == 3, f"Expected cell instance 3 at top-right corner, got {tr_instance}" assert tr_material == 5, f"Expected material ID 5 at top-right corner, got {tr_material}" + +def test_setter_from_list(): + mat = openmc.Material() + model = openmc.Model(materials=[mat]) + assert isinstance(model.materials, openmc.Materials) + + tally = openmc.Tally() + model = openmc.Model(tallies=[tally]) + assert isinstance(model.tallies, openmc.Tallies) + + plot = openmc.Plot() + model = openmc.Model(plots=[plot]) + assert isinstance(model.plots, openmc.Plots) From 4cce6ee6c05c3070bb5f256324f064570b2869bc Mon Sep 17 00:00:00 2001 From: John Tramm Date: Tue, 29 Jul 2025 05:15:40 -0500 Subject: [PATCH 082/159] Fix for Weight Window Scaling Bug (#3511) Co-authored-by: Paul Romano --- include/openmc/weight_windows.h | 1 + tests/regression_tests/weightwindows/generators/test.py | 4 ++-- tests/regression_tests/weightwindows/results_true.dat | 2 +- tests/regression_tests/weightwindows/test.py | 6 +++--- tests/testing_harness.py | 2 +- tests/unit_tests/weightwindows/test_ww_gen.py | 2 +- tests/unit_tests/weightwindows/test_wwinp_reader.py | 2 +- 7 files changed, 10 insertions(+), 9 deletions(-) diff --git a/include/openmc/weight_windows.h b/include/openmc/weight_windows.h index 6f2ef07079f..7638155228e 100644 --- a/include/openmc/weight_windows.h +++ b/include/openmc/weight_windows.h @@ -71,6 +71,7 @@ struct WeightWindow { { lower_weight *= factor; upper_weight *= factor; + survival_weight *= factor; } }; diff --git a/tests/regression_tests/weightwindows/generators/test.py b/tests/regression_tests/weightwindows/generators/test.py index d6a40b44f01..4dc0ab80d35 100644 --- a/tests/regression_tests/weightwindows/generators/test.py +++ b/tests/regression_tests/weightwindows/generators/test.py @@ -46,14 +46,14 @@ def test_ww_generator(run_in_tmpdir): # just test that the generation happens successfully here assert os.path.exists('weight_windows.h5') - wws_mean = openmc.hdf5_to_wws() + wws_mean = openmc.WeightWindowsList.from_hdf5() assert len(wws_mean) == 1 # check that generation using the relative error works too wwg.update_parameters['value'] = 'rel_err' model.run() - wws_rel_err = openmc.hdf5_to_wws() + wws_rel_err = openmc.WeightWindowsList.from_hdf5() assert len(wws_rel_err) == 1 # we should not get the same set of weight windows when switching to use of diff --git a/tests/regression_tests/weightwindows/results_true.dat b/tests/regression_tests/weightwindows/results_true.dat index 671be1d7b06..122c5d89098 100644 --- a/tests/regression_tests/weightwindows/results_true.dat +++ b/tests/regression_tests/weightwindows/results_true.dat @@ -1 +1 @@ -a5880ad9262e8aba90801783891ee74618144101401f06fb46e954e851a3c517ab28d5f0d6e1b2b364844f08d363cba35ab23b63fc81012ea8a6a328755d56c4 \ No newline at end of file +5df0c08573ccee3fd3495c877c7dc0c4b69c245faf8473b848c89fb837dea7fd437936ffebeb6455c793e66046c46eb0ad6941cc5ef1699fc5fc8a7fcb9b5a0c \ No newline at end of file diff --git a/tests/regression_tests/weightwindows/test.py b/tests/regression_tests/weightwindows/test.py index 864f4f062dc..cfb651338c7 100644 --- a/tests/regression_tests/weightwindows/test.py +++ b/tests/regression_tests/weightwindows/test.py @@ -1,4 +1,3 @@ -from copy import deepcopy import pytest import numpy as np @@ -8,6 +7,7 @@ from tests.testing_harness import HashedPyAPITestHarness + @pytest.fixture def model(): model = openmc.Model() @@ -114,7 +114,7 @@ def test_weightwindows(model): def test_wwinp_cylindrical(): - ww = openmc.wwinp_to_wws('ww_n_cyl.txt')[0] + ww = openmc.WeightWindowsList.from_wwinp('ww_n_cyl.txt')[0] mesh = ww.mesh @@ -144,7 +144,7 @@ def test_wwinp_cylindrical(): def test_wwinp_spherical(): - ww = openmc.wwinp_to_wws('ww_n_sph.txt')[0] + ww = openmc.WeightWindowsList.from_wwinp('ww_n_sph.txt')[0] mesh = ww.mesh diff --git a/tests/testing_harness.py b/tests/testing_harness.py index 6de475e6e04..11ced5b38a7 100644 --- a/tests/testing_harness.py +++ b/tests/testing_harness.py @@ -443,7 +443,7 @@ def _compare_results(self): class WeightWindowPyAPITestHarness(PyAPITestHarness): def _get_results(self): """Digest info in the weight window file and return as a string.""" - ww = openmc.hdf5_to_wws()[0] + ww = openmc.WeightWindowsList.from_hdf5()[0] # Access the weight window bounds lower_bound = ww.lower_ww_bounds diff --git a/tests/unit_tests/weightwindows/test_ww_gen.py b/tests/unit_tests/weightwindows/test_ww_gen.py index 555421461b7..7ee03f55c28 100644 --- a/tests/unit_tests/weightwindows/test_ww_gen.py +++ b/tests/unit_tests/weightwindows/test_ww_gen.py @@ -303,7 +303,7 @@ def test_python_hdf5_roundtrip(run_in_tmpdir, model): openmc.lib.finalize() - wws_hdf5 = openmc.hdf5_to_wws()[0] + wws_hdf5 = openmc.WeightWindowsList.from_hdf5()[0] # ensure assert all(wws.energy_bounds == wws_hdf5.energy_bounds) diff --git a/tests/unit_tests/weightwindows/test_wwinp_reader.py b/tests/unit_tests/weightwindows/test_wwinp_reader.py index 434a36753ab..28548a4486f 100644 --- a/tests/unit_tests/weightwindows/test_wwinp_reader.py +++ b/tests/unit_tests/weightwindows/test_wwinp_reader.py @@ -96,7 +96,7 @@ def id_fn(params): def test_wwinp_reader(wwinp_data, request): wwinp_file, mesh, particle_types, energy_bounds = wwinp_data - wws = openmc.wwinp_to_wws(request.node.path.parent / wwinp_file) + wws = openmc.WeightWindowsList.from_wwinp(request.node.path.parent / wwinp_file) for i, ww in enumerate(wws): e_bounds = energy_bounds[i] From 6f5e1347d4bed2af88060c6644b20a6949c7a192 Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Tue, 29 Jul 2025 12:17:51 +0100 Subject: [PATCH 083/159] Add test for FW-CADIS based WW generation on a DAGMC model (#3504) Co-authored-by: Jon Shimwell Co-authored-by: Patrick Shriwise Co-authored-by: Paul Romano --- src/dagmc.cpp | 2 +- .../dagmc/dagmc_tetrahedral_no_graveyard.h5m | Bin 0 -> 31448 bytes tests/unit_tests/weightwindows/test_ww_gen.py | 70 ++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 tests/unit_tests/dagmc/dagmc_tetrahedral_no_graveyard.h5m diff --git a/src/dagmc.cpp b/src/dagmc.cpp index 8c81952a7b7..b2ebe89c0b4 100644 --- a/src/dagmc.cpp +++ b/src/dagmc.cpp @@ -672,7 +672,7 @@ std::pair DAGCell::distance( p->last_dir() = u; p->history().reset(); } - if (on_surface == 0) { + if (on_surface == SURFACE_NONE) { p->history().reset(); } diff --git a/tests/unit_tests/dagmc/dagmc_tetrahedral_no_graveyard.h5m b/tests/unit_tests/dagmc/dagmc_tetrahedral_no_graveyard.h5m new file mode 100644 index 0000000000000000000000000000000000000000..2aa72956b812354bbf64350252e8d04ca160ad4d GIT binary patch literal 31448 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 literal 0 HcmV?d00001 diff --git a/tests/unit_tests/weightwindows/test_ww_gen.py b/tests/unit_tests/weightwindows/test_ww_gen.py index 7ee03f55c28..a4456e6809a 100644 --- a/tests/unit_tests/weightwindows/test_ww_gen.py +++ b/tests/unit_tests/weightwindows/test_ww_gen.py @@ -1,3 +1,4 @@ +import copy from itertools import permutations from pathlib import Path @@ -332,3 +333,72 @@ def test_ww_bounds_set_in_memory(run_in_tmpdir, model): wws.bounds = (bounds, bounds) openmc.lib.finalize() + + +@pytest.mark.skipif(not openmc.lib._dagmc_enabled(), reason="DAGMC CAD geometry is not enabled.") +def test_ww_generation_with_dagmc(run_in_tmpdir): + mat1 = openmc.Material(name="1") + mat1.add_nuclide("H1", 1, percent_type="ao") + mat1.set_density("g/cm3", 0.001) + + materials = openmc.Materials([mat1]) + dag_univ = openmc.DAGMCUniverse( + Path(__file__).parent.parent / "dagmc" / "dagmc_tetrahedral_no_graveyard.h5m") + bound_dag_univ = dag_univ.bounded_universe(padding_distance=1) + geometry = openmc.Geometry(bound_dag_univ) + + settings = openmc.Settings() + settings.batches = 6 + settings.particles = 30 + settings.run_mode = "fixed source" + + # Create a point source which are supported by random ray mode + my_source = openmc.IndependentSource() + my_source.space = openmc.stats.Point((0.25, 0.25, 0.25)) + my_source.energy = openmc.stats.delta_function(14e6) + settings.source = my_source + + model = openmc.Model(geometry, materials, settings) + + rr_model = copy.deepcopy(model) + rr_model.settings.inactive = 3 + + rr_model.convert_to_multigroup( + method="stochastic_slab", + overwrite_mgxs_library=True, + nparticles=10, + groups="CASMO-2" + ) + + rr_model.convert_to_random_ray() + + mesh = openmc.RegularMesh.from_domain(rr_model, dimension=(4, 4, 4)) + + # avoid writing files we don't make use of + rr_model.settings.output = {"summary": False, "tallies": False} + + # Subdivide random ray source regions + rr_model.settings.random_ray["source_region_meshes"] = [ + (mesh, [rr_model.geometry.root_universe]) + ] + + # less likely to get negative values in the weight window + rr_model.settings.random_ray["volume_estimator"] = "naive" + + # Add a weight window generator to the model + rr_model.settings.weight_window_generators = openmc.WeightWindowGenerator( + method="fw_cadis", + mesh=mesh, + max_realizations=42, + particle_type='neutron', + energy_bounds=[0.0, 100e6] + ) + + rr_model.run() + + model.settings.weight_windows_on = True + model.settings.weight_window_checkpoints = {"collision": True, "surface": True} + model.settings.survival_biasing = False + model.settings.weight_windows = openmc.WeightWindowsList.from_hdf5() + + model.run() From a64cc96ed5acfd063598cc8fc57f4ff0c9285eed Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Wed, 30 Jul 2025 20:10:34 +0300 Subject: [PATCH 084/159] Fixed a bug in distribcell offsets logic (#3424) --- include/openmc/cell.h | 13 ++- include/openmc/geometry_aux.h | 12 +- include/openmc/lattice.h | 2 +- include/openmc/universe.h | 1 + src/cell.cpp | 13 ++- src/finalize.cpp | 1 - src/geometry_aux.cpp | 85 +++++--------- src/lattice.cpp | 5 +- src/random_ray/flat_source_domain.cpp | 10 +- src/tallies/filter_distribcell.cpp | 2 +- .../lattice_distribmat/False/inputs_true.dat | 74 ++++++++++++ .../lattice_distribmat/False/results_true.dat | 2 + .../lattice_distribmat/True/inputs_true.dat | 107 ++++++++++++++++++ .../lattice_distribmat/True/results_true.dat | 2 + .../lattice_distribmat/__init__.py | 0 .../lattice_distribmat/test.py | 83 ++++++++++++++ 16 files changed, 325 insertions(+), 87 deletions(-) create mode 100644 tests/regression_tests/lattice_distribmat/False/inputs_true.dat create mode 100644 tests/regression_tests/lattice_distribmat/False/results_true.dat create mode 100644 tests/regression_tests/lattice_distribmat/True/inputs_true.dat create mode 100644 tests/regression_tests/lattice_distribmat/True/results_true.dat create mode 100644 tests/regression_tests/lattice_distribmat/__init__.py create mode 100644 tests/regression_tests/lattice_distribmat/test.py diff --git a/include/openmc/cell.h b/include/openmc/cell.h index 98709fb33ea..e9fcfe3911b 100644 --- a/include/openmc/cell.h +++ b/include/openmc/cell.h @@ -226,6 +226,8 @@ class Cell { void set_temperature( double T, int32_t instance = -1, bool set_contained = false); + int32_t n_instances() const; + //! Set the rotation matrix of a cell instance //! \param[in] rot The rotation matrix of length 3 or 9 void set_rotation(const vector& rot); @@ -312,12 +314,11 @@ class Cell { //---------------------------------------------------------------------------- // Data members - int32_t id_; //!< Unique ID - std::string name_; //!< User-defined name - Fill type_; //!< Material, universe, or lattice - int32_t universe_; //!< Universe # this cell is in - int32_t fill_; //!< Universe # filling this cell - int32_t n_instances_ {0}; //!< Number of instances of this cell + int32_t id_; //!< Unique ID + std::string name_; //!< User-defined name + Fill type_; //!< Material, universe, or lattice + int32_t universe_; //!< Universe # this cell is in + int32_t fill_; //!< Universe # filling this cell //! \brief Index corresponding to this cell in distribcell arrays int distribcell_index_ {C_NONE}; diff --git a/include/openmc/geometry_aux.h b/include/openmc/geometry_aux.h index 0c870c9fb8e..f60f2d649ee 100644 --- a/include/openmc/geometry_aux.h +++ b/include/openmc/geometry_aux.h @@ -15,8 +15,6 @@ namespace openmc { namespace model { -extern std::unordered_map> - universe_cell_counts; extern std::unordered_map universe_level_counts; } // namespace model @@ -80,15 +78,13 @@ void prepare_distribcell( const std::vector* user_distribcells = nullptr); //============================================================================== -//! Recursively search through the geometry and count cell instances. +//! Recursively search through the geometry and count universe instances. //! -//! This function will update the Cell::n_instances value for each cell in the -//! geometry. -//! \param univ_indx The index of the universe to begin searching from (probably -//! the root universe). +//! This function will update Universe.n_instances_ for each +//! universe in the geometry. //============================================================================== -void count_cell_instances(int32_t univ_indx); +void count_universe_instances(); //============================================================================== //! Recursively search through universes and count universe instances. diff --git a/include/openmc/lattice.h b/include/openmc/lattice.h index fb374ab75c3..f87d28b21bd 100644 --- a/include/openmc/lattice.h +++ b/include/openmc/lattice.h @@ -76,7 +76,7 @@ class Lattice { } //! Populate the distribcell offset tables. - int32_t fill_offset_table(int32_t offset, int32_t target_univ_id, int map, + int32_t fill_offset_table(int32_t target_univ_id, int map, std::unordered_map& univ_count_memo); //! \brief Check lattice indices. diff --git a/include/openmc/universe.h b/include/openmc/universe.h index 52df432b838..b7450224f4f 100644 --- a/include/openmc/universe.h +++ b/include/openmc/universe.h @@ -29,6 +29,7 @@ class Universe { public: int32_t id_; //!< Unique ID vector cells_; //!< Cells within this universe + int32_t n_instances_; //!< Number of instances of this universe //! \brief Write universe information to an HDF5 group. //! \param group_id An HDF5 group id. diff --git a/src/cell.cpp b/src/cell.cpp index b6862ba177f..d838bfbb4d1 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -40,6 +40,11 @@ vector> cells; // Cell implementation //============================================================================== +int32_t Cell::n_instances() const +{ + return model::universes[universe_]->n_instances_; +} + void Cell::set_rotation(const vector& rot) { if (fill_ == C_NONE) { @@ -115,8 +120,8 @@ void Cell::set_temperature(double T, int32_t instance, bool set_contained) if (type_ == Fill::MATERIAL) { if (instance >= 0) { // If temperature vector is not big enough, resize it first - if (sqrtkT_.size() != n_instances_) - sqrtkT_.resize(n_instances_, sqrtkT_[0]); + if (sqrtkT_.size() != n_instances()) + sqrtkT_.resize(n_instances(), sqrtkT_[0]); // Set temperature for the corresponding instance sqrtkT_.at(instance) = std::sqrt(K_BOLTZMANN * T); @@ -170,7 +175,7 @@ void Cell::import_properties_hdf5(hid_t group) // Ensure number of temperatures makes sense auto n_temps = temps.size(); - if (n_temps > 1 && n_temps != n_instances_) { + if (n_temps > 1 && n_temps != n_instances()) { throw std::runtime_error(fmt::format( "Number of temperatures for cell {} doesn't match number of instances", id_)); @@ -1618,7 +1623,7 @@ extern "C" int openmc_cell_get_num_instances( set_errmsg("Index in cells array is out of bounds."); return OPENMC_E_OUT_OF_BOUNDS; } - *num_instances = model::cells[index]->n_instances_; + *num_instances = model::cells[index]->n_instances(); return 0; } diff --git a/src/finalize.cpp b/src/finalize.cpp index 2d14cdec803..e38b0b251cb 100644 --- a/src/finalize.cpp +++ b/src/finalize.cpp @@ -185,7 +185,6 @@ int openmc_finalize() int openmc_reset() { - model::universe_cell_counts.clear(); model::universe_level_counts.clear(); for (auto& t : model::tallies) { diff --git a/src/geometry_aux.cpp b/src/geometry_aux.cpp index 2f8a5574337..51732cf8b90 100644 --- a/src/geometry_aux.cpp +++ b/src/geometry_aux.cpp @@ -25,21 +25,9 @@ namespace openmc { namespace model { -std::unordered_map> - universe_cell_counts; std::unordered_map universe_level_counts; } // namespace model -// adds the cell counts of universe b to universe a -void update_universe_cell_count(int32_t a, int32_t b) -{ - auto& universe_a_counts = model::universe_cell_counts[a]; - const auto& universe_b_counts = model::universe_cell_counts[b]; - for (const auto& it : universe_b_counts) { - universe_a_counts[it.first] += it.second; - } -} - void read_geometry_xml() { // Display output message @@ -263,7 +251,7 @@ void finalize_geometry() { // Perform some final operations to set up the geometry adjust_indices(); - count_cell_instances(model::root_universe); + count_universe_instances(); partition_universes(); // Assign temperatures to cells that don't have temperatures already assigned @@ -356,35 +344,38 @@ void prepare_distribcell(const std::vector* user_distribcells) Cell& c {*model::cells[i]}; if (c.material_.size() > 1) { - if (c.material_.size() != c.n_instances_) { + if (c.material_.size() != c.n_instances()) { fatal_error(fmt::format( "Cell {} was specified with {} materials but has {} distributed " "instances. The number of materials must equal one or the number " "of instances.", - c.id_, c.material_.size(), c.n_instances_)); + c.id_, c.material_.size(), c.n_instances())); } } if (c.sqrtkT_.size() > 1) { - if (c.sqrtkT_.size() != c.n_instances_) { + if (c.sqrtkT_.size() != c.n_instances()) { fatal_error(fmt::format( "Cell {} was specified with {} temperatures but has {} distributed " "instances. The number of temperatures must equal one or the number " "of instances.", - c.id_, c.sqrtkT_.size(), c.n_instances_)); + c.id_, c.sqrtkT_.size(), c.n_instances())); } } } // Search through universes for material cells and assign each one a - // unique distribcell array index. - int distribcell_index = 0; + // distribcell array index according to the containing universe. vector target_univ_ids; for (const auto& u : model::universes) { for (auto idx : u->cells_) { if (distribcells.find(idx) != distribcells.end()) { - model::cells[idx]->distribcell_index_ = distribcell_index++; - target_univ_ids.push_back(u->id_); + if (!contains(target_univ_ids, u->id_)) { + target_univ_ids.push_back(u->id_); + } + model::cells[idx]->distribcell_index_ = + std::find(target_univ_ids.begin(), target_univ_ids.end(), u->id_) - + target_univ_ids.begin(); } } } @@ -419,8 +410,7 @@ void prepare_distribcell(const std::vector* user_distribcells) } else if (c.type_ == Fill::LATTICE) { c.offset_[map] = offset; Lattice& lat = *model::lattices[c.fill_]; - offset += - lat.fill_offset_table(offset, target_univ_id, map, univ_count_memo); + offset += lat.fill_offset_table(target_univ_id, map, univ_count_memo); } } } @@ -429,32 +419,12 @@ void prepare_distribcell(const std::vector* user_distribcells) //============================================================================== -void count_cell_instances(int32_t univ_indx) +void count_universe_instances() { - const auto univ_counts = model::universe_cell_counts.find(univ_indx); - if (univ_counts != model::universe_cell_counts.end()) { - for (const auto& it : univ_counts->second) { - model::cells[it.first]->n_instances_ += it.second; - } - } else { - for (int32_t cell_indx : model::universes[univ_indx]->cells_) { - Cell& c = *model::cells[cell_indx]; - ++c.n_instances_; - model::universe_cell_counts[univ_indx][cell_indx] += 1; - - if (c.type_ == Fill::UNIVERSE) { - // This cell contains another universe. Recurse into that universe. - count_cell_instances(c.fill_); - update_universe_cell_count(univ_indx, c.fill_); - } else if (c.type_ == Fill::LATTICE) { - // This cell contains a lattice. Recurse into the lattice universes. - Lattice& lat = *model::lattices[c.fill_]; - for (auto it = lat.begin(); it != lat.end(); ++it) { - count_cell_instances(*it); - update_universe_cell_count(univ_indx, *it); - } - } - } + for (auto& univ : model::universes) { + std::unordered_map univ_count_memo; + univ->n_instances_ = count_universe_instances( + model::root_universe, univ->id_, univ_count_memo); } } @@ -528,19 +498,16 @@ std::string distribcell_path_inner(int32_t target_cell, int32_t map, // Material cells don't contain other cells so ignore them. if (c.type_ != Fill::MATERIAL) { - int32_t temp_offset; - if (c.type_ == Fill::UNIVERSE) { - temp_offset = - offset + c.offset_[map]; // TODO: should also apply to lattice fills? - } else { + int32_t temp_offset = offset + c.offset_[map]; + if (c.type_ == Fill::LATTICE) { Lattice& lat = *model::lattices[c.fill_]; int32_t indx = lat.universes_.size() * map + lat.begin().indx_; - temp_offset = offset + lat.offsets_[indx]; + temp_offset += lat.offsets_[indx]; } // The desired cell is the first cell that gives an offset smaller or // equal to the target offset. - if (temp_offset <= target_offset - c.offset_[map]) + if (temp_offset <= target_offset) break; } } @@ -570,12 +537,12 @@ std::string distribcell_path_inner(int32_t target_cell, int32_t map, path << "l" << lat.id_; for (ReverseLatticeIter it = lat.rbegin(); it != lat.rend(); ++it) { int32_t indx = lat.universes_.size() * map + it.indx_; - int32_t temp_offset = offset + lat.offsets_[indx]; - if (temp_offset <= target_offset - c.offset_[map]) { + int32_t temp_offset = offset + lat.offsets_[indx] + c.offset_[map]; + if (temp_offset <= target_offset) { offset = temp_offset; path << "(" << lat.index_to_string(it.indx_) << ")->"; - path << distribcell_path_inner(target_cell, map, target_offset, - *model::universes[*it], offset + c.offset_[map]); + path << distribcell_path_inner( + target_cell, map, target_offset, *model::universes[*it], offset); return path.str(); } } diff --git a/src/lattice.cpp b/src/lattice.cpp index 73005ad096c..efbfcb2163a 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -103,8 +103,8 @@ void Lattice::adjust_indices() //============================================================================== -int32_t Lattice::fill_offset_table(int32_t offset, int32_t target_univ_id, - int map, std::unordered_map& univ_count_memo) +int32_t Lattice::fill_offset_table(int32_t target_univ_id, int map, + std::unordered_map& univ_count_memo) { // If the offsets have already been determined for this "map", don't bother // recalculating all of them and just return the total offset. Note that the @@ -119,6 +119,7 @@ int32_t Lattice::fill_offset_table(int32_t offset, int32_t target_univ_id, count_universe_instances(last_univ, target_univ_id, univ_count_memo); } + int32_t offset = 0; for (LatticeIter it = begin(); it != end(); ++it) { offsets_[map * universes_.size() + it.indx_] = offset; offset += count_universe_instances(*it, target_univ_id, univ_count_memo); diff --git a/src/random_ray/flat_source_domain.cpp b/src/random_ray/flat_source_domain.cpp index b04b72981f8..40923883088 100644 --- a/src/random_ray/flat_source_domain.cpp +++ b/src/random_ray/flat_source_domain.cpp @@ -46,7 +46,7 @@ FlatSourceDomain::FlatSourceDomain() : negroups_(data::mg.num_energy_groups_) source_region_offsets_.push_back(-1); } else { source_region_offsets_.push_back(base_source_regions); - base_source_regions += c->n_instances_; + base_source_regions += c->n_instances(); } } @@ -61,7 +61,7 @@ FlatSourceDomain::FlatSourceDomain() : negroups_(data::mg.num_energy_groups_) for (int i = 0; i < model::cells.size(); i++) { Cell& cell = *model::cells[i]; if (cell.type_ == Fill::MATERIAL) { - for (int j = 0; j < cell.n_instances_; j++) { + for (int j = 0; j < cell.n_instances(); j++) { source_regions_.material(source_region_id++) = cell.material(j); } } @@ -1014,7 +1014,7 @@ void FlatSourceDomain::apply_external_source_to_cell_and_children( Cell& cell = *model::cells[i_cell]; if (cell.type_ == Fill::MATERIAL) { - vector instances(cell.n_instances_); + vector instances(cell.n_instances()); std::iota(instances.begin(), instances.end(), 0); apply_external_source_to_cell_instances( i_cell, discrete, strength_factor, target_material_id, instances); @@ -1343,12 +1343,12 @@ void FlatSourceDomain::apply_mesh_to_cell_and_children(int32_t i_cell, Cell& cell = *model::cells[i_cell]; if (cell.type_ == Fill::MATERIAL) { - vector instances(cell.n_instances_); + vector instances(cell.n_instances()); std::iota(instances.begin(), instances.end(), 0); apply_mesh_to_cell_instances( i_cell, mesh_idx, target_material_id, instances, is_target_void); } else if (target_material_id == C_NONE && !is_target_void) { - for (int j = 0; j < cell.n_instances_; j++) { + for (int j = 0; j < cell.n_instances(); j++) { std::unordered_map> cell_instance_list = cell.get_contained_cells(j, nullptr); for (const auto& pair : cell_instance_list) { diff --git a/src/tallies/filter_distribcell.cpp b/src/tallies/filter_distribcell.cpp index 02e88dde283..f511a681686 100644 --- a/src/tallies/filter_distribcell.cpp +++ b/src/tallies/filter_distribcell.cpp @@ -34,7 +34,7 @@ void DistribcellFilter::set_cell(int32_t cell) assert(cell >= 0); assert(cell < model::cells.size()); cell_ = cell; - n_bins_ = model::cells[cell]->n_instances_; + n_bins_ = model::cells[cell]->n_instances(); } void DistribcellFilter::get_all_bins( diff --git a/tests/regression_tests/lattice_distribmat/False/inputs_true.dat b/tests/regression_tests/lattice_distribmat/False/inputs_true.dat new file mode 100644 index 00000000000..d39a103695b --- /dev/null +++ b/tests/regression_tests/lattice_distribmat/False/inputs_true.dat @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 1.0 1.0 + 1 + 1 1 + -1.0 -1.0 + +1 + + + 1.0 1.0 + 1 + 1 1 + -1.0 0 + +1 + + + 1.0 1.0 + 1 + 1 1 + 0 -1.0 + +1 + + + 1.0 1.0 + 1 + 1 1 + 0 0 + +1 + + + + + + + + + + + + + + + eigenvalue + 1000 + 10 + 5 + + diff --git a/tests/regression_tests/lattice_distribmat/False/results_true.dat b/tests/regression_tests/lattice_distribmat/False/results_true.dat new file mode 100644 index 00000000000..45a9ddb0a69 --- /dev/null +++ b/tests/regression_tests/lattice_distribmat/False/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +1.863277E+00 1.289821E-02 diff --git a/tests/regression_tests/lattice_distribmat/True/inputs_true.dat b/tests/regression_tests/lattice_distribmat/True/inputs_true.dat new file mode 100644 index 00000000000..450306a0c58 --- /dev/null +++ b/tests/regression_tests/lattice_distribmat/True/inputs_true.dat @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.0 1.0 + 8 + 1 1 + -1.0 -1.0 + +8 + + + 1.0 1.0 + 8 + 1 1 + -1.0 0 + +8 + + + 1.0 1.0 + 8 + 1 1 + 0 -1.0 + +8 + + + 1.0 1.0 + 8 + 1 1 + 0 0 + +8 + + + + + + + + + + + + + + + eigenvalue + 1000 + 10 + 5 + + diff --git a/tests/regression_tests/lattice_distribmat/True/results_true.dat b/tests/regression_tests/lattice_distribmat/True/results_true.dat new file mode 100644 index 00000000000..45a9ddb0a69 --- /dev/null +++ b/tests/regression_tests/lattice_distribmat/True/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +1.863277E+00 1.289821E-02 diff --git a/tests/regression_tests/lattice_distribmat/__init__.py b/tests/regression_tests/lattice_distribmat/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/lattice_distribmat/test.py b/tests/regression_tests/lattice_distribmat/test.py new file mode 100644 index 00000000000..4d0b6e15600 --- /dev/null +++ b/tests/regression_tests/lattice_distribmat/test.py @@ -0,0 +1,83 @@ +import numpy as np +import openmc +from openmc.utility_funcs import change_directory +import pytest + +from tests.testing_harness import PyAPITestHarness + + +@pytest.fixture +def model(): + model = openmc.model.Model() + + uo2 = openmc.Material(name='UO2') + uo2.set_density('g/cm3', 10.0) + uo2.add_nuclide('U235', 1.0) + uo2.add_nuclide('O16', 2.0) + water = openmc.Material(name='light water') + water.add_nuclide('H1', 2.0) + water.add_nuclide('O16', 1.0) + water.set_density('g/cm3', 1.0) + water.add_s_alpha_beta('c_H_in_H2O') + model.materials.extend([uo2, water]) + + cyl = openmc.ZCylinder(r=0.4) + pin = openmc.model.pin([cyl], [uo2, water]) + d = 1.0 + + lattice00 = openmc.RectLattice() + lattice00.lower_left = (-d, -d) + lattice00.pitch = (d, d) + lattice00.outer = pin + lattice00.universes = [[pin]] + box00 = openmc.model.RectangularPrism(d, d, origin=(-d/2,-d/2)) + + lattice01 = openmc.RectLattice() + lattice01.lower_left = (-d, 0) + lattice01.pitch = (d, d) + lattice01.outer = pin + lattice01.universes = [[pin]] + box01 = openmc.model.RectangularPrism(d, d, origin=(-d/2,d/2)) + + lattice10 = openmc.RectLattice() + lattice10.lower_left = (0, -d) + lattice10.pitch = (d, d) + lattice10.outer = pin + lattice10.universes = [[pin]] + box10 = openmc.model.RectangularPrism(d, d, origin=(d/2,-d/2)) + + lattice11 = openmc.RectLattice() + lattice11.lower_left = (0, 0) + lattice11.pitch = (d, d) + lattice11.outer = pin + lattice11.universes = [[pin]] + box11 = openmc.model.RectangularPrism(d, d, origin=(d/2,d/2)) + + + cell00 = openmc.Cell(fill=lattice00, region = -box00) + cell01 = openmc.Cell(fill=lattice01, region = -box01) + cell10 = openmc.Cell(fill=lattice10, region = -box10) + cell11 = openmc.Cell(fill=lattice11, region = -box11) + + univ = openmc.Universe(cells=[cell00, cell01, cell10, cell11]) + + box = openmc.model.RectangularPrism(2*d, 2*d, boundary_type='reflective') + + main_cell = openmc.Cell(fill=univ, region=-box) + model.geometry = openmc.Geometry([main_cell]) + model.geometry.merge_surfaces = True + + model.settings.batches = 10 + model.settings.inactive = 5 + model.settings.particles = 1000 + + return model + +@pytest.mark.parametrize("distribmat", [False, True]) +def test_lattice(model, distribmat): + with change_directory(str(distribmat)): + openmc.reset_auto_ids() + if distribmat: + model.differentiate_mats(depletable_only=False) + harness = PyAPITestHarness('statepoint.10.h5', model) + harness.main() From 4fabed542da3d2e37eeba31872987e090b6ffc4b Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Fri, 8 Aug 2025 03:53:33 +0300 Subject: [PATCH 085/159] fixed a bug in MeshMaterialFilter.from_volumes (#3520) Co-authored-by: Paul Romano --- openmc/filter.py | 7 ++++++- tests/unit_tests/test_filter_meshmaterial.py | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/openmc/filter.py b/openmc/filter.py index f29c540857c..53fec898fcc 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -1082,7 +1082,12 @@ def from_volumes(cls, mesh: openmc.MeshBase, volumes: openmc.MeshMaterialVolumes A new MeshMaterialFilter instance """ - bins = list(zip(*np.where(volumes._materials > -1))) + # Get flat arrays of material IDs and element indices + mat_ids = volumes._materials[volumes._materials > -1] + elems, _ = np.where(volumes._materials > -1) + + # Stack them into a 2D array of (element, material) pairs + bins = np.column_stack((elems, mat_ids)) return cls(mesh, bins) def __hash__(self): diff --git a/tests/unit_tests/test_filter_meshmaterial.py b/tests/unit_tests/test_filter_meshmaterial.py index 12053320976..a05ed5a1207 100644 --- a/tests/unit_tests/test_filter_meshmaterial.py +++ b/tests/unit_tests/test_filter_meshmaterial.py @@ -9,6 +9,7 @@ def test_filter_mesh_material(run_in_tmpdir): materials = [] for i in range(4): mat = openmc.Material() + mat.id = 10*(i+1) mat.add_nuclide('Fe56', 1.0) materials.append(mat) @@ -35,7 +36,7 @@ def test_filter_mesh_material(run_in_tmpdir): # MeshMaterialFilter with corresponding bins vols = mesh.material_volumes(model) mmf = openmc.MeshMaterialFilter.from_volumes(mesh, vols) - expected_bins = [(0, 1), (1, 1), (1, 2), (2, 2), (2, 3), (3, 3), (3, 4), (4, 4)] + expected_bins = [(0, 10), (1, 10), (1, 20), (2, 20), (2, 30), (3, 40), (3, 30), (4, 40)] np.testing.assert_equal(mmf.bins, expected_bins) # Create two tallies, one with a mesh filter and one with mesh-material From 4500f07b44daec71f38d0d9b73736e41f9d23815 Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Fri, 8 Aug 2025 12:59:22 +0300 Subject: [PATCH 086/159] Remove reorder_attributes from openmc._xml (#3519) --- openmc/_xml.py | 17 - openmc/data/library.py | 3 +- openmc/geometry.py | 1 - openmc/material.py | 4 +- openmc/plots.py | 4 +- openmc/settings.py | 3 +- openmc/tallies.py | 3 +- .../adj_cell_rotation/inputs_true.dat | 32 +- .../asymmetric_lattice/inputs_true.dat | 92 +- .../cpp_driver/inputs_true.dat | 32 +- .../create_fission_neutrons/inputs_true.dat | 26 +- .../dagmc/external/inputs_true.dat | 16 +- .../dagmc/legacy/inputs_true.dat | 16 +- .../dagmc/refl/inputs_true.dat | 4 +- .../dagmc/universes/inputs_true.dat | 50 +- .../dagmc/uwuw/inputs_true.dat | 4 +- .../last_step_reference_materials.xml | 2082 ++++++++--------- .../diff_tally/inputs_true.dat | 116 +- .../distribmat/inputs_true.dat | 36 +- .../eigenvalue_genperbatch/inputs_true.dat | 10 +- .../energy_cutoff/inputs_true.dat | 22 +- .../energy_laws/inputs_true.dat | 16 +- .../external_moab/inputs_true.dat | 72 +- .../filter_cellfrom/inputs_true.dat | 64 +- .../filter_cellinstance/inputs_true.dat | 28 +- .../filter_energyfun/inputs_true.dat | 8 +- .../filter_mesh/inputs_true.dat | 30 +- .../filter_meshborn/inputs_true.dat | 8 +- .../filter_musurface/inputs_true.dat | 14 +- .../filter_translations/inputs_true.dat | 34 +- .../fixed_source/inputs_true.dat | 12 +- tests/regression_tests/ifp/inputs_true.dat | 10 +- .../iso_in_lab/inputs_true.dat | 106 +- .../lattice_distribmat/False/inputs_true.dat | 46 +- .../lattice_distribmat/True/inputs_true.dat | 88 +- .../lattice_hex_coincident/inputs_true.dat | 70 +- .../lattice_hex_x/inputs_true.dat | 70 +- .../lattice_multiple/inputs_true.dat | 30 +- .../lattice_rotated/inputs_true.dat | 34 +- .../regression_tests/mg_basic/inputs_true.dat | 30 +- .../mg_basic_delayed/inputs_true.dat | 28 +- .../mg_convert/inputs_true.dat | 14 +- .../mg_legendre/inputs_true.dat | 8 +- .../mg_max_order/inputs_true.dat | 8 +- .../mg_survival_biasing/inputs_true.dat | 8 +- .../mg_tallies/inputs_true.dat | 8 +- .../mg_temperature_multi/inputs_true.dat | 24 +- .../mgxs_library_ce_to_mg/inputs_true.dat | 54 +- .../inputs_true.dat | 54 +- .../mgxs_library_condense/inputs_true.dat | 54 +- .../mgxs_library_correction/inputs_true.dat | 54 +- .../mgxs_library_distribcell/inputs_true.dat | 62 +- .../mgxs_library_hdf5/inputs_true.dat | 54 +- .../mgxs_library_histogram/inputs_true.dat | 54 +- .../mgxs_library_mesh/inputs_true.dat | 30 +- .../mgxs_library_no_nuclides/inputs_true.dat | 54 +- .../mgxs_library_nuclides/inputs_true.dat | 54 +- .../inputs_true.dat | 54 +- .../adj_cell_rotation_inputs_true.dat | 32 +- .../model_xml/energy_laws_inputs_true.dat | 16 +- .../lattice_multiple_inputs_true.dat | 30 +- .../photon_production_inputs_true.dat | 16 +- .../multipole/inputs_true.dat | 26 +- .../regression_tests/ncrystal/inputs_true.dat | 14 +- .../regression_tests/periodic/inputs_true.dat | 28 +- .../periodic_6fold/inputs_true.dat | 22 +- .../periodic_hex/inputs_true.dat | 18 +- .../photon_production/inputs_true.dat | 16 +- .../photon_production_fission/inputs_true.dat | 10 +- .../photon_source/inputs_true.dat | 14 +- .../pulse_height/inputs_true.dat | 16 +- .../inputs_true.dat | 32 +- .../random_ray_adjoint_k_eff/inputs_true.dat | 74 +- .../infinite_medium/inputs_true.dat | 30 +- .../material_wise/inputs_true.dat | 30 +- .../stochastic_slab/inputs_true.dat | 30 +- .../inputs_true.dat | 30 +- .../cell/inputs_true.dat | 32 +- .../material/inputs_true.dat | 32 +- .../universe/inputs_true.dat | 32 +- .../linear/inputs_true.dat | 32 +- .../linear_xy/inputs_true.dat | 32 +- .../flat/inputs_true.dat | 32 +- .../linear/inputs_true.dat | 32 +- .../False/inputs_true.dat | 32 +- .../True/inputs_true.dat | 32 +- .../flat/inputs_true.dat | 80 +- .../linear_xy/inputs_true.dat | 80 +- .../random_ray_halton_samples/inputs_true.dat | 74 +- .../random_ray_k_eff/inputs_true.dat | 74 +- .../random_ray_k_eff_mesh/inputs_true.dat | 74 +- .../random_ray_linear/linear/inputs_true.dat | 74 +- .../linear_xy/inputs_true.dat | 74 +- .../inputs_true.dat | 32 +- .../random_ray_void/flat/inputs_true.dat | 32 +- .../random_ray_void/linear/inputs_true.dat | 32 +- .../hybrid/inputs_true.dat | 32 +- .../naive/inputs_true.dat | 32 +- .../simulation_averaged/inputs_true.dat | 32 +- .../hybrid/inputs_true.dat | 32 +- .../naive/inputs_true.dat | 32 +- .../simulation_averaged/inputs_true.dat | 32 +- .../resonance_scattering/inputs_true.dat | 16 +- .../salphabeta/inputs_true.dat | 60 +- .../score_current/inputs_true.dat | 30 +- tests/regression_tests/source/inputs_true.dat | 102 +- .../source_dlopen/inputs_true.dat | 14 +- .../inputs_true.dat | 14 +- tests/regression_tests/stride/inputs_true.dat | 10 +- .../surface_source/inputs_true_read_h5.dat | 8 +- .../surface_source/inputs_true_read_mcpl.dat | 8 +- .../surface_source/inputs_true_write_h5.dat | 10 +- .../surface_source/inputs_true_write_mcpl.dat | 10 +- .../case-01/inputs_true.dat | 54 +- .../case-02/inputs_true.dat | 54 +- .../case-03/inputs_true.dat | 54 +- .../case-04/inputs_true.dat | 54 +- .../case-05/inputs_true.dat | 54 +- .../case-06/inputs_true.dat | 54 +- .../case-07/inputs_true.dat | 54 +- .../case-08/inputs_true.dat | 54 +- .../case-09/inputs_true.dat | 54 +- .../case-10/inputs_true.dat | 54 +- .../case-11/inputs_true.dat | 54 +- .../case-12/inputs_true.dat | 42 +- .../case-13/inputs_true.dat | 42 +- .../case-14/inputs_true.dat | 42 +- .../case-15/inputs_true.dat | 42 +- .../case-16/inputs_true.dat | 42 +- .../case-17/inputs_true.dat | 42 +- .../case-18/inputs_true.dat | 42 +- .../case-19/inputs_true.dat | 42 +- .../case-20/inputs_true.dat | 42 +- .../case-21/inputs_true.dat | 42 +- .../case-a01/inputs_true.dat | 54 +- .../case-d01/inputs_true.dat | 16 +- .../case-d02/inputs_true.dat | 16 +- .../case-d03/inputs_true.dat | 16 +- .../case-d04/inputs_true.dat | 16 +- .../case-d05/inputs_true.dat | 16 +- .../case-d06/inputs_true.dat | 16 +- .../case-d07/inputs_true.dat | 42 +- .../case-d08/inputs_true.dat | 42 +- .../case-e01/inputs_true.dat | 54 +- .../case-e02/inputs_true.dat | 54 +- .../case-e03/inputs_true.dat | 42 +- .../surface_tally/inputs_true.dat | 36 +- .../regression_tests/tallies/inputs_true.dat | 108 +- .../tally_aggregation/inputs_true.dat | 30 +- .../tally_arithmetic/inputs_true.dat | 20 +- .../tally_slice_merge/inputs_true.dat | 106 +- .../time_cutoff/inputs_true.dat | 4 +- tests/regression_tests/torus/inputs_true.dat | 28 +- .../torus/large_major/inputs_true.dat | 16 +- .../inputs_true.dat | 8 +- tests/regression_tests/triso/inputs_true.dat | 718 +++--- .../unstructured_mesh/inputs_true.dat | 74 +- .../unstructured_mesh/inputs_true0.dat | 74 +- .../unstructured_mesh/inputs_true1.dat | 74 +- .../unstructured_mesh/inputs_true10.dat | 74 +- .../unstructured_mesh/inputs_true11.dat | 74 +- .../unstructured_mesh/inputs_true12.dat | 74 +- .../unstructured_mesh/inputs_true13.dat | 74 +- .../unstructured_mesh/inputs_true14.dat | 74 +- .../unstructured_mesh/inputs_true15.dat | 74 +- .../unstructured_mesh/inputs_true2.dat | 74 +- .../unstructured_mesh/inputs_true3.dat | 74 +- .../unstructured_mesh/inputs_true4.dat | 101 +- .../unstructured_mesh/inputs_true5.dat | 101 +- .../unstructured_mesh/inputs_true6.dat | 101 +- .../unstructured_mesh/inputs_true7.dat | 101 +- .../unstructured_mesh/inputs_true8.dat | 74 +- .../unstructured_mesh/inputs_true9.dat | 74 +- tests/regression_tests/void/inputs_true.dat | 106 +- .../volume_calc/inputs_true.dat | 32 +- .../volume_calc/inputs_true_mg.dat | 32 +- .../weightwindows/inputs_true.dat | 46 +- .../weightwindows_fw_cadis/inputs_true.dat | 32 +- .../flat/inputs_true.dat | 32 +- .../linear/inputs_true.dat | 32 +- 180 files changed, 5006 insertions(+), 5035 deletions(-) diff --git a/openmc/_xml.py b/openmc/_xml.py index 17389a82f0c..05412128dd8 100644 --- a/openmc/_xml.py +++ b/openmc/_xml.py @@ -63,23 +63,6 @@ def get_text(elem, name, default=None): return child.text if child is not None else default -def reorder_attributes(root): - """Sort attributes in XML to preserve pre-Python 3.8 behavior - - Parameters - ---------- - root : lxml.etree._Element - Root element - - """ - for el in root.iter(): - attrib = el.attrib - if len(attrib) > 1: - # adjust attribute order, e.g. by sorting - attribs = sorted(attrib.items()) - attrib.clear() - attrib.update(attribs) - def get_elem_tuple(elem, name, dtype=int): """Helper function to get a tuple of values from an elem diff --git a/openmc/data/library.py b/openmc/data/library.py index a6ce1bbd32c..bec538c0696 100644 --- a/openmc/data/library.py +++ b/openmc/data/library.py @@ -5,7 +5,7 @@ import lxml.etree as ET import openmc -from openmc._xml import clean_indentation, reorder_attributes +from openmc._xml import clean_indentation class DataLibrary(list): @@ -132,7 +132,6 @@ def export_to_xml(self, path='cross_sections.xml'): clean_indentation(root) # Write XML file - reorder_attributes(root) # TODO: Remove when support is Python 3.8+ tree = ET.ElementTree(root) tree.write(str(path), xml_declaration=True, encoding='utf-8', method='xml') diff --git a/openmc/geometry.py b/openmc/geometry.py index f56b9b6fe37..8496fb23ad7 100644 --- a/openmc/geometry.py +++ b/openmc/geometry.py @@ -140,7 +140,6 @@ def to_xml_element(self, remove_surfs=False) -> ET.Element: # Clean the indentation in the file to be user-readable xml.clean_indentation(element) - xml.reorder_attributes(element) # TODO: Remove when support is Python 3.8+ return element diff --git a/openmc/material.py b/openmc/material.py index cec74af6270..db67b709ca0 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -17,7 +17,7 @@ import openmc import openmc.data import openmc.checkvalue as cv -from ._xml import clean_indentation, reorder_attributes +from ._xml import clean_indentation from .mixin import IDManagerMixin from .utility_funcs import input_path from . import waste @@ -1919,7 +1919,6 @@ def _write_xml(self, file, header=True, level=0, spaces_per_level=2, clean_indentation(element, level=level+1) element.tail = element.tail.strip(' ') file.write((level+1)*spaces_per_level*' ') - reorder_attributes(element) # TODO: Remove when support is Python 3.8+ file.write(ET.tostring(element, encoding="unicode")) # Write the elements. @@ -1928,7 +1927,6 @@ def _write_xml(self, file, header=True, level=0, spaces_per_level=2, clean_indentation(element, level=level+1) element.tail = element.tail.strip(' ') file.write((level+1)*spaces_per_level*' ') - reorder_attributes(element) # TODO: Remove when support is Python 3.8+ file.write(ET.tostring(element, encoding="unicode")) # Write the closing tag for the root element. diff --git a/openmc/plots.py b/openmc/plots.py index 9e097e2b9bf..34dde84e415 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -10,7 +10,7 @@ import openmc.checkvalue as cv from openmc.checkvalue import PathLike -from ._xml import clean_indentation, get_elem_tuple, reorder_attributes, get_text +from ._xml import clean_indentation, get_elem_tuple, get_text from .mixin import IDManagerMixin _BASES = {'xy', 'xz', 'yz'} @@ -1857,8 +1857,6 @@ def to_xml_element(self): # Clean the indentation in the file to be user-readable clean_indentation(self._plots_file) - # TODO: Remove when support is Python 3.8+ - reorder_attributes(self._plots_file) return self._plots_file diff --git a/openmc/settings.py b/openmc/settings.py index 36eaec6cb45..327faf544c0 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -11,7 +11,7 @@ import openmc.checkvalue as cv from openmc.checkvalue import PathLike from openmc.stats.multivariate import MeshSpatial -from ._xml import clean_indentation, get_text, reorder_attributes +from ._xml import clean_indentation, get_text from .mesh import _read_meshes, RegularMesh, MeshBase from .source import SourceBase, MeshSource, IndependentSource from .utility_funcs import input_path @@ -2201,7 +2201,6 @@ def to_xml_element(self, mesh_memo=None): # Clean the indentation in the file to be user-readable clean_indentation(element) - reorder_attributes(element) # TODO: Remove when support is Python 3.8+ return element diff --git a/openmc/tallies.py b/openmc/tallies.py index 06de0205c9e..3a5b425198e 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -15,7 +15,7 @@ import openmc import openmc.checkvalue as cv -from ._xml import clean_indentation, reorder_attributes, get_text +from ._xml import clean_indentation, get_text from .mixin import IDManagerMixin from .mesh import MeshBase @@ -3329,7 +3329,6 @@ def to_xml_element(self, memo=None): # Clean the indentation in the file to be user-readable clean_indentation(element) - reorder_attributes(element) # TODO: Remove when support is Python 3.8+ return element diff --git a/tests/regression_tests/adj_cell_rotation/inputs_true.dat b/tests/regression_tests/adj_cell_rotation/inputs_true.dat index 7e2a0cf628f..18c0552cebb 100644 --- a/tests/regression_tests/adj_cell_rotation/inputs_true.dat +++ b/tests/regression_tests/adj_cell_rotation/inputs_true.dat @@ -1,35 +1,35 @@ - - - + + + - - + + - - - - - - - - - - + + + + + + + + + + eigenvalue 10000 10 5 - + -4.0 -4.0 -4.0 4.0 4.0 4.0 diff --git a/tests/regression_tests/asymmetric_lattice/inputs_true.dat b/tests/regression_tests/asymmetric_lattice/inputs_true.dat index 302ef24c200..ee3d689076f 100644 --- a/tests/regression_tests/asymmetric_lattice/inputs_true.dat +++ b/tests/regression_tests/asymmetric_lattice/inputs_true.dat @@ -1,40 +1,40 @@ - - - - - - - + + + + + + + - - - - - - + + + + + + - - - - - + + + + + - - - - - + + + + + - + @@ -47,7 +47,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -77,7 +77,7 @@ - + @@ -92,7 +92,7 @@ - + @@ -107,7 +107,7 @@ - + @@ -122,7 +122,7 @@ - + @@ -135,7 +135,7 @@ - + @@ -149,7 +149,7 @@ - + @@ -157,7 +157,7 @@ - + 1.26 1.26 17 17 @@ -190,25 +190,25 @@ 8 8 8 7 7 7 - - - - - - - - - - - - + + + + + + + + + + + + eigenvalue 100 10 5 - + -32 -32 0 32 32 32 diff --git a/tests/regression_tests/cpp_driver/inputs_true.dat b/tests/regression_tests/cpp_driver/inputs_true.dat index 5d067cb6854..fd450428a91 100644 --- a/tests/regression_tests/cpp_driver/inputs_true.dat +++ b/tests/regression_tests/cpp_driver/inputs_true.dat @@ -1,26 +1,26 @@ - - - + + + - - + + - - - + + + - - + + 4.0 4.0 2 2 @@ -29,12 +29,12 @@ 2 2 2 2 - - - - - - + + + + + + eigenvalue diff --git a/tests/regression_tests/create_fission_neutrons/inputs_true.dat b/tests/regression_tests/create_fission_neutrons/inputs_true.dat index 9e44a022497..47e8c38307a 100644 --- a/tests/regression_tests/create_fission_neutrons/inputs_true.dat +++ b/tests/regression_tests/create_fission_neutrons/inputs_true.dat @@ -1,30 +1,30 @@ - - - - + + + + - - - - - - - + + + + + + + fixed source 100 10 - + -1 -1 -1 1 1 1 - + false diff --git a/tests/regression_tests/dagmc/external/inputs_true.dat b/tests/regression_tests/dagmc/external/inputs_true.dat index 31f4c1f886d..8a6c6fe74a8 100644 --- a/tests/regression_tests/dagmc/external/inputs_true.dat +++ b/tests/regression_tests/dagmc/external/inputs_true.dat @@ -1,26 +1,26 @@ - - - + + + - - - + + + - + eigenvalue 100 5 0 - + -4 -4 -4 4 4 4 diff --git a/tests/regression_tests/dagmc/legacy/inputs_true.dat b/tests/regression_tests/dagmc/legacy/inputs_true.dat index b06516cb1eb..ad2f8e54d81 100644 --- a/tests/regression_tests/dagmc/legacy/inputs_true.dat +++ b/tests/regression_tests/dagmc/legacy/inputs_true.dat @@ -1,26 +1,26 @@ - - - + + + - - - + + + - + eigenvalue 100 5 0 - + -4 -4 -4 4 4 4 diff --git a/tests/regression_tests/dagmc/refl/inputs_true.dat b/tests/regression_tests/dagmc/refl/inputs_true.dat index 979eeb49206..58cb9e66f2a 100644 --- a/tests/regression_tests/dagmc/refl/inputs_true.dat +++ b/tests/regression_tests/dagmc/refl/inputs_true.dat @@ -3,14 +3,14 @@ - + eigenvalue 100 5 0 - + -4 -4 -4 4 4 4 diff --git a/tests/regression_tests/dagmc/universes/inputs_true.dat b/tests/regression_tests/dagmc/universes/inputs_true.dat index 4b5be3612bd..2fa79d831e6 100644 --- a/tests/regression_tests/dagmc/universes/inputs_true.dat +++ b/tests/regression_tests/dagmc/universes/inputs_true.dat @@ -1,33 +1,33 @@ - - - - - - + + + + + + - - - - - - + + + + + + - - - - - + + + + + - - + + 24.0 24.0 2 2 @@ -36,12 +36,12 @@ 1 1 1 1 - - - - - - + + + + + + eigenvalue diff --git a/tests/regression_tests/dagmc/uwuw/inputs_true.dat b/tests/regression_tests/dagmc/uwuw/inputs_true.dat index d7e4a12aa78..01082cb238c 100644 --- a/tests/regression_tests/dagmc/uwuw/inputs_true.dat +++ b/tests/regression_tests/dagmc/uwuw/inputs_true.dat @@ -3,14 +3,14 @@ - + eigenvalue 100 5 0 - + -4 -4 -4 4 4 4 diff --git a/tests/regression_tests/deplete_with_transport/last_step_reference_materials.xml b/tests/regression_tests/deplete_with_transport/last_step_reference_materials.xml index f61595dd9fb..242e2e7db5f 100644 --- a/tests/regression_tests/deplete_with_transport/last_step_reference_materials.xml +++ b/tests/regression_tests/deplete_with_transport/last_step_reference_materials.xml @@ -1,1049 +1,1049 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + diff --git a/tests/regression_tests/diff_tally/inputs_true.dat b/tests/regression_tests/diff_tally/inputs_true.dat index 3ec4154e82e..19356b64adb 100644 --- a/tests/regression_tests/diff_tally/inputs_true.dat +++ b/tests/regression_tests/diff_tally/inputs_true.dat @@ -1,40 +1,40 @@ - - - - - - - + + + + + + + - - - - - - + + + + + + - - - - - + + + + + - - - - - + + + + + - + @@ -47,7 +47,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -77,7 +77,7 @@ - + @@ -92,7 +92,7 @@ - + @@ -107,7 +107,7 @@ - + @@ -122,7 +122,7 @@ - + @@ -135,7 +135,7 @@ - + @@ -149,8 +149,8 @@ - - + + @@ -174,9 +174,9 @@ - + - + 1.26 1.26 17 17 @@ -277,30 +277,30 @@ 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + eigenvalue 100 3 0 - + -160 -160 -183 160 160 183 @@ -429,10 +429,10 @@ nu-fission scatter 5 - - - - - + + + + + diff --git a/tests/regression_tests/distribmat/inputs_true.dat b/tests/regression_tests/distribmat/inputs_true.dat index ade86999d0a..35b3b5b9fb7 100644 --- a/tests/regression_tests/distribmat/inputs_true.dat +++ b/tests/regression_tests/distribmat/inputs_true.dat @@ -2,24 +2,24 @@ - - - + + + - - - + + + - - - + + + - + 2.0 2.0 1 @@ -29,30 +29,30 @@ 11 11 11 11 - - - - - + + + + + eigenvalue 1000 5 0 - + -1 -1 -1 1 1 1 - + 400 400 0 0 0 7 7 - + 400 400 0 0 0 7 7 diff --git a/tests/regression_tests/eigenvalue_genperbatch/inputs_true.dat b/tests/regression_tests/eigenvalue_genperbatch/inputs_true.dat index a3e67d62f69..15b810a6261 100644 --- a/tests/regression_tests/eigenvalue_genperbatch/inputs_true.dat +++ b/tests/regression_tests/eigenvalue_genperbatch/inputs_true.dat @@ -1,14 +1,14 @@ - - - + + + - + eigenvalue @@ -16,7 +16,7 @@ 7 3 3 - + -4.0 -4.0 -4.0 4.0 4.0 4.0 diff --git a/tests/regression_tests/energy_cutoff/inputs_true.dat b/tests/regression_tests/energy_cutoff/inputs_true.dat index af8f86b6762..557cb7a3b16 100644 --- a/tests/regression_tests/energy_cutoff/inputs_true.dat +++ b/tests/regression_tests/energy_cutoff/inputs_true.dat @@ -2,28 +2,28 @@ - - + + - - - - - - - + + + + + + + fixed source 100 10 - + -1 -1 -1 1 1 1 - + 4.0 diff --git a/tests/regression_tests/energy_laws/inputs_true.dat b/tests/regression_tests/energy_laws/inputs_true.dat index 4d8e825d1b5..8c5191217d7 100644 --- a/tests/regression_tests/energy_laws/inputs_true.dat +++ b/tests/regression_tests/energy_laws/inputs_true.dat @@ -1,18 +1,18 @@ - - - - - - - + + + + + + + - + eigenvalue diff --git a/tests/regression_tests/external_moab/inputs_true.dat b/tests/regression_tests/external_moab/inputs_true.dat index 23b656bceeb..ed035f897a3 100644 --- a/tests/regression_tests/external_moab/inputs_true.dat +++ b/tests/regression_tests/external_moab/inputs_true.dat @@ -1,63 +1,63 @@ - - - + + + - - - - - - + + + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + fixed source 100 10 - + 0.0 0.0 0.0 - + 15000000.0 1.0 - + test_mesh_tets.h5m diff --git a/tests/regression_tests/filter_cellfrom/inputs_true.dat b/tests/regression_tests/filter_cellfrom/inputs_true.dat index b85f63c6e18..20d0d69d4c1 100644 --- a/tests/regression_tests/filter_cellfrom/inputs_true.dat +++ b/tests/regression_tests/filter_cellfrom/inputs_true.dat @@ -1,54 +1,54 @@ - - - - - - - + + + + + + + - - - + + + - - - - - + + + + + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + eigenvalue 2000 15 5 - + -2.0 -2.0 -2.0 2.0 2.0 2.0 diff --git a/tests/regression_tests/filter_cellinstance/inputs_true.dat b/tests/regression_tests/filter_cellinstance/inputs_true.dat index b17cc0eb59f..2677d7ad2e9 100644 --- a/tests/regression_tests/filter_cellinstance/inputs_true.dat +++ b/tests/regression_tests/filter_cellinstance/inputs_true.dat @@ -1,22 +1,22 @@ - - - + + + - - + + - + - + 2 2 4 4 @@ -27,19 +27,19 @@ 3 3 2 3 3 3 3 2 - - - - - - + + + + + + eigenvalue 1000 5 0 - + 0.0 0.0 0.0 diff --git a/tests/regression_tests/filter_energyfun/inputs_true.dat b/tests/regression_tests/filter_energyfun/inputs_true.dat index 4b7b74d28ec..b7a70290fec 100644 --- a/tests/regression_tests/filter_energyfun/inputs_true.dat +++ b/tests/regression_tests/filter_energyfun/inputs_true.dat @@ -1,14 +1,14 @@ - - - + + + - + eigenvalue diff --git a/tests/regression_tests/filter_mesh/inputs_true.dat b/tests/regression_tests/filter_mesh/inputs_true.dat index 4995749bbd3..10f70a720f2 100644 --- a/tests/regression_tests/filter_mesh/inputs_true.dat +++ b/tests/regression_tests/filter_mesh/inputs_true.dat @@ -1,28 +1,28 @@ - - - + + + - - + + - - - - - - - - - - + + + + + + + + + + eigenvalue diff --git a/tests/regression_tests/filter_meshborn/inputs_true.dat b/tests/regression_tests/filter_meshborn/inputs_true.dat index 3ba38d56e60..a94646ec8de 100644 --- a/tests/regression_tests/filter_meshborn/inputs_true.dat +++ b/tests/regression_tests/filter_meshborn/inputs_true.dat @@ -2,19 +2,19 @@ - - + + - + fixed source 2000 8 - + 0.0 -10.0 -10.0 10.0 10.0 10.0 diff --git a/tests/regression_tests/filter_musurface/inputs_true.dat b/tests/regression_tests/filter_musurface/inputs_true.dat index 031f62159ab..6db8543c2d8 100644 --- a/tests/regression_tests/filter_musurface/inputs_true.dat +++ b/tests/regression_tests/filter_musurface/inputs_true.dat @@ -1,20 +1,20 @@ - - - + + + - - + + - - + + eigenvalue diff --git a/tests/regression_tests/filter_translations/inputs_true.dat b/tests/regression_tests/filter_translations/inputs_true.dat index 5004c3217ab..41ae9b6dc1b 100644 --- a/tests/regression_tests/filter_translations/inputs_true.dat +++ b/tests/regression_tests/filter_translations/inputs_true.dat @@ -1,28 +1,28 @@ - - - + + + - - + + - - - - - - - - - - + + + + + + + + + + eigenvalue @@ -57,10 +57,10 @@ 2 - + 3 - + 4 diff --git a/tests/regression_tests/fixed_source/inputs_true.dat b/tests/regression_tests/fixed_source/inputs_true.dat index 0fdb1466ec5..a3674177503 100644 --- a/tests/regression_tests/fixed_source/inputs_true.dat +++ b/tests/regression_tests/fixed_source/inputs_true.dat @@ -1,21 +1,21 @@ - - - - + + + + - + fixed source 100 10 - + 0.0 0.0 0.0 diff --git a/tests/regression_tests/ifp/inputs_true.dat b/tests/regression_tests/ifp/inputs_true.dat index a3a3f1d77ea..2d69b29abd6 100644 --- a/tests/regression_tests/ifp/inputs_true.dat +++ b/tests/regression_tests/ifp/inputs_true.dat @@ -1,21 +1,21 @@ - - - + + + - + eigenvalue 1000 20 5 - + -10.0 -10.0 -10.0 10.0 10.0 10.0 diff --git a/tests/regression_tests/iso_in_lab/inputs_true.dat b/tests/regression_tests/iso_in_lab/inputs_true.dat index bb6494adafb..adfcf7e51a7 100644 --- a/tests/regression_tests/iso_in_lab/inputs_true.dat +++ b/tests/regression_tests/iso_in_lab/inputs_true.dat @@ -1,44 +1,44 @@ - - - - - - - + + + + + + + U234 U235 U238 Xe135 O16 - - - - - - + + + + + + Zr90 Zr91 Zr92 Zr94 Zr96 - - - - - + + + + + H1 O16 B10 B11 - - - - - + + + + + H1 O16 B10 B11 - + @@ -52,7 +52,7 @@ Fe54 Fe56 Fe57 Fe58 Ni58 Ni60 Mn55 Cr52 C0 Cu63 - + @@ -68,7 +68,7 @@ H1 O16 B10 B11 Fe54 Fe56 Fe57 Fe58 Ni58 Mn55 Cr52 - + @@ -84,7 +84,7 @@ H1 O16 B10 B11 Fe54 Fe56 Fe57 Fe58 Ni58 Mn55 Cr52 - + @@ -100,7 +100,7 @@ H1 O16 B10 B11 Fe54 Fe56 Fe57 Fe58 Ni58 Mn55 Cr52 - + @@ -116,7 +116,7 @@ H1 O16 B10 B11 Fe54 Fe56 Fe57 Fe58 Ni58 Mn55 Cr52 - + @@ -132,7 +132,7 @@ H1 O16 B10 B11 Fe54 Fe56 Fe57 Fe58 Ni58 Mn55 Cr52 - + @@ -146,7 +146,7 @@ H1 O16 B10 B11 Zr90 Zr91 Zr92 Zr94 Zr96 - + @@ -161,8 +161,8 @@ - - + + @@ -186,9 +186,9 @@ - + - + 1.26 1.26 17 17 @@ -289,30 +289,30 @@ 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + eigenvalue 100 10 5 - + -160 -160 -183 160 160 183 diff --git a/tests/regression_tests/lattice_distribmat/False/inputs_true.dat b/tests/regression_tests/lattice_distribmat/False/inputs_true.dat index d39a103695b..78398905718 100644 --- a/tests/regression_tests/lattice_distribmat/False/inputs_true.dat +++ b/tests/regression_tests/lattice_distribmat/False/inputs_true.dat @@ -1,26 +1,26 @@ - - - - + + + + - - - + + + - - - - - + + + + + 1.0 1.0 1 @@ -53,17 +53,17 @@ 1 - - - - - - - - - - - + + + + + + + + + + + eigenvalue diff --git a/tests/regression_tests/lattice_distribmat/True/inputs_true.dat b/tests/regression_tests/lattice_distribmat/True/inputs_true.dat index 450306a0c58..aec3a540084 100644 --- a/tests/regression_tests/lattice_distribmat/True/inputs_true.dat +++ b/tests/regression_tests/lattice_distribmat/True/inputs_true.dat @@ -1,59 +1,59 @@ - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + 1.0 1.0 8 @@ -86,17 +86,17 @@ 8 - - - - - - - - - - - + + + + + + + + + + + eigenvalue diff --git a/tests/regression_tests/lattice_hex_coincident/inputs_true.dat b/tests/regression_tests/lattice_hex_coincident/inputs_true.dat index ac82de0adf9..fafc03da1ed 100644 --- a/tests/regression_tests/lattice_hex_coincident/inputs_true.dat +++ b/tests/regression_tests/lattice_hex_coincident/inputs_true.dat @@ -1,45 +1,45 @@ - + - + - - + + - - - - - + + + + + - - + + - - - - - + + + + + - - - - + + + + - - + + 1.4 3
0.0 0.0
@@ -50,25 +50,25 @@ 2 2 2
- - - - - - - - - - - - + + + + + + + + + + + +
eigenvalue 1000 5 2 - + -0.9899494936611666 -0.9899494936611666 0.0 0.9899494936611666 0.9899494936611666 10.0 diff --git a/tests/regression_tests/lattice_hex_x/inputs_true.dat b/tests/regression_tests/lattice_hex_x/inputs_true.dat index 252fbfc0b5d..b6536c6a8b3 100644 --- a/tests/regression_tests/lattice_hex_x/inputs_true.dat +++ b/tests/regression_tests/lattice_hex_x/inputs_true.dat @@ -1,32 +1,32 @@ - + - - - + + + - - - - + + + + - - - + + + - - - - - + + + + + @@ -42,8 +42,8 @@ - - + + 1.235 5.0 4
0.0 0.0 5.0
@@ -91,29 +91,29 @@ 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
- - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + +
eigenvalue 1000 10 5 - + -13.62546635287517 -13.62546635287517 0.0 13.62546635287517 13.62546635287517 10.0 diff --git a/tests/regression_tests/lattice_multiple/inputs_true.dat b/tests/regression_tests/lattice_multiple/inputs_true.dat index b249d97c40e..06abef58219 100644 --- a/tests/regression_tests/lattice_multiple/inputs_true.dat +++ b/tests/regression_tests/lattice_multiple/inputs_true.dat @@ -1,15 +1,15 @@ - - - - + + + + - - - + + + @@ -18,8 +18,8 @@ - - + + 1.2 1.2 1 @@ -37,12 +37,12 @@ 4 4 4 4 - - - - - - + + + + + + eigenvalue diff --git a/tests/regression_tests/lattice_rotated/inputs_true.dat b/tests/regression_tests/lattice_rotated/inputs_true.dat index 1b7c74870ad..e53b93f9324 100644 --- a/tests/regression_tests/lattice_rotated/inputs_true.dat +++ b/tests/regression_tests/lattice_rotated/inputs_true.dat @@ -1,18 +1,18 @@ - - - + + + - - - + + + - - - + + + @@ -22,8 +22,8 @@ - - + + 1.25 @@ -51,18 +51,18 @@ 1 1 1 1 1 1 1 1 - - - - - + + + + + eigenvalue 1000 5 0 - + 0.0 0.0 0.0 diff --git a/tests/regression_tests/mg_basic/inputs_true.dat b/tests/regression_tests/mg_basic/inputs_true.dat index 8c6faaf070d..aa2c2fa0a4d 100644 --- a/tests/regression_tests/mg_basic/inputs_true.dat +++ b/tests/regression_tests/mg_basic/inputs_true.dat @@ -3,29 +3,29 @@ 2g.h5 - + - + - + - + - + - - + + @@ -35,20 +35,20 @@ - - - - - - - + + + + + + + eigenvalue 1000 10 5 - + 0.0 -1000.0 -1000.0 154.90833333333333 1000.0 1000.0 diff --git a/tests/regression_tests/mg_basic_delayed/inputs_true.dat b/tests/regression_tests/mg_basic_delayed/inputs_true.dat index e0bcad15cef..cc3f2cfb2c4 100644 --- a/tests/regression_tests/mg_basic_delayed/inputs_true.dat +++ b/tests/regression_tests/mg_basic_delayed/inputs_true.dat @@ -3,27 +3,27 @@ 2g.h5 - + - + - + - + - + - + @@ -34,20 +34,20 @@ - - - - - - - + + + + + + + eigenvalue 1000 10 5 - + 0.0 -1000.0 -1000.0 154.90833333333333 1000.0 1000.0 diff --git a/tests/regression_tests/mg_convert/inputs_true.dat b/tests/regression_tests/mg_convert/inputs_true.dat index 4e51ec80c52..3b1f511e63b 100644 --- a/tests/regression_tests/mg_convert/inputs_true.dat +++ b/tests/regression_tests/mg_convert/inputs_true.dat @@ -3,23 +3,23 @@ mgxs.h5 - + - - - - - + + + + + eigenvalue 100 10 5 - + -5 -5 -5 5 5 5 diff --git a/tests/regression_tests/mg_legendre/inputs_true.dat b/tests/regression_tests/mg_legendre/inputs_true.dat index 34ff7b6300d..81362f20a08 100644 --- a/tests/regression_tests/mg_legendre/inputs_true.dat +++ b/tests/regression_tests/mg_legendre/inputs_true.dat @@ -3,21 +3,21 @@ 2g.h5 - + - - + + eigenvalue 1000 10 5 - + 0.0 -1000.0 -1000.0 929.45 1000.0 1000.0 diff --git a/tests/regression_tests/mg_max_order/inputs_true.dat b/tests/regression_tests/mg_max_order/inputs_true.dat index b6d70dc4c01..c8f42d1a3cb 100644 --- a/tests/regression_tests/mg_max_order/inputs_true.dat +++ b/tests/regression_tests/mg_max_order/inputs_true.dat @@ -3,21 +3,21 @@ 2g.h5 - + - - + + eigenvalue 1000 10 5 - + 0.0 -1000.0 -1000.0 929.45 1000.0 1000.0 diff --git a/tests/regression_tests/mg_survival_biasing/inputs_true.dat b/tests/regression_tests/mg_survival_biasing/inputs_true.dat index 6529c60d83e..bee69729dec 100644 --- a/tests/regression_tests/mg_survival_biasing/inputs_true.dat +++ b/tests/regression_tests/mg_survival_biasing/inputs_true.dat @@ -3,21 +3,21 @@ 2g.h5 - + - - + + eigenvalue 1000 10 5 - + 0.0 -1000.0 -1000.0 929.45 1000.0 1000.0 diff --git a/tests/regression_tests/mg_tallies/inputs_true.dat b/tests/regression_tests/mg_tallies/inputs_true.dat index c526b65a205..f4f154244a4 100644 --- a/tests/regression_tests/mg_tallies/inputs_true.dat +++ b/tests/regression_tests/mg_tallies/inputs_true.dat @@ -3,21 +3,21 @@ 2g.h5 - + - - + + eigenvalue 1000 10 5 - + 0.0 -1000.0 -1000.0 929.45 1000.0 1000.0 diff --git a/tests/regression_tests/mg_temperature_multi/inputs_true.dat b/tests/regression_tests/mg_temperature_multi/inputs_true.dat index 0c452cd520e..b84a6782b9b 100644 --- a/tests/regression_tests/mg_temperature_multi/inputs_true.dat +++ b/tests/regression_tests/mg_temperature_multi/inputs_true.dat @@ -3,31 +3,31 @@ mgxs.h5 - + - + - - - - - - - - - + + + + + + + + + eigenvalue 1000 10 5 - + -0.63 -0.63 -1 0.63 0.63 1 diff --git a/tests/regression_tests/mgxs_library_ce_to_mg/inputs_true.dat b/tests/regression_tests/mgxs_library_ce_to_mg/inputs_true.dat index 6c071263f3d..2f6dde1ab0b 100644 --- a/tests/regression_tests/mgxs_library_ce_to_mg/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_ce_to_mg/inputs_true.dat @@ -1,47 +1,47 @@ - - - - - - + + + + + + - - - - - - + + + + + + - - - - - + + + + + - - - - - - - - - + + + + + + + + + eigenvalue 100 10 5 - + -0.63 -0.63 -1 0.63 0.63 1 diff --git a/tests/regression_tests/mgxs_library_ce_to_mg_nuclides/inputs_true.dat b/tests/regression_tests/mgxs_library_ce_to_mg_nuclides/inputs_true.dat index ad3d50974b0..e1f330cf925 100644 --- a/tests/regression_tests/mgxs_library_ce_to_mg_nuclides/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_ce_to_mg_nuclides/inputs_true.dat @@ -1,47 +1,47 @@ - - - - - - + + + + + + - - - - - - + + + + + + - - - - - + + + + + - - - - - - - - - + + + + + + + + + eigenvalue 100 10 5 - + -0.63 -0.63 -1 0.63 0.63 1 diff --git a/tests/regression_tests/mgxs_library_condense/inputs_true.dat b/tests/regression_tests/mgxs_library_condense/inputs_true.dat index 8606e9fdda3..4451d214c47 100644 --- a/tests/regression_tests/mgxs_library_condense/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_condense/inputs_true.dat @@ -1,47 +1,47 @@ - - - - - - + + + + + + - - - - - - + + + + + + - - - - - + + + + + - - - - - - - - - + + + + + + + + + eigenvalue 100 10 5 - + -0.63 -0.63 -1 0.63 0.63 1 diff --git a/tests/regression_tests/mgxs_library_correction/inputs_true.dat b/tests/regression_tests/mgxs_library_correction/inputs_true.dat index ea2d1b714a2..bb463778fd4 100644 --- a/tests/regression_tests/mgxs_library_correction/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_correction/inputs_true.dat @@ -1,47 +1,47 @@ - - - - - - + + + + + + - - - - - - + + + + + + - - - - - + + + + + - - - - - - - - - + + + + + + + + + eigenvalue 100 10 5 - + -0.63 -0.63 -1 0.63 0.63 1 diff --git a/tests/regression_tests/mgxs_library_distribcell/inputs_true.dat b/tests/regression_tests/mgxs_library_distribcell/inputs_true.dat index f1a60533491..e3826cc18b2 100644 --- a/tests/regression_tests/mgxs_library_distribcell/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_distribcell/inputs_true.dat @@ -1,38 +1,38 @@ - - - - - - + + + + + + - - - - - - + + + + + + - - - - - + + + + + - - - - - - - + + + + + + + 1.26 1.26 17 17 @@ -56,19 +56,19 @@ 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - - - - - - + + + + + + eigenvalue 100 10 5 - + -10.71 -10.71 -1 10.71 10.71 1 diff --git a/tests/regression_tests/mgxs_library_hdf5/inputs_true.dat b/tests/regression_tests/mgxs_library_hdf5/inputs_true.dat index 8606e9fdda3..4451d214c47 100644 --- a/tests/regression_tests/mgxs_library_hdf5/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_hdf5/inputs_true.dat @@ -1,47 +1,47 @@ - - - - - - + + + + + + - - - - - - + + + + + + - - - - - + + + + + - - - - - - - - - + + + + + + + + + eigenvalue 100 10 5 - + -0.63 -0.63 -1 0.63 0.63 1 diff --git a/tests/regression_tests/mgxs_library_histogram/inputs_true.dat b/tests/regression_tests/mgxs_library_histogram/inputs_true.dat index 013fcd0fa5f..94c65641806 100644 --- a/tests/regression_tests/mgxs_library_histogram/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_histogram/inputs_true.dat @@ -1,47 +1,47 @@ - - - - - - + + + + + + - - - - - - + + + + + + - - - - - + + + + + - - - - - - - - - + + + + + + + + + eigenvalue 100 10 5 - + -0.63 -0.63 -1 0.63 0.63 1 diff --git a/tests/regression_tests/mgxs_library_mesh/inputs_true.dat b/tests/regression_tests/mgxs_library_mesh/inputs_true.dat index 1ecb7a2d3b4..5a6e8a20a95 100644 --- a/tests/regression_tests/mgxs_library_mesh/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_mesh/inputs_true.dat @@ -1,28 +1,28 @@ - - - + + + - - + + - - - - - - - - - - + + + + + + + + + + eigenvalue diff --git a/tests/regression_tests/mgxs_library_no_nuclides/inputs_true.dat b/tests/regression_tests/mgxs_library_no_nuclides/inputs_true.dat index 5cad36b7ea3..af09268cde9 100644 --- a/tests/regression_tests/mgxs_library_no_nuclides/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_no_nuclides/inputs_true.dat @@ -1,47 +1,47 @@ - - - - - - + + + + + + - - - - - - + + + + + + - - - - - + + + + + - - - - - - - - - + + + + + + + + + eigenvalue 100 10 5 - + -0.63 -0.63 -1 0.63 0.63 1 diff --git a/tests/regression_tests/mgxs_library_nuclides/inputs_true.dat b/tests/regression_tests/mgxs_library_nuclides/inputs_true.dat index 9b3a22510b1..c35e57f0ef8 100644 --- a/tests/regression_tests/mgxs_library_nuclides/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_nuclides/inputs_true.dat @@ -1,47 +1,47 @@ - - - - - - + + + + + + - - - - - - + + + + + + - - - - - + + + + + - - - - - - - - - + + + + + + + + + eigenvalue 100 10 5 - + -0.63 -0.63 -1 0.63 0.63 1 diff --git a/tests/regression_tests/mgxs_library_specific_nuclides/inputs_true.dat b/tests/regression_tests/mgxs_library_specific_nuclides/inputs_true.dat index 1372741c947..dd9d1ceb070 100644 --- a/tests/regression_tests/mgxs_library_specific_nuclides/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_specific_nuclides/inputs_true.dat @@ -1,47 +1,47 @@ - - - - - - + + + + + + - - - - - - + + + + + + - - - - - + + + + + - - - - - - - - - + + + + + + + + + eigenvalue 100 10 5 - + -0.63 -0.63 -1 0.63 0.63 1 diff --git a/tests/regression_tests/model_xml/adj_cell_rotation_inputs_true.dat b/tests/regression_tests/model_xml/adj_cell_rotation_inputs_true.dat index 7e2a0cf628f..18c0552cebb 100644 --- a/tests/regression_tests/model_xml/adj_cell_rotation_inputs_true.dat +++ b/tests/regression_tests/model_xml/adj_cell_rotation_inputs_true.dat @@ -1,35 +1,35 @@ - - - + + + - - + + - - - - - - - - - - + + + + + + + + + + eigenvalue 10000 10 5 - + -4.0 -4.0 -4.0 4.0 4.0 4.0 diff --git a/tests/regression_tests/model_xml/energy_laws_inputs_true.dat b/tests/regression_tests/model_xml/energy_laws_inputs_true.dat index 4d8e825d1b5..8c5191217d7 100644 --- a/tests/regression_tests/model_xml/energy_laws_inputs_true.dat +++ b/tests/regression_tests/model_xml/energy_laws_inputs_true.dat @@ -1,18 +1,18 @@ - - - - - - - + + + + + + + - + eigenvalue diff --git a/tests/regression_tests/model_xml/lattice_multiple_inputs_true.dat b/tests/regression_tests/model_xml/lattice_multiple_inputs_true.dat index b249d97c40e..06abef58219 100644 --- a/tests/regression_tests/model_xml/lattice_multiple_inputs_true.dat +++ b/tests/regression_tests/model_xml/lattice_multiple_inputs_true.dat @@ -1,15 +1,15 @@ - - - - + + + + - - - + + + @@ -18,8 +18,8 @@ - - + + 1.2 1.2 1 @@ -37,12 +37,12 @@ 4 4 4 4 - - - - - - + + + + + + eigenvalue diff --git a/tests/regression_tests/model_xml/photon_production_inputs_true.dat b/tests/regression_tests/model_xml/photon_production_inputs_true.dat index 10f0bad9841..07eebaa3e6f 100644 --- a/tests/regression_tests/model_xml/photon_production_inputs_true.dat +++ b/tests/regression_tests/model_xml/photon_production_inputs_true.dat @@ -2,28 +2,28 @@ - - + + - - - - + + + + fixed source 10000 1 - + 0 0 0 - + 14000000.0 1.0 diff --git a/tests/regression_tests/multipole/inputs_true.dat b/tests/regression_tests/multipole/inputs_true.dat index 7bea669307c..22a351240cd 100644 --- a/tests/regression_tests/multipole/inputs_true.dat +++ b/tests/regression_tests/multipole/inputs_true.dat @@ -2,21 +2,21 @@ - - - + + + - - - + + + - + 2.0 2.0 1 @@ -26,18 +26,18 @@ 11 11 11 11 - - - - - + + + + + eigenvalue 1000 5 0 - + -1 -1 -1 1 1 1 diff --git a/tests/regression_tests/ncrystal/inputs_true.dat b/tests/regression_tests/ncrystal/inputs_true.dat index ecbc3c54f40..81ee2e312c5 100644 --- a/tests/regression_tests/ncrystal/inputs_true.dat +++ b/tests/regression_tests/ncrystal/inputs_true.dat @@ -1,26 +1,26 @@ - - - + + + - - + + fixed source 100000 10 - + 0 0 -20 - + 0.012 1.0 diff --git a/tests/regression_tests/periodic/inputs_true.dat b/tests/regression_tests/periodic/inputs_true.dat index 65e1b5ce309..9183db3c49d 100644 --- a/tests/regression_tests/periodic/inputs_true.dat +++ b/tests/regression_tests/periodic/inputs_true.dat @@ -2,33 +2,33 @@ - - - + + + - - - + + + - - - - - - - + + + + + + + eigenvalue 1000 4 0 - + 0 0 0 5 5 0 diff --git a/tests/regression_tests/periodic_6fold/inputs_true.dat b/tests/regression_tests/periodic_6fold/inputs_true.dat index f7f413d092b..075cffc123b 100644 --- a/tests/regression_tests/periodic_6fold/inputs_true.dat +++ b/tests/regression_tests/periodic_6fold/inputs_true.dat @@ -2,30 +2,30 @@ - - - + + + - - - + + + - - - - + + + + eigenvalue 1000 4 0 - + 0 0 0 5 5 0 diff --git a/tests/regression_tests/periodic_hex/inputs_true.dat b/tests/regression_tests/periodic_hex/inputs_true.dat index 188c8dfa93f..e65af3d940e 100644 --- a/tests/regression_tests/periodic_hex/inputs_true.dat +++ b/tests/regression_tests/periodic_hex/inputs_true.dat @@ -1,19 +1,19 @@ - - - + + + - - - - - - + + + + + + eigenvalue diff --git a/tests/regression_tests/photon_production/inputs_true.dat b/tests/regression_tests/photon_production/inputs_true.dat index 10f0bad9841..07eebaa3e6f 100644 --- a/tests/regression_tests/photon_production/inputs_true.dat +++ b/tests/regression_tests/photon_production/inputs_true.dat @@ -2,28 +2,28 @@ - - + + - - - - + + + + fixed source 10000 1 - + 0 0 0 - + 14000000.0 1.0 diff --git a/tests/regression_tests/photon_production_fission/inputs_true.dat b/tests/regression_tests/photon_production_fission/inputs_true.dat index fa379b39e10..11a194e3ce9 100644 --- a/tests/regression_tests/photon_production_fission/inputs_true.dat +++ b/tests/regression_tests/photon_production_fission/inputs_true.dat @@ -1,21 +1,21 @@ - - - + + + - + eigenvalue 1000 5 2 - + 0 0 0 diff --git a/tests/regression_tests/photon_source/inputs_true.dat b/tests/regression_tests/photon_source/inputs_true.dat index 3d2e0524350..adaa5fb42db 100644 --- a/tests/regression_tests/photon_source/inputs_true.dat +++ b/tests/regression_tests/photon_source/inputs_true.dat @@ -2,22 +2,22 @@ - - - - - + + + + + - + fixed source 10000 1 - + 0 0 0 diff --git a/tests/regression_tests/pulse_height/inputs_true.dat b/tests/regression_tests/pulse_height/inputs_true.dat index 544ddb09788..590928e4355 100644 --- a/tests/regression_tests/pulse_height/inputs_true.dat +++ b/tests/regression_tests/pulse_height/inputs_true.dat @@ -2,22 +2,22 @@ - - - + + + - - - - + + + + fixed source 100 5 - + 0.0 0.0 0.0 diff --git a/tests/regression_tests/random_ray_adjoint_fixed_source/inputs_true.dat b/tests/regression_tests/random_ray_adjoint_fixed_source/inputs_true.dat index 65ffd5af7f2..30e62a8853e 100644 --- a/tests/regression_tests/random_ray_adjoint_fixed_source/inputs_true.dat +++ b/tests/regression_tests/random_ray_adjoint_fixed_source/inputs_true.dat @@ -3,24 +3,24 @@ mgxs.h5 - + - + - + - - - - - + + + + + 2.5 2.5 2.5 12 12 12 @@ -182,19 +182,19 @@ 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - - - - + + + + + + fixed source 500 10 5 - + 100.0 1.0 @@ -207,7 +207,7 @@ 500.0 100.0 - + 0.0 0.0 0.0 30.0 30.0 30.0 diff --git a/tests/regression_tests/random_ray_adjoint_k_eff/inputs_true.dat b/tests/regression_tests/random_ray_adjoint_k_eff/inputs_true.dat index 725702a4912..cd4e92aa1ba 100644 --- a/tests/regression_tests/random_ray_adjoint_k_eff/inputs_true.dat +++ b/tests/regression_tests/random_ray_adjoint_k_eff/inputs_true.dat @@ -3,32 +3,32 @@ mgxs.h5 - + - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + 0.126 0.126 10 10 @@ -53,23 +53,23 @@ 2 2 2 5 - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + eigenvalue @@ -80,7 +80,7 @@ 100.0 20.0 - + -1.26 -1.26 -1 1.26 1.26 1 diff --git a/tests/regression_tests/random_ray_auto_convert/infinite_medium/inputs_true.dat b/tests/regression_tests/random_ray_auto_convert/infinite_medium/inputs_true.dat index 02be4ea7a80..464c89a5df9 100644 --- a/tests/regression_tests/random_ray_auto_convert/infinite_medium/inputs_true.dat +++ b/tests/regression_tests/random_ray_auto_convert/infinite_medium/inputs_true.dat @@ -2,36 +2,36 @@ mgxs.h5 - - + + - + - + - - - - - - - - - + + + + + + + + + eigenvalue 100 10 5 - + -0.63 -0.63 -1 0.63 0.63 1 @@ -41,7 +41,7 @@ multi-group - + -0.63 -0.63 -1.0 0.63 0.63 1.0 diff --git a/tests/regression_tests/random_ray_auto_convert/material_wise/inputs_true.dat b/tests/regression_tests/random_ray_auto_convert/material_wise/inputs_true.dat index 02be4ea7a80..464c89a5df9 100644 --- a/tests/regression_tests/random_ray_auto_convert/material_wise/inputs_true.dat +++ b/tests/regression_tests/random_ray_auto_convert/material_wise/inputs_true.dat @@ -2,36 +2,36 @@ mgxs.h5 - - + + - + - + - - - - - - - - - + + + + + + + + + eigenvalue 100 10 5 - + -0.63 -0.63 -1 0.63 0.63 1 @@ -41,7 +41,7 @@ multi-group - + -0.63 -0.63 -1.0 0.63 0.63 1.0 diff --git a/tests/regression_tests/random_ray_auto_convert/stochastic_slab/inputs_true.dat b/tests/regression_tests/random_ray_auto_convert/stochastic_slab/inputs_true.dat index 02be4ea7a80..464c89a5df9 100644 --- a/tests/regression_tests/random_ray_auto_convert/stochastic_slab/inputs_true.dat +++ b/tests/regression_tests/random_ray_auto_convert/stochastic_slab/inputs_true.dat @@ -2,36 +2,36 @@ mgxs.h5 - - + + - + - + - - - - - - - - - + + + + + + + + + eigenvalue 100 10 5 - + -0.63 -0.63 -1 0.63 0.63 1 @@ -41,7 +41,7 @@ multi-group - + -0.63 -0.63 -1.0 0.63 0.63 1.0 diff --git a/tests/regression_tests/random_ray_diagonal_stabilization/inputs_true.dat b/tests/regression_tests/random_ray_diagonal_stabilization/inputs_true.dat index 4d4ed76dcaf..47325ebd7d5 100644 --- a/tests/regression_tests/random_ray_diagonal_stabilization/inputs_true.dat +++ b/tests/regression_tests/random_ray_diagonal_stabilization/inputs_true.dat @@ -2,36 +2,36 @@ mgxs.h5 - - + + - + - + - - - - - - - - - + + + + + + + + + eigenvalue 100 20 15 - + -0.63 -0.63 -1 0.63 0.63 1 @@ -41,7 +41,7 @@ multi-group - + -0.63 -0.63 -1.0 0.63 0.63 1.0 diff --git a/tests/regression_tests/random_ray_fixed_source_domain/cell/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_domain/cell/inputs_true.dat index d6ca8918cd0..4b8af76aaf6 100644 --- a/tests/regression_tests/random_ray_fixed_source_domain/cell/inputs_true.dat +++ b/tests/regression_tests/random_ray_fixed_source_domain/cell/inputs_true.dat @@ -3,24 +3,24 @@ mgxs.h5 - + - + - + - - - - - + + + + + 2.5 2.5 2.5 12 12 12 @@ -182,19 +182,19 @@ 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - - - - + + + + + + fixed source 90 10 5 - + 100.0 1.0 @@ -207,7 +207,7 @@ 500.0 100.0 - + 0.0 0.0 0.0 30.0 30.0 30.0 diff --git a/tests/regression_tests/random_ray_fixed_source_domain/material/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_domain/material/inputs_true.dat index 7fae491aea4..82fe48b614c 100644 --- a/tests/regression_tests/random_ray_fixed_source_domain/material/inputs_true.dat +++ b/tests/regression_tests/random_ray_fixed_source_domain/material/inputs_true.dat @@ -3,24 +3,24 @@ mgxs.h5 - + - + - + - - - - - + + + + + 2.5 2.5 2.5 12 12 12 @@ -182,19 +182,19 @@ 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - - - - + + + + + + fixed source 90 10 5 - + 100.0 1.0 @@ -207,7 +207,7 @@ 500.0 100.0 - + 0.0 0.0 0.0 30.0 30.0 30.0 diff --git a/tests/regression_tests/random_ray_fixed_source_domain/universe/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_domain/universe/inputs_true.dat index 75d84efe978..c4fd06f421e 100644 --- a/tests/regression_tests/random_ray_fixed_source_domain/universe/inputs_true.dat +++ b/tests/regression_tests/random_ray_fixed_source_domain/universe/inputs_true.dat @@ -3,24 +3,24 @@ mgxs.h5 - + - + - + - - - - - + + + + + 2.5 2.5 2.5 12 12 12 @@ -182,19 +182,19 @@ 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - - - - + + + + + + fixed source 90 10 5 - + 100.0 1.0 @@ -207,7 +207,7 @@ 500.0 100.0 - + 0.0 0.0 0.0 30.0 30.0 30.0 diff --git a/tests/regression_tests/random_ray_fixed_source_linear/linear/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_linear/linear/inputs_true.dat index 6ef3f08713b..4085b0c7a1f 100644 --- a/tests/regression_tests/random_ray_fixed_source_linear/linear/inputs_true.dat +++ b/tests/regression_tests/random_ray_fixed_source_linear/linear/inputs_true.dat @@ -3,24 +3,24 @@ mgxs.h5 - + - + - + - - - - - + + + + + 2.5 2.5 2.5 12 12 12 @@ -182,19 +182,19 @@ 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - - - - + + + + + + fixed source 90 40 20 - + 100.0 1.0 @@ -207,7 +207,7 @@ 500.0 100.0 - + 0.0 0.0 0.0 30.0 30.0 30.0 diff --git a/tests/regression_tests/random_ray_fixed_source_linear/linear_xy/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_linear/linear_xy/inputs_true.dat index 805c53fe674..ff085d2fbb6 100644 --- a/tests/regression_tests/random_ray_fixed_source_linear/linear_xy/inputs_true.dat +++ b/tests/regression_tests/random_ray_fixed_source_linear/linear_xy/inputs_true.dat @@ -3,24 +3,24 @@ mgxs.h5 - + - + - + - - - - - + + + + + 2.5 2.5 2.5 12 12 12 @@ -182,19 +182,19 @@ 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - - - - + + + + + + fixed source 90 40 20 - + 100.0 1.0 @@ -207,7 +207,7 @@ 500.0 100.0 - + 0.0 0.0 0.0 30.0 30.0 30.0 diff --git a/tests/regression_tests/random_ray_fixed_source_mesh/flat/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_mesh/flat/inputs_true.dat index 67c2ffc15e1..12c4d74edbf 100644 --- a/tests/regression_tests/random_ray_fixed_source_mesh/flat/inputs_true.dat +++ b/tests/regression_tests/random_ray_fixed_source_mesh/flat/inputs_true.dat @@ -3,24 +3,24 @@ mgxs.h5 - + - + - + - - - - - + + + + + 2.5 2.5 2.5 12 12 12 @@ -182,19 +182,19 @@ 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - - - - + + + + + + fixed source 90 30 15 - + 100.0 1.0 @@ -207,7 +207,7 @@ 500.0 100.0 - + 0.0 0.0 0.0 30.0 30.0 30.0 diff --git a/tests/regression_tests/random_ray_fixed_source_mesh/linear/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_mesh/linear/inputs_true.dat index 10b7c74f180..07adb1ff551 100644 --- a/tests/regression_tests/random_ray_fixed_source_mesh/linear/inputs_true.dat +++ b/tests/regression_tests/random_ray_fixed_source_mesh/linear/inputs_true.dat @@ -3,24 +3,24 @@ mgxs.h5 - + - + - + - - - - - + + + + + 2.5 2.5 2.5 12 12 12 @@ -182,19 +182,19 @@ 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - - - - + + + + + + fixed source 90 30 15 - + 100.0 1.0 @@ -207,7 +207,7 @@ 500.0 100.0 - + 0.0 0.0 0.0 30.0 30.0 30.0 diff --git a/tests/regression_tests/random_ray_fixed_source_normalization/False/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_normalization/False/inputs_true.dat index 559cff0f0df..ab077ae8aaf 100644 --- a/tests/regression_tests/random_ray_fixed_source_normalization/False/inputs_true.dat +++ b/tests/regression_tests/random_ray_fixed_source_normalization/False/inputs_true.dat @@ -3,24 +3,24 @@ mgxs.h5 - + - + - + - - - - - + + + + + 2.5 2.5 2.5 12 12 12 @@ -182,19 +182,19 @@ 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - - - - + + + + + + fixed source 90 10 5 - + 100.0 1.0 @@ -207,7 +207,7 @@ 500.0 100.0 - + 0.0 0.0 0.0 30.0 30.0 30.0 diff --git a/tests/regression_tests/random_ray_fixed_source_normalization/True/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_normalization/True/inputs_true.dat index 75d84efe978..c4fd06f421e 100644 --- a/tests/regression_tests/random_ray_fixed_source_normalization/True/inputs_true.dat +++ b/tests/regression_tests/random_ray_fixed_source_normalization/True/inputs_true.dat @@ -3,24 +3,24 @@ mgxs.h5 - + - + - + - - - - - + + + + + 2.5 2.5 2.5 12 12 12 @@ -182,19 +182,19 @@ 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - - - - + + + + + + fixed source 90 10 5 - + 100.0 1.0 @@ -207,7 +207,7 @@ 500.0 100.0 - + 0.0 0.0 0.0 30.0 30.0 30.0 diff --git a/tests/regression_tests/random_ray_fixed_source_subcritical/flat/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_subcritical/flat/inputs_true.dat index 0bf4cfdc90d..a124b5e3c45 100644 --- a/tests/regression_tests/random_ray_fixed_source_subcritical/flat/inputs_true.dat +++ b/tests/regression_tests/random_ray_fixed_source_subcritical/flat/inputs_true.dat @@ -3,34 +3,34 @@ mgxs.h5 - + - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + 0.126 0.126 10 10 @@ -71,30 +71,30 @@ 2 12 12 13 - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + fixed source 30 125 100 - + 1.134 -1.26 -1.0 1.26 -1.134 1.0 @@ -110,7 +110,7 @@ 40.0 40.0 - + -1.26 -1.26 -1 1.26 1.26 1 diff --git a/tests/regression_tests/random_ray_fixed_source_subcritical/linear_xy/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_subcritical/linear_xy/inputs_true.dat index f7572a07281..cc557afb6e4 100644 --- a/tests/regression_tests/random_ray_fixed_source_subcritical/linear_xy/inputs_true.dat +++ b/tests/regression_tests/random_ray_fixed_source_subcritical/linear_xy/inputs_true.dat @@ -3,34 +3,34 @@ mgxs.h5 - + - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + 0.126 0.126 10 10 @@ -71,30 +71,30 @@ 2 12 12 13 - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + fixed source 30 125 100 - + 1.134 -1.26 -1.0 1.26 -1.134 1.0 @@ -110,7 +110,7 @@ 40.0 40.0 - + -1.26 -1.26 -1 1.26 1.26 1 diff --git a/tests/regression_tests/random_ray_halton_samples/inputs_true.dat b/tests/regression_tests/random_ray_halton_samples/inputs_true.dat index 624ab495f41..3f058e45c21 100644 --- a/tests/regression_tests/random_ray_halton_samples/inputs_true.dat +++ b/tests/regression_tests/random_ray_halton_samples/inputs_true.dat @@ -3,32 +3,32 @@ mgxs.h5 - + - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + 0.126 0.126 10 10 @@ -53,23 +53,23 @@ 2 2 2 5 - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + eigenvalue @@ -80,7 +80,7 @@ 100.0 20.0 - + -1.26 -1.26 -1 1.26 1.26 1 diff --git a/tests/regression_tests/random_ray_k_eff/inputs_true.dat b/tests/regression_tests/random_ray_k_eff/inputs_true.dat index a2b058f2bdb..33c9cac339c 100644 --- a/tests/regression_tests/random_ray_k_eff/inputs_true.dat +++ b/tests/regression_tests/random_ray_k_eff/inputs_true.dat @@ -3,32 +3,32 @@ mgxs.h5 - + - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + 0.126 0.126 10 10 @@ -53,23 +53,23 @@ 2 2 2 5 - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + eigenvalue @@ -80,7 +80,7 @@ 100.0 20.0 - + -1.26 -1.26 -1 1.26 1.26 1 diff --git a/tests/regression_tests/random_ray_k_eff_mesh/inputs_true.dat b/tests/regression_tests/random_ray_k_eff_mesh/inputs_true.dat index cdc717198f3..f0822d9f964 100644 --- a/tests/regression_tests/random_ray_k_eff_mesh/inputs_true.dat +++ b/tests/regression_tests/random_ray_k_eff_mesh/inputs_true.dat @@ -3,32 +3,32 @@ mgxs.h5 - + - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + 0.126 0.126 10 10 @@ -53,23 +53,23 @@ 2 2 2 5 - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + eigenvalue @@ -80,7 +80,7 @@ 100.0 20.0 - + -1.26 -1.26 -1 1.26 1.26 1 diff --git a/tests/regression_tests/random_ray_linear/linear/inputs_true.dat b/tests/regression_tests/random_ray_linear/linear/inputs_true.dat index 4df51bad5ba..00699655722 100644 --- a/tests/regression_tests/random_ray_linear/linear/inputs_true.dat +++ b/tests/regression_tests/random_ray_linear/linear/inputs_true.dat @@ -3,32 +3,32 @@ mgxs.h5 - + - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + 0.126 0.126 10 10 @@ -53,23 +53,23 @@ 2 2 2 5 - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + eigenvalue @@ -80,7 +80,7 @@ 100.0 20.0 - + -1.26 -1.26 -1 1.26 1.26 1 diff --git a/tests/regression_tests/random_ray_linear/linear_xy/inputs_true.dat b/tests/regression_tests/random_ray_linear/linear_xy/inputs_true.dat index 113771438c4..81527c4793f 100644 --- a/tests/regression_tests/random_ray_linear/linear_xy/inputs_true.dat +++ b/tests/regression_tests/random_ray_linear/linear_xy/inputs_true.dat @@ -3,32 +3,32 @@ mgxs.h5 - + - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + 0.126 0.126 10 10 @@ -53,23 +53,23 @@ 2 2 2 5 - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + eigenvalue @@ -80,7 +80,7 @@ 100.0 20.0 - + -1.26 -1.26 -1 1.26 1.26 1 diff --git a/tests/regression_tests/random_ray_point_source_locator/inputs_true.dat b/tests/regression_tests/random_ray_point_source_locator/inputs_true.dat index 9c7cd4ab5b8..82ad979da89 100644 --- a/tests/regression_tests/random_ray_point_source_locator/inputs_true.dat +++ b/tests/regression_tests/random_ray_point_source_locator/inputs_true.dat @@ -3,24 +3,24 @@ mgxs.h5 - + - + - + - - - - - + + + + + 2.5 2.5 2.5 12 12 12 @@ -182,19 +182,19 @@ 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - - - - + + + + + + fixed source 90 30 15 - + 2.5 2.5 2.5 @@ -206,7 +206,7 @@ 500.0 100.0 - + 0.0 0.0 0.0 30.0 30.0 30.0 diff --git a/tests/regression_tests/random_ray_void/flat/inputs_true.dat b/tests/regression_tests/random_ray_void/flat/inputs_true.dat index 617a6605374..ea8c22f0b54 100644 --- a/tests/regression_tests/random_ray_void/flat/inputs_true.dat +++ b/tests/regression_tests/random_ray_void/flat/inputs_true.dat @@ -3,24 +3,24 @@ mgxs.h5 - + - + - + - - - - - + + + + + 2.5 2.5 2.5 12 12 12 @@ -182,19 +182,19 @@ 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - - - - + + + + + + fixed source 90 40 20 - + 100.0 1.0 @@ -207,7 +207,7 @@ 500.0 100.0 - + 0.0 0.0 0.0 30.0 30.0 30.0 diff --git a/tests/regression_tests/random_ray_void/linear/inputs_true.dat b/tests/regression_tests/random_ray_void/linear/inputs_true.dat index 4b6a682d9db..a089604ef4e 100644 --- a/tests/regression_tests/random_ray_void/linear/inputs_true.dat +++ b/tests/regression_tests/random_ray_void/linear/inputs_true.dat @@ -3,24 +3,24 @@ mgxs.h5 - + - + - + - - - - - + + + + + 2.5 2.5 2.5 12 12 12 @@ -182,19 +182,19 @@ 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - - - - + + + + + + fixed source 90 40 20 - + 100.0 1.0 @@ -207,7 +207,7 @@ 500.0 100.0 - + 0.0 0.0 0.0 30.0 30.0 30.0 diff --git a/tests/regression_tests/random_ray_volume_estimator/hybrid/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator/hybrid/inputs_true.dat index 9c15ec97db0..089534d744a 100644 --- a/tests/regression_tests/random_ray_volume_estimator/hybrid/inputs_true.dat +++ b/tests/regression_tests/random_ray_volume_estimator/hybrid/inputs_true.dat @@ -3,24 +3,24 @@ mgxs.h5 - + - + - + - - - - - + + + + + 2.5 2.5 2.5 12 12 12 @@ -182,19 +182,19 @@ 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - - - - + + + + + + fixed source 90 10 5 - + 100.0 1.0 @@ -207,7 +207,7 @@ 500.0 100.0 - + 0.0 0.0 0.0 30.0 30.0 30.0 diff --git a/tests/regression_tests/random_ray_volume_estimator/naive/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator/naive/inputs_true.dat index 7d05f0978da..56507df045e 100644 --- a/tests/regression_tests/random_ray_volume_estimator/naive/inputs_true.dat +++ b/tests/regression_tests/random_ray_volume_estimator/naive/inputs_true.dat @@ -3,24 +3,24 @@ mgxs.h5 - + - + - + - - - - - + + + + + 2.5 2.5 2.5 12 12 12 @@ -182,19 +182,19 @@ 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - - - - + + + + + + fixed source 90 10 5 - + 100.0 1.0 @@ -207,7 +207,7 @@ 500.0 100.0 - + 0.0 0.0 0.0 30.0 30.0 30.0 diff --git a/tests/regression_tests/random_ray_volume_estimator/simulation_averaged/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator/simulation_averaged/inputs_true.dat index 301671e6d45..0331b562cdb 100644 --- a/tests/regression_tests/random_ray_volume_estimator/simulation_averaged/inputs_true.dat +++ b/tests/regression_tests/random_ray_volume_estimator/simulation_averaged/inputs_true.dat @@ -3,24 +3,24 @@ mgxs.h5 - + - + - + - - - - - + + + + + 2.5 2.5 2.5 12 12 12 @@ -182,19 +182,19 @@ 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - - - - + + + + + + fixed source 90 10 5 - + 100.0 1.0 @@ -207,7 +207,7 @@ 500.0 100.0 - + 0.0 0.0 0.0 30.0 30.0 30.0 diff --git a/tests/regression_tests/random_ray_volume_estimator_linear/hybrid/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator_linear/hybrid/inputs_true.dat index 6440cca04a0..343833210e9 100644 --- a/tests/regression_tests/random_ray_volume_estimator_linear/hybrid/inputs_true.dat +++ b/tests/regression_tests/random_ray_volume_estimator_linear/hybrid/inputs_true.dat @@ -3,24 +3,24 @@ mgxs.h5 - + - + - + - - - - - + + + + + 2.5 2.5 2.5 12 12 12 @@ -182,19 +182,19 @@ 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - - - - + + + + + + fixed source 90 40 20 - + 100.0 1.0 @@ -207,7 +207,7 @@ 500.0 100.0 - + 0.0 0.0 0.0 30.0 30.0 30.0 diff --git a/tests/regression_tests/random_ray_volume_estimator_linear/naive/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator_linear/naive/inputs_true.dat index fcf93b046d0..50be43eb386 100644 --- a/tests/regression_tests/random_ray_volume_estimator_linear/naive/inputs_true.dat +++ b/tests/regression_tests/random_ray_volume_estimator_linear/naive/inputs_true.dat @@ -3,24 +3,24 @@ mgxs.h5 - + - + - + - - - - - + + + + + 2.5 2.5 2.5 12 12 12 @@ -182,19 +182,19 @@ 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - - - - + + + + + + fixed source 90 40 20 - + 100.0 1.0 @@ -207,7 +207,7 @@ 500.0 100.0 - + 0.0 0.0 0.0 30.0 30.0 30.0 diff --git a/tests/regression_tests/random_ray_volume_estimator_linear/simulation_averaged/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator_linear/simulation_averaged/inputs_true.dat index 1df3204a370..3d64ab97837 100644 --- a/tests/regression_tests/random_ray_volume_estimator_linear/simulation_averaged/inputs_true.dat +++ b/tests/regression_tests/random_ray_volume_estimator_linear/simulation_averaged/inputs_true.dat @@ -3,24 +3,24 @@ mgxs.h5 - + - + - + - - - - - + + + + + 2.5 2.5 2.5 12 12 12 @@ -182,19 +182,19 @@ 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - - - - + + + + + + fixed source 90 40 20 - + 100.0 1.0 @@ -207,7 +207,7 @@ 500.0 100.0 - + 0.0 0.0 0.0 30.0 30.0 30.0 diff --git a/tests/regression_tests/resonance_scattering/inputs_true.dat b/tests/regression_tests/resonance_scattering/inputs_true.dat index dba4535c2b4..ebe6a5dbe89 100644 --- a/tests/regression_tests/resonance_scattering/inputs_true.dat +++ b/tests/regression_tests/resonance_scattering/inputs_true.dat @@ -1,24 +1,24 @@ - - - - - - + + + + + + - + eigenvalue 1000 10 5 - + -4 -4 -4 4 4 4 diff --git a/tests/regression_tests/salphabeta/inputs_true.dat b/tests/regression_tests/salphabeta/inputs_true.dat index ef0e843c2f9..56f4be37593 100644 --- a/tests/regression_tests/salphabeta/inputs_true.dat +++ b/tests/regression_tests/salphabeta/inputs_true.dat @@ -1,36 +1,36 @@ - - - - - + + + + + - - - - + + + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + @@ -40,18 +40,18 @@ - - - - - + + + + + eigenvalue 400 5 0 - + -4 -4 -4 4 4 4 diff --git a/tests/regression_tests/score_current/inputs_true.dat b/tests/regression_tests/score_current/inputs_true.dat index ddbd6c24b05..42c2d3df2e6 100644 --- a/tests/regression_tests/score_current/inputs_true.dat +++ b/tests/regression_tests/score_current/inputs_true.dat @@ -1,28 +1,28 @@ - - - + + + - - + + - - - - - - - - - - + + + + + + + + + + eigenvalue diff --git a/tests/regression_tests/source/inputs_true.dat b/tests/regression_tests/source/inputs_true.dat index e787f24d413..9f10b79d6b1 100644 --- a/tests/regression_tests/source/inputs_true.dat +++ b/tests/regression_tests/source/inputs_true.dat @@ -1,150 +1,150 @@ - - - + + + - + eigenvalue 1000 10 5 - + - + -4.0 -1.0 3.0 0.2 0.3 0.5 - + -2.0 0.0 2.0 0.2 0.3 0.2 - + -1.0 0.0 1.0 0.5 0.25 0.25 - + - + - + -4.0 -4.0 -4.0 4.0 4.0 4.0 - - + + - + 1.2 -2.3 0.781 - + 1.0 1.3894954943731377 1.93069772888325 2.6826957952797255 3.72759372031494 5.17947467923121 7.196856730011519 10.0 13.894954943731374 19.306977288832496 26.826957952797247 37.2759372031494 51.7947467923121 71.96856730011518 100.0 138.94954943731375 193.06977288832496 268.26957952797244 372.7593720314938 517.9474679231207 719.6856730011514 1000.0 1389.4954943731375 1930.6977288832495 2682.6957952797247 3727.593720314938 5179.474679231207 7196.856730011514 10000.0 13894.95494373136 19306.977288832495 26826.95795279722 37275.93720314938 51794.74679231213 71968.56730011514 100000.0 138949.5494373136 193069.77288832495 268269.5795279722 372759.3720314938 517947.4679231202 719685.6730011514 1000000.0 1389495.494373136 1930697.7288832497 2682695.7952797217 3727593.720314938 5179474.679231202 7196856.730011513 10000000.0 0.0 2.9086439299358713e-08 5.80533561806147e-08 8.67817193689187e-08 1.1515347785771536e-07 1.4305204600565115e-07 1.7036278261198208e-07 1.9697346200185813e-07 2.227747351856934e-07 2.4766057919761985e-07 2.715287327665956e-07 2.9428111652990295e-07 3.1582423606228735e-07 3.360695660646056e-07 3.549339141332686e-07 3.723397626156721e-07 3.882155871468592e-07 4.024961505584776e-07 4.151227709522976e-07 4.260435628367196e-07 4.3521365033538783e-07 4.4259535159179273e-07 4.4815833361210174e-07 4.5187973690993757e-07 4.5374426944091084e-07 4.5374426944091084e-07 4.5187973690993757e-07 4.4815833361210174e-07 4.4259535159179273e-07 4.352136503353879e-07 4.2604356283671966e-07 4.1512277095229767e-07 4.0249615055847764e-07 3.8821558714685926e-07 3.723397626156722e-07 3.5493391413326864e-07 3.360695660646057e-07 3.158242360622874e-07 2.942811165299031e-07 2.715287327665957e-07 2.4766057919762e-07 2.2277473518569352e-07 1.9697346200185819e-07 1.7036278261198226e-07 1.4305204600565126e-07 1.1515347785771556e-07 8.678171936891881e-08 5.805335618061493e-08 2.9086439299358858e-08 5.559621115282002e-23 - - - + + + 0.7071067811865476 0.0 -0.7071067811865475 0.3 0.4 0.3 - + - + 1.0 1.3894954943731377 1.93069772888325 2.6826957952797255 3.72759372031494 5.17947467923121 7.196856730011519 10.0 13.894954943731374 19.306977288832496 26.826957952797247 37.2759372031494 51.7947467923121 71.96856730011518 100.0 138.94954943731375 193.06977288832496 268.26957952797244 372.7593720314938 517.9474679231207 719.6856730011514 1000.0 1389.4954943731375 1930.6977288832495 2682.6957952797247 3727.593720314938 5179.474679231207 7196.856730011514 10000.0 13894.95494373136 19306.977288832495 26826.95795279722 37275.93720314938 51794.74679231213 71968.56730011514 100000.0 138949.5494373136 193069.77288832495 268269.5795279722 372759.3720314938 517947.4679231202 719685.6730011514 1000000.0 1389495.494373136 1930697.7288832497 2682695.7952797217 3727593.720314938 5179474.679231202 7196856.730011513 10000000.0 0.0 2.9086439299358713e-08 5.80533561806147e-08 8.67817193689187e-08 1.1515347785771536e-07 1.4305204600565115e-07 1.7036278261198208e-07 1.9697346200185813e-07 2.227747351856934e-07 2.4766057919761985e-07 2.715287327665956e-07 2.9428111652990295e-07 3.1582423606228735e-07 3.360695660646056e-07 3.549339141332686e-07 3.723397626156721e-07 3.882155871468592e-07 4.024961505584776e-07 4.151227709522976e-07 4.260435628367196e-07 4.3521365033538783e-07 4.4259535159179273e-07 4.4815833361210174e-07 4.5187973690993757e-07 4.5374426944091084e-07 4.5374426944091084e-07 4.5187973690993757e-07 4.4815833361210174e-07 4.4259535159179273e-07 4.352136503353879e-07 4.2604356283671966e-07 4.1512277095229767e-07 4.0249615055847764e-07 3.8821558714685926e-07 3.723397626156722e-07 3.5493391413326864e-07 3.360695660646057e-07 3.158242360622874e-07 2.942811165299031e-07 2.715287327665957e-07 2.4766057919762e-07 2.2277473518569352e-07 1.9697346200185819e-07 1.7036278261198226e-07 1.4305204600565126e-07 1.1515347785771556e-07 8.678171936891881e-08 5.805335618061493e-08 2.9086439299358858e-08 5.559621115282002e-23 - - - - - + + + + + -2.0 0.0 2.0 0.2 0.3 0.2 - + 1.0 1.3894954943731377 1.93069772888325 2.6826957952797255 3.72759372031494 5.17947467923121 7.196856730011519 10.0 13.894954943731374 19.306977288832496 26.826957952797247 37.2759372031494 51.7947467923121 71.96856730011518 100.0 138.94954943731375 193.06977288832496 268.26957952797244 372.7593720314938 517.9474679231207 719.6856730011514 1000.0 1389.4954943731375 1930.6977288832495 2682.6957952797247 3727.593720314938 5179.474679231207 7196.856730011514 10000.0 13894.95494373136 19306.977288832495 26826.95795279722 37275.93720314938 51794.74679231213 71968.56730011514 100000.0 138949.5494373136 193069.77288832495 268269.5795279722 372759.3720314938 517947.4679231202 719685.6730011514 1000000.0 1389495.494373136 1930697.7288832497 2682695.7952797217 3727593.720314938 5179474.679231202 7196856.730011513 10000000.0 0.0 2.9086439299358713e-08 5.80533561806147e-08 8.67817193689187e-08 1.1515347785771536e-07 1.4305204600565115e-07 1.7036278261198208e-07 1.9697346200185813e-07 2.227747351856934e-07 2.4766057919761985e-07 2.715287327665956e-07 2.9428111652990295e-07 3.1582423606228735e-07 3.360695660646056e-07 3.549339141332686e-07 3.723397626156721e-07 3.882155871468592e-07 4.024961505584776e-07 4.151227709522976e-07 4.260435628367196e-07 4.3521365033538783e-07 4.4259535159179273e-07 4.4815833361210174e-07 4.5187973690993757e-07 4.5374426944091084e-07 4.5374426944091084e-07 4.5187973690993757e-07 4.4815833361210174e-07 4.4259535159179273e-07 4.352136503353879e-07 4.2604356283671966e-07 4.1512277095229767e-07 4.0249615055847764e-07 3.8821558714685926e-07 3.723397626156722e-07 3.5493391413326864e-07 3.360695660646057e-07 3.158242360622874e-07 2.942811165299031e-07 2.715287327665957e-07 2.4766057919762e-07 2.2277473518569352e-07 1.9697346200185819e-07 1.7036278261198226e-07 1.4305204600565126e-07 1.1515347785771556e-07 8.678171936891881e-08 5.805335618061493e-08 2.9086439299358858e-08 5.559621115282002e-23 - - - - - + + + + + -2.0 0.0 2.0 0.2 0.3 0.2 - + - + - + 1.0 1.3894954943731377 1.93069772888325 2.6826957952797255 3.72759372031494 5.17947467923121 7.196856730011519 10.0 13.894954943731374 19.306977288832496 26.826957952797247 37.2759372031494 51.7947467923121 71.96856730011518 100.0 138.94954943731375 193.06977288832496 268.26957952797244 372.7593720314938 517.9474679231207 719.6856730011514 1000.0 1389.4954943731375 1930.6977288832495 2682.6957952797247 3727.593720314938 5179.474679231207 7196.856730011514 10000.0 13894.95494373136 19306.977288832495 26826.95795279722 37275.93720314938 51794.74679231213 71968.56730011514 100000.0 138949.5494373136 193069.77288832495 268269.5795279722 372759.3720314938 517947.4679231202 719685.6730011514 1000000.0 1389495.494373136 1930697.7288832497 2682695.7952797217 3727593.720314938 5179474.679231202 7196856.730011513 10000000.0 0.0 2.9086439299358713e-08 5.80533561806147e-08 8.67817193689187e-08 1.1515347785771536e-07 1.4305204600565115e-07 1.7036278261198208e-07 1.9697346200185813e-07 2.227747351856934e-07 2.4766057919761985e-07 2.715287327665956e-07 2.9428111652990295e-07 3.1582423606228735e-07 3.360695660646056e-07 3.549339141332686e-07 3.723397626156721e-07 3.882155871468592e-07 4.024961505584776e-07 4.151227709522976e-07 4.260435628367196e-07 4.3521365033538783e-07 4.4259535159179273e-07 4.4815833361210174e-07 4.5187973690993757e-07 4.5374426944091084e-07 4.5374426944091084e-07 4.5187973690993757e-07 4.4815833361210174e-07 4.4259535159179273e-07 4.352136503353879e-07 4.2604356283671966e-07 4.1512277095229767e-07 4.0249615055847764e-07 3.8821558714685926e-07 3.723397626156722e-07 3.5493391413326864e-07 3.360695660646057e-07 3.158242360622874e-07 2.942811165299031e-07 2.715287327665957e-07 2.4766057919762e-07 2.2277473518569352e-07 1.9697346200185819e-07 1.7036278261198226e-07 1.4305204600565126e-07 1.1515347785771556e-07 8.678171936891881e-08 5.805335618061493e-08 2.9086439299358858e-08 5.559621115282002e-23 - - - + + + 0.7071067811865476 0.0 -0.7071067811865475 0.3 0.4 0.3 - + - + - + - + 1.0 1.3894954943731377 1.93069772888325 2.6826957952797255 3.72759372031494 5.17947467923121 7.196856730011519 10.0 13.894954943731374 19.306977288832496 26.826957952797247 37.2759372031494 51.7947467923121 71.96856730011518 100.0 138.94954943731375 193.06977288832496 268.26957952797244 372.7593720314938 517.9474679231207 719.6856730011514 1000.0 1389.4954943731375 1930.6977288832495 2682.6957952797247 3727.593720314938 5179.474679231207 7196.856730011514 10000.0 13894.95494373136 19306.977288832495 26826.95795279722 37275.93720314938 51794.74679231213 71968.56730011514 100000.0 138949.5494373136 193069.77288832495 268269.5795279722 372759.3720314938 517947.4679231202 719685.6730011514 1000000.0 1389495.494373136 1930697.7288832497 2682695.7952797217 3727593.720314938 5179474.679231202 7196856.730011513 10000000.0 0.0 2.9086439299358713e-08 5.80533561806147e-08 8.67817193689187e-08 1.1515347785771536e-07 1.4305204600565115e-07 1.7036278261198208e-07 1.9697346200185813e-07 2.227747351856934e-07 2.4766057919761985e-07 2.715287327665956e-07 2.9428111652990295e-07 3.1582423606228735e-07 3.360695660646056e-07 3.549339141332686e-07 3.723397626156721e-07 3.882155871468592e-07 4.024961505584776e-07 4.151227709522976e-07 4.260435628367196e-07 4.3521365033538783e-07 4.4259535159179273e-07 4.4815833361210174e-07 4.5187973690993757e-07 4.5374426944091084e-07 4.5374426944091084e-07 4.5187973690993757e-07 4.4815833361210174e-07 4.4259535159179273e-07 4.352136503353879e-07 4.2604356283671966e-07 4.1512277095229767e-07 4.0249615055847764e-07 3.8821558714685926e-07 3.723397626156722e-07 3.5493391413326864e-07 3.360695660646057e-07 3.158242360622874e-07 2.942811165299031e-07 2.715287327665957e-07 2.4766057919762e-07 2.2277473518569352e-07 1.9697346200185819e-07 1.7036278261198226e-07 1.4305204600565126e-07 1.1515347785771556e-07 8.678171936891881e-08 5.805335618061493e-08 2.9086439299358858e-08 5.559621115282002e-23 - diff --git a/tests/regression_tests/source_dlopen/inputs_true.dat b/tests/regression_tests/source_dlopen/inputs_true.dat index 28d1e06e2bd..9b4601d9f34 100644 --- a/tests/regression_tests/source_dlopen/inputs_true.dat +++ b/tests/regression_tests/source_dlopen/inputs_true.dat @@ -2,23 +2,23 @@ - - - - - + + + + + - + fixed source 1000 10 0 - + diff --git a/tests/regression_tests/source_parameterized_dlopen/inputs_true.dat b/tests/regression_tests/source_parameterized_dlopen/inputs_true.dat index 6cf3473f9f7..088d65ada3e 100644 --- a/tests/regression_tests/source_parameterized_dlopen/inputs_true.dat +++ b/tests/regression_tests/source_parameterized_dlopen/inputs_true.dat @@ -2,23 +2,23 @@ - - - - - + + + + + - + fixed source 1000 10 0 - + diff --git a/tests/regression_tests/stride/inputs_true.dat b/tests/regression_tests/stride/inputs_true.dat index f93ec33d100..ebae53c05a7 100644 --- a/tests/regression_tests/stride/inputs_true.dat +++ b/tests/regression_tests/stride/inputs_true.dat @@ -1,21 +1,21 @@ - - - + + + - + eigenvalue 1000 10 5 - + -4 -4 -4 4 4 4 diff --git a/tests/regression_tests/surface_source/inputs_true_read_h5.dat b/tests/regression_tests/surface_source/inputs_true_read_h5.dat index 14a55f0bf04..321c11d42bb 100644 --- a/tests/regression_tests/surface_source/inputs_true_read_h5.dat +++ b/tests/regression_tests/surface_source/inputs_true_read_h5.dat @@ -7,10 +7,10 @@ - - - - + + + + fixed source diff --git a/tests/regression_tests/surface_source/inputs_true_read_mcpl.dat b/tests/regression_tests/surface_source/inputs_true_read_mcpl.dat index b8d8c575aac..2ee76f618db 100644 --- a/tests/regression_tests/surface_source/inputs_true_read_mcpl.dat +++ b/tests/regression_tests/surface_source/inputs_true_read_mcpl.dat @@ -7,10 +7,10 @@ - - - - + + + + fixed source diff --git a/tests/regression_tests/surface_source/inputs_true_write_h5.dat b/tests/regression_tests/surface_source/inputs_true_write_h5.dat index 5c85de6318b..10e3af0a7ea 100644 --- a/tests/regression_tests/surface_source/inputs_true_write_h5.dat +++ b/tests/regression_tests/surface_source/inputs_true_write_h5.dat @@ -7,16 +7,16 @@ - - - - + + + + fixed source 1000 10 - + 0 0 0 diff --git a/tests/regression_tests/surface_source/inputs_true_write_mcpl.dat b/tests/regression_tests/surface_source/inputs_true_write_mcpl.dat index c55223324e0..e9758144d90 100644 --- a/tests/regression_tests/surface_source/inputs_true_write_mcpl.dat +++ b/tests/regression_tests/surface_source/inputs_true_write_mcpl.dat @@ -7,16 +7,16 @@ - - - - + + + + fixed source 1000 10 - + 0 0 0 diff --git a/tests/regression_tests/surface_source_write/case-01/inputs_true.dat b/tests/regression_tests/surface_source_write/case-01/inputs_true.dat index 7368debd5f7..2a67b03dd2f 100644 --- a/tests/regression_tests/surface_source_write/case-01/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-01/inputs_true.dat @@ -1,47 +1,47 @@ - - - - - - - + + + + + + + - - - + + + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + eigenvalue 100 5 1 - + -2.0 -2.0 -2.0 2.0 2.0 2.0 diff --git a/tests/regression_tests/surface_source_write/case-02/inputs_true.dat b/tests/regression_tests/surface_source_write/case-02/inputs_true.dat index 12f15499f20..527f076b9c0 100644 --- a/tests/regression_tests/surface_source_write/case-02/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-02/inputs_true.dat @@ -1,47 +1,47 @@ - - - - - - - + + + + + + + - - - + + + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + eigenvalue 100 5 1 - + -2.0 -2.0 -2.0 2.0 2.0 2.0 diff --git a/tests/regression_tests/surface_source_write/case-03/inputs_true.dat b/tests/regression_tests/surface_source_write/case-03/inputs_true.dat index cd0f7deda59..58c4e0a240a 100644 --- a/tests/regression_tests/surface_source_write/case-03/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-03/inputs_true.dat @@ -1,47 +1,47 @@ - - - - - - - + + + + + + + - - - + + + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + eigenvalue 100 5 1 - + -2.0 -2.0 -2.0 2.0 2.0 2.0 diff --git a/tests/regression_tests/surface_source_write/case-04/inputs_true.dat b/tests/regression_tests/surface_source_write/case-04/inputs_true.dat index ac3c03e37e0..46701aea7ee 100644 --- a/tests/regression_tests/surface_source_write/case-04/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-04/inputs_true.dat @@ -1,47 +1,47 @@ - - - - - - - + + + + + + + - - - + + + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + eigenvalue 100 5 1 - + -2.0 -2.0 -2.0 2.0 2.0 2.0 diff --git a/tests/regression_tests/surface_source_write/case-05/inputs_true.dat b/tests/regression_tests/surface_source_write/case-05/inputs_true.dat index 143e8520074..c420d797ce3 100644 --- a/tests/regression_tests/surface_source_write/case-05/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-05/inputs_true.dat @@ -1,47 +1,47 @@ - - - - - - - + + + + + + + - - - + + + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + eigenvalue 100 5 1 - + -2.0 -2.0 -2.0 2.0 2.0 2.0 diff --git a/tests/regression_tests/surface_source_write/case-06/inputs_true.dat b/tests/regression_tests/surface_source_write/case-06/inputs_true.dat index f2dc7ea35d6..e02d5e90c12 100644 --- a/tests/regression_tests/surface_source_write/case-06/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-06/inputs_true.dat @@ -1,47 +1,47 @@ - - - - - - - + + + + + + + - - - + + + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + eigenvalue 100 5 1 - + -2.0 -2.0 -2.0 2.0 2.0 2.0 diff --git a/tests/regression_tests/surface_source_write/case-07/inputs_true.dat b/tests/regression_tests/surface_source_write/case-07/inputs_true.dat index a6107d16faa..a4588c8d083 100644 --- a/tests/regression_tests/surface_source_write/case-07/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-07/inputs_true.dat @@ -1,47 +1,47 @@ - - - - - - - + + + + + + + - - - + + + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + eigenvalue 100 5 1 - + -2.0 -2.0 -2.0 2.0 2.0 2.0 diff --git a/tests/regression_tests/surface_source_write/case-08/inputs_true.dat b/tests/regression_tests/surface_source_write/case-08/inputs_true.dat index b87e2b2076d..ecf3a6a2e50 100644 --- a/tests/regression_tests/surface_source_write/case-08/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-08/inputs_true.dat @@ -1,47 +1,47 @@ - - - - - - - + + + + + + + - - - + + + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + eigenvalue 100 5 1 - + -2.0 -2.0 -2.0 2.0 2.0 2.0 diff --git a/tests/regression_tests/surface_source_write/case-09/inputs_true.dat b/tests/regression_tests/surface_source_write/case-09/inputs_true.dat index f4f6ebd87cf..5d60f9dbeb3 100644 --- a/tests/regression_tests/surface_source_write/case-09/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-09/inputs_true.dat @@ -1,47 +1,47 @@ - - - - - - - + + + + + + + - - - + + + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + eigenvalue 100 5 1 - + -2.0 -2.0 -2.0 2.0 2.0 2.0 diff --git a/tests/regression_tests/surface_source_write/case-10/inputs_true.dat b/tests/regression_tests/surface_source_write/case-10/inputs_true.dat index 372e5c0116f..1940826c23b 100644 --- a/tests/regression_tests/surface_source_write/case-10/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-10/inputs_true.dat @@ -1,47 +1,47 @@ - - - - - - - + + + + + + + - - - + + + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + eigenvalue 100 5 1 - + -2.0 -2.0 -2.0 2.0 2.0 2.0 diff --git a/tests/regression_tests/surface_source_write/case-11/inputs_true.dat b/tests/regression_tests/surface_source_write/case-11/inputs_true.dat index d5401dc873f..a4feff1b1fe 100644 --- a/tests/regression_tests/surface_source_write/case-11/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-11/inputs_true.dat @@ -1,47 +1,47 @@ - - - - - - - + + + + + + + - - - + + + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + eigenvalue 100 5 1 - + -2.0 -2.0 -2.0 2.0 2.0 2.0 diff --git a/tests/regression_tests/surface_source_write/case-12/inputs_true.dat b/tests/regression_tests/surface_source_write/case-12/inputs_true.dat index cb8f877416c..c069f425c73 100644 --- a/tests/regression_tests/surface_source_write/case-12/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-12/inputs_true.dat @@ -1,40 +1,40 @@ - - - - - - - + + + + + + + - - - + + + - - - - - - - - - - + + + + + + + + + + eigenvalue 100 5 1 - + -2.0 -2.0 -2.0 2.0 2.0 2.0 diff --git a/tests/regression_tests/surface_source_write/case-13/inputs_true.dat b/tests/regression_tests/surface_source_write/case-13/inputs_true.dat index 23bbf0455eb..2a93fdb4d62 100644 --- a/tests/regression_tests/surface_source_write/case-13/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-13/inputs_true.dat @@ -1,40 +1,40 @@ - - - - - - - + + + + + + + - - - + + + - - - - - - - - - - + + + + + + + + + + eigenvalue 100 5 1 - + -2.0 -2.0 -2.0 2.0 2.0 2.0 diff --git a/tests/regression_tests/surface_source_write/case-14/inputs_true.dat b/tests/regression_tests/surface_source_write/case-14/inputs_true.dat index 29acd94f250..893f8ddc153 100644 --- a/tests/regression_tests/surface_source_write/case-14/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-14/inputs_true.dat @@ -1,40 +1,40 @@ - - - - - - - + + + + + + + - - - + + + - - - - - - - - - - + + + + + + + + + + eigenvalue 100 5 1 - + -2.0 -2.0 -2.0 2.0 2.0 2.0 diff --git a/tests/regression_tests/surface_source_write/case-15/inputs_true.dat b/tests/regression_tests/surface_source_write/case-15/inputs_true.dat index c775f2f5276..875a2fb0bb9 100644 --- a/tests/regression_tests/surface_source_write/case-15/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-15/inputs_true.dat @@ -1,40 +1,40 @@ - - - - - - - + + + + + + + - - - + + + - - - - - - - - - - + + + + + + + + + + eigenvalue 100 5 1 - + -2.0 -2.0 -2.0 2.0 2.0 2.0 diff --git a/tests/regression_tests/surface_source_write/case-16/inputs_true.dat b/tests/regression_tests/surface_source_write/case-16/inputs_true.dat index 969a8f31ea8..347855e9024 100644 --- a/tests/regression_tests/surface_source_write/case-16/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-16/inputs_true.dat @@ -1,40 +1,40 @@ - - - - - - - + + + + + + + - - - + + + - - - - - - - - - - + + + + + + + + + + eigenvalue 100 5 1 - + -2.0 -2.0 -2.0 2.0 2.0 2.0 diff --git a/tests/regression_tests/surface_source_write/case-17/inputs_true.dat b/tests/regression_tests/surface_source_write/case-17/inputs_true.dat index 0a65db51008..95d0a67124a 100644 --- a/tests/regression_tests/surface_source_write/case-17/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-17/inputs_true.dat @@ -1,40 +1,40 @@ - - - - - - - + + + + + + + - - - + + + - - - - - - - - - - + + + + + + + + + + eigenvalue 100 5 1 - + -2.0 -2.0 -2.0 2.0 2.0 2.0 diff --git a/tests/regression_tests/surface_source_write/case-18/inputs_true.dat b/tests/regression_tests/surface_source_write/case-18/inputs_true.dat index 89df27b475b..807c72ae65a 100644 --- a/tests/regression_tests/surface_source_write/case-18/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-18/inputs_true.dat @@ -1,40 +1,40 @@ - - - - - - - + + + + + + + - - - + + + - - - - - - - - - - + + + + + + + + + + eigenvalue 100 5 1 - + -2.0 -2.0 -2.0 2.0 2.0 2.0 diff --git a/tests/regression_tests/surface_source_write/case-19/inputs_true.dat b/tests/regression_tests/surface_source_write/case-19/inputs_true.dat index 3ea7dbffe38..42aa78a096d 100644 --- a/tests/regression_tests/surface_source_write/case-19/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-19/inputs_true.dat @@ -1,40 +1,40 @@ - - - - - - - + + + + + + + - - - + + + - - - - - - - - - - + + + + + + + + + + eigenvalue 100 5 1 - + -2.0 -2.0 -2.0 2.0 2.0 2.0 diff --git a/tests/regression_tests/surface_source_write/case-20/inputs_true.dat b/tests/regression_tests/surface_source_write/case-20/inputs_true.dat index c3c768a3da9..4c7a3f11d9b 100644 --- a/tests/regression_tests/surface_source_write/case-20/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-20/inputs_true.dat @@ -1,40 +1,40 @@ - - - - - - - + + + + + + + - - - + + + - - - - - - - - - - + + + + + + + + + + eigenvalue 100 5 1 - + -2.0 -2.0 -2.0 2.0 2.0 2.0 diff --git a/tests/regression_tests/surface_source_write/case-21/inputs_true.dat b/tests/regression_tests/surface_source_write/case-21/inputs_true.dat index 5dae8374d27..71f9aae6a9c 100644 --- a/tests/regression_tests/surface_source_write/case-21/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-21/inputs_true.dat @@ -1,40 +1,40 @@ - - - - - - - + + + + + + + - - - + + + - - - - - - - - - - + + + + + + + + + + eigenvalue 100 5 1 - + -2.0 -2.0 -2.0 2.0 2.0 2.0 diff --git a/tests/regression_tests/surface_source_write/case-a01/inputs_true.dat b/tests/regression_tests/surface_source_write/case-a01/inputs_true.dat index 5cec1b76f73..e9840be8753 100644 --- a/tests/regression_tests/surface_source_write/case-a01/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-a01/inputs_true.dat @@ -1,47 +1,47 @@ - - - - - - - + + + + + + + - - - + + + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + eigenvalue 100 5 1 - + -2.0 -2.0 -2.0 2.0 2.0 2.0 diff --git a/tests/regression_tests/surface_source_write/case-d01/inputs_true.dat b/tests/regression_tests/surface_source_write/case-d01/inputs_true.dat index b5452080c23..04703acfeda 100644 --- a/tests/regression_tests/surface_source_write/case-d01/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-d01/inputs_true.dat @@ -1,26 +1,26 @@ - - - + + + - - - + + + - + eigenvalue 100 5 1 - + -4 -4 -20 4 4 20 diff --git a/tests/regression_tests/surface_source_write/case-d02/inputs_true.dat b/tests/regression_tests/surface_source_write/case-d02/inputs_true.dat index d6ca4143c62..d8b4e680334 100644 --- a/tests/regression_tests/surface_source_write/case-d02/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-d02/inputs_true.dat @@ -1,26 +1,26 @@ - - - + + + - - - + + + - + eigenvalue 100 5 1 - + -4 -4 -20 4 4 20 diff --git a/tests/regression_tests/surface_source_write/case-d03/inputs_true.dat b/tests/regression_tests/surface_source_write/case-d03/inputs_true.dat index ba326c978fc..d769184aa53 100644 --- a/tests/regression_tests/surface_source_write/case-d03/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-d03/inputs_true.dat @@ -1,26 +1,26 @@ - - - + + + - - - + + + - + eigenvalue 100 5 1 - + -4 -4 -20 4 4 20 diff --git a/tests/regression_tests/surface_source_write/case-d04/inputs_true.dat b/tests/regression_tests/surface_source_write/case-d04/inputs_true.dat index c309dd1552b..abefc269278 100644 --- a/tests/regression_tests/surface_source_write/case-d04/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-d04/inputs_true.dat @@ -1,26 +1,26 @@ - - - + + + - - - + + + - + eigenvalue 100 5 1 - + -4 -4 -20 4 4 20 diff --git a/tests/regression_tests/surface_source_write/case-d05/inputs_true.dat b/tests/regression_tests/surface_source_write/case-d05/inputs_true.dat index c49703f831a..8b46b2b4ea4 100644 --- a/tests/regression_tests/surface_source_write/case-d05/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-d05/inputs_true.dat @@ -1,26 +1,26 @@ - - - + + + - - - + + + - + eigenvalue 100 5 1 - + -4 -4 -20 4 4 20 diff --git a/tests/regression_tests/surface_source_write/case-d06/inputs_true.dat b/tests/regression_tests/surface_source_write/case-d06/inputs_true.dat index 48db2c30dd2..ca52cf455b7 100644 --- a/tests/regression_tests/surface_source_write/case-d06/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-d06/inputs_true.dat @@ -1,26 +1,26 @@ - - - + + + - - - + + + - + eigenvalue 100 5 1 - + -4 -4 -20 4 4 20 diff --git a/tests/regression_tests/surface_source_write/case-d07/inputs_true.dat b/tests/regression_tests/surface_source_write/case-d07/inputs_true.dat index e2897125b40..40c439fe698 100644 --- a/tests/regression_tests/surface_source_write/case-d07/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-d07/inputs_true.dat @@ -1,40 +1,40 @@ - - - + + + - - - + + + - + - - - - - - - - - - - - - + + + + + + + + + + + + + eigenvalue 100 5 1 - + -4 -4 -20 4 4 20 diff --git a/tests/regression_tests/surface_source_write/case-d08/inputs_true.dat b/tests/regression_tests/surface_source_write/case-d08/inputs_true.dat index 185b8629b90..c81d21b9791 100644 --- a/tests/regression_tests/surface_source_write/case-d08/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-d08/inputs_true.dat @@ -1,40 +1,40 @@ - - - + + + - - - + + + - + - - - - - - - - - - - - - + + + + + + + + + + + + + eigenvalue 100 5 1 - + -4 -4 -20 4 4 20 diff --git a/tests/regression_tests/surface_source_write/case-e01/inputs_true.dat b/tests/regression_tests/surface_source_write/case-e01/inputs_true.dat index ac3c03e37e0..46701aea7ee 100644 --- a/tests/regression_tests/surface_source_write/case-e01/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-e01/inputs_true.dat @@ -1,47 +1,47 @@ - - - - - - - + + + + + + + - - - + + + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + eigenvalue 100 5 1 - + -2.0 -2.0 -2.0 2.0 2.0 2.0 diff --git a/tests/regression_tests/surface_source_write/case-e02/inputs_true.dat b/tests/regression_tests/surface_source_write/case-e02/inputs_true.dat index a6107d16faa..a4588c8d083 100644 --- a/tests/regression_tests/surface_source_write/case-e02/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-e02/inputs_true.dat @@ -1,47 +1,47 @@ - - - - - - - + + + + + + + - - - + + + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + eigenvalue 100 5 1 - + -2.0 -2.0 -2.0 2.0 2.0 2.0 diff --git a/tests/regression_tests/surface_source_write/case-e03/inputs_true.dat b/tests/regression_tests/surface_source_write/case-e03/inputs_true.dat index 23bbf0455eb..2a93fdb4d62 100644 --- a/tests/regression_tests/surface_source_write/case-e03/inputs_true.dat +++ b/tests/regression_tests/surface_source_write/case-e03/inputs_true.dat @@ -1,40 +1,40 @@ - - - - - - - + + + + + + + - - - + + + - - - - - - - - - - + + + + + + + + + + eigenvalue 100 5 1 - + -2.0 -2.0 -2.0 2.0 2.0 2.0 diff --git a/tests/regression_tests/surface_tally/inputs_true.dat b/tests/regression_tests/surface_tally/inputs_true.dat index 192c2e89f40..2b070c5a3fd 100644 --- a/tests/regression_tests/surface_tally/inputs_true.dat +++ b/tests/regression_tests/surface_tally/inputs_true.dat @@ -1,35 +1,35 @@ - - - - - + + + + + - - - - + + + + - - - - - - - - + + + + + + + + eigenvalue 1000 10 0 - + -0.62992 -0.62992 -1 0.62992 0.62992 1 diff --git a/tests/regression_tests/tallies/inputs_true.dat b/tests/regression_tests/tallies/inputs_true.dat index 2ce36c5dfcd..40829f865a1 100644 --- a/tests/regression_tests/tallies/inputs_true.dat +++ b/tests/regression_tests/tallies/inputs_true.dat @@ -1,40 +1,40 @@ - - - - - - - + + + + + + + - - - - - - + + + + + + - - - - - + + + + + - - - - - + + + + + - + @@ -47,7 +47,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -77,7 +77,7 @@ - + @@ -92,7 +92,7 @@ - + @@ -107,7 +107,7 @@ - + @@ -122,7 +122,7 @@ - + @@ -135,7 +135,7 @@ - + @@ -149,8 +149,8 @@ - - + + @@ -174,9 +174,9 @@ - + - + 1.26 1.26 17 17 @@ -277,30 +277,30 @@ 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + eigenvalue 400 5 0 - + -160 -160 -183 160 160 183 @@ -342,7 +342,7 @@ 4 - + 4 diff --git a/tests/regression_tests/tally_aggregation/inputs_true.dat b/tests/regression_tests/tally_aggregation/inputs_true.dat index 9f4f9565524..7351b230c81 100644 --- a/tests/regression_tests/tally_aggregation/inputs_true.dat +++ b/tests/regression_tests/tally_aggregation/inputs_true.dat @@ -1,24 +1,24 @@ - - - - - - + + + + + + - - - + + + - + 1.2 1.2 1 @@ -28,11 +28,11 @@ 1 1 1 1 - - - - - + + + + + eigenvalue diff --git a/tests/regression_tests/tally_arithmetic/inputs_true.dat b/tests/regression_tests/tally_arithmetic/inputs_true.dat index 7669b4d0360..2b5234c34a8 100644 --- a/tests/regression_tests/tally_arithmetic/inputs_true.dat +++ b/tests/regression_tests/tally_arithmetic/inputs_true.dat @@ -1,24 +1,24 @@ - - - - - + + + + + - - - + + + - - + + eigenvalue diff --git a/tests/regression_tests/tally_slice_merge/inputs_true.dat b/tests/regression_tests/tally_slice_merge/inputs_true.dat index 7159d833a5a..7a992cc39b8 100644 --- a/tests/regression_tests/tally_slice_merge/inputs_true.dat +++ b/tests/regression_tests/tally_slice_merge/inputs_true.dat @@ -1,40 +1,40 @@ - - - - - - - + + + + + + + - - - - - - + + + + + + - - - - - + + + + + - - - - - + + + + + - + @@ -47,7 +47,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -77,7 +77,7 @@ - + @@ -92,7 +92,7 @@ - + @@ -107,7 +107,7 @@ - + @@ -122,7 +122,7 @@ - + @@ -135,7 +135,7 @@ - + @@ -149,8 +149,8 @@ - - + + @@ -174,9 +174,9 @@ - + - + 1.26 1.26 17 17 @@ -277,30 +277,30 @@ 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + eigenvalue 100 10 5 - + -160 -160 -183 160 160 183 diff --git a/tests/regression_tests/time_cutoff/inputs_true.dat b/tests/regression_tests/time_cutoff/inputs_true.dat index 7d3e94f50f9..e1102d47565 100644 --- a/tests/regression_tests/time_cutoff/inputs_true.dat +++ b/tests/regression_tests/time_cutoff/inputs_true.dat @@ -4,13 +4,13 @@ - + fixed source 100 10 - + 0.0 0.0 0.0 diff --git a/tests/regression_tests/torus/inputs_true.dat b/tests/regression_tests/torus/inputs_true.dat index a5c7a8d7aee..df4af14438a 100644 --- a/tests/regression_tests/torus/inputs_true.dat +++ b/tests/regression_tests/torus/inputs_true.dat @@ -1,13 +1,13 @@ - - - + + + - - + + @@ -15,15 +15,15 @@ - - - - - - - - - + + + + + + + + + eigenvalue diff --git a/tests/regression_tests/torus/large_major/inputs_true.dat b/tests/regression_tests/torus/large_major/inputs_true.dat index fa7deb29a87..513a8c67e74 100644 --- a/tests/regression_tests/torus/large_major/inputs_true.dat +++ b/tests/regression_tests/torus/large_major/inputs_true.dat @@ -2,27 +2,27 @@ - - + + - - + + - - - + + + fixed source 1000 10 - + -1000.0 0 0 diff --git a/tests/regression_tests/trigger_statepoint_restart/inputs_true.dat b/tests/regression_tests/trigger_statepoint_restart/inputs_true.dat index 6f0b629eb62..01755ad8bc2 100644 --- a/tests/regression_tests/trigger_statepoint_restart/inputs_true.dat +++ b/tests/regression_tests/trigger_statepoint_restart/inputs_true.dat @@ -1,14 +1,14 @@ - - - + + + - + eigenvalue diff --git a/tests/regression_tests/triso/inputs_true.dat b/tests/regression_tests/triso/inputs_true.dat index 37f37af7871..c9668456854 100644 --- a/tests/regression_tests/triso/inputs_true.dat +++ b/tests/regression_tests/triso/inputs_true.dat @@ -1,38 +1,38 @@ - - - - - - + + + + + + - - + + - - + + - - - - - + + + + + - - + + - - + + @@ -42,171 +42,171 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -253,187 +253,187 @@ 24 25 26 21 22 23 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue 100 4 0 - + 0.0 0.0 0.0 diff --git a/tests/regression_tests/unstructured_mesh/inputs_true.dat b/tests/regression_tests/unstructured_mesh/inputs_true.dat index 07486d3e0ee..e7a485b4d13 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true.dat @@ -1,54 +1,54 @@ - - - + + + - - - - - - + + + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + fixed source 1000 10 - - - + + + 1.0 1.0 @@ -67,7 +67,7 @@ -10.0 -10.0 -10.0 10.0 10.0 10.0
- + test_mesh_hexes.e diff --git a/tests/regression_tests/unstructured_mesh/inputs_true0.dat b/tests/regression_tests/unstructured_mesh/inputs_true0.dat index c6b28d890ed..2508c25b3c3 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true0.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true0.dat @@ -1,54 +1,54 @@ - - - + + + - - - - - - + + + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + fixed source 1000 10 - - - + + + 1.0 1.0 @@ -67,7 +67,7 @@ -10.0 -10.0 -10.0 10.0 10.0 10.0 - + test_mesh_tets_w_holes.e diff --git a/tests/regression_tests/unstructured_mesh/inputs_true1.dat b/tests/regression_tests/unstructured_mesh/inputs_true1.dat index 40183c89bc8..04bbdb4b274 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true1.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true1.dat @@ -1,54 +1,54 @@ - - - + + + - - - - - - + + + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + fixed source 1000 10 - - - + + + 1.0 1.0 @@ -67,7 +67,7 @@ -10.0 -10.0 -10.0 10.0 10.0 10.0 - + test_mesh_tets.e diff --git a/tests/regression_tests/unstructured_mesh/inputs_true10.dat b/tests/regression_tests/unstructured_mesh/inputs_true10.dat index 81dfbdc52d9..169f0a6b4e6 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true10.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true10.dat @@ -1,54 +1,54 @@ - - - + + + - - - - - - + + + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + fixed source 1000 10 - - - + + + 1.0 1.0 @@ -67,7 +67,7 @@ -10.0 -10.0 -10.0 10.0 10.0 10.0 - + test_mesh_tets_w_holes.e diff --git a/tests/regression_tests/unstructured_mesh/inputs_true11.dat b/tests/regression_tests/unstructured_mesh/inputs_true11.dat index b224a3ebc36..8163750cafe 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true11.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true11.dat @@ -1,54 +1,54 @@ - - - + + + - - - - - - + + + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + fixed source 1000 10 - - - + + + 1.0 1.0 @@ -67,7 +67,7 @@ -10.0 -10.0 -10.0 10.0 10.0 10.0 - + test_mesh_tets.e diff --git a/tests/regression_tests/unstructured_mesh/inputs_true12.dat b/tests/regression_tests/unstructured_mesh/inputs_true12.dat index a18aee627ce..be0655e98d0 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true12.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true12.dat @@ -1,54 +1,54 @@ - - - + + + - - - - - - + + + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + fixed source 1000 10 - - - + + + 1.0 1.0 @@ -67,7 +67,7 @@ -10.0 -10.0 -10.0 10.0 10.0 10.0 - + test_mesh_tets_w_holes.e diff --git a/tests/regression_tests/unstructured_mesh/inputs_true13.dat b/tests/regression_tests/unstructured_mesh/inputs_true13.dat index 0e9123c9036..42c0cc1226d 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true13.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true13.dat @@ -1,54 +1,54 @@ - - - + + + - - - - - - + + + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + fixed source 1000 10 - - - + + + 1.0 1.0 @@ -67,7 +67,7 @@ -10.0 -10.0 -10.0 10.0 10.0 10.0 - + test_mesh_tets.e diff --git a/tests/regression_tests/unstructured_mesh/inputs_true14.dat b/tests/regression_tests/unstructured_mesh/inputs_true14.dat index 9d4db490451..a27c7445dc1 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true14.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true14.dat @@ -1,54 +1,54 @@ - - - + + + - - - - - - + + + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + fixed source 1000 10 - - - + + + 1.0 1.0 @@ -67,7 +67,7 @@ -10.0 -10.0 -10.0 10.0 10.0 10.0 - + test_mesh_tets_w_holes.e diff --git a/tests/regression_tests/unstructured_mesh/inputs_true15.dat b/tests/regression_tests/unstructured_mesh/inputs_true15.dat index eab97b95227..c69bd15d0dd 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true15.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true15.dat @@ -1,54 +1,54 @@ - - - + + + - - - - - - + + + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + fixed source 1000 10 - - - + + + 1.0 1.0 @@ -67,7 +67,7 @@ -10.0 -10.0 -10.0 10.0 10.0 10.0 - + test_mesh_tets.e diff --git a/tests/regression_tests/unstructured_mesh/inputs_true2.dat b/tests/regression_tests/unstructured_mesh/inputs_true2.dat index 3521ac7b81e..ba9d477b559 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true2.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true2.dat @@ -1,54 +1,54 @@ - - - + + + - - - - - - + + + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + fixed source 1000 10 - - - + + + 1.0 1.0 @@ -67,7 +67,7 @@ -10.0 -10.0 -10.0 10.0 10.0 10.0 - + test_mesh_tets_w_holes.e diff --git a/tests/regression_tests/unstructured_mesh/inputs_true3.dat b/tests/regression_tests/unstructured_mesh/inputs_true3.dat index a376b401d6c..d840bfa095d 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true3.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true3.dat @@ -1,54 +1,54 @@ - - - + + + - - - - - - + + + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + fixed source 1000 10 - - - + + + 1.0 1.0 @@ -67,7 +67,7 @@ -10.0 -10.0 -10.0 10.0 10.0 10.0 - + test_mesh_tets.e diff --git a/tests/regression_tests/unstructured_mesh/inputs_true4.dat b/tests/regression_tests/unstructured_mesh/inputs_true4.dat index 843632fdd63..db8e371771e 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true4.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true4.dat @@ -1,54 +1,54 @@ - - - + + + - - - - - - - + + + + + + + - - - - + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + fixed source - 100 + 1000 10 - - - + + + 1.0 1.0 @@ -56,34 +56,33 @@ 0.0 1.0 - 15000000.0 1.0 - + 10 10 10 -10.0 -10.0 -10.0 10.0 10.0 10.0 - - test_mesh_tets_w_holes.exo + + test_mesh_tets_w_holes.e - - 9 + + 1 - - 10 + + 2 - - 9 + + 1 flux tracklength - - 10 + + 2 flux tracklength diff --git a/tests/regression_tests/unstructured_mesh/inputs_true5.dat b/tests/regression_tests/unstructured_mesh/inputs_true5.dat index dc9129d98aa..b2bbc7e1651 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true5.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true5.dat @@ -1,54 +1,54 @@ - - - + + + - - - - - - - + + + + + + + - - - - + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + fixed source - 100 + 1000 10 - - - + + + 1.0 1.0 @@ -56,34 +56,33 @@ 0.0 1.0 - 15000000.0 1.0 - + 10 10 10 -10.0 -10.0 -10.0 10.0 10.0 10.0 - - test_mesh_tets.exo + + test_mesh_tets.e - - 11 + + 1 - - 12 + + 2 - - 11 + + 1 flux tracklength - - 12 + + 2 flux tracklength diff --git a/tests/regression_tests/unstructured_mesh/inputs_true6.dat b/tests/regression_tests/unstructured_mesh/inputs_true6.dat index 82bc3070372..d6ad0d98d85 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true6.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true6.dat @@ -1,54 +1,54 @@ - - - + + + - - - - - - - + + + + + + + - - - - + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + fixed source - 100 + 1000 10 - - - + + + 1.0 1.0 @@ -56,34 +56,33 @@ 0.0 1.0 - 15000000.0 1.0 - + 10 10 10 -10.0 -10.0 -10.0 10.0 10.0 10.0 - - test_mesh_tets_w_holes.exo + + test_mesh_tets_w_holes.e - - 13 + + 1 - - 14 + + 2 - - 13 + + 1 flux tracklength - - 14 + + 2 flux tracklength diff --git a/tests/regression_tests/unstructured_mesh/inputs_true7.dat b/tests/regression_tests/unstructured_mesh/inputs_true7.dat index b982c40d055..924790a8c9d 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true7.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true7.dat @@ -1,54 +1,54 @@ - - - + + + - - - - - - - + + + + + + + - - - - + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + fixed source - 100 + 1000 10 - - - + + + 1.0 1.0 @@ -56,34 +56,33 @@ 0.0 1.0 - 15000000.0 1.0 - + 10 10 10 -10.0 -10.0 -10.0 10.0 10.0 10.0 - - test_mesh_tets.exo + + test_mesh_tets.e - - 15 + + 1 - - 16 + + 2 - - 15 + + 1 flux tracklength - - 16 + + 2 flux tracklength diff --git a/tests/regression_tests/unstructured_mesh/inputs_true8.dat b/tests/regression_tests/unstructured_mesh/inputs_true8.dat index 7994e1add76..c04beb21122 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true8.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true8.dat @@ -1,54 +1,54 @@ - - - + + + - - - - - - + + + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + fixed source 1000 10 - - - + + + 1.0 1.0 @@ -67,7 +67,7 @@ -10.0 -10.0 -10.0 10.0 10.0 10.0 - + test_mesh_tets_w_holes.e diff --git a/tests/regression_tests/unstructured_mesh/inputs_true9.dat b/tests/regression_tests/unstructured_mesh/inputs_true9.dat index 4fff123d7d4..111898ff241 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true9.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true9.dat @@ -1,54 +1,54 @@ - - - + + + - - - - - - + + + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + fixed source 1000 10 - - - + + + 1.0 1.0 @@ -67,7 +67,7 @@ -10.0 -10.0 -10.0 10.0 10.0 10.0 - + test_mesh_tets.e diff --git a/tests/regression_tests/void/inputs_true.dat b/tests/regression_tests/void/inputs_true.dat index 7fee0779e06..cd2d14206b9 100644 --- a/tests/regression_tests/void/inputs_true.dat +++ b/tests/regression_tests/void/inputs_true.dat @@ -2,8 +2,8 @@ - - + + @@ -58,62 +58,62 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixed source 1000 3 - + 0.0 0.0 0.0 diff --git a/tests/regression_tests/volume_calc/inputs_true.dat b/tests/regression_tests/volume_calc/inputs_true.dat index e3a8162e3b8..ed7024c5772 100644 --- a/tests/regression_tests/volume_calc/inputs_true.dat +++ b/tests/regression_tests/volume_calc/inputs_true.dat @@ -2,27 +2,27 @@ - - - - + + + + - - - - + + + + - - - - - + + + + + volume @@ -53,7 +53,7 @@ 100 -1.0 -1.0 -6.0 1.0 1.0 6.0 - + material @@ -61,7 +61,7 @@ 100 -1.0 -1.0 -6.0 1.0 1.0 6.0 - + cell @@ -69,7 +69,7 @@ 100 -1.0 -1.0 -6.0 1.0 1.0 6.0 - + diff --git a/tests/regression_tests/volume_calc/inputs_true_mg.dat b/tests/regression_tests/volume_calc/inputs_true_mg.dat index 2ace5cd3c92..127566084eb 100644 --- a/tests/regression_tests/volume_calc/inputs_true_mg.dat +++ b/tests/regression_tests/volume_calc/inputs_true_mg.dat @@ -3,26 +3,26 @@ mg_lib.h5 - - - - + + + + - - - - + + + + - - - - - + + + + + volume @@ -54,7 +54,7 @@ 100 -1.0 -1.0 -6.0 1.0 1.0 6.0 - + material @@ -62,7 +62,7 @@ 100 -1.0 -1.0 -6.0 1.0 1.0 6.0 - + cell @@ -70,7 +70,7 @@ 100 -1.0 -1.0 -6.0 1.0 1.0 6.0 - + diff --git a/tests/regression_tests/weightwindows/inputs_true.dat b/tests/regression_tests/weightwindows/inputs_true.dat index 91fec4af92d..eb9393179fe 100644 --- a/tests/regression_tests/weightwindows/inputs_true.dat +++ b/tests/regression_tests/weightwindows/inputs_true.dat @@ -2,39 +2,39 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - + + fixed source 200 2 - + 0.001 0.001 0.001 diff --git a/tests/regression_tests/weightwindows_fw_cadis/inputs_true.dat b/tests/regression_tests/weightwindows_fw_cadis/inputs_true.dat index 586bbb12026..448b4b145c1 100644 --- a/tests/regression_tests/weightwindows_fw_cadis/inputs_true.dat +++ b/tests/regression_tests/weightwindows_fw_cadis/inputs_true.dat @@ -3,24 +3,24 @@ mgxs.h5 - + - + - + - - - - - + + + + + 2.5 2.5 2.5 12 12 12 @@ -182,19 +182,19 @@ 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - - - - + + + + + + fixed source 90 10 5 - + 100.0 1.0 @@ -222,7 +222,7 @@ 500.0 100.0 - + 0.0 0.0 0.0 30.0 30.0 30.0 diff --git a/tests/regression_tests/weightwindows_fw_cadis_mesh/flat/inputs_true.dat b/tests/regression_tests/weightwindows_fw_cadis_mesh/flat/inputs_true.dat index ffb2ac112da..d82aa6fae86 100644 --- a/tests/regression_tests/weightwindows_fw_cadis_mesh/flat/inputs_true.dat +++ b/tests/regression_tests/weightwindows_fw_cadis_mesh/flat/inputs_true.dat @@ -3,24 +3,24 @@ mgxs.h5 - + - + - + - - - - - + + + + + 2.5 2.5 2.5 12 12 12 @@ -182,19 +182,19 @@ 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - - - - + + + + + + fixed source 750 30 20 - + 100.0 1.0 @@ -222,7 +222,7 @@ 500.0 100.0 - + 0.0 0.0 0.0 30.0 30.0 30.0 diff --git a/tests/regression_tests/weightwindows_fw_cadis_mesh/linear/inputs_true.dat b/tests/regression_tests/weightwindows_fw_cadis_mesh/linear/inputs_true.dat index 6aa91ca798a..9e4b21d27be 100644 --- a/tests/regression_tests/weightwindows_fw_cadis_mesh/linear/inputs_true.dat +++ b/tests/regression_tests/weightwindows_fw_cadis_mesh/linear/inputs_true.dat @@ -3,24 +3,24 @@ mgxs.h5 - + - + - + - - - - - + + + + + 2.5 2.5 2.5 12 12 12 @@ -182,19 +182,19 @@ 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - - - - + + + + + + fixed source 750 30 20 - + 100.0 1.0 @@ -222,7 +222,7 @@ 500.0 100.0 - + 0.0 0.0 0.0 30.0 30.0 30.0 From e36c0aef2f9603cec8ad3b171b7667ed9ab773ff Mon Sep 17 00:00:00 2001 From: Boris Polania Date: Fri, 8 Aug 2025 07:09:24 -0700 Subject: [PATCH 087/159] Add stat:sum field to MCPL files for proper weight normalization (#3522) Co-authored-by: Paul Romano --- include/openmc/mcpl_interface.h | 13 ++- src/mcpl_interface.cpp | 36 +++++++ tests/cpp_unit_tests/CMakeLists.txt | 1 + tests/cpp_unit_tests/test_mcpl_stat_sum.cpp | 108 ++++++++++++++++++++ tests/unit_tests/test_mcpl_stat_sum.py | 69 +++++++++++++ 5 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 tests/cpp_unit_tests/test_mcpl_stat_sum.cpp create mode 100644 tests/unit_tests/test_mcpl_stat_sum.py diff --git a/include/openmc/mcpl_interface.h b/include/openmc/mcpl_interface.h index f7323e10a67..a76d72e6494 100644 --- a/include/openmc/mcpl_interface.h +++ b/include/openmc/mcpl_interface.h @@ -19,8 +19,17 @@ namespace openmc { //! \return Vector of source sites vector mcpl_source_sites(std::string path); -//! Write an MCPL source file -// +//! Write an MCPL source file with stat:sum metadata +//! +//! This function writes particle data to an MCPL file. For MCPL >= 2.1.0, +//! it includes a stat:sum field (key: "openmc_np1") containing the total +//! number of source particles, which is essential for proper file merging +//! and weight normalization when using MCPL files with McStas/McXtrace. +//! +//! The stat:sum field follows the crash-safety pattern: +//! - Initially set to -1 when opening (indicates incomplete file) +//! - Updated with actual particle count before closing +//! //! \param[in] filename Path to MCPL file //! \param[in] source_bank Vector of SourceSites to write to file for this //! MPI rank. diff --git a/src/mcpl_interface.cpp b/src/mcpl_interface.cpp index 13915c3b8e9..b8e78071003 100644 --- a/src/mcpl_interface.cpp +++ b/src/mcpl_interface.cpp @@ -67,6 +67,8 @@ using mcpl_hdr_set_srcname_fpt = void (*)( using mcpl_add_particle_fpt = void (*)( mcpl_outfile_t* outfile_handle, const mcpl_particle_repr_t* particle); using mcpl_close_outfile_fpt = void (*)(mcpl_outfile_t* outfile_handle); +using mcpl_hdr_add_stat_sum_fpt = void (*)( + mcpl_outfile_t* outfile_handle, const char* key, double value); namespace openmc { @@ -110,6 +112,7 @@ struct McplApi { mcpl_hdr_set_srcname_fpt hdr_set_srcname; mcpl_add_particle_fpt add_particle; mcpl_close_outfile_fpt close_outfile; + mcpl_hdr_add_stat_sum_fpt hdr_add_stat_sum; explicit McplApi(LibraryHandleType lib_handle) { @@ -147,6 +150,15 @@ struct McplApi { load_symbol_platform("mcpl_add_particle")); close_outfile = reinterpret_cast( load_symbol_platform("mcpl_close_outfile")); + + // Try to load mcpl_hdr_add_stat_sum (available in MCPL >= 2.1.0) + // Set to nullptr if not available for graceful fallback + try { + hdr_add_stat_sum = reinterpret_cast( + load_symbol_platform("mcpl_hdr_add_stat_sum")); + } catch (const std::runtime_error&) { + hdr_add_stat_sum = nullptr; + } } }; @@ -498,12 +510,36 @@ void write_mcpl_source_point(const char* filename, span source_bank, "OpenMC {}.{}.{}", VERSION_MAJOR, VERSION_MINOR, VERSION_RELEASE); } g_mcpl_api->hdr_set_srcname(file_id, src_line.c_str()); + + // Initialize stat:sum with -1 to indicate incomplete file (issue #3514) + // This follows MCPL >= 2.1.0 convention for tracking simulation statistics + // The -1 value indicates "not available" if file creation is interrupted + if (g_mcpl_api->hdr_add_stat_sum) { + // Using key "openmc_np1" following tkittel's recommendation + // Initial value of -1 prevents misleading values in case of crashes + g_mcpl_api->hdr_add_stat_sum(file_id, "openmc_np1", -1.0); + } } write_mcpl_source_bank_internal(file_id, source_bank, bank_index); if (mpi::master) { if (file_id) { + // Update stat:sum with actual particle count before closing (issue #3514) + // This represents the original number of source particles in the + // simulation (not the number of particles in the file) + if (g_mcpl_api->hdr_add_stat_sum) { + // Calculate total source particles from active batches + // Per issue #3514: this should be the original number of source + // particles, not the number written to the file + int64_t total_source_particles = + static_cast(settings::n_batches - settings::n_inactive) * + settings::gen_per_batch * settings::n_particles; + // Update with actual count - this overwrites the initial -1 value + g_mcpl_api->hdr_add_stat_sum( + file_id, "openmc_np1", static_cast(total_source_particles)); + } + g_mcpl_api->close_outfile(file_id); } } diff --git a/tests/cpp_unit_tests/CMakeLists.txt b/tests/cpp_unit_tests/CMakeLists.txt index f0f5f2853ad..8fedc2daa57 100644 --- a/tests/cpp_unit_tests/CMakeLists.txt +++ b/tests/cpp_unit_tests/CMakeLists.txt @@ -4,6 +4,7 @@ set(TEST_NAMES test_tally test_interpolate test_math + test_mcpl_stat_sum # Add additional unit test files here ) diff --git a/tests/cpp_unit_tests/test_mcpl_stat_sum.cpp b/tests/cpp_unit_tests/test_mcpl_stat_sum.cpp new file mode 100644 index 00000000000..909830e035c --- /dev/null +++ b/tests/cpp_unit_tests/test_mcpl_stat_sum.cpp @@ -0,0 +1,108 @@ +#include +#include +#include +#include + +#include "openmc/bank.h" +#include "openmc/mcpl_interface.h" + +// Test the MCPL stat:sum functionality (issue #3514) +TEST_CASE("MCPL stat:sum field") +{ + // Check if MCPL interface is available + if (!openmc::is_mcpl_interface_available()) { + SKIP("MCPL library not available"); + } + + SECTION("stat:sum field is written to MCPL files") + { + // Create a temporary filename + std::string filename = "test_stat_sum.mcpl"; + + // Create some test particles + std::vector source_bank(100); + std::vector bank_index = {0, 100}; // 100 particles total + + // Initialize test particles + for (int i = 0; i < 100; ++i) { + source_bank[i].particle = openmc::ParticleType::neutron; + source_bank[i].r = {i * 0.1, i * 0.2, i * 0.3}; + source_bank[i].u = {0.0, 0.0, 1.0}; + source_bank[i].E = 2.0e6; // 2 MeV + source_bank[i].time = 0.0; + source_bank[i].wgt = 1.0; + } + + // Write the MCPL file + openmc::write_mcpl_source_point(filename.c_str(), source_bank, bank_index); + + // Verify the file was created + FILE* f = std::fopen(filename.c_str(), "r"); + REQUIRE(f != nullptr); + std::fclose(f); + + // Read the file back to check stat:sum + // Note: This would require mcpl_open_file and checking the header + // Since we can't easily read MCPL headers in C++ without the full MCPL API, + // we rely on the Python test to verify the actual content + + // Clean up + std::remove(filename.c_str()); + } + + SECTION("stat:sum uses correct particle count") + { + std::string filename = "test_count.mcpl"; + + // Test with different particle counts + std::vector test_counts = {1, 10, 100, 1000}; + + for (int count : test_counts) { + std::vector source_bank(count); + std::vector bank_index = {0, count}; + + // Initialize particles + for (int i = 0; i < count; ++i) { + source_bank[i].particle = openmc::ParticleType::neutron; + source_bank[i].r = {0.0, 0.0, 0.0}; + source_bank[i].u = {0.0, 0.0, 1.0}; + source_bank[i].E = 1.0e6; + source_bank[i].time = 0.0; + source_bank[i].wgt = 1.0; + } + + // Write MCPL file + openmc::write_mcpl_source_point( + filename.c_str(), source_bank, bank_index); + + // The stat:sum should equal count (verified by Python test) + // Here we just verify the file was created successfully + FILE* f = std::fopen(filename.c_str(), "r"); + REQUIRE(f != nullptr); + std::fclose(f); + + // Clean up + std::remove(filename.c_str()); + } + } + + SECTION("stat:sum handles empty particle bank") + { + std::string filename = "test_empty.mcpl"; + + // Create empty particle bank + std::vector source_bank; + std::vector bank_index = {0}; + + // This should still create a valid MCPL file with stat:sum = 0 + openmc::write_mcpl_source_point(filename.c_str(), source_bank, bank_index); + + // Verify file was created + FILE* f = std::fopen(filename.c_str(), "r"); + REQUIRE(f != nullptr); + std::fclose(f); + + // Clean up + std::remove(filename.c_str()); + } +} diff --git a/tests/unit_tests/test_mcpl_stat_sum.py b/tests/unit_tests/test_mcpl_stat_sum.py new file mode 100644 index 00000000000..b6919295603 --- /dev/null +++ b/tests/unit_tests/test_mcpl_stat_sum.py @@ -0,0 +1,69 @@ +"""Test for MCPL stat:sum functionality""" + +from pathlib import Path +import shutil + +import pytest +import openmc + + +@pytest.mark.skipif(shutil.which("mcpl-config") is None, reason="MCPL is not available.") +def test_mcpl_stat_sum_field(run_in_tmpdir): + """Test that MCPL files contain proper stat:sum field with particle count. + + This test verifies that when OpenMC creates MCPL source files, they contain + the stat:sum field. Since MCPL functions are not exposed in the Python API, + this test creates an actual OpenMC simulation to generate MCPL files and + then checks their content. + """ + + mcpl = pytest.importorskip("mcpl") + + # Create a minimal working model that will generate MCPL files + model = openmc.examples.pwr_pin_cell() + model.settings.batches = 5 + model.settings.inactive = 2 + model.settings.particles = 1000 + model.settings.sourcepoint = {'mcpl': True, 'separate': True} + + # Run a short simulation to generate MCPL files + model.run(output=False) + + # Find the generated MCPL file (from the last batch) + mcpl_file = Path('source.5.mcpl') + assert mcpl_file.exists(), "No MCPL files were generated" + + # Open and verify the stat:sum field exists + with mcpl.MCPLFile(mcpl_file) as f: + # Check if stat:sum field exists using convenience property + if hasattr(f, 'stat_sum'): + # Use the convenience .stat_sum property directly + stat_sum_dict = f.stat_sum + assert 'openmc_np1' in stat_sum_dict, "openmc_np1 key not found in stat_sum" + stat_sum_value = int(stat_sum_dict['openmc_np1']) + else: + # Fallback to checking comments for older MCPL versions + comments = f.comments + + # Check for stat:sum in comments (MCPL stores these as comments) + stat_sum_value = None + + for comment in comments: + if 'stat:sum:openmc_np1' in comment: + # Extract the value + parts = comment.split(':') + if len(parts) >= 4: + stat_sum_value = int(parts[3].strip()) + break + else: + pytest.skip("stat:sum field not found - may be running with MCPL < 2.1.0") + + # Verify the stat:sum value is reasonable + assert stat_sum_value != -1, "stat:sum was not updated from initial -1 value" + + # In eigenvalue mode, active batches generate source particles + active_batches = model.settings.batches - model.settings.inactive # 3 active batches + expected_particles = active_batches * model.settings.particles # 3000 total + + assert stat_sum_value == expected_particles, \ + f"stat:sum value {stat_sum_value} doesn't match expected {expected_particles}" From a11021cd07bf43b2ea3070222cd916a737b6ed80 Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Fri, 8 Aug 2025 20:47:55 +0300 Subject: [PATCH 088/159] Consistent XML parsing using functions from _xml module (#3517) Co-authored-by: Paul Romano --- openmc/_xml.py | 14 ++-- openmc/cell.py | 22 +++--- openmc/dagmc.py | 8 +- openmc/data/library.py | 10 +-- openmc/deplete/chain.py | 3 +- openmc/deplete/nuclide.py | 34 +++++---- openmc/filter.py | 49 ++++++------- openmc/filter_expansion.py | 31 ++++---- openmc/lattice.py | 21 +++--- openmc/material.py | 43 ++++++----- openmc/mesh.py | 42 +++++------ openmc/plots.py | 137 +++++++++++++++++------------------ openmc/settings.py | 55 +++++++------- openmc/source.py | 12 +-- openmc/stats/multivariate.py | 34 ++++----- openmc/stats/univariate.py | 25 +++---- openmc/surface.py | 13 ++-- openmc/tallies.py | 35 +++++---- openmc/tally_derivative.py | 9 ++- openmc/trigger.py | 11 +-- openmc/volume.py | 11 +-- openmc/weight_windows.py | 15 ++-- 22 files changed, 313 insertions(+), 321 deletions(-) diff --git a/openmc/_xml.py b/openmc/_xml.py index 05412128dd8..758d8052557 100644 --- a/openmc/_xml.py +++ b/openmc/_xml.py @@ -64,8 +64,8 @@ def get_text(elem, name, default=None): -def get_elem_tuple(elem, name, dtype=int): - """Helper function to get a tuple of values from an elem +def get_elem_list(elem, name, dtype=int): + """Helper function to get a list of values from an elem Parameters ---------- @@ -78,9 +78,9 @@ def get_elem_tuple(elem, name, dtype=int): Returns ------- - tuple of dtype - Data read from the tuple + list of dtype + Data read from the list """ - subelem = elem.find(name) - if subelem is not None: - return tuple([dtype(x) for x in subelem.text.split()]) + text = get_text(elem, name) + if text is not None: + return [dtype(x) for x in text.split()] diff --git a/openmc/cell.py b/openmc/cell.py index cd0573e8b8e..672afe0953d 100644 --- a/openmc/cell.py +++ b/openmc/cell.py @@ -8,7 +8,7 @@ import openmc import openmc.checkvalue as cv -from ._xml import get_text +from ._xml import get_elem_list, get_text from .mixin import IDManagerMixin from .plots import add_plot_params from .region import Region, Complement @@ -689,9 +689,8 @@ def from_xml_element(cls, elem, surfaces, materials, get_universe): c = cls(cell_id, name) # Assign material/distributed materials or fill - mat_text = get_text(elem, 'material') - if mat_text is not None: - mat_ids = mat_text.split() + mat_ids = get_elem_list(elem, 'material', str) + if mat_ids is not None: if len(mat_ids) > 1: c.fill = [materials[i] for i in mat_ids] else: @@ -706,19 +705,18 @@ def from_xml_element(cls, elem, surfaces, materials, get_universe): c.region = Region.from_expression(region, surfaces) # Check for other attributes - t = get_text(elem, 'temperature') - if t is not None: - if ' ' in t: - c.temperature = [float(t_i) for t_i in t.split()] + temperature = get_elem_list(elem, 'temperature', float) + if temperature is not None: + if len(temperature) > 1: + c.temperature = temperature else: - c.temperature = float(t) + c.temperature = temperature[0] v = get_text(elem, 'volume') if v is not None: c.volume = float(v) for key in ('temperature', 'rotation', 'translation'): - value = get_text(elem, key) - if value is not None: - values = [float(x) for x in value.split()] + values = get_elem_list(elem, key, float) + if values is not None: if key == 'rotation' and len(values) == 9: values = np.array(values).reshape(3, 3) setattr(c, key, values) diff --git a/openmc/dagmc.py b/openmc/dagmc.py index 2486f7516f0..d1265be2687 100644 --- a/openmc/dagmc.py +++ b/openmc/dagmc.py @@ -8,7 +8,7 @@ import openmc import openmc.checkvalue as cv -from ._xml import get_text +from ._xml import get_elem_list, get_text from .checkvalue import check_type, check_value from .surface import _BOUNDARY_TYPES from .bounding_box import BoundingBox @@ -468,8 +468,8 @@ def from_xml_element(cls, elem, mats = None): if name is not None: out.name = name - out.auto_geom_ids = bool(elem.get('auto_geom_ids')) - out.auto_mat_ids = bool(elem.get('auto_mat_ids')) + out.auto_geom_ids = bool(get_text(elem, "auto_geom_ids")) + out.auto_mat_ids = bool(get_text(elem, "auto_mat_ids")) el_mat_override = elem.find('material_overrides') if el_mat_override is not None: @@ -480,7 +480,7 @@ def from_xml_element(cls, elem, mats = None): out._material_overrides = {} for elem in el_mat_override.findall('cell_override'): cell_id = int(get_text(elem, 'id')) - mat_ids = get_text(elem, 'material_ids').split(' ') + mat_ids = get_elem_list(elem, "material_ids", str) or [] mat_objs = [mats[mat_id] for mat_id in mat_ids] out._material_overrides[cell_id] = mat_objs diff --git a/openmc/data/library.py b/openmc/data/library.py index bec538c0696..b49757b0dc3 100644 --- a/openmc/data/library.py +++ b/openmc/data/library.py @@ -5,7 +5,7 @@ import lxml.etree as ET import openmc -from openmc._xml import clean_indentation +from openmc._xml import get_elem_list, get_text, clean_indentation class DataLibrary(list): @@ -172,9 +172,9 @@ def from_xml(cls, path=None): directory = os.path.dirname(path) for lib_element in root.findall('library'): - filename = os.path.join(directory, lib_element.attrib['path']) - filetype = lib_element.attrib['type'] - materials = lib_element.attrib['materials'].split() + filename = os.path.join(directory, get_text(lib_element, "path")) + filetype = get_text(lib_element, "type") + materials = get_elem_list(lib_element, "materials", str) or [] library = {'path': filename, 'type': filetype, 'materials': materials} data.libraries.append(library) @@ -182,7 +182,7 @@ def from_xml(cls, path=None): # get depletion chain data dep_node = root.find("depletion_chain") if dep_node is not None: - filename = os.path.join(directory, dep_node.attrib['path']) + filename = os.path.join(directory, get_text(dep_node, "path")) library = {'path': filename, 'type': 'depletion_chain', 'materials': []} data.libraries.append(library) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 8e24f716bc2..f1a23317fb9 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -22,6 +22,7 @@ from openmc.data import gnds_name, zam from openmc.exceptions import DataError from .nuclide import FissionYieldDistribution, Nuclide +from .._xml import get_text import openmc.data @@ -553,7 +554,7 @@ def from_xml(cls, filename, fission_q=None): root = ET.parse(str(filename)) for i, nuclide_elem in enumerate(root.findall('nuclide')): - this_q = fission_q.get(nuclide_elem.get("name")) + this_q = fission_q.get(get_text(nuclide_elem, "name")) nuc = Nuclide.from_xml(nuclide_elem, root, this_q) chain.add_nuclide(nuc) diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index 60e3e5317f1..95881483474 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -14,6 +14,7 @@ from openmc.checkvalue import check_type from openmc.stats import Univariate +from .._xml import get_elem_list, get_text __all__ = [ "DecayTuple", "ReactionTuple", "Nuclide", "FissionYield", @@ -225,38 +226,39 @@ def from_xml(cls, element, root=None, fission_q=None): """ nuc = cls() - nuc.name = element.get('name') + nuc.name = get_text(element, "name") # Check for half-life - if 'half_life' in element.attrib: - nuc.half_life = float(element.get('half_life')) - nuc.decay_energy = float(element.get('decay_energy', '0')) + half_life = get_text(element, "half_life") + if half_life is not None: + nuc.half_life = float(half_life) + nuc.decay_energy = float(get_text(element, "decay_energy", 0.0)) # Check for decay paths for decay_elem in element.iter('decay'): - d_type = decay_elem.get('type') - target = decay_elem.get('target') + d_type = get_text(decay_elem, "type") + target = get_text(decay_elem, "target") if target is not None and target.lower() == "nothing": target = None - branching_ratio = float(decay_elem.get('branching_ratio')) + branching_ratio = float(get_text(decay_elem, "branching_ratio")) nuc.decay_modes.append(DecayTuple(d_type, target, branching_ratio)) # Check for sources for src_elem in element.iter('source'): - particle = src_elem.get('particle') + particle = get_text(src_elem, "particle") distribution = Univariate.from_xml_element(src_elem) nuc.sources[particle] = distribution # Check for reaction paths for reaction_elem in element.iter('reaction'): - r_type = reaction_elem.get('type') - Q = float(reaction_elem.get('Q', '0')) - branching_ratio = float(reaction_elem.get('branching_ratio', '1')) + r_type = get_text(reaction_elem, "type") + Q = float(get_text(reaction_elem, "Q", 0.0)) + branching_ratio = float(get_text(reaction_elem, "branching_ratio", 1.0)) # If the type is not fission, get target and Q value, otherwise # just set null values if r_type != 'fission': - target = reaction_elem.get('target') + target = get_text(reaction_elem, "target") if target is not None and target.lower() == "nothing": target = None else: @@ -271,7 +273,7 @@ def from_xml(cls, element, root=None, fission_q=None): fpy_elem = element.find('neutron_fission_yields') if fpy_elem is not None: # Check for use of FPY from other nuclide - parent = fpy_elem.get('parent') + parent = get_text(fpy_elem, "parent") if parent is not None: assert root is not None fpy_elem = root.find( @@ -529,9 +531,9 @@ def from_xml_element(cls, element): """ all_yields = {} for yield_elem in element.iter("fission_yields"): - energy = float(yield_elem.get("energy")) - products = yield_elem.find("products").text.split() - yields = map(float, yield_elem.find("data").text.split()) + energy = float(get_text(yield_elem, "energy")) + products = get_elem_list(yield_elem, "products", str) or [] + yields = get_elem_list(yield_elem, "data", float) or [] # Get a map of products to their corresponding yield all_yields[energy] = dict(zip(products, yields)) diff --git a/openmc/filter.py b/openmc/filter.py index 53fec898fcc..6a666d2a02c 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -17,7 +17,7 @@ from .mixin import IDManagerMixin from .surface import Surface from .universe import UniverseBase -from ._xml import get_text +from ._xml import get_elem_list, get_text _FILTER_TYPES = ( @@ -259,17 +259,15 @@ def from_xml_element(cls, elem, **kwargs): Filter object """ - filter_type = elem.get('type') - if filter_type is None: - filter_type = elem.find('type').text + filter_type = get_text(elem, "type") # If the filter type matches this class's short_name, then # there is no overridden from_xml_element method if filter_type == cls.short_name.lower(): # Get bins from element -- the default here works for any filters # that just store a list of bins that can be represented as integers - filter_id = int(elem.get('id')) - bins = [int(x) for x in get_text(elem, 'bins').split()] + filter_id = int(get_text(elem, "id")) + bins = get_elem_list(elem, "bins", int) or [] return cls(bins, filter_id=filter_id) # Search through all subclasses and find the one matching the HDF5 @@ -701,8 +699,8 @@ def to_xml_element(self): @classmethod def from_xml_element(cls, elem, **kwargs): - filter_id = int(elem.get('id')) - bins = [int(x) for x in get_text(elem, 'bins').split()] + filter_id = int(get_text(elem, "id")) + bins = get_elem_list(elem, "bins", int) or [] cell_instances = list(zip(bins[::2], bins[1::2])) return cls(cell_instances, filter_id=filter_id) @@ -784,8 +782,8 @@ def from_hdf5(cls, group, **kwargs): @classmethod def from_xml_element(cls, elem, **kwargs): - filter_id = int(elem.get('id')) - bins = get_text(elem, 'bins').split() + filter_id = int(get_text(elem, "id")) + bins = get_elem_list(elem, "bins", str) or [] return cls(bins, filter_id=filter_id) @@ -1004,12 +1002,12 @@ def to_xml_element(self): def from_xml_element(cls, elem: ET.Element, **kwargs) -> MeshFilter: mesh_id = int(get_text(elem, 'bins')) mesh_obj = kwargs['meshes'][mesh_id] - filter_id = int(elem.get('id')) + filter_id = int(get_text(elem, "id")) out = cls(mesh_obj, filter_id=filter_id) - translation = elem.get('translation') + translation = get_elem_list(elem, "translation", float) or [] if translation: - out.translation = [float(x) for x in translation.split()] + out.translation = translation return out @@ -1149,16 +1147,16 @@ def to_xml_element(self): @classmethod def from_xml_element(cls, elem: ET.Element, **kwargs) -> MeshMaterialFilter: - filter_id = int(elem.get('id')) - mesh_id = int(elem.get('mesh')) + filter_id = int(get_text(elem, "id")) + mesh_id = int(get_text(elem, "mesh")) mesh_obj = kwargs['meshes'][mesh_id] - bins = [int(x) for x in get_text(elem, 'bins').split()] + bins = get_elem_list(elem, "bins", int) or [] bins = list(zip(bins[::2], bins[1::2])) out = cls(mesh_obj, bins, filter_id=filter_id) - translation = elem.get('translation') + translation = get_elem_list(elem, "translation", float) or [] if translation: - out.translation = [float(x) for x in translation.split()] + out.translation = translation return out @classmethod @@ -1557,8 +1555,8 @@ def to_xml_element(self): @classmethod def from_xml_element(cls, elem, **kwargs): - filter_id = int(elem.get('id')) - bins = [float(x) for x in get_text(elem, 'bins').split()] + filter_id = int(get_text(elem, "id")) + bins = get_elem_list(elem, "bins", float) or [] return cls(bins, filter_id=filter_id) @@ -2447,12 +2445,13 @@ def to_xml_element(self): @classmethod def from_xml_element(cls, elem, **kwargs): - filter_id = int(elem.get('id')) - energy = [float(x) for x in get_text(elem, 'energy').split()] - y = [float(x) for x in get_text(elem, 'y').split()] + filter_id = int(get_text(elem, "id")) + energy = get_elem_list(elem, "energy", float) or [] + y = get_elem_list(elem, "y", float) or [] out = cls(energy, y, filter_id=filter_id) - if elem.find('interpolation') is not None: - out.interpolation = elem.find('interpolation').text + interpolation = get_text(elem, "interpolation") + if interpolation is not None: + out.interpolation = interpolation return out def can_merge(self, other): diff --git a/openmc/filter_expansion.py b/openmc/filter_expansion.py index cdb2f20e06b..b79c8fc79e7 100644 --- a/openmc/filter_expansion.py +++ b/openmc/filter_expansion.py @@ -4,6 +4,7 @@ import openmc.checkvalue as cv from .filter import Filter +from ._xml import get_text class ExpansionFilter(Filter): @@ -49,8 +50,8 @@ def to_xml_element(self): @classmethod def from_xml_element(cls, elem, **kwargs): - filter_id = int(elem.get('id')) - order = int(elem.find('order').text) + filter_id = int(get_text(elem, "id")) + order = int(get_text(elem, "order")) return cls(order, filter_id=filter_id) def merge(self, other): @@ -263,11 +264,11 @@ def to_xml_element(self): @classmethod def from_xml_element(cls, elem, **kwargs): - filter_id = int(elem.get('id')) - order = int(elem.find('order').text) - axis = elem.find('axis').text - minimum = float(elem.find('min').text) - maximum = float(elem.find('max').text) + filter_id = int(get_text(elem, "id")) + order = int(get_text(elem, "order")) + axis = get_text(elem, "axis") + minimum = float(get_text(elem, "min")) + maximum = float(get_text(elem, "max")) return cls(order, axis, minimum, maximum, filter_id=filter_id) @@ -362,10 +363,10 @@ def to_xml_element(self): @classmethod def from_xml_element(cls, elem, **kwargs): - filter_id = int(elem.get('id')) - order = int(elem.find('order').text) + filter_id = int(get_text(elem, "id")) + order = int(get_text(elem, "order")) filter = cls(order, filter_id=filter_id) - filter.cosine = elem.get('cosine') + filter.cosine = get_text(elem, "cosine") return filter @@ -518,11 +519,11 @@ def to_xml_element(self): @classmethod def from_xml_element(cls, elem, **kwargs): - filter_id = int(elem.get('id')) - order = int(elem.find('order').text) - x = float(elem.find('x').text) - y = float(elem.find('y').text) - r = float(elem.find('r').text) + filter_id = int(get_text(elem, "id")) + order = int(get_text(elem, "order")) + x = float(get_text(elem, "x")) + y = float(get_text(elem, "y")) + r = float(get_text(elem, "r")) return cls(order, x, y, r, filter_id=filter_id) diff --git a/openmc/lattice.py b/openmc/lattice.py index 51806856051..f0e8b40b0d2 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -10,7 +10,7 @@ import openmc import openmc.checkvalue as cv -from ._xml import get_text +from ._xml import get_elem_list, get_text from .mixin import IDManagerMixin @@ -959,18 +959,17 @@ def from_xml_element(cls, elem, get_universe): lat_id = int(get_text(elem, 'id')) name = get_text(elem, 'name') lat = cls(lat_id, name) - lat.lower_left = [float(i) - for i in get_text(elem, 'lower_left').split()] - lat.pitch = [float(i) for i in get_text(elem, 'pitch').split()] + lat.lower_left = get_elem_list(elem, "lower_left", float) + lat.pitch = get_elem_list(elem, "pitch", float) outer = get_text(elem, 'outer') if outer is not None: lat.outer = get_universe(int(outer)) # Get array of universes - dimension = get_text(elem, 'dimension').split() + dimension = get_elem_list(elem, 'dimension', int) shape = np.array(dimension, dtype=int)[::-1] - uarray = np.array([get_universe(int(i)) for i in - get_text(elem, 'universes').split()]) + universes = get_elem_list(elem, 'universes', int) + uarray = np.array([get_universe(u) for u in universes]) uarray.shape = shape lat.universes = uarray return lat @@ -1530,8 +1529,8 @@ def from_xml_element(cls, elem, get_universe): lat_id = int(get_text(elem, 'id')) name = get_text(elem, 'name') lat = cls(lat_id, name) - lat.center = [float(i) for i in get_text(elem, 'center').split()] - lat.pitch = [float(i) for i in get_text(elem, 'pitch').split()] + lat.center = get_elem_list(elem, "center", float) + lat.pitch = get_elem_list(elem, "pitch", float) lat.orientation = get_text(elem, 'orientation', 'y') outer = get_text(elem, 'outer') if outer is not None: @@ -1548,8 +1547,8 @@ def from_xml_element(cls, elem, get_universe): univs = [deepcopy(univs) for i in range(n_axial)] # Get flat array of universes - uarray = np.array([get_universe(int(i)) for i in - get_text(elem, 'universes').split()]) + universes = get_elem_list(elem, "universes", int) + uarray = np.array([get_universe(u) for u in universes]) # Fill nested lists j = 0 diff --git a/openmc/material.py b/openmc/material.py index db67b709ca0..0afe5b670cf 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -17,7 +17,7 @@ import openmc import openmc.data import openmc.checkvalue as cv -from ._xml import clean_indentation +from ._xml import clean_indentation, get_elem_list, get_text from .mixin import IDManagerMixin from .utility_funcs import input_path from . import waste @@ -1672,50 +1672,55 @@ def from_xml_element(cls, elem: ET.Element) -> Material: Material generated from XML element """ - mat_id = int(elem.get('id')) + mat_id = int(get_text(elem, 'id')) + # Add NCrystal material from cfg string - if "cfg" in elem.attrib: - cfg = elem.get("cfg") + cfg = get_text(elem, "cfg") + if cfg is not None: return Material.from_ncrystal(cfg, material_id=mat_id) mat = cls(mat_id) - mat.name = elem.get('name') + mat.name = get_text(elem, 'name') - if "temperature" in elem.attrib: - mat.temperature = float(elem.get("temperature")) + temperature = get_text(elem, "temperature") + if temperature is not None: + mat.temperature = float(temperature) - if 'volume' in elem.attrib: - mat.volume = float(elem.get('volume')) + volume = get_text(elem, "volume") + if volume is not None: + mat.volume = float(volume) # Get each nuclide for nuclide in elem.findall('nuclide'): - name = nuclide.attrib['name'] + name = get_text(nuclide, "name") if 'ao' in nuclide.attrib: mat.add_nuclide(name, float(nuclide.attrib['ao'])) elif 'wo' in nuclide.attrib: mat.add_nuclide(name, float(nuclide.attrib['wo']), 'wo') # Get depletable attribute - mat.depletable = elem.get('depletable') in ('true', '1') + depletable = get_text(elem, "depletable") + mat.depletable = depletable in ('true', '1') # Get each S(a,b) table for sab in elem.findall('sab'): - fraction = float(sab.get('fraction', 1.0)) - mat.add_s_alpha_beta(sab.get('name'), fraction) + fraction = float(get_text(sab, "fraction", 1.0)) + name = get_text(sab, "name") + mat.add_s_alpha_beta(name, fraction) # Get total material density density = elem.find('density') - units = density.get('units') + units = get_text(density, "units") if units == 'sum': mat.set_density(units) else: - value = float(density.get('value')) + value = float(get_text(density, 'value')) mat.set_density(units, value) # Check for isotropic scattering nuclides - isotropic = elem.find('isotropic') + isotropic = get_elem_list(elem, "isotropic", str) if isotropic is not None: - mat.isotropic = isotropic.text.split() + mat.isotropic = isotropic return mat @@ -1982,9 +1987,9 @@ def from_xml_element(cls, elem) -> Materials: materials.append(Material.from_xml_element(material)) # Check for cross sections settings - xs = elem.find('cross_sections') + xs = get_text(elem, "cross_sections") if xs is not None: - materials.cross_sections = xs.text + materials.cross_sections = xs return materials diff --git a/openmc/mesh.py b/openmc/mesh.py index 33970288440..2e9abd1b65c 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -16,7 +16,7 @@ import openmc.checkvalue as cv from openmc.checkvalue import PathLike from openmc.utility_funcs import change_directory -from ._xml import get_text +from ._xml import get_elem_list, get_text from .mixin import IDManagerMixin from .surface import _BOUNDARY_TYPES from .utility_funcs import input_path @@ -1187,21 +1187,21 @@ def from_xml_element(cls, elem: ET.Element): mesh_id = int(get_text(elem, 'id')) mesh = cls(mesh_id=mesh_id) - dimension = get_text(elem, 'dimension') + dimension = get_elem_list(elem, "dimension", int) if dimension is not None: - mesh.dimension = [int(x) for x in dimension.split()] + mesh.dimension = dimension - lower_left = get_text(elem, 'lower_left') + lower_left = get_elem_list(elem, "lower_left", float) if lower_left is not None: - mesh.lower_left = [float(x) for x in lower_left.split()] + mesh.lower_left = lower_left - upper_right = get_text(elem, 'upper_right') + upper_right = get_elem_list(elem, "upper_right", float) if upper_right is not None: - mesh.upper_right = [float(x) for x in upper_right.split()] + mesh.upper_right = upper_right - width = get_text(elem, 'width') + width = get_elem_list(elem, "width", float) if width is not None: - mesh.width = [float(x) for x in width.split()] + mesh.width = width return mesh @@ -1507,9 +1507,9 @@ def from_xml_element(cls, elem: ET.Element): """ mesh_id = int(get_text(elem, 'id')) mesh = cls(mesh_id=mesh_id) - mesh.x_grid = [float(x) for x in get_text(elem, 'x_grid').split()] - mesh.y_grid = [float(y) for y in get_text(elem, 'y_grid').split()] - mesh.z_grid = [float(z) for z in get_text(elem, 'z_grid').split()] + mesh.x_grid = get_elem_list(elem, "x_grid", float) + mesh.y_grid = get_elem_list(elem, "y_grid", float) + mesh.z_grid = get_elem_list(elem, "z_grid", float) return mesh @@ -1923,10 +1923,10 @@ def from_xml_element(cls, elem: ET.Element): mesh_id = int(get_text(elem, 'id')) mesh = cls( - r_grid = [float(x) for x in get_text(elem, "r_grid").split()], - phi_grid = [float(x) for x in get_text(elem, "phi_grid").split()], - z_grid = [float(x) for x in get_text(elem, "z_grid").split()], - origin = [float(x) for x in get_text(elem, "origin", default=[0., 0., 0.]).split()], + r_grid = get_elem_list(elem, "r_grid", float), + phi_grid = get_elem_list(elem, "phi_grid", float), + z_grid = get_elem_list(elem, "z_grid", float), + origin = get_elem_list(elem, "origin", float) or [0., 0., 0.], mesh_id=mesh_id, ) @@ -2296,10 +2296,10 @@ def from_xml_element(cls, elem: ET.Element): mesh_id = int(get_text(elem, 'id')) mesh = cls( mesh_id=mesh_id, - r_grid = [float(x) for x in get_text(elem, "r_grid").split()], - theta_grid = [float(x) for x in get_text(elem, "theta_grid").split()], - phi_grid = [float(x) for x in get_text(elem, "phi_grid").split()], - origin = [float(x) for x in get_text(elem, "origin", default=[0., 0., 0.]).split()], + r_grid = get_elem_list(elem, "r_grid", float), + theta_grid = get_elem_list(elem, "theta_grid", float), + phi_grid = get_elem_list(elem, "phi_grid", float), + origin = get_elem_list(elem, "origin", float) or [0., 0., 0.], ) return mesh @@ -2842,7 +2842,7 @@ def from_xml_element(cls, elem: ET.Element): filename = get_text(elem, 'filename') library = get_text(elem, 'library') length_multiplier = float(get_text(elem, 'length_multiplier', 1.0)) - options = elem.get('options') + options = get_text(elem, "options") return cls(filename, library, mesh_id, '', length_multiplier, options) diff --git a/openmc/plots.py b/openmc/plots.py index 34dde84e415..072a9a319e3 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -10,7 +10,7 @@ import openmc.checkvalue as cv from openmc.checkvalue import PathLike -from ._xml import clean_indentation, get_elem_tuple, get_text +from ._xml import clean_indentation, get_elem_list, get_text from .mixin import IDManagerMixin _BASES = {'xy', 'xz', 'yz'} @@ -944,64 +944,61 @@ def from_xml_element(cls, elem): Plot object """ - plot_id = int(elem.get("id")) + plot_id = int(get_text(elem, "id")) name = get_text(elem, 'name', '') plot = cls(plot_id, name) if "filename" in elem.keys(): - plot.filename = elem.get("filename") - plot.color_by = elem.get("color_by") - plot.type = elem.get("type") + plot.filename = get_text(elem, "filename") + plot.color_by = get_text(elem, "color_by") + plot.type = get_text(elem, "type") if plot.type == 'slice': - plot.basis = elem.get("basis") + plot.basis = get_text(elem, "basis") - plot.origin = get_elem_tuple(elem, "origin", float) - plot.width = get_elem_tuple(elem, "width", float) - plot.pixels = get_elem_tuple(elem, "pixels") - plot._background = get_elem_tuple(elem, "background") + plot.origin = tuple(get_elem_list(elem, "origin", float)) + plot.width = tuple(get_elem_list(elem, "width", float)) + plot.pixels = tuple(get_elem_list(elem, "pixels")) + background = get_elem_list(elem, "background") + if background is not None: + plot._background = tuple(background) # Set plot colors colors = {} for color_elem in elem.findall("color"): - uid = int(color_elem.get("id")) - colors[uid] = tuple([int(x) - for x in color_elem.get("rgb").split()]) + uid = int(get_text(color_elem, "id")) + colors[uid] = tuple(get_elem_list(color_elem, "rgb", int)) plot.colors = colors # Set masking information mask_elem = elem.find("mask") if mask_elem is not None: - plot.mask_components = [ - int(x) for x in mask_elem.get("components").split()] - background = mask_elem.get("background") + plot.mask_components = get_elem_list(mask_elem, "components", int) + background = get_elem_list(mask_elem, "background", int) if background is not None: - plot.mask_background = tuple( - [int(x) for x in background.split()]) + plot.mask_background = tuple(background) # show overlaps - overlap_elem = elem.find("show_overlaps") - if overlap_elem is not None: - plot.show_overlaps = (overlap_elem.text in ('true', '1')) - overlap_color = get_elem_tuple(elem, "overlap_color") + overlap = get_text(elem, "show_overlaps") + if overlap is not None: + plot.show_overlaps = (overlap in ('true', '1')) + overlap_color = get_elem_list(elem, "overlap_color", int) if overlap_color is not None: - plot.overlap_color = overlap_color + plot.overlap_color = tuple(overlap_color) # Set universe level - level = elem.find("level") + level = get_text(elem, "level") if level is not None: - plot.level = int(level.text) + plot.level = int(level) # Set meshlines mesh_elem = elem.find("meshlines") if mesh_elem is not None: - meshlines = {'type': mesh_elem.get('meshtype')} + meshlines = {'type': get_text(mesh_elem, "meshtype")} if 'id' in mesh_elem.keys(): - meshlines['id'] = int(mesh_elem.get('id')) + meshlines['id'] = int(get_text(mesh_elem, "id")) if 'linewidth' in mesh_elem.keys(): - meshlines['linewidth'] = int(mesh_elem.get('linewidth')) + meshlines['linewidth'] = int(get_text(mesh_elem, "linewidth")) if 'color' in mesh_elem.keys(): - meshlines['color'] = tuple( - [int(x) for x in mesh_elem.get('color').split()] - ) + meshlines['color'] = tuple(get_elem_list(mesh_elem, "color", int)) plot.meshlines = meshlines return plot @@ -1259,38 +1256,39 @@ def _read_xml_attributes(self, elem): None """ - if "filename" in elem.keys(): - self.filename = elem.get("filename") - self.color_by = elem.get("color_by") + filename = get_text(elem, "filename") + if filename is not None: + self.filename = filename + self.color_by = get_text(elem, "color_by") - horizontal_fov = elem.find("horizontal_field_of_view") + horizontal_fov = get_text(elem, "horizontal_field_of_view") if horizontal_fov is not None: - self.horizontal_field_of_view = float(horizontal_fov.text) + self.horizontal_field_of_view = float(horizontal_fov) - if (tmp := elem.find("orthographic_width")) is not None: - self.orthographic_width = float(tmp) + orthographic_width = get_text(elem, "orthographic_width") + if orthographic_width is not None: + self.orthographic_width = float(orthographic_width) - self.pixels = get_elem_tuple(elem, "pixels") - self.camera_position = get_elem_tuple(elem, "camera_position", float) - self.look_at = get_elem_tuple(elem, "look_at", float) + self.pixels = tuple(get_elem_list(elem, "pixels", int)) + self.camera_position = tuple(get_elem_list(elem, "camera_position", float)) + self.look_at = tuple(get_elem_list(elem, "look_at", float)) - if elem.find("background") is not None: - self.background = get_elem_tuple(elem, "background") + background = get_elem_list(elem, "background", int) + if background is not None: + self.background = tuple(background) # Set masking information if (mask_elem := elem.find("mask")) is not None: - mask_components = [int(x) - for x in mask_elem.get("components").split()] + mask_components = get_elem_list(mask_elem, "components", int) # TODO: set mask components(needs geometry information) - background = mask_elem.get("background") + background = get_elem_list(mask_elem, "background", int) if background is not None: - self.mask_background = tuple( - [int(x) for x in background.split()]) + self.mask_background = tuple(background) # Set universe level - level = elem.find("level") + level = get_text(elem, "level") if level is not None: - self.level = int(level.text) + self.level = int(level) class WireframeRayTracePlot(RayTracePlot): @@ -1515,7 +1513,7 @@ def from_xml_element(cls, elem): """ - plot_id = int(elem.get("id")) + plot_id = int(get_text(elem, "id")) plot_name = get_text(elem, 'name', '') plot = cls(plot_id, plot_name) plot.type = "wireframe_raytrace" @@ -1523,19 +1521,18 @@ def from_xml_element(cls, elem): plot._read_xml_attributes(elem) # Attempt to get wireframe thickness.May not be present - wireframe_thickness = elem.find("wireframe_thickness") + wireframe_thickness = get_text(elem, "wireframe_thickness") if wireframe_thickness is not None: - plot.wireframe_thickness = int(wireframe_thickness.text) - wireframe_color = elem.get("wireframe_color") + plot.wireframe_thickness = int(wireframe_thickness) + wireframe_color = get_elem_list(elem, "wireframe_color", int) if wireframe_color: - plot.wireframe_color = [int(item) for item in wireframe_color] + plot.wireframe_color = wireframe_color # Set plot colors for color_elem in elem.findall("color"): - uid = int(color_elem.get("id")) - plot.colors[uid] = tuple(int(i) - for i in get_text(color_elem, 'rgb').split()) - plot.xs[uid] = float(color_elem.get("xs")) + uid = int(get_text(color_elem, "id")) + plot.colors[uid] = tuple(get_elem_list(color_elem, "rgb", int)) + plot.xs[uid] = float(get_text(color_elem, "xs")) return plot @@ -1693,15 +1690,17 @@ def to_xml_element(self): def _read_phong_attributes(self, elem): """Read attributes specific to the Phong plot from an XML element""" - if elem.find('light_position') is not None: - self.light_position = get_elem_tuple(elem, 'light_position', float) + light_position = get_elem_list(elem, 'light_position', float) + if light_position is not None: + self.light_position = tuple(light_position) - diffuse_fraction = elem.find('diffuse_fraction') + diffuse_fraction = get_text(elem, "diffuse_fraction") if diffuse_fraction is not None: - self.diffuse_fraction = float(diffuse_fraction.text) + self.diffuse_fraction = float(diffuse_fraction) - if elem.find('opaque_ids') is not None: - self.opaque_domains = list(get_elem_tuple(elem, 'opaque_ids', int)) + opaque_domains = get_elem_list(elem, 'opaque_ids', int) + if opaque_domains is not None: + self.opaque_domains = opaque_domains @classmethod def from_xml_element(cls, elem): @@ -1719,7 +1718,7 @@ def from_xml_element(cls, elem): """ - plot_id = int(elem.get("id")) + plot_id = int(get_text(elem, "id")) plot_name = get_text(elem, 'name', '') plot = cls(plot_id, plot_name) plot.type = "solid_raytrace" @@ -1729,8 +1728,8 @@ def from_xml_element(cls, elem): # Set plot colors for color_elem in elem.findall("color"): - uid = color_elem.get("id") - plot.colors[uid] = get_elem_tuple(color_elem, "rgb") + uid = get_text(color_elem, "id") + plot.colors[uid] = tuple(get_elem_list(color_elem, "rgb", int)) return plot @@ -1897,7 +1896,7 @@ def from_xml_element(cls, elem): # Generate each plot plots = cls() for e in elem.findall('plot'): - plot_type = e.get('type') + plot_type = get_text(e, "type") if plot_type == 'wireframe_raytrace': plots.append(WireframeRayTracePlot.from_xml_element(e)) elif plot_type == 'solid_raytrace': diff --git a/openmc/settings.py b/openmc/settings.py index 327faf544c0..ce3743ff5d1 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -11,7 +11,7 @@ import openmc.checkvalue as cv from openmc.checkvalue import PathLike from openmc.stats.multivariate import MeshSpatial -from ._xml import clean_indentation, get_text +from ._xml import clean_indentation, get_elem_list, get_text from .mesh import _read_meshes, RegularMesh, MeshBase from .source import SourceBase, MeshSource, IndependentSource from .utility_funcs import input_path @@ -1791,23 +1791,21 @@ def _output_from_xml_element(self, root): def _statepoint_from_xml_element(self, root): elem = root.find('state_point') if elem is not None: - text = get_text(elem, 'batches') - if text is not None: - self.statepoint['batches'] = [int(x) for x in text.split()] + batches = get_elem_list(elem, "batches", int) + if batches is not None: + self.statepoint['batches'] = batches def _sourcepoint_from_xml_element(self, root): elem = root.find('source_point') if elem is not None: for key in ('separate', 'write', 'overwrite_latest', 'batches', 'mcpl'): - value = get_text(elem, key) - if value is not None: - if key in ('separate', 'write', 'mcpl'): - value = value in ('true', '1') - elif key == 'overwrite_latest': - value = value in ('true', '1') + if key in ('separate', 'write', 'mcpl', 'overwrite_latest'): + value = get_text(elem, key) in ('true', '1') + if key == 'overwrite_latest': key = 'overwrite' - else: - value = [int(x) for x in value.split()] + else: + value = get_elem_list(elem, key, int) + if value is not None: self.sourcepoint[key] = value def _surf_source_read_from_xml_element(self, root): @@ -1824,11 +1822,12 @@ def _surf_source_write_from_xml_element(self, root): if elem is None: return for key in ('surface_ids', 'max_particles', 'max_source_files', 'mcpl', 'cell', 'cellto', 'cellfrom'): - value = get_text(elem, key) + if key == 'surface_ids': + value = get_elem_list(elem, key, int) + else: + value = get_text(elem, key) if value is not None: - if key == 'surface_ids': - value = [int(x) for x in value.split()] - elif key == 'mcpl': + if key == 'mcpl': value = value in ('true', '1') elif key in ('max_particles', 'max_source_files', 'cell', 'cellfrom', 'cellto'): value = int(value) @@ -1958,22 +1957,21 @@ def _temperature_from_xml_element(self, root): text = get_text(root, 'temperature_method') if text is not None: self.temperature['method'] = text - text = get_text(root, 'temperature_range') + text = get_elem_list(root, "temperature_range", float) if text is not None: - self.temperature['range'] = [float(x) for x in text.split()] + self.temperature['range'] = text text = get_text(root, 'temperature_multipole') if text is not None: self.temperature['multipole'] = text in ('true', '1') def _trace_from_xml_element(self, root): - text = get_text(root, 'trace') + text = get_elem_list(root, "trace", int) if text is not None: - self.trace = [int(x) for x in text.split()] + self.trace = text def _track_from_xml_element(self, root): - text = get_text(root, 'track') - if text is not None: - values = [int(x) for x in text.split()] + values = get_elem_list(root, "track", int) + if values is not None: self.track = list(zip(values[::3], values[1::3], values[2::3])) def _ufs_mesh_from_xml_element(self, root, meshes): @@ -1990,14 +1988,15 @@ def _resonance_scattering_from_xml_element(self, root): if elem is not None: keys = ('enable', 'method', 'energy_min', 'energy_max', 'nuclides') for key in keys: - value = get_text(elem, key) + if key == 'nuclides': + value = get_elem_list(elem, key, str) + else: + value = get_text(elem, key) if value is not None: if key == 'enable': value = value in ('true', '1') elif key in ('energy_min', 'energy_max'): value = float(value) - elif key == 'nuclides': - value = value.split() self.resonance_scattering[key] = value def _create_fission_neutrons_from_xml_element(self, root): @@ -2109,8 +2108,8 @@ def _random_ray_from_xml_element(self, root): mesh = MeshBase.from_xml_element(mesh_elem) domains = [] for domain_elem in mesh_elem.findall('domain'): - domain_id = int(domain_elem.get('id')) - domain_type = domain_elem.get('type') + domain_id = int(get_text(domain_elem, "id")) + domain_type = get_text(domain_elem, "type") if domain_type == 'material': domain = openmc.Material(domain_id) elif domain_type == 'cell': diff --git a/openmc/source.py b/openmc/source.py index 87e734e9a25..c463ccb2756 100644 --- a/openmc/source.py +++ b/openmc/source.py @@ -18,7 +18,7 @@ from openmc.checkvalue import PathLike from openmc.stats.multivariate import UnitSphere, Spatial from openmc.stats.univariate import Univariate -from ._xml import get_text +from ._xml import get_elem_list, get_text from .mesh import MeshBase, StructuredMesh, UnstructuredMesh from .utility_funcs import input_path @@ -210,7 +210,7 @@ def _get_constraints(elem: ET.Element) -> dict[str, Any]: constraints = {} domain_type = get_text(elem, "domain_type") if domain_type is not None: - domain_ids = [int(x) for x in get_text(elem, "domain_ids").split()] + domain_ids = get_elem_list(elem, "domain_ids", int) # Instantiate some throw-away domains that are used by the # constructor to assign IDs @@ -224,13 +224,13 @@ def _get_constraints(elem: ET.Element) -> dict[str, Any]: domains = [openmc.Universe(uid) for uid in domain_ids] constraints['domains'] = domains - time_bounds = get_text(elem, "time_bounds") + time_bounds = get_elem_list(elem, "time_bounds", float) if time_bounds is not None: - constraints['time_bounds'] = [float(x) for x in time_bounds.split()] + constraints['time_bounds'] = time_bounds - energy_bounds = get_text(elem, "energy_bounds") + energy_bounds = get_elem_list(elem, "energy_bounds", float) if energy_bounds is not None: - constraints['energy_bounds'] = [float(x) for x in energy_bounds.split()] + constraints['energy_bounds'] = energy_bounds fissionable = get_text(elem, "fissionable") if fissionable is not None: diff --git a/openmc/stats/multivariate.py b/openmc/stats/multivariate.py index cd474fa9b28..222d2d18a56 100644 --- a/openmc/stats/multivariate.py +++ b/openmc/stats/multivariate.py @@ -10,7 +10,7 @@ import openmc import openmc.checkvalue as cv -from .._xml import get_text +from .._xml import get_elem_list, get_text from ..mesh import MeshBase from .univariate import PowerLaw, Uniform, Univariate @@ -152,9 +152,9 @@ def from_xml_element(cls, elem): """ mu_phi = cls() - uvw = get_text(elem, 'reference_uvw') + uvw = get_elem_list(elem, "reference_uvw", float) if uvw is not None: - mu_phi.reference_uvw = [float(x) for x in uvw.split()] + mu_phi.reference_uvw = uvw mu_phi.mu = Univariate.from_xml_element(elem.find('mu')) mu_phi.phi = Univariate.from_xml_element(elem.find('phi')) return mu_phi @@ -246,9 +246,9 @@ def from_xml_element(cls, elem: ET.Element): """ monodirectional = cls() - uvw = get_text(elem, 'reference_uvw') + uvw = get_elem_list(elem, "reference_uvw", float) if uvw is not None: - monodirectional.reference_uvw = [float(x) for x in uvw.split()] + monodirectional.reference_uvw = uvw return monodirectional @@ -504,7 +504,7 @@ def from_xml_element(cls, elem: ET.Element): r = Univariate.from_xml_element(elem.find('r')) cos_theta = Univariate.from_xml_element(elem.find('cos_theta')) phi = Univariate.from_xml_element(elem.find('phi')) - origin = [float(x) for x in elem.get('origin').split()] + origin = get_elem_list(elem, "origin", float) return cls(r, cos_theta, phi, origin=origin) @@ -626,7 +626,7 @@ def from_xml_element(cls, elem: ET.Element): r = Univariate.from_xml_element(elem.find('r')) phi = Univariate.from_xml_element(elem.find('phi')) z = Univariate.from_xml_element(elem.find('z')) - origin = [float(x) for x in elem.get('origin').split()] + origin = get_elem_list(elem, "origin", float) return cls(r, phi, z, origin=origin) @@ -743,18 +743,14 @@ def from_xml_element(cls, elem, meshes): """ - mesh_id = int(elem.get('mesh_id')) + mesh_id = int(get_text(elem, "mesh_id")) # check if this mesh has been read in from another location already if mesh_id not in meshes: raise ValueError(f'Could not locate mesh with ID "{mesh_id}"') - volume_normalized = elem.get("volume_normalized") volume_normalized = get_text(elem, 'volume_normalized').lower() == 'true' - strengths = get_text(elem, 'strengths') - if strengths is not None: - strengths = [float(b) for b in get_text(elem, 'strengths').split()] - + strengths = get_elem_list(elem, 'strengths', float) return cls(meshes[mesh_id], strengths, volume_normalized) @@ -860,12 +856,10 @@ def from_xml_element(cls, elem: ET.Element) -> PointCloud: """ - coord_data = get_text(elem, 'coords') - positions = np.array([float(b) for b in coord_data.split()]).reshape((-1, 3)) + coord_data = get_elem_list(elem, 'coords', float) + positions = np.array(coord_data).reshape((-1, 3)) - strengths = get_text(elem, 'strengths') - if strengths is not None: - strengths = [float(b) for b in strengths.split()] + strengths = get_elem_list(elem, 'strengths', float) return cls(positions, strengths) @@ -979,7 +973,7 @@ def from_xml_element(cls, elem: ET.Element): """ only_fissionable = get_text(elem, 'type') == 'fission' - params = [float(x) for x in get_text(elem, 'parameters').split()] + params = get_elem_list(elem, "parameters", float) lower_left = params[:len(params)//2] upper_right = params[len(params)//2:] return cls(lower_left, upper_right, only_fissionable) @@ -1046,7 +1040,7 @@ def from_xml_element(cls, elem: ET.Element): Point distribution generated from XML element """ - xyz = [float(x) for x in get_text(elem, 'parameters').split()] + xyz = get_elem_list(elem, "parameters", float) return cls(xyz) diff --git a/openmc/stats/univariate.py b/openmc/stats/univariate.py index e0475bf78fb..28d5e87ef9e 100644 --- a/openmc/stats/univariate.py +++ b/openmc/stats/univariate.py @@ -12,7 +12,7 @@ from scipy.integrate import trapezoid import openmc.checkvalue as cv -from .._xml import get_text +from .._xml import get_elem_list, get_text from ..mixin import EqualityMixin _INTERPOLATION_SCHEMES = { @@ -57,8 +57,7 @@ def from_xml_element(cls, elem): return Normal.from_xml_element(elem) elif distribution == 'muir': # Support older files where Muir had its own class - params = [float(x) for x in get_text(elem, 'parameters').split()] - return muir(*params) + return muir(*get_elem_list(elem, "parameters", float)) elif distribution == 'tabular': return Tabular.from_xml_element(elem) elif distribution == 'legendre': @@ -240,7 +239,7 @@ def from_xml_element(cls, elem: ET.Element): Discrete distribution generated from XML element """ - params = [float(x) for x in get_text(elem, 'parameters').split()] + params = get_elem_list(elem, "parameters", float) x = params[:len(params)//2] p = params[len(params)//2:] return cls(x, p) @@ -448,8 +447,8 @@ def from_xml_element(cls, elem: ET.Element): Uniform distribution generated from XML element """ - params = get_text(elem, 'parameters').split() - return cls(*map(float, params)) + params = get_elem_list(elem, "parameters", float) + return cls(*params) class PowerLaw(Univariate): @@ -557,8 +556,8 @@ def from_xml_element(cls, elem: ET.Element): Distribution generated from XML element """ - params = get_text(elem, 'parameters').split() - return cls(*map(float, params)) + params = get_elem_list(elem, "parameters", float) + return cls(*params) class Maxwell(Univariate): @@ -737,8 +736,8 @@ def from_xml_element(cls, elem: ET.Element): Watt distribution generated from XML element """ - params = get_text(elem, 'parameters').split() - return cls(*map(float, params)) + params = get_elem_list(elem, "parameters", float) + return cls(*params) class Normal(Univariate): @@ -827,8 +826,8 @@ def from_xml_element(cls, elem: ET.Element): Normal distribution generated from XML element """ - params = get_text(elem, 'parameters').split() - return cls(*map(float, params)) + params = get_elem_list(elem, "parameters", float) + return cls(*params) def muir(e0: float, m_rat: float, kt: float): @@ -1115,7 +1114,7 @@ def from_xml_element(cls, elem: ET.Element): """ interpolation = get_text(elem, 'interpolation') - params = [float(x) for x in get_text(elem, 'parameters').split()] + params = get_elem_list(elem, "parameters", float) m = (len(params) + 1)//2 # +1 for when len(params) is odd x = params[:m] p = params[m:] diff --git a/openmc/surface.py b/openmc/surface.py index 840c3125d88..4839783ffa8 100644 --- a/openmc/surface.py +++ b/openmc/surface.py @@ -13,6 +13,7 @@ from .mixin import IDManagerMixin, IDWarning from .region import Region, Intersection, Union from .bounding_box import BoundingBox +from ._xml import get_elem_list, get_text _BOUNDARY_TYPES = {'transmission', 'vacuum', 'reflective', 'periodic', 'white'} @@ -451,17 +452,17 @@ def from_xml_element(elem): """ # Determine appropriate class - surf_type = elem.get('type') + surf_type = get_text(elem, "type") cls = _SURFACE_CLASSES[surf_type] # Determine ID, boundary type, boundary albedo, coefficients kwargs = {} - kwargs['surface_id'] = int(elem.get('id')) - kwargs['boundary_type'] = elem.get('boundary', 'transmission') + kwargs['surface_id'] = int(get_text(elem, "id")) + kwargs['boundary_type'] = get_text(elem, "boundary", "transmission") if kwargs['boundary_type'] in _ALBEDO_BOUNDARIES: - kwargs['albedo'] = float(elem.get('albedo', 1.0)) - kwargs['name'] = elem.get('name') - coeffs = [float(x) for x in elem.get('coeffs').split()] + kwargs['albedo'] = float(get_text(elem, "albedo", 1.0)) + kwargs['name'] = get_text(elem, "name") + coeffs = get_elem_list(elem, "coeffs", float) kwargs.update(dict(zip(cls._coeff_keys, coeffs))) return cls(**kwargs) diff --git a/openmc/tallies.py b/openmc/tallies.py index 3a5b425198e..075b1e99117 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -15,7 +15,7 @@ import openmc import openmc.checkvalue as cv -from ._xml import clean_indentation, get_text +from ._xml import clean_indentation, get_elem_list, get_text from .mixin import IDManagerMixin from .mesh import MeshBase @@ -1006,8 +1006,8 @@ def from_xml_element(cls, elem, **kwargs): Tally object """ - tally_id = int(elem.get('id')) - name = elem.get('name', '') + tally_id = int(get_text(elem, "id")) + name = get_text(elem, "name", "") tally = cls(tally_id=tally_id, name=name) text = get_text(elem, 'multiply_density') @@ -1015,25 +1015,24 @@ def from_xml_element(cls, elem, **kwargs): tally.multiply_density = text in ('true', '1') # Read filters - filters_elem = elem.find('filters') - if filters_elem is not None: - filter_ids = [int(x) for x in filters_elem.text.split()] + filter_ids = get_elem_list(elem, "filters", int) + if filter_ids is not None: tally.filters = [kwargs['filters'][uid] for uid in filter_ids] # Read nuclides - nuclides_elem = elem.find('nuclides') - if nuclides_elem is not None: - tally.nuclides = nuclides_elem.text.split() + nuclides = get_elem_list(elem, "nuclides", str) + if nuclides is not None: + tally.nuclides = nuclides # Read scores - scores_elem = elem.find('scores') - if scores_elem is not None: - tally.scores = scores_elem.text.split() + scores = get_elem_list(elem, "scores", str) + if scores is not None: + tally.scores = scores # Set estimator - estimator_elem = elem.find('estimator') - if estimator_elem is not None: - tally.estimator = estimator_elem.text + estimator = get_text(elem, "estimator") + if estimator is not None: + tally.estimator = estimator # Read triggers tally.triggers = [ @@ -1042,9 +1041,9 @@ def from_xml_element(cls, elem, **kwargs): ] # Read tally derivative - deriv_elem = elem.find('derivative') - if deriv_elem is not None: - deriv_id = int(deriv_elem.text) + deriv = get_text(elem, "derivative") + if deriv is not None: + deriv_id = int(deriv) tally.derivative = kwargs['derivatives'][deriv_id] return tally diff --git a/openmc/tally_derivative.py b/openmc/tally_derivative.py index ff918c1b3c6..f7ba5dce58e 100644 --- a/openmc/tally_derivative.py +++ b/openmc/tally_derivative.py @@ -4,6 +4,7 @@ import openmc.checkvalue as cv from .mixin import EqualityMixin, IDManagerMixin +from ._xml import get_text class TallyDerivative(EqualityMixin, IDManagerMixin): @@ -122,8 +123,8 @@ def from_xml_element(cls, elem): Tally derivative object """ - derivative_id = int(elem.get("id")) - variable = elem.get("variable") - material = int(elem.get("material")) - nuclide = elem.get("nuclide") if variable == "nuclide_density" else None + derivative_id = int(get_text(elem, "id")) + variable = get_text(elem, "variable") + material = int(get_text(elem, "material")) + nuclide = get_text(elem, "nuclide") if variable == "nuclide_density" else None return cls(derivative_id, variable, material, nuclide) diff --git a/openmc/trigger.py b/openmc/trigger.py index be2537e3ac8..70b6b7a0398 100644 --- a/openmc/trigger.py +++ b/openmc/trigger.py @@ -5,6 +5,7 @@ import openmc.checkvalue as cv from .mixin import EqualityMixin +from ._xml import get_elem_list, get_text class Trigger(EqualityMixin): @@ -129,16 +130,16 @@ def from_xml_element(cls, elem: ET.Element): """ # Generate trigger object - trigger_type = elem.get("type") - threshold = float(elem.get("threshold")) - ignore_zeros = str(elem.get("ignore_zeros", "false")).lower() + trigger_type = get_text(elem, "type") + threshold = float(get_text(elem, "threshold")) + ignore_zeros = str(get_text(elem, "ignore_zeros", "false")).lower() # Try to convert to bool. Let Trigger error out on instantiation. ignore_zeros = ignore_zeros in ('true', '1') trigger = cls(trigger_type, threshold, ignore_zeros) # Add scores if present - scores = elem.get("scores") + scores = get_elem_list(elem, "scores", str) if scores is not None: - trigger.scores = scores.split() + trigger.scores = scores return trigger diff --git a/openmc/volume.py b/openmc/volume.py index df19def1eac..c44adf98a52 100644 --- a/openmc/volume.py +++ b/openmc/volume.py @@ -10,7 +10,7 @@ import openmc import openmc.checkvalue as cv -from openmc._xml import get_text +from openmc._xml import get_elem_list, get_text _VERSION_VOLUME = 1 @@ -375,13 +375,10 @@ def from_xml_element(cls, elem): """ domain_type = get_text(elem, "domain_type") - domain_ids = get_text(elem, "domain_ids").split() - ids = [int(x) for x in domain_ids] + ids = get_elem_list(elem, "domain_ids", int) samples = int(get_text(elem, "samples")) - lower_left = get_text(elem, "lower_left").split() - lower_left = tuple([float(x) for x in lower_left]) - upper_right = get_text(elem, "upper_right").split() - upper_right = tuple([float(x) for x in upper_right]) + lower_left = tuple(get_elem_list(elem, "lower_left", float)) + upper_right = tuple(get_elem_list(elem, "upper_right", float)) # Instantiate some throw-away domains that are used by the constructor # to assign IDs diff --git a/openmc/weight_windows.py b/openmc/weight_windows.py index 90856ebab7f..5d52a579a11 100644 --- a/openmc/weight_windows.py +++ b/openmc/weight_windows.py @@ -14,7 +14,7 @@ from openmc.mesh import MeshBase, RectilinearMesh, CylindricalMesh, SphericalMesh, UnstructuredMesh import openmc.checkvalue as cv from openmc.checkvalue import PathLike -from ._xml import get_text, clean_indentation +from ._xml import get_elem_list, get_text, clean_indentation from .mixin import IDManagerMixin from .utility_funcs import change_directory @@ -379,9 +379,9 @@ def from_xml_element(cls, elem: ET.Element, meshes: dict[int, MeshBase]) -> Self mesh = meshes[mesh_id] # Read all other parameters - lower_ww_bounds = [float(l) for l in get_text(elem, 'lower_ww_bounds').split()] - upper_ww_bounds = [float(u) for u in get_text(elem, 'upper_ww_bounds').split()] - e_bounds = [float(b) for b in get_text(elem, 'energy_bounds').split()] + lower_ww_bounds = get_elem_list(elem, "lower_ww_bounds", float) + upper_ww_bounds = get_elem_list(elem, "upper_ww_bounds", float) + e_bounds = get_elem_list(elem, "energy_bounds", float) particle_type = get_text(elem, 'particle_type') survival_ratio = float(get_text(elem, 'survival_ratio')) @@ -730,11 +730,8 @@ def from_xml_element(cls, elem: ET.Element, meshes: dict) -> Self: mesh_id = int(get_text(elem, 'mesh')) mesh = meshes[mesh_id] - - if (energy_bounds := get_text(elem, 'energy_bounds')) is not None: - energy_bounds = [float(x) for x in energy_bounds.split()] - else: - energy_bounds = None + + energy_bounds = get_elem_list(elem, "energy_bounds, float") particle_type = get_text(elem, 'particle_type') wwg = cls(mesh, energy_bounds, particle_type) From a34365396e8bd66698f5481474ebf17a5ed30d14 Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Mon, 11 Aug 2025 18:51:45 +0300 Subject: [PATCH 089/159] Remove unused special accessors for tallies (#3527) --- include/openmc/tallies/tally.h | 2 -- src/tallies/tally.cpp | 4 ---- 2 files changed, 6 deletions(-) diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index 48f678ced0f..8c088c46089 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -169,10 +169,8 @@ class Tally { // We need to have quick access to some filters. The following gives indices // for various filters that could be in the tally or C_NONE if they are not // present. - int energy_filter_ {C_NONE}; int energyout_filter_ {C_NONE}; int delayedgroup_filter_ {C_NONE}; - int cell_filter_ {C_NONE}; vector triggers_; diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index e737f79d354..ba183860269 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -487,10 +487,6 @@ void Tally::add_filter(Filter* filter) energyout_filter_ = filters_.size(); } else if (filter->type() == FilterType::DELAYED_GROUP) { delayedgroup_filter_ = filters_.size(); - } else if (filter->type() == FilterType::CELL) { - cell_filter_ = filters_.size(); - } else if (filter->type() == FilterType::ENERGY) { - energy_filter_ = filters_.size(); } filters_.push_back(filter_idx); } From f8fa751da0437ddf42a1f4873e5162a6fcb1c51e Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Wed, 13 Aug 2025 15:41:39 +0200 Subject: [PATCH 090/159] Adding 616 group structure (#3531) --- openmc/mgxs/__init__.py | 94 ++++++++++++++++++++++++++++++++++++++++- src/particle.cpp | 2 +- 2 files changed, 94 insertions(+), 2 deletions(-) diff --git a/openmc/mgxs/__init__.py b/openmc/mgxs/__init__.py index 901c1eabb6b..f8de84b37ea 100644 --- a/openmc/mgxs/__init__.py +++ b/openmc/mgxs/__init__.py @@ -21,7 +21,7 @@ - "ECCO-1968_" designed for fine group reactor cell calculations for fast, intermediate and thermal reactor applications ([SAR1990]_) - activation_ energy group structures "VITAMIN-J-42", "VITAMIN-J-175", - "TRIPOLI-315", "CCFE-709_" and "UKAEA-1102_" + "TRIPOLI-315", "LLNL-616", "CCFE-709_" and "UKAEA-1102_" .. _CASMO: http://large.stanford.edu/courses/2013/ph241/dalvi1/docs/c5.physor2006.pdf .. _SCALE44: https://www-nds.iaea.org/publications/indc/indc-czr-0001.pdf @@ -31,6 +31,7 @@ .. _SHEM-361: http://merlin.polymtl.ca/downloads/FP214.pdf .. _activation: https://fispact.ukaea.uk/wiki/Keyword:GETXS .. _VITAMIN-J-42: https://www.oecd-nea.org/dbdata/nds_jefreports/jefreport-10.pdf +.. _LLNL-616: https://fispact.ukaea.uk/manual/user_manual.pdf .. _CCFE-709: https://fispact.ukaea.uk/wiki/CCFE-709_group_structure .. _UKAEA-1102: https://fispact.ukaea.uk/wiki/UKAEA-1102_group_structure .. _ECCO-1968: https://serpent.vtt.fi/mediawiki/index.php/ECCO_1968-group_structure @@ -346,6 +347,97 @@ 4.06569e+06, 4.96585e+06, 6.06530e+06, 6.70319e+06, 7.40817e+06, 8.18730e+06, 9.04836e+06, 9.99999e+06, 1.16183e+07, 1.38403e+07, 1.49182e+07, 1.96403e+07]) +GROUP_STRUCTURES['LLNL-616'] = np.array([ + 1.0000e-5, 1.0471e-5, 1.0965e-5, 1.1482e-5, 1.2023e-5, 1.2589e-5, 1.3183e-5, + 1.3804e-5, 1.4454e-5, 1.5136e-5, 1.5849e-5, 1.6596e-5, 1.7378e-5, 1.8197e-5, + 1.9055e-5, 1.9953e-5, 2.0893e-5, 2.1878e-5, 2.2909e-5, 2.3988e-5, 2.5119e-5, + 2.6303e-5, 2.7542e-5, 2.8840e-5, 3.0200e-5, 3.1623e-5, 3.3113e-5, 3.4674e-5, + 3.6308e-5, 3.8019e-5, 3.9811e-5, 4.1687e-5, 4.3652e-5, 4.5709e-5, 4.7863e-5, + 5.0119e-5, 5.2481e-5, 5.4954e-5, 5.7544e-5, 6.0256e-5, 6.3096e-5, 6.6069e-5, + 6.9183e-5, 7.2444e-5, 7.5858e-5, 7.9433e-5, 8.3176e-5, 8.7096e-5, 9.1201e-5, + 9.5499e-5, 1.0000e-4, 1.0471e-4, 1.0965e-4, 1.1482e-4, 1.2023e-4, 1.2589e-4, + 1.3183e-4, 1.3804e-4, 1.4454e-4, 1.5136e-4, 1.5849e-4, 1.6596e-4, 1.7378e-4, + 1.8197e-4, 1.9055e-4, 1.9953e-4, 2.0893e-4, 2.1878e-4, 2.2909e-4, 2.3988e-4, + 2.5119e-4, 2.6303e-4, 2.7542e-4, 2.8840e-4, 3.0200e-4, 3.1623e-4, 3.3113e-4, + 3.4674e-4, 3.6308e-4, 3.8019e-4, 3.9811e-4, 4.1687e-4, 4.3652e-4, 4.5709e-4, + 4.7863e-4, 5.0119e-4, 5.2481e-4, 5.4954e-4, 5.7544e-4, 6.0256e-4, 6.3096e-4, + 6.6069e-4, 6.9183e-4, 7.2444e-4, 7.5858e-4, 7.9433e-4, 8.3176e-4, 8.7096e-4, + 9.1201e-4, 9.5499e-4, 1.0000e-3, 1.0471e-3, 1.0965e-3, 1.1482e-3, 1.2023e-3, + 1.2589e-3, 1.3183e-3, 1.3804e-3, 1.4454e-3, 1.5136e-3, 1.5849e-3, 1.6596e-3, + 1.7378e-3, 1.8197e-3, 1.9055e-3, 1.9953e-3, 2.0893e-3, 2.1878e-3, 2.2909e-3, + 2.3988e-3, 2.5119e-3, 2.6303e-3, 2.7542e-3, 2.8840e-3, 3.0200e-3, 3.1623e-3, + 3.3113e-3, 3.4674e-3, 3.6308e-3, 3.8019e-3, 3.9811e-3, 4.1687e-3, 4.3652e-3, + 4.5709e-3, 4.7863e-3, 5.0119e-3, 5.2481e-3, 5.4954e-3, 5.7544e-3, 6.0256e-3, + 6.3096e-3, 6.6069e-3, 6.9183e-3, 7.2444e-3, 7.5858e-3, 7.9433e-3, 8.3176e-3, + 8.7096e-3, 9.1201e-3, 9.5499e-3, 1.0000e-2, 1.0471e-2, 1.0965e-2, 1.1482e-2, + 1.2023e-2, 1.2589e-2, 1.3183e-2, 1.3804e-2, 1.4454e-2, 1.5136e-2, 1.5849e-2, + 1.6596e-2, 1.7378e-2, 1.8197e-2, 1.9055e-2, 1.9953e-2, 2.0893e-2, 2.1878e-2, + 2.2909e-2, 2.3988e-2, 2.5119e-2, 2.6303e-2, 2.7542e-2, 2.8840e-2, 3.0200e-2, + 3.1623e-2, 3.3113e-2, 3.4674e-2, 3.6308e-2, 3.8019e-2, 3.9811e-2, 4.1687e-2, + 4.3652e-2, 4.5709e-2, 4.7863e-2, 5.0119e-2, 5.2481e-2, 5.4954e-2, 5.7544e-2, + 6.0256e-2, 6.3096e-2, 6.6069e-2, 6.9183e-2, 7.2444e-2, 7.5858e-2, 7.9433e-2, + 8.3176e-2, 8.7096e-2, 9.1201e-2, 9.5499e-2, 1.0000e-1, 1.0471e-1, 1.0965e-1, + 1.1482e-1, 1.2023e-1, 1.2589e-1, 1.3183e-1, 1.3804e-1, 1.4454e-1, 1.5136e-1, + 1.5849e-1, 1.6596e-1, 1.7378e-1, 1.8197e-1, 1.9055e-1, 1.9953e-1, 2.0893e-1, + 2.1878e-1, 2.2909e-1, 2.3988e-1, 2.5119e-1, 2.6303e-1, 2.7542e-1, 2.8840e-1, + 3.0200e-1, 3.1623e-1, 3.3113e-1, 3.4674e-1, 3.6308e-1, 3.8019e-1, 3.9811e-1, + 4.1687e-1, 4.3652e-1, 4.5709e-1, 4.7863e-1, 5.0119e-1, 5.2481e-1, 5.4954e-1, + 5.7544e-1, 6.0256e-1, 6.3096e-1, 6.6069e-1, 6.9183e-1, 7.2444e-1, 7.5858e-1, + 7.9433e-1, 8.3176e-1, 8.7096e-1, 9.1201e-1, 9.5499e-1, 1.0000e0, 1.0471e0, + 1.0965e0, 1.1482e0, 1.2023e0, 1.2589e0, 1.3183e0, 1.3804e0, 1.4454e0, + 1.5136e0, 1.5849e0, 1.6596e0, 1.7378e0, 1.8197e0, 1.9055e0, 1.9953e0, + 2.0893e0, 2.1878e0, 2.2909e0, 2.3988e0, 2.5119e0, 2.6303e0, 2.7542e0, + 2.8840e0, 3.0200e0, 3.1623e0, 3.3113e0, 3.4674e0, 3.6308e0, 3.8019e0, + 3.9811e0, 4.1687e0, 4.3652e0, 4.5709e0, 4.7863e0, 5.0119e0, 5.2481e0, + 5.4954e0, 5.7544e0, 6.0256e0, 6.3096e0, 6.6069e0, 6.9183e0, 7.2444e0, + 7.5858e0, 7.9433e0, 8.3176e0, 8.7096e0, 9.1201e0, 9.5499e0, 1.0000e1, + 1.0471e1, 1.0965e1, 1.1482e1, 1.2023e1, 1.2589e1, 1.3183e1, 1.3804e1, + 1.4454e1, 1.5136e1, 1.5849e1, 1.6596e1, 1.7378e1, 1.8197e1, 1.9055e1, + 1.9953e1, 2.0893e1, 2.1878e1, 2.2909e1, 2.3988e1, 2.5119e1, 2.6303e1, + 2.7542e1, 2.8840e1, 3.0200e1, 3.1623e1, 3.3113e1, 3.4674e1, 3.6308e1, + 3.8019e1, 3.9811e1, 4.1687e1, 4.3652e1, 4.5709e1, 4.7863e1, 5.0119e1, + 5.2481e1, 5.4954e1, 5.7544e1, 6.0256e1, 6.3096e1, 6.6069e1, 6.9183e1, + 7.2444e1, 7.5858e1, 7.9433e1, 8.3176e1, 8.7096e1, 9.1201e1, 9.5499e1, + 1.0000e2, 1.0471e2, 1.0965e2, 1.1482e2, 1.2023e2, 1.2589e2, 1.3183e2, + 1.3804e2, 1.4454e2, 1.5136e2, 1.5849e2, 1.6596e2, 1.7378e2, 1.8197e2, + 1.9055e2, 1.9953e2, 2.0893e2, 2.1878e2, 2.2909e2, 2.3988e2, 2.5119e2, + 2.6303e2, 2.7542e2, 2.8840e2, 3.0200e2, 3.1623e2, 3.3113e2, 3.4674e2, + 3.6308e2, 3.8019e2, 3.9811e2, 4.1687e2, 4.3652e2, 4.5709e2, 4.7863e2, + 5.0119e2, 5.2481e2, 5.4954e2, 5.7544e2, 6.0256e2, 6.3096e2, 6.6069e2, + 6.9183e2, 7.2444e2, 7.5858e2, 7.9433e2, 8.3176e2, 8.7096e2, 9.1201e2, + 9.5499e2, 1.0000e3, 1.0471e3, 1.0965e3, 1.1482e3, 1.2023e3, 1.2589e3, + 1.3183e3, 1.3804e3, 1.4454e3, 1.5136e3, 1.5849e3, 1.6596e3, 1.7378e3, + 1.8197e3, 1.9055e3, 1.9953e3, 2.0893e3, 2.1878e3, 2.2909e3, 2.3988e3, + 2.5119e3, 2.6303e3, 2.7542e3, 2.8840e3, 3.0200e3, 3.1623e3, 3.3113e3, + 3.4674e3, 3.6308e3, 3.8019e3, 3.9811e3, 4.1687e3, 4.3652e3, 4.5709e3, + 4.7863e3, 5.0119e3, 5.2481e3, 5.4954e3, 5.7544e3, 6.0256e3, 6.3096e3, + 6.6069e3, 6.9183e3, 7.2444e3, 7.5858e3, 7.9433e3, 8.3176e3, 8.7096e3, + 9.1201e3, 9.5499e3, 1.0000e4, 1.0471e4, 1.0965e4, 1.1482e4, 1.2023e4, + 1.2589e4, 1.3183e4, 1.3804e4, 1.4454e4, 1.5136e4, 1.5849e4, 1.6596e4, + 1.7378e4, 1.8197e4, 1.9055e4, 1.9953e4, 2.0893e4, 2.1878e4, 2.2909e4, + 2.3988e4, 2.5119e4, 2.6303e4, 2.7542e4, 2.8840e4, 3.0200e4, 3.1623e4, + 3.3113e4, 3.4674e4, 3.6308e4, 3.8019e4, 3.9811e4, 4.1687e4, 4.3652e4, + 4.5709e4, 4.7863e4, 5.0119e4, 5.2481e4, 5.4954e4, 5.7544e4, 6.0256e4, + 6.3096e4, 6.6069e4, 6.9183e4, 7.2444e4, 7.5858e4, 7.9433e4, 8.3176e4, + 8.7096e4, 9.1201e4, 9.5499e4, 1.0000e5, 1.0471e5, 1.0965e5, 1.1482e5, + 1.2023e5, 1.2589e5, 1.3183e5, 1.3804e5, 1.4454e5, 1.5136e5, 1.5849e5, + 1.6596e5, 1.7378e5, 1.8197e5, 1.9055e5, 1.9953e5, 2.0893e5, 2.1878e5, + 2.2909e5, 2.3988e5, 2.5119e5, 2.6303e5, 2.7542e5, 2.8840e5, 3.0200e5, + 3.1623e5, 3.3113e5, 3.4674e5, 3.6308e5, 3.8019e5, 3.9811e5, 4.1687e5, + 4.3652e5, 4.5709e5, 4.7863e5, 5.0119e5, 5.2481e5, 5.4954e5, 5.7544e5, + 6.0256e5, 6.3096e5, 6.6069e5, 6.9183e5, 7.2444e5, 7.5858e5, 7.9433e5, + 8.3176e5, 8.7096e5, 9.1201e5, 9.5499e5, 1.0000e6, 1.0471e6, 1.0965e6, + 1.1482e6, 1.2023e6, 1.2589e6, 1.3183e6, 1.3804e6, 1.4454e6, 1.5136e6, + 1.5849e6, 1.6596e6, 1.7378e6, 1.8197e6, 1.9055e6, 1.9953e6, 2.0893e6, + 2.1878e6, 2.2909e6, 2.3988e6, 2.5119e6, 2.6303e6, 2.7542e6, 2.8840e6, + 3.0200e6, 3.1623e6, 3.3113e6, 3.4674e6, 3.6308e6, 3.8019e6, 3.9811e6, + 4.1687e6, 4.3652e6, 4.5709e6, 4.7863e6, 5.0119e6, 5.2481e6, 5.4954e6, + 5.7544e6, 6.0256e6, 6.3096e6, 6.6069e6, 6.9183e6, 7.2444e6, 7.5858e6, + 7.9433e6, 8.3176e6, 8.7096e6, 9.1201e6, 9.5499e6, 1.0000e7, 1.0471e7, + 1.0965e7, 1.1482e7, 1.2023e7, 1.2589e7, 1.3183e7, 1.3804e7, 1.4454e7, + 1.5136e7, 1.5849e7, 1.6596e7, 1.7378e7, 1.8197e7, 1.9055e7, 1.9953e7, + 2.0000e7 +]) GROUP_STRUCTURES['CCFE-709'] = np.array([ 1.e-5, 1.0471e-5, 1.0965e-5, 1.1482e-5, 1.2023e-5, 1.2589e-5, 1.3183e-5, 1.3804e-5, 1.4454e-5, 1.5136e-5, diff --git a/src/particle.cpp b/src/particle.cpp index 118b084b04e..7a003feb94d 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -105,7 +105,7 @@ void Particle::split(double wgt) bank.E = settings::run_CE ? E() : g(); bank.time = time(); - // Convert signed index to a singed surface ID + // Convert signed index to a signed surface ID if (surface() == SURFACE_NONE) { bank.surf_id = SURFACE_NONE; } else { From 14d51e0ebaa4de3f122ec85478002d559245fabc Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Thu, 14 Aug 2025 20:26:27 +0200 Subject: [PATCH 091/159] more helpful error message for dose_coefficients (#3534) --- openmc/data/effective_dose/dose.py | 7 ++++++- tests/unit_tests/test_data_dose.py | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/openmc/data/effective_dose/dose.py b/openmc/data/effective_dose/dose.py index c7f458d1c6c..d49043b0a68 100644 --- a/openmc/data/effective_dose/dose.py +++ b/openmc/data/effective_dose/dose.py @@ -82,7 +82,12 @@ def dose_coefficients(particle, geometry='AP', data_source='icrp116'): cv.check_value('data_source', data_source, {'icrp74', 'icrp116'}) if (data_source, particle) not in _FILES: - raise ValueError(f"{particle} has no dose data in data source {data_source}.") + available_particles = sorted({p for (ds, p) in _FILES if ds == data_source}) + msg = ( + f"'{particle}' has no dose data in data source {data_source}. " + f"Available particles for {data_source} are: {available_particles}" + ) + raise ValueError(msg) elif (data_source, particle) not in _DOSE_TABLES: _load_dose_icrp(data_source, particle) diff --git a/tests/unit_tests/test_data_dose.py b/tests/unit_tests/test_data_dose.py index 2d80cf8384f..4f188001436 100644 --- a/tests/unit_tests/test_data_dose.py +++ b/tests/unit_tests/test_data_dose.py @@ -41,3 +41,23 @@ def test_dose_coefficients(): dose_coefficients('neutron', 'ZZ') with raises(ValueError): dose_coefficients('neutron', data_source='icrp7000') + with raises(ValueError) as excinfo: + dose_coefficients("photons", data_source="icrp116") + expected_particles = [ + "electron", + "helium", + "mu+", + "mu-", + "neutron", + "photon", + "photon kerma", + "pi+", + "pi-", + "positron", + "proton", + ] + expected_msg = ( + "'photons' has no dose data in data source icrp116. " + f"Available particles for icrp116 are: {expected_particles}" + ) + assert str(excinfo.value) == expected_msg From 75b581301256c3c77676f798883d993917c1d221 Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Mon, 18 Aug 2025 15:42:38 +0200 Subject: [PATCH 092/159] Use cached property for openmc.data.Decay.sources (#3535) Co-authored-by: Paul Romano --- openmc/data/decay.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/openmc/data/decay.py b/openmc/data/decay.py index 66acb2212d8..1a11d3614fe 100644 --- a/openmc/data/decay.py +++ b/openmc/data/decay.py @@ -1,4 +1,5 @@ from collections.abc import Iterable +from functools import cached_property from io import StringIO from math import log import re @@ -339,7 +340,6 @@ def __init__(self, ev_or_filename): self.modes = [] self.spectra = {} self.average_energies = {} - self._sources = None # Get head record items = get_head_record(file_obj) @@ -506,14 +506,9 @@ def from_endf(cls, ev_or_filename): """ return cls(ev_or_filename) - @property + @cached_property def sources(self): """Radioactive decay source distributions""" - # If property has been computed already, return it - # TODO: Replace with functools.cached_property when support is Python 3.9+ - if self._sources is not None: - return self._sources - sources = {} name = self.nuclide['name'] decay_constant = self.decay_constant.n @@ -571,8 +566,7 @@ def sources(self): merged_sources[particle_type] = combine_distributions( dist_list, [1.0]*len(dist_list)) - self._sources = merged_sources - return self._sources + return merged_sources _DECAY_PHOTON_ENERGY = {} From c5a71738642244a5d7a27898dd0491f1e3966a6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Delaporte-Mathurin?= <40028739+RemDelaporteMathurin@users.noreply.github.com> Date: Mon, 18 Aug 2025 10:34:12 -0400 Subject: [PATCH 093/159] Avoid duplicate materials written to XML (#3536) Co-authored-by: Paul Romano --- openmc/material.py | 2 +- tests/unit_tests/test_materials.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/openmc/material.py b/openmc/material.py index 0afe5b670cf..c7b954b6666 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -1927,7 +1927,7 @@ def _write_xml(self, file, header=True, level=0, spaces_per_level=2, file.write(ET.tostring(element, encoding="unicode")) # Write the elements. - for material in sorted(self, key=lambda x: x.id): + for material in sorted(set(self), key=lambda x: x.id): element = material.to_xml_element(nuclides_to_ignore=nuclides_to_ignore) clean_indentation(element, level=level+1) element.tail = element.tail.strip(' ') diff --git a/tests/unit_tests/test_materials.py b/tests/unit_tests/test_materials.py index 6c8c59fc925..03484e2b6c5 100644 --- a/tests/unit_tests/test_materials.py +++ b/tests/unit_tests/test_materials.py @@ -61,3 +61,19 @@ def test_materials_deplete(): # Ni59 is one of the main activation product of Ni60 in the first irradiation # step. It then decays in the second cooling step (flux = 0) assert Ni59_mat_1_step_1 > 0.0 and Ni59_mat_1_step_1 > Ni59_mat_1_step_2 + + +def test_export_duplicate_materials_to_xml(run_in_tmpdir): + """ + Test exporting Materials to xml with a duplicate and checking that only + unique entities are exported. + """ + my_mat = openmc.Material(name="my_mat") + my_mat2 = openmc.Material(name="my_mat2") + + materials = openmc.Materials([my_mat, my_mat2, my_mat]) + + materials.export_to_xml("materials.xml") + + materials_in = openmc.Materials.from_xml("materials.xml") + assert len(materials_in) == 2 From 68e894c4b01cfdc027b8961a0f02be987bb44c53 Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Mon, 18 Aug 2025 16:40:54 +0200 Subject: [PATCH 094/159] Fix a bug in time cutoff behavior (#3526) --- src/particle.cpp | 77 ++++++++----------- src/physics_mg.cpp | 1 + .../time_cutoff/results_true.dat | 4 +- tests/unit_tests/test_mesh.py | 2 +- 4 files changed, 37 insertions(+), 47 deletions(-) diff --git a/src/particle.cpp b/src/particle.cpp index 7a003feb94d..a0f1d66bd5f 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -44,34 +44,30 @@ namespace openmc { double Particle::speed() const { - // Determine mass in eV/c^2 - double mass; - switch (this->type()) { - case ParticleType::neutron: - mass = MASS_NEUTRON_EV; - break; - case ParticleType::photon: - mass = 0.0; - break; - case ParticleType::electron: - case ParticleType::positron: - mass = MASS_ELECTRON_EV; - break; - } - - if (this->E() < 1.0e-9 * mass) { - // If the energy is much smaller than the mass, revert to non-relativistic - // formula. The 1e-9 criterion is specifically chosen as the point below - // which the error from using the non-relativistic formula is less than the - // round-off eror when using the relativistic formula (see analysis at - // https://gist.github.com/paulromano/da3b473fe3df33de94b265bdff0c7817) - return C_LIGHT * std::sqrt(2 * this->E() / mass); + if (settings::run_CE) { + // Determine mass in eV/c^2 + double mass; + switch (this->type()) { + case ParticleType::neutron: + mass = MASS_NEUTRON_EV; + break; + case ParticleType::photon: + mass = 0.0; + break; + case ParticleType::electron: + case ParticleType::positron: + mass = MASS_ELECTRON_EV; + break; + } + // Equivalent to C * sqrt(1-(m/(m+E))^2) without problem at E<E() * (this->E() + 2 * mass)) / + (this->E() + mass); } else { - // Calculate inverse of Lorentz factor - const double inv_gamma = mass / (this->E() + mass); - - // Calculate speed via v = c * sqrt(1 - γ^-2) - return C_LIGHT * std::sqrt(1 - inv_gamma * inv_gamma); + auto& macro_xs = data::mg.macro_xs_[this->material()]; + int macro_t = this->mg_xs_cache().t; + int macro_a = macro_xs.get_angle_index(this->u()); + return 1.0 / macro_xs.get_xs(MgxsType::INVERSE_VELOCITY, this->g(), nullptr, + nullptr, nullptr, macro_t, macro_a); } } @@ -241,8 +237,14 @@ void Particle::event_advance() collision_distance() = -std::log(prn(current_seed())) / macro_xs().total; } - // Select smaller of the two distances - double distance = std::min(boundary().distance(), collision_distance()); + double speed = this->speed(); + double time_cutoff = settings::time_cutoff[static_cast(type())]; + double distance_cutoff = + (time_cutoff < INFTY) ? (time_cutoff - time()) * speed : INFTY; + + // Select smaller of the three distances + double distance = + std::min({boundary().distance(), collision_distance(), distance_cutoff}); // Advance particle in space and time // Short-term solution until the surface source is revised and we can use @@ -250,23 +252,10 @@ void Particle::event_advance() for (int j = 0; j < n_coord(); ++j) { coord(j).r() += distance * coord(j).u(); } - double dt = distance / this->speed(); + double dt = distance / speed; this->time() += dt; this->lifetime() += dt; - // Kill particle if its time exceeds the cutoff - bool hit_time_boundary = false; - double time_cutoff = settings::time_cutoff[static_cast(type())]; - if (time() > time_cutoff) { - double dt = time() - time_cutoff; - time() = time_cutoff; - lifetime() = time_cutoff; - - double push_back_distance = speed() * dt; - this->move_distance(-push_back_distance); - hit_time_boundary = true; - } - // Score track-length tallies if (!model::active_tracklength_tallies.empty()) { score_tracklength_tally(*this, distance); @@ -284,7 +273,7 @@ void Particle::event_advance() } // Set particle weight to zero if it hit the time boundary - if (hit_time_boundary) { + if (distance == distance_cutoff) { wgt() = 0.0; } } diff --git a/src/physics_mg.cpp b/src/physics_mg.cpp index c97a3d6f3dc..5ebef9c1417 100644 --- a/src/physics_mg.cpp +++ b/src/physics_mg.cpp @@ -137,6 +137,7 @@ void create_fission_sites(Particle& p) SourceSite site; site.r = p.r(); site.particle = ParticleType::neutron; + site.time = p.time(); site.wgt = 1. / weight; site.parent_id = p.id(); site.progeny_id = p.n_progeny()++; diff --git a/tests/regression_tests/time_cutoff/results_true.dat b/tests/regression_tests/time_cutoff/results_true.dat index 1cafceb772e..d3d5e1b3cdd 100644 --- a/tests/regression_tests/time_cutoff/results_true.dat +++ b/tests/regression_tests/time_cutoff/results_true.dat @@ -1,5 +1,5 @@ tally 1: -2.000000E+03 -4.000000E+05 +1.383148E+02 +1.913099E+03 0.000000E+00 0.000000E+00 diff --git a/tests/unit_tests/test_mesh.py b/tests/unit_tests/test_mesh.py index 3c8e988c032..67ca4028e06 100644 --- a/tests/unit_tests/test_mesh.py +++ b/tests/unit_tests/test_mesh.py @@ -620,7 +620,7 @@ def test_mesh_material_volumes_serialize(): assert new_volumes.by_element(3) == [(2, 1.0)] -def test_raytrace_mesh_infinite_loop(): +def test_raytrace_mesh_infinite_loop(run_in_tmpdir): # Create a model with one large spherical cell sphere = openmc.Sphere(r=100, boundary_type='vacuum') cell = openmc.Cell(region=-sphere) From e878aeb82ff240478e3079787a1f94ea5ac58e7f Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Thu, 21 Aug 2025 11:14:21 +0200 Subject: [PATCH 095/159] not printing nuclides with 0 percent to terminal (option 2 ) (#3448) --- openmc/deplete/stepresult.py | 2 +- tests/unit_tests/test_deplete_resultslist.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/openmc/deplete/stepresult.py b/openmc/deplete/stepresult.py index 7c86b86458c..1a26cbe3466 100644 --- a/openmc/deplete/stepresult.py +++ b/openmc/deplete/stepresult.py @@ -233,7 +233,7 @@ def get_material(self, mat_id): ) from e for nuc, _ in sorted(self.index_nuc.items(), key=lambda x: x[1]): atoms = self[0, mat_id, nuc] - if atoms < 0.0: + if atoms <= 0.0: continue atom_per_bcm = atoms / vol * 1e-24 material.add_nuclide(nuc, atom_per_bcm) diff --git a/tests/unit_tests/test_deplete_resultslist.py b/tests/unit_tests/test_deplete_resultslist.py index b22a786509e..308eccf7bbd 100644 --- a/tests/unit_tests/test_deplete_resultslist.py +++ b/tests/unit_tests/test_deplete_resultslist.py @@ -220,5 +220,4 @@ def test_stepresult_get_material(res): # Spot check number densities densities = mat1.get_nuclide_atom_densities() assert densities['Xe135'] == pytest.approx(1e-14) - assert densities['I135'] == pytest.approx(1e-21) assert densities['U234'] == pytest.approx(1.00506e-05) From 5e3249f0063e1bb9c2fd69d3b7542b4106d20baa Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Thu, 21 Aug 2025 13:13:50 -0800 Subject: [PATCH 096/159] fix tests that accidentaly got broken (#3543) --- tests/unit_tests/test_material.py | 7 ++++--- tests/unit_tests/test_materials.py | 9 +++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/unit_tests/test_material.py b/tests/unit_tests/test_material.py index eae814a755b..2e37242720b 100644 --- a/tests/unit_tests/test_material.py +++ b/tests/unit_tests/test_material.py @@ -738,11 +738,12 @@ def test_material_deplete(): chain_file=chain, ) - for material in depleted_material: + for i_step, material in enumerate(depleted_material): assert isinstance(material, openmc.Material) - assert len(material.get_nuclides()) > len(pristine_material.get_nuclides()) + if i_step > 0: + assert len(material.get_nuclides()) > len(pristine_material.get_nuclides()) - Co58_mat_1_step_0 = depleted_material[0].get_nuclide_atom_densities("Co58")["Co58"] + Co58_mat_1_step_0 = depleted_material[0].get_nuclide_atom_densities("Co58").get("Co58", 0.0) Co58_mat_1_step_1 = depleted_material[1].get_nuclide_atom_densities("Co58")["Co58"] Co58_mat_1_step_2 = depleted_material[2].get_nuclide_atom_densities("Co58")["Co58"] diff --git a/tests/unit_tests/test_materials.py b/tests/unit_tests/test_materials.py index 03484e2b6c5..5a382b777df 100644 --- a/tests/unit_tests/test_materials.py +++ b/tests/unit_tests/test_materials.py @@ -38,13 +38,14 @@ def test_materials_deplete(): assert list(depleted_material.keys()) == [pristine_material_1.id, pristine_material_2.id] for mat_id, materials in depleted_material.items(): - for material in materials: + for i_step, material in enumerate(materials): assert isinstance(material, openmc.Material) - assert len(material.get_nuclides()) > 1 + if i_step > 0: + assert len(material.get_nuclides()) > 1 assert mat_id == material.id mats = depleted_material[pristine_material_1.id] - Co58_mat_1_step_0 = mats[0].get_nuclide_atom_densities("Co58")["Co58"] + Co58_mat_1_step_0 = mats[0].get_nuclide_atom_densities("Co58").get("Co58", 0.0) Co58_mat_1_step_1 = mats[1].get_nuclide_atom_densities("Co58")["Co58"] Co58_mat_1_step_2 = mats[2].get_nuclide_atom_densities("Co58")["Co58"] @@ -53,7 +54,7 @@ def test_materials_deplete(): # It then decays in the second cooling step (flux = 0) assert Co58_mat_1_step_1 > 0.0 and Co58_mat_1_step_1 > Co58_mat_1_step_2 - Ni59_mat_1_step_0 = mats[0].get_nuclide_atom_densities("Ni59")["Ni59"] + Ni59_mat_1_step_0 = mats[0].get_nuclide_atom_densities("Ni59").get("Ni59", 0.0) Ni59_mat_1_step_1 = mats[1].get_nuclide_atom_densities("Ni59")["Ni59"] Ni59_mat_1_step_2 = mats[2].get_nuclide_atom_densities("Ni59")["Ni59"] From d1df80a210f94a9b68194310e1e540a863b2ce8c Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Fri, 22 Aug 2025 06:58:39 -0800 Subject: [PATCH 097/159] Leverage particle.move_distance in event advance (#3544) --- src/particle.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/particle.cpp b/src/particle.cpp index a0f1d66bd5f..fb41d82e950 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -247,11 +247,7 @@ void Particle::event_advance() std::min({boundary().distance(), collision_distance(), distance_cutoff}); // Advance particle in space and time - // Short-term solution until the surface source is revised and we can use - // this->move_distance(distance) - for (int j = 0; j < n_coord(); ++j) { - coord(j).r() += distance * coord(j).u(); - } + this->move_distance(distance); double dt = distance / speed; this->time() += dt; this->lifetime() += dt; From b14eff47603dbc1e52fbef11cced930bbc6869b2 Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Wed, 27 Aug 2025 22:28:11 +0300 Subject: [PATCH 098/159] fix broken CI (#3551) --- .github/workflows/format-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/format-check.yml b/.github/workflows/format-check.yml index cef14ca2c8c..c2ceb88e2a3 100644 --- a/.github/workflows/format-check.yml +++ b/.github/workflows/format-check.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: cpp-linter/cpp-linter-action@v2 + - uses: cpp-linter/cpp-linter-action@v2.16.0 id: linter env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 2a278c93a67e6666c4408b752fd9c4f0e91e7cfe Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Thu, 28 Aug 2025 02:18:06 +0300 Subject: [PATCH 099/159] Revert "fix broken CI" (#3554) --- .github/workflows/format-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/format-check.yml b/.github/workflows/format-check.yml index c2ceb88e2a3..cef14ca2c8c 100644 --- a/.github/workflows/format-check.yml +++ b/.github/workflows/format-check.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: cpp-linter/cpp-linter-action@v2.16.0 + - uses: cpp-linter/cpp-linter-action@v2 id: linter env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 5529731e2f7933b3386a854db24edf3e2e535a44 Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Fri, 29 Aug 2025 15:06:33 +0300 Subject: [PATCH 100/159] Refactor endf_data to be a fixture (#3539) --- openmc/config.py | 16 +++---- tests/conftest.py | 6 ++- tests/unit_tests/test_data_decay.py | 9 ++-- tests/unit_tests/test_data_kalbach_mann.py | 7 +-- tests/unit_tests/test_data_misc.py | 2 +- tests/unit_tests/test_data_multipole.py | 10 ++-- tests/unit_tests/test_data_neutron.py | 56 ++++++++-------------- tests/unit_tests/test_data_photon.py | 7 ++- tests/unit_tests/test_data_thermal.py | 22 ++++----- tests/unit_tests/test_deplete_chain.py | 10 ++-- 10 files changed, 59 insertions(+), 86 deletions(-) diff --git a/openmc/config.py b/openmc/config.py index d61fb2d31e6..e75973495e1 100644 --- a/openmc/config.py +++ b/openmc/config.py @@ -193,12 +193,12 @@ def _default_config() -> _Config: """ config = _Config() - if "OPENMC_CROSS_SECTIONS" in os.environ: - config['cross_sections'] = os.environ["OPENMC_CROSS_SECTIONS"] - if "OPENMC_MG_CROSS_SECTIONS" in os.environ: - config['mg_cross_sections'] = os.environ["OPENMC_MG_CROSS_SECTIONS"] - chain_file = os.environ.get("OPENMC_CHAIN_FILE") - xs_path = config.get('cross_sections') + for key,var in _Config._PATH_KEYS.items(): + if var in os.environ: + config[key] = os.environ[var] + + chain_file = config.get("chain_file") + xs_path = config.get("cross_sections") if chain_file is None and xs_path is not None and xs_path.exists(): try: data = DataLibrary.from_xml(xs_path) @@ -209,10 +209,8 @@ def _default_config() -> _Config: else: for lib in reversed(data.libraries): if lib['type'] == 'depletion_chain': - chain_file = xs_path.parent / lib['path'] + config['chain_file'] = xs_path.parent / lib['path'] break - if chain_file is not None: - config['chain_file'] = chain_file return config diff --git a/tests/conftest.py b/tests/conftest.py index cd86da53900..fa6718502d8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,4 @@ +import os import pytest import openmc @@ -28,7 +29,10 @@ def run_in_tmpdir(tmpdir): yield finally: orig.chdir() - + +@pytest.fixture(scope="module") +def endf_data(): + return os.environ['OPENMC_ENDF_DATA'] @pytest.fixture(scope='session', autouse=True) def resolve_paths(): diff --git a/tests/unit_tests/test_data_decay.py b/tests/unit_tests/test_data_decay.py index 8900e7eace5..de8d90a434d 100644 --- a/tests/unit_tests/test_data_decay.py +++ b/tests/unit_tests/test_data_decay.py @@ -17,25 +17,22 @@ def ufloat_close(a, b): @pytest.fixture(scope='module') -def nb90(): +def nb90(endf_data): """Nb90 decay data.""" - endf_data = os.environ['OPENMC_ENDF_DATA'] filename = os.path.join(endf_data, 'decay', 'dec-041_Nb_090.endf') return openmc.data.Decay.from_endf(filename) @pytest.fixture(scope='module') -def ba137m(): +def ba137m(endf_data): """Ba137_m1 decay data.""" - endf_data = os.environ['OPENMC_ENDF_DATA'] filename = os.path.join(endf_data, 'decay', 'dec-056_Ba_137m1.endf') return openmc.data.Decay.from_endf(filename) @pytest.fixture(scope='module') -def u235_yields(): +def u235_yields(endf_data): """U235 fission product yield data.""" - endf_data = os.environ['OPENMC_ENDF_DATA'] filename = os.path.join(endf_data, 'nfy', 'nfy-092_U_235.endf') return openmc.data.FissionProductYields.from_endf(filename) diff --git a/tests/unit_tests/test_data_kalbach_mann.py b/tests/unit_tests/test_data_kalbach_mann.py index 3b837af7da9..5d06669f7b9 100644 --- a/tests/unit_tests/test_data_kalbach_mann.py +++ b/tests/unit_tests/test_data_kalbach_mann.py @@ -7,6 +7,7 @@ import numpy as np +import openmc from openmc.data import IncidentNeutron from openmc.data.kalbach_mann import _separation_energy, _AtomicRepresentation from openmc.data import kalbach_slope @@ -129,7 +130,7 @@ def test_kalbach_slope(): ('Hg204.h5', 'n-080_Hg_204.endf') ] ) -def test_comparison_slope_hdf5(hdf5_filename, endf_filename): +def test_comparison_slope_hdf5(hdf5_filename, endf_filename, endf_data): """Test the calculation of the Kalbach-Mann slope done by OpenMC by comparing it to HDF5 data. The test is based on the first product of MT=5 (neutron). The isotopes tested have been selected because the @@ -147,13 +148,13 @@ def test_comparison_slope_hdf5(hdf5_filename, endf_filename): """ # HDF5 data - hdf5_directory = Path(os.environ['OPENMC_CROSS_SECTIONS']).parent + hdf5_directory = Path(openmc.config.get('cross_sections')).parent hdf5_data = IncidentNeutron.from_hdf5(hdf5_directory / hdf5_filename) hdf5_product = hdf5_data[5].products[0] hdf5_distribution = hdf5_product.distribution[0] # ENDF data - endf_directory = Path(os.environ['OPENMC_ENDF_DATA']) + endf_directory = Path(endf_data) endf_path = endf_directory / 'neutrons' / endf_filename endf_data = IncidentNeutron.from_endf(endf_path) endf_product = endf_data[5].products[0] diff --git a/tests/unit_tests/test_data_misc.py b/tests/unit_tests/test_data_misc.py index b65c704af8e..14db689130e 100644 --- a/tests/unit_tests/test_data_misc.py +++ b/tests/unit_tests/test_data_misc.py @@ -30,7 +30,7 @@ def test_data_library(tmpdir): assert os.path.exists(filename) new_lib = openmc.data.DataLibrary() - directory = os.path.dirname(os.environ['OPENMC_CROSS_SECTIONS']) + directory = os.path.dirname(openmc.config.get('cross_sections')) new_lib.register_file(os.path.join(directory, 'H1.h5')) assert new_lib[-1]['type'] == 'neutron' new_lib.register_file(os.path.join(directory, 'c_Zr_in_ZrH.h5')) diff --git a/tests/unit_tests/test_data_multipole.py b/tests/unit_tests/test_data_multipole.py index bc7136a97c8..105099bb940 100644 --- a/tests/unit_tests/test_data_multipole.py +++ b/tests/unit_tests/test_data_multipole.py @@ -8,14 +8,14 @@ @pytest.fixture(scope='module') def u235(): - directory = pathlib.Path(os.environ['OPENMC_CROSS_SECTIONS']).parent + directory = pathlib.Path(openmc.config.get('cross_sections')).parent u235 = directory / 'wmp' / '092235.h5' return openmc.data.WindowedMultipole.from_hdf5(u235) @pytest.fixture(scope='module') def b10(): - directory = pathlib.Path(os.environ['OPENMC_CROSS_SECTIONS']).parent + directory = pathlib.Path(openmc.config.get('cross_sections')).parent b10 = directory / 'wmp' / '005010.h5' return openmc.data.WindowedMultipole.from_hdf5(b10) @@ -48,17 +48,15 @@ def test_export_to_hdf5(tmpdir, u235): assert os.path.exists(filename) -def test_from_endf(): +def test_from_endf(endf_data): pytest.importorskip('vectfit') - endf_data = os.environ['OPENMC_ENDF_DATA'] endf_file = os.path.join(endf_data, 'neutrons', 'n-001_H_001.endf') assert openmc.data.WindowedMultipole.from_endf( endf_file, log=True, wmp_options={"n_win": 400, "n_cf": 3}) -def test_from_endf_search(): +def test_from_endf_search(endf_data): pytest.importorskip('vectfit') - endf_data = os.environ['OPENMC_ENDF_DATA'] endf_file = os.path.join(endf_data, 'neutrons', 'n-095_Am_244.endf') assert openmc.data.WindowedMultipole.from_endf( endf_file, log=True, wmp_options={"search": True, 'rtol':1e-2}) diff --git a/tests/unit_tests/test_data_neutron.py b/tests/unit_tests/test_data_neutron.py index db6ae1eb850..d43d93ae516 100644 --- a/tests/unit_tests/test_data_neutron.py +++ b/tests/unit_tests/test_data_neutron.py @@ -14,128 +14,113 @@ @pytest.fixture(scope='module') def pu239(): """Pu239 HDF5 data.""" - directory = os.path.dirname(os.environ['OPENMC_CROSS_SECTIONS']) + directory = os.path.dirname(openmc.config.get('cross_sections')) filename = os.path.join(directory, 'Pu239.h5') return openmc.data.IncidentNeutron.from_hdf5(filename) @pytest.fixture(scope='module') -def xe135(): +def xe135(endf_data): """Xe135 ENDF data (contains SLBW resonance range)""" - endf_data = os.environ['OPENMC_ENDF_DATA'] filename = os.path.join(endf_data, 'neutrons', 'n-054_Xe_135.endf') return openmc.data.IncidentNeutron.from_endf(filename) @pytest.fixture(scope='module') -def sm150(): +def sm150(endf_data): """Sm150 ENDF data (contains MLBW resonance range)""" - endf_data = os.environ['OPENMC_ENDF_DATA'] filename = os.path.join(endf_data, 'neutrons', 'n-062_Sm_150.endf') return openmc.data.IncidentNeutron.from_endf(filename) @pytest.fixture(scope='module') -def gd154(): +def gd154(endf_data): """Gd154 ENDF data (contains Reich Moore resonance range and reosnance covariance with LCOMP=1).""" - endf_data = os.environ['OPENMC_ENDF_DATA'] filename = os.path.join(endf_data, 'neutrons', 'n-064_Gd_154.endf') return openmc.data.IncidentNeutron.from_endf(filename, covariance=True) @pytest.fixture(scope='module') -def cl35(): +def cl35(endf_data): """Cl35 ENDF data (contains RML resonance range)""" - endf_data = os.environ['OPENMC_ENDF_DATA'] filename = os.path.join(endf_data, 'neutrons', 'n-017_Cl_035.endf') return openmc.data.IncidentNeutron.from_endf(filename) @pytest.fixture(scope='module') -def am241(): +def am241(endf_data): """Am241 ENDF data (contains Madland-Nix fission energy distribution).""" - endf_data = os.environ['OPENMC_ENDF_DATA'] filename = os.path.join(endf_data, 'neutrons', 'n-095_Am_241.endf') return openmc.data.IncidentNeutron.from_endf(filename) @pytest.fixture(scope='module') -def u233(): +def u233(endf_data): """U233 ENDF data (contains Watt fission energy distribution).""" - endf_data = os.environ['OPENMC_ENDF_DATA'] filename = os.path.join(endf_data, 'neutrons', 'n-092_U_233.endf') return openmc.data.IncidentNeutron.from_endf(filename) @pytest.fixture(scope='module') -def u236(): +def u236(endf_data): """U236 ENDF data (contains Watt fission energy distribution).""" - endf_data = os.environ['OPENMC_ENDF_DATA'] filename = os.path.join(endf_data, 'neutrons', 'n-092_U_236.endf') return openmc.data.IncidentNeutron.from_endf(filename) @pytest.fixture(scope='module') -def na22(): +def na22(endf_data): """Na22 ENDF data (contains evaporation spectrum).""" - endf_data = os.environ['OPENMC_ENDF_DATA'] filename = os.path.join(endf_data, 'neutrons', 'n-011_Na_022.endf') return openmc.data.IncidentNeutron.from_endf(filename) @pytest.fixture(scope='module') -def na23(): +def na23(endf_data): """Na23 ENDF data (contains MLBW resonance covariance with LCOMP=0).""" - endf_data = os.environ['OPENMC_ENDF_DATA'] filename = os.path.join(endf_data, 'neutrons', 'n-011_Na_023.endf') return openmc.data.IncidentNeutron.from_endf(filename, covariance=True) @pytest.fixture(scope='module') -def be9(): +def be9(endf_data): """Be9 ENDF data (contains laboratory angle-energy distribution).""" - endf_data = os.environ['OPENMC_ENDF_DATA'] filename = os.path.join(endf_data, 'neutrons', 'n-004_Be_009.endf') return openmc.data.IncidentNeutron.from_endf(filename) @pytest.fixture(scope='module') -def h2(): - endf_data = os.environ['OPENMC_ENDF_DATA'] +def h2(endf_data): endf_file = os.path.join(endf_data, 'neutrons', 'n-001_H_002.endf') return openmc.data.IncidentNeutron.from_njoy( endf_file, temperatures=_TEMPERATURES) @pytest.fixture(scope='module') -def am244(): - endf_data = os.environ['OPENMC_ENDF_DATA'] +def am244(endf_data): endf_file = os.path.join(endf_data, 'neutrons', 'n-095_Am_244.endf') return openmc.data.IncidentNeutron.from_njoy(endf_file) @pytest.fixture(scope='module') -def ti50(): +def ti50(endf_data): """Ti50 ENDF data (contains Multi-level Breit-Wigner resonance range and resonance covariance with LCOMP=1).""" - endf_data = os.environ['OPENMC_ENDF_DATA'] filename = os.path.join(endf_data, 'neutrons', 'n-022_Ti_050.endf') return openmc.data.IncidentNeutron.from_endf(filename, covariance=True) @pytest.fixture(scope='module') -def cf252(): +def cf252(endf_data): """Cf252 ENDF data (contains RM resonance covariance with LCOMP=0).""" - endf_data = os.environ['OPENMC_ENDF_DATA'] filename = os.path.join(endf_data, 'neutrons', 'n-098_Cf_252.endf') return openmc.data.IncidentNeutron.from_endf(filename, covariance=True) @pytest.fixture(scope='module') -def th232(): +def th232(endf_data): """Th232 ENDF data (contains RM resonance covariance with LCOMP=2).""" - endf_data = os.environ['OPENMC_ENDF_DATA'] filename = os.path.join(endf_data, 'neutrons', 'n-090_Th_232.endf') return openmc.data.IncidentNeutron.from_endf(filename, covariance=True) @@ -453,8 +438,7 @@ def test_laboratory(be9): @needs_njoy -def test_correlated(tmpdir): - endf_data = os.environ['OPENMC_ENDF_DATA'] +def test_correlated(tmpdir, endf_data): endf_file = os.path.join(endf_data, 'neutrons', 'n-014_Si_030.endf') si30 = openmc.data.IncidentNeutron.from_njoy(endf_file, heatr=False) @@ -480,8 +464,7 @@ def test_nbody(tmpdir, h2): @needs_njoy -def test_ace_convert(run_in_tmpdir): - endf_data = os.environ['OPENMC_ENDF_DATA'] +def test_ace_convert(run_in_tmpdir, endf_data): filename = os.path.join(endf_data, 'neutrons', 'n-001_H_001.endf') ace_ascii = 'ace_ascii' ace_binary = 'ace_binary' @@ -515,8 +498,7 @@ def test_ace_table_types(): @needs_njoy -def test_high_temperature(): - endf_data = os.environ['OPENMC_ENDF_DATA'] +def test_high_temperature(endf_data): endf_file = os.path.join(endf_data, 'neutrons', 'n-001_H_001.endf') # Ensure that from_njoy works when given a high temperature diff --git a/tests/unit_tests/test_data_photon.py b/tests/unit_tests/test_data_photon.py index 010ac1f353e..98b180f525c 100644 --- a/tests/unit_tests/test_data_photon.py +++ b/tests/unit_tests/test_data_photon.py @@ -9,9 +9,8 @@ @pytest.fixture(scope='module') -def elements_endf(): +def elements_endf(endf_data): """Dictionary of element ENDF data indexed by atomic symbol.""" - endf_data = os.environ['OPENMC_ENDF_DATA'] elements = {'H': 1, 'O': 8, 'Al': 13, 'Cu': 29, 'Ag': 47, 'U': 92, 'Pu': 94} data = {} for symbol, Z in elements.items(): @@ -145,8 +144,8 @@ def test_export_to_hdf5(tmpdir, element): element2.export_to_hdf5(filename, 'w') -def test_photodat_only(run_in_tmpdir): - endf_dir = Path(os.environ['OPENMC_ENDF_DATA']) +def test_photodat_only(run_in_tmpdir, endf_data): + endf_dir = Path(endf_data) photoatomic_file = endf_dir / 'photoat' / 'photoat-001_H_000.endf' data = openmc.data.IncidentPhoton.from_endf(photoatomic_file) data.export_to_hdf5('tmp.h5', 'w') diff --git a/tests/unit_tests/test_data_thermal.py b/tests/unit_tests/test_data_thermal.py index c9fd9045b73..c444d0c5860 100644 --- a/tests/unit_tests/test_data_thermal.py +++ b/tests/unit_tests/test_data_thermal.py @@ -13,7 +13,7 @@ @pytest.fixture(scope='module') def h2o(): """H in H2O thermal scattering data.""" - directory = os.path.dirname(os.environ['OPENMC_CROSS_SECTIONS']) + directory = os.path.dirname(openmc.config.get('cross_sections')) filename = os.path.join(directory, 'c_H_in_H2O.h5') return openmc.data.ThermalScattering.from_hdf5(filename) @@ -21,15 +21,14 @@ def h2o(): @pytest.fixture(scope='module') def graphite(): """Graphite thermal scattering data.""" - directory = os.path.dirname(os.environ['OPENMC_CROSS_SECTIONS']) + directory = os.path.dirname(openmc.config.get('cross_sections')) filename = os.path.join(directory, 'c_Graphite.h5') return openmc.data.ThermalScattering.from_hdf5(filename) @pytest.fixture(scope='module') -def h2o_njoy(): +def h2o_njoy(endf_data): """H in H2O generated using NJOY.""" - endf_data = os.environ['OPENMC_ENDF_DATA'] path_h1 = os.path.join(endf_data, 'neutrons', 'n-001_H_001.endf') path_h2o = os.path.join(endf_data, 'thermal_scatt', 'tsl-HinH2O.endf') return openmc.data.ThermalScattering.from_njoy( @@ -37,17 +36,15 @@ def h2o_njoy(): @pytest.fixture(scope='module') -def hzrh(): +def hzrh(endf_data): """H in ZrH thermal scattering data.""" - endf_data = os.environ['OPENMC_ENDF_DATA'] filename = os.path.join(endf_data, 'thermal_scatt', 'tsl-HinZrH.endf') return openmc.data.ThermalScattering.from_endf(filename, divide_incoherent_elastic=True) @pytest.fixture(scope='module') -def hzrh_njoy(): +def hzrh_njoy(endf_data): """H in ZrH generated using NJOY.""" - endf_data = os.environ['OPENMC_ENDF_DATA'] path_h1 = os.path.join(endf_data, 'neutrons', 'n-001_H_001.endf') path_hzrh = os.path.join(endf_data, 'thermal_scatt', 'tsl-HinZrH.endf') with_endf_data = openmc.data.ThermalScattering.from_njoy( @@ -60,9 +57,8 @@ def hzrh_njoy(): @pytest.fixture(scope='module') -def sio2(): +def sio2(endf_data): """SiO2 thermal scattering data.""" - endf_data = os.environ['OPENMC_ENDF_DATA'] filename = os.path.join(endf_data, 'thermal_scatt', 'tsl-SiO2.endf') return openmc.data.ThermalScattering.from_endf(filename, divide_incoherent_elastic=True) @@ -102,8 +98,7 @@ def test_graphite_xs(graphite): assert elastic([1e-3, 1.0]) == pytest.approx([0.0, 0.62586153]) @needs_njoy -def test_graphite_njoy(): - endf_data = os.environ['OPENMC_ENDF_DATA'] +def test_graphite_njoy(endf_data): path_c0 = os.path.join(endf_data, 'neutrons', 'n-006_C_000.endf') path_gr = os.path.join(endf_data, 'thermal_scatt', 'tsl-graphite.endf') graphite = openmc.data.ThermalScattering.from_njoy( @@ -141,8 +136,7 @@ def test_continuous_dist(h2o_njoy): assert isinstance(dist, openmc.data.IncoherentInelasticAE) -def test_h2o_endf(): - endf_data = os.environ['OPENMC_ENDF_DATA'] +def test_h2o_endf(endf_data): filename = os.path.join(endf_data, 'thermal_scatt', 'tsl-HinH2O.endf') h2o = openmc.data.ThermalScattering.from_endf(filename, divide_incoherent_elastic=True) assert not h2o.elastic diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index bd5cc226352..e90b6102241 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -54,11 +54,11 @@ def simple_chain(): @pytest.fixture(scope='module') -def endf_chain(): - endf_data = Path(os.environ['OPENMC_ENDF_DATA']) - decay_data = (endf_data / 'decay').glob('*.endf') - fpy_data = (endf_data / 'nfy').glob('*.endf') - neutron_data = (endf_data / 'neutrons').glob('*.endf') +def endf_chain(endf_data): + endf_dir = Path(endf_data) + decay_data = (endf_dir / 'decay').glob('*.endf') + fpy_data = (endf_dir / 'nfy').glob('*.endf') + neutron_data = (endf_dir / 'neutrons').glob('*.endf') return Chain.from_endf(decay_data, fpy_data, neutron_data) From ec15803d4134e7e72384801ce08b03b45608d6ba Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Sat, 30 Aug 2025 03:39:45 +0200 Subject: [PATCH 101/159] adding ecco 33 (#3556) --- openmc/mgxs/__init__.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/openmc/mgxs/__init__.py b/openmc/mgxs/__init__.py index f8de84b37ea..5de85afb0e7 100644 --- a/openmc/mgxs/__init__.py +++ b/openmc/mgxs/__init__.py @@ -18,6 +18,8 @@ ([ZAL1999]_ and [REARDEN2013]_) - "MPACT-X" (where X is 51 (PWR), 60 (BWR), 69 (Magnox)) from the MPACT_ reactor physics code ([KIM2019]_ and [KIM2020]_) +- "ECCO-33" intended for fast reactor criticality benchmarks. It’s derived as a + subset of VITAMIN‑J - "ECCO-1968_" designed for fine group reactor cell calculations for fast, intermediate and thermal reactor applications ([SAR1990]_) - activation_ energy group structures "VITAMIN-J-42", "VITAMIN-J-175", @@ -25,6 +27,7 @@ .. _CASMO: http://large.stanford.edu/courses/2013/ph241/dalvi1/docs/c5.physor2006.pdf .. _SCALE44: https://www-nds.iaea.org/publications/indc/indc-czr-0001.pdf +.. _ECCO-33: https://serpent.vtt.fi/mediawiki/index.php/ECCO_33-group_structure .. _SCALE252: https://oecd-nea.org/science/wpncs/amct/workingarea/meeting2013/EGAMCT2013_08.pdf .. _MPACT: https://vera.ornl.gov/mpact/ .. _XMAS-172: https://www-nds.iaea.org/wimsd/energy.htm @@ -81,6 +84,16 @@ 0., 3.e-2, 5.8e-2, 1.4e-1, 2.8e-1, 3.5e-1, 6.25e-1, 9.72e-1, 1.02, 1.097, 1.15, 1.855, 4., 9.877, 1.5968e1, 1.4873e2, 5.53e3, 9.118e3, 1.11e5, 5.e5, 8.21e5, 1.353e6, 2.231e6, 3.679e6, 6.0655e6, 2.e7]) +GROUP_STRUCTURES['ECCO-33'] = np.array([ + 1.0000100000E-05, 1.0000000000E-01, 5.4000000000E-01, 4.0000000000E+00, + 8.3152870000E+00, 1.3709590000E+01, 2.2603290000E+01, 4.0169000000E+01, + 6.7904050000E+01, 9.1660880000E+01, 1.4862540000E+02, 3.0432480000E+02, + 4.5399930000E+02, 7.4851830000E+02, 1.2340980000E+03, 2.0346840000E+03, + 3.3546260000E+03, 5.5308440000E+03, 9.1188200000E+03, 1.5034390000E+04, + 2.4787520000E+04, 4.0867710000E+04, 6.7379470000E+04, 1.1109000000E+05, + 1.8315640000E+05, 3.0197380000E+05, 4.9787070000E+05, 8.2085000000E+05, + 1.3533530000E+06, 2.2313020000E+06, 3.6787940000E+06, 6.0653070000E+06, + 1.0000000000E+07, 1.9640330000E+07]) GROUP_STRUCTURES['CASMO-40'] = np.array([ 0., 1.5e-2, 3.e-2, 4.2e-2, 5.8e-2, 8.e-2, 1.e-1, 1.4e-1, 1.8e-1, 2.2e-1, 2.8e-1, 3.5e-1, 6.25e-1, 8.5e-1, 9.5e-1, From 00edc77691c60411cf00431fdc6788f8fceb886a Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Sat, 30 Aug 2025 05:39:46 +0200 Subject: [PATCH 102/159] Change test order to run unit tests first. (#3533) Co-authored-by: Paul Romano --- openmc/cell.py | 6 +++--- openmc/config.py | 6 +++--- tests/unit_tests/test_config.py | 5 +++-- tools/ci/gha-script.sh | 4 ++-- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/openmc/cell.py b/openmc/cell.py index 672afe0953d..88aaebc8f27 100644 --- a/openmc/cell.py +++ b/openmc/cell.py @@ -567,10 +567,10 @@ def plot(self, *args, **kwargs): .. versionadded:: 0.14.0 """ # Create dummy universe but preserve used_ids - next_id = openmc.Universe.next_id + next_id = openmc.UniverseBase.next_id u = openmc.Universe(cells=[self]) - openmc.Universe.used_ids.remove(u.id) - openmc.Universe.next_id = next_id + openmc.UniverseBase.used_ids.remove(u.id) + openmc.UniverseBase.next_id = next_id return u.plot(*args, **kwargs) def create_xml_subelement(self, xml_element, memo=None): diff --git a/openmc/config.py b/openmc/config.py index e75973495e1..23d8e23a7b9 100644 --- a/openmc/config.py +++ b/openmc/config.py @@ -178,7 +178,7 @@ def patch(self, key: str, value: Any): self[key] = previous_value -def _default_config() -> _Config: +def _default_config(**kwargs) -> _Config: """Create a configuration initialized from environment variables. This function checks for OPENMC_CROSS_SECTIONS, OPENMC_MG_CROSS_SECTIONS, @@ -192,11 +192,11 @@ def _default_config() -> _Config: A new configuration object. """ - config = _Config() + config = _Config(kwargs) for key,var in _Config._PATH_KEYS.items(): if var in os.environ: config[key] = os.environ[var] - + chain_file = config.get("chain_file") xs_path = config.get("cross_sections") if chain_file is None and xs_path is not None and xs_path.exists(): diff --git a/tests/unit_tests/test_config.py b/tests/unit_tests/test_config.py index 242c59b5e23..45e6a7e5a1b 100644 --- a/tests/unit_tests/test_config.py +++ b/tests/unit_tests/test_config.py @@ -13,6 +13,7 @@ def reset_config_and_env(): """A fixture to ensure each test has a clean config, env, and CWD.""" original_env = dict(os.environ) original_cwd = os.getcwd() + original_resolve_paths = openmc.config["resolve_paths"] # Reset environment variables that affect config for key in ['OPENMC_CROSS_SECTIONS', 'OPENMC_MG_CROSS_SECTIONS', 'OPENMC_CHAIN_FILE']: @@ -25,13 +26,13 @@ def reset_config_and_env(): try: yield finally: - # Restore environment and CWD + # Restore environment, CWD and resolve_paths os.environ.clear() os.environ.update(original_env) os.chdir(original_cwd) # Restore config one last time for safety between modules - openmc.config = _default_config() + openmc.config = _default_config(resolve_paths=original_resolve_paths) def test_config_basics(): diff --git a/tools/ci/gha-script.sh b/tools/ci/gha-script.sh index c7c634ffa33..c0f754c32c8 100755 --- a/tools/ci/gha-script.sh +++ b/tools/ci/gha-script.sh @@ -14,5 +14,5 @@ if [[ $EVENT == 'y' ]]; then args="${args} --event " fi -# Run regression and unit tests -pytest --cov=openmc -v $args tests +# Run unit tests and then regression tests +pytest --cov=openmc -v $args tests/unit_tests tests/regression_tests From eaed4009873e6dc28e0aa0794209be2857552163 Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Wed, 3 Sep 2025 05:52:27 +0200 Subject: [PATCH 103/159] Fixed a bug in plotting cross sections with S(a,b) data (#3558) --- openmc/plotter.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openmc/plotter.py b/openmc/plotter.py index 85c4963a76f..693cdaca4eb 100644 --- a/openmc/plotter.py +++ b/openmc/plotter.py @@ -413,7 +413,8 @@ def _calculate_cexs_nuclide(this, types, temperature=294., sab_name=None, # Prep S(a,b) data if needed if sab_name: - sab = openmc.data.ThermalScattering.from_hdf5(sab_name) + sab = openmc.data.ThermalScattering.from_hdf5( + library.get_by_material(sab_name, data_type='thermal')['path']) # Obtain the nearest temperature if strT in sab.temperatures: sabT = strT @@ -640,14 +641,13 @@ def _calculate_cexs_elem_mat(this, types, temperature=294., sab = openmc.data.ThermalScattering.from_hdf5( library.get_by_material(sab_name, data_type='thermal')['path']) for nuc in sab.nuclides: - sabs[nuc] = library.get_by_material(sab_name, - data_type='thermal')['path'] + sabs[nuc] = sab_name else: if sab_name: - sab = openmc.data.ThermalScattering.from_hdf5(sab_name) + sab = openmc.data.ThermalScattering.from_hdf5( + library.get_by_material(sab_name, data_type='thermal')['path']) for nuc in sab.nuclides: - sabs[nuc] = library.get_by_material(sab_name, - data_type='thermal')['path'] + sabs[nuc] = sab_name # Now we can create the data sets to be plotted xs = {} @@ -655,8 +655,8 @@ def _calculate_cexs_elem_mat(this, types, temperature=294., for nuclide in nuclides.items(): name = nuclide[0] nuc = nuclide[1] - sab_tab = sabs[name] - temp_E, temp_xs = calculate_cexs(nuc, types, T, sab_tab, cross_sections, + sab_name = sabs[name] + temp_E, temp_xs = calculate_cexs(nuc, types, T, sab_name, cross_sections, ncrystal_cfg=ncrystal_cfg ) E.append(temp_E) From 5918564727a34ac7bb071ceafe1a7bf4d50542d7 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 3 Sep 2025 11:35:11 -0500 Subject: [PATCH 104/159] Bump up tolerance for flaky activation test (#3560) --- tests/unit_tests/test_deplete_activation.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/unit_tests/test_deplete_activation.py b/tests/unit_tests/test_deplete_activation.py index 6afea8c9f9f..cbe00d680c9 100644 --- a/tests/unit_tests/test_deplete_activation.py +++ b/tests/unit_tests/test_deplete_activation.py @@ -45,7 +45,7 @@ def model(): @pytest.mark.parametrize("reaction_rate_mode,reaction_rate_opts,tolerance", [ ("direct", {}, 1e-5), - ("flux", {'energies': ENERGIES}, 0.01), + ("flux", {'energies': ENERGIES}, 0.1), ("flux", {'energies': ENERGIES, 'reactions': ['(n,gamma)']}, 1e-5), ("flux", {'energies': ENERGIES, 'reactions': ['(n,gamma)'], 'nuclides': ['W186', 'H3']}, 1e-2), ]) @@ -61,11 +61,10 @@ def test_activation(run_in_tmpdir, model, reaction_rate_mode, reaction_rate_opts w186 = openmc.deplete.Nuclide('W186') w186.add_reaction('(n,gamma)', None, 0.0, 1.0) chain.add_nuclide(w186) - chain.export_to_xml('test_chain.xml') # Create transport operator op = openmc.deplete.CoupledOperator( - model, 'test_chain.xml', + model, chain, normalization_mode="source-rate", reaction_rate_mode=reaction_rate_mode, reaction_rate_opts=reaction_rate_opts, From 366509051381db89a3a6a7b3346e3b339e8af0af Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 5 Sep 2025 06:34:49 -0500 Subject: [PATCH 105/159] Do not apply boundary conditions when initialized in volume calculation mode (#3562) --- src/particle.cpp | 3 ++- tests/unit_tests/test_mesh.py | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/particle.cpp b/src/particle.cpp index fb41d82e950..fd4df14c3ff 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -544,7 +544,8 @@ void Particle::cross_surface(const Surface& surf) #endif // Handle any applicable boundary conditions. - if (surf.bc_ && settings::run_mode != RunMode::PLOTTING) { + if (surf.bc_ && settings::run_mode != RunMode::PLOTTING && + settings::run_mode != RunMode::VOLUME) { surf.bc_->handle_particle(*this, surf); return; } diff --git a/tests/unit_tests/test_mesh.py b/tests/unit_tests/test_mesh.py index 67ca4028e06..1930e3264ed 100644 --- a/tests/unit_tests/test_mesh.py +++ b/tests/unit_tests/test_mesh.py @@ -620,6 +620,33 @@ def test_mesh_material_volumes_serialize(): assert new_volumes.by_element(3) == [(2, 1.0)] +def test_mesh_material_volumes_boundary_conditions(sphere_model): + """Test the material volumes method using a regular mesh + that overlaps with a vacuum boundary condition.""" + + mesh = openmc.SphericalMesh.from_domain(sphere_model.geometry, dimension=(1, 1, 1)) + # extend mesh beyond the outer sphere surface to test rays crossing the boundary condition + mesh.r_grid[-1] += 5.0 + + # add a new cell to the modelthat occupies the outside of the sphere + sphere_surfaces = list(filter(lambda s: isinstance(s, openmc.Sphere), + sphere_model.geometry.get_all_surfaces().values())) + outer_cell = openmc.Cell(region=+sphere_surfaces[0]) + sphere_model.geometry.root_universe.add_cell(outer_cell) + + volumes = mesh.material_volumes(sphere_model, (0, 100, 100)) + sphere_volume = 4/3*np.pi*25**3 + mats = sphere_model.materials + expected_volumes = [(mats[0].id, 0.25*sphere_volume), + (mats[1].id, 0.25*sphere_volume), + (mats[2].id, 0.5*sphere_volume), + (None, 4/3*np.pi*mesh.r_grid[-1]**3 - sphere_volume)] + + for evaluated, expected in zip(volumes.by_element(0), expected_volumes): + assert evaluated[0] == expected[0] + assert evaluated[1] == pytest.approx(expected[1], rel=1e-2) + + def test_raytrace_mesh_infinite_loop(run_in_tmpdir): # Create a model with one large spherical cell sphere = openmc.Sphere(r=100, boundary_type='vacuum') From ca4295748d1b547ea3e5d9c8fb1ec55b64809289 Mon Sep 17 00:00:00 2001 From: Robert Carlsen Date: Wed, 10 Sep 2025 20:13:28 -0600 Subject: [PATCH 106/159] depletion: fix performance of chain matrix construction (#3567) Co-authored-by: GuySten <62616591+GuySten@users.noreply.github.com> --- openmc/deplete/chain.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index f1a23317fb9..ac5c02aa501 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -627,9 +627,15 @@ def form_matrix(self, rates, fission_yields=None): """ reactions = set() - # Use DOK matrix as intermediate representation for matrix n = len(self) - matrix = sp.dok_matrix((n, n)) + + # we accumulate indices and value entries for everything and create the matrix + # in one step at the end to avoid expensive index checks scipy otherwise does. + rows, cols, vals = [], [], [] + def setval(i, j, val): + rows.append(i) + cols.append(j) + vals.append(val) if fission_yields is None: fission_yields = self.get_default_fission_yields() @@ -639,7 +645,7 @@ def form_matrix(self, rates, fission_yields=None): if nuc.half_life is not None: decay_constant = math.log(2) / nuc.half_life if decay_constant != 0.0: - matrix[i, i] -= decay_constant + setval(i, i, -decay_constant) # Gain from radioactive decay if nuc.n_decay_modes != 0: @@ -650,19 +656,19 @@ def form_matrix(self, rates, fission_yields=None): if branch_val != 0.0: if target is not None: k = self.nuclide_dict[target] - matrix[k, i] += branch_val + setval(k, i, branch_val) # Produce alphas and protons from decay if 'alpha' in decay_type: k = self.nuclide_dict.get('He4') if k is not None: count = decay_type.count('alpha') - matrix[k, i] += count * branch_val + setval(k, i, count * branch_val) elif 'p' in decay_type: k = self.nuclide_dict.get('H1') if k is not None: count = decay_type.count('p') - matrix[k, i] += count * branch_val + setval(k, i, count * branch_val) if nuc.name in rates.index_nuc: # Extract all reactions for this nuclide in this cell @@ -679,13 +685,13 @@ def form_matrix(self, rates, fission_yields=None): if r_type not in reactions: reactions.add(r_type) if path_rate != 0.0: - matrix[i, i] -= path_rate + setval(i, i, -path_rate) # Gain term; allow for total annihilation for debug purposes if r_type != 'fission': if target is not None and path_rate != 0.0: k = self.nuclide_dict[target] - matrix[k, i] += path_rate * br + setval(k, i, path_rate * br) # Determine light nuclide production, e.g., (n,d) should # produce H2 @@ -693,20 +699,20 @@ def form_matrix(self, rates, fission_yields=None): for light_nuc in light_nucs: k = self.nuclide_dict.get(light_nuc) if k is not None: - matrix[k, i] += path_rate * br + setval(k, i, path_rate * br) else: for product, y in fission_yields[nuc.name].items(): yield_val = y * path_rate if yield_val != 0.0: k = self.nuclide_dict[product] - matrix[k, i] += yield_val + setval(k, i, yield_val) # Clear set of reactions reactions.clear() # Return CSC representation instead of DOK - return matrix.tocsc() + return sp.csc_matrix((vals, (rows, cols)), shape=(n, n)) def form_rr_term(self, tr_rates, current_timestep, mats): """Function to form the transfer rate term matrices. From c7175289eb0f801b38b7b684a5ddd4803261f66a Mon Sep 17 00:00:00 2001 From: Jack Fletcher <115663563+j-fletcher@users.noreply.github.com> Date: Thu, 11 Sep 2025 08:57:02 -0400 Subject: [PATCH 107/159] PowerLaw raises an error if sampling interval contains negative values (#3542) --- openmc/stats/univariate.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/openmc/stats/univariate.py b/openmc/stats/univariate.py index 28d5e87ef9e..d6cf19f2b87 100644 --- a/openmc/stats/univariate.py +++ b/openmc/stats/univariate.py @@ -480,6 +480,9 @@ class PowerLaw(Univariate): """ def __init__(self, a: float = 0.0, b: float = 1.0, n: float = 0.): + if a >= b: + raise ValueError( + "Lower bound of sampling interval must be less than upper bound.") self.a = a self.b = b self.n = n @@ -494,6 +497,9 @@ def a(self): @a.setter def a(self, a): cv.check_type('interval lower bound', a, Real) + if a < 0: + raise ValueError( + "PowerLaw sampling is restricted to positive-valued intervals.") self._a = a @property @@ -503,6 +509,9 @@ def b(self): @b.setter def b(self, b): cv.check_type('interval upper bound', b, Real) + if b < 0: + raise ValueError( + "PowerLaw sampling is restricted to positive-valued intervals.") self._b = b @property From afd9d0607445f00c6de97b1ff866521c319722b0 Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Fri, 12 Sep 2025 22:23:17 +0200 Subject: [PATCH 108/159] Fixed a bug when combining TimeFilter, MeshFilter, and tracklength estimator (#3525) Co-authored-by: Paul Romano --- include/openmc/tallies/tally.h | 10 ++++ include/openmc/tallies/tally_scoring.h | 10 ++++ src/particle.cpp | 5 ++ src/tallies/tally.cpp | 80 ++++++++++++++++++++----- src/tallies/tally_scoring.cpp | 59 ++++++++++++++++-- tests/unit_tests/test_mesh.py | 83 +++++++++++++++++++++++++- 6 files changed, 227 insertions(+), 20 deletions(-) diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index 8c088c46089..3beeb9d5ac5 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -203,11 +203,14 @@ extern vector> tallies; extern vector active_tallies; extern vector active_analog_tallies; extern vector active_tracklength_tallies; +extern vector active_timed_tracklength_tallies; extern vector active_collision_tallies; extern vector active_meshsurf_tallies; extern vector active_surface_tallies; extern vector active_pulse_height_tallies; extern vector pulse_height_cells; +extern vector time_grid; + } // namespace model namespace simulation { @@ -239,6 +242,13 @@ void read_tallies_xml(pugi::xml_node root); //! batch to a new random variable void accumulate_tallies(); +//! Determine distance to next time boundary +// +//! \param time Current time of particle +//! \param speed Speed of particle +//! \return Distance to next time boundary (or INFTY if none) +double distance_to_time_boundary(double time, double speed); + //! Determine which tallies should be active void setup_active_tallies(); diff --git a/include/openmc/tallies/tally_scoring.h b/include/openmc/tallies/tally_scoring.h index 28f1f16223d..c3ab779e6a1 100644 --- a/include/openmc/tallies/tally_scoring.h +++ b/include/openmc/tallies/tally_scoring.h @@ -91,6 +91,16 @@ void score_analog_tally_mg(Particle& p); //! \param distance The distance in [cm] traveled by the particle void score_tracklength_tally(Particle& p, double distance); +//! Score time filtered tallies using a tracklength estimate of the flux. +// +//! This is triggered at every event (surface crossing, lattice crossing, or +//! collision) and thus cannot be done for tallies that require post-collision +//! information. +// +//! \param p The particle being tracked +//! \param total_distance The distance in [cm] traveled by the particle +void score_timed_tracklength_tally(Particle& p, double total_distance); + //! Score surface or mesh-surface tallies for particle currents. // //! \param p The particle being tracked diff --git a/src/particle.cpp b/src/particle.cpp index fd4df14c3ff..f25ada02c8f 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -252,6 +252,11 @@ void Particle::event_advance() this->time() += dt; this->lifetime() += dt; + // Score timed track-length tallies + if (!model::active_timed_tracklength_tallies.empty()) { + score_timed_tracklength_tally(*this, distance); + } + // Score track-length tallies if (!model::active_tracklength_tallies.empty()) { score_tracklength_tally(*this, distance); diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index ba183860269..ae0bffe6eb0 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -27,10 +27,12 @@ #include "openmc/tallies/filter_legendre.h" #include "openmc/tallies/filter_mesh.h" #include "openmc/tallies/filter_meshborn.h" +#include "openmc/tallies/filter_meshmaterial.h" #include "openmc/tallies/filter_meshsurface.h" #include "openmc/tallies/filter_particle.h" #include "openmc/tallies/filter_sph_harm.h" #include "openmc/tallies/filter_surface.h" +#include "openmc/tallies/filter_time.h" #include "openmc/xml_interface.h" #include "xtensor/xadapt.hpp" @@ -38,9 +40,10 @@ #include "xtensor/xview.hpp" #include -#include // for max +#include // for max, set_union #include -#include // for size_t +#include // for size_t +#include // for back_inserter #include namespace openmc { @@ -56,11 +59,13 @@ vector> tallies; vector active_tallies; vector active_analog_tallies; vector active_tracklength_tallies; +vector active_timed_tracklength_tallies; vector active_collision_tallies; vector active_meshsurf_tallies; vector active_surface_tallies; vector active_pulse_height_tallies; vector pulse_height_cells; +vector time_grid; } // namespace model namespace simulation { @@ -243,8 +248,8 @@ Tally::Tally(pugi::xml_node node) for (int score : scores_) { switch (score) { case SCORE_PULSE_HEIGHT: - fatal_error( - "For pulse-height tallies, photon transport needs to be activated."); + fatal_error("For pulse-height tallies, photon transport needs to be " + "activated."); break; } } @@ -318,7 +323,8 @@ Tally::Tally(pugi::xml_node node) if (has_energyout && i_nuc == -1) { fatal_error(fmt::format( "Error on tally {}: Cannot use a " - "'nuclide_density' or 'temperature' derivative on a tally with an " + "'nuclide_density' or 'temperature' derivative on a tally with " + "an " "outgoing energy filter and 'total' nuclide rate. Instead, tally " "each nuclide in the material individually.", id_)); @@ -493,9 +499,9 @@ void Tally::add_filter(Filter* filter) void Tally::set_strides() { - // Set the strides. Filters are traversed in reverse so that the last filter - // has the shortest stride in memory and the first filter has the longest - // stride. + // Set the strides. Filters are traversed in reverse so that the last + // filter has the shortest stride in memory and the first filter has the + // longest stride. auto n = filters_.size(); strides_.resize(n, 0); int stride = 1; @@ -551,7 +557,8 @@ void Tally::set_scores(const vector& scores) // Iterate over the given scores. for (auto score_str : scores) { - // Make sure a delayed group filter wasn't used with an incompatible score. + // Make sure a delayed group filter wasn't used with an incompatible + // score. if (delayedgroup_filter_ != C_NONE) { if (score_str != "delayed-nu-fission" && score_str != "decay-rate") fatal_error("Cannot tally " + score_str + "with a delayedgroup filter"); @@ -984,8 +991,8 @@ void reduce_tally_results() } } - // Note that global tallies are *always* reduced even when no_reduce option is - // on. + // Note that global tallies are *always* reduced even when no_reduce option + // is on. // Get view of global tally values auto& gt = simulation::global_tallies; @@ -1064,21 +1071,59 @@ void accumulate_tallies() } } +double distance_to_time_boundary(double time, double speed) +{ + if (model::time_grid.empty()) { + return INFTY; + } else if (time >= model::time_grid.back()) { + return INFTY; + } else { + double next_time = + *std::upper_bound(model::time_grid.begin(), model::time_grid.end(), time); + return (next_time - time) * speed; + } +} + +//! Add new points to the global time grid +// +//! \param grid Vector of new time points to add +void add_to_time_grid(vector grid) +{ + if (grid.empty()) + return; + + // Create new vector with enough space to hold old and new grid points + vector merged; + merged.reserve(model::time_grid.size() + grid.size()); + + // Merge and remove duplicates + std::set_union(model::time_grid.begin(), model::time_grid.end(), grid.begin(), + grid.end(), std::back_inserter(merged)); + + // Swap in the new grid + model::time_grid.swap(merged); +} + void setup_active_tallies() { model::active_tallies.clear(); model::active_analog_tallies.clear(); model::active_tracklength_tallies.clear(); + model::active_timed_tracklength_tallies.clear(); model::active_collision_tallies.clear(); model::active_meshsurf_tallies.clear(); model::active_surface_tallies.clear(); model::active_pulse_height_tallies.clear(); + model::time_grid.clear(); for (auto i = 0; i < model::tallies.size(); ++i) { const auto& tally {*model::tallies[i]}; if (tally.active_) { model::active_tallies.push_back(i); + bool mesh_present = (tally.get_filter() || + tally.get_filter()); + auto time_filter = tally.get_filter(); switch (tally.type_) { case TallyType::VOLUME: @@ -1087,7 +1132,12 @@ void setup_active_tallies() model::active_analog_tallies.push_back(i); break; case TallyEstimator::TRACKLENGTH: - model::active_tracklength_tallies.push_back(i); + if (time_filter && mesh_present) { + model::active_timed_tracklength_tallies.push_back(i); + add_to_time_grid(time_filter->bins()); + } else { + model::active_tracklength_tallies.push_back(i); + } break; case TallyEstimator::COLLISION: model::active_collision_tallies.push_back(i); @@ -1123,10 +1173,12 @@ void free_memory_tally() model::active_tallies.clear(); model::active_analog_tallies.clear(); model::active_tracklength_tallies.clear(); + model::active_timed_tracklength_tallies.clear(); model::active_collision_tallies.clear(); model::active_meshsurf_tallies.clear(); model::active_surface_tallies.clear(); model::active_pulse_height_tallies.clear(); + model::time_grid.clear(); model::tally_map.clear(); } @@ -1465,8 +1517,8 @@ extern "C" int openmc_tally_get_n_realizations(int32_t index, int32_t* n) return 0; } -//! \brief Returns a pointer to a tally results array along with its shape. This -//! allows a user to obtain in-memory tally results from Python directly. +//! \brief Returns a pointer to a tally results array along with its shape. +//! This allows a user to obtain in-memory tally results from Python directly. extern "C" int openmc_tally_results( int32_t index, double** results, size_t* shape) { diff --git a/src/tallies/tally_scoring.cpp b/src/tallies/tally_scoring.cpp index e73fb90f311..04b047b6497 100644 --- a/src/tallies/tally_scoring.cpp +++ b/src/tallies/tally_scoring.cpp @@ -2404,15 +2404,13 @@ void score_analog_tally_mg(Particle& p) match.bins_present_ = false; } -void score_tracklength_tally(Particle& p, double distance) +void score_tracklength_tally_general( + Particle& p, double flux, const vector& tallies) { - // Determine the tracklength estimate of the flux - double flux = p.wgt() * distance; - // Set 'none' value for log union grid index int i_log_union = C_NONE; - for (auto i_tally : model::active_tracklength_tallies) { + for (auto i_tally : tallies) { const Tally& tally {*model::tallies[i_tally]}; // Initialize an iterator over valid filter bin combinations. If there are @@ -2481,6 +2479,57 @@ void score_tracklength_tally(Particle& p, double distance) match.bins_present_ = false; } +void score_timed_tracklength_tally(Particle& p, double total_distance) +{ + double speed = p.speed(); + double total_dt = total_distance / speed; + + // save particle last state + auto time_last = p.time_last(); + auto r_last = p.r_last(); + + // move particle back + p.move_distance(-total_distance); + p.time() -= total_dt; + p.lifetime() -= total_dt; + + double distance_traveled = 0.0; + while (distance_traveled < total_distance) { + + double distance = std::min(distance_to_time_boundary(p.time(), speed), + total_distance - distance_traveled); + double dt = distance / speed; + + // Save particle last state for tracklength tallies + p.time_last() = p.time(); + p.r_last() = p.r(); + + // Advance particle in space and time + p.move_distance(distance); + p.time() += dt; + p.lifetime() += dt; + + // Determine the tracklength estimate of the flux + double flux = p.wgt() * distance; + + score_tracklength_tally_general( + p, flux, model::active_timed_tracklength_tallies); + distance_traveled += distance; + } + + p.time_last() = time_last; + p.r_last() = r_last; +} + +void score_tracklength_tally(Particle& p, double distance) +{ + + // Determine the tracklength estimate of the flux + double flux = p.wgt() * distance; + + score_tracklength_tally_general(p, flux, model::active_tracklength_tallies); +} + void score_collision_tally(Particle& p) { // Determine the collision estimate of the flux diff --git a/tests/unit_tests/test_mesh.py b/tests/unit_tests/test_mesh.py index 1930e3264ed..f0f289408ab 100644 --- a/tests/unit_tests/test_mesh.py +++ b/tests/unit_tests/test_mesh.py @@ -3,10 +3,12 @@ from pathlib import Path import numpy as np +from scipy.stats import chi2 import pytest import openmc import openmc.lib from openmc.utility_funcs import change_directory +from uncertainties.unumpy import uarray, nominal_values, std_devs @pytest.mark.parametrize("val_left,val_right", [(0, 0), (-1., -1.), (2.0, 2)]) @@ -481,7 +483,7 @@ def test_umesh(run_in_tmpdir, simple_umesh, export_type): np.testing.assert_almost_equal(mean, ref_data) # attempt to apply a dataset with an improper size to a VTK write - with pytest.raises(ValueError, match='Cannot apply dataset "mean"') as e: + with pytest.raises(ValueError, match='Cannot apply dataset "mean"'): simple_umesh.write_data_to_vtk(datasets={'mean': ref_data[:-2]}, filename=filename) def test_mesh_get_homogenized_materials(): @@ -678,3 +680,82 @@ def test_raytrace_mesh_infinite_loop(run_in_tmpdir): # Run the model; this should not cause an infinite loop model.run() + + +def test_filter_time_mesh(run_in_tmpdir): + """Test combination of TimeFilter and MeshFilter""" + + # Define material + mat = openmc.Material() + mat.add_nuclide('Fe56', 1.0) + mat.set_density('g/cm3', 7.8) + + # Define geometry + surf_Z1 = openmc.XPlane(x0=-1e10, boundary_type="reflective") + surf_Z2 = openmc.XPlane(x0=1e10, boundary_type="reflective") + cell_F = openmc.Cell(fill=mat, region=+surf_Z1 & -surf_Z2) + model = openmc.Model() + model.geometry = openmc.Geometry([cell_F]) + + # Define settings + model.settings.run_mode = "fixed source" + model.settings.particles = 1000 + model.settings.batches = 20 + model.settings.output = {"tallies": False} + model.settings.cutoff = {"time_neutron": 1e-7} + + # Define tallies + + # Create a mesh filter that can be used in a tally + mesh = openmc.RegularMesh() + mesh.dimension = (21, 1, 1) + mesh.lower_left = (-20.5, -1e10, -1e10) + mesh.upper_right = (20.5, 1e10, 1e10) + time_grid = np.linspace(0.0, 1e-7, 21) + + mesh_filter = openmc.MeshFilter(mesh) + time_filter = openmc.TimeFilter(time_grid) + + # Now use the mesh filter in a tally and indicate what scores are desired + tally1 = openmc.Tally(name="collision") + tally1.estimator = "collision" + tally1.filters = [time_filter, mesh_filter] + tally1.scores = ["flux"] + tally2 = openmc.Tally(name="tracklength") + tally2.estimator = "tracklength" + tally2.filters = [time_filter, mesh_filter] + tally2.scores = ["flux"] + model.tallies = openmc.Tallies([tally1, tally2]) + + # Run and post-process + model.run(apply_tally_results=True) + + # Get radial flux distribution + flux_collision = tally1.mean.ravel() + flux_collision_unc = tally1.std_dev.ravel() + flux_tracklength = tally2.mean.ravel() + flux_tracklength_unc = tally2.std_dev.ravel() + + # Construct arrays with uncertainties + collision = uarray(flux_collision, flux_collision_unc) + tracklength = uarray(flux_tracklength, flux_tracklength_unc) + delta = collision - tracklength + + # Compute differences and standard deviations + diff = nominal_values(delta) + std_dev = std_devs(delta) + + # Exclude zero-uncertainty bins + mask = std_dev > 0.0 + dof = int(np.sum(mask)) + + # Global chi-square consistency test between collision and tracklength + # estimators. Target false positive rate ~1e-4 (1 in 10,000) + z = diff[mask] / std_dev[mask] + chi2_stat = np.sum(z * z) + alpha = 1.0e-4 + crit = chi2.ppf(1 - alpha, dof) + assert chi2_stat < crit, ( + f"Collision vs tracklength tallies disagree: chi2={chi2_stat:.2f} " + f">= {crit=:.2f} ({dof=}, {alpha=})" + ) From 607f6babe5c7fc1a441082fb355e7f30f182e72d Mon Sep 17 00:00:00 2001 From: Kevin Sawatzky <66632997+nuclearkevin@users.noreply.github.com> Date: Thu, 18 Sep 2025 23:11:06 -0500 Subject: [PATCH 109/159] Add distributed cell densities (#3546) Co-authored-by: Patrick Shriwise Co-authored-by: Paul Romano --- docs/source/capi/index.rst | 29 ++- docs/source/io_formats/properties.rst | 3 +- docs/source/io_formats/summary.rst | 3 +- include/openmc/capi.h | 4 + include/openmc/cell.h | 25 +++ include/openmc/constants.h | 4 +- include/openmc/geometry_aux.h | 6 + include/openmc/material.h | 7 + include/openmc/particle_data.h | 8 + openmc/cell.py | 43 ++++- openmc/lib/cell.py | 46 +++++ openmc/model/model.py | 16 +- src/cell.cpp | 178 +++++++++++++++++- src/geometry.cpp | 4 +- src/geometry_aux.cpp | 29 +++ src/initialize.cpp | 9 + src/material.cpp | 4 +- src/mgxs.cpp | 8 +- src/particle.cpp | 7 +- src/physics.cpp | 4 +- src/tallies/tally_scoring.cpp | 33 ++-- tests/regression_tests/cpp_driver/driver.cpp | 18 +- .../cpp_driver/results_true.dat | 22 +-- .../regression_tests/dagmc/external/main.cpp | 3 + .../lattice_distribrho/__init__.py | 0 .../lattice_distribrho/inputs_true.dat | 40 ++++ .../lattice_distribrho/results_true.dat | 2 + .../lattice_distribrho/test.py | 51 +++++ .../multipole/results_true.dat | 1 + tests/unit_tests/test_cell.py | 23 +++ tests/unit_tests/test_lib.py | 23 +++ tests/unit_tests/test_model.py | 6 +- 32 files changed, 607 insertions(+), 52 deletions(-) create mode 100644 tests/regression_tests/lattice_distribrho/__init__.py create mode 100644 tests/regression_tests/lattice_distribrho/inputs_true.dat create mode 100644 tests/regression_tests/lattice_distribrho/results_true.dat create mode 100644 tests/regression_tests/lattice_distribrho/test.py diff --git a/docs/source/capi/index.rst b/docs/source/capi/index.rst index d9ac0d1e019..2583d51dff2 100644 --- a/docs/source/capi/index.rst +++ b/docs/source/capi/index.rst @@ -84,6 +84,17 @@ Functions :return: Return status (negative if an error occurred) :rtype: int +.. c:function:: int openmc_cell_get_density(int32_t index, const int32_t* instance, double* density) + + Get the density of a cell + + :param int32_t index: Index in the cells array + :param int32_t* instance: Which instance of the cell. If a null pointer is passed, the density + multiplier of the first instance is returned. + :param double* density: Density of the cell in [g/cm3] + :return: Return status (negative if an error occurred) + :rtype: int + .. c:function:: int openmc_cell_set_fill(int32_t index, int type, int32_t n, const int32_t* indices) Set the fill for a cell @@ -113,8 +124,22 @@ Functions :param double T: Temperature in Kelvin :param instance: Which instance of the cell. To set the temperature for all instances, pass a null pointer. - :param set_contained: If the cell is not filled by a material, whether to set the temperatures - of all filled cells + :param bool set_contained: If the cell is not filled by a material, whether + to set the temperatures of all filled cells + :type instance: const int32_t* + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_cell_set_density(index index, double density, const int32_t* instance, bool set_contained) + + Set the density of a cell. + + :param int32_t index: Index in the cells array + :param double density: Density of the cell in [g/cm3] + :param instance: Which instance of the cell. To set the density multiplier for all + instances, pass a null pointer. + :param bool set_contained: If the cell is not filled by a material, whether + to set the density multiplier of all filled cells :type instance: const int32_t* :return: Return status (negative if an error occurred) :rtype: int diff --git a/docs/source/io_formats/properties.rst b/docs/source/io_formats/properties.rst index 5030e78f35d..4cc5da379b1 100644 --- a/docs/source/io_formats/properties.rst +++ b/docs/source/io_formats/properties.rst @@ -4,7 +4,7 @@ Properties File Format ====================== -The current version of the properties file format is 1.0. +The current version of the properties file format is 1.1. **/** @@ -25,6 +25,7 @@ The current version of the properties file format is 1.0. **/geometry/cells/cell /** :Datasets: - **temperature** (*double[]*) -- Temperature of the cell in [K]. + - **density** (*double[]*) -- Density of the cell in [g/cm3]. **/materials/** diff --git a/docs/source/io_formats/summary.rst b/docs/source/io_formats/summary.rst index 7d3ab94d9f4..64ca68b9c37 100644 --- a/docs/source/io_formats/summary.rst +++ b/docs/source/io_formats/summary.rst @@ -4,7 +4,7 @@ Summary File Format =================== -The current version of the summary file format is 6.0. +The current version of the summary file format is 6.1. **/** @@ -38,6 +38,7 @@ The current version of the summary file format is 6.0. is an array if the cell uses distributed materials, otherwise it is a scalar. - **temperature** (*double[]*) -- Temperature of the cell in Kelvin. + - **density** (*double[]*) -- Density of the cell in [g/cm3]. - **translation** (*double[3]*) -- Translation applied to the fill universe. This dataset is present only if fill_type is set to 'universe'. diff --git a/include/openmc/capi.h b/include/openmc/capi.h index 54257d09385..d8041ef4149 100644 --- a/include/openmc/capi.h +++ b/include/openmc/capi.h @@ -17,6 +17,8 @@ int openmc_cell_get_fill( int openmc_cell_get_id(int32_t index, int32_t* id); int openmc_cell_get_temperature( int32_t index, const int32_t* instance, double* T); +int openmc_cell_get_density( + int32_t index, const int32_t* instance, double* rho); int openmc_cell_get_translation(int32_t index, double xyz[]); int openmc_cell_get_rotation(int32_t index, double rot[], size_t* n); int openmc_cell_get_name(int32_t index, const char** name); @@ -27,6 +29,8 @@ int openmc_cell_set_fill( int openmc_cell_set_id(int32_t index, int32_t id); int openmc_cell_set_temperature( int32_t index, double T, const int32_t* instance, bool set_contained = false); +int openmc_cell_set_density(int32_t index, double rho, const int32_t* instance, + bool set_contained = false); int openmc_cell_set_translation(int32_t index, const double xyz[]); int openmc_cell_set_rotation(int32_t index, const double rot[], size_t rot_len); int openmc_dagmc_universe_get_cell_ids( diff --git a/include/openmc/cell.h b/include/openmc/cell.h index e9fcfe3911b..9dfe2339ab6 100644 --- a/include/openmc/cell.h +++ b/include/openmc/cell.h @@ -216,6 +216,18 @@ class Cell { //! \return Temperature in [K] double temperature(int32_t instance = -1) const; + //! Get the density multiplier of a cell instance + //! \param[in] instance Instance index. If -1 is given, the density multiplier + //! for the first instance is returned. + //! \return Density multiplier + double density_mult(int32_t instance = -1) const; + + //! Get the density of a cell instance in g/cm3 + //! \param[in] instance Instance index. If -1 is given, the density + //! for the first instance is returned. + //! \return Density in [g/cm3] + double density(int32_t instance = -1) const; + //! Set the temperature of a cell instance //! \param[in] T Temperature in [K] //! \param[in] instance Instance index. If -1 is given, the temperature for @@ -226,6 +238,16 @@ class Cell { void set_temperature( double T, int32_t instance = -1, bool set_contained = false); + //! Set the density of a cell instance + //! \param[in] density Density [g/cm3] + //! \param[in] instance Instance index. If -1 is given, the density + //! for all instances is set. + //! \param[in] set_contained If this cell is not filled with a material, + //! collect all contained cells with material fills and set their + //! densities. + void set_density( + double density, int32_t instance = -1, bool set_contained = false); + int32_t n_instances() const; //! Set the rotation matrix of a cell instance @@ -334,6 +356,9 @@ class Cell { //! T. The units are sqrt(eV). vector sqrtkT_; + //! \brief Unitless density multiplier(s) within this cell. + vector density_mult_; + //! \brief Neighboring cells in the same universe. NeighborList neighbors_; diff --git a/include/openmc/constants.h b/include/openmc/constants.h index ae705607958..df13da3707d 100644 --- a/include/openmc/constants.h +++ b/include/openmc/constants.h @@ -28,11 +28,11 @@ constexpr int HDF5_VERSION[] {3, 0}; constexpr array VERSION_STATEPOINT {18, 1}; constexpr array VERSION_PARTICLE_RESTART {2, 0}; constexpr array VERSION_TRACK {3, 0}; -constexpr array VERSION_SUMMARY {6, 0}; +constexpr array VERSION_SUMMARY {6, 1}; constexpr array VERSION_VOLUME {1, 0}; constexpr array VERSION_VOXEL {2, 0}; constexpr array VERSION_MGXS_LIBRARY {1, 0}; -constexpr array VERSION_PROPERTIES {1, 0}; +constexpr array VERSION_PROPERTIES {1, 1}; constexpr array VERSION_WEIGHT_WINDOWS {1, 0}; // ============================================================================ diff --git a/include/openmc/geometry_aux.h b/include/openmc/geometry_aux.h index f60f2d649ee..4dafdea5c2c 100644 --- a/include/openmc/geometry_aux.h +++ b/include/openmc/geometry_aux.h @@ -37,6 +37,12 @@ void adjust_indices(); void assign_temperatures(); +//============================================================================== +//! Finalize densities (compute density multipliers). +//============================================================================== + +void finalize_cell_densities(); + //============================================================================== //! \brief Obtain a list of temperatures that each nuclide/thermal scattering //! table appears at in the model. Later, this list is used to determine the diff --git a/include/openmc/material.h b/include/openmc/material.h index fe587a86f9e..e36946c71b6 100644 --- a/include/openmc/material.h +++ b/include/openmc/material.h @@ -99,6 +99,13 @@ class Material { //---------------------------------------------------------------------------- // Accessors + //! Get the atom density in [atom/b-cm] + //! \return Density in [atom/b-cm] + double atom_density(int32_t i, double rho_multiplier = 1.0) const + { + return atom_density_(i) * rho_multiplier; + } + //! Get density in [atom/b-cm] //! \return Density in [atom/b-cm] double density() const { return density_; } diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index ec393845f81..1a22f583798 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -389,6 +389,11 @@ class GeometryState { const double& sqrtkT() const { return sqrtkT_; } double& sqrtkT_last() { return sqrtkT_last_; } + // density multiplier of the current and last cell + double& density_mult() { return density_mult_; } + const double& density_mult() const { return density_mult_; } + double& density_mult_last() { return density_mult_last_; } + private: int64_t id_ {-1}; //!< Unique ID @@ -417,6 +422,9 @@ class GeometryState { double sqrtkT_ {-1.0}; //!< sqrt(k_Boltzmann * temperature) in eV double sqrtkT_last_ {0.0}; //!< last temperature + double density_mult_ {1.0}; //!< density multiplier + double density_mult_last_ {1.0}; //!< last density multiplier + #ifdef OPENMC_DAGMC_ENABLED moab::DagMC::RayHistory history_; Direction last_dir_; diff --git a/openmc/cell.py b/openmc/cell.py index 88aaebc8f27..82a034b1c8c 100644 --- a/openmc/cell.py +++ b/openmc/cell.py @@ -72,6 +72,10 @@ class Cell(IDManagerMixin): temperature : float or iterable of float Temperature of the cell in Kelvin. Multiple temperatures can be given to give each distributed cell instance a unique temperature. + density : float or iterable of float + Density of the cell in [g/cm3]. Multiple densities can be given to give + each distributed cell instance a unique density. Densities set here will + override the density set on materials used to fill the cell. translation : Iterable of float If the cell is filled with a universe, this array specifies a vector that is used to translate (shift) the universe. @@ -109,6 +113,7 @@ def __init__(self, cell_id=None, name='', fill=None, region=None): self._rotation = None self._rotation_matrix = None self._temperature = None + self._density = None self._translation = None self._paths = None self._num_instances = None @@ -141,6 +146,7 @@ def __repr__(self): if self.fill_type == 'material': string += '\t{0: <15}=\t{1}\n'.format('Temperature', self.temperature) + string += '\t{0: <15}=\t{1}\n'.format('Density', self.density) string += '{: <16}=\t{}\n'.format('\tTranslation', self.translation) string += '{: <16}=\t{}\n'.format('\tVolume', self.volume) @@ -257,6 +263,30 @@ def temperature(self, temperature): else: self._temperature = temperature + @property + def density(self): + return self._density + + @density.setter + def density(self, density): + # Make sure densities are greater than zero + cv.check_type('cell density', density, (Iterable, Real), none_ok=True) + if isinstance(density, Iterable): + cv.check_type('cell density', density, Iterable, Real) + for rho in density: + cv.check_greater_than('cell density', rho, 0.0, True) + elif isinstance(density, Real): + cv.check_greater_than('cell density', density, 0.0, True) + + # If this cell is filled with a universe or lattice, propagate + # densities to all cells contained. Otherwise, simply assign it. + if self.fill_type in ('universe', 'lattice'): + for c in self.get_all_cells().values(): + if c.fill_type == 'material': + c._density = density + else: + self._density = density + @property def translation(self): return self._translation @@ -525,6 +555,8 @@ def clone(self, clone_materials=True, clone_regions=True, memo=None): clone.volume = self.volume if self.temperature is not None: clone.temperature = self.temperature + if self.density is not None: + clone.density = self.density if self.translation is not None: clone.translation = self.translation if self.rotation is not None: @@ -650,6 +682,12 @@ def create_surface_elements(node, element, memo=None): else: element.set("temperature", str(self.temperature)) + if self.density is not None: + if isinstance(self.density, Iterable): + element.set("density", ' '.join(str(t) for t in self.density)) + else: + element.set("density", str(self.density)) + if self.translation is not None: element.set("translation", ' '.join(map(str, self.translation))) @@ -711,10 +749,13 @@ def from_xml_element(cls, elem, surfaces, materials, get_universe): c.temperature = temperature else: c.temperature = temperature[0] + density = get_elem_list(elem, 'density', float) + if density is not None: + c.density = density if len(density) > 1 else density[0] v = get_text(elem, 'volume') if v is not None: c.volume = float(v) - for key in ('temperature', 'rotation', 'translation'): + for key in ('temperature', 'density', 'rotation', 'translation'): values = get_elem_list(elem, key, float) if values is not None: if key == 'rotation' and len(values) == 9: diff --git a/openmc/lib/cell.py b/openmc/lib/cell.py index 971a24cba91..dfd09d2f9c1 100644 --- a/openmc/lib/cell.py +++ b/openmc/lib/cell.py @@ -34,6 +34,10 @@ c_int32, POINTER(c_int32), POINTER(c_double)] _dll.openmc_cell_get_temperature.restype = c_int _dll.openmc_cell_get_temperature.errcheck = _error_handler +_dll.openmc_cell_get_density.argtypes = [ + c_int32, POINTER(c_int32), POINTER(c_double)] +_dll.openmc_cell_get_density.restype = c_int +_dll.openmc_cell_get_density.errcheck = _error_handler _dll.openmc_cell_get_name.argtypes = [c_int32, POINTER(c_char_p)] _dll.openmc_cell_get_name.restype = c_int _dll.openmc_cell_get_name.errcheck = _error_handler @@ -58,6 +62,10 @@ c_int32, c_double, POINTER(c_int32), c_bool] _dll.openmc_cell_set_temperature.restype = c_int _dll.openmc_cell_set_temperature.errcheck = _error_handler +_dll.openmc_cell_set_density.argtypes = [ + c_int32, c_double, POINTER(c_int32), c_bool] +_dll.openmc_cell_set_density.restype = c_int +_dll.openmc_cell_set_density.errcheck = _error_handler _dll.openmc_cell_set_translation.argtypes = [c_int32, POINTER(c_double)] _dll.openmc_cell_set_translation.restype = c_int _dll.openmc_cell_set_translation.errcheck = _error_handler @@ -236,6 +244,44 @@ def set_temperature(self, T, instance=None, set_contained=False): _dll.openmc_cell_set_temperature(self._index, T, instance, set_contained) + def get_density(self, instance: int | None = None): + """Get the density of a cell in [g/cm3] + + Parameters + ---------- + instance : int or None + Which instance of the cell + + """ + + if instance is not None: + instance = c_int32(instance) + + rho = c_double() + _dll.openmc_cell_get_density(self._index, instance, rho) + return rho.value + + def set_density(self, rho: float, instance: int | None = None, + set_contained: bool = False): + """Set the density of a cell + + Parameters + ---------- + rho : float + Density of the cell in [g/cm3] + instance : int or None + Which instance of the cell + set_contained : bool + If cell is not filled by a material, whether to set the density + of all filled cells + + """ + + if instance is not None: + instance = c_int32(instance) + + _dll.openmc_cell_set_density(self._index, rho, instance, set_contained) + @property def translation(self): translation = np.zeros(3) diff --git a/openmc/model/model.py b/openmc/model/model.py index 03fdcda1fc8..59e6fa511e5 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -633,7 +633,7 @@ def import_properties(self, filename: PathLike): raise ValueError("Number of cells in properties file doesn't " "match current model.") - # Update temperatures for cells filled with materials + # Update temperatures and densities for cells filled with materials for name, group in cells_group.items(): cell_id = int(name.split()[1]) cell = cells[cell_id] @@ -648,6 +648,20 @@ def import_properties(self, filename: PathLike): else: lib_cell.set_temperature(temperature[0]) + if group['density']: + density = group['density'][()] + if density.size > 1: + cell.density = [rho for rho in density] + else: + cell.density = density + if self.is_initialized: + lib_cell = openmc.lib.cells[cell_id] + if density.size > 1: + for i, rho in enumerate(density): + lib_cell.set_density(rho, i) + else: + lib_cell.set_density(density[0]) + # Make sure number of materials matches mats_group = fh['materials'] n_cells = mats_group.attrs['n_materials'] diff --git a/src/cell.cpp b/src/cell.cpp index d838bfbb4d1..e2479994a6a 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -101,6 +101,25 @@ double Cell::temperature(int32_t instance) const } } +double Cell::density_mult(int32_t instance) const +{ + if (instance >= 0) { + return density_mult_.size() == 1 ? density_mult_.at(0) + : density_mult_.at(instance); + } else { + return density_mult_[0]; + } +} + +double Cell::density(int32_t instance) const +{ + const int32_t mat_index = material(instance); + if (mat_index == MATERIAL_VOID) + return 0.0; + + return density_mult(instance) * model::materials[mat_index]->density_gpcc(); +} + void Cell::set_temperature(double T, int32_t instance, bool set_contained) { if (settings::temperature_method == TemperatureMethod::INTERPOLATION) { @@ -151,6 +170,47 @@ void Cell::set_temperature(double T, int32_t instance, bool set_contained) } } +void Cell::set_density(double density, int32_t instance, bool set_contained) +{ + if (type_ != Fill::MATERIAL && !set_contained) { + fatal_error( + fmt::format("Attempted to set the density multiplier of cell {} " + "which is not filled by a material.", + id_)); + } + + if (type_ == Fill::MATERIAL) { + const int32_t mat_index = material(instance); + if (mat_index == MATERIAL_VOID) + return; + + if (instance >= 0) { + // If density multiplier vector is not big enough, resize it first + if (density_mult_.size() != n_instances()) + density_mult_.resize(n_instances(), density_mult_[0]); + + // Set density multiplier for the corresponding instance + density_mult_.at(instance) = + density / model::materials[mat_index]->density_gpcc(); + } else { + // Set density multiplier for all instances + for (auto& x : density_mult_) { + x = density / model::materials[mat_index]->density_gpcc(); + } + } + } else { + auto contained_cells = this->get_contained_cells(instance); + for (const auto& entry : contained_cells) { + auto& cell = model::cells[entry.first]; + assert(cell->type_ == Fill::MATERIAL); + auto& instances = entry.second; + for (auto instance : instances) { + cell->set_density(density, instance); + } + } + } +} + void Cell::export_properties_hdf5(hid_t group) const { // Create a group for this cell. @@ -162,6 +222,15 @@ void Cell::export_properties_hdf5(hid_t group) const temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN); write_dataset(cell_group, "temperature", temps); + // Write density for one or more cell instances + if (type_ == Fill::MATERIAL && material_.size() > 0) { + vector density; + for (int32_t i = 0; i < density_mult_.size(); ++i) + density.push_back(this->density(i)); + + write_dataset(cell_group, "density", density); + } + close_group(cell_group); } @@ -176,7 +245,7 @@ void Cell::import_properties_hdf5(hid_t group) // Ensure number of temperatures makes sense auto n_temps = temps.size(); if (n_temps > 1 && n_temps != n_instances()) { - throw std::runtime_error(fmt::format( + fatal_error(fmt::format( "Number of temperatures for cell {} doesn't match number of instances", id_)); } @@ -188,6 +257,25 @@ void Cell::import_properties_hdf5(hid_t group) this->set_temperature(temps[i], i); } + // Read densities + if (object_exists(cell_group, "density")) { + vector density; + read_dataset(cell_group, "density", density); + + // Ensure number of densities makes sense + auto n_density = density.size(); + if (n_density > 1 && n_density != n_instances()) { + fatal_error(fmt::format("Number of densities for cell {} " + "doesn't match number of instances", + id_)); + } + + // Set densities. + for (int32_t i = 0; i < n_density; ++i) { + this->set_density(density[i], i); + } + } + close_group(cell_group); } @@ -227,6 +315,8 @@ void Cell::to_hdf5(hid_t cell_group) const temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN); write_dataset(group, "temperature", temps); + write_dataset(group, "density_mult", density_mult_); + } else if (type_ == Fill::UNIVERSE) { write_dataset(group, "fill_type", "universe"); write_dataset(group, "fill", model::universes[fill_]->id_); @@ -344,6 +434,44 @@ CSGCell::CSGCell(pugi::xml_node cell_node) } } + // Read the density element which can be distributed similar to temperature. + // These get assigned to the density multiplier, requiring a division by + // the material density. + // Note: calculating the actual density multiplier is deferred until materials + // are finalized. density_mult_ contains the true density in the meantime. + if (check_for_node(cell_node, "density")) { + density_mult_ = get_node_array(cell_node, "density"); + density_mult_.shrink_to_fit(); + + // Make sure this is a material-filled cell. + if (material_.size() == 0) { + fatal_error(fmt::format( + "Cell {} was specified with a density but no material. Density" + "specification is only valid for cells filled with a material.", + id_)); + } + + // Make sure this is a non-void material. + for (auto mat_id : material_) { + if (mat_id == MATERIAL_VOID) { + fatal_error(fmt::format( + "Cell {} was specified with a density, but contains a void " + "material. Density specification is only valid for cells " + "filled with a non-void material.", + id_)); + } + } + + // Make sure all densities are non-negative and greater than zero. + for (auto rho : density_mult_) { + if (rho <= 0) { + fatal_error(fmt::format( + "Cell {} was specified with a density less than or equal to zero", + id_)); + } + } + } + // Read the region specification. std::string region_spec; if (check_for_node(cell_node, "region")) { @@ -1129,6 +1257,24 @@ extern "C" int openmc_cell_set_temperature( return 0; } +extern "C" int openmc_cell_set_density( + int32_t index, double density, const int32_t* instance, bool set_contained) +{ + if (index < 0 || index >= model::cells.size()) { + strcpy(openmc_err_msg, "Index in cells array is out of bounds."); + return OPENMC_E_OUT_OF_BOUNDS; + } + + int32_t instance_index = instance ? *instance : -1; + try { + model::cells[index]->set_density(density, instance_index, set_contained); + } catch (const std::exception& e) { + set_errmsg(e.what()); + return OPENMC_E_UNASSIGNED; + } + return 0; +} + extern "C" int openmc_cell_get_temperature( int32_t index, const int32_t* instance, double* T) { @@ -1147,6 +1293,36 @@ extern "C" int openmc_cell_get_temperature( return 0; } +extern "C" int openmc_cell_get_density( + int32_t index, const int32_t* instance, double* density) +{ + if (index < 0 || index >= model::cells.size()) { + strcpy(openmc_err_msg, "Index in cells array is out of bounds."); + return OPENMC_E_OUT_OF_BOUNDS; + } + + int32_t instance_index = instance ? *instance : -1; + try { + if (model::cells[index]->type_ != Fill::MATERIAL) { + fatal_error( + fmt::format("Cell {}, instance {} is not filled with a material.", + model::cells[index]->id_, instance_index)); + } + + int32_t mat_index = model::cells[index]->material(instance_index); + if (mat_index == MATERIAL_VOID) { + *density = 0.0; + } else { + *density = model::cells[index]->density_mult(instance_index) * + model::materials[mat_index]->density_gpcc(); + } + } catch (const std::exception& e) { + set_errmsg(e.what()); + return OPENMC_E_UNASSIGNED; + } + return 0; +} + //! Get the bounding box of a cell extern "C" int openmc_cell_bounding_box( const int32_t index, double* llc, double* urc) diff --git a/src/geometry.cpp b/src/geometry.cpp index df485089163..5ff15097b19 100644 --- a/src/geometry.cpp +++ b/src/geometry.cpp @@ -172,11 +172,13 @@ bool find_cell_inner( p.cell_instance() = cell_instance_at_level(p, p.n_coord() - 1); } - // Set the material and temperature. + // Set the material, temperature and density multiplier. p.material_last() = p.material(); p.material() = c.material(p.cell_instance()); p.sqrtkT_last() = p.sqrtkT(); p.sqrtkT() = c.sqrtkT(p.cell_instance()); + p.density_mult_last() = p.density_mult(); + p.density_mult() = c.density_mult(p.cell_instance()); return true; diff --git a/src/geometry_aux.cpp b/src/geometry_aux.cpp index 51732cf8b90..8a145fb1f1e 100644 --- a/src/geometry_aux.cpp +++ b/src/geometry_aux.cpp @@ -195,6 +195,24 @@ void assign_temperatures() //============================================================================== +void finalize_cell_densities() +{ + for (auto& c : model::cells) { + // Convert to density multipliers. + if (!c->density_mult_.empty()) { + for (int32_t instance = 0; instance < c->density_mult_.size(); + ++instance) { + c->density_mult_[instance] /= + model::materials[c->material(instance)]->density_gpcc(); + } + } else { + c->density_mult_ = {1.0}; + } + } +} + +//============================================================================== + void get_temperatures( vector>& nuc_temps, vector>& thermal_temps) { @@ -362,6 +380,17 @@ void prepare_distribcell(const std::vector* user_distribcells) c.id_, c.sqrtkT_.size(), c.n_instances())); } } + + if (c.density_mult_.size() > 1) { + if (c.density_mult_.size() != c.n_instances()) { + fatal_error(fmt::format("Cell {} was specified with {} density " + "multipliers but has {} distributed " + "instances. The number of density multipliers " + "must equal one or the number " + "of instances.", + c.id_, c.density_mult_.size(), c.n_instances())); + } + } } // Search through universes for material cells and assign each one a diff --git a/src/initialize.cpp b/src/initialize.cpp index 2da78137d10..36f32611690 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -401,6 +401,10 @@ bool read_model_xml() // Finalize cross sections having assigned temperatures finalize_cross_sections(); + // Compute cell density multipliers now that material densities + // have been finalized (from geometry_aux.h) + finalize_cell_densities(); + if (check_for_node(root, "tallies")) read_tallies_xml(root.child("tallies")); @@ -441,6 +445,11 @@ void read_separate_xml_files() // Finalize cross sections having assigned temperatures finalize_cross_sections(); + + // Compute cell density multipliers now that material densities + // have been finalized (from geometry_aux.h) + finalize_cell_densities(); + read_tallies_xml(); // Initialize distribcell_filters diff --git a/src/material.cpp b/src/material.cpp index 32384ddf12b..54caa38409a 100644 --- a/src/material.cpp +++ b/src/material.cpp @@ -890,7 +890,7 @@ void Material::calculate_neutron_xs(Particle& p) const // ADD TO MACROSCOPIC CROSS SECTION // Copy atom density of nuclide in material - double atom_density = atom_density_(i); + double atom_density = this->atom_density(i, p.density_mult()); // Add contributions to cross sections p.macro_xs().total += atom_density * micro.total; @@ -925,7 +925,7 @@ void Material::calculate_photon_xs(Particle& p) const // ADD TO MACROSCOPIC CROSS SECTION // Copy atom density of nuclide in material - double atom_density = atom_density_(i); + double atom_density = this->atom_density(i, p.density_mult()); // Add contributions to material macroscopic cross sections p.macro_xs().total += atom_density * micro.total; diff --git a/src/mgxs.cpp b/src/mgxs.cpp index a88d2c196b6..a2c479f2156 100644 --- a/src/mgxs.cpp +++ b/src/mgxs.cpp @@ -617,10 +617,12 @@ void Mgxs::calculate_xs(Particle& p) } int temperature = p.mg_xs_cache().t; int angle = p.mg_xs_cache().a; - p.macro_xs().total = xs[temperature].total(angle, p.g()); - p.macro_xs().absorption = xs[temperature].absorption(angle, p.g()); + p.macro_xs().total = xs[temperature].total(angle, p.g()) * p.density_mult(); + p.macro_xs().absorption = + xs[temperature].absorption(angle, p.g()) * p.density_mult(); p.macro_xs().nu_fission = - fissionable ? xs[temperature].nu_fission(angle, p.g()) : 0.; + fissionable ? xs[temperature].nu_fission(angle, p.g()) * p.density_mult() + : 0.; } //============================================================================== diff --git a/src/particle.cpp b/src/particle.cpp index f25ada02c8f..f5ad45d80fe 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -200,7 +200,8 @@ void Particle::event_calculate_xs() // Calculate microscopic and macroscopic cross sections if (material() != MATERIAL_VOID) { if (settings::run_CE) { - if (material() != material_last() || sqrtkT() != sqrtkT_last()) { + if (material() != material_last() || sqrtkT() != sqrtkT_last() || + density_mult() != density_mult_last()) { // If the material is the same as the last material and the // temperature hasn't changed, we don't need to lookup cross // sections again. @@ -564,9 +565,10 @@ void Particle::cross_surface(const Surface& surf) int32_t i_cell = next_cell(surface_index(), cell_last(n_coord() - 1), lowest_coord().universe()) - 1; - // save material and temp + // save material, temperature, and density multiplier material_last() = material(); sqrtkT_last() = sqrtkT(); + density_mult_last() = density_mult(); // set new cell value lowest_coord().cell() = i_cell; auto& cell = model::cells[i_cell]; @@ -577,6 +579,7 @@ void Particle::cross_surface(const Surface& surf) material() = cell->material(cell_instance()); sqrtkT() = cell->sqrtkT(cell_instance()); + density_mult() = cell->density_mult(cell_instance()); return; } #endif diff --git a/src/physics.cpp b/src/physics.cpp index 3c06e543de1..8ad1b99d751 100644 --- a/src/physics.cpp +++ b/src/physics.cpp @@ -496,7 +496,7 @@ int sample_nuclide(Particle& p) for (int i = 0; i < n; ++i) { // Get atom density int i_nuclide = mat->nuclide_[i]; - double atom_density = mat->atom_density_[i]; + double atom_density = mat->atom_density(i, p.density_mult()); // Increment probability to compare to cutoff prob += atom_density * p.neutron_xs(i_nuclide).total; @@ -521,7 +521,7 @@ int sample_element(Particle& p) for (int i = 0; i < mat->element_.size(); ++i) { // Find atom density int i_element = mat->element_[i]; - double atom_density = mat->atom_density_[i]; + double atom_density = mat->atom_density(i, p.density_mult()); // Determine microscopic cross section double sigma = atom_density * p.photon_xs(i_element).total; diff --git a/src/tallies/tally_scoring.cpp b/src/tallies/tally_scoring.cpp index 04b047b6497..f078a5c6e54 100644 --- a/src/tallies/tally_scoring.cpp +++ b/src/tallies/tally_scoring.cpp @@ -233,7 +233,7 @@ double score_fission_q(const Particle& p, int score_bin, const Tally& tally, double score {0.0}; for (auto i = 0; i < material.nuclide_.size(); ++i) { auto j_nuclide = material.nuclide_[i]; - auto atom_density = material.atom_density_(i); + auto atom_density = material.atom_density(i, p.density_mult()); const Nuclide& nuc {*data::nuclides[j_nuclide]}; score += get_nuc_fission_q(nuc, p, score_bin) * atom_density * p.neutron_xs(j_nuclide).fission; @@ -696,7 +696,7 @@ void score_general_ce_nonanalog(Particle& p, int i_tally, int start_index, const Material& material {*model::materials[p.material()]}; for (auto i = 0; i < material.nuclide_.size(); ++i) { auto j_nuclide = material.nuclide_[i]; - auto atom_density = material.atom_density_(i); + auto atom_density = material.atom_density(i, p.density_mult()); score += p.neutron_xs(j_nuclide).fission * data::nuclides[j_nuclide]->nu( E, ReactionProduct::EmissionMode::prompt) * @@ -743,7 +743,7 @@ void score_general_ce_nonanalog(Particle& p, int i_tally, int start_index, const Material& material {*model::materials[p.material()]}; for (auto i = 0; i < material.nuclide_.size(); ++i) { auto j_nuclide = material.nuclide_[i]; - auto atom_density = material.atom_density_(i); + auto atom_density = material.atom_density(i, p.density_mult()); // Tally each delayed group bin individually for (auto d_bin = 0; d_bin < filt.n_bins(); ++d_bin) { auto d = filt.groups()[d_bin]; @@ -763,7 +763,7 @@ void score_general_ce_nonanalog(Particle& p, int i_tally, int start_index, const Material& material {*model::materials[p.material()]}; for (auto i = 0; i < material.nuclide_.size(); ++i) { auto j_nuclide = material.nuclide_[i]; - auto atom_density = material.atom_density_(i); + auto atom_density = material.atom_density(i, p.density_mult()); score += p.neutron_xs(j_nuclide).fission * data::nuclides[j_nuclide]->nu( E, ReactionProduct::EmissionMode::delayed) * @@ -824,7 +824,7 @@ void score_general_ce_nonanalog(Particle& p, int i_tally, int start_index, const Material& material {*model::materials[p.material()]}; for (auto i = 0; i < material.nuclide_.size(); ++i) { auto j_nuclide = material.nuclide_[i]; - auto atom_density = material.atom_density_(i); + auto atom_density = material.atom_density(i, p.density_mult()); const auto& nuc {*data::nuclides[j_nuclide]}; if (nuc.fissionable_) { const auto& rxn {*nuc.fission_rx_[0]}; @@ -849,7 +849,7 @@ void score_general_ce_nonanalog(Particle& p, int i_tally, int start_index, const Material& material {*model::materials[p.material()]}; for (auto i = 0; i < material.nuclide_.size(); ++i) { auto j_nuclide = material.nuclide_[i]; - auto atom_density = material.atom_density_(i); + auto atom_density = material.atom_density(i, p.density_mult()); const auto& nuc {*data::nuclides[j_nuclide]}; if (nuc.fissionable_) { const auto& rxn {*nuc.fission_rx_[0]}; @@ -893,7 +893,7 @@ void score_general_ce_nonanalog(Particle& p, int i_tally, int start_index, const Material& material {*model::materials[p.material()]}; for (auto i = 0; i < material.nuclide_.size(); ++i) { auto j_nuclide = material.nuclide_[i]; - auto atom_density = material.atom_density_(i); + auto atom_density = material.atom_density(i, p.density_mult()); const auto& nuc {*data::nuclides[j_nuclide]}; if (nuc.fissionable_) { const auto& rxn {*nuc.fission_rx_[0]}; @@ -924,7 +924,7 @@ void score_general_ce_nonanalog(Particle& p, int i_tally, int start_index, const Material& material {*model::materials[p.material()]}; for (auto i = 0; i < material.nuclide_.size(); ++i) { auto j_nuclide = material.nuclide_[i]; - auto atom_density = material.atom_density_(i); + auto atom_density = material.atom_density(i, p.density_mult()); if (p.neutron_xs(j_nuclide).elastic == CACHE_INVALID) data::nuclides[j_nuclide]->calculate_elastic_xs(p); score += p.neutron_xs(j_nuclide).elastic * atom_density * flux; @@ -1025,7 +1025,7 @@ void score_general_ce_nonanalog(Particle& p, int i_tally, int start_index, const Material& material {*model::materials[p.material()]}; for (auto i = 0; i < material.nuclide_.size(); ++i) { auto j_nuclide = material.nuclide_[i]; - auto atom_density = material.atom_density_(i); + auto atom_density = material.atom_density(i, p.density_mult()); score += p.neutron_xs(j_nuclide).reaction[m] * atom_density * flux; } } @@ -1079,7 +1079,7 @@ void score_general_ce_nonanalog(Particle& p, int i_tally, int start_index, const Material& material {*model::materials[p.material()]}; for (auto i = 0; i < material.nuclide_.size(); ++i) { auto j_nuclide = material.nuclide_[i]; - auto atom_density = material.atom_density_(i); + auto atom_density = material.atom_density(i, p.density_mult()); score += get_nuclide_xs(p, j_nuclide, score_bin) * atom_density * flux; } @@ -2383,7 +2383,8 @@ void score_analog_tally_mg(Particle& p) model::materials[p.material()]->mat_nuclide_index_[i_nuclide]; if (j == C_NONE) continue; - atom_density = model::materials[p.material()]->atom_density_(j); + atom_density = + model::materials[p.material()]->atom_density(j, p.density_mult()); } score_general_mg(p, i_tally, i * tally.scores_.size(), filter_index, @@ -2449,8 +2450,9 @@ void score_tracklength_tally_general( atom_density = 1.0; } } else { - atom_density = - tally.multiply_density() ? mat->atom_density_(j) : 1.0; + atom_density = tally.multiply_density() + ? mat->atom_density(j, p.density_mult()) + : 1.0; } } } @@ -2579,8 +2581,9 @@ void score_collision_tally(Particle& p) atom_density = 1.0; } } else { - atom_density = - tally.multiply_density() ? mat->atom_density_(j) : 1.0; + atom_density = tally.multiply_density() + ? mat->atom_density(j, p.density_mult()) + : 1.0; } } diff --git a/tests/regression_tests/cpp_driver/driver.cpp b/tests/regression_tests/cpp_driver/driver.cpp index 48ed7f3171b..a99c97b64e4 100644 --- a/tests/regression_tests/cpp_driver/driver.cpp +++ b/tests/regression_tests/cpp_driver/driver.cpp @@ -15,14 +15,16 @@ using namespace openmc; -int main(int argc, char** argv) { +int main(int argc, char** argv) +{ #ifdef OPENMC_MPI MPI_Comm world {MPI_COMM_WORLD}; int err = openmc_init(argc, argv, &world); #else int err = openmc_init(argc, argv, nullptr); #endif - if (err) fatal_error(openmc_err_msg); + if (err) + fatal_error(openmc_err_msg); // create a new cell filter auto cell_filter = Filter::create(); @@ -30,7 +32,7 @@ int main(int argc, char** argv) { // add all cells to the cell filter std::vector cell_indices; for (auto& entry : openmc::model::cell_map) { - cell_indices.push_back(entry.second); + cell_indices.push_back(entry.second); } // enable distribcells offsets for all cells prepare_distribcell(&cell_indices); @@ -39,7 +41,6 @@ int main(int argc, char** argv) { std::sort(cell_indices.begin(), cell_indices.end()); cell_filter->set_cells(cell_indices); - // create a new tally auto tally = Tally::create(); std::vector filters = {cell_filter}; @@ -60,14 +61,19 @@ int main(int argc, char** argv) { } } - // set a higher temperature for only one of the lattice cells (ID is 4 in the model) + // set a higher temperature for only one of the lattice cells (ID is 4 in the + // model) model::cells[model::cell_map[4]]->set_temperature(400.0, 3, true); + // set the density of another lattice cell to 2 + model::cells[model::cell_map[4]]->set_density(2.0, 2, true); + // the summary file will be used to check that // temperatures were set correctly so clear // error output can be provided #ifdef OPENMC_MPI - if (openmc::mpi::master) openmc::write_summary(); + if (openmc::mpi::master) + openmc::write_summary(); #else openmc::write_summary(); #endif diff --git a/tests/regression_tests/cpp_driver/results_true.dat b/tests/regression_tests/cpp_driver/results_true.dat index c2b0b8d6f36..26f07842cd4 100644 --- a/tests/regression_tests/cpp_driver/results_true.dat +++ b/tests/regression_tests/cpp_driver/results_true.dat @@ -1,13 +1,13 @@ k-combined: -1.933305E+00 1.300360E-02 +1.953962E+00 1.828426E-02 tally 1: -9.552846E+01 -1.019358E+03 -2.887973E+01 -9.308509E+01 -9.732441E+01 -1.059022E+03 -2.217326E+02 -5.486892E+03 -2.217326E+02 -5.486892E+03 +9.607953E+01 +1.031898E+03 +2.853683E+01 +9.085469E+01 +9.745011E+01 +1.058928E+03 +2.220665E+02 +5.496813E+03 +2.220665E+02 +5.496813E+03 diff --git a/tests/regression_tests/dagmc/external/main.cpp b/tests/regression_tests/dagmc/external/main.cpp index 3765cf79ae2..e78ab03fa2f 100644 --- a/tests/regression_tests/dagmc/external/main.cpp +++ b/tests/regression_tests/dagmc/external/main.cpp @@ -100,6 +100,9 @@ int main(int argc, char* argv[]) } } + // Finalize cell densities + openmc::finalize_cell_densities(); + // Run OpenMC openmc_err = openmc_run(); if (openmc_err) diff --git a/tests/regression_tests/lattice_distribrho/__init__.py b/tests/regression_tests/lattice_distribrho/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/lattice_distribrho/inputs_true.dat b/tests/regression_tests/lattice_distribrho/inputs_true.dat new file mode 100644 index 00000000000..5031bea6e28 --- /dev/null +++ b/tests/regression_tests/lattice_distribrho/inputs_true.dat @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + 1.0 1.0 + 2 2 + -1.0 -1.0 + +1 1 +1 1 + + + + + + + + + eigenvalue + 1000 + 10 + 5 + + diff --git a/tests/regression_tests/lattice_distribrho/results_true.dat b/tests/regression_tests/lattice_distribrho/results_true.dat new file mode 100644 index 00000000000..d7094f4e373 --- /dev/null +++ b/tests/regression_tests/lattice_distribrho/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +1.904471E+00 5.255549E-03 diff --git a/tests/regression_tests/lattice_distribrho/test.py b/tests/regression_tests/lattice_distribrho/test.py new file mode 100644 index 00000000000..ec94fe96b81 --- /dev/null +++ b/tests/regression_tests/lattice_distribrho/test.py @@ -0,0 +1,51 @@ +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + + +@pytest.fixture +def model(): + model = openmc.Model() + + uo2 = openmc.Material(name='UO2') + uo2.set_density('g/cm3', 10.0) + uo2.add_nuclide('U235', 1.0) + uo2.add_nuclide('O16', 2.0) + water = openmc.Material(name='light water') + water.add_nuclide('H1', 2.0) + water.add_nuclide('O16', 1.0) + water.set_density('g/cm3', 1.0) + water.add_s_alpha_beta('c_H_in_H2O') + model.materials.extend([uo2, water]) + + cyl = openmc.ZCylinder(r=0.4) + pin = openmc.model.pin([cyl], [uo2, water]) + d = 1.0 + + lattice = openmc.RectLattice() + lattice.lower_left = (-d, -d) + lattice.pitch = (d, d) + lattice.universes = [[pin, pin], + [pin, pin]] + box = openmc.model.RectangularPrism( + 2.0 * d, 2.0 * d, + origin=(0.0, 0.0), + boundary_type='reflective' + ) + + pin.cells[1].density = [10.0, 20.0, 10.0, 20.0] + + model.geometry = openmc.Geometry([openmc.Cell(fill=lattice, region=-box)]) + model.geometry.merge_surfaces = True + + model.settings.batches = 10 + model.settings.inactive = 5 + model.settings.particles = 1000 + + return model + + +def test_lattice_checkerboard(model): + harness = PyAPITestHarness('statepoint.10.h5', model) + harness.main() diff --git a/tests/regression_tests/multipole/results_true.dat b/tests/regression_tests/multipole/results_true.dat index 8e82655ce50..8eb75b894bc 100644 --- a/tests/regression_tests/multipole/results_true.dat +++ b/tests/regression_tests/multipole/results_true.dat @@ -38,5 +38,6 @@ Cell Region = -1 Rotation = None Temperature = [500. 700. 0. 800.] + Density = None Translation = None Volume = None diff --git a/tests/unit_tests/test_cell.py b/tests/unit_tests/test_cell.py index 95c8249bb33..60b20581868 100644 --- a/tests/unit_tests/test_cell.py +++ b/tests/unit_tests/test_cell.py @@ -126,6 +126,29 @@ def test_temperature(cell_with_lattice): c.temperature = (300., 600., 900.) +def test_densities(cell_with_lattice): + # Make sure density propagates through universes + m = openmc.Material() + s = openmc.XPlane() + c1 = openmc.Cell(fill=m, region=+s) + c2 = openmc.Cell(fill=m, region=-s) + u1 = openmc.Universe(cells=[c1, c2]) + c = openmc.Cell(fill=u1) + + c.density = 1. + assert c1.density == 1. + assert c2.density == 1. + with pytest.raises(ValueError): + c.density = -1. + c.density = None + assert c1.density == None + assert c2.density == None + + # distributed density + cells, _, _, _ = cell_with_lattice + c = cells[0] + c.density = (1., 2., 3.) + def test_rotation(): u = openmc.Universe() c = openmc.Cell(fill=u) diff --git a/tests/unit_tests/test_lib.py b/tests/unit_tests/test_lib.py index 8ab35335fc0..8080ce89483 100644 --- a/tests/unit_tests/test_lib.py +++ b/tests/unit_tests/test_lib.py @@ -159,6 +159,29 @@ def test_properties_temperature(lib_init): assert cell.get_temperature() == pytest.approx(200.0) +def test_cell_density(lib_init): + cell = openmc.lib.cells[1] + cell.set_density(1.5, 0) + assert cell.get_density(0) == pytest.approx(1.5) + cell.set_density(2.0) + assert cell.get_density() == pytest.approx(2.0) + + +def test_properties_cell_density(lib_init): + # Cell density should be 2.0 from above test + cell = openmc.lib.cells[1] + assert cell.get_density() == pytest.approx(2.0) + + # Export properties and change density + openmc.lib.export_properties('properties.h5') + cell.set_density(3.0) + assert cell.get_density() == pytest.approx(3.0) + + # Import properties and check that density is restored + openmc.lib.import_properties('properties.h5') + assert cell.get_density() == pytest.approx(2.0) + + def test_new_cell(lib_init): with pytest.raises(exc.AllocationError): openmc.lib.Cell(1) diff --git a/tests/unit_tests/test_model.py b/tests/unit_tests/test_model.py index 6e4dec00fff..12f0df6c9e5 100644 --- a/tests/unit_tests/test_model.py +++ b/tests/unit_tests/test_model.py @@ -251,10 +251,11 @@ def test_import_properties(run_in_tmpdir, mpi_intracomm): model = openmc.examples.pwr_pin_cell() model.init_lib(output=False, intracomm=mpi_intracomm) - # Change fuel temperature and density and export properties + # Change cell fuel temperature, density, material density and export properties cell = openmc.lib.cells[1] cell.set_temperature(600.0) cell.fill.set_density(5.0, 'g/cm3') + cell.set_density(10.0) openmc.lib.export_properties(output=False) # Import properties to existing model @@ -264,9 +265,11 @@ def test_import_properties(run_in_tmpdir, mpi_intracomm): # First python cell = model.geometry.get_all_cells()[1] assert cell.temperature == [600.0] + assert cell.density == [pytest.approx(10.0, 1e-5)] assert cell.fill.get_mass_density() == pytest.approx(5.0) # Now C assert openmc.lib.cells[1].get_temperature() == 600. + assert openmc.lib.cells[1].get_density() == pytest.approx(10.0, 1e-5) assert openmc.lib.materials[1].get_density('g/cm3') == pytest.approx(5.0) # Clear the C API @@ -283,6 +286,7 @@ def test_import_properties(run_in_tmpdir, mpi_intracomm): ) cell = model_with_properties.geometry.get_all_cells()[1] assert cell.temperature == [600.0] + assert cell.density == [pytest.approx(10.0, 1e-5)] assert cell.fill.get_mass_density() == pytest.approx(5.0) From 007ac8148bcda4911edb0191df762fa7ef3c5b8e Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 18 Sep 2025 23:27:41 -0500 Subject: [PATCH 110/159] Allow newer Sphinx version and fix docbuild warnings (#3571) --- docs/source/conf.py | 2 -- openmc/deplete/abc.py | 9 +++++---- openmc/deplete/integrators.py | 1 - openmc/mgxs/mgxs.py | 5 +++-- openmc/plots.py | 6 ++++-- pyproject.toml | 4 ++-- 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 8aeff5cdc7a..826c20022ae 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -121,9 +121,7 @@ # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages -import sphinx_rtd_theme html_theme = 'sphinx_rtd_theme' -html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] html_baseurl = "https://docs.openmc.org/en/stable/" html_logo = '_images/openmc_logo.png' diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index e6d4c151273..c5a6219c7b8 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -12,6 +12,7 @@ from inspect import signature from numbers import Real, Integral from pathlib import Path +from textwrap import dedent import time from typing import Optional, Union, Sequence from warnings import warn @@ -526,7 +527,7 @@ class Integrator(ABC): r"""Abstract class for solving the time-integration for depletion """ - _params = r""" + _params = dedent(r""" Parameters ---------- operator : openmc.deplete.abc.TransportOperator @@ -617,7 +618,7 @@ class Integrator(ABC): .. versionadded:: 0.15.3 - """ + """) def __init__( self, @@ -1020,7 +1021,7 @@ class SIIntegrator(Integrator): the number of particles used in initial transport calculation """ - _params = r""" + _params = dedent(r""" Parameters ---------- operator : openmc.deplete.abc.TransportOperator @@ -1108,7 +1109,7 @@ class SIIntegrator(Integrator): .. versionadded:: 0.12 - """ + """) def __init__( self, diff --git a/openmc/deplete/integrators.py b/openmc/deplete/integrators.py index 000cb2c41c0..7c543a6cb86 100644 --- a/openmc/deplete/integrators.py +++ b/openmc/deplete/integrators.py @@ -22,7 +22,6 @@ class PredictorIntegrator(Integrator): .. math:: \mathbf{n}_{i+1} = \exp\left(h\mathbf{A}(\mathbf{n}_i) \right) \mathbf{n}_i - """ _num_stages = 1 diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index 1bc7f938786..b8f2b8d3fb6 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -2,6 +2,7 @@ from numbers import Integral import os import warnings +from textwrap import dedent import h5py import numpy as np @@ -164,7 +165,7 @@ class MGXS: """ - _params = """ + _params = dedent(""" Parameters ---------- domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh @@ -251,7 +252,7 @@ class MGXS: .. versionadded:: 0.13.1 - """ + """) # Store whether or not the number density should be removed for microscopic # values of this data diff --git a/openmc/plots.py b/openmc/plots.py index 072a9a319e3..e9130f0bea5 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -1,6 +1,7 @@ from collections.abc import Iterable, Mapping from numbers import Integral, Real from pathlib import Path +from textwrap import dedent import h5py import lxml.etree as ET @@ -167,7 +168,8 @@ 'yellowgreen': (154, 205, 50) } -_PLOT_PARAMS = """ +_PLOT_PARAMS = dedent("""\ + Parameters ---------- origin : iterable of float @@ -249,7 +251,7 @@ ------- matplotlib.axes.Axes Axes containing resulting image -""" +""") # Decorator for consistently adding plot parameters to docstrings (Model.plot, diff --git a/pyproject.toml b/pyproject.toml index 6e8ed798e78..7705de8dae4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,12 +41,12 @@ dependencies = [ [project.optional-dependencies] depletion-mpi = ["mpi4py"] docs = [ - "sphinx==5.0.2", + "sphinx", "sphinxcontrib-katex", "sphinx-numfig", "jupyter", "sphinxcontrib-svg2pdfconverter", - "sphinx-rtd-theme==1.0.0" + "sphinx-rtd-theme" ] test = ["packaging", "pytest", "pytest-cov", "colorama", "openpyxl"] ci = ["cpp-coveralls", "coveralls"] From ecb0a3361f600594984b72ce37e33cd7650497b6 Mon Sep 17 00:00:00 2001 From: Ilham Variansyah Date: Fri, 19 Sep 2025 15:10:08 +0700 Subject: [PATCH 111/159] Combing for fission site sampling, and delayed neutron emission time (#2992) Co-authored-by: Gavin Ridley Co-authored-by: Paul Romano --- docs/source/methods/neutron_physics.rst | 19 +- examples/pincell_pulsed/run_pulse.py | 101 + include/openmc/settings.h | 1 + openmc/settings.py | 27 + src/eigenvalue.cpp | 106 +- src/finalize.cpp | 1 + src/physics.cpp | 20 +- src/physics_mg.cpp | 20 +- src/settings.cpp | 6 + src/tallies/tally_scoring.cpp | 3 +- .../adj_cell_rotation/results_true.dat | 2 +- .../albedo_box/results_true.dat | 2 +- .../asymmetric_lattice/results_true.dat | 2 +- .../cmfd_feed/results_true.dat | 612 ++-- .../cmfd_feed_2g/results_true.dat | 538 ++-- .../results_true.dat | 562 ++-- .../cmfd_feed_ng/results_true.dat | 680 ++--- .../cmfd_feed_rectlin/results_true.dat | 778 +++--- .../cmfd_feed_ref_d/results_true.dat | 562 ++-- .../cmfd_feed_rolling_window/results_true.dat | 562 ++-- .../cmfd_nofeed/results_true.dat | 610 ++-- .../cmfd_restart/results_true.dat | 612 ++-- .../complex_cell/results_true.dat | 16 +- .../confidence_intervals/results_true.dat | 6 +- .../cpp_driver/results_true.dat | 22 +- .../dagmc/external/results_true.dat | 6 +- .../dagmc/legacy/results_true.dat | 6 +- .../dagmc/refl/results_true.dat | 6 +- .../dagmc/universes/inputs_true.dat | 8 + .../dagmc/universes/results_true.dat | 18 +- .../regression_tests/dagmc/universes/test.py | 160 +- .../regression_tests/density/results_true.dat | 2 +- .../test_reference_coupled_days.h5 | Bin 36048 -> 36048 bytes .../test_reference_coupled_hours.h5 | Bin 36048 -> 36048 bytes .../test_reference_coupled_minutes.h5 | Bin 36048 -> 36048 bytes .../test_reference_coupled_months.h5 | Bin 36048 -> 36048 bytes .../test_reference_fission_q.h5 | Bin 36048 -> 36048 bytes .../test_reference_source_rate.h5 | Bin 36048 -> 36048 bytes .../ref_depletion_with_ext_source.h5 | Bin 37328 -> 37328 bytes .../ref_depletion_with_feed.h5 | Bin 37328 -> 37328 bytes .../ref_depletion_with_removal.h5 | Bin 37328 -> 37328 bytes .../ref_depletion_with_transfer.h5 | Bin 37328 -> 37328 bytes .../ref_no_depletion_only_feed.h5 | Bin 37328 -> 37328 bytes .../ref_no_depletion_only_removal.h5 | Bin 37328 -> 37328 bytes .../ref_no_depletion_with_ext_source.h5 | Bin 37328 -> 37328 bytes .../ref_no_depletion_with_transfer.h5 | Bin 37328 -> 37328 bytes .../deplete_with_transfer_rates/test.py | 3 +- .../last_step_reference_materials.xml | 1280 ++++----- .../deplete_with_transport/test_reference.h5 | Bin 163736 -> 163736 bytes .../diff_tally/results_true.dat | 220 +- .../distribmat/results_true.dat | 2 +- .../eigenvalue_genperbatch/results_true.dat | 6 +- .../eigenvalue_no_inactive/results_true.dat | 2 +- .../energy_grid/results_true.dat | 2 +- .../energy_laws/results_true.dat | 2 +- .../regression_tests/entropy/results_true.dat | 20 +- .../filter_cellfrom/results_true.dat | 62 +- .../filter_cellinstance/results_true.dat | 162 +- .../case-3/results_true.dat | 2 +- .../case-4/results_true.dat | 6 +- .../filter_energyfun/results_true.dat | 10 +- .../filter_mesh/results_true.dat | 2 +- .../filter_musurface/results_true.dat | 10 +- .../filter_translations/results_true.dat | 674 ++--- tests/regression_tests/ifp/results_true.dat | 14 +- .../infinite_cell/results_true.dat | 2 +- .../iso_in_lab/results_true.dat | 2 +- .../regression_tests/lattice/results_true.dat | 2 +- .../lattice_distribmat/False/results_true.dat | 2 +- .../lattice_distribmat/True/results_true.dat | 2 +- .../lattice_distribrho/results_true.dat | 2 +- .../lattice_hex/results_true.dat | 2 +- .../lattice_hex_coincident/results_true.dat | 2 +- .../lattice_hex_x/results_true.dat | 2 +- .../lattice_multiple/results_true.dat | 2 +- .../lattice_rotated/results_true.dat | 2 +- .../mg_basic/results_true.dat | 2 +- .../mg_basic_delayed/results_true.dat | 2 +- .../mg_convert/results_true.dat | 24 +- tests/regression_tests/mg_convert/test.py | 6 +- .../mg_legendre/results_true.dat | 2 +- .../mg_max_order/results_true.dat | 2 +- .../mg_survival_biasing/results_true.dat | 2 +- .../mg_tallies/results_true.dat | 2482 ++++++++--------- .../mg_temperature/results_true.dat | 20 +- .../mg_temperature_multi/results_true.dat | 10 +- .../mgxs_library_ce_to_mg/results_true.dat | 2 +- .../results_true.dat | 2 +- .../mgxs_library_condense/results_true.dat | 572 ++-- .../mgxs_library_correction/results_true.dat | 80 +- .../mgxs_library_distribcell/results_true.dat | 128 +- .../mgxs_library_hdf5/results_true.dat | 278 +- .../mgxs_library_histogram/results_true.dat | 660 ++--- .../mgxs_library_mesh/results_true.dat | 548 ++-- .../mgxs_library_no_nuclides/results_true.dat | 782 +++--- .../mgxs_library_nuclides/results_true.dat | 2 +- .../results_true.dat | 2 +- .../test_reference_materials_direct.csv | 8 +- .../microxs/test_reference_materials_flux.csv | 8 +- .../microxs/test_reference_mesh_direct.csv | 8 +- .../microxs/test_reference_mesh_flux.csv | 8 +- .../multipole/results_true.dat | 54 +- .../regression_tests/output/results_true.dat | 2 +- .../particle_restart_eigval/results_true.dat | 10 +- .../particle_restart_eigval/settings.xml | 2 +- .../particle_restart_eigval/test.py | 2 +- .../periodic/results_true.dat | 2 +- .../periodic_6fold/results_true.dat | 2 +- .../periodic_hex/results_true.dat | 2 +- .../results_true.dat | 58 +- .../ptables_off/results_true.dat | 2 +- .../quadric_surfaces/results_true.dat | 2 +- .../results_true.dat | 2 +- .../infinite_medium/results_true.dat | 2 +- .../material_wise/results_true.dat | 2 +- .../stochastic_slab/results_true.dat | 2 +- .../results_true.dat | 2 +- .../linear/results_true.dat | 4 +- .../flat/results_true.dat | 6 +- .../linear_xy/results_true.dat | 14 +- .../results_true.dat | 10 +- .../random_ray_k_eff/results_true.dat | 14 +- .../random_ray_k_eff_mesh/results_true.dat | 18 +- .../random_ray_linear/linear/results_true.dat | 6 +- .../hybrid/results_true.dat | 4 +- .../reflective_plane/results_true.dat | 2 +- .../resonance_scattering/results_true.dat | 2 +- .../rotation/results_true.dat | 2 +- .../salphabeta/results_true.dat | 2 +- .../score_current/results_true.dat | 1842 ++++++------ tests/regression_tests/seed/results_true.dat | 2 +- .../regression_tests/source/results_true.dat | 2 +- .../source_file/results_true.dat | 2 +- .../source_mcpl_file/results_true.dat | 2 +- .../sourcepoint_batch/results_true.dat | 4 +- .../sourcepoint_latest/results_true.dat | 2 +- .../sourcepoint_restart/results_true.dat | 856 +++--- .../statepoint_batch/__init__.py | 0 .../statepoint_batch/geometry.xml | 8 - .../statepoint_batch/materials.xml | 9 - .../statepoint_batch/results_true.dat | 2 - .../statepoint_batch/settings.xml | 17 - .../regression_tests/statepoint_batch/test.py | 18 - .../statepoint_restart/results_true.dat | 1166 ++++---- .../statepoint_sourcesep/results_true.dat | 2 +- .../regression_tests/stride/results_true.dat | 2 +- .../surface_source/surface_source_true.h5 | Bin 106144 -> 106144 bytes .../surface_source/surface_source_true.mcpl | Bin 36071 -> 36120 bytes .../case-01/results_true.dat | 2 +- .../case-01/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-02/results_true.dat | 2 +- .../case-02/surface_source_true.h5 | Bin 12752 -> 13896 bytes .../case-03/results_true.dat | 2 +- .../case-03/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-04/results_true.dat | 2 +- .../case-04/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-05/results_true.dat | 2 +- .../case-05/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-06/results_true.dat | 2 +- .../case-06/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-07/results_true.dat | 2 +- .../case-07/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-08/results_true.dat | 2 +- .../case-08/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-09/results_true.dat | 2 +- .../case-09/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-10/results_true.dat | 2 +- .../case-10/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-11/results_true.dat | 2 +- .../case-11/surface_source_true.h5 | Bin 6408 -> 5160 bytes .../case-12/results_true.dat | 2 +- .../case-12/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-13/results_true.dat | 2 +- .../case-13/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-14/results_true.dat | 2 +- .../case-14/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-15/results_true.dat | 2 +- .../case-15/surface_source_true.h5 | Bin 2144 -> 2144 bytes .../case-16/results_true.dat | 2 +- .../case-16/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-17/results_true.dat | 2 +- .../case-17/surface_source_true.h5 | Bin 2144 -> 2144 bytes .../case-18/results_true.dat | 2 +- .../case-18/surface_source_true.h5 | Bin 2144 -> 2144 bytes .../case-19/results_true.dat | 2 +- .../case-19/surface_source_true.h5 | Bin 2144 -> 2144 bytes .../case-20/results_true.dat | 2 +- .../case-20/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-21/results_true.dat | 2 +- .../case-21/surface_source_true.h5 | Bin 2144 -> 2144 bytes .../case-a01/results_true.dat | 2 +- .../case-a01/surface_source_true.h5 | Bin 6616 -> 6720 bytes .../case-d01/results_true.dat | 2 +- .../case-d01/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-d02/results_true.dat | 2 +- .../case-d02/surface_source_true.h5 | Bin 31472 -> 33344 bytes .../case-d03/results_true.dat | 2 +- .../case-d03/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-d04/results_true.dat | 2 +- .../case-d04/surface_source_true.h5 | Bin 31472 -> 33344 bytes .../case-d05/results_true.dat | 2 +- .../case-d05/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-d06/results_true.dat | 2 +- .../case-d06/surface_source_true.h5 | Bin 29496 -> 30744 bytes .../case-d07/results_true.dat | 2 +- .../case-d07/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-d08/results_true.dat | 2 +- .../case-d08/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-e01/results_true.dat | 2 +- .../case-e01/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-e02/results_true.dat | 2 +- .../case-e02/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-e03/results_true.dat | 2 +- .../case-e03/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../surface_tally/results_true.dat | 82 +- .../survival_biasing/results_true.dat | 34 +- .../regression_tests/tallies/results_true.dat | 2 +- .../tally_aggregation/results_true.dat | 194 +- .../tally_arithmetic/results_true.dat | 98 +- .../tally_assumesep/results_true.dat | 14 +- .../tally_nuclides/results_true.dat | 50 +- .../tally_slice_merge/results_true.dat | 104 +- tests/regression_tests/torus/results_true.dat | 2 +- tests/regression_tests/trace/results_true.dat | 2 +- .../track_output/results_true.dat | 144 +- .../translation/results_true.dat | 2 +- .../trigger_batch_interval/results_true.dat | 50 +- .../results_true.dat | 50 +- .../trigger_no_status/results_true.dat | 50 +- .../inputs_true.dat | 2 +- .../results_true.dat | 6 +- .../trigger_statepoint_restart/test.py | 10 +- .../trigger_tallies/results_true.dat | 50 +- tests/regression_tests/triso/results_true.dat | 2 +- .../uniform_fs/results_true.dat | 2 +- .../universe/results_true.dat | 2 +- .../white_plane/results_true.dat | 2 +- tests/unit_tests/test_deplete_resultslist.py | 20 +- tests/unit_tests/test_lib.py | 17 +- tests/unit_tests/test_settings.py | 2 + tests/unit_tests/test_statepoint_batches.py | 26 + 241 files changed, 9795 insertions(+), 9675 deletions(-) create mode 100644 examples/pincell_pulsed/run_pulse.py delete mode 100644 tests/regression_tests/statepoint_batch/__init__.py delete mode 100644 tests/regression_tests/statepoint_batch/geometry.xml delete mode 100644 tests/regression_tests/statepoint_batch/materials.xml delete mode 100644 tests/regression_tests/statepoint_batch/results_true.dat delete mode 100644 tests/regression_tests/statepoint_batch/settings.xml delete mode 100644 tests/regression_tests/statepoint_batch/test.py create mode 100644 tests/unit_tests/test_statepoint_batches.py diff --git a/docs/source/methods/neutron_physics.rst b/docs/source/methods/neutron_physics.rst index fe8b8ad8500..2b797e3dbcf 100644 --- a/docs/source/methods/neutron_physics.rst +++ b/docs/source/methods/neutron_physics.rst @@ -290,7 +290,10 @@ create and store fission sites for the following generation. First, the average number of prompt and delayed neutrons must be determined to decide whether the secondary neutrons will be prompt or delayed. This is important because delayed neutrons have a markedly different spectrum from prompt neutrons, one that has a -lower average energy of emission. The total number of neutrons emitted +lower average energy of emission. Furthermore, in simulations where tracking +time of neutrons is important, we need to consider the emission time delay of +the secondary neutrons, which is dependent on the decay constant of the +delayed neutron precursor. The total number of neutrons emitted :math:`\nu_t` is given as a function of incident energy in the ENDF format. Two representations exist for :math:`\nu_t`. The first is a polynomial of order :math:`N` with coefficients :math:`c_0,c_1,\dots,c_N`. If :math:`\nu_t` has this @@ -306,8 +309,8 @@ interpolation law. The number of prompt neutrons released per fission event :math:`\nu_p` is also given as a function of incident energy and can be specified in a polynomial or tabular format. The number of delayed neutrons released per fission event :math:`\nu_d` can only be specified in a tabular -format. In practice, we only need to determine :math:`nu_t` and -:math:`nu_d`. Once these have been determined, we can calculated the delayed +format. In practice, we only need to determine :math:`\nu_t` and +:math:`\nu_d`. Once these have been determined, we can calculate the delayed neutron fraction .. math:: @@ -335,8 +338,14 @@ neutrons. Otherwise, we produce :math:`\lfloor \nu \rfloor + 1` neutrons. Then, for each fission site produced, we sample the outgoing angle and energy according to the algorithms given in :ref:`sample-angle` and :ref:`sample-energy` respectively. If the neutron is to be born delayed, then -there is an extra step of sampling a delayed neutron precursor group since they -each have an associated secondary energy distribution. +there is an extra step of sampling a delayed neutron precursor group to get the +associated secondary energy distribution and the decay constant +:math:`\lambda`, which is needed to sample the emission delay time :math:`t_d`: + +.. math:: + :label: sample-delay-time + + t_d = -\frac{\ln \xi}{\lambda}. The sampled outgoing angle and energy of fission neutrons along with the position of the collision site are stored in an array called the fission diff --git a/examples/pincell_pulsed/run_pulse.py b/examples/pincell_pulsed/run_pulse.py new file mode 100644 index 00000000000..b6a61ad3d81 --- /dev/null +++ b/examples/pincell_pulsed/run_pulse.py @@ -0,0 +1,101 @@ +import matplotlib.pyplot as plt +import numpy as np +import openmc + +############################################################################### +# Create materials for the problem + +uo2 = openmc.Material(name="UO2 fuel at 2.4% wt enrichment") +uo2.set_density("g/cm3", 10.29769) +uo2.add_element("U", 1.0, enrichment=2.4) +uo2.add_element("O", 2.0) + +helium = openmc.Material(name="Helium for gap") +helium.set_density("g/cm3", 0.001598) +helium.add_element("He", 2.4044e-4) + +zircaloy = openmc.Material(name="Zircaloy 4") +zircaloy.set_density("g/cm3", 6.55) +zircaloy.add_element("Sn", 0.014, "wo") +zircaloy.add_element("Fe", 0.00165, "wo") +zircaloy.add_element("Cr", 0.001, "wo") +zircaloy.add_element("Zr", 0.98335, "wo") + +borated_water = openmc.Material(name="Borated water") +borated_water.set_density("g/cm3", 0.740582) +borated_water.add_element("B", 2.0e-4) # 3x the original pincell +borated_water.add_element("H", 5.0e-2) +borated_water.add_element("O", 2.4e-2) +borated_water.add_s_alpha_beta("c_H_in_H2O") + +############################################################################### +# Define problem geometry + +# Create cylindrical surfaces +fuel_or = openmc.ZCylinder(r=0.39218, name="Fuel OR") +clad_ir = openmc.ZCylinder(r=0.40005, name="Clad IR") +clad_or = openmc.ZCylinder(r=0.45720, name="Clad OR") + +# Create a region represented as the inside of a rectangular prism +pitch = 1.25984 +box = openmc.model.RectangularPrism(pitch, pitch, boundary_type="reflective") + +# Create cells, mapping materials to regions +fuel = openmc.Cell(fill=uo2, region=-fuel_or) +gap = openmc.Cell(fill=helium, region=+fuel_or & -clad_ir) +clad = openmc.Cell(fill=zircaloy, region=+clad_ir & -clad_or) +water = openmc.Cell(fill=borated_water, region=+clad_or & -box) + +# Create a model and assign geometry +model = openmc.Model() +model.geometry = openmc.Geometry([fuel, gap, clad, water]) + +############################################################################### +# Define problem settings + +# Set the mode +model.settings.run_mode = "fixed source" + +# Indicate how many batches and particles to run +model.settings.batches = 10 +model.settings.particles = 10000 + +# Set time cutoff (we only care about t < 100 seconds, see tally below) +model.settings.cutoff = {"time_neutron": 100} + +# Create the neutron pulse source (by default, isotropic direction, t=0) +space = openmc.stats.Point() # At the origin (0, 0, 0) +energy = openmc.stats.delta_function(14.1e6) # At 14.1 MeV +model.settings.source = openmc.IndependentSource(space=space, energy=energy) + +############################################################################### +# Define tallies + +# Create time filter +t_grid = np.insert(np.logspace(-6, 2, 100), 0, 0.0) +time_filter = openmc.TimeFilter(t_grid) + +# Tally for total neutron density in time +density_tally = openmc.Tally(name="Density") +density_tally.filters = [time_filter] +density_tally.scores = ["inverse-velocity"] + +# Add tallies to model +model.tallies = openmc.Tallies([density_tally]) + + +# Run the model +model.run(apply_tally_results=True) + +# Bin-averaged result +density_mean = density_tally.mean.ravel() / np.diff(t_grid) + +# Plot particle density versus time +fig, ax = plt.subplots() +ax.stairs(density_mean, t_grid) +ax.set_xscale("log") +ax.set_yscale("log") +ax.set_xlabel("Time [s]") +ax.set_ylabel("Total density") +ax.grid() +plt.show() diff --git a/include/openmc/settings.h b/include/openmc/settings.h index 069d94c3b28..999b1b83f87 100644 --- a/include/openmc/settings.h +++ b/include/openmc/settings.h @@ -150,6 +150,7 @@ extern double source_rejection_fraction; //!< Minimum fraction of source sites extern int max_history_splits; //!< maximum number of particle splits for weight windows +extern int max_secondaries; //!< maximum number of secondaries in the bank extern int64_t ssw_max_particles; //!< maximum number of particles to be //!< banked on surfaces per process extern int64_t ssw_max_files; //!< maximum number of surface source files diff --git a/openmc/settings.py b/openmc/settings.py index ce3743ff5d1..7bbc31c192a 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -128,6 +128,10 @@ class Settings: Maximum number of times a particle can split during a history .. versionadded:: 0.13 + max_secondaries : int + Maximum secondary bank size + + .. versionadded:: 0.15.3 max_tracks : int Maximum number of tracks written to a track file (per MPI process). @@ -431,6 +435,7 @@ def __init__(self, **kwargs): self._weight_window_checkpoints = {} self._max_history_splits = None self._max_tracks = None + self._max_secondaries = None self._use_decay_photons = None self._random_ray = {} @@ -1137,6 +1142,16 @@ def max_history_splits(self, value: int): cv.check_greater_than('max particle splits', value, 0) self._max_history_splits = value + @property + def max_secondaries(self) -> int: + return self._max_secondaries + + @max_secondaries.setter + def max_secondaries(self, value: int): + cv.check_type('maximum secondary bank size', value, Integral) + cv.check_greater_than('max secondary bank size', value, 0) + self._max_secondaries = value + @property def max_tracks(self) -> int: return self._max_tracks @@ -1673,6 +1688,11 @@ def _create_max_history_splits_subelement(self, root): elem = ET.SubElement(root, "max_history_splits") elem.text = str(self._max_history_splits) + def _create_max_secondaries_subelement(self, root): + if self._max_secondaries is not None: + elem = ET.SubElement(root, "max_secondaries") + elem.text = str(self._max_secondaries) + def _create_max_tracks_subelement(self, root): if self._max_tracks is not None: elem = ET.SubElement(root, "max_tracks") @@ -2073,6 +2093,11 @@ def _max_history_splits_from_xml_element(self, root): if text is not None: self.max_history_splits = int(text) + def _max_secondaries_from_xml_element(self, root): + text = get_text(root, 'max_secondaries') + if text is not None: + self.max_secondaries = int(text) + def _max_tracks_from_xml_element(self, root): text = get_text(root, 'max_tracks') if text is not None: @@ -2194,6 +2219,7 @@ def to_xml_element(self, mesh_memo=None): self._create_weight_window_checkpoints_subelement(element) self._create_max_history_splits_subelement(element) self._create_max_tracks_subelement(element) + self._create_max_secondaries_subelement(element) self._create_random_ray_subelement(element, mesh_memo) self._create_use_decay_photons_subelement(element) self._create_source_rejection_fraction_subelement(element) @@ -2302,6 +2328,7 @@ def from_xml_element(cls, elem, meshes=None): settings._weight_window_checkpoints_from_xml_element(elem) settings._max_history_splits_from_xml_element(elem) settings._max_tracks_from_xml_element(elem) + settings._max_secondaries_from_xml_element(elem) settings._random_ray_from_xml_element(elem) settings._use_decay_photons_from_xml_element(elem) settings._source_rejection_fraction_from_xml_element(elem) diff --git a/src/eigenvalue.cpp b/src/eigenvalue.cpp index 2685bbe98a4..a2120a006d5 100644 --- a/src/eigenvalue.cpp +++ b/src/eigenvalue.cpp @@ -127,30 +127,8 @@ void synchronize_bank() "No fission sites banked on MPI rank " + std::to_string(mpi::rank)); } - // Make sure all processors start at the same point for random sampling. Then - // skip ahead in the sequence using the starting index in the 'global' - // fission bank for each processor. - - int64_t id = simulation::total_gen + overall_generation(); - uint64_t seed = init_seed(id, STREAM_TRACKING); - advance_prn_seed(start, &seed); - - // Determine how many fission sites we need to sample from the source bank - // and the probability for selecting a site. - - int64_t sites_needed; - if (total < settings::n_particles) { - sites_needed = settings::n_particles % total; - } else { - sites_needed = settings::n_particles; - } - double p_sample = static_cast(sites_needed) / total; - simulation::time_bank_sample.start(); - // ========================================================================== - // SAMPLE N_PARTICLES FROM FISSION BANK AND PLACE IN TEMP_SITES - // Allocate temporary source bank -- we don't really know how many fission // sites were created, so overallocate by a factor of 3 int64_t index_temp = 0; @@ -165,33 +143,38 @@ void synchronize_bank() temp_delayed_groups, temp_lifetimes, 3 * simulation::work_per_rank); } - for (int64_t i = 0; i < simulation::fission_bank.size(); i++) { - const auto& site = simulation::fission_bank[i]; + // ========================================================================== + // SAMPLE N_PARTICLES FROM FISSION BANK AND PLACE IN TEMP_SITES - // If there are less than n_particles particles banked, automatically add - // int(n_particles/total) sites to temp_sites. For example, if you need - // 1000 and 300 were banked, this would add 3 source sites per banked site - // and the remaining 100 would be randomly sampled. - if (total < settings::n_particles) { - for (int64_t j = 1; j <= settings::n_particles / total; ++j) { - temp_sites[index_temp] = site; - if (settings::ifp_on) { - copy_ifp_data_from_fission_banks( - i, temp_delayed_groups[index_temp], temp_lifetimes[index_temp]); - } - ++index_temp; - } - } + // We use Uniform Combing method to exactly get the targeted particle size + // [https://doi.org/10.1080/00295639.2022.2091906] - // Randomly sample sites needed - if (prn(&seed) < p_sample) { - temp_sites[index_temp] = site; - if (settings::ifp_on) { - copy_ifp_data_from_fission_banks( - i, temp_delayed_groups[index_temp], temp_lifetimes[index_temp]); - } - ++index_temp; + // Make sure all processors use the same random number seed. + int64_t id = simulation::total_gen + overall_generation(); + uint64_t seed = init_seed(id, STREAM_TRACKING); + + // Comb specification + double teeth_distance = static_cast(total) / settings::n_particles; + double teeth_offset = prn(&seed) * teeth_distance; + + // First and last hitting tooth + int64_t end = start + simulation::fission_bank.size(); + int64_t tooth_start = std::ceil((start - teeth_offset) / teeth_distance); + int64_t tooth_end = std::floor((end - teeth_offset) / teeth_distance) + 1; + + // Locally comb particles in fission_bank + double tooth = tooth_start * teeth_distance + teeth_offset; + for (int64_t i = tooth_start; i < tooth_end; i++) { + int64_t idx = std::floor(tooth) - start; + temp_sites[index_temp] = simulation::fission_bank[idx]; + if (settings::ifp_on) { + copy_ifp_data_from_fission_banks( + idx, temp_delayed_groups[index_temp], temp_lifetimes[index_temp]); } + ++index_temp; + + // Next tooth + tooth += teeth_distance; } // At this point, the sampling of source sites is done and now we need to @@ -217,37 +200,6 @@ void synchronize_bank() finish = index_temp; #endif - // Now that the sampling is complete, we need to ensure that we have exactly - // n_particles source sites. The way this is done in a reproducible manner is - // to adjust only the source sites on the last processor. - - if (mpi::rank == mpi::n_procs - 1) { - if (finish > settings::n_particles) { - // If we have extra sites sampled, we will simply discard the extra - // ones on the last processor - index_temp = settings::n_particles - start; - - } else if (finish < settings::n_particles) { - // If we have too few sites, repeat sites from the very end of the - // fission bank - sites_needed = settings::n_particles - finish; - // TODO: sites_needed > simulation::fission_bank.size() or other test to - // make sure we don't need info from other proc - for (int i = 0; i < sites_needed; ++i) { - int i_bank = simulation::fission_bank.size() - sites_needed + i; - temp_sites[index_temp] = simulation::fission_bank[i_bank]; - if (settings::ifp_on) { - copy_ifp_data_from_fission_banks(i_bank, - temp_delayed_groups[index_temp], temp_lifetimes[index_temp]); - } - ++index_temp; - } - } - - // the last processor should not be sending sites to right - finish = simulation::work_index[mpi::rank + 1]; - } - simulation::time_bank_sample.stop(); simulation::time_bank_sendrecv.start(); diff --git a/src/finalize.cpp b/src/finalize.cpp index e38b0b251cb..25b471a8bd2 100644 --- a/src/finalize.cpp +++ b/src/finalize.cpp @@ -92,6 +92,7 @@ int openmc_finalize() settings::max_lost_particles = 10; settings::max_order = 0; settings::max_particles_in_flight = 100000; + settings::max_secondaries = 10000; settings::max_particle_events = 1'000'000; settings::max_history_splits = 10'000'000; settings::max_tracks = 1000; diff --git a/src/physics.cpp b/src/physics.cpp index 8ad1b99d751..f667fd586d7 100644 --- a/src/physics.cpp +++ b/src/physics.cpp @@ -115,7 +115,7 @@ void sample_neutron_reaction(Particle& p) // Make sure particle population doesn't grow out of control for // subcritical multiplication problems. - if (p.secondary_bank().size() >= 10000) { + if (p.secondary_bank().size() >= settings::max_secondaries) { fatal_error( "The secondary particle bank appears to be growing without " "bound. You are likely running a subcritical multiplication problem " @@ -210,13 +210,23 @@ void create_fission_sites(Particle& p, int i_nuclide, const Reaction& rx) site.particle = ParticleType::neutron; site.time = p.time(); site.wgt = 1. / weight; - site.parent_id = p.id(); - site.progeny_id = p.n_progeny()++; site.surf_id = 0; // Sample delayed group and angle/energy for fission reaction sample_fission_neutron(i_nuclide, rx, &site, p); + // Reject site if it exceeds time cutoff + if (site.delayed_group > 0) { + double t_cutoff = settings::time_cutoff[static_cast(site.particle)]; + if (site.time > t_cutoff) { + continue; + } + } + + // Set parent and progeny IDs + site.parent_id = p.id(); + site.progeny_id = p.n_progeny()++; + // Store fission site in bank if (use_fission_bank) { int64_t idx = simulation::fission_bank.thread_safe_append(site); @@ -1069,6 +1079,10 @@ void sample_fission_neutron( // set the delayed group for the particle born from fission site->delayed_group = group; + // Sample time of emission based on decay constant of precursor + double decay_rate = rx.products_[site->delayed_group].decay_rate_; + site->time -= std::log(prn(p.current_seed())) / decay_rate; + } else { // ==================================================================== // PROMPT NEUTRON SAMPLED diff --git a/src/physics_mg.cpp b/src/physics_mg.cpp index 5ebef9c1417..4c28cb1795a 100644 --- a/src/physics_mg.cpp +++ b/src/physics_mg.cpp @@ -139,8 +139,6 @@ void create_fission_sites(Particle& p) site.particle = ParticleType::neutron; site.time = p.time(); site.wgt = 1. / weight; - site.parent_id = p.id(); - site.progeny_id = p.n_progeny()++; // Sample the cosine of the angle, assuming fission neutrons are emitted // isotropically @@ -165,6 +163,24 @@ void create_fission_sites(Particle& p) // of the code, 0 is prompt. site.delayed_group = dg + 1; + // If delayed product production, sample time of emission + if (dg != -1) { + auto& macro_xs = data::mg.macro_xs_[p.material()]; + double decay_rate = + macro_xs.get_xs(MgxsType::DECAY_RATE, 0, nullptr, nullptr, &dg, 0, 0); + site.time -= std::log(prn(p.current_seed())) / decay_rate; + + // Reject site if it exceeds time cutoff + double t_cutoff = settings::time_cutoff[static_cast(site.particle)]; + if (site.time > t_cutoff) { + continue; + } + } + + // Set parent and progeny ID + site.parent_id = p.id(); + site.progeny_id = p.n_progeny()++; + // Store fission site in bank if (use_fission_bank) { int64_t idx = simulation::fission_bank.thread_safe_append(site); diff --git a/src/settings.cpp b/src/settings.cpp index afa42a3c5e6..03d42bb9da1 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -114,6 +114,7 @@ int max_order {0}; int n_log_bins {8000}; int n_batches; int n_max_batches; +int max_secondaries {10000}; int max_history_splits {10'000'000}; int max_tracks {1000}; ResScatMethod res_scat_method {ResScatMethod::rvs}; @@ -1144,6 +1145,11 @@ void read_settings_xml(pugi::xml_node root) weight_windows_on = get_node_value_bool(root, "weight_windows_on"); } + if (check_for_node(root, "max_secondaries")) { + settings::max_secondaries = + std::stoi(get_node_value(root, "max_secondaries")); + } + if (check_for_node(root, "max_history_splits")) { settings::max_history_splits = std::stoi(get_node_value(root, "max_history_splits")); diff --git a/src/tallies/tally_scoring.cpp b/src/tallies/tally_scoring.cpp index f078a5c6e54..0df80a2398f 100644 --- a/src/tallies/tally_scoring.cpp +++ b/src/tallies/tally_scoring.cpp @@ -1624,8 +1624,7 @@ void score_general_mg(Particle& p, int i_tally, int start_index, tally.estimator_ == TallyEstimator::COLLISION) { if (settings::survival_biasing) { // Determine weight that was absorbed - wgt_absorb = p.wgt_last() * p.neutron_xs(p.event_nuclide()).absorption / - p.neutron_xs(p.event_nuclide()).total; + wgt_absorb = p.wgt_last() * p.macro_xs().absorption / p.macro_xs().total; // Then we either are alive and had a scatter (and so g changed), // or are dead and g did not change diff --git a/tests/regression_tests/adj_cell_rotation/results_true.dat b/tests/regression_tests/adj_cell_rotation/results_true.dat index b3df12e71fe..ddb1546b5b9 100644 --- a/tests/regression_tests/adj_cell_rotation/results_true.dat +++ b/tests/regression_tests/adj_cell_rotation/results_true.dat @@ -1,2 +1,2 @@ k-combined: -4.403987E-01 1.514158E-03 +4.368327E-01 1.953533E-03 diff --git a/tests/regression_tests/albedo_box/results_true.dat b/tests/regression_tests/albedo_box/results_true.dat index 0f571f2459d..dca80abcd8e 100644 --- a/tests/regression_tests/albedo_box/results_true.dat +++ b/tests/regression_tests/albedo_box/results_true.dat @@ -1,2 +1,2 @@ k-combined: -1.590800E+00 4.251788E-03 +1.593206E+00 2.925742E-03 diff --git a/tests/regression_tests/asymmetric_lattice/results_true.dat b/tests/regression_tests/asymmetric_lattice/results_true.dat index 741327d80b3..3d6b610a69d 100644 --- a/tests/regression_tests/asymmetric_lattice/results_true.dat +++ b/tests/regression_tests/asymmetric_lattice/results_true.dat @@ -1 +1 @@ -b0ca1fb0436732188b1a199b3250ca9a33782f8fc379b0f7ff9c582e0c794b0a0470df063cafd0b05e802b26f61eaaf9ff5c0a8a672a933246acf49eed3ebf9f \ No newline at end of file +cc76769636be4f681137598cf366e978d7347425a1dfa1b293d17a28381b2b62595fb7f0d2f126dd06972ff9e79089a18dd53aba45fa2b1f316515b91fe6495a \ No newline at end of file diff --git a/tests/regression_tests/cmfd_feed/results_true.dat b/tests/regression_tests/cmfd_feed/results_true.dat index f5f22d9acd1..1ef9624d4c6 100644 --- a/tests/regression_tests/cmfd_feed/results_true.dat +++ b/tests/regression_tests/cmfd_feed/results_true.dat @@ -1,117 +1,117 @@ k-combined: -1.164262E+00 9.207592E-03 +1.181723E+00 9.944883E-03 tally 1: -1.156972E+01 -1.339924E+01 -2.136306E+01 -4.567185E+01 -2.859527E+01 -8.195821E+01 -3.470754E+01 -1.207851E+02 -3.766403E+01 -1.422263E+02 -3.778821E+01 -1.432660E+02 -3.573197E+01 -1.278854E+02 -2.849979E+01 -8.135515E+01 -2.073803E+01 -4.303374E+01 -1.112117E+01 -1.242944E+01 +1.169899E+01 +1.373251E+01 +2.142380E+01 +4.605511E+01 +2.968085E+01 +8.838716E+01 +3.561418E+01 +1.271206E+02 +3.777783E+01 +1.428817E+02 +3.805832E+01 +1.450213E+02 +3.439836E+01 +1.184892E+02 +2.852438E+01 +8.161896E+01 +2.088423E+01 +4.376204E+01 +1.076670E+01 +1.168108E+01 tally 2: -2.388054E+01 -2.875255E+01 -1.667791E+01 -1.403426E+01 -4.224771E+01 -8.942109E+01 -2.993088E+01 -4.490335E+01 -5.689839E+01 -1.625557E+02 -4.043633E+01 -8.212299E+01 -6.764024E+01 -2.297126E+02 -4.807902E+01 -1.161468E+02 -7.314835E+01 -2.684645E+02 -5.203584E+01 -1.359261E+02 -7.375727E+01 -2.733105E+02 -5.252944E+01 -1.386205E+02 -6.909571E+01 -2.397721E+02 -4.922548E+01 -1.217465E+02 -5.685978E+01 -1.621746E+02 -4.051938E+01 -8.237277E+01 -4.185562E+01 -8.784067E+01 -2.983570E+01 -4.467414E+01 -2.238373E+01 -2.520356E+01 -1.566758E+01 -1.234103E+01 +2.321241E+01 +2.702156E+01 +1.620912E+01 +1.317752E+01 +4.197404E+01 +8.845008E+01 +2.982666E+01 +4.469221E+01 +5.810089E+01 +1.695857E+02 +4.134123E+01 +8.588866E+01 +6.982488E+01 +2.447068E+02 +4.966939E+01 +1.238763E+02 +7.428421E+01 +2.767613E+02 +5.287955E+01 +1.403163E+02 +7.447402E+01 +2.785012E+02 +5.324628E+01 +1.423393E+02 +6.895164E+01 +2.381937E+02 +4.916366E+01 +1.211701E+02 +5.679253E+01 +1.617881E+02 +4.043125E+01 +8.204061E+01 +4.218618E+01 +8.933666E+01 +2.978592E+01 +4.456592E+01 +2.196426E+01 +2.435867E+01 +1.525576E+01 +1.175879E+01 tally 3: -1.609520E+01 -1.307542E+01 -1.033429E+00 -5.510889E-02 -2.877073E+01 -4.149542E+01 -1.964219E+00 -1.954692E-01 -3.896816E+01 -7.629752E+01 -2.484053E+00 -3.103733E-01 -4.634285E+01 -1.079367E+02 -2.974750E+00 -4.468223E-01 -5.007964E+01 -1.259202E+02 -3.181802E+00 -5.103621E-01 -5.058915E+01 -1.286193E+02 -3.249442E+00 -5.337712E-01 -4.744464E+01 -1.131026E+02 -3.067644E+00 -4.736335E-01 -3.900632E+01 -7.634433E+01 -2.443552E+00 -3.028060E-01 -2.874166E+01 -4.146375E+01 -1.810421E+00 -1.671667E-01 -1.509222E+01 -1.145579E+01 -1.014919E+00 -5.391053E-02 +1.563788E+01 +1.226528E+01 +1.053289E+00 +5.666942E-02 +2.870755E+01 +4.139654E+01 +1.838017E+00 +1.710528E-01 +3.978616E+01 +7.955764E+01 +2.560657E+00 +3.334449E-01 +4.780385E+01 +1.147770E+02 +3.139243E+00 +4.967628E-01 +5.106650E+01 +1.308704E+02 +3.170056E+00 +5.078920E-01 +5.123992E+01 +1.318586E+02 +3.211706E+00 +5.205979E-01 +4.729862E+01 +1.121695E+02 +3.068662E+00 +4.749488E-01 +3.898816E+01 +7.630564E+01 +2.516911E+00 +3.199696E-01 +2.865357E+01 +4.125742E+01 +1.852314E+00 +1.741116E-01 +1.467340E+01 +1.088460E+01 +9.268633E-01 +4.450662E-02 tally 4: -3.148231E+00 -4.974555E-01 +3.029754E+00 +4.613561E-01 0.000000E+00 0.000000E+00 -2.805439E+00 -3.982239E-01 -5.574031E+00 -1.561105E+00 +2.832501E+00 +4.049252E-01 +5.517243E+00 +1.527794E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -128,14 +128,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -5.574031E+00 -1.561105E+00 -2.805439E+00 -3.982239E-01 -5.171038E+00 -1.344877E+00 -7.372031E+00 -2.725420E+00 +5.517243E+00 +1.527794E+00 +2.832501E+00 +4.049252E-01 +5.117178E+00 +1.316972E+00 +7.333303E+00 +2.701677E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -152,14 +152,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -7.372031E+00 -2.725420E+00 -5.171038E+00 -1.344877E+00 -6.946847E+00 -2.424850E+00 -8.496610E+00 -3.627542E+00 +7.333303E+00 +2.701677E+00 +5.117178E+00 +1.316972E+00 +7.248464E+00 +2.641591E+00 +8.817788E+00 +3.905530E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -176,14 +176,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -8.496610E+00 -3.627542E+00 -6.946847E+00 -2.424850E+00 -8.479501E+00 -3.607280E+00 -9.261869E+00 -4.305912E+00 +8.817788E+00 +3.905530E+00 +7.248464E+00 +2.641591E+00 +8.646465E+00 +3.749847E+00 +9.460948E+00 +4.495388E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -200,14 +200,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -9.261869E+00 -4.305912E+00 -8.479501E+00 -3.607280E+00 -9.232858E+00 -4.278432E+00 -9.306384E+00 -4.348594E+00 +9.460948E+00 +4.495388E+00 +8.646465E+00 +3.749847E+00 +9.379341E+00 +4.415049E+00 +9.278640E+00 +4.320720E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -224,14 +224,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -9.306384E+00 -4.348594E+00 -9.232858E+00 -4.278432E+00 -9.299764E+00 -4.347828E+00 -8.511976E+00 -3.639893E+00 +9.278640E+00 +4.320720E+00 +9.379341E+00 +4.415049E+00 +9.465746E+00 +4.498591E+00 +8.656146E+00 +3.760545E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -248,14 +248,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -8.511976E+00 -3.639893E+00 -9.299764E+00 -4.347828E+00 -8.726086E+00 -3.819567E+00 -7.147277E+00 -2.562747E+00 +8.656146E+00 +3.760545E+00 +9.465746E+00 +4.498591E+00 +8.589782E+00 +3.700308E+00 +6.996002E+00 +2.456935E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -272,14 +272,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -7.147277E+00 -2.562747E+00 -8.726086E+00 -3.819567E+00 -7.218790E+00 -2.612243E+00 -5.018287E+00 -1.263077E+00 +6.996002E+00 +2.456935E+00 +8.589782E+00 +3.700308E+00 +7.352050E+00 +2.714808E+00 +5.105164E+00 +1.312559E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -296,14 +296,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -5.018287E+00 -1.263077E+00 -7.218790E+00 -2.612243E+00 -5.443494E+00 -1.487018E+00 -2.732334E+00 -3.773047E-01 +5.105164E+00 +1.312559E+00 +7.352050E+00 +2.714808E+00 +5.442756E+00 +1.486776E+00 +2.697305E+00 +3.675580E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -320,12 +320,12 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -2.732334E+00 -3.773047E-01 -5.443494E+00 -1.487018E+00 -3.044773E+00 -4.655756E-01 +2.697305E+00 +3.675580E-01 +5.442756E+00 +1.486776E+00 +3.017025E+00 +4.571443E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -345,144 +345,144 @@ tally 4: 0.000000E+00 0.000000E+00 tally 5: -1.609029E+01 -1.306718E+01 -2.230601E+00 -2.559496E-01 -2.876780E+01 -4.148686E+01 -3.835952E+00 -7.456562E-01 -3.895738E+01 -7.625344E+01 -4.841024E+00 -1.197335E+00 -4.633595E+01 -1.079043E+02 -6.236821E+00 -1.963311E+00 -5.007472E+01 -1.258967E+02 -6.749745E+00 -2.297130E+00 -5.058336E+01 -1.285894E+02 -6.727612E+00 -2.315656E+00 -4.743869E+01 -1.130735E+02 -6.338193E+00 -2.042159E+00 -3.899838E+01 -7.631289E+01 -5.187573E+00 -1.359733E+00 -2.873434E+01 -4.144233E+01 -3.815610E+00 -7.367619E-01 -1.509020E+01 -1.145268E+01 -2.125767E+00 -2.341894E-01 +1.563588E+01 +1.226217E+01 +2.209027E+00 +2.507131E-01 +2.870034E+01 +4.137550E+01 +3.726620E+00 +7.021948E-01 +3.977762E+01 +7.952285E+01 +5.304975E+00 +1.427333E+00 +4.779747E+01 +1.147456E+02 +6.528302E+00 +2.151715E+00 +5.105366E+01 +1.308037E+02 +6.986782E+00 +2.467487E+00 +5.123380E+01 +1.318264E+02 +6.845633E+00 +2.383542E+00 +4.729296E+01 +1.121433E+02 +6.252695E+00 +1.977351E+00 +3.898236E+01 +7.628315E+01 +5.461495E+00 +1.528579E+00 +2.864576E+01 +4.123538E+01 +3.857301E+00 +7.581323E-01 +1.467047E+01 +1.088031E+01 +2.277024E+00 +2.679801E-01 cmfd indices 1.000000E+01 1.000000E+00 1.000000E+00 1.000000E+00 k cmfd -1.129918E+00 -1.143848E+00 -1.147976E+00 -1.151534E+00 -1.152378E+00 -1.148219E+00 -1.150402E+00 -1.154647E+00 -1.156159E+00 -1.160048E+00 -1.167441E+00 -1.168163E+00 -1.168629E+00 -1.164120E+00 -1.165051E+00 -1.169177E+00 +1.169107E+00 +1.175079E+00 +1.173912E+00 +1.175368E+00 +1.174026E+00 +1.181745E+00 +1.182261E+00 +1.183559E+00 +1.178691E+00 +1.179222E+00 +1.179017E+00 +1.172979E+00 +1.175043E+00 +1.173458E+00 +1.174152E+00 +1.171451E+00 cmfd entropy -3.224769E+00 -3.225945E+00 -3.227421E+00 -3.226174E+00 -3.224429E+00 -3.227049E+00 -3.230710E+00 -3.230315E+00 -3.226825E+00 -3.226655E+00 -3.226588E+00 -3.224155E+00 -3.223246E+00 -3.222640E+00 -3.223920E+00 -3.222838E+00 +3.207640E+00 +3.210547E+00 +3.212218E+00 +3.209573E+00 +3.211619E+00 +3.212126E+00 +3.213163E+00 +3.214288E+00 +3.215737E+00 +3.213677E+00 +3.214925E+00 +3.215612E+00 +3.216708E+00 +3.221454E+00 +3.219048E+00 +3.218387E+00 cmfd balance -3.90454E-03 -4.08089E-03 -3.46511E-03 -4.09535E-03 -2.62009E-03 -2.23559E-03 -2.54033E-03 -2.12799E-03 -2.25864E-03 -1.85766E-03 -1.49916E-03 -1.63471E-03 -1.48377E-03 -1.59800E-03 -1.37354E-03 -1.32853E-03 +4.88208E-03 +4.75139E-03 +3.15783E-03 +3.67091E-03 +2.99797E-03 +2.91060E-03 +2.06576E-03 +1.83482E-03 +1.56292E-03 +1.58659E-03 +2.32986E-03 +1.47376E-03 +1.46673E-03 +1.22627E-03 +1.31963E-03 +1.26456E-03 cmfd dominance ratio -5.539E-01 -5.537E-01 -5.536E-01 -5.515E-01 -5.512E-01 -5.514E-01 -5.518E-01 -5.507E-01 -5.500E-01 -5.497E-01 -5.477E-01 -5.461E-01 -5.444E-01 -5.445E-01 -5.454E-01 +5.467E-01 +5.453E-01 +5.458E-01 +5.436E-01 +5.442E-01 +5.406E-01 +5.401E-01 +5.413E-01 +4.995E-01 +5.396E-01 +5.409E-01 +5.414E-01 +5.423E-01 +5.456E-01 +5.442E-01 5.441E-01 cmfd openmc source comparison -9.875240E-03 -1.106163E-02 -9.847628E-03 -6.065921E-03 -5.772039E-03 -4.615656E-03 -4.244331E-03 -3.694299E-03 -3.545814E-03 -3.213063E-03 -3.467537E-03 -3.383489E-03 -3.697591E-03 -3.937358E-03 -3.369124E-03 -3.190359E-03 +9.587418E-03 +8.150978E-03 +6.677661E-03 +6.334727E-03 +5.153692E-03 +5.082964E-03 +4.633153E-03 +4.037383E-03 +3.528742E-03 +4.559089E-03 +3.517370E-03 +3.306117E-03 +2.913809E-03 +1.906045E-03 +1.932794E-03 +1.711341E-03 cmfd source -4.360494E-02 -8.397599E-02 -1.074181E-01 -1.294531E-01 -1.385611E-01 -1.407934E-01 -1.325191E-01 -1.044311E-01 -7.660359E-02 -4.263941E-02 +4.496492E-02 +7.869674E-02 +1.100280E-01 +1.354045E-01 +1.363339E-01 +1.380533E-01 +1.314512E-01 +1.077480E-01 +7.847306E-02 +3.884630E-02 diff --git a/tests/regression_tests/cmfd_feed_2g/results_true.dat b/tests/regression_tests/cmfd_feed_2g/results_true.dat index 2b61d9f4e81..e66eae13c45 100644 --- a/tests/regression_tests/cmfd_feed_2g/results_true.dat +++ b/tests/regression_tests/cmfd_feed_2g/results_true.dat @@ -1,112 +1,112 @@ k-combined: -1.035567E+00 9.463160E-03 +1.027434E+00 6.509170E-03 tally 1: -1.146535E+02 -1.315267E+03 -1.157458E+02 -1.340166E+03 -1.140491E+02 -1.301364E+03 -1.146589E+02 -1.315433E+03 +1.162758E+02 +1.352562E+03 +1.138125E+02 +1.295815E+03 +1.143712E+02 +1.308316E+03 +1.150293E+02 +1.323834E+03 tally 2: -4.319968E+01 -9.360083E+01 -6.373035E+01 -2.038056E+02 -1.889646E+02 -1.812892E+03 -1.024866E+02 -5.254528E+02 -4.323262E+01 -9.360200E+01 -6.363111E+01 -2.028178E+02 -1.849746E+02 -1.711533E+03 -1.034532E+02 -5.352768E+02 -4.296541E+01 -9.249656E+01 -6.346919E+01 -2.018659E+02 -1.888697E+02 -1.812037E+03 -1.025707E+02 -5.262261E+02 -4.691085E+01 -1.269707E+02 -6.299377E+01 -1.990497E+02 -1.853864E+02 -1.719984E+03 -1.023015E+02 -5.235858E+02 +4.284580E+01 +9.207089E+01 +6.335165E+01 +2.014931E+02 +1.894187E+02 +1.818190E+03 +1.033212E+02 +5.340768E+02 +4.282771E+01 +9.186295E+01 +6.295029E+01 +1.983895E+02 +1.834276E+02 +1.684375E+03 +1.022482E+02 +5.228403E+02 +4.330690E+01 +9.402038E+01 +6.395965E+01 +2.053163E+02 +1.851113E+02 +1.714198E+03 +1.030809E+02 +5.314535E+02 +4.337097E+01 +9.426435E+01 +6.417590E+01 +2.063443E+02 +1.846817E+02 +1.706518E+03 +1.027233E+02 +5.279582E+02 tally 3: -6.034963E+01 -1.827718E+02 +5.992726E+01 +1.803120E+02 0.000000E+00 0.000000E+00 -1.865665E-02 -4.244195E-05 -4.170941E+00 -8.769372E-01 -3.453368E+00 -5.989168E-01 +2.172646E-02 +4.414237E-05 +4.181401E+00 +8.912796E-01 +3.536506E+00 +6.287425E-01 0.000000E+00 0.000000E+00 -9.743205E+01 -4.749420E+02 -8.570316E-01 -3.807993E-02 -6.005903E+01 -1.807233E+02 +9.824432E+01 +4.828691E+02 +9.116848E-01 +4.231247E-02 +5.955090E+01 +1.775522E+02 0.000000E+00 0.000000E+00 -1.885450E-02 -3.653402E-05 -4.323863E+00 -9.447512E-01 -3.465465E+00 -6.022861E-01 +1.893222E-02 +3.288000E-05 +4.048183E+00 +8.291130E-01 +3.384041E+00 +5.742363E-01 0.000000E+00 0.000000E+00 -9.843481E+01 -4.846158E+02 -9.048150E-01 -4.205551E-02 -5.996660E+01 -1.802150E+02 +9.734253E+01 +4.738861E+02 +9.157632E-01 +4.329280E-02 +6.045835E+01 +1.835255E+02 0.000000E+00 0.000000E+00 -1.221444E-02 -2.263445E-05 -4.301287E+00 -9.309882E-01 -3.456076E+00 -5.992144E-01 +1.501842E-02 +1.896931E-05 +4.289989E+00 +9.251538E-01 +3.481357E+00 +6.071667E-01 0.000000E+00 0.000000E+00 -9.761231E+01 -4.765834E+02 -8.434644E-01 -3.728180E-02 -5.961891E+01 -1.783106E+02 +9.799829E+01 +4.803591E+02 +8.899390E-01 +4.078750E-02 +6.064531E+01 +1.842746E+02 0.000000E+00 0.000000E+00 -1.500709E-02 -3.541280E-05 -4.155669E+00 -8.713442E-01 -3.455342E+00 -6.006426E-01 +1.495496E-02 +3.082640E-05 +4.321537E+00 +9.371611E-01 +3.453767E+00 +5.983727E-01 0.000000E+00 0.000000E+00 -9.726798E+01 -4.733393E+02 -9.202611E-01 -4.390503E-02 +9.771776E+01 +4.777781E+02 +8.975444E-01 +4.157471E-02 tally 4: 0.000000E+00 0.000000E+00 @@ -116,14 +116,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -8.943264E+00 -4.008855E+00 -3.661063E+01 -6.704808E+01 -8.945553E+00 -4.011707E+00 -3.696832E+01 -6.835286E+01 +8.840487E+00 +3.915792E+00 +3.700362E+01 +6.851588E+01 +8.756789E+00 +3.844443E+00 +3.672366E+01 +6.747174E+01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -132,14 +132,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -8.844569E+00 -3.924591E+00 -3.666726E+01 -6.726522E+01 -8.769637E+00 -3.855006E+00 -3.654115E+01 -6.680777E+01 +8.860460E+00 +3.940908E+00 +3.704658E+01 +6.864069E+01 +8.832046E+00 +3.916611E+00 +3.736239E+01 +6.982147E+01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -156,14 +156,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -8.945553E+00 -4.011707E+00 -3.696832E+01 -6.835286E+01 -8.943264E+00 -4.008855E+00 -3.661063E+01 -6.704808E+01 +8.756789E+00 +3.844443E+00 +3.672366E+01 +6.747174E+01 +8.840487E+00 +3.915792E+00 +3.700362E+01 +6.851588E+01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -180,14 +180,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -8.648474E+00 -3.752219E+00 -3.689442E+01 -6.808997E+01 -8.757378E+00 -3.850408E+00 -3.716920E+01 -6.909715E+01 +8.892576E+00 +3.964978E+00 +3.703525E+01 +6.860645E+01 +8.824229E+00 +3.906925E+00 +3.685909E+01 +6.796123E+01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -212,22 +212,22 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -8.783669E+00 -3.870748E+00 -3.687358E+01 -6.802581E+01 -8.755250E+00 -3.846298E+00 -3.660278E+01 -6.704349E+01 -8.769637E+00 -3.855006E+00 -3.654115E+01 -6.680777E+01 -8.844569E+00 -3.924591E+00 -3.666726E+01 -6.726522E+01 +9.050876E+00 +4.111409E+00 +3.656082E+01 +6.687580E+01 +9.042402E+00 +4.105842E+00 +3.687247E+01 +6.801050E+01 +8.832046E+00 +3.916611E+00 +3.736239E+01 +6.982147E+01 +8.860460E+00 +3.940908E+00 +3.704658E+01 +6.864069E+01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -252,14 +252,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -8.755250E+00 -3.846298E+00 -3.660278E+01 -6.704349E+01 -8.783669E+00 -3.870748E+00 -3.687358E+01 -6.802581E+01 +9.042402E+00 +4.105842E+00 +3.687247E+01 +6.801050E+01 +9.050876E+00 +4.111409E+00 +3.656082E+01 +6.687580E+01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -268,14 +268,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -8.757378E+00 -3.850408E+00 -3.716920E+01 -6.909715E+01 -8.648474E+00 -3.752219E+00 -3.689442E+01 -6.808997E+01 +8.824229E+00 +3.906925E+00 +3.685909E+01 +6.796123E+01 +8.892576E+00 +3.964978E+00 +3.703525E+01 +6.860645E+01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -301,133 +301,133 @@ tally 4: 0.000000E+00 0.000000E+00 tally 5: -6.036829E+01 -1.828885E+02 -1.008777E+02 -5.091033E+02 -1.353211E+01 -9.188155E+00 -4.618716E+01 -1.067410E+02 -6.007789E+01 -1.808371E+02 -1.018892E+02 -5.192269E+02 -1.357961E+01 -9.247052E+00 -4.618560E+01 -1.067058E+02 -5.997882E+01 -1.802895E+02 -1.010597E+02 -5.108478E+02 -1.374955E+01 -9.502494E+00 -4.619500E+01 -1.067547E+02 -5.963392E+01 -1.783999E+02 -1.007134E+02 -5.074700E+02 -1.319398E+01 -8.734106E+00 -4.586295E+01 -1.052870E+02 +5.994898E+01 +1.804403E+02 +1.017670E+02 +5.181130E+02 +1.354160E+01 +9.220935E+00 +4.648971E+01 +1.081636E+02 +5.956983E+01 +1.776668E+02 +1.007226E+02 +5.073592E+02 +1.347883E+01 +9.120344E+00 +4.609907E+01 +1.063127E+02 +6.047337E+01 +1.836168E+02 +1.014777E+02 +5.150557E+02 +1.390508E+01 +9.722899E+00 +4.629251E+01 +1.072027E+02 +6.066027E+01 +1.843661E+02 +1.011648E+02 +5.120629E+02 +1.365982E+01 +9.372236E+00 +4.602994E+01 +1.060579E+02 cmfd indices 2.000000E+00 2.000000E+00 1.000000E+00 2.000000E+00 k cmfd -1.018115E+00 -1.022665E+00 -1.020323E+00 -1.020653E+00 -1.021036E+00 -1.020623E+00 -1.021482E+00 -1.025450E+00 -1.027292E+00 -1.028065E+00 -1.027065E+00 -1.024275E+00 -1.025309E+00 -1.026039E+00 -1.026700E+00 -1.023865E+00 +1.013488E+00 +1.024396E+00 +1.015533E+00 +1.009319E+00 +1.012726E+00 +1.014831E+00 +1.021757E+00 +1.022002E+00 +1.023619E+00 +1.020953E+00 +1.023910E+00 +1.027657E+00 +1.024501E+00 +1.023838E+00 +1.025464E+00 +1.022802E+00 cmfd entropy -1.998965E+00 -1.999214E+00 -1.999348E+00 -1.999366E+00 -1.999564E+00 -1.999453E+00 -1.999533E+00 -1.999630E+00 -1.999739E+00 -1.999588E+00 -1.999581E+00 -1.999719E+00 -1.999773E+00 -1.999764E+00 -1.999821E+00 -1.999843E+00 +1.998974E+00 +1.998742E+00 +1.999128E+00 +1.998952E+00 +1.998951E+00 +1.999439E+00 +1.999626E+00 +1.999826E+00 +1.999513E+00 +1.999451E+00 +1.999514E+00 +1.999590E+00 +1.999563E+00 +1.999604E+00 +1.999742E+00 +1.999736E+00 cmfd balance -5.73174E-04 -7.55398E-04 -1.46671E-03 -6.39625E-04 -8.19008E-04 -1.93449E-03 -1.15900E-03 -1.01690E-03 -5.62788E-04 -6.90450E-04 -6.01060E-04 -5.73418E-04 -4.37190E-04 -4.82966E-04 -4.09700E-04 -3.45096E-04 +9.79896E-04 +4.24873E-04 +8.05696E-04 +1.92071E-03 +3.70731E-04 +2.81424E-04 +8.28991E-04 +6.12217E-04 +5.29185E-04 +4.97799E-04 +3.09154E-04 +1.73703E-04 +2.56689E-04 +2.64938E-04 +1.96305E-04 +1.82702E-04 cmfd dominance ratio -6.264E-03 -6.142E-03 -5.987E-03 -6.082E-03 -5.895E-03 -5.939E-03 -5.910E-03 -5.948E-03 -6.013E-03 -6.017E-03 -6.024E-03 -6.008E-03 -5.976E-03 -5.987E-03 -5.967E-03 -5.929E-03 +6.304E-03 +6.246E-03 +6.159E-03 +6.249E-03 +6.101E-03 +6.155E-03 +6.010E-03 +6.177E-03 +6.349E-03 +6.241E-03 +6.244E-03 +6.249E-03 +6.270E-03 +6.272E-03 +6.278E-03 +6.290E-03 cmfd openmc source comparison -4.832872E-05 -6.552342E-05 -7.516800E-05 -7.916087E-05 -9.022260E-05 -8.574478E-05 -7.891622E-05 -7.281636E-05 -7.750571E-05 -6.565408E-05 -6.078665E-05 -5.834343E-05 -4.758176E-05 -5.723990E-05 -4.994116E-05 -4.116808E-05 +4.046094E-05 +5.979431E-05 +3.836521E-05 +4.577591E-05 +5.012911E-05 +2.114677E-05 +2.074571E-05 +3.042280E-05 +2.408163E-05 +2.434542E-05 +1.190699E-05 +9.499301E-06 +2.354221E-05 +2.937924E-05 +1.889875E-05 +1.913866E-05 cmfd source -2.455663E-01 -2.553511E-01 -2.512257E-01 -2.478570E-01 +2.489706E-01 +2.426801E-01 +2.532142E-01 +2.551351E-01 0.000000E+00 0.000000E+00 0.000000E+00 diff --git a/tests/regression_tests/cmfd_feed_expanding_window/results_true.dat b/tests/regression_tests/cmfd_feed_expanding_window/results_true.dat index 8fd8cdbb845..b39f82f96ea 100644 --- a/tests/regression_tests/cmfd_feed_expanding_window/results_true.dat +++ b/tests/regression_tests/cmfd_feed_expanding_window/results_true.dat @@ -1,117 +1,117 @@ k-combined: -1.167865E+00 7.492213E-03 +1.170835E+00 5.423480E-03 tally 1: -1.146860E+01 -1.318884E+01 -2.161527E+01 -4.685283E+01 -2.951158E+01 -8.733566E+01 -3.521610E+01 -1.242821E+02 -3.774236E+01 -1.426501E+02 -3.727918E+01 -1.391158E+02 -3.377176E+01 -1.143839E+02 -2.904497E+01 -8.452907E+01 -2.090871E+01 -4.384549E+01 -1.078642E+01 -1.168086E+01 +1.205100E+01 +1.456707E+01 +2.183882E+01 +4.781179E+01 +2.844010E+01 +8.102358E+01 +3.356334E+01 +1.130832E+02 +3.660829E+01 +1.344973E+02 +3.697740E+01 +1.371500E+02 +3.400119E+01 +1.160196E+02 +2.839868E+01 +8.083199E+01 +2.140398E+01 +4.615447E+01 +1.118179E+01 +1.262942E+01 tally 2: -1.136810E+00 -1.292338E+00 -7.987303E-01 -6.379700E-01 -2.266938E+00 -5.139009E+00 -1.613483E+00 -2.603328E+00 -3.046349E+00 -9.280239E+00 -2.182459E+00 -4.763126E+00 -3.568068E+00 -1.273111E+01 -2.532456E+00 -6.413333E+00 -3.989504E+00 -1.591614E+01 -2.848301E+00 -8.112818E+00 -3.853133E+00 -1.484663E+01 -2.718493E+00 -7.390202E+00 -3.478138E+00 -1.209745E+01 -2.467281E+00 -6.087476E+00 -2.952220E+00 -8.715605E+00 -2.103261E+00 -4.423706E+00 -1.917459E+00 -3.676649E+00 -1.378369E+00 -1.899902E+00 -1.048240E+00 -1.098807E+00 -7.511947E-01 -5.642934E-01 +1.218245E+00 +1.484121E+00 +8.387442E-01 +7.034918E-01 +2.142134E+00 +4.588738E+00 +1.526727E+00 +2.330895E+00 +2.736157E+00 +7.486556E+00 +1.973921E+00 +3.896363E+00 +3.606244E+00 +1.300500E+01 +2.537580E+00 +6.439313E+00 +3.668958E+00 +1.346126E+01 +2.599095E+00 +6.755294E+00 +3.647982E+00 +1.330777E+01 +2.539750E+00 +6.450332E+00 +3.118921E+00 +9.727669E+00 +2.186447E+00 +4.780549E+00 +2.881110E+00 +8.300795E+00 +2.042635E+00 +4.172360E+00 +2.045602E+00 +4.184486E+00 +1.458384E+00 +2.126884E+00 +1.022124E+00 +1.044738E+00 +7.112678E-01 +5.059018E-01 tally 3: -7.701233E-01 -5.930898E-01 -4.481585E-02 -2.008461E-03 -1.547307E+00 -2.394158E+00 -1.226539E-01 -1.504398E-02 -2.106373E+00 -4.436806E+00 -1.450618E-01 -2.104294E-02 -2.437654E+00 -5.942157E+00 -1.521380E-01 -2.314598E-02 -2.754639E+00 -7.588038E+00 -1.745460E-01 -3.046629E-02 -2.623852E+00 -6.884601E+00 -1.851602E-01 -3.428432E-02 -2.376886E+00 -5.649588E+00 -1.615729E-01 -2.610582E-02 -2.021856E+00 -4.087900E+00 -1.533174E-01 -2.350622E-02 -1.333190E+00 -1.777397E+00 -7.076188E-02 -5.007243E-03 -7.258527E-01 -5.268622E-01 -3.656030E-02 -1.336656E-03 +8.048428E-01 +6.477720E-01 +6.603741E-02 +4.360940E-03 +1.466886E+00 +2.151755E+00 +1.002354E-01 +1.004713E-02 +1.909238E+00 +3.645189E+00 +1.202824E-01 +1.446786E-02 +2.443130E+00 +5.968886E+00 +1.627351E-01 +2.648270E-02 +2.492602E+00 +6.213064E+00 +1.910368E-01 +3.649506E-02 +2.437262E+00 +5.940245E+00 +1.568389E-01 +2.459843E-02 +2.104091E+00 +4.427200E+00 +1.450465E-01 +2.103848E-02 +1.965900E+00 +3.864762E+00 +1.367918E-01 +1.871199E-02 +1.402871E+00 +1.968047E+00 +1.061316E-01 +1.126391E-02 +6.832689E-01 +4.668565E-01 +4.599034E-02 +2.115112E-03 tally 4: -1.667432E-01 -2.780328E-02 +1.497312E-01 +2.241943E-02 0.000000E+00 0.000000E+00 -1.292567E-01 -1.670730E-02 -2.813370E-01 -7.915052E-02 +1.535839E-01 +2.358801E-02 +2.882052E-01 +8.306225E-02 0.000000E+00 0.000000E+00 0.000000E+00 @@ -128,14 +128,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -2.813370E-01 -7.915052E-02 -1.292567E-01 -1.670730E-02 -2.670549E-01 -7.131835E-02 -4.055324E-01 -1.644566E-01 +2.882052E-01 +8.306225E-02 +1.535839E-01 +2.358801E-02 +2.526805E-01 +6.384743E-02 +3.616220E-01 +1.307705E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -152,14 +152,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -4.055324E-01 -1.644566E-01 -2.670549E-01 -7.131835E-02 -3.848125E-01 -1.480807E-01 -4.809430E-01 -2.313062E-01 +3.616220E-01 +1.307705E-01 +2.526805E-01 +6.384743E-02 +3.594306E-01 +1.291904E-01 +4.229730E-01 +1.789062E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -176,14 +176,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -4.809430E-01 -2.313062E-01 -3.848125E-01 -1.480807E-01 -4.543918E-01 -2.064719E-01 -5.106133E-01 -2.607260E-01 +4.229730E-01 +1.789062E-01 +3.594306E-01 +1.291904E-01 +3.973299E-01 +1.578711E-01 +4.255879E-01 +1.811250E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -200,14 +200,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -5.106133E-01 -2.607260E-01 -4.543918E-01 -2.064719E-01 -4.543120E-01 -2.063994E-01 -4.626328E-01 -2.140291E-01 +4.255879E-01 +1.811250E-01 +3.973299E-01 +1.578711E-01 +4.633933E-01 +2.147333E-01 +4.672837E-01 +2.183540E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -224,14 +224,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -4.626328E-01 -2.140291E-01 -4.543120E-01 -2.063994E-01 -4.827759E-01 -2.330726E-01 -4.442622E-01 -1.973689E-01 +4.672837E-01 +2.183540E-01 +4.633933E-01 +2.147333E-01 +4.251073E-01 +1.807162E-01 +3.842922E-01 +1.476805E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -248,14 +248,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -4.442622E-01 -1.973689E-01 -4.827759E-01 -2.330726E-01 -4.630420E-01 -2.144079E-01 -3.886524E-01 -1.510507E-01 +3.842922E-01 +1.476805E-01 +4.251073E-01 +1.807162E-01 +4.045096E-01 +1.636280E-01 +3.192860E-01 +1.019436E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -272,14 +272,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -3.886524E-01 -1.510507E-01 -4.630420E-01 -2.144079E-01 -3.535870E-01 -1.250237E-01 -2.530312E-01 -6.402478E-02 +3.192860E-01 +1.019436E-01 +4.045096E-01 +1.636280E-01 +3.738326E-01 +1.397508E-01 +2.598153E-01 +6.750398E-02 0.000000E+00 0.000000E+00 0.000000E+00 @@ -296,14 +296,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -2.530312E-01 -6.402478E-02 -3.535870E-01 -1.250237E-01 -2.465524E-01 -6.078808E-02 -1.197152E-01 -1.433173E-02 +2.598153E-01 +6.750398E-02 +3.738326E-01 +1.397508E-01 +2.453191E-01 +6.018146E-02 +1.098964E-01 +1.207721E-02 0.000000E+00 0.000000E+00 0.000000E+00 @@ -320,12 +320,12 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -1.197152E-01 -1.433173E-02 -2.465524E-01 -6.078808E-02 -1.369631E-01 -1.875888E-02 +1.098964E-01 +1.207721E-02 +2.453191E-01 +6.018146E-02 +1.458094E-01 +2.126039E-02 0.000000E+00 0.000000E+00 0.000000E+00 @@ -345,119 +345,119 @@ tally 4: 0.000000E+00 0.000000E+00 tally 5: -7.701233E-01 -5.930898E-01 -1.386250E-01 -1.921688E-02 -1.547307E+00 -2.394158E+00 -2.630277E-01 -6.918357E-02 -2.106373E+00 -4.436806E+00 -2.807880E-01 -7.884187E-02 -2.435849E+00 -5.933361E+00 -3.322060E-01 -1.103608E-01 -2.753634E+00 -7.582501E+00 -3.825922E-01 -1.463768E-01 -2.623852E+00 -6.884601E+00 -3.888710E-01 -1.512206E-01 -2.376886E+00 -5.649588E+00 -3.196217E-01 -1.021581E-01 -2.021856E+00 -4.087900E+00 -2.897881E-01 -8.397715E-02 -1.333190E+00 -1.777397E+00 -1.627110E-01 -2.647486E-02 -7.258527E-01 -5.268622E-01 -9.348666E-02 -8.739755E-03 +8.048428E-01 +6.477720E-01 +1.018934E-01 +1.038226E-02 +1.466886E+00 +2.151755E+00 +1.414681E-01 +2.001322E-02 +1.909238E+00 +3.645189E+00 +2.450211E-01 +6.003535E-02 +2.443130E+00 +5.968886E+00 +3.360056E-01 +1.128997E-01 +2.492602E+00 +6.213064E+00 +3.266277E-01 +1.066856E-01 +2.437262E+00 +5.940245E+00 +2.878100E-01 +8.283461E-02 +2.104091E+00 +4.427200E+00 +3.440457E-01 +1.183675E-01 +1.965900E+00 +3.864762E+00 +2.880615E-01 +8.297945E-02 +1.401955E+00 +1.965478E+00 +1.646479E-01 +2.710892E-02 +6.832689E-01 +4.668565E-01 +1.147413E-01 +1.316557E-02 cmfd indices 1.000000E+01 1.000000E+00 1.000000E+00 1.000000E+00 k cmfd -1.149077E+00 -1.156751E+00 -1.158648E+00 -1.159506E+00 -1.156567E+00 -1.160259E+00 -1.150345E+00 -1.149846E+00 -1.151606E+00 -1.164544E+00 -1.174648E+00 +1.181376E+00 +1.176656E+00 +1.161939E+00 +1.163552E+00 +1.163035E+00 +1.170382E+00 +1.160597E+00 +1.154301E+00 +1.159007E+00 +1.148290E+00 +1.157088E+00 cmfd entropy -3.216173E+00 -3.228717E+00 -3.220402E+00 -3.214352E+00 -3.215636E+00 -3.213599E+00 -3.212854E+00 -3.213131E+00 -3.213196E+00 -3.205474E+00 -3.202869E+00 +3.246419E+00 +3.246511E+00 +3.252247E+00 +3.240919E+00 +3.237600E+00 +3.233990E+00 +3.234226E+00 +3.229356E+00 +3.224272E+00 +3.225381E+00 +3.226778E+00 cmfd balance -3.08825E-03 -1.42345E-03 -1.21253E-03 -1.17694E-03 -1.05901E-03 -9.29611E-04 -1.35587E-03 -1.13579E-03 -1.14964E-03 -1.29313E-03 -1.46566E-03 +4.18486E-03 +1.72126E-03 +1.10899E-03 +1.88170E-03 +1.31646E-03 +1.34128E-03 +1.57944E-03 +2.11251E-03 +1.79912E-03 +1.86000E-03 +1.47765E-03 cmfd dominance ratio 5.524E-01 -5.614E-01 -5.522E-01 -5.487E-01 -5.482E-01 -5.446E-01 -5.437E-01 -5.429E-01 -5.407E-01 -5.380E-01 -5.377E-01 +5.597E-01 +5.622E-01 +5.544E-01 +5.541E-01 +5.519E-01 +5.532E-01 +5.550E-01 +5.484E-01 +5.497E-01 +5.500E-01 cmfd openmc source comparison -1.586045E-02 -6.953134E-03 -6.860419E-03 -6.198467E-03 -5.142854E-03 -4.373354E-03 -5.564831E-03 -4.184765E-03 -1.867780E-03 -2.734784E-03 -2.523985E-03 +1.905464E-03 +4.145126E-03 +2.465876E-03 +2.346755E-03 +1.848120E-03 +3.263822E-03 +3.641639E-03 +4.031509E-03 +4.999010E-03 +6.640746E-03 +5.691414E-03 cmfd source -4.241440E-02 -8.226026E-02 -1.180811E-01 -1.328433E-01 -1.412410E-01 -1.424902E-01 -1.269340E-01 -1.096490E-01 -6.953251E-02 -3.455422E-02 +4.951338E-02 +8.478025E-02 +1.083132E-01 +1.301432E-01 +1.341190E-01 +1.445825E-01 +1.255119E-01 +1.063303E-01 +7.830158E-02 +3.840469E-02 diff --git a/tests/regression_tests/cmfd_feed_ng/results_true.dat b/tests/regression_tests/cmfd_feed_ng/results_true.dat index 153238bfeb5..4ea1515f184 100644 --- a/tests/regression_tests/cmfd_feed_ng/results_true.dat +++ b/tests/regression_tests/cmfd_feed_ng/results_true.dat @@ -1,208 +1,208 @@ k-combined: -1.005987E+00 1.354263E-02 +1.008852E+00 9.028695E-03 tally 1: -1.140273E+02 -1.301245E+03 -1.147962E+02 -1.319049E+03 -1.151426E+02 -1.326442E+03 -1.149265E+02 -1.321518E+03 +1.151271E+02 +1.325871E+03 +1.143934E+02 +1.309051E+03 +1.142507E+02 +1.306616E+03 +1.140242E+02 +1.300786E+03 tally 2: -3.462748E+01 -7.542476E+01 -5.129219E+01 -1.658142E+02 -1.034704E+01 -6.730603E+00 -8.672967E+00 -4.715295E+00 -1.344669E+02 -1.132019E+03 -7.262519E+01 -3.300787E+02 -3.447358E+01 -7.459545E+01 -5.075836E+01 -1.619570E+02 -1.080516E+01 -7.366369E+00 -8.908065E+00 -4.991975E+00 -1.354224E+02 -1.146824E+03 -7.300078E+01 -3.332531E+02 -3.432298E+01 -7.388666E+01 -5.096378E+01 -1.627979E+02 -1.053664E+01 -7.029789E+00 -8.826624E+00 -4.904429E+00 -1.388389E+02 -1.207280E+03 -7.376623E+01 -3.403211E+02 -4.383841E+01 -1.943331E+02 -5.165881E+01 -1.675923E+02 -1.059646E+01 -7.048052E+00 -8.763673E+00 -4.823505E+00 -1.378179E+02 -1.188104E+03 -7.431708E+01 -3.453746E+02 +3.403617E+01 +7.260478E+01 +5.031678E+01 +1.588977E+02 +1.003700E+01 +6.373741E+00 +8.514575E+00 +4.571811E+00 +1.413036E+02 +1.264708E+03 +7.321799E+01 +3.353408E+02 +3.354839E+01 +7.052647E+01 +4.895930E+01 +1.501243E+02 +9.972495E+00 +6.271276E+00 +8.436263E+00 +4.481319E+00 +1.353506E+02 +1.146040E+03 +7.309382E+01 +3.341751E+02 +3.389861E+01 +7.205501E+01 +5.005946E+01 +1.571210E+02 +1.041650E+01 +6.810868E+00 +8.839753E+00 +4.897617E+00 +1.344145E+02 +1.130242E+03 +7.270373E+01 +3.307223E+02 +3.347928E+01 +7.040185E+01 +4.940585E+01 +1.535767E+02 +9.898649E+00 +6.175319E+00 +8.406032E+00 +4.442856E+00 +1.374544E+02 +1.182191E+03 +7.334301E+01 +3.365399E+02 tally 3: -4.858880E+01 -1.488705E+02 +4.755532E+01 +1.419592E+02 0.000000E+00 0.000000E+00 -8.148667E-03 -1.337050E-05 +1.628248E-02 +3.680742E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -3.347376E+00 -7.045574E-01 -2.433484E+00 -3.727266E-01 +3.347160E+00 +7.104290E-01 +2.453669E+00 +3.797470E-01 0.000000E+00 0.000000E+00 -6.095478E+00 -2.332622E+00 +5.925795E+00 +2.220734E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.235421E-01 -1.205155E-03 -3.647699E-01 -8.953589E-03 +9.542527E-02 +1.118856E-03 +3.018759E-01 +6.371059E-03 0.000000E+00 0.000000E+00 -2.673965E+00 -4.517972E-01 +2.501316E+00 +3.938401E-01 0.000000E+00 0.000000E+00 -6.841105E+01 -2.929195E+02 -5.902473E-01 -2.307683E-02 -4.792291E+01 -1.444397E+02 +6.926265E+01 +3.001504E+02 +6.765221E-01 +2.991787E-02 +4.605523E+01 +1.328435E+02 0.000000E+00 0.000000E+00 -2.183275E-02 -8.061011E-05 +1.687782E-02 +3.921162E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -3.435826E+00 -7.480244E-01 -2.450829E+00 -3.800327E-01 +3.481608E+00 +7.705602E-01 +2.439374E+00 +3.779705E-01 0.000000E+00 0.000000E+00 -6.331358E+00 -2.530492E+00 +5.855806E+00 +2.162361E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.012941E-01 -8.217580E-04 -2.981274E-01 -5.863186E-03 +1.016677E-01 +9.263706E-04 +3.264878E-01 +7.385168E-03 0.000000E+00 0.000000E+00 -2.535628E+00 -4.048310E-01 +2.519730E+00 +3.986182E-01 0.000000E+00 0.000000E+00 -6.912003E+01 -2.987684E+02 -5.984862E-01 -2.386115E-02 -4.822881E+01 -1.458309E+02 +6.920950E+01 +2.996184E+02 +5.985719E-01 +2.368062E-02 +4.730723E+01 +1.403550E+02 0.000000E+00 0.000000E+00 -1.525590E-02 -3.794082E-05 +6.800415E-03 +1.163614E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -3.314494E+00 -6.944527E-01 -2.424209E+00 -3.691360E-01 +3.347607E+00 +7.085691E-01 +2.556997E+00 +4.109118E-01 0.000000E+00 0.000000E+00 -6.254897E+00 -2.474317E+00 +6.171063E+00 +2.391770E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.112542E-01 -1.051688E-03 -3.234880E-01 -7.156753E-03 +7.942016E-02 +5.628747E-04 +3.456912E-01 +7.940086E-03 0.000000E+00 0.000000E+00 -2.474142E+00 -3.837496E-01 +2.684459E+00 +4.546338E-01 0.000000E+00 0.000000E+00 -6.987095E+01 -3.053358E+02 -5.928782E-01 -2.368116E-02 -4.885415E+01 -1.499856E+02 +6.850588E+01 +2.936078E+02 +6.458806E-01 +2.672541E-02 +4.673301E+01 +1.374202E+02 0.000000E+00 0.000000E+00 -1.262416E-02 -2.486286E-05 +1.504681E-02 +4.202606E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -3.426826E+00 -7.480396E-01 -2.487209E+00 -3.903882E-01 +3.139138E+00 +6.206027E-01 +2.341735E+00 +3.454035E-01 0.000000E+00 0.000000E+00 -6.127303E+00 -2.364605E+00 +5.923755E+00 +2.216341E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -9.439007E-02 -1.082564E-03 -2.963491E-01 -5.652952E-03 +7.030156E-02 +4.283974E-04 +3.247594E-01 +7.133772E-03 0.000000E+00 0.000000E+00 -2.620696E+00 -4.311792E-01 +2.559691E+00 +4.119674E-01 0.000000E+00 0.000000E+00 -7.030725E+01 -3.091241E+02 -5.986966E-01 -2.352487E-02 +6.938051E+01 +3.012078E+02 +5.159565E-01 +1.730381E-02 tally 4: 0.000000E+00 0.000000E+00 @@ -216,18 +216,18 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -7.028624E+00 -3.105990E+00 -2.154648E+00 -2.948865E-01 -2.714077E+01 -4.607926E+01 -7.035506E+00 -3.118549E+00 -2.085916E+00 -2.730884E-01 -2.750091E+01 -4.731304E+01 +7.028166E+00 +3.103666E+00 +2.028371E+00 +2.606104E-01 +2.715466E+01 +4.614452E+01 +6.981028E+00 +3.059096E+00 +2.032450E+00 +2.610410E-01 +2.734281E+01 +4.675062E+01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -240,18 +240,18 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -7.170567E+00 -3.240715E+00 -2.131758E+00 -2.862355E-01 -2.747702E+01 -4.722977E+01 -7.068817E+00 -3.139275E+00 -2.095865E+00 -2.762179E-01 -2.737835E+01 -4.688689E+01 +6.969559E+00 +3.054867E+00 +2.042871E+00 +2.624815E-01 +2.766332E+01 +4.787778E+01 +7.022610E+00 +3.098329E+00 +2.109973E+00 +2.824233E-01 +2.733826E+01 +4.676309E+01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -276,18 +276,18 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -7.035506E+00 -3.118549E+00 -2.085916E+00 -2.730884E-01 -2.750091E+01 -4.731304E+01 -7.028624E+00 -3.105990E+00 -2.154648E+00 -2.948865E-01 -2.714077E+01 -4.607926E+01 +6.981028E+00 +3.059096E+00 +2.032450E+00 +2.610410E-01 +2.734281E+01 +4.675062E+01 +7.028166E+00 +3.103666E+00 +2.028371E+00 +2.606104E-01 +2.715466E+01 +4.614452E+01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -312,18 +312,18 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -7.103896E+00 -3.171147E+00 -2.095666E+00 -2.756673E-01 -2.711842E+01 -4.600196E+01 -7.197270E+00 -3.255501E+00 -2.046179E+00 -2.630301E-01 -2.729901E+01 -4.662030E+01 +6.782152E+00 +2.885448E+00 +1.951138E+00 +2.400912E-01 +2.739652E+01 +4.697696E+01 +6.873220E+00 +2.965563E+00 +1.999066E+00 +2.521586E-01 +2.732700E+01 +4.670990E+01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -360,30 +360,30 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -7.240906E+00 -3.296285E+00 -2.130816E+00 -2.850713E-01 -2.761433E+01 -4.771548E+01 -7.126427E+00 -3.199209E+00 -2.177254E+00 -2.998765E-01 -2.754936E+01 -4.748447E+01 -7.068817E+00 -3.139275E+00 -2.095865E+00 -2.762179E-01 -2.737835E+01 -4.688689E+01 -7.170567E+00 -3.240715E+00 -2.131758E+00 -2.862355E-01 -2.747702E+01 -4.722977E+01 +6.933695E+00 +3.028826E+00 +2.080336E+00 +2.719620E-01 +2.726925E+01 +4.650782E+01 +6.836291E+00 +2.935998E+00 +2.124868E+00 +2.841370E-01 +2.709685E+01 +4.591604E+01 +7.022610E+00 +3.098329E+00 +2.109973E+00 +2.824233E-01 +2.733826E+01 +4.676309E+01 +6.969559E+00 +3.054867E+00 +2.042871E+00 +2.624815E-01 +2.766332E+01 +4.787778E+01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -420,18 +420,18 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -7.126427E+00 -3.199209E+00 -2.177254E+00 -2.998765E-01 -2.754936E+01 -4.748447E+01 -7.240906E+00 -3.296285E+00 -2.130816E+00 -2.850713E-01 -2.761433E+01 -4.771548E+01 +6.836291E+00 +2.935998E+00 +2.124868E+00 +2.841370E-01 +2.709685E+01 +4.591604E+01 +6.933695E+00 +3.028826E+00 +2.080336E+00 +2.719620E-01 +2.726925E+01 +4.650782E+01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -444,18 +444,18 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -7.197270E+00 -3.255501E+00 -2.046179E+00 -2.630301E-01 -2.729901E+01 -4.662030E+01 -7.103896E+00 -3.171147E+00 -2.095666E+00 -2.756673E-01 -2.711842E+01 -4.600196E+01 +6.873220E+00 +2.965563E+00 +1.999066E+00 +2.521586E-01 +2.732700E+01 +4.670990E+01 +6.782152E+00 +2.885448E+00 +1.951138E+00 +2.400912E-01 +2.739652E+01 +4.697696E+01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -493,124 +493,124 @@ tally 4: 0.000000E+00 0.000000E+00 tally 5: -4.859695E+01 -1.489217E+02 -8.528963E+00 -4.561881E+00 -7.144500E+01 -3.194467E+02 -1.120265E+01 -7.944038E+00 -4.009821E+00 -1.012575E+00 -3.235456E+01 -6.558658E+01 -4.794474E+01 -1.445671E+02 -8.782187E+00 -4.852477E+00 -7.195036E+01 -3.237389E+02 -1.075572E+01 -7.333822E+00 -4.084680E+00 -1.054714E+00 -3.267154E+01 -6.675266E+01 -4.824407E+01 -1.459220E+02 -8.679106E+00 -4.742342E+00 -7.266858E+01 -3.302641E+02 -1.094872E+01 -7.536318E+00 -3.935828E+00 -9.749543E-01 -3.319517E+01 -6.894791E+01 -4.886677E+01 -1.500624E+02 -8.614512E+00 -4.662618E+00 -7.321408E+01 -3.352172E+02 -1.095123E+01 -7.574359E+00 -3.885927E+00 -9.539315E-01 -3.334065E+01 -6.953215E+01 +4.757160E+01 +1.420561E+02 +8.379464E+00 +4.426892E+00 +7.205748E+01 +3.248178E+02 +1.047742E+01 +6.922945E+00 +3.879420E+00 +9.517577E-01 +3.297227E+01 +6.802001E+01 +4.607211E+01 +1.329402E+02 +8.295180E+00 +4.334618E+00 +7.205259E+01 +3.247324E+02 +1.059307E+01 +7.039382E+00 +3.783039E+00 +9.030658E-01 +3.288052E+01 +6.762048E+01 +4.731403E+01 +1.403940E+02 +8.728060E+00 +4.774750E+00 +7.152809E+01 +3.200993E+02 +1.074636E+01 +7.270116E+00 +4.007351E+00 +1.010562E+00 +3.249303E+01 +6.611829E+01 +4.674806E+01 +1.375015E+02 +8.265491E+00 +4.296947E+00 +7.226174E+01 +3.267127E+02 +1.079228E+01 +7.340408E+00 +3.843888E+00 +9.273339E-01 +3.300391E+01 +6.823122E+01 cmfd indices 2.000000E+00 2.000000E+00 1.000000E+00 3.000000E+00 k cmfd -1.026473E+00 -1.024183E+00 -1.023151E+00 -1.025047E+00 -1.019801E+00 -1.020492E+00 -1.015249E+00 -1.016714E+00 -1.016047E+00 -1.019687E+00 -1.020955E+00 +1.011190E+00 +1.010705E+00 +1.014132E+00 +1.015900E+00 +1.019132E+00 +1.022616E+00 +1.023007E+00 +1.022300E+00 +1.014692E+00 +1.007628E+00 +1.006204E+00 cmfd entropy -1.999640E+00 -1.999556E+00 -1.999484E+00 -1.999718E+00 -1.999697E+00 -1.999657E+00 -1.999883E+00 -1.999902E+00 -1.999977E+00 -1.999977E+00 -1.999906E+00 +1.999167E+00 +1.999076E+00 +1.998507E+00 +1.997924E+00 +1.997814E+00 +1.997752E+00 +1.997882E+00 +1.998074E+00 +1.998109E+00 +1.998301E+00 +1.998581E+00 cmfd balance -8.10090E-04 -1.28103E-03 -7.97200E-04 -5.82188E-04 -7.20670E-04 -7.31475E-04 -5.71904E-04 -6.14057E-04 -6.00142E-04 -5.47870E-04 -3.53604E-04 +9.30124E-04 +2.56632E-04 +3.62598E-04 +4.17543E-04 +5.25720E-04 +4.90208E-04 +3.61304E-04 +2.24090E-04 +1.86602E-04 +1.78395E-04 +6.42497E-05 cmfd dominance ratio -3.977E-03 -4.018E-03 -3.950E-03 -3.866E-03 -3.840E-03 -3.888E-03 -3.867E-03 -3.896E-03 -3.924E-03 -3.885E-03 -3.913E-03 +4.194E-03 +4.234E-03 +4.149E-03 +4.209E-03 +4.185E-03 +4.197E-03 +4.175E-03 +4.105E-03 +4.056E-03 +4.122E-03 +4.113E-03 cmfd openmc source comparison -4.787501E-05 -4.450525E-05 -2.532345E-05 -3.844307E-05 -4.821504E-05 -4.840760E-05 -3.739246E-05 -3.957960E-05 -4.521480E-05 -4.072007E-05 -2.532636E-05 +2.078995E-05 +2.415218E-05 +3.311536E-05 +3.598613E-05 +3.156455E-05 +2.737017E-05 +2.468500E-05 +2.514215E-05 +1.601626E-05 +1.424027E-05 +4.201148E-06 cmfd source -2.486236E-01 -2.531628E-01 -2.460219E-01 -2.521918E-01 +2.558585E-01 +2.597562E-01 +2.529866E-01 +2.313986E-01 0.000000E+00 0.000000E+00 0.000000E+00 diff --git a/tests/regression_tests/cmfd_feed_rectlin/results_true.dat b/tests/regression_tests/cmfd_feed_rectlin/results_true.dat index 600b5d1528d..a98f3fbd383 100644 --- a/tests/regression_tests/cmfd_feed_rectlin/results_true.dat +++ b/tests/regression_tests/cmfd_feed_rectlin/results_true.dat @@ -1,149 +1,149 @@ k-combined: -1.160561E+00 1.029736E-02 +1.157362E+00 9.651846E-03 tally 1: -1.089904E+01 -1.193573E+01 -2.026534E+01 -4.113383E+01 -2.723584E+01 -7.440537E+01 -3.309956E+01 -1.101184E+02 -3.659327E+01 -1.341221E+02 -3.780158E+01 -1.430045E+02 -3.520883E+01 -1.241772E+02 -2.961801E+01 -8.784675E+01 -2.182029E+01 -4.781083E+01 -1.180347E+01 -1.399970E+01 +1.160989E+01 +1.351117E+01 +2.127132E+01 +4.540172E+01 +2.903242E+01 +8.450044E+01 +3.443549E+01 +1.188763E+02 +3.678332E+01 +1.355586E+02 +3.760088E+01 +1.418740E+02 +3.433077E+01 +1.181837E+02 +2.861986E+01 +8.231285E+01 +2.182277E+01 +4.792086E+01 +1.138713E+01 +1.304425E+01 tally 2: -8.794706E+00 -3.939413E+00 -6.131113E+00 -1.913116E+00 -3.265522E+01 -5.364042E+01 -2.304238E+01 -2.672742E+01 -2.250225E+01 -2.541491E+01 -1.601668E+01 -1.288405E+01 -5.525355E+01 -1.531915E+02 -3.929637E+01 -7.748545E+01 -3.222711E+01 -5.216630E+01 -2.285375E+01 -2.623641E+01 -7.051908E+01 -2.495413E+02 -5.010064E+01 -1.259701E+02 -3.728726E+01 -6.974440E+01 -2.652872E+01 -3.530919E+01 -3.747306E+01 -7.058235E+01 -2.681415E+01 -3.613314E+01 -7.296802E+01 -2.669214E+02 -5.202502E+01 -1.356733E+02 -3.333947E+01 -5.579355E+01 -2.361733E+01 -2.800800E+01 -5.785916E+01 -1.680561E+02 -4.093282E+01 -8.410711E+01 -2.377151E+01 -2.842464E+01 -1.681789E+01 -1.423646E+01 -3.422028E+01 -5.880493E+01 -2.415199E+01 -2.930240E+01 -8.890608E+00 -3.986356E+00 -6.140159E+00 -1.897764E+00 +8.861425E+00 +3.953963E+00 +6.090757E+00 +1.866235E+00 +3.309260E+01 +5.493482E+01 +2.330765E+01 +2.725350E+01 +2.295343E+01 +2.647375E+01 +1.629166E+01 +1.334081E+01 +5.714234E+01 +1.640293E+02 +4.054051E+01 +8.261123E+01 +3.331786E+01 +5.565082E+01 +2.366345E+01 +2.807637E+01 +7.034800E+01 +2.485209E+02 +5.001286E+01 +1.256087E+02 +3.651223E+01 +6.686058E+01 +2.606851E+01 +3.408881E+01 +3.729833E+01 +6.997492E+01 +2.657970E+01 +3.553107E+01 +7.212382E+01 +2.611253E+02 +5.124647E+01 +1.318756E+02 +3.304529E+01 +5.486643E+01 +2.347504E+01 +2.771353E+01 +5.711252E+01 +1.640619E+02 +4.060288E+01 +8.298231E+01 +2.384078E+01 +2.855909E+01 +1.684046E+01 +1.426180E+01 +3.329487E+01 +5.573544E+01 +2.349871E+01 +2.776487E+01 +8.676886E+00 +3.814132E+00 +5.980976E+00 +1.815143E+00 tally 3: -5.925339E+00 -1.788596E+00 -3.912632E-01 -8.061838E-03 -2.215544E+01 -2.471404E+01 -1.465191E+00 -1.092622E-01 -1.542988E+01 -1.195626E+01 -1.022876E+00 -5.356825E-02 -3.792029E+01 -7.218149E+01 -2.370476E+00 -2.839465E-01 -2.200154E+01 -2.432126E+01 -1.360836E+00 -9.402424E-02 -4.824980E+01 -1.168447E+02 -3.124366E+00 -4.907460E-01 -2.557420E+01 -3.282066E+01 -1.725855E+00 -1.519830E-01 -2.577963E+01 -3.341077E+01 -1.654042E+00 -1.386845E-01 -5.008220E+01 -1.257610E+02 -3.223857E+00 -5.237360E-01 -2.273380E+01 -2.595325E+01 -1.438369E+00 -1.050840E-01 -3.938822E+01 -7.789691E+01 -2.648324E+00 -3.559703E-01 -1.623604E+01 -1.327214E+01 -1.058882E+00 -5.680630E-02 -2.325730E+01 -2.717892E+01 -1.584276E+00 -1.275868E-01 -5.937929E+00 -1.774847E+00 -3.866918E-01 -7.994353E-03 +5.847135E+00 +1.719612E+00 +3.960040E-01 +8.297721E-03 +2.245534E+01 +2.530222E+01 +1.468678E+00 +1.094031E-01 +1.571194E+01 +1.240597E+01 +1.004169E+00 +5.156464E-02 +3.901605E+01 +7.652715E+01 +2.648696E+00 +3.571840E-01 +2.275978E+01 +2.597555E+01 +1.456067E+00 +1.075035E-01 +4.821184E+01 +1.167725E+02 +3.105774E+00 +4.859873E-01 +2.519281E+01 +3.183845E+01 +1.595498E+00 +1.292133E-01 +2.560583E+01 +3.297409E+01 +1.673871E+00 +1.429651E-01 +4.930025E+01 +1.220909E+02 +3.206604E+00 +5.220919E-01 +2.255825E+01 +2.560134E+01 +1.422870E+00 +1.027151E-01 +3.910818E+01 +7.698933E+01 +2.568903E+00 +3.333385E-01 +1.620292E+01 +1.321173E+01 +1.068678E+00 +5.798793E-02 +2.264343E+01 +2.578648E+01 +1.503553E+00 +1.158411E-01 +5.751110E+00 +1.676910E+00 +3.450582E-01 +6.411784E-03 tally 4: -3.063235E+00 -4.714106E-01 +3.051764E+00 +4.671879E-01 0.000000E+00 0.000000E+00 -1.425705E+00 -1.043616E-01 -4.355515E+00 -9.538346E-01 +1.407008E+00 +1.004485E-01 +4.354708E+00 +9.506434E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -160,14 +160,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -4.355515E+00 -9.538346E-01 -1.425705E+00 -1.043616E-01 -3.859174E+00 -7.519499E-01 -6.350310E+00 -2.024214E+00 +4.354708E+00 +9.506434E-01 +1.407008E+00 +1.004485E-01 +3.852730E+00 +7.498016E-01 +6.382605E+00 +2.043123E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -184,14 +184,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -6.350310E+00 -2.024214E+00 -3.859174E+00 -7.519499E-01 -4.993073E+00 -1.258264E+00 -7.194275E+00 -2.596886E+00 +6.382605E+00 +2.043123E+00 +3.852730E+00 +7.498016E-01 +5.061607E+00 +1.288306E+00 +7.281209E+00 +2.659444E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -208,14 +208,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -7.194275E+00 -2.596886E+00 -4.993073E+00 -1.258264E+00 -6.786617E+00 -2.312259E+00 -8.306978E+00 -3.459668E+00 +7.281209E+00 +2.659444E+00 +5.061607E+00 +1.288306E+00 +7.096602E+00 +2.527474E+00 +8.632232E+00 +3.736665E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -232,14 +232,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -8.306978E+00 -3.459668E+00 -6.786617E+00 -2.312259E+00 -7.603410E+00 -2.905228E+00 -8.791222E+00 -3.882935E+00 +8.632232E+00 +3.736665E+00 +7.096602E+00 +2.527474E+00 +7.759456E+00 +3.019026E+00 +8.968687E+00 +4.037738E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -256,14 +256,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -8.791222E+00 -3.882935E+00 -7.603410E+00 -2.905228E+00 -8.867113E+00 -3.938503E+00 -9.302808E+00 -4.340042E+00 +8.968687E+00 +4.037738E+00 +7.759456E+00 +3.019026E+00 +8.749025E+00 +3.839161E+00 +9.126289E+00 +4.176440E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -280,14 +280,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -9.302808E+00 -4.340042E+00 -8.867113E+00 -3.938503E+00 -9.270113E+00 -4.306513E+00 -9.263471E+00 -4.302184E+00 +9.126289E+00 +4.176440E+00 +8.749025E+00 +3.839161E+00 +9.259277E+00 +4.304020E+00 +9.165726E+00 +4.216173E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -304,14 +304,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -9.263471E+00 -4.302184E+00 -9.270113E+00 -4.306513E+00 -9.348712E+00 -4.388570E+00 -8.977713E+00 -4.047349E+00 +9.165726E+00 +4.216173E+00 +9.259277E+00 +4.304020E+00 +9.433925E+00 +4.473551E+00 +8.910566E+00 +3.991458E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -328,14 +328,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -8.977713E+00 -4.047349E+00 -9.348712E+00 -4.388570E+00 -9.234380E+00 -4.280666E+00 -8.036978E+00 -3.239853E+00 +8.910566E+00 +3.991458E+00 +9.433925E+00 +4.473551E+00 +9.099397E+00 +4.154062E+00 +7.770126E+00 +3.029928E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -352,14 +352,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -8.036978E+00 -3.239853E+00 -9.234380E+00 -4.280666E+00 -8.713633E+00 -3.808850E+00 -7.160878E+00 -2.572915E+00 +7.770126E+00 +3.029928E+00 +9.099397E+00 +4.154062E+00 +8.575842E+00 +3.694998E+00 +6.934243E+00 +2.418570E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -376,14 +376,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -7.160878E+00 -2.572915E+00 -8.713633E+00 -3.808850E+00 -7.378538E+00 -2.732122E+00 -5.145728E+00 -1.329190E+00 +6.934243E+00 +2.418570E+00 +8.575842E+00 +3.694998E+00 +7.437526E+00 +2.780629E+00 +5.136923E+00 +1.331553E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -400,14 +400,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -5.145728E+00 -1.329190E+00 -7.378538E+00 -2.732122E+00 -6.660125E+00 -2.228506E+00 -4.087925E+00 -8.435381E-01 +5.136923E+00 +1.331553E+00 +7.437526E+00 +2.780629E+00 +6.648582E+00 +2.216687E+00 +4.050691E+00 +8.279789E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -424,14 +424,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -4.087925E+00 -8.435381E-01 -6.660125E+00 -2.228506E+00 -4.466295E+00 -1.002320E+00 -1.468481E+00 -1.099604E-01 +4.050691E+00 +8.279789E-01 +6.648582E+00 +2.216687E+00 +4.390906E+00 +9.686686E-01 +1.391624E+00 +9.861955E-02 0.000000E+00 0.000000E+00 0.000000E+00 @@ -448,12 +448,12 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -1.468481E+00 -1.099604E-01 -4.466295E+00 -1.002320E+00 -3.139355E+00 -4.955456E-01 +1.391624E+00 +9.861955E-02 +4.390906E+00 +9.686686E-01 +3.113477E+00 +4.874296E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -473,164 +473,164 @@ tally 4: 0.000000E+00 0.000000E+00 tally 5: -5.925339E+00 -1.788596E+00 -8.954773E-01 -4.217679E-02 -2.215054E+01 -2.470273E+01 -2.944747E+00 -4.443186E-01 -1.542658E+01 -1.195085E+01 -1.950262E+00 -1.946340E-01 -3.791137E+01 -7.214779E+01 -4.955741E+00 -1.254830E+00 -2.200054E+01 -2.431894E+01 -3.031040E+00 -4.685971E-01 -4.823946E+01 -1.167941E+02 -6.226321E+00 -1.956308E+00 -2.556930E+01 -3.280828E+01 -3.582546E+00 -6.505962E-01 -2.577249E+01 -3.339204E+01 -3.316825E+00 -5.676842E-01 -5.007249E+01 -1.257124E+02 -6.462364E+00 -2.120157E+00 -2.273285E+01 -2.595106E+01 -2.960964E+00 -4.496277E-01 -3.937362E+01 -7.783849E+01 -5.339336E+00 -1.449257E+00 -1.623315E+01 -1.326746E+01 -2.289661E+00 -2.698618E-01 -2.325250E+01 -2.716827E+01 -3.253010E+00 -5.413837E-01 -5.937929E+00 -1.774847E+00 -9.208589E-01 -4.421403E-02 +5.846103E+00 +1.718959E+00 +8.952028E-01 +4.184063E-02 +2.245226E+01 +2.529524E+01 +3.092645E+00 +4.848878E-01 +1.571095E+01 +1.240426E+01 +2.178244E+00 +2.438479E-01 +3.900638E+01 +7.648853E+01 +5.265574E+00 +1.401505E+00 +2.275895E+01 +2.597348E+01 +3.067909E+00 +4.810969E-01 +4.820213E+01 +1.167232E+02 +6.403602E+00 +2.070034E+00 +2.519091E+01 +3.183360E+01 +3.463531E+00 +6.097568E-01 +2.560388E+01 +3.296889E+01 +3.456252E+00 +6.101655E-01 +4.929539E+01 +1.220666E+02 +6.629094E+00 +2.223366E+00 +2.255498E+01 +2.559363E+01 +2.833426E+00 +4.150907E-01 +3.909813E+01 +7.694976E+01 +5.582222E+00 +1.584757E+00 +1.620082E+01 +1.320814E+01 +2.282196E+00 +2.703156E-01 +2.263498E+01 +2.576758E+01 +3.162736E+00 +5.145038E-01 +5.750110E+00 +1.676357E+00 +9.181679E-01 +4.562885E-02 cmfd indices 1.400000E+01 1.000000E+00 1.000000E+00 1.000000E+00 k cmfd -1.125528E+00 -1.145509E+00 -1.158948E+00 -1.171983E+00 -1.180649E+00 -1.184072E+00 -1.188112E+00 -1.183095E+00 -1.182269E+00 -1.175467E+00 -1.175184E+00 -1.172637E+00 -1.171593E+00 -1.175439E+00 -1.174650E+00 -1.176474E+00 +1.166740E+00 +1.184008E+00 +1.166534E+00 +1.155559E+00 +1.164960E+00 +1.163229E+00 +1.165897E+00 +1.170104E+00 +1.170207E+00 +1.168091E+00 +1.170940E+00 +1.174589E+00 +1.174609E+00 +1.171505E+00 +1.174456E+00 +1.178370E+00 cmfd entropy -3.607059E+00 -3.604890E+00 -3.601329E+00 -3.597776E+00 -3.597360E+00 -3.597387E+00 -3.595379E+00 -3.596995E+00 -3.600901E+00 -3.601832E+00 -3.601426E+00 -3.604521E+00 -3.602848E+00 -3.603875E+00 -3.605213E+00 -3.606699E+00 +3.594757E+00 +3.587018E+00 +3.590385E+00 +3.595101E+00 +3.592151E+00 +3.600294E+00 +3.602102E+00 +3.604941E+00 +3.605897E+00 +3.604880E+00 +3.601658E+00 +3.602551E+00 +3.600160E+00 +3.604540E+00 +3.604094E+00 +3.602509E+00 cmfd balance -4.46212E-03 -4.66648E-03 -5.04274E-03 -5.21553E-03 -3.92498E-03 -2.97185E-03 -2.79785E-03 -2.66951E-03 -2.17472E-03 -1.98009E-03 -1.77035E-03 -1.51281E-03 -1.52807E-03 -1.33341E-03 -1.18155E-03 -1.07752E-03 +5.52960E-03 +5.42154E-03 +3.62152E-03 +2.92850E-03 +4.08642E-03 +2.07444E-03 +2.03704E-03 +2.06886E-03 +2.09646E-03 +1.94256E-03 +2.02728E-03 +1.89830E-03 +1.83910E-03 +1.48140E-03 +1.47034E-03 +1.64452E-03 cmfd dominance ratio -6.136E-01 -6.127E-01 -6.137E-01 -6.102E-01 -6.067E-01 -6.061E-01 -6.031E-01 6.046E-01 -6.071E-01 -6.089E-01 -6.073E-01 -6.080E-01 -6.080E-01 +6.015E-01 +6.059E-01 +6.060E-01 +6.061E-01 +6.109E-01 +6.110E-01 +6.108E-01 +6.120E-01 +6.124E-01 +6.109E-01 6.090E-01 -6.092E-01 -6.094E-01 +6.116E-01 +6.137E-01 +6.117E-01 +6.131E-01 cmfd openmc source comparison -1.043027E-02 -1.278226E-02 -1.184867E-02 -1.017186E-02 -1.099696E-02 -7.955341E-03 -8.360344E-03 -6.875508E-03 -4.824018E-03 -4.915363E-03 -5.371647E-03 -4.593100E-03 -4.894955E-03 -4.928253E-03 -4.292171E-03 -4.018545E-03 +1.035187E-02 +9.394886E-03 +6.879487E-03 +7.236029E-03 +6.543528E-03 +3.600620E-03 +2.859638E-03 +2.230047E-03 +2.180643E-03 +1.638534E-03 +1.764349E-03 +1.621487E-03 +1.221762E-03 +1.626297E-03 +1.951813E-03 +9.584126E-04 cmfd source -1.600876E-02 -6.000305E-02 -4.248071E-02 -9.935789E-02 -5.768092E-02 -1.338593E-01 -7.417398E-02 -7.102984E-02 -1.382563E-01 -6.181889E-02 -1.142473E-01 -4.571447E-02 -6.864655E-02 -1.672206E-02 +1.677059E-02 +6.229453E-02 +4.278394E-02 +1.134852E-01 +6.231020E-02 +1.327286E-01 +6.808362E-02 +7.130954E-02 +1.362127E-01 +6.048013E-02 +1.094460E-01 +4.546687E-02 +6.403310E-02 +1.459510E-02 diff --git a/tests/regression_tests/cmfd_feed_ref_d/results_true.dat b/tests/regression_tests/cmfd_feed_ref_d/results_true.dat index 9cc26b8cff8..34cde1a46e6 100644 --- a/tests/regression_tests/cmfd_feed_ref_d/results_true.dat +++ b/tests/regression_tests/cmfd_feed_ref_d/results_true.dat @@ -1,117 +1,117 @@ k-combined: -1.167869E+00 7.492916E-03 +1.162249E+00 5.812620E-03 tally 1: -1.146821E+01 -1.318787E+01 -2.161476E+01 -4.685066E+01 -2.951084E+01 -8.733116E+01 -3.521523E+01 -1.242762E+02 -3.774181E+01 -1.426460E+02 -3.727924E+01 -1.391162E+02 -3.377236E+01 -1.143877E+02 -2.904590E+01 -8.453427E+01 -2.090941E+01 -4.384824E+01 -1.078680E+01 -1.168172E+01 +1.153831E+01 +1.338142E+01 +2.155552E+01 +4.659071E+01 +2.813997E+01 +7.941672E+01 +3.270996E+01 +1.073664E+02 +3.639852E+01 +1.329148E+02 +3.729637E+01 +1.393474E+02 +3.443129E+01 +1.186461E+02 +2.832690E+01 +8.040998E+01 +2.177527E+01 +4.771147E+01 +1.146822E+01 +1.328252E+01 tally 2: -1.136805E+00 -1.292326E+00 -7.987282E-01 -6.379667E-01 -2.266961E+00 -5.139112E+00 -1.613498E+00 -2.603376E+00 -3.046379E+00 -9.280427E+00 -2.182480E+00 -4.763219E+00 -3.568101E+00 -1.273134E+01 -2.532478E+00 -6.413444E+00 -3.989532E+00 -1.591637E+01 -2.848319E+00 -8.112921E+00 -3.853139E+00 -1.484668E+01 -2.718497E+00 -7.390223E+00 -3.478134E+00 -1.209742E+01 -2.467279E+00 -6.087465E+00 -2.952214E+00 -8.715569E+00 -2.103257E+00 -4.423688E+00 -1.917446E+00 -3.676599E+00 -1.378361E+00 -1.899878E+00 -1.048230E+00 -1.098785E+00 -7.511876E-01 -5.642828E-01 +1.024353E+00 +1.049299E+00 +6.991057E-01 +4.887487E-01 +2.200432E+00 +4.841901E+00 +1.561655E+00 +2.438768E+00 +2.910400E+00 +8.470426E+00 +2.095155E+00 +4.389674E+00 +3.466006E+00 +1.201320E+01 +2.480456E+00 +6.152662E+00 +3.711781E+00 +1.377732E+01 +2.646019E+00 +7.001418E+00 +3.953648E+00 +1.563133E+01 +2.832759E+00 +8.024524E+00 +3.597870E+00 +1.294467E+01 +2.555396E+00 +6.530048E+00 +2.860871E+00 +8.184585E+00 +2.032282E+00 +4.130169E+00 +2.006740E+00 +4.027007E+00 +1.408150E+00 +1.982886E+00 +1.035163E+00 +1.071562E+00 +7.084068E-01 +5.018402E-01 tally 3: -7.701212E-01 -5.930866E-01 -4.481580E-02 -2.008456E-03 -1.547321E+00 -2.394203E+00 -1.226538E-01 -1.504395E-02 -2.106393E+00 -4.436893E+00 -1.450617E-01 -2.104289E-02 -2.437675E+00 -5.942260E+00 -1.521379E-01 -2.314593E-02 -2.754657E+00 -7.588135E+00 -1.745458E-01 -3.046622E-02 -2.623856E+00 -6.884619E+00 -1.851600E-01 -3.428423E-02 -2.376884E+00 -5.649579E+00 -1.615728E-01 -2.610576E-02 -2.021851E+00 -4.087882E+00 -1.533172E-01 -2.350617E-02 -1.333182E+00 -1.777374E+00 -7.076179E-02 -5.007231E-03 -7.258458E-01 -5.268521E-01 -3.656026E-02 -1.336653E-03 +6.713566E-01 +4.507196E-01 +5.581718E-02 +3.115557E-03 +1.508976E+00 +2.277009E+00 +1.139601E-01 +1.298690E-02 +2.035529E+00 +4.143377E+00 +1.221001E-01 +1.490843E-02 +2.385217E+00 +5.689262E+00 +1.500087E-01 +2.250260E-02 +2.546286E+00 +6.483574E+00 +1.546601E-01 +2.391975E-02 +2.726427E+00 +7.433407E+00 +1.686144E-01 +2.843081E-02 +2.466613E+00 +6.084179E+00 +1.558230E-01 +2.428079E-02 +1.951251E+00 +3.807379E+00 +1.197744E-01 +1.434590E-02 +1.347903E+00 +1.816842E+00 +9.419149E-02 +8.872037E-03 +6.840490E-01 +4.679231E-01 +5.000289E-02 +2.500289E-03 tally 4: -1.667426E-01 -2.780308E-02 +1.561665E-01 +2.438798E-02 0.000000E+00 0.000000E+00 -1.292556E-01 -1.670700E-02 -2.813401E-01 -7.915227E-02 +1.307011E-01 +1.708276E-02 +2.703168E-01 +7.307115E-02 0.000000E+00 0.000000E+00 0.000000E+00 @@ -128,14 +128,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -2.813401E-01 -7.915227E-02 -1.292556E-01 -1.670700E-02 -2.670582E-01 -7.132006E-02 -4.055365E-01 -1.644599E-01 +2.703168E-01 +7.307115E-02 +1.307011E-01 +1.708276E-02 +2.637619E-01 +6.957033E-02 +3.685390E-01 +1.358210E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -152,14 +152,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -4.055365E-01 -1.644599E-01 -2.670582E-01 -7.132006E-02 -3.848164E-01 -1.480837E-01 -4.809472E-01 -2.313102E-01 +3.685390E-01 +1.358210E-01 +2.637619E-01 +6.957033E-02 +3.887017E-01 +1.510890E-01 +4.439407E-01 +1.970834E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -176,14 +176,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -4.809472E-01 -2.313102E-01 -3.848164E-01 -1.480837E-01 -4.543959E-01 -2.064756E-01 -5.106174E-01 -2.607301E-01 +4.439407E-01 +1.970834E-01 +3.887017E-01 +1.510890E-01 +4.456116E-01 +1.985697E-01 +4.737035E-01 +2.243950E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -200,14 +200,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -5.106174E-01 -2.607301E-01 -4.543959E-01 -2.064756E-01 -4.543155E-01 -2.064026E-01 -4.626331E-01 -2.140294E-01 +4.737035E-01 +2.243950E-01 +4.456116E-01 +1.985697E-01 +4.760577E-01 +2.266309E-01 +4.703245E-01 +2.212052E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -224,14 +224,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -4.626331E-01 -2.140294E-01 -4.543155E-01 -2.064026E-01 -4.827763E-01 -2.330729E-01 -4.442611E-01 -1.973679E-01 +4.703245E-01 +2.212052E-01 +4.760577E-01 +2.266309E-01 +4.878056E-01 +2.379543E-01 +4.373120E-01 +1.912418E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -248,14 +248,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -4.442611E-01 -1.973679E-01 -4.827763E-01 -2.330729E-01 -4.630415E-01 -2.144074E-01 -3.886521E-01 -1.510505E-01 +4.373120E-01 +1.912418E-01 +4.878056E-01 +2.379543E-01 +4.262194E-01 +1.816630E-01 +3.334152E-01 +1.111657E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -272,14 +272,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -3.886521E-01 -1.510505E-01 -4.630415E-01 -2.144074E-01 -3.535862E-01 -1.250232E-01 -2.530293E-01 -6.402384E-02 +3.334152E-01 +1.111657E-01 +4.262194E-01 +1.816630E-01 +3.560156E-01 +1.267471E-01 +2.409954E-01 +5.807879E-02 0.000000E+00 0.000000E+00 0.000000E+00 @@ -296,14 +296,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -2.530293E-01 -6.402384E-02 -3.535862E-01 -1.250232E-01 -2.465508E-01 -6.078730E-02 -1.197139E-01 -1.433141E-02 +2.409954E-01 +5.807879E-02 +3.560156E-01 +1.267471E-01 +2.646501E-01 +7.003965E-02 +1.327244E-01 +1.761576E-02 0.000000E+00 0.000000E+00 0.000000E+00 @@ -320,12 +320,12 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -1.197139E-01 -1.433141E-02 -2.465508E-01 -6.078730E-02 -1.369614E-01 -1.875841E-02 +1.327244E-01 +1.761576E-02 +2.646501E-01 +7.003965E-02 +1.480567E-01 +2.192079E-02 0.000000E+00 0.000000E+00 0.000000E+00 @@ -345,119 +345,119 @@ tally 4: 0.000000E+00 0.000000E+00 tally 5: -7.701212E-01 -5.930866E-01 -1.386254E-01 -1.921700E-02 -1.547321E+00 -2.394203E+00 -2.630318E-01 -6.918571E-02 -2.106393E+00 -4.436893E+00 -2.807911E-01 -7.884363E-02 -2.435870E+00 -5.933464E+00 -3.322093E-01 -1.103630E-01 -2.753652E+00 -7.582597E+00 -3.825961E-01 -1.463797E-01 -2.623856E+00 -6.884619E+00 -3.888719E-01 -1.512213E-01 -2.376884E+00 -5.649579E+00 -3.196211E-01 -1.021576E-01 -2.021851E+00 -4.087882E+00 -2.897873E-01 -8.397667E-02 -1.333182E+00 -1.777374E+00 -1.627096E-01 -2.647441E-02 -7.258458E-01 -5.268521E-01 -9.348575E-02 -8.739586E-03 +6.713566E-01 +4.507196E-01 +9.805793E-02 +9.615358E-03 +1.508976E+00 +2.277009E+00 +1.968348E-01 +3.874394E-02 +2.032573E+00 +4.131353E+00 +2.120922E-01 +4.498309E-02 +2.385217E+00 +5.689262E+00 +2.863031E-01 +8.196946E-02 +2.545200E+00 +6.478041E+00 +3.278920E-01 +1.075131E-01 +2.726427E+00 +7.433407E+00 +3.770758E-01 +1.421861E-01 +2.466613E+00 +6.084179E+00 +3.593230E-01 +1.291130E-01 +1.951251E+00 +3.807379E+00 +2.474112E-01 +6.121229E-02 +1.347903E+00 +1.816842E+00 +2.127109E-01 +4.524594E-02 +6.823620E-01 +4.656179E-01 +1.249863E-01 +1.562159E-02 cmfd indices 1.000000E+01 1.000000E+00 1.000000E+00 1.000000E+00 k cmfd -1.149087E+00 -1.156777E+00 -1.158641E+00 -1.159507E+00 -1.156564E+00 -1.160257E+00 -1.150344E+00 -1.149854E+00 -1.151616E+00 -1.164575E+00 -1.174683E+00 +1.181365E+00 +1.176693E+00 +1.161946E+00 +1.163565E+00 +1.163043E+00 +1.169908E+00 +1.149155E+00 +1.142379E+00 +1.152957E+00 +1.137602E+00 +1.141883E+00 cmfd entropy -3.216202E+00 -3.228703E+00 -3.220414E+00 -3.214361E+00 -3.215642E+00 -3.213607E+00 -3.212862E+00 -3.213128E+00 -3.213189E+00 -3.205465E+00 -3.202859E+00 +3.246422E+00 +3.246496E+00 +3.252238E+00 +3.240920E+00 +3.237606E+00 +3.234301E+00 +3.234103E+00 +3.229918E+00 +3.226983E+00 +3.221321E+00 +3.223622E+00 cmfd balance -3.08825E-03 -1.42554E-03 -1.21448E-03 -1.17859E-03 -1.06034E-03 -9.30949E-04 -1.35713E-03 -1.13694E-03 -1.14938E-03 -1.29296E-03 -1.46518E-03 +4.18486E-03 +1.72126E-03 +1.10906E-03 +1.88158E-03 +1.31626E-03 +1.30818E-03 +1.77315E-03 +2.16148E-03 +1.67789E-03 +2.31333E-03 +1.94932E-03 cmfd dominance ratio -5.503E-01 -5.596E-01 5.505E-01 -5.471E-01 -5.468E-01 -5.427E-01 -5.421E-01 -5.412E-01 -5.389E-01 -5.371E-01 -5.329E-01 +5.580E-01 +5.610E-01 +5.526E-01 +5.519E-01 +5.499E-01 +5.504E-01 +5.504E-01 +5.475E-01 +5.465E-01 +5.477E-01 cmfd openmc source comparison -1.571006E-02 -6.945629E-03 -6.838511E-03 -6.183655E-03 -5.138825E-03 -4.362701E-03 -5.558586E-03 -4.188314E-03 -1.837101E-03 -2.737321E-03 -2.529244E-03 +1.902234E-03 +4.110960E-03 +2.452031E-03 +2.337951E-03 +1.838979E-03 +3.138637E-03 +2.684401E-03 +2.912891E-03 +2.823494E-03 +6.391584E-03 +5.904139E-03 cmfd source -4.240947E-02 -8.226746E-02 -1.180848E-01 -1.328470E-01 -1.412449E-01 -1.424870E-01 -1.269297E-01 -1.096476E-01 -6.953001E-02 -3.455212E-02 +4.488002E-02 +8.895136E-02 +1.085930E-01 +1.229651E-01 +1.330479E-01 +1.497140E-01 +1.309102E-01 +1.028556E-01 +7.738878E-02 +4.069397E-02 diff --git a/tests/regression_tests/cmfd_feed_rolling_window/results_true.dat b/tests/regression_tests/cmfd_feed_rolling_window/results_true.dat index 5ff4a132fd6..2e31b35bab5 100644 --- a/tests/regression_tests/cmfd_feed_rolling_window/results_true.dat +++ b/tests/regression_tests/cmfd_feed_rolling_window/results_true.dat @@ -1,117 +1,117 @@ k-combined: -1.173626E+00 1.098719E-02 +1.158333E+00 1.402684E-02 tally 1: -1.101892E+01 -1.218768E+01 -2.036233E+01 -4.152577E+01 -2.937587E+01 -8.637268E+01 -3.502389E+01 -1.231700E+02 -3.804803E+01 -1.453948E+02 -3.822561E+01 -1.465677E+02 -3.456290E+01 -1.198651E+02 -2.904088E+01 -8.470264E+01 -2.111529E+01 -4.463713E+01 -1.147633E+01 -1.326012E+01 +1.169478E+01 +1.373162E+01 +2.192038E+01 +4.844559E+01 +2.913292E+01 +8.542569E+01 +3.446069E+01 +1.201782E+02 +3.624088E+01 +1.320213E+02 +3.569791E+01 +1.278170E+02 +3.340601E+01 +1.119165E+02 +2.908648E+01 +8.514603E+01 +2.175458E+01 +4.767916E+01 +1.171268E+01 +1.378033E+01 tally 2: -1.010478E+00 -1.021066E+00 -6.902031E-01 -4.763804E-01 -1.899891E+00 -3.609584E+00 -1.322615E+00 -1.749312E+00 -2.756419E+00 -7.597845E+00 -1.955934E+00 -3.825676E+00 -3.818740E+00 -1.458278E+01 -2.704746E+00 -7.315652E+00 -3.920857E+00 -1.537312E+01 -2.843144E+00 -8.083470E+00 -3.835060E+00 -1.470768E+01 -2.728705E+00 -7.445831E+00 -3.510590E+00 -1.232424E+01 -2.497237E+00 -6.236191E+00 -2.717388E+00 -7.384198E+00 -1.903638E+00 -3.623837E+00 -2.207863E+00 -4.874659E+00 -1.563588E+00 -2.444808E+00 -1.289027E+00 -1.661591E+00 -9.022714E-01 -8.140937E-01 +1.132414E+00 +1.282361E+00 +7.822980E-01 +6.119901E-01 +2.124428E+00 +4.513196E+00 +1.490832E+00 +2.222581E+00 +3.158472E+00 +9.975946E+00 +2.221665E+00 +4.935795E+00 +3.994786E+00 +1.595831E+01 +2.828109E+00 +7.998199E+00 +3.491035E+00 +1.218732E+01 +2.461223E+00 +6.057617E+00 +3.461784E+00 +1.198395E+01 +2.473337E+00 +6.117398E+00 +3.027132E+00 +9.163527E+00 +2.150455E+00 +4.624458E+00 +2.471217E+00 +6.106911E+00 +1.737168E+00 +3.017752E+00 +1.880969E+00 +3.538044E+00 +1.316574E+00 +1.733367E+00 +1.233103E+00 +1.520543E+00 +8.543318E-01 +7.298828E-01 tally 3: -6.593197E-01 -4.347024E-01 -5.216387E-02 -2.721069E-03 -1.266446E+00 -1.603885E+00 -8.298797E-02 -6.887003E-03 -1.888709E+00 -3.567222E+00 -1.398940E-01 -1.957033E-02 -2.616078E+00 -6.843867E+00 -1.588627E-01 -2.523735E-02 -2.733300E+00 -7.470927E+00 -1.944290E-01 -3.780262E-02 -2.643656E+00 -6.988917E+00 -1.612338E-01 -2.599633E-02 -2.410643E+00 -5.811199E+00 -1.754603E-01 -3.078631E-02 -1.838012E+00 -3.378288E+00 -1.126265E-01 -1.268473E-02 -1.500033E+00 -2.250100E+00 -1.102554E-01 -1.215626E-02 -8.750096E-01 -7.656418E-01 -6.757592E-02 -4.566505E-03 +7.540113E-01 +5.685330E-01 +6.367610E-02 +4.054646E-03 +1.436984E+00 +2.064922E+00 +8.961822E-02 +8.031425E-03 +2.132240E+00 +4.546449E+00 +1.356065E-01 +1.838913E-02 +2.726356E+00 +7.433016E+00 +1.780572E-01 +3.170438E-02 +2.379700E+00 +5.662970E+00 +1.544735E-01 +2.386206E-02 +2.383471E+00 +5.680933E+00 +1.568319E-01 +2.459624E-02 +2.047271E+00 +4.191318E+00 +1.662654E-01 +2.764418E-02 +1.673107E+00 +2.799287E+00 +1.132020E-01 +1.281468E-02 +1.271576E+00 +1.616906E+00 +7.546797E-02 +5.695415E-03 +8.249906E-01 +6.806094E-01 +5.660098E-02 +3.203671E-03 tally 4: -1.490605E-01 -2.221904E-02 +1.551630E-01 +2.407556E-02 0.000000E+00 0.000000E+00 -1.139233E-01 -1.297851E-02 -2.549497E-01 -6.499934E-02 +1.487992E-01 +2.214121E-02 +2.878091E-01 +8.283409E-02 0.000000E+00 0.000000E+00 0.000000E+00 @@ -128,14 +128,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -2.549497E-01 -6.499934E-02 -1.139233E-01 -1.297851E-02 -2.191337E-01 -4.801958E-02 -3.295187E-01 -1.085826E-01 +2.878091E-01 +8.283409E-02 +1.487992E-01 +2.214121E-02 +2.936596E-01 +8.623595E-02 +3.954149E-01 +1.563529E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -152,14 +152,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -3.295187E-01 -1.085826E-01 -2.191337E-01 -4.801958E-02 -3.872400E-01 -1.499548E-01 -4.595835E-01 -2.112170E-01 +3.954149E-01 +1.563529E-01 +2.936596E-01 +8.623595E-02 +3.991153E-01 +1.592930E-01 +4.758410E-01 +2.264247E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -176,14 +176,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -4.595835E-01 -2.112170E-01 -3.872400E-01 -1.499548E-01 -4.668106E-01 -2.179121E-01 -5.112307E-01 -2.613569E-01 +4.758410E-01 +2.264247E-01 +3.991153E-01 +1.592930E-01 +4.850882E-01 +2.353106E-01 +5.210840E-01 +2.715285E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -200,14 +200,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -5.112307E-01 -2.613569E-01 -4.668106E-01 -2.179121E-01 -4.716605E-01 -2.224636E-01 -4.916148E-01 -2.416851E-01 +5.210840E-01 +2.715285E-01 +4.850882E-01 +2.353106E-01 +4.790245E-01 +2.294645E-01 +4.570092E-01 +2.088574E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -224,14 +224,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -4.916148E-01 -2.416851E-01 -4.716605E-01 -2.224636E-01 -4.696777E-01 -2.205972E-01 -4.365150E-01 -1.905453E-01 +4.570092E-01 +2.088574E-01 +4.790245E-01 +2.294645E-01 +4.505886E-01 +2.030301E-01 +3.884038E-01 +1.508575E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -248,14 +248,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -4.365150E-01 -1.905453E-01 -4.696777E-01 -2.205972E-01 -4.179902E-01 -1.747158E-01 -3.350564E-01 -1.122628E-01 +3.884038E-01 +1.508575E-01 +4.505886E-01 +2.030301E-01 +3.986999E-01 +1.589616E-01 +3.220889E-01 +1.037412E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -272,14 +272,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -3.350564E-01 -1.122628E-01 -4.179902E-01 -1.747158E-01 -3.743487E-01 -1.401370E-01 -2.400661E-01 -5.763172E-02 +3.220889E-01 +1.037412E-01 +3.986999E-01 +1.589616E-01 +3.319083E-01 +1.101631E-01 +2.101261E-01 +4.415297E-02 0.000000E+00 0.000000E+00 0.000000E+00 @@ -296,14 +296,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -2.400661E-01 -5.763172E-02 -3.743487E-01 -1.401370E-01 -3.063657E-01 -9.385994E-02 -1.605078E-01 -2.576275E-02 +2.101261E-01 +4.415297E-02 +3.319083E-01 +1.101631E-01 +2.795459E-01 +7.814588E-02 +1.306499E-01 +1.706938E-02 0.000000E+00 0.000000E+00 0.000000E+00 @@ -320,12 +320,12 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -1.605078E-01 -2.576275E-02 -3.063657E-01 -9.385994E-02 -1.700639E-01 -2.892174E-02 +1.306499E-01 +1.706938E-02 +2.795459E-01 +7.814588E-02 +1.585507E-01 +2.513833E-02 0.000000E+00 0.000000E+00 0.000000E+00 @@ -345,119 +345,119 @@ tally 4: 0.000000E+00 0.000000E+00 tally 5: -6.593197E-01 -4.347024E-01 -1.314196E-01 -1.727112E-02 -1.266446E+00 -1.603885E+00 -2.002491E-01 -4.009970E-02 -1.888709E+00 -3.567222E+00 -2.444522E-01 -5.975686E-02 -2.616078E+00 -6.843867E+00 -3.234935E-01 -1.046481E-01 -2.733300E+00 -7.470927E+00 -3.309500E-01 -1.095279E-01 -2.643656E+00 -6.988917E+00 -4.025014E-01 -1.620074E-01 -2.410643E+00 -5.811199E+00 -3.050040E-01 -9.302747E-02 -1.838012E+00 -3.378288E+00 -2.800764E-01 -7.844279E-02 -1.500033E+00 -2.250100E+00 -2.324765E-01 -5.404531E-02 -8.750096E-01 -7.656418E-01 -1.256919E-01 -1.579844E-02 +7.530237E-01 +5.670447E-01 +1.178984E-01 +1.390004E-02 +1.436984E+00 +2.064922E+00 +1.458395E-01 +2.126916E-02 +2.132240E+00 +4.546449E+00 +2.950109E-01 +8.703141E-02 +2.726356E+00 +7.433016E+00 +3.397288E-01 +1.154157E-01 +2.379700E+00 +5.662970E+00 +3.113206E-01 +9.692053E-02 +2.383471E+00 +5.680933E+00 +3.455611E-01 +1.194125E-01 +2.047271E+00 +4.191318E+00 +2.910986E-01 +8.473841E-02 +1.673107E+00 +2.799287E+00 +2.381996E-01 +5.673907E-02 +1.270672E+00 +1.614606E+00 +1.795241E-01 +3.222889E-02 +8.249906E-01 +6.806094E-01 +1.201397E-01 +1.443354E-02 cmfd indices 1.000000E+01 1.000000E+00 1.000000E+00 1.000000E+00 k cmfd -1.166297E+00 -1.148237E+00 -1.162472E+00 -1.187078E+00 -1.188411E+00 -1.194879E+00 -1.216739E+00 -1.216829E+00 -1.196596E+00 -1.199223E+00 -1.211817E+00 +1.184474E+00 +1.188170E+00 +1.165127E+00 +1.135010E+00 +1.145439E+00 +1.158451E+00 +1.154420E+00 +1.179615E+00 +1.197843E+00 +1.181252E+00 +1.186316E+00 cmfd entropy -3.215349E+00 -3.225577E+00 -3.223357E+00 -3.208828E+00 -3.211072E+00 -3.206578E+00 -3.195799E+00 -3.198236E+00 -3.220074E+00 -3.230249E+00 -3.238015E+00 +3.243654E+00 +3.244091E+00 +3.249203E+00 +3.249952E+00 +3.245538E+00 +3.240838E+00 +3.238919E+00 +3.223131E+00 +3.216002E+00 +3.220324E+00 +3.224804E+00 cmfd balance -2.07468E-03 -2.39687E-03 -1.51020E-03 -2.07618E-03 -1.89367E-03 -2.00902E-03 -2.54856E-03 -2.43636E-03 -2.48527E-03 -3.07775E-03 -3.37967E-03 +4.21104E-03 +1.38052E-03 +1.34642E-03 +2.39255E-03 +2.07426E-03 +1.27927E-03 +2.26249E-03 +2.72103E-03 +2.71504E-03 +2.19156E-03 +1.91989E-03 cmfd dominance ratio -5.505E-01 -5.558E-01 -5.584E-01 -5.477E-01 -5.477E-01 -5.426E-01 -5.335E-01 -5.305E-01 -5.427E-01 +5.520E-01 +5.535E-01 +5.628E-01 +5.696E-01 +5.723E-01 +5.651E-01 +5.648E-01 +5.555E-01 +5.448E-01 +5.488E-01 5.484E-01 -5.465E-01 cmfd openmc source comparison -5.598628E-03 -1.162952E-02 -1.083004E-02 -1.037470E-02 -5.649750E-03 -5.137914E-03 -3.868209E-03 -1.208756E-02 -5.398131E-03 -8.119598E-03 -4.573105E-03 +1.713810E-03 +2.429503E-03 +4.526209E-03 +7.978149E-03 +3.320012E-03 +3.880041E-03 +1.580215E-02 +1.663452E-02 +1.878103E-02 +7.436342E-03 +3.724478E-03 cmfd source -4.219187E-02 -8.692096E-02 -1.061389E-01 -1.199181E-01 -1.377328E-01 -1.326161E-01 -1.345872E-01 -1.112871E-01 -7.569533E-02 -5.291175E-02 +4.688167E-02 +9.066303E-02 +1.150679E-01 +1.430247E-01 +1.370466E-01 +1.267615E-01 +1.244218E-01 +1.048774E-01 +7.083441E-02 +4.042108E-02 diff --git a/tests/regression_tests/cmfd_nofeed/results_true.dat b/tests/regression_tests/cmfd_nofeed/results_true.dat index 756aa457016..ea1a0230b93 100644 --- a/tests/regression_tests/cmfd_nofeed/results_true.dat +++ b/tests/regression_tests/cmfd_nofeed/results_true.dat @@ -1,117 +1,117 @@ k-combined: -1.172893E+00 8.095197E-03 +1.169143E+00 7.248013E-03 tally 1: -1.156995E+01 -1.347019E+01 -2.120053E+01 -4.531200E+01 -2.995993E+01 -8.997889E+01 -3.498938E+01 -1.226414E+02 -3.794510E+01 -1.442188E+02 -3.798115E+01 -1.446436E+02 -3.415954E+01 -1.171635E+02 -2.960329E+01 -8.785532E+01 -2.182231E+01 -4.782353E+01 -1.147379E+01 -1.321150E+01 +1.115130E+01 +1.249933E+01 +2.147608E+01 +4.643964E+01 +2.923697E+01 +8.598273E+01 +3.439175E+01 +1.189653E+02 +3.729169E+01 +1.395456E+02 +3.709975E+01 +1.380839E+02 +3.415420E+01 +1.168226E+02 +2.895696E+01 +8.419764E+01 +2.140382E+01 +4.646784E+01 +1.125483E+01 +1.275812E+01 tally 2: -2.345900E+01 -2.783672E+01 -1.627300E+01 -1.339749E+01 -4.127267E+01 -8.563716E+01 -2.934800E+01 -4.334439E+01 -5.742644E+01 -1.658158E+02 -4.092600E+01 -8.423842E+01 -6.740126E+01 -2.279288E+02 -4.796500E+01 -1.154602E+02 -7.340327E+01 -2.701349E+02 -5.235900E+01 -1.374186E+02 -7.387392E+01 -2.740829E+02 -5.277400E+01 -1.398425E+02 -6.733428E+01 -2.274414E+02 -4.800100E+01 -1.156206E+02 -5.794970E+01 -1.685421E+02 -4.124600E+01 -8.540686E+01 -4.257013E+01 -9.092401E+01 -3.014500E+01 -4.566369E+01 -2.300274E+01 -2.659051E+01 -1.613400E+01 -1.307448E+01 +2.275147E+01 +2.604974E+01 +1.587800E+01 +1.271197E+01 +4.191959E+01 +8.846441E+01 +2.966500E+01 +4.430669E+01 +5.699815E+01 +1.637359E+02 +4.037600E+01 +8.219353E+01 +6.656851E+01 +2.228703E+02 +4.718000E+01 +1.119941E+02 +7.334215E+01 +2.701014E+02 +5.223500E+01 +1.370726E+02 +7.394394E+01 +2.748295E+02 +5.273400E+01 +1.397334E+02 +6.931604E+01 +2.406234E+02 +4.949000E+01 +1.226886E+02 +5.799672E+01 +1.687992E+02 +4.142300E+01 +8.612116E+01 +4.320102E+01 +9.402138E+01 +3.068300E+01 +4.749148E+01 +2.295257E+01 +2.657057E+01 +1.606200E+01 +1.301453E+01 tally 3: -1.564900E+01 -1.239852E+01 -1.086873E+00 -6.047264E-02 -2.821700E+01 -4.006730E+01 -1.855830E+00 -1.755984E-01 -3.946300E+01 -7.833997E+01 -2.523142E+00 -3.210725E-01 -4.622500E+01 -1.072588E+02 -2.919735E+00 -4.340292E-01 -5.033400E+01 -1.270010E+02 -3.243022E+00 -5.318534E-01 -5.082400E+01 -1.297502E+02 -3.313898E+00 -5.546608E-01 -4.623700E+01 -1.072944E+02 -2.887766E+00 -4.203084E-01 -3.975000E+01 -7.934279E+01 -2.567158E+00 -3.333121E-01 -2.903500E+01 -4.237270E+01 -1.852070E+00 -1.733821E-01 -1.557800E+01 -1.219084E+01 -9.951884E-01 -5.121758E-02 +1.528200E+01 +1.177982E+01 +1.040687E+00 +5.586386E-02 +2.857900E+01 +4.113603E+01 +1.871515E+00 +1.774689E-01 +3.888800E+01 +7.626262E+01 +2.534433E+00 +3.274088E-01 +4.541400E+01 +1.037867E+02 +2.926509E+00 +4.319780E-01 +5.034500E+01 +1.273517E+02 +3.215813E+00 +5.215716E-01 +5.076100E+01 +1.295218E+02 +3.194424E+00 +5.165993E-01 +4.768100E+01 +1.138949E+02 +3.058255E+00 +4.732131E-01 +3.995800E+01 +8.015691E+01 +2.454286E+00 +3.044948E-01 +2.957000E+01 +4.412743E+01 +1.938963E+00 +1.908501E-01 +1.544000E+01 +1.202869E+01 +1.038073E+00 +5.487827E-02 tally 4: -3.111000E+00 -4.872850E-01 +3.086000E+00 +4.780160E-01 0.000000E+00 0.000000E+00 -2.794000E+00 -3.972060E-01 -5.520000E+00 -1.535670E+00 +2.739000E+00 +3.790990E-01 +5.478000E+00 +1.505928E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -128,14 +128,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -5.520000E+00 -1.535670E+00 -2.794000E+00 -3.972060E-01 -5.071000E+00 -1.305697E+00 -7.303000E+00 -2.685365E+00 +5.478000E+00 +1.505928E+00 +2.739000E+00 +3.790990E-01 +5.094000E+00 +1.310476E+00 +7.282000E+00 +2.669810E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -152,14 +152,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -7.303000E+00 -2.685365E+00 -5.071000E+00 -1.305697E+00 -7.015000E+00 -2.471981E+00 -8.545000E+00 -3.670539E+00 +7.282000E+00 +2.669810E+00 +5.094000E+00 +1.310476E+00 +6.987000E+00 +2.461137E+00 +8.487000E+00 +3.624153E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -176,14 +176,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -8.545000E+00 -3.670539E+00 -7.015000E+00 -2.471981E+00 -8.431000E+00 -3.570057E+00 -9.224000E+00 -4.268004E+00 +8.487000E+00 +3.624153E+00 +6.987000E+00 +2.461137E+00 +8.250000E+00 +3.421824E+00 +9.022000E+00 +4.088536E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -200,14 +200,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -9.224000E+00 -4.268004E+00 -8.431000E+00 -3.570057E+00 -9.217000E+00 -4.259749E+00 -9.305000E+00 -4.340149E+00 +9.022000E+00 +4.088536E+00 +8.250000E+00 +3.421824E+00 +9.300000E+00 +4.344142E+00 +9.262000E+00 +4.308946E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -224,14 +224,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -9.305000E+00 -4.340149E+00 -9.217000E+00 -4.259749E+00 -9.374000E+00 -4.415290E+00 -8.611000E+00 -3.718227E+00 +9.262000E+00 +4.308946E+00 +9.300000E+00 +4.344142E+00 +9.267000E+00 +4.310941E+00 +8.487000E+00 +3.613257E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -248,14 +248,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -8.611000E+00 -3.718227E+00 -9.374000E+00 -4.415290E+00 -8.515000E+00 -3.639945E+00 -7.056000E+00 -2.501658E+00 +8.487000E+00 +3.613257E+00 +9.267000E+00 +4.310941E+00 +8.682000E+00 +3.778194E+00 +7.123000E+00 +2.544345E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -272,14 +272,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -7.056000E+00 -2.501658E+00 -8.515000E+00 -3.639945E+00 -7.385000E+00 -2.737677E+00 -5.194000E+00 -1.356022E+00 +7.123000E+00 +2.544345E+00 +8.682000E+00 +3.778194E+00 +7.421000E+00 +2.773897E+00 +5.198000E+00 +1.369216E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -296,14 +296,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -5.194000E+00 -1.356022E+00 -7.385000E+00 -2.737677E+00 -5.436000E+00 -1.481654E+00 -2.756000E+00 -3.843860E-01 +5.198000E+00 +1.369216E+00 +7.421000E+00 +2.773897E+00 +5.567000E+00 +1.561013E+00 +2.763000E+00 +3.882750E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -320,12 +320,12 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -2.756000E+00 -3.843860E-01 -5.436000E+00 -1.481654E+00 -3.030000E+00 -4.643920E-01 +2.763000E+00 +3.882750E-01 +5.567000E+00 +1.561013E+00 +3.106000E+00 +4.853380E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -345,144 +345,144 @@ tally 4: 0.000000E+00 0.000000E+00 tally 5: -1.564300E+01 -1.238861E+01 -2.090227E+00 -2.262627E-01 -2.821400E+01 -4.005826E+01 -3.870834E+00 -7.614623E-01 -3.945400E+01 -7.830326E+01 -5.290557E+00 -1.422339E+00 -4.621500E+01 -1.072116E+02 -6.144745E+00 -1.903309E+00 -5.032900E+01 -1.269756E+02 -6.867524E+00 -2.373593E+00 -5.081000E+01 -1.296784E+02 -6.456283E+00 -2.130767E+00 -4.623000E+01 -1.072613E+02 -5.931623E+00 -1.776451E+00 -3.974500E+01 -7.932288E+01 -5.339603E+00 -1.456812E+00 -2.902800E+01 -4.235232E+01 -4.102240E+00 -8.519551E-01 -1.557800E+01 -1.219084E+01 -2.137954E+00 -2.347770E-01 +1.527700E+01 +1.177204E+01 +2.285003E+00 +2.661382E-01 +2.857200E+01 +4.111506E+01 +4.126099E+00 +8.737996E-01 +3.887800E+01 +7.622094E+01 +5.121343E+00 +1.333837E+00 +4.540600E+01 +1.037492E+02 +6.160114E+00 +1.913665E+00 +5.033400E+01 +1.272949E+02 +6.859861E+00 +2.384476E+00 +5.075800E+01 +1.295055E+02 +6.929393E+00 +2.443364E+00 +4.767500E+01 +1.138658E+02 +6.385463E+00 +2.080293E+00 +3.995300E+01 +8.013651E+01 +5.620641E+00 +1.603844E+00 +2.956200E+01 +4.410395E+01 +3.952701E+00 +7.877105E-01 +1.543500E+01 +1.202080E+01 +2.203583E+00 +2.512936E-01 cmfd indices 1.000000E+01 1.000000E+00 1.000000E+00 1.000000E+00 k cmfd -1.129918E+00 -1.148352E+00 -1.143137E+00 -1.145795E+00 -1.147285E+00 -1.148588E+00 -1.148151E+00 -1.162118E+00 -1.165380E+00 -1.162851E+00 -1.163379E+00 -1.166986E+00 -1.167838E+00 -1.171743E+00 -1.170398E+00 -1.169773E+00 +1.169107E+00 +1.173852E+00 +1.181921E+00 +1.187733E+00 +1.185766E+00 +1.177020E+00 +1.181200E+00 +1.180088E+00 +1.180676E+00 +1.174948E+00 +1.174167E+00 +1.174935E+00 +1.169912E+00 +1.169058E+00 +1.170366E+00 +1.169774E+00 cmfd entropy -3.224769E+00 -3.222795E+00 -3.221174E+00 -3.222164E+00 -3.221025E+00 -3.220967E+00 -3.223497E+00 -3.219213E+00 -3.221679E+00 -3.222084E+00 -3.222281E+00 -3.222540E+00 -3.224614E+00 -3.224193E+00 -3.224925E+00 -3.224367E+00 +3.207640E+00 +3.212075E+00 +3.215463E+00 +3.219545E+00 +3.225225E+00 +3.227103E+00 +3.229048E+00 +3.228263E+00 +3.229077E+00 +3.229932E+00 +3.229351E+00 +3.228195E+00 +3.228493E+00 +3.227823E+00 +3.225830E+00 +3.227270E+00 cmfd balance -3.90454E-03 -4.33180E-03 -3.77057E-03 -3.16391E-03 -3.11765E-03 -2.59886E-03 -2.81060E-03 -3.25473E-03 -2.68544E-03 -2.01716E-03 -1.89350E-03 -1.79159E-03 -1.51353E-03 -1.48514E-03 -1.50207E-03 -1.44045E-03 +4.88208E-03 +4.63702E-03 +3.41158E-03 +2.99755E-03 +2.78360E-03 +3.31542E-03 +2.64344E-03 +2.04609E-03 +1.84340E-03 +1.65450E-03 +1.70816E-03 +1.69952E-03 +1.51417E-03 +1.32738E-03 +1.41435E-03 +1.01462E-03 cmfd dominance ratio -5.539E-01 -5.522E-01 -5.491E-01 -5.511E-01 -5.506E-01 -5.523E-01 -5.523E-01 +5.467E-01 +5.468E-01 +5.448E-01 +5.457E-01 +5.457E-01 +5.485E-01 +5.500E-01 +5.497E-01 +5.495E-01 +5.499E-01 +5.502E-01 +5.485E-01 +5.492E-01 +5.483E-01 5.473E-01 -5.478E-01 -5.461E-01 -5.462E-01 -5.459E-01 -5.475E-01 -5.463E-01 5.466E-01 -5.469E-01 cmfd openmc source comparison -9.875240E-03 -1.119358E-02 -8.513903E-03 -7.728971E-03 -5.993771E-03 -5.837301E-03 -4.861789E-03 -5.624038E-03 -4.297229E-03 -4.029732E-03 -3.669197E-03 -3.598834E-03 -3.023310E-03 -3.347346E-03 -2.943658E-03 -2.764986E-03 +9.587418E-03 +7.785087E-03 +6.798967E-03 +5.947641E-03 +4.980801E-03 +4.272665E-03 +4.073759E-03 +4.305612E-03 +3.572759E-03 +3.785830E-03 +3.766828E-03 +3.495462E-03 +3.017281E-03 +2.857633E-03 +2.858606E-03 +2.449010E-03 cmfd source -4.561921E-02 -7.896381E-02 -1.084687E-01 -1.264057E-01 -1.408942E-01 -1.438180E-01 -1.247333E-01 -1.100896E-01 -7.897187E-02 -4.203556E-02 +4.390084E-02 +7.966902E-02 +1.087889E-01 +1.263915E-01 +1.394331E-01 +1.383156E-01 +1.319970E-01 +1.051339E-01 +8.248244E-02 +4.388774E-02 diff --git a/tests/regression_tests/cmfd_restart/results_true.dat b/tests/regression_tests/cmfd_restart/results_true.dat index f5f22d9acd1..1ef9624d4c6 100644 --- a/tests/regression_tests/cmfd_restart/results_true.dat +++ b/tests/regression_tests/cmfd_restart/results_true.dat @@ -1,117 +1,117 @@ k-combined: -1.164262E+00 9.207592E-03 +1.181723E+00 9.944883E-03 tally 1: -1.156972E+01 -1.339924E+01 -2.136306E+01 -4.567185E+01 -2.859527E+01 -8.195821E+01 -3.470754E+01 -1.207851E+02 -3.766403E+01 -1.422263E+02 -3.778821E+01 -1.432660E+02 -3.573197E+01 -1.278854E+02 -2.849979E+01 -8.135515E+01 -2.073803E+01 -4.303374E+01 -1.112117E+01 -1.242944E+01 +1.169899E+01 +1.373251E+01 +2.142380E+01 +4.605511E+01 +2.968085E+01 +8.838716E+01 +3.561418E+01 +1.271206E+02 +3.777783E+01 +1.428817E+02 +3.805832E+01 +1.450213E+02 +3.439836E+01 +1.184892E+02 +2.852438E+01 +8.161896E+01 +2.088423E+01 +4.376204E+01 +1.076670E+01 +1.168108E+01 tally 2: -2.388054E+01 -2.875255E+01 -1.667791E+01 -1.403426E+01 -4.224771E+01 -8.942109E+01 -2.993088E+01 -4.490335E+01 -5.689839E+01 -1.625557E+02 -4.043633E+01 -8.212299E+01 -6.764024E+01 -2.297126E+02 -4.807902E+01 -1.161468E+02 -7.314835E+01 -2.684645E+02 -5.203584E+01 -1.359261E+02 -7.375727E+01 -2.733105E+02 -5.252944E+01 -1.386205E+02 -6.909571E+01 -2.397721E+02 -4.922548E+01 -1.217465E+02 -5.685978E+01 -1.621746E+02 -4.051938E+01 -8.237277E+01 -4.185562E+01 -8.784067E+01 -2.983570E+01 -4.467414E+01 -2.238373E+01 -2.520356E+01 -1.566758E+01 -1.234103E+01 +2.321241E+01 +2.702156E+01 +1.620912E+01 +1.317752E+01 +4.197404E+01 +8.845008E+01 +2.982666E+01 +4.469221E+01 +5.810089E+01 +1.695857E+02 +4.134123E+01 +8.588866E+01 +6.982488E+01 +2.447068E+02 +4.966939E+01 +1.238763E+02 +7.428421E+01 +2.767613E+02 +5.287955E+01 +1.403163E+02 +7.447402E+01 +2.785012E+02 +5.324628E+01 +1.423393E+02 +6.895164E+01 +2.381937E+02 +4.916366E+01 +1.211701E+02 +5.679253E+01 +1.617881E+02 +4.043125E+01 +8.204061E+01 +4.218618E+01 +8.933666E+01 +2.978592E+01 +4.456592E+01 +2.196426E+01 +2.435867E+01 +1.525576E+01 +1.175879E+01 tally 3: -1.609520E+01 -1.307542E+01 -1.033429E+00 -5.510889E-02 -2.877073E+01 -4.149542E+01 -1.964219E+00 -1.954692E-01 -3.896816E+01 -7.629752E+01 -2.484053E+00 -3.103733E-01 -4.634285E+01 -1.079367E+02 -2.974750E+00 -4.468223E-01 -5.007964E+01 -1.259202E+02 -3.181802E+00 -5.103621E-01 -5.058915E+01 -1.286193E+02 -3.249442E+00 -5.337712E-01 -4.744464E+01 -1.131026E+02 -3.067644E+00 -4.736335E-01 -3.900632E+01 -7.634433E+01 -2.443552E+00 -3.028060E-01 -2.874166E+01 -4.146375E+01 -1.810421E+00 -1.671667E-01 -1.509222E+01 -1.145579E+01 -1.014919E+00 -5.391053E-02 +1.563788E+01 +1.226528E+01 +1.053289E+00 +5.666942E-02 +2.870755E+01 +4.139654E+01 +1.838017E+00 +1.710528E-01 +3.978616E+01 +7.955764E+01 +2.560657E+00 +3.334449E-01 +4.780385E+01 +1.147770E+02 +3.139243E+00 +4.967628E-01 +5.106650E+01 +1.308704E+02 +3.170056E+00 +5.078920E-01 +5.123992E+01 +1.318586E+02 +3.211706E+00 +5.205979E-01 +4.729862E+01 +1.121695E+02 +3.068662E+00 +4.749488E-01 +3.898816E+01 +7.630564E+01 +2.516911E+00 +3.199696E-01 +2.865357E+01 +4.125742E+01 +1.852314E+00 +1.741116E-01 +1.467340E+01 +1.088460E+01 +9.268633E-01 +4.450662E-02 tally 4: -3.148231E+00 -4.974555E-01 +3.029754E+00 +4.613561E-01 0.000000E+00 0.000000E+00 -2.805439E+00 -3.982239E-01 -5.574031E+00 -1.561105E+00 +2.832501E+00 +4.049252E-01 +5.517243E+00 +1.527794E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -128,14 +128,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -5.574031E+00 -1.561105E+00 -2.805439E+00 -3.982239E-01 -5.171038E+00 -1.344877E+00 -7.372031E+00 -2.725420E+00 +5.517243E+00 +1.527794E+00 +2.832501E+00 +4.049252E-01 +5.117178E+00 +1.316972E+00 +7.333303E+00 +2.701677E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -152,14 +152,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -7.372031E+00 -2.725420E+00 -5.171038E+00 -1.344877E+00 -6.946847E+00 -2.424850E+00 -8.496610E+00 -3.627542E+00 +7.333303E+00 +2.701677E+00 +5.117178E+00 +1.316972E+00 +7.248464E+00 +2.641591E+00 +8.817788E+00 +3.905530E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -176,14 +176,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -8.496610E+00 -3.627542E+00 -6.946847E+00 -2.424850E+00 -8.479501E+00 -3.607280E+00 -9.261869E+00 -4.305912E+00 +8.817788E+00 +3.905530E+00 +7.248464E+00 +2.641591E+00 +8.646465E+00 +3.749847E+00 +9.460948E+00 +4.495388E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -200,14 +200,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -9.261869E+00 -4.305912E+00 -8.479501E+00 -3.607280E+00 -9.232858E+00 -4.278432E+00 -9.306384E+00 -4.348594E+00 +9.460948E+00 +4.495388E+00 +8.646465E+00 +3.749847E+00 +9.379341E+00 +4.415049E+00 +9.278640E+00 +4.320720E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -224,14 +224,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -9.306384E+00 -4.348594E+00 -9.232858E+00 -4.278432E+00 -9.299764E+00 -4.347828E+00 -8.511976E+00 -3.639893E+00 +9.278640E+00 +4.320720E+00 +9.379341E+00 +4.415049E+00 +9.465746E+00 +4.498591E+00 +8.656146E+00 +3.760545E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -248,14 +248,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -8.511976E+00 -3.639893E+00 -9.299764E+00 -4.347828E+00 -8.726086E+00 -3.819567E+00 -7.147277E+00 -2.562747E+00 +8.656146E+00 +3.760545E+00 +9.465746E+00 +4.498591E+00 +8.589782E+00 +3.700308E+00 +6.996002E+00 +2.456935E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -272,14 +272,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -7.147277E+00 -2.562747E+00 -8.726086E+00 -3.819567E+00 -7.218790E+00 -2.612243E+00 -5.018287E+00 -1.263077E+00 +6.996002E+00 +2.456935E+00 +8.589782E+00 +3.700308E+00 +7.352050E+00 +2.714808E+00 +5.105164E+00 +1.312559E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -296,14 +296,14 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -5.018287E+00 -1.263077E+00 -7.218790E+00 -2.612243E+00 -5.443494E+00 -1.487018E+00 -2.732334E+00 -3.773047E-01 +5.105164E+00 +1.312559E+00 +7.352050E+00 +2.714808E+00 +5.442756E+00 +1.486776E+00 +2.697305E+00 +3.675580E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -320,12 +320,12 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -2.732334E+00 -3.773047E-01 -5.443494E+00 -1.487018E+00 -3.044773E+00 -4.655756E-01 +2.697305E+00 +3.675580E-01 +5.442756E+00 +1.486776E+00 +3.017025E+00 +4.571443E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -345,144 +345,144 @@ tally 4: 0.000000E+00 0.000000E+00 tally 5: -1.609029E+01 -1.306718E+01 -2.230601E+00 -2.559496E-01 -2.876780E+01 -4.148686E+01 -3.835952E+00 -7.456562E-01 -3.895738E+01 -7.625344E+01 -4.841024E+00 -1.197335E+00 -4.633595E+01 -1.079043E+02 -6.236821E+00 -1.963311E+00 -5.007472E+01 -1.258967E+02 -6.749745E+00 -2.297130E+00 -5.058336E+01 -1.285894E+02 -6.727612E+00 -2.315656E+00 -4.743869E+01 -1.130735E+02 -6.338193E+00 -2.042159E+00 -3.899838E+01 -7.631289E+01 -5.187573E+00 -1.359733E+00 -2.873434E+01 -4.144233E+01 -3.815610E+00 -7.367619E-01 -1.509020E+01 -1.145268E+01 -2.125767E+00 -2.341894E-01 +1.563588E+01 +1.226217E+01 +2.209027E+00 +2.507131E-01 +2.870034E+01 +4.137550E+01 +3.726620E+00 +7.021948E-01 +3.977762E+01 +7.952285E+01 +5.304975E+00 +1.427333E+00 +4.779747E+01 +1.147456E+02 +6.528302E+00 +2.151715E+00 +5.105366E+01 +1.308037E+02 +6.986782E+00 +2.467487E+00 +5.123380E+01 +1.318264E+02 +6.845633E+00 +2.383542E+00 +4.729296E+01 +1.121433E+02 +6.252695E+00 +1.977351E+00 +3.898236E+01 +7.628315E+01 +5.461495E+00 +1.528579E+00 +2.864576E+01 +4.123538E+01 +3.857301E+00 +7.581323E-01 +1.467047E+01 +1.088031E+01 +2.277024E+00 +2.679801E-01 cmfd indices 1.000000E+01 1.000000E+00 1.000000E+00 1.000000E+00 k cmfd -1.129918E+00 -1.143848E+00 -1.147976E+00 -1.151534E+00 -1.152378E+00 -1.148219E+00 -1.150402E+00 -1.154647E+00 -1.156159E+00 -1.160048E+00 -1.167441E+00 -1.168163E+00 -1.168629E+00 -1.164120E+00 -1.165051E+00 -1.169177E+00 +1.169107E+00 +1.175079E+00 +1.173912E+00 +1.175368E+00 +1.174026E+00 +1.181745E+00 +1.182261E+00 +1.183559E+00 +1.178691E+00 +1.179222E+00 +1.179017E+00 +1.172979E+00 +1.175043E+00 +1.173458E+00 +1.174152E+00 +1.171451E+00 cmfd entropy -3.224769E+00 -3.225945E+00 -3.227421E+00 -3.226174E+00 -3.224429E+00 -3.227049E+00 -3.230710E+00 -3.230315E+00 -3.226825E+00 -3.226655E+00 -3.226588E+00 -3.224155E+00 -3.223246E+00 -3.222640E+00 -3.223920E+00 -3.222838E+00 +3.207640E+00 +3.210547E+00 +3.212218E+00 +3.209573E+00 +3.211619E+00 +3.212126E+00 +3.213163E+00 +3.214288E+00 +3.215737E+00 +3.213677E+00 +3.214925E+00 +3.215612E+00 +3.216708E+00 +3.221454E+00 +3.219048E+00 +3.218387E+00 cmfd balance -3.90454E-03 -4.08089E-03 -3.46511E-03 -4.09535E-03 -2.62009E-03 -2.23559E-03 -2.54033E-03 -2.12799E-03 -2.25864E-03 -1.85766E-03 -1.49916E-03 -1.63471E-03 -1.48377E-03 -1.59800E-03 -1.37354E-03 -1.32853E-03 +4.88208E-03 +4.75139E-03 +3.15783E-03 +3.67091E-03 +2.99797E-03 +2.91060E-03 +2.06576E-03 +1.83482E-03 +1.56292E-03 +1.58659E-03 +2.32986E-03 +1.47376E-03 +1.46673E-03 +1.22627E-03 +1.31963E-03 +1.26456E-03 cmfd dominance ratio -5.539E-01 -5.537E-01 -5.536E-01 -5.515E-01 -5.512E-01 -5.514E-01 -5.518E-01 -5.507E-01 -5.500E-01 -5.497E-01 -5.477E-01 -5.461E-01 -5.444E-01 -5.445E-01 -5.454E-01 +5.467E-01 +5.453E-01 +5.458E-01 +5.436E-01 +5.442E-01 +5.406E-01 +5.401E-01 +5.413E-01 +4.995E-01 +5.396E-01 +5.409E-01 +5.414E-01 +5.423E-01 +5.456E-01 +5.442E-01 5.441E-01 cmfd openmc source comparison -9.875240E-03 -1.106163E-02 -9.847628E-03 -6.065921E-03 -5.772039E-03 -4.615656E-03 -4.244331E-03 -3.694299E-03 -3.545814E-03 -3.213063E-03 -3.467537E-03 -3.383489E-03 -3.697591E-03 -3.937358E-03 -3.369124E-03 -3.190359E-03 +9.587418E-03 +8.150978E-03 +6.677661E-03 +6.334727E-03 +5.153692E-03 +5.082964E-03 +4.633153E-03 +4.037383E-03 +3.528742E-03 +4.559089E-03 +3.517370E-03 +3.306117E-03 +2.913809E-03 +1.906045E-03 +1.932794E-03 +1.711341E-03 cmfd source -4.360494E-02 -8.397599E-02 -1.074181E-01 -1.294531E-01 -1.385611E-01 -1.407934E-01 -1.325191E-01 -1.044311E-01 -7.660359E-02 -4.263941E-02 +4.496492E-02 +7.869674E-02 +1.100280E-01 +1.354045E-01 +1.363339E-01 +1.380533E-01 +1.314512E-01 +1.077480E-01 +7.847306E-02 +3.884630E-02 diff --git a/tests/regression_tests/complex_cell/results_true.dat b/tests/regression_tests/complex_cell/results_true.dat index 3ce3d7cbc7f..ddedb07ff05 100644 --- a/tests/regression_tests/complex_cell/results_true.dat +++ b/tests/regression_tests/complex_cell/results_true.dat @@ -1,11 +1,11 @@ k-combined: -2.564169E-01 4.095378E-03 +2.603220E-01 1.429366E-03 tally 1: -2.607144E+00 -1.360414E+00 -2.681079E+00 -1.439354E+00 -9.627534E-01 -1.855496E-01 -1.123751E-01 +2.624819E+00 +1.378200E+00 +2.730035E+00 +1.492361E+00 +1.013707E+00 +2.055807E-01 +1.123257E-01 2.530233E-03 diff --git a/tests/regression_tests/confidence_intervals/results_true.dat b/tests/regression_tests/confidence_intervals/results_true.dat index 6a906d07e74..8ca25662499 100644 --- a/tests/regression_tests/confidence_intervals/results_true.dat +++ b/tests/regression_tests/confidence_intervals/results_true.dat @@ -1,5 +1,5 @@ k-combined: -2.759923E-01 6.988588E-03 +2.850178E-01 9.646334E-03 tally 1: -6.167984E+01 -4.772717E+02 +6.234169E+01 +4.884167E+02 diff --git a/tests/regression_tests/cpp_driver/results_true.dat b/tests/regression_tests/cpp_driver/results_true.dat index 26f07842cd4..09f188db6b7 100644 --- a/tests/regression_tests/cpp_driver/results_true.dat +++ b/tests/regression_tests/cpp_driver/results_true.dat @@ -1,13 +1,13 @@ k-combined: -1.953962E+00 1.828426E-02 +1.874924E+00 2.180236E-02 tally 1: -9.607953E+01 -1.031898E+03 -2.853683E+01 -9.085469E+01 -9.745011E+01 -1.058928E+03 -2.220665E+02 -5.496813E+03 -2.220665E+02 -5.496813E+03 +9.484447E+01 +1.002269E+03 +2.746252E+01 +8.406603E+01 +9.833099E+01 +1.076376E+03 +2.206380E+02 +5.417609E+03 +2.206380E+02 +5.417609E+03 diff --git a/tests/regression_tests/dagmc/external/results_true.dat b/tests/regression_tests/dagmc/external/results_true.dat index cda65693787..9a6b481b7f5 100644 --- a/tests/regression_tests/dagmc/external/results_true.dat +++ b/tests/regression_tests/dagmc/external/results_true.dat @@ -1,5 +1,5 @@ k-combined: -9.118190E-01 3.615552E-02 +1.083415E+00 5.991738E-02 tally 1: -8.430103E+00 -1.442878E+01 +8.862860E+00 +1.602117E+01 diff --git a/tests/regression_tests/dagmc/legacy/results_true.dat b/tests/regression_tests/dagmc/legacy/results_true.dat index cda65693787..9a6b481b7f5 100644 --- a/tests/regression_tests/dagmc/legacy/results_true.dat +++ b/tests/regression_tests/dagmc/legacy/results_true.dat @@ -1,5 +1,5 @@ k-combined: -9.118190E-01 3.615552E-02 +1.083415E+00 5.991738E-02 tally 1: -8.430103E+00 -1.442878E+01 +8.862860E+00 +1.602117E+01 diff --git a/tests/regression_tests/dagmc/refl/results_true.dat b/tests/regression_tests/dagmc/refl/results_true.dat index 533c62a9024..b49a4a7a499 100644 --- a/tests/regression_tests/dagmc/refl/results_true.dat +++ b/tests/regression_tests/dagmc/refl/results_true.dat @@ -1,5 +1,5 @@ k-combined: -2.035173E+00 3.967029E-02 +2.047107E+00 8.605767E-02 tally 1: -1.064492E+01 -2.301019E+01 +1.145034E+01 +2.636875E+01 diff --git a/tests/regression_tests/dagmc/universes/inputs_true.dat b/tests/regression_tests/dagmc/universes/inputs_true.dat index 2fa79d831e6..be1a17383c3 100644 --- a/tests/regression_tests/dagmc/universes/inputs_true.dat +++ b/tests/regression_tests/dagmc/universes/inputs_true.dat @@ -48,6 +48,14 @@ 100 10 5 + + + -10.0 -10.0 -24.0 10.0 10.0 24.0 + + + true + + false diff --git a/tests/regression_tests/dagmc/universes/results_true.dat b/tests/regression_tests/dagmc/universes/results_true.dat index 76cbc9db0cf..aacb7d1ab5a 100644 --- a/tests/regression_tests/dagmc/universes/results_true.dat +++ b/tests/regression_tests/dagmc/universes/results_true.dat @@ -1,13 +1,13 @@ k-combined: -9.887663E-01 1.510336E-02 +9.719586E-01 3.630894E-02 tally 1: -4.340758E+00 -4.265459E+00 -4.712319E+00 -4.654778E+00 -4.151897E+00 -3.588090E+00 -2.965925E+00 -1.852746E+00 +4.463288E+00 +4.136647E+00 +4.769631E+00 +4.622840E+00 +4.315273E+00 +3.871129E+00 +4.091804E+00 +3.582192E+00 0.000000E+00 0.000000E+00 diff --git a/tests/regression_tests/dagmc/universes/test.py b/tests/regression_tests/dagmc/universes/test.py index 057a7b0d477..d68c6b11cfa 100644 --- a/tests/regression_tests/dagmc/universes/test.py +++ b/tests/regression_tests/dagmc/universes/test.py @@ -11,81 +11,87 @@ reason="DAGMC CAD geometry is not enabled.") -class DAGMCUniverseTest(PyAPITestHarness): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - ### MATERIALS ### - fuel = openmc.Material(name='no-void fuel') - fuel.set_density('g/cc', 10.29769) - fuel.add_nuclide('U234', 0.93120485) - fuel.add_nuclide('U235', 0.00055815) - fuel.add_nuclide('U238', 0.022408) - fuel.add_nuclide('O16', 0.045829) - - cladding = openmc.Material(name='clad') - cladding.set_density('g/cc', 6.55) - cladding.add_nuclide('Zr90', 0.021827) - cladding.add_nuclide('Zr91', 0.00476) - cladding.add_nuclide('Zr92', 0.0072758) - cladding.add_nuclide('Zr94', 0.0073734) - cladding.add_nuclide('Zr96', 0.0011879) - - water = openmc.Material(name='water') - water.set_density('g/cc', 0.740582) - water.add_nuclide('H1', 0.049457) - water.add_nuclide('O16', 0.024672) - water.add_nuclide('B10', 8.0042e-06) - water.add_nuclide('B11', 3.2218e-05) - water.add_s_alpha_beta('c_H_in_H2O') - - self._model.materials = openmc.Materials([fuel, cladding, water]) - - ### GEOMETRY ### - # create the DAGMC universe - pincell_univ = openmc.DAGMCUniverse(filename='dagmc.h5m', auto_geom_ids=True) - - # creates another DAGMC universe, this time with within a bounded cell - bound_pincell_universe = openmc.DAGMCUniverse(filename='dagmc.h5m').bounded_universe() - # uses the bound_dag_cell as the root argument to test the type checks in openmc.Geometry - bound_pincell_geometry = openmc.Geometry(root=bound_pincell_universe) - # assigns the bound_dag_geometry to the model to test the type checks in model.Geometry setter - self._model.geometry = bound_pincell_geometry - - # create a 2 x 2 lattice using the DAGMC pincell - pitch = np.asarray((24.0, 24.0)) - lattice = openmc.RectLattice() - lattice.pitch = pitch - lattice.universes = [[pincell_univ] * 2] * 2 - lattice.lower_left = -pitch - - left = openmc.XPlane(x0=-pitch[0], name='left', boundary_type='reflective') - right = openmc.XPlane(x0=pitch[0], name='right', boundary_type='reflective') - front = openmc.YPlane(y0=-pitch[1], name='front', boundary_type='reflective') - back = openmc.YPlane(y0=pitch[1], name='back', boundary_type='reflective') - # clip the DAGMC geometry at +/- 10 cm w/ CSG planes - bottom = openmc.ZPlane(z0=-10.0, name='bottom', boundary_type='reflective') - top = openmc.ZPlane(z0=10.0, name='top', boundary_type='reflective') - - bounding_region = +left & -right & +front & -back & +bottom & -top - bounding_cell = openmc.Cell(fill=lattice, region=bounding_region) - - self._model.geometry = openmc.Geometry([bounding_cell]) - - # add a cell instance tally - tally = openmc.Tally(name='cell instance tally') - # using scattering - cell_instance_filter = openmc.CellInstanceFilter(((4, 0), (4, 1), (4, 2), (4, 3), (4, 4))) - tally.filters = [cell_instance_filter] - tally.scores = ['scatter'] - self._model.tallies = [tally] - - # settings - self._model.settings.particles = 100 - self._model.settings.batches = 10 - self._model.settings.inactive = 5 - self._model.settings.output = {'summary' : False} - -def test_univ(): - harness = DAGMCUniverseTest('statepoint.10.h5', model=openmc.Model()) +@pytest.fixture +def pin_lattice_model(): + ### MATERIALS ### + fuel = openmc.Material(name='no-void fuel') + fuel.set_density('g/cc', 10.29769) + fuel.add_nuclide('U234', 0.93120485) + fuel.add_nuclide('U235', 0.00055815) + fuel.add_nuclide('U238', 0.022408) + fuel.add_nuclide('O16', 0.045829) + + cladding = openmc.Material(name='clad') + cladding.set_density('g/cc', 6.55) + cladding.add_nuclide('Zr90', 0.021827) + cladding.add_nuclide('Zr91', 0.00476) + cladding.add_nuclide('Zr92', 0.0072758) + cladding.add_nuclide('Zr94', 0.0073734) + cladding.add_nuclide('Zr96', 0.0011879) + + water = openmc.Material(name='water') + water.set_density('g/cc', 0.740582) + water.add_nuclide('H1', 0.049457) + water.add_nuclide('O16', 0.024672) + water.add_nuclide('B10', 8.0042e-06) + water.add_nuclide('B11', 3.2218e-05) + water.add_s_alpha_beta('c_H_in_H2O') + + model = openmc.Model() + model.materials = openmc.Materials([fuel, cladding, water]) + + ### GEOMETRY ### + # create the DAGMC universe + pincell_univ = openmc.DAGMCUniverse(filename='dagmc.h5m', auto_geom_ids=True) + + # creates another DAGMC universe, this time with within a bounded cell + bound_pincell_universe = openmc.DAGMCUniverse(filename='dagmc.h5m').bounded_universe() + # uses the bound_dag_cell as the root argument to test the type checks in openmc.Geometry + bound_pincell_geometry = openmc.Geometry(root=bound_pincell_universe) + # assigns the bound_dag_geometry to the model to test the type checks in model.Geometry setter + model.geometry = bound_pincell_geometry + + # create a 2 x 2 lattice using the DAGMC pincell + pitch = np.asarray((24.0, 24.0)) + lattice = openmc.RectLattice() + lattice.pitch = pitch + lattice.universes = [[pincell_univ] * 2] * 2 + lattice.lower_left = -pitch + + left = openmc.XPlane(x0=-pitch[0], name='left', boundary_type='reflective') + right = openmc.XPlane(x0=pitch[0], name='right', boundary_type='reflective') + front = openmc.YPlane(y0=-pitch[1], name='front', boundary_type='reflective') + back = openmc.YPlane(y0=pitch[1], name='back', boundary_type='reflective') + # clip the DAGMC geometry at +/- 10 cm w/ CSG planes + bottom = openmc.ZPlane(z0=-10.0, name='bottom', boundary_type='reflective') + top = openmc.ZPlane(z0=10.0, name='top', boundary_type='reflective') + + bounding_region = +left & -right & +front & -back & +bottom & -top + bounding_cell = openmc.Cell(fill=lattice, region=bounding_region) + + model.geometry = openmc.Geometry([bounding_cell]) + + # add a cell instance tally + tally = openmc.Tally(name='cell instance tally') + # using scattering + cell_instance_filter = openmc.CellInstanceFilter(((4, 0), (4, 1), (4, 2), (4, 3), (4, 4))) + tally.filters = [cell_instance_filter] + tally.scores = ['scatter'] + model.tallies = [tally] + + # settings + model.settings.particles = 100 + model.settings.batches = 10 + model.settings.inactive = 5 + model.settings.output = {'summary' : False} + model.settings.source = openmc.IndependentSource( + space=openmc.stats.Box((-10., -10., -24.), (10., 10., 24.)), + constraints={'fissionable': True}, + ) + + return model + + +def test_univ(pin_lattice_model): + harness = PyAPITestHarness('statepoint.10.h5', model=pin_lattice_model) harness.main() diff --git a/tests/regression_tests/density/results_true.dat b/tests/regression_tests/density/results_true.dat index c8e3b1ede81..42dc0c19f35 100644 --- a/tests/regression_tests/density/results_true.dat +++ b/tests/regression_tests/density/results_true.dat @@ -1,2 +1,2 @@ k-combined: -1.110057E+00 1.303260E-02 +1.082191E+00 3.064029E-02 diff --git a/tests/regression_tests/deplete_no_transport/test_reference_coupled_days.h5 b/tests/regression_tests/deplete_no_transport/test_reference_coupled_days.h5 index 5fdc656d97018b596147839415fa967031dc18e5..9757c9791be0030366544aaf515276409d1fec03 100644 GIT binary patch delta 250 zcmcaGlj*`trVUlrj7*!Wt>qbk)NGsiKx(qJ-G3m*+MXZ8@pb@lyd9T%GRjW=*d?y` zFYxU3nKj~e3{cP}m3HXDWp;ZQ|M6taZh67#+!d;#c}L-@u1=2ZR_A0{v}>>PF&Det zlRLYuO&NUR?cWNmlWYt(c4j-fH;1`J$+;ti>$Gcesk6$S$Q3iM2RR3sJUGpA`=-;= zV?T^!9e+68n*6g{T~Y7AoEsW%5O)4tZG4Bhc#18|4Ra?u_Ly^Sn|{DEJm$8o#^lBx PamG`VXZ9Ggfb0YSug+EV delta 250 zcmcaGlj*`trVUlrj0~Hrt>qbk)NGsiKx(qJ-G3m*+MXZ8@pb@lyd9T%GRjQ;*d?y` zH}LHBnKj~e3{cQ2m3HXDWp;ZQ|Hov_Zh66~+!d;#c}L-@u1t>XR_A11v}>>PF&DdC zlRLYuO&!E5?B5EllWYt(c4j@hH;1`J$+<0s>$Gcesk8c?$Q3iM2RZwiJUGpA`=-qbk)NGsiKx(qJ-G3m*+MXZ8@pb@lyd9T%GRjW=*d?y` zFYxU3nKj~e3{cP}m3HXDWp;ZQ|M6taZh67#+!d;#c}L-@u1=2ZR_A0{v}>>PF&Det zlRLYu>lyCF+pq3wQB~R_E^iCuVisI&aZ$eh-#V=p;i{&mlVlfMLAZ*i zcGk70aZ_wzuG|%HJ?z%+%YWelx*P5-K9A(!tx38kO(WUi9@uv4t=wD@W4mje6ISIo o-nPx&qbk)NGsiKx(qJ-G3m*+MXZ8@pb@lyd9T%GRjQ;*d?y` zH}LHBnKj~e3{cQ2m3HXDWp;ZQ|Hov_Zh66~+!d;#c}L-@u1t>XR_A11v}>>PF&DdC zlRLYu>l*?q>{oTQs4DG|au%HSDR_n8PbZ7nH)UqLlyv61{8gv7;*`^aBQ6b7lBYS{ zml2t{=haH5N7EiuNwN#BAY7$X zJL}rhxGAZ<-c$Ny$$ykpGR`=wj|w?rjhJ$5A3-0R&K6{vE7Z%39E7( nZ`qbk)NGsiKx(qJ-G3m*+MXZ8@pb@lyd9T%GRjW=*d?y` zFYxU3nKj~e3{cP}m3HXDWp;ZQ|M6taZh67#+!d;#c}L-@u1=2ZR_A0{v}>>PF&Det zlRLYu>lyUp?SF;#t$8eR%Bdn>ppW_6Bqtr|eBSpVhn>!tPi9~9L&}LgoAa@zjgu26 z+ot(adqSK#J{iqlr+(2{L`*-;;IFNV#)?CS7F=y{xjXUl=Xpr3I@6T;@hXz5q&IA5 zv5cEy3v*>}jbPZ>f0zEk1!f#mJ}-&n;B)b8Tk<2=;U0J`D{lW=*w{{A@qbk)NGsiKx(qJ-G3m*+MXZ8@pb@lyd9T%GRjQ;*d?y` zH}LHBnKj~e3{cQ2m3HXDWp;ZQ|Hov_Zh66~+!d;#c}L-@u1t>XR_A11v}>>PF&DdC zlRLYu>l;KX?0<&#t$8eR%BeJ8ppW_6Bqtr|eBSpVhn-HDPi9~9L&}LgoAa@zjgu2Q z+ot(adqSK#KN-zmr+(2{L`*-;;IFNV>WV{$7F=y{xi|6h=Xpr3I@^@`@hXz5j5cg% zv5cEy3v*?EjbPZ>f0zEk1!f&nJ}-&n;PdfpTk<2=;U0J+D{lW=*x1fc@qbk)NGsiKx(qJ-G3m*+MXZ8@pb@lyd9T%GRjW=*d?y` zFYxU3nKj~e3{cP}m3HXDWp;ZQ|M6taZh67#+!d;#c}L-@u1=2ZR_A0{v}>>PF&Det zlRLYu>lrHJ>@OZrx4it+(77d2Va;+yMdwS-ExQ#Ab~|ry6VrX~Fww<=Rzg{#936T=d0$i|OP^!tI-SS7z0$Gn{Z& z?G;d7zJ+(1EzFh03~XU(X{rox4TkGo{)(6!hx6MSk9z-e@j8mc?|$K8+AJX delta 353 zcmcaGlj*`trVUlrj0~Hrt>qbk)NGsiKx(qJ-G3m*+MXZ8@pb@lyd9T%GRjQ;*d?y` zH}LHBnKj~e3{cQ2m3HXDWp;ZQ|Hov_Zh66~+!d;#c}L-@u1t>XR_A11v}>>PF&DdC zlRLYu>l=2L*`Gb2Zh85sp>s>5!kXoZiq4muTXriL>~>z~CZ_w|VWzXD+1qbk)NGsiKx(qJ-G3m*+MXZ8@pb@lyd9T%GRjW=*d?y` zFYxU3nKj~e3{cP}m3HXDWp;ZQ|M6taZh67#+!d;#c}L-@u1=2ZR_A0{v}>>PF&Det zlRLX@>Mz9E{|fC}^H}7RQ$@Z&AM>|KPCC;0yzfH}JDo9~%)aJ_loNY4=VMJ9Cnrv} zP4lJpggAA4GMc|m{i3son0}hUUt1TA6^9NjxZ2`!cjD#G^N<{MrYZH~RU}7AZ`jUa z88^ii=E&X}!Ly-Tzuv-2S()v7Nr;jr>-J+qR`) j(yL_tDB4Ng-0b85^bzNuqbk)NGsiKx(qJ-G3m*+MXZ8@pb@lyd9T%GRjQ;*d?y` zH}LHBnKj~e3{cQ2m3HXDWp;ZQ|Hov_Zh66~+!d;#c}L-@u1t>XR_A11v}>>PF&DdC zlRLX@>RHO|e}?w0c`S0usWe}pkNMjqCmrd0-uEGgolcogW?%C|%85Oj^RcFllM_4J zrukBPLYz838O>j(e$iP(Oh3)wudR#fibIDMTy1f=H}Uf4c}R{r+m!n8Dw3m&Hf(3H zjGJN$b7X&w;MvfBm;S;9W*t*LFNx&d^YLt3@*~*c?tddIZvR`@*v?S$Mt-ZqZQHUi j=~Xg+6z!yMZg%nj`iS#S@_F-%&u!mNp4nr<4D=ZQKjU?D diff --git a/tests/regression_tests/deplete_no_transport/test_reference_source_rate.h5 b/tests/regression_tests/deplete_no_transport/test_reference_source_rate.h5 index 3f3b4aa2ac5c2040059adee2acda433177ff4464..c9c7e9e363421792108b928ada201f80d20a67df 100644 GIT binary patch delta 188 zcmcaGlj*`trVUlrj7*!Wt>qbk)NGsiKx(qJ-G3m*+MXZ8@pb@lyd9T%GRjW=*d@-m zezIVXJY(x*!=9GOYrDB48NlF!J&0-uNw7aWZO=1T#?MZNPPLr+IDMB>ljZ$sYkc20 z9cH@0p%UoqH0#fzmm)1$PCV*@9!JdUoTM&I%U!2_(Yb&_G=l%Ht;>!}JlhsrZE-=^ J(=UK-2LSZmL|y;@ delta 188 zcmcaGlj*`trVUlrj0~Hrt>qbk)NGsiKx(qJ-G3m*+MXZ8@pb@lyd9T%GRjQ;*d@-m zZn9vHJmZqdhCMBl*LHJ9wv+}kSoI1xGXTMX`f~dt)Al@bW&G^4?^MgFkJEQKHCo=E zw#N63(_y9?94dj%PP6_jdMVP9<;1Bj=yAln&PnppwA^*-7o7_@L?ihB+PZAM#ItR| O)fN|oJ^cdcb^rj;XGiV; diff --git a/tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_ext_source.h5 b/tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_ext_source.h5 index aa09e1bcf4e352f94798f2cc266718b5f77fcec1..3ed38f3d6ce3973fd63e7967b1246c8012ebf063 100644 GIT binary patch delta 573 zcmcbxnCZe|rVR`F>USrv(vX|WXU6~qyH0cH%&q$X=g+=t^ep6Lzb#Dua_|(E`n7U! z`OpUY8PRu^!TFp!5AV8dqXFkLXH|aMzsJTdai`9if9wC*N=+~R;`TwqZr%q@#>vMW z>+M2=7h3)}_}|vA%{uA8q-(a1430%d*ek#d;Kyj&d@wzFTe}eISsP{QfDBqBQp=`JI)^`&u;R?d{==2-s zn85i4+}(om7e3hUe7)&`_3n9-&-csvB%GdVFCu4QX!&=O^UBAH7Cgt6IE(*mwJO-W z!+B#-()4p%W;omJ+hwTxAkH~rf6|xw^SRCkbPHbShXuGq-=Fp1_xl4b?*bke6n-^y zop{ire!6Yfa~A z`G>st!}S=N;QSVAKG6xqjke{loDWa*Y_R=tvTbGL<5t_Jzg2Rbe=QTV4LP>`DVuYd qE#LJSX19KO*iJk+f&V#c|I6bKZ`L*S*BN4Zf-;Mza^!fgFR4sY~=W9JMXb+E@U<;F%eyzpw?}G$f ze)Gy%UgoEkBKXS}J_viL2_L6yOG&csZHnKpC?=%mLfnR=CAvFMvD9oBznV zqnB0S>Nofw-+U=(Kb)WN=XuMA3PU*m=_M_}>1EDPz99oc+Y7dX%{&%x1(U>L`t6%^ z;rwj^>$QDbKic0|`k<=UfA-|_{jxsMcTd!`B(0EYX%U5cO4Fn(^ugf(IDf*fXIeAUe%rtTVWP(z{T8K6xcp-+g@m2Oli~cY z_8Yz(KhX^5UsVyDSAC|*cGsRc8_S~`Y%_B{pSqdfZW}E*kxR;Ng`jP~r~K%tS{1f? n8cfoa<2`IA9-P4cX diff --git a/tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_feed.h5 b/tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_feed.h5 index 284d5cbe4ed05d7404222e70200aa0e0318cf2ad..a147022322a1887615f9000e41992c5c48d74b2a 100644 GIT binary patch delta 717 zcmcbxnCZe|rVR`F>gOe|(vX|WXU6~qYff|M%&q$X=kK^{^ep6Lzb#CDaqtwD`n7U! zdH)9c8PRu^!TCZv5AV8dqXFlOWL19JzsJTdd8f{qf9wC*GE6W2;`TwqZvF>O#>vMW z>+K?f7h3)}_}|vI%{uA8q-(b4430%d*ek#d;LOjF-de|O4|BlPRUiIo8!Ui3=;l_> z&-+*C!_{a1=9RFyw-3%Ybjg*zc-<7vKh5|))cc$#lyAtu(7IcD>$?e-a0MZIboz~R zOyGQP?ruT(3m@!@Uu}9|y?fr|^Zl~kA6`$k=Qw;%{8){FvvcdQXY9&)&ZVj=*Y*GQ zcUIMi-=o>4?fmA&+b2`DoO06kuX0?If5++kIY!TB^^49N8-L!}`Q6rK!!Iw<=~r7O zFJzOSctNZFTF2%|S(S*0C|s5`;f2h5xHBBre&uRhjfjW`SG13b{1t)A-=CGGc<$wL zIR8qJf2zn9B{*N%O<42R2Wz`Ka~{gSpZw4Eu=GCZ`cvw5m-XMRyYT`T5z3O0*{A0H zw|y(K@ZtBDS8XrUEGjZQpa?f$`}FH;U8>mO5mDqhlRNnoA|fiv^jAdgMp(Fe@4O_hH1m{Z83w SSj4ikALmS7KT(zi6cGSL_T?-9 delta 717 zcmcbxnCZe|rVR`F>Z>Q6k%(K#Z^r-yrhI=psusP0^GzNYw1>w{u!YGhz1Cv+_dx&NP4#dhn{A0^(k<|Wh-BsJr|M%h*dKBOW?0h+y;ObZUAK!c_Xg{3q_2+rZhYCYD|M(>>!RckrP`)7pL+cB+gUvh^a0Q`aG5z*U zx^VtBf%V$Htsm_@mprKI^`AZYe7~&sYnLPTmp{prCHEUR$1TG&#&!W<$Q2!!`1IB-Ej;N5!0{b+rRUf2={k~U!{g>uQ*)&PJ@EH%d-&? zv251r#m0Md;PMYsT&2IAGO^p5Gi%Br#(%aq@3aQkhilmFN&Hwn>mM*8mT*7TX*=`Z zwt{2n!_WCwZSQ>Ezqein5fMMv?y6B-%LI>z_act7zf3_y1n2dO2G`ao!!6ADq}sxh zz7Nj7>Gdal$rd9xfBV_aVvaLzP(CmsY;-j9-Sf@i3i5LuC RXJ+^N&Yiq|qAUw2A^@aa;FkaZ diff --git a/tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_removal.h5 b/tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_removal.h5 index 271af24103cda8f8c4886da551422a824dcde360..3d48105d7b444cc16f5b6438f97f897d2c1657fd 100644 GIT binary patch delta 717 zcmcbxnCZe|rVR`F>dz&w(vX|WXU6~q2Tyb8%&q$X=Wn@d^ep6Lzb#Due()5Q`n7U! z`TPd^8PRu^!TIt#5AV8dqXFmhW>tRLzsJU|WT(!Vf9wC*>P;{H;`TwqZr%q@#>vMW z>+LFn7h3)}_}@05%{uA8q-(Yx430%d*ek#dV9C#s-de|O4|Bk$RUiIo8!Ui3==fI8 z&-+*C!_~L{=9RFyw-3$_cgdB$c-<7vf6Mqj)cc$#lyAtuuxPjT)^`&u;R;&z==2-s zn85kA+}(om7e3hcKic%bdiT7^=lf;7H{6|M&vy8p_^}!T=jcGby9shC&i7u-xMcat z$~pA#Y~B-YD$Z|SynQld%PA)V|0>5d`FEVopJViFR=?=XvGM1fo!@O;HvIAuoqn}t z@e^A&a^!z zm&5rcyWUx>09hxs&f_`!`Sj VXwNUzwei-J#go@hlw|=$1OPN1;b8y( delta 717 zcmcbxnCZe|rVR`F>gy++k%(K#Z^r-yDtv!CsusP0^9>&uw1>w{u!YGBzt&>;_dx+NQg9f*~E_{a92c~bksbysb}{@;sN=uvyBPlfvZ2^e|+<$p#5;Z|DWeAA1Vyt{JWR51gDodL-~db3=?0l9c<>YfGa2!i|My- z(uMQ43ar=mZT)CJaoK~aUjNyX&-crE#|Iv}INP{)7tx6Eos$?XLQE^liNG$9B=L2kMWFHSAOaoMeuQJJs6-)vVvY{?k8O zspAIh*B@Q6{eOIxY33&-xB*dIRqgfJZ19LU;gjhyOKT}SB8+d$GkZ555pgSi)EBKf zbOf%yE#J#=dW8|3-#w>-M>{qE$_GY7zQGThP)|#^g0zW8_ZB}jh4YuP%(^MC>7%{# SA|unRp9?3ipD4=$iUX#+2(vX|WXU6~qyH0cH%&q$X=Wn`e^ep6Lzb#CDd+-#N`n7U! z`IrX#8PRu^!TAb15AV8dqXFlC%dGsge~*n_-A;&oFv{}$u>Q15e|P`)7p!=l~VTi;Evge%D1qtkDk zV*=+pad!*KU-)4E>eHqN*1P9TKHo2!)37kyp6&2G@nbax&M|>}cN64PoNv6Camn(P zm2<@5*}Ny*RGdG&c>83^mQzj^{#A}^^6xmEKga0VtbWm%W8=>|JHOkyZ209RI{j*k z3(R9V(tYc89{BE5o9(gi=5`%t6VaSgOtzn$CLYwNcm9*T$@mvP+)a1mnv~+^B4SCo zZr5|hjT7L3GQY57;b(PGxcW^med7*YSOMn?x|uY2Emw#0S+bVLP7d<2GwSB&i_`gQ zJ4yO}r$L>DUE&)SKSN!BG{@MnWyivQn?6U1_U8ScdJQU#uY&hR_VsSVN zJeG>jY&!A(IU<(gR@u*yXVrqMe<&KNyDj`6oWDZ*mch47V>o|bqV#$FH-S(-FqR^g s=I%XEfruqnxjPE4$_?Qfo~}9FJWJrCy}=2UnHJG=Ca<3;%L0le0PHO5YybcN delta 746 zcmcbxnCZe|rVR`F>T4#Qk%(K#Z^r-yW_*7;susP0^K~8=w1>w{u!YIXz1Cv+_dx%a14#dhn{A2syJgNQRx~sM+|L?^s^eDg$*#B}e%Yiaxdzb^(xUF!D$zA|=kRAV# zbw@9&z}2tyKfd`=(0({S;m`Ay4;6-R{6&=+wTWB**-qGV;bF=3YqmGLJkO*^A);C4=!e+weTZ1P zCY7A2K5HS|LHcS*TLX*{f$E%~rnCOy0k{LQa}G}kWHf>Ew;UIGeQCQZln;!hYZ(Uu qcNJK|6-ZtZE_-kTVW3Cr%Slr*KH2}%S!Z)VVd3QU6J=RIu>=5#9qAhY diff --git a/tests/regression_tests/deplete_with_transfer_rates/ref_no_depletion_only_feed.h5 b/tests/regression_tests/deplete_with_transfer_rates/ref_no_depletion_only_feed.h5 index d080b447093018f62b3eced413db81fa606d3338..70a4a3d67a513287b449b1afb36556bf48bbff7b 100644 GIT binary patch delta 30 lcmcbxnCZe|rVVfUnI9~hG&#{#e&Pk8%?cCNFtPyo_5kbT4rc%W delta 30 lcmcbxnCZe|rVVfUnG5_5Pfm1|pLjuNv%-Wmj4VLDJpkRP4LJY+ diff --git a/tests/regression_tests/deplete_with_transfer_rates/ref_no_depletion_only_removal.h5 b/tests/regression_tests/deplete_with_transfer_rates/ref_no_depletion_only_removal.h5 index 36f89e0090b147be206c995014745f3e9cb6c884..b41218a9b295a14f08c076e026adb858b4cb5b0b 100644 GIT binary patch delta 30 lcmcbxnCZe|rVVfUnLWNvnw;n=Kk6 diff --git a/tests/regression_tests/deplete_with_transfer_rates/ref_no_depletion_with_ext_source.h5 b/tests/regression_tests/deplete_with_transfer_rates/ref_no_depletion_with_ext_source.h5 index f3b1b171ebcab9d99fb7b73f5b8273ac72187971..71bfdae11234c73d8c7e38e2f6883813cf73f85e 100644 GIT binary patch delta 30 lcmcbxnCZe|rVVfUnKN>yOipx_pLjuNv%-Wmj4VLDJpkM04G91M delta 30 lcmcbxnCZe|rVVfUnH}aGnVje - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + diff --git a/tests/regression_tests/deplete_with_transport/test_reference.h5 b/tests/regression_tests/deplete_with_transport/test_reference.h5 index e8478250be6497c4d6ada14ca8fd20ae217b4fc6..cc616e79102ff6121285cff1ecaecf84b7f932ce 100644 GIT binary patch literal 163736 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
soe6>?;W*IS5G<{kw%O8uQvW&aKBrTQC zCagDS`GYlIb3O1)fIR!)?8yw9Mxye3+qryVF1b|~JD^)=POjY&y1_;}fe=jGbm0EF zVq!&zl>Td$p8OH#%_?w7%X|shiB>Eh7GEIr2<;+wxb5+=`G z?6Y$3hbF>FskiEvG$&GY{PdCWlPt&@8=3luh&W=GV4s&`zLXLA&i?xoSb8RGxxaXp zzNF>stUW#^EIoTB=Qzok36P(kj+WKUZzSYmFU+zrolQPlXO<%A$QrM{!A_ElJ0c2+mDQFy_gH$4Y#iQa%|uDd&$Gq{vEwl3+t|T3 zYXnIb=R+Pd%$o@z#pP8cv*wYS(f$uV?X)0IiuX+Hr5aD1j6N_dOSY6aF|gM#ww^+_ zU7uq{NLrpW-MW+=KgUg4#?ABjO1vl$da!y<6OmUxb*)CcJvkxci`K*>CTX&9U;k>U zTSTA5`wz5sDyDSgt1N&;aB_dm3I0}^265JvT5N=@@}|#UFC+G#N&v|0^#1pMAk9KZjV|1e3^Z% zJe9@wUXQFs3}^Y{xjAK6^lnjdoyf$acDou0i$d&Ke)|G)q?-N3U{fY3E4{9JbIEn$ z$mYU#9ogkX%Gb&H=U93^=PUa&v?VQH>^{GhjdQh8{QBV60;KITW#!C$jYN{z+rSx* z=a2{buD;%T2$NjV6lOG7Etc4&dNgNeaxt+)tY6g=mY!Q8^X&4>B`xQQ;OZ7EJ=aTw zr_UWMMt;m}+I+1?GZ8i++|_rXGpW~CaOU;Z7UT?HH}8ZUvBZq|?n|RKR1!Hl(f-F- zdX}0s#0aWLTJFwu%r<4|$u$1-DC&j)d1{Z;yi-Io@pyMq<(`so1}Iet=1~AIRtkFHI)mCROO|>s3sMM#ZG57C5;$q}HzG!Twaw_gjl?*inNkU_r;{a*ZycSj zz$6P!*7OOfyGgi@ei5iVuZWmr<265yrN?dMhieuSSpF1ey=40%ad>d+lXdmPBiXR6 zocFE4-cJb_5iIJr3oFCsQ2$liI+ z@@IMR{cb~8ad>#mY=WJ~7&TWyL@dR~#JSJk`fP3_4s0*onEA$;lo}maMHn;5=J!tp zmmRx7M7_O{pk7izEIQpM;SNhrZJR`ScV$){yB@TQoma!|jT;nYCqSCz9B-J|(M+7X zE&E~1rdg!NYvc5)0R)-l(Y+!|{U(upRcO(L1to-x*K?)QEIs*k=X0mA@=s>c6(M7m z9@`u0b3`>nNjyBF$GKBY#JveiM-CjbfSe$7c4A_GR{RvU91U9%PaL}Cd|tPvoN!C- zKkX7rkEmMy%_V~+Er&}f8>j>G#;R$}!n0_59=(%x3` z&4g0uVjY7Sb4cU)pCYwgEy$f&G6NLb6Np8RUbGAuUqZ}U)hG2nOV6l|Tm6r+_?4F% z^oE_s(se6*yRQ@>oA%{Nx*TdEY}cKtpA1;dd=B?TTnQ zK}L_uxy#b?cDQkR+)znNCGpVPY(1-H$?cPB{6ZAgCAqerY$6O)?8Yk!+LI+`7s{_| zWRmLL_b$9L;}&uI!>HkD`h~>q6Y6K#dA`Bc%PD)Di+yy873X*VUyzNBw1xk5r>XJKCyhh7tpG$`dtSe_bbke+-PfyJv6Fj^p_3&Vl z2bo#-9?K^Zq0%+kqw|W0p6-z$&slmLmMawnSxQ=JX6yH6^YoDNk)^^58RV(cE=9xh zS^ik2=F3aXBpswuO9O^jkW)(^i6jh9AP)6gu{)hnO!P?|=n=!xBe{93Pxu%~%k5Vu z>}LDpv3u-A<6I%~?y3nhPnb0k+rDP4kbmn)`YPp%S0pe={V^Y;7l_9bt7J}lYMPc2 zhi4RzV)G(VcfR#l6G_XWyE50=^XXKH#X@~D1<8ZeCr>NiV$B!NPTHNs=8@YEXziJ+ zVnLqDe{@dRIe|D)p`7{Etd!`ZHf#!eov_M&vfMG&I$^Qv%p>f)5pZdR_?ciKa@v%) ziMJoO5GF3qeTt`5d8I%Cp(ZqY+X$sn!Y`1yw1}l=@Xq}f>ZX#G zJ5~E$Wv@3Tja*l=e}ypl?!e?oy8%taYxQxnCj~i?G9v3Q2%KY**Y0OD-wTcQ$l(EGJaOjH=-^C&Vq(n|yAHM{Q{P_^J_js}xFdB?}(6XPiv)Pszwd-swvFE3Md>UsaMgyp$H zQtmn{4xg=CA31%L5P5Ed&%Ok+X2LGncBY%8BPlj-?_p=wymO3@8(=awh6p+y`+m#G z62j&~vri_=pL_S8Dn*(}TIypBR_yq>SeMxHQ9_7xeZ0G5uw^4*f9;-A5Yv&&9emR9 z##?hT-oxZ@a#k!6Rd#jz(V7zCnyh5%BbJ`bRp)IBS@E1BrR2z7Z#0XGOAI?CKziOx z8vI_hnFyUEaAVYg*`!*(6aFifT966hXRnsB)~kDyE1d@}D<&qsnENG#rRVOB2uy=D z4?anbaw1rI%w_M-@3TpQEcQAx)Ba{NF;4jL`@WKk$){(>UAiYnkoRnb)Hju1CGxLt zsB(Ywo|so)^n$%Fs8}^){~=-4ymNNMaCRK7lKk*)x~&lD+a!?GytRd>AF}zu;XU)n zmiv?BM;;=`6LuFa?0t5N(5&q@&qBJ4FiJQ4BOm5o3QHWp>X(3<{8dAio;6vsF59#E zRU%(glE1u}n0-cn(Y!^o$v_p`!CG8In`htg<3)^^*glPFau>h}_hcnrCF>Lf5@(H=#HzkE&~3!HUDz z+c(@|?^7ZMJJnj(3XzKiBCo$KYa%v;yz3p3>qwg2cUSm4+JbZ&=yCt6W<1fnDs{yA zic;ds@fdG5-A$ya|n9jr+#&UXvSRh}1#* zf@2?75bDPkxvwA3K*6 zi@jlx#hRCoB&|3yMA-5D>|04Cy!E9G^Ip3Pl0}`H0b4c?1w%W$r^-swPjrytm`s8u)VPInE+`LQ) z%S$7VImNQ#Fzku@iuSugWYw8DkKzut5Yu|vAAT~`kvw`iyj#XOf-H!?p;ajrN36VB zKV_7ADdE~A()N<&PkmIxC3V*P)e#V?$;M~R#+y6EOvT91r%yi*5p5#O#`f?vIp<8C zXc*?*z}oLLZceto32u;+#Y5asQY;(=F(N z069rl%lddmBhfx=ySb6^98&G-m@&;$n52+;x&52Cc*0_&ejodTC4}T8=YV*Yo=JOe zD*CeWMnI!h6I)MCo!glANy22Vohdmow2>&Q(y3GsoKKcmRgbtmg7v&*x9FCGJ7NeE z*{$1S$FtUT+ByF0Ja498qd~C7tG%rF493#4TT*(^>&2{oIa>LeuWBZ8GNKpUKjut6 zI9d^F^TdMuqIzL*)WI7>TK)jt8tF>HHg_?Tz22DU`b~E107=U$7Ht>UdHz9yR`d1+ z!emBnebJi0CL(>yT|8l-6S<)OK;!NmOmaNqVNka-2}Jow;a6|YmJuKK$9}xU($g<< zeW5RFK2>gTI>H|J{oZjZ1KxKd4<=goYkl2FESo1i@wA&WxlK86@P+kEvfBE3Tk3~v z#9hn1?lbmO5DEMH41UJab8qtT==-ew*Pe>>{_OdEf~|kC%MC%2s9ShumtiyU;lcSc zG7WP{&#PuHy*F8qpA8pnoDdODR12<_kX>6sym21)BAcbB`f{{P5No{=JXLENThG(i z<8wtfHxmP{m>69D*h1_)m;O1u-&oRnh0kq6R{ZQ)?0RskdN#Rl(Qtv}NBNvMlysRk zkim+>NfL@LF0f>4D+qoys%v{PObz-x`?u>{d z>daGax%4k4lxhclD`ENbAidXo8`iq6y`xl+?awDq%ynsR5mF~L3b(t|L>$>F`#ef> zKKa^Vru~%x7UVZatJ}xgV~LxZ5}&m^%Lv~cFV)_#^t^wSV6%et{HN*Jcp-K?M;xAI z(X+1rxj|0mXwBs&;wH18-`8`q$hr~zr&&r^kk@2B%pBh>mN-8CjFjy7Vj}a(vwMLo zJwA?0w^gwADc;3fmb3NbZ+8#>C@M_8+AP>@v}zL(Uh{R5Kp!X4rJ-{BUe-D)tFQ0z z>8$lnL7C+VN53*6C1^(_dwm!$(r|AHE6?{hVXw}P=hL6(kA1!lJ+D55U(JJ?peFJAp zoSHrdjaS&?)}fICXuNjHceFcxMDG`5eB>s*T8iEm-3!}gCPCQZZ}M$=SB92hwPTfn z4CU1EapCnzYB?nSVwO&n{?=k_sMRLZyGN6-jfq;fZz`boTgN}t#jnyv=f|p6*iE_; zfWGhCFwb|g%Ed;`^Ks*QXJr-F`*8g4y}Q}$NDVrFA+YPn?lrwNaqaHy)7Hq9U?&2H zoRJmojcW%!)Rc}HhL37qQerx}5*wMJQgV`YzFlqb?%AqB=z0E;+P#kNzM=P1iYA^L zq(ss4{6)vC<$_vId@VMejT2``kLT+zq{dyJ?v#bs&B)el4)6e2E(CJn*@~ zmv5G-;Sq+}yUo^F;@u*ArZIQsG+@R{V^?`(#A4HzUMauv0r|6a!@fDQ|MY$EgNM8gHatN7h|8xJ z4)sCf;c>@yOa3>+uRtaHxpS83aN@~KdQij_87*AJ>g)TTgOH zDO%x*5_81dYiqFx)rYxO8Od10>f2G%kE8cHNmCftKUX4t-K~}wo1u-~Po+E)8Mj;+ z`M=)%#pe8+IF5Z~rC~K9i_rPphV$Md8m_A0y2@8)1$LBTy5(alt=_WEFN!#1mzFW{ zF`w4h_6w}W=C)aWnIV^gjW4k>dUylnL*FS;!F!*g^JddMWS%5=qI}qIt=gz>vrs-f zaN_Potx$x|M@OF;VQ&qNf6wNec{XR>NIYdnoRLgX8MaP&r`vNgP2AGjuUKDaG`>i& zN!M2IBR1>S{e`kCQZd&}*Va}%LgT$Qt>ZcVr}Hq09ljGva!~#mK*W5-wjjP=O`BpA zI_eh3{^gV3l#aTi^G)3|#LJ$ZHOK1}HOt$wORy}#XQL-{ABZp7He_hQ0xR6KQCg?& z{RiyorPhtdS0`b2(*~reuSDmUV(yO#-t(-LqyI)=(}>~s(D$9^%~SLLJP+aHlRS16 zqvSeAUPV>O?#O&`PJN-il7g^{1^&?1#CFYa*7q)B!xpU=qKunmej9JEg5$nhtK0F3 ztnb+!wKA#mNWqqF8QU`6Mv&89<5`&AeA!2w`m*6+-7`A;IQh>vCP5(Ay@FGpG0!{e z?Lc3S{KOWmm+_1$&iyZ&gI^Ad(8FV$hetPu6=SzuE-o9-I^S+TbkGXz1~Xi6`mxd7 z3|Ze3&rt{vxxywM;TW<25$b=!}xwAiQOjOO1zS8Ft88+pc+q8vbl&&IPYH60cU1F7|m@hb=q2 zb#qEcDrW9{{OizcwB9J26clqJ0G)r!w2BL9AH0{--(!BGSg&}|{5!QB>)(D7#k0nc zS!!xmP(ECkT`@VeR0&TNG;e*bQjV=WZ8zw%z?g7yX3g(p&!YS-X*jVU`x5s?C- zBx^(2ud##M)rPFT|V~+TVU{C+N6p!?oCG6Di$BvT}eHuRQDgNd= z(jWCGx07j>h+ZlDbt&+)I8hKOj<@CN16^<4C@t<5gJM^O{O+ogVX$yFt?9}?5O?!f z-DCSwpy<8dZekz8|DT-aQ;u&z`dn|sHb1V6{MUuf)UOdvNPpIMhJ+UAqj+`Lhh$-J zPo5&bFL9p79L5x2V8gd__v#106-U-b8O{>$vqea#hOr(Lpkam|AEhsHW-Ysrf8tFWPPHK+Jgl!~Z~GjD`eito z6|Vd$J_aMb+iQa4dx4ezg>8e0{4iu~%eIvy4ahZnr8q|U572rYLh@J72iKcKV&p!d z_^`xJC`|L#FUtC`L#NO0(MEWv{f2A#lo*;%kM~j->zogueEyf1yh>D54~2hoxwFg< zlX&0*`-YI}svgi8>gA;RgA0N!axABrWMTU^^+k{Uz2E|aps{>gF(AiyALt!H_SKdb zJoo-M+8+*bFBXx6>}#CmJ0G3>9Od_z7>pF3-9`Pb8SkWGCN@xi;M!0i_~M}k{m!(m z(5VjsTZJN(#8DxbbRj?Zs=N-|xbSC9{p1)ptjYA^-mP-LD|NlD&KcnwR%cvq(~SC0 zj=bsOFHS=JiZ@hWes8^u;(>u%4y}!yRh0cP{P9(3Zjv(ZIQ#!|LMc8{}+{?7*Vo=0s`>hMH zI9#`mRLJuf0-hBqJq3CtfS-@^+36oBJ{U0I+Bo}sj6$zzXHw5(4w^@xt#>-yD~J4n zUN6IR(ohQJ^9;jn0L~-NZs6YKz~XZ?)vacPUz!Yi&O7};c*qd8kRdaQ@X#&5Y1zL4;h}(_nVTR7 z!b7hQ^1t|%g(>_wILr(^WKoCQ*GCRYbq#@|Dt5KR%rz9}z9({QK^O8%oKSxJd=mKI zteDT%`wVO+7Cstqqy9^~joJ!bCv;!OS&qWDUHwR(PYp);sQyOhi&GoQ>0$h+pObbh z;a^0-J_4jF|Ye`;5l8CCnA zeze7wYx9X0wc&X3)}5D>d%+ynr4!ke67ZcmYfMm?Hhi!rTx{ycBDk5MuP^Uj03^4~ zPe^njdG~1xdAI*T{j^V;dYCPHuTuCKaV=LIAaMVGeMwdYVWD%9t`z=*%wL4vzK8lT zGk@*ATlGKy4$Vt%tvu5UM&h)AkR)rk!M9@cM7_$Im7~>bt3uDZOH#{>sq7 zgbU%x(y6+RQA2dTcy+jy_U-`kUlBY%KYlneP2p#ldds)-yO94f?No^~BKk-F$FK1l z)_^KL0JpCv@C-{1fFSV=H)<7eSZ8`9icw1s(la~!7AE?OB}AxPUa6D<-0D8Je+9ye zRIb-*yI9cqG^~>`_isAlXWz|qrSLcCK8g$Ue?7Owp!2DQ9kXuDQDh&*c=<9!7Y`dMojH;s)xUw5FRdUHqL-`8?y!s;%c5(Rt{aSl`4OO*L3?Rcx;= zao#ya!*?Iu)dWi8zkv_bZQ^L@w)ekc5l}neKB;ajP z4X1;Ay6~dG^f_hjWiUT;@X`&3A~2D9PDlC;@~4-u&q=fY$!{#}9=CJhL;Ps9EsRA7 zBYS7wtKPz8j`Szof^m!8f9^kX{pN9Zu1Xe4*CVhQlfkUdYNRe_`Z!F!{5c2d^k>2+>+~i@FVX`VOSPAKRRB& z)3NcN``iQ~6jL&{OHk-ZEI(w6;ZlZmiGNxQlKa6-SaU&ZyCe*)EH4XxM$BjRaq>Pg z9R+V|<1U^%TLLbbo*GCoK=Qh1aK`Sa4bq?R*0)wy6p?+ICgd{ji$(FxxUyQ=b$gUQ z>~It^Z`_UI{p@ecGO145@UD+=>|G+?a?kKZa;vf^3@)wkE{i9_gA(HpI?HCjt>NqP zA{!;3Iqn$is1NGjmO0-cIO6t~vc7VS}4 z!OeS3-!-9m;mM#gynSHsP;9d|zZCR0Bhztk9)OCno}H^|b6{e}n(T84Vm|aKSKcli zgg@qi-Tnp%tCaPo`cJ9Hq{LD7r~k|)(Kbnx&ol_yb?-_z${+p-cMp2%_#b)hGj-;z z-VKwM)T6bsd%^h0+gBF0iow79hmt*i=s?3}nvb@3$AFl6=(8cqd?3X1mBI2E(jVIw zwb%bLBY*KNfh!{KKGGkIKHfj=Bf{T&WB!1xYsg=`SYu}X5yL`RkFzvedZG7jSk{;1 z7RcKR=sfQj-rOz@1svZis5I$9E-Guo+j{?iWBWs~QO;sem|mU|QibqvUFLk&+$`c} zR(s&IurI>HbK>4Aca9)=n3#_hOZ6gtk~t?T&Pa$-_*p(qe=#*i0M{e~heV zwSSAyQr6G9)wyNp`4`IHx%;=BvwMW%4Po;^SK}2`_&zgr6;Aa5&oPnH8$QzTZ|{tf zq3j`eSWul`N@p5~r0k8RZ7BuYU9sO!AN*&3LgRbfcBe`EQx*&d(?mw%@tw-`|9#2U8v={LcNbAV;up0fI`-zKuOMys# zy0^|1Jk&vWcroCsx-AuiN^>!$JQ@RF$xWKUpjH%myT82H#H$O>{!t;oX9(QcCGz{_ftt{xUP)a>q0l)d!OuR2P&*(fOyN%FSd_hF59mmBniMf&7JAm7kr3A#1^^ckc-dUh=uh z(JDecV8B9g@|L$49FM3o@D9cy$1$pD@jIhH?rnFW{u=^xi$2z)kAb% zR;SgcV)=&R)zn`2$dm)g3pA^cg{~ui;TtqIdE!6*eetsUaO5LFsIp$$;n>{`%>_>S^pE#YdM`i)pfU5*P(RRfF5M;gZ7LF z?_NOu{mVWv?+Xvm`Lv>Saa&kCsz>#t)%|T1mWEa%*1tRzh`QUVk-n3;GU{dw6&t>{@Abc|Y*X(x`uln78*z$W1n#WuzY_oi{8^s$tLiR^LYNn&` zFT~vbHvQ{o%HIK{zw&baA^9`^a>%xJBkVd-j$1gUA8c>!ky*`0CQO_fJ2H0VAT&ZB33 zIG#lM^T?%7^4e_Hmav+S!sTmq)VWD z-tgo%?{;7G{~YhjRjT{>pi%h>o>b4<49C4g`bhv->mvW z(0!>iw8M4KCks-oMSJ2s94u zEqynK{Fg{Qm6=T<@?UWwa<`JwkpH^2-!Xck3DuVq9!Y8am_Yt3%=8m!`&|hNf5G4H zUAvJe1pmFKvJLpz50pATjn#zFA7UY7&j& zgR2d3KYXque5TSq%{b7A_D3g3+9c8z<-@;8w(sy_L-{(-6ocW#&nRDOaCwj%GHm})hEO~=R*gVbBOuk z@J@%%Iz)ehwZ(0_$ZI3un0RQ>;L&36>{5taJ|n{C1Mr}q&ph&9{ukcG3CSUR&bE)s zi?BiGWrx@=2Gd2zzbmI#^6Z)rqU_h0k3z3Wh&)`g(k3&q4}zAD=bUn(7!=wUx}HJO zg>noM3sidUwnSNC4e(ZJX_>Tzx|M9S;XVnf+_<33q@M^l^CMAA2HkG8K{u$*DO{$Ya`GOE0e%d2G zW-N*DgGd)sNvz!YDr8<}G20kgv~i@4Ed6kmM_h+aT)Q z1q_Oic^Q2lycGT5{~N{g(U)=!cRxq?^JsNeS-=Xd|8B;gu${CO6#k8Tltr75A$o?I zul|K>h{M_)(zw+^FW`=9{v^*&+^=HU|1*$BAI@2Xq)oBT0_h&hpR2aTz;rR2h3*xy z-`)4petcU){**dH!Hk6->5p2^U)}d>sD7cL)49G~0iEC9?8c6XIH7#N@s$j*fn$4M z+Yc_b9m9hl?tupTWU(}CqEoKanb(I)v|_pUJ%)kk%hy9XbOoS?hh+WxBJvkAW1d6F zT?h|7`8k5FE~9u%AD0NN>zJj;Z$HC%sbyvqAHMKtsYu8}`rg;EY{)aK4!^27O1#zU z1y`@XTqx>&N`>|(%QV(%avjMlUD8aa zD;?z%zNO07eR4tk7=POJ;d3UESG+{_dg5UeKijVbag9Azg-3#_?;J8E;`xt>T`g|n zaJuox+x{2&@MwD1{rfLw!1mC@tkX|Q!G*c*dcmj}t>15+#J!zN{J(kU%{k}I^Gd*H zoHFlBOgo3us+nuqZUpp>4N&0>Lpp!VQf9EkBUwJbuQ}m|6!-m-tT)D;NR0dcwHIT2 z(;JyJXQwbuAv}IR^aDgO%AcoC_FPnY#4a zS66)g`yVdT1{1uwsOFQPMj=MMRz1GtHiKpUl)lhF&`FiEwM*zAk?l<`I?@ny-i~rT z;^$+)AN`A6_;8w-i;16(oXYSZ=ABb6&sLh?&Y6sKDcZ%DDdwruxjc&npBUi#M$k#e zqczgXNn};kPg&atI=XfDcJE82!9SE^jhDABV4qEC4DjF!xJwnwQ?_p=c=bCDynQ|& zd!q0}mo|E@fmFHj8ch&vNL|PvLRzu(HQUk;nX{Jz7V_Tbc%K% za}ryB#K*cw&?)DSeV;W+BIj7KC-D<>z`IZ9+}r5zM1yk)NrH1&zaq`8cAe}tcQX-2|CK>0I)5j#ivWs zGGrv@u-$zIuN{uL<7P(2dHp3OcxBt+smX+VjQPR5g2vuiOo%q?U=zVl?J4Wr8{bIe zs|%xl7zjG$&bRlcP*LG`)_lkHs%Egu{mR-G<(+V48d=&OZ;WvxXGV7go>J^GcO$Ql z-xQ`7&WyJcbj+6KBqira%g32c@q+i4@U9wS^ zXEMRhHOrsDWQs7JtdZFPvuVuV@ATFdg3j;npYNLulgPUah2u8)S!ZIcjasF}8+~#t ztgp;t@AtOt?A3R}_lNB1R}2}nmr5!{|Gw!?zCR^Bd(9W-4RP=tJt#kou{L{vzWs+J?^dN?eUvq$`;>DOz_C7$6hzbmtm>eroKD^ z6W9|&y;qz1%+zeTTZi^YSD4}N6i)4w z3_gZ8k8mya_%`8hjdi#LM20AO$mab!oiBkzCM$@h1`~RCJL(z>`x6@c$d6r7O*V5_ z%(Z_U+b5jyMG3m(ua`{l+^@W|vM%{p^rYX-abkah_4PFd34TPT!bu!sBywMkNZ+QN z%KA{rH0jXb^xqe)j*iV>tUn~KX;-=9(q?(NR++}Q!h?xakdlWTbFYxFRG-CI7?{3g z5p?<+T$Q4*TYS;uzvtmI1&efuEa zAu_hb4Hq|e;$TcS!F_v`zmMN8!)D~H>sA|Pu+EC)ub&7yr@r+_XApC^KSzsmH|4&% zG?MDMl@|XaS~~a5mzX=#WjlGs(go*mteneTFv0)OMjfYqn~$Y@E$!rFAm(cm6A#4_ zbhdbS@}KM@k;kaUZc-6+nwO4MKOyMIe3B0`emaNAyKI#=&2+|nCXGiA-ZsJQM}9v& zHkFT^Nq9DtFFcJ^y!N{=M9{ejxlgnc`+S4f;yOJ+Cq+&8M@BClZm)9FCUnmNR+kw$ zawg6lzo(tAY9(Zfvrh(m8V}FMYGu9lrgP3>*~i2jdI>r=1gv|?>PX}r^-dNn1f6h8 zhZm08%a}zA)9bf1Ggxle52tumd)({b(79jggnfzJXr<>a#d>3;nx=TiG1o>x-c7kP zvIlN&A+eCfs4K~v_+!h^xIUsWj(tjK3Z+?_!rE^>R@K{WjCWMp?Of$1?7b;E$eYxJ zlYENYpS1N+^l;tvHNV4q68XsVZv9X~57#5wdWEd1@KYC#^u*FHV$W0x550)Gfb*4y zY-2Dm!-oxx?y)GBW93#0bPCIpm?!uAj=uyya`^mjnRyaf=F-?A6T#2fUYB`;;|%!3 z$(%Ry)brSOo!V8+98cVsmUcMfz6pNSo<+w^E(iOO?$6P4VIJG{f%U*If{rc?M}i>1 zPx1GNxJ`ed`u#o2G(9z5cF$G8nQ0Ew9v+_J{pf_Z2;5%P;4#JN+?z1Hx>9Uvk|98; zc?x^;>fniHg3k4hcpA$I5_wHSUT9N)5^s2h(Oc8t3}tnHX07M3w+g{$uc^4>%eCu` z+Onp2HnaVUpHjtGYn84|H!)|B(rTEu=?888_G_jQc&Pm@^yn6XPV3M9owi1cn1R#J z=-{w<%;ha}d!~mY4nt-p>^V(w)_3s*yPs5JhL>4H&nHe{?4S4Pyd&s5uQgYDP2gd3 z6Ytoj9{#O<)%&!D4)=Sx7Pwty0ps;K#ByMV8y?J;@8F$iip$MNtk*J^V&fmL0D6^K z%=vb$L@q(+;hQU_No^#ua9+;zJpEB}+|$&!i5y1!p#F(=u($FPtZ|zERd-EKq8kOZc^nS_z4-lA1b}-;z~3n~uO=v%;xCSgu4s`7Jml~pDp3n7YfikXZEvW(rC!YpJZ#3Mou;TdKepSnhl;LN-~>(d>Z@r?(Mg?2pd zrRZUc!$@XcE{S}~rm!=F&_ny5@fzX{#Qbd7)|p_5dFt%Wxy$K!_$9Vl9RWA1G%PCini__Ru?t1xhg3dVht|6(HM85vxOZTR~ zur;(aU}K=dpH3QuCHT%_rXP%%0=GEgym9-~pW2w<0(9YLfBw$LK972x39y^QvYaMp zJ2&}}wEe5SNFv`^>owpa=+JtCN0q0H@rVywUo6h&Vq8s`A02gOu&`DOyKaI`{703cSA_mF9IoB9X{Rvx zjkLy323%HSTa^jl0;Wt;E)h54j<@Y|l&CyxhSw(G?JTsI!{&AxmbVdf zVxQmbjrdF=)AF!=SR?e1v8=lNLERSIU7>Z~DsfLjs-iOc)svTSQlnw%r*|g!_@h$K zz$f`w!QPBhuJ`9K9}a)g=DKX|Cmy?4NceZfLPbV`pVXql%feQ)c;}AWOVKlPSW`Fk z7n<`I@%gH%z{RVkc*1e1E0-0B{fRg&>c3|iOBeRb-jw@Jt9Yf20TS7ONtAjMSEma! z!h;EWI9XT2)ctV=8`O)@;$yPHZGLdGAG>Od_X_;GzwojISLdOhfsc`2rggOs`y5YP z1!n$A?+&4d{@7QVIcEkueL4Kg{)$=5I`D2v=ZZV-ta4N1N}36-*DJ~IR+o*Li>w88 z^3P(W61`QMco=f$tooK(5;>vv71yR83=LGbEX=0DSHZf62Gu-9;-q`|{LMxD7W;!U zyBtmM_JhBAi^mJGvGXML_}nRs+Lh^QN8BE_)bMx0A?I zj#{mo{OpanGIDk|9iG&7KL0G^9F{$%@=8w68#fJans~m?6u-{7Y%*s+%n8n1GqwA< zfQiRiKI$OoaBYyg*xI_v!oY&ooOF!Ep z60~I=yEA?IkC~?jUVGahLl+RZ>UDh4?{5}nhw~N*vCm<96o$Pvw>@Jp}>$mbS$@c|4wC`py zF7sLNouCtFp!2em(C2Ns!Tp=KaSC^Mk@=IjZ-M^OIGflkmP+&RgtELFF5AJv=1JJs z2Az+<9o9U|^W)BuaKRajotleg(_hrHWV8(t`yAkx%)CiwhGQ0Ulv=>PKl!@7%V!=t zo|h5hz-fgyKHpdyu`|VM-31)iiz{%x1F0tp?;<_?>2xu-IEF<25cqo`g3!aXx^y-@ zYij&cV!HJewOQ;$xzTr`*%9A2?(WlJVS>jd(6$|0$j90&54$l5&0tBg?dLb`%hzoE z*vW2UPH_H;1}(wQ;mSyvf9^DR#3fKC95aXI6@g~=tn z-FBYpHF5r`GkK{?^)(OcVqA7A51GYoHq%9N66X|)qn^^8MI^Flm`CsCc_-~r9BpJv$vKVBStPTu5)?^!jG zrzMeV9NtoI?(@E*x{g>eEnb{<$F#|E4r|v{GF4e|#pj}v%!`~&aH|Wk2eX{=u~x&K zyHtsLzt$w~7Znh6{_YQt=pfFy%XHD@oBJbIKJQo@v=x6fV#s(udloZd!+6cY-SKL9 z;cN2LCipPZ$&i#2*_hdD|Gbmhv)JW7CBHZA-7pASDIJ$ z_R`|O=DFz%=*+Uduz@vpYNkEBGK;0bKHscvC!FW1PofYJhl{=Kk?bxm#Bw8o+g$HX zVO6_#__h*sj!%?_iV){Yo+wYpO`OM{>I}3+PGi+{Ctq6HPh;;mIFjQJTHsp8N~JC( zo8UU+;9JKg}`q9#_hVxk8>gTmFcNg5~aL~!!nZ&trk4yL+ z*HSER{SWs|yBX}(&$QMYg3ju%;k<9exnO`J$as-t z+W@C4&cYLE65M8jXT;=wBsrC0>ZNZ&&pw*QwpTu1A13Ie4TrpH=_1x;uByH1cRbFD zuzn@(X_+jWe9AC5i(T8VFx9Z=fpZMb)#Xo^;M}7f(>wBWFrA9Eu1I43JSWmcX!Cr* z{e+$rUP7Ex%p^*;6LebA-tH;-LyJdx-o81;IgdFD-YKYDGoMCf7vi|hX? ziZNFGOp;seELOjrnYDzVBQxD0+Vz7(-ag!j%5bs5&G5vTOXtB!SPrUY@YN zo9l(kxj#;NyJCzh-R>+Wm1beGj>jbVJ`wS(`h3JVLFabFN_*W)5;-w?VS3es#8%EqOw<7F*^wr!nmPlj#jgjC-&HYpBRwDkn$7D^vxh|%CVl#VG8Stx@ z5||=mX0a{<-_kAf9(ZI?a9zk*6MSOGtO|?I#=@Tbs2+Vm_<{N0q)or0|LGBL0AZ&k z*TmH}{jxtj7J(-f`QaAN%U^GjQ9i#!cz4OC@2H*={VtcxB}Mdiu&tFH z`qv9~4h|_z{t|>yM-}gm4HEM;ED35_|AxS_FOANkPdPa6<!DB1+VD(%s-0rJ#AuoR7 z{XV|{pWQY%Y}qQs@=k9892VX0Ewrx+m@4@>dI_WY)P(jYfr0<@=k%XW8?7RvdfCq| z1DV*0pFhR>=g^wY%M{{C=5^DLu}H72sV zP`zW1m7VY&(iO_*kF4o&6se+qq8&#aYNF^xVLk7MTDQf1V1CBbPo;$$F1EA^N6pB? zv}d$0>YsN5hqkX5SlG(J_K^kev|3ber7n&AVaSE#rOtfGz;PDItJ&#SB}hZ_H6IFP z#XiKN`W;in#WoHnDhhwaOol#>{4w}o`$|UCz5(!cyev*sNf<`f9w-a9z~QASZ87PH z9&ksP2OND^1~@t-G6cmDKQbOGPZLfeeyq4&&mO;k`1$UtNza#y>I+YVN)EDEA$gVG z#dlRTpn5g?TGhw5d-lLSop>dowjS`UZmHZ{T@?O%>t0cD8-r^c!69@u1K`jpl7wn> z5uiUXIcMz`NS7hgPdYu z*vWL)c{pFzPpd zoj6eAyCx4EJQ@6Ti1(CZv%Bj5*o(sKx5)|)MPxW_d@oE{X91LcYcBETQ+oa80pOrHYImHk64YcIA8fNnc$js{ z2!ox7AK|;QVOwL-Jg@T?j;?(ks9xAzUj&kWeZNB=h>G_3<)ICCohsthsvH4B^Fdutz7zoK=+1)VL+JZmJf-Zx>Z=s` zJ8pg1d#V!Y&w+gr&-gwddsk$3KD5|@{Kfv#KM7^r!j$hn$?&u|kSz!;15XRqp6Uk= z%wAZ-On&GZ`>)x#S`*U$+NI$BW)`sM)&*uWmV;N-#l@M0NPhxd`S*mKMExFn1(;t< zRU!S^#-ZSH;SZ{Rldt#Gf4+n4i$(bT(W{!Mzh*gp2eoUQ3|x|VdFg2M0C-ymXD)Mz zK$d&<%=b>?kdI}d(V5f_wqE%6Siijjgze!{GNnWKFGjDn&dc0LSsz<2Phg1|sz({qu=k__7>Dn^v%e#Pd|r?cnAEYsozR32o<;J-`%+j*7#4( z|4vqcyORQUo#^TViTJftQMyuql_mE*pyH*_lU2U)Q#Jzak5sdM*ZeC~kG9mZS7kbk z-XnKenJ(xJNB--v!6j{TU$nk!fzcz!4Yc8eg#r{H-Y@3Lq6^E|B?eXhZtTq&)`gGM zxUdr0F(9R}@;bq~7#z=d5~vV~%l*0}B(Gm~W3Gf}zkvod&zlZuI zO*gio_bj-DUeM)AXh44@3(?2h`a$rv6W#VHl5pRZwQP19eRwWXq4MmNQ7|7Wqi#G< z3OemVpE&P9_4Q?Hk$1zXsNcRTy#=fBNBwUsbEKXYo8cAP%5sR zE7lJf7G_uyoJ8S4Ih_x$y>a;d`;zqeKcm1}E0fJEtP~t+1!jA0B78nVDp@TxSfkKe z^ZyHKmJvRKfN8sL(;}4rV=3|PZgv+?{jCV?Z0>Vc(H7xpODb{;*DLtl6g`d3JR^-*(Pb9$GDXF0N?yfwmnZDXy$i zaLl64M|N5d`tL2g!J9$cN8viZUv8!ZMF0C|aFQDN$2%G0*++c;P}ch~HZu9KG@56@ z1+V|TDTDA~dj4i{aLNK!}>`Q=})nm_)5+O(w{3dXS_6d z(D~w;$j$er|Iwf2`Bl3D0fbLU_jj6x{?dbLhhH%!5q*{41ElvlTZ%&J=(L0Vt2q2J z>UmP0e+-D1%#jWg?-#r0YSP$TL+d>yqW5;sA%utGLO%20KElKGg_f~WMl{dz=FFYm zFH*?fX|7hEeVB^uz3xiQQ1wAU_^SP-rL|HocxmD|{Y6_GKJD%e?^e-;GHNk?Q_O?l z$QZcGwp0S13EWWVXGZoJbullCAp!O0NN5HtUGYQxIo(rqyR3=(Q2y6Xzr!RocEbq8 zV{TLh*`goN{yD@JeyaE=4_h9o%PoBB13Jk|jzIg-jqkEL``<^Vs&!e$F9PF>qyaD;CX7;re6wjNt%5FFX zAb)C;HJQe|p${cQe>F676Ys%QJbZCEP8cRz+%$^+tpkO?#Lia#K~Pc|eq(&C7;N2g zw(m>?iVwSg{c~gtL+52fuiesghfsW2eD3VuiF;^&ECNb3zD%L`kVXA@a*hJ(@4lt{ zXz*H_1U!>sQZZrK2b65x6BQ(d;F)v&zwiFhhLW|{q<5MP1DyvK#w#YvK->2?9u-xD z&;O+S5C7yw_?%>EZ4P7*KIiXIEcAYk;y0Beq%^s=$e&j7-#O4@kK)IngIBI<8e|KC}sFb3j{Jr5uo- z##sq&)NkjO9C>rgf9^9m{@vXrLk97~W%^Lu%KiXkRl5H3ylx+OaZ{_Zsb2)HKNmkc zK+OBp{Qd1r^L7Y`dAX#p9xehttfNJrGSPb9klaOQ{vdm|@4C!$ss-WmUJ?iA=h1Nr z{_s~!u|7P9{MUu+ib48C$X>Y>`@ACUWZ<6chTwV8UXYMpsy=r_4A$=pIOM@bhNq1@ zQ%dv(fm6Ip>G$XYaH{p|JAZSeKgoyN{w{JN`)c-SGoWEW@$=CG0p)f{=sX;5{Ve2} zF^Ufp6tn$#vL-3~h7CpWIhYXh8C(5x!ZLe7dkk~*S!Hqf&Osr@lU@&oTXaNF6a9*G zG-{9S`ij9%_vn>5?qhRsDe>^(;^!3_&uIY(U;fy z#s_PbkUTZ>3O$ln$xyH+{PX?|;yp^b{;+ROf-ubBffT)_Hhida*QO$R7_hsay2uk> z0)&cZcxl>D{DX%Xc$H8gJPhabv(42-`MSsj>D5E72oKGD$U>Kz^C`d>L~=Hc6OIPS=5HLUk;pz=Nko&JS88$+g=Io+l!v? z(?t5*@e!m-|EHfbl^JxK$|8Mc%RRLx>?>NomRO;Jx=$&EpL@20#!Pbve+>V4)D{x& z32rY|4N9UK01Mgmy;Vk%&^aPS?Q{Yewo4|YSKCa2*A^iPht4{|{eQxO4@O}Ce+54sG z{YyRPkUkHqO%?l=jZ*YABBX7`<`LSTr*CqN{_xIH{?D~n{AK2d1S~UVI8&zF2YB>P z?$B%!h0j?=Sk$$2VJdUS^SzN1!0!^zS>Z1wfMK?#bMNP##Qrs{a|mj`@OCz z0r=u_*m|_Q1`Knm{;B0V48Hh0dzWfg4hjT0$bRf-f9e^<{~oJB_O7B3`uZRZ+8^Dw zZvP@Z(7c9N$o@dJtv$roKO@$?}5 z`O0w3z|9u<_p1KK9;W9|yz$kRXNhK64BmPE$nx&@9x(Y_^_B{89{aNNT)W(o3=NHB z`a`g3z{1>jgclTpLv&{h%VH4zbZxcP?+QlwsP)GpEK0fve`Nd*_O@xG``U6(N}cm& zMfrIT`}U>?62eQZc)la&-pIhOs^7HpKNIh-#ec}@ekBZTe~;}?4%UGRM-M5=Q!fMR z_ZNM7UY3HB-=Fw5>mzynh-P{6i52Y+oBp--!~c1oZod@MdDcOcUsjb<)Y_0i{vtZw z>rw6!I`5n+;c^SBBHFfU9I1sxpGk;x5c+)6DfPIjE=+57B-Lfk0NdH7SQ^_> z5SP7PaOxtm_o1)ya=siWo z?WEYp) zm4UL7JHGSw_5u~#dVyP)q+n*LnK1tQAe0TejKBLl37U3k{D|IB0yY8g>NF{ z0kE#C`#x+~2wo9$b=EGy;TEGOCd#|1p&h;E3Dy!wDwcdlL~6ohZn z(nmius6vik?Cr|7V_-?PW;jTv6l_`1OX}Q#^yh1Z)r0rcXkMPvs!Y;!H`1Tq^xk*3 zGb8(1eJPYHpix8N=jjt8(hVnMe?lFF2A5S;;h2wnNhs(8GS3gB{i&0HS}smo`#26l z&)Ku3-hOjn{J7@#$-E-KK*PpX6DB~R_qFu6uevbO=O^JELbcD(yhc0gch#yV==^xJ zzO|Nx3(fCyTI#>Uv}7st-p@4*8N+?>Mo`w} zr70jPQ89bOy%b3I3&>phgzAg7CD&_|CeisK)Z0TD=RoHRALY78vs{GF;~5g%&q=6W z*85=KibEL6zaHhOn;_o!gb8MOx}P)p!26TCUHOT5RSq^msq?DDdz2yxs=-DRK+T(c z14NYo=favt*9QGMauZ88rA zk-x}ow2HiLB@esmOqedY^@2{tk?B|&QAlIRc>Iik9^|JJ(pn0VlS1mitssOV^>NYEAsE$nZ7r0 zo89ox{>zs4h#ynR^=?oqm0 z_fM-2sMkKR+{Pvbfo)sc#u6FkDt?9SOw*vh`ST^??o#lk`ax;`OSC^Nngu-P8&E!q z?Tl>Mp)#~TSB}X!`c_F$es5_g<-U0g%Ktpbc7J&JGm_`^BesvJR?OkjE$OiIoB_bw zWEx{bF9F8}L?XOh^&t11%T_}+qoC?W@BPBPWq`*ZD(!s&I**6**u<2O(EGD8!FoJ< z_M!aY5lKaZ$1&m*eykFVJN>c{Kk7L{XU;?+eoyZ-=)I*W3wJzqe0w0I7g&&_)g@nw zLC@gk+2@@4@T0`gwQav=!J-zkm`zYI5PsmD@b)!2571xKvclA+D8Cms`TF3|r4UN| zIc2%(yix5@|w6g!Fg#hlETmH<>h%r zGO7m@8OvUXpF#0+@TfcIn^6=$hw0W2zzdpiM&%QC#V8R!&uDzD$r6FK(l@@1e8eF4 z6`8xQZcl*ckC|zH@sxq9SIJvcRgr$0IR1PC6;ZuFa*)qqp%BSyU`8k}6QKT$QXAW# z%%IPd{pl}IIGT}%=zXwDO<-LVh84Un4UOdkK=I*5(#s)!GC9TAI>Hb?9Z)9Z&mzk(jT+(ePWk7_XKaxDJbZlP;>cNq&nKxWc}qUeP~@BE z`8}=LRtKIw`gL%yz7H^~JSw<bT#c&k=cu6=!c_*Rxndax))6(iFKD@yxEMSLA z!06rAW<8%0^#U=S=jVw0{)n63#OR0)SQH!fPh=D(y{%&{ZjyRz`_ z4@*KYmUQYFyoka1*Ph3uzD$FplUlS5B~?JdT5tKK5Q-0H9*on8<{^8RdcS{%=naI= zO%uc8YnjOZ27KESJldI0;V&X6WVR;&(fcf{IC+GSj|$IsnWyo6poW$uhL4EnxuWQm z1%BZ0>fqd-C42;2x%9{~s(JTX?V~8iM0Sdtt8ZBp$KmbP* zl`Il#CxO!!JvCpV{(g*yE+yz5TJN^Inx|&}Q-5!z?z~8CfZ~k{8s6)wbI9J^VvRjU z7?FQZF*p*CV2NZ=XfF3Z&0*Op{t{AyX85i7+QGP|qU$`JiL4<@Ga^|9Izg zK$v#^X5cb+QDs`7Q9Ot#qvs%)H*Q%DUHXdC74l4B>gWF0$)FVe*QuMTz;GNiQ zQu|{6|GYE)fAh|z>4z!v&YSsvq2^;+$L?R@YwX;>Dt?{o+<$TgQz>AZd#LA$E7|4+ znh99q+H$|UMt%_UP%2^q{LIVPgXDPW*~i4Zp~`kY&r}kbWy^cr??k_=CB0F{p`mpw zc=4`BXZ|F{sxx#=-QOG69J^}qh|3Z`b~kRHE~yYp?MzP{ZC}BZhqsuu5_II8R;yJa zN#y$}{BmCiI`%^CI|YPja1QHjRY&}0FpuwDv)cAo@dxxd7t7^{cc+_AEPbfR$G$e- z%LR4on4`#t)Cz)5g}qS-zaxo^D+kEd5_GCgF>?%yJL(O<^$1dGGNHp7>?X z{=arXmiWa4iN9VF`Ir#DpPxp?686>q@1ZGzPDJH3*UV%R8A#i_?;z+Hic;4$ou4A+ zOQ@2HMHaA2MSBW7{$E>f8CF&I{14M19tBa6ZV3qi0c&qyZ%PDFLE-=s0!k{SbP9@6 zqNtRJAR!Xc-6f%VAb{SeFQOvsC{QHzNYDde>-&ziL?B?6D`|6|2M~sKpulyxCu@FXo;j zkR8VcH&PBTfsrtiFWw^hKWVAmzvy1T)lQeC6$r&dvg}!NzmPX>(@6UZqoX8I zxt%UUg63+Z>pyMyhlmcdzA8B4fYy^eJO5Tk7ab6;za#8iicoV>HVxm}LSndoxb|S{ z63J8aR>k@;xh<&TeqeMycZ}``KUhPQ8zLIp+P0DJ=Slh#R1DF&_1nvdFO1M8rA{(s z*LHM?;!%@h&oV&|zq<2AdRbs~^4}>LbTB zy+CC~8;{z#lAaH!EksDwq(bVi?jo1}$uCf1>$ZW7}DH*Et z#{ATuMXbN?&6+IdpC{-LS>IHNKOQx;8IFE9TY_v=(No*0?jbkE11O6yI={n4xYE6G z@|i*Jk`MK8(V=Nwn41K>ymxN=H{&XDw@=ZQn(8rHnw}`tB8W$&COtG*-xVSpkHh69 z?(ZP(3Hqf67@eY5+gT67aq@OG3Mw5K9a@1@wuis5{&wYr+Y%J3NS@v7{n^$hXrsKU zOJXY?)ox_GDmze$>^?nJVG**6NSJ+LUdQMp_YFUNiSZ*`**(#P(XkLJ3o^Yyj>dWO zkgs9;z9;;RBuUE=z5d?1Ynw(Fjh&)9UA2q#iv@P747+V18D~y*Q(!H$#cllo@w4#Cfk44X& z0`{<4MEjSa2iC{LEFfZl>0ybXmU(U#2^y7~gf1qoB2Gk?UsPo|qEiNehE-hpXne!@ z%>?UW#7sE(d2jYMQaph3n!(n^OlA8>0_zhL`2NK5P!I3LzJ0wKK#CT>uetxLX%%r` z-bvVI^gw44x@c&h;n7%sIIZ3+53!Nh>%-F%q3>)arVh{hrXgM5BXyj-##1exK8#Lf zQj6$_m! zbwH(stT*Pa;Zd2|XHnUt)yR8+*0uzxZ6s{0G`kI>Q%uEBx{LLXgrx^t9s0pXcJHt^ zv`JAG^X3piuNB1NQ)Om^motid;j%HI!2Avw(R_n(5hA2shU;P6M+$93&Ng9mURYQ_ z%&s{3+fw+^3XG2WHC36QfPKV@h4`qE%?h$HrXv}d=!%L+Z;5$};!)K*)aSF6F+27U zKh4U%k5E>;pV-3aPz}5Z*1eCDw@}F>I`pSKrW5mgxn!u*8K=^u$Q49*Gl}^_xg+{) zrO0mZ4IZTpFrZywD?#oQlIbQfZzEq8XSerdKCIsd}!^YXw8l0l5lJlC6#B4z8y zZ5M5>g!&z1KZ4HL=n5W{r2RW@^iB`m#a$x*DAA6FD9&k2;eZ}CKS=W+@x{sGorCUb zVS3oxW^tSQ2`M_VW_NUEWCgK4+xFS;kqg?mFY7Lnf;6*c~pP=^H8h`&?#G~OI zCQvX}A<~z9RO{`N9pwElq0mFTabZF6-E+*YX4=_Hk70C5vrn91!1_!BbqCq;A*+aE z*IZ-RuoF7Js}Ld0hey-bBah6H79xy8z0UFG+lbOi%lx4}6>H!!>5oJr$1iP70x z_3~QP-bbi{Y#YyD@19pa-2S&|_yjFFlU(wt1CP#gl}X_oDv`!N)~8P1*+p<>uXCm` ze$2HhhXbDC{>^eR)FMd1n}w253;G`^0-b_{| z>d+4s5%DkukCCF;DS^^5H`fr(2hVq2(z>8>$q9jMLVBo%Ri#s?OEIGJL&yBL1Loh` zUj%eubm-gca0-q%c^)@D{u+$VBcTs`oX@FHt<`4_ERt7|$vJTbjTjFU@^tX{$Ek5%?7#$Mlv3EPlIQi+kyT^JlI!@QE-Yl7rp!_+_FVAE1 z3%>gtsUHw^Mb%T?jx-GGq8ikm)-^{8kkl+_WeYop-IHHaeq(fQ{P+CS$k5 zxalTOA+HQ0Dasw2d?FQl=a)E;yRz5P8Exe1OqBH0MSB^x&xKtnL=>|`5>9pPBI1(O zcmIc<`KW}WF@I4PO!f5eoR+*?eVuV<14&D?%H1$uLk6FE1PG9tp(1C$_q&JcVf~bK zln=FY(BZZMCM&g59v)D?3uB5S~tf_vS)Tyk(&X>!nVv@ zQE^!+-6J=!_s`dMN%XAqko3^IwB45b$cjTB+a9*AFHM?Rv)DPUfktHihoepud3?G@ ziH>kmy!KdFK{$t|A5jmwqg2M>ON`ujR6p`{O0GjLGM(&Q6#4N0A=2-&Ka_J|&6jzu z+ZfLDjqN_P^J~zj#NgAEs8H6IAGMZih-SQ-hFXXRnqmD88I;GPcjvnT)_C)gP>FoT zj3jIhlwjmd2}}>On55VHRj|Ir1t#1aM(0lS)ytZp2>pL6_=(i7Zjqb zAWII$qeM?FFKfIhL@qeh?yyJgA>Gz;Vu$$WT&w1WhYe2NH~Z%5A^veqIIH>mI2lUP zM$6qQgw3@O(mN2&b4Gcz{U(!7<59}tJ&IYIVx*?fsM;iS52<^6x_t;+*HjbjlLi-@ zyn6&+bPGnO`ePu+Cu=NjqIt1(Sr41f!()@3wd8`jXQijB{nSO31svQW)(a5d=Ys*H zbXc4g65x0oyV$TF-75tG!!UpT)v&z^;JYN z^;dveng<$qWQ^#ZI3BIc`_d=CU4Y!09JN8QK4c5~>b5*gJ_!^@J(aIx_IW}ysu`n0 z|8VHua2f@gdGF%j+p$&T^=G*x-kUC{%1FU7NfI8Fe{`81X39qtWfeSVi1rbK{;}o$ z!+p0WOOLY0$!FN68XWqa_=Tj5E>Q{;Kjv;Eh0VKp*IOj+Ywdzwt{*!y?1)F(i`!f| zALSulKe9D+4E7KTPJQ&y?=Y07skvi#!!%o$@$g*Kt*l-xD<(pZHmQj*A=uod?>9$Y zth=Dk8;h&nVK^xLlxhkoQ5n)dHTtadU=JzVSKm3rV}`~#{a-MBE)i;Ybr^pcSS_f2 zBOyZltfh1ek1ZoNR~V;rm!4qp6779MJzbRFr#Sm`ZVB@Kw??P?wLQdb%ID`IM(5%S zQYs5SEDsQqtY3}MNw$LK>ao1Uhq?PXCGM+8ElgSIJm8KJvKsM z&T?q@l!nc_$B<@@HoHfg)AeEar_)1gQ6m+-qQ5hoO14VSL&LKgOKTWT`272h zrXr??qTW4aCU>y+(_cD{H!Q6p|MsabNgi=Q+fK@2g*bR@p4-?lmH1-hVkskQ#_wGu z?by2@F^r%6eEnfk%^Y=jhoG$2T!UG48AGQ<98~92B^5;@9obPF$g8oJW95N>P zqK+UBVb1H1*O@^+`y;8xi-?&ku<9QR^~R=2h$GTViX40YsK$1HvrSzIuJK!av4vZP z)U-}5d&O2m5@%0v{1XHDbK3W4r)Md+4+`w6+N>%7`SV$Z+-J8(!S7{F%=qr*gZr{; z#;NVQQQ$r~IVk#ICsz#qYeskDPvQjB>s6EUJb)K|LH-dg`iR0dF4reMPhjsOJkM6` zEv$ilP#g4j-2nC5u?qWWB*ipgy_cselu|aI67uJ_B9}tPmqC3pqe=$NFaP(1@0XLS zcyuSh{nWNd3$-OS@AhO(6JtL-21Tn*{n4&wgU^3jedzF68ZK3xq1#KCftW^|u}+@~ zi2tr&Z|Nsc4>M6@R6iXI^rzyd@||3#dO%FD!`K4+Hld?pJmONm7Bn zS1iBlye%LLS8(bCHXt*QNA+qu_Y@y|F5D`y7Ey*>huxDKwCAB)gAN~Uyy~Eag=nF~ z0B|2*Y}QA)cLvn|YiUu74M>3cz{bB1M@ASyf5<4*#i4mKpCF&-clPB9&Vl;D3txY~ zu|CHP|DCemB@P{j79ce?rQ;m1?$_@S$7dP%tGUY^JFH&SMT0pn`$h#MBl1h$X93h} z@v`_xJ_rZ=DB9Q5+IQ9&ziYe-|Ll;1FRn-m5_L^LEyu6YUcma(&-+{Q=JtT~dOYP?ys`n#JNc*p z*RAis9)>O2tT*?9da!j_=R00$pdL?{sFgpF9o(PO-D^t+fqZ-nm%o?r0sX1e@)m~dfxXg~7bzEy z0`lQ#L$U^bg8RUGMI?_oDtO`J#hRiv#S@TIt>5F+9$q-ofu3aPgB;8dqrGX!H3fNa z_-A+wRzVbk17gx_V88D?`+e4@c$^@A$zUrBgc;bw$U&Sa|6d@Vf4}p}=?o$W-*?fE z(xq7ee#1p^@#~JK;PnP%L7{6LdUcf&VOZjT`}D}&pU6nTXY1aK{<}5`PAISjyBnl>J7D)j0OT{0k@2_3}^@_ROK7n7p1N~u$JI14U8`KA$$++;qmlV|N zU5S}kzio99KL7gVxz_U&(DG)>wEB5|__(s=dd$o3yJLhKtXsYdEJsaFDidIFEIF@;So|+xmMa9LZ7ggQb4$R63|0sP`M!w!+QDt{X)bmP;Il3n5tA2R6rz{r$o+9pr`2iwv0jq_rH7T z!`P2Z#b)_xb`-(>8_#g-^cVxYl;k-jC~VbC`1jC#b3qRQAn$(;^0UMY{BYE}s39EI zXEbZS;9Q!>4}S>m+|g~k4tsQbFp-&_gWkQiH@7RUhK}8wKcZ>{@Xzg7^V>R7pnhO7 zPcZSrBslL`bT2LaE(82ixrz5_GAkhP)8sI5m**a+SFEY|(czbS7H0C8SA;QKS`!>D zbDx6;W|Xt#92G_2sx1LIi<3)`bHPEXR3KHBd}u3I-gp`C`3o4IRc&J zgdd`;c{R0Q!uRH{NbpxLgZ_X4heTPi;XnNPz)5#K4F$DD^{ z`@@{|vG>Z8>a}U8RV$$)wfx;O6X0)dxqL2Tdj;fk*K~eSQy=(uhcWDt_#Xhzr{0t6 zpGgJ&E89eo^Kl@EKbCi1`W(<*fXNbEQ+s$OpdjCmk19`K{dBXTdcShzVdhHfRI=?E z$TuB%ctgJmQsr-*m6HJeOR<+$;6yj*A0e~EZIyHb|E|Ew3nA>m7lM3> zh&bAIn?S$wwT3INwhOMo*R4G%YxBn;Cijwc=1MO3dA^ua`AGy;xYh6e#A6D2@}MPd zHo6?rO>R0b9tF&vW`O)t!aie5}a_?kkdK(6 zX_BjXk^rw>_-UZs@B`r0Sk)bbksQc}@n@re<~4wi=P#YI?R`{8;NKX3`#{DD?59(F zX$@B#6Few;Rfa#|5A>AV-SSEo8|*WBCvNzrEX*kVQK=+n5qh(knyP}-pU-Am^aFkxy3FQd z_(46n*{i~St#5!IqjS$UV?Tp=KvmG2y@z?g{=9o-cCM8OoY(%BxF`w_MfmS`HALyx zIP~((t5l{1F4)~cm_ejY9#;DWYRbG z!GIgK)3=Wx5=P-w+cpxWlxYYX`^DliPz^Od-U_ud0`eap^t$}@CFn=Mako^@(t`N? zqw12wHFuyN_7k6(9A&|N)!pLk+&lv0{l4-K@2T4tVXa;I*LKm9kVP9;DRny!oa@n& zCj3nS?(aDDsOigJh<7NX$SJ!D;^B=xqk9(A>(fx3Ud%BYBj_hS^B!Zr37CiTtobzE z8AX7XnvIU)PS3w4e81xM>7~~&pqEriN2RSJ1S6kM&+lOK+;%8ab4aoGmL7&MT=7j4 zhh-{%8hCb2L+lAxl8<5U7srj9racXT_1u)n;^o^4A;k%PeA9b?-k2h^1@moF~Low3NS5WQ?{+$ zH0CcBZl4;igaR0vqo!|y^B#1*r(dRHo3LJT&UqScHgMi8o}L?H>I43rM0a<+=@F>E z4gJ!tZ;(ejzLLY>Dvyi#aY$OG3{H^o7gV;>LtPe&fsq&36qsDTMFu zfALV>Vg>k+LNaCiDFqAMP;>5c3jYM8cj2gB^9l`h$AcgduIYL5m9ndZMMR zj(57H;9o?$N+#B0P<5j{x8+%G_#4CDct>hQ3?CX>NX1P+Bp-tm@YWSjM4C6trxp;u zC9pBIcc%hG-9)=uw-NE`4*hA+vMoNJXAV0?K%bRgaN(Js7 z8|AOT-WwwFkv$@9#RGpWxErRLr~tEvjTh6?>_C0lZxaV-E1;JwJ@LHq%!KvOp`RUC z8-V>;T#i|e&jt1Nm9LVV%Jo6MvVNV$Y;y(VqgL`<1qA(py_99V6o)UCh8y3&-W<#` z5Z=+0Pa*Xz?0#jsi8n_X_GDtPOBJGq=iu}M2Io4cTil%Zr9ZHTzpu?wuPcH2|Ml{T z{43&MzZ4&Plibw+^4X(a+YB+SA*^TI^UYf;M-Z?46>==iU{Hs%X2o`}3GLA9z_zT} zJwCX1$9s~_RtaXTfPHJX?@=UcTp`eB_^feFJs!k6 zcf_16HUINImaytM2*8xM5y_iSV1NV^9M#=1w#?;XD|7 zZUdW-(fujp^55k($gc!tCF!ez>g~^q3iAW`!1Z=lk+Z;`+V9Bt(`_@Bm{X~x|j0%8rJWl7McH6Yy*1_j(qU}jSmm}u92otB0?UnXQM(k zGyXy$i&Xv@>s8P{O2sitcCemA9mNkcj3A$|XtE*ht^)F_n!e)_>!#v_-%H>7HfQ@2 z^v}}XsHP5N0Qtjq&!as`zXjmpK2fz_mDu~v?{lY)&+x#%#A#^#rpL7jiddEiSv(m|{7O7K7*uh2844XFBvYo?D& z4aCTs?=0rPMxbAN^=(xj0_`Wjh4SRVE=nu`Re05pe z3-l|TP#&%m4Y>lp@=IYb)*FL7q7~k&bqK)yMPsN<+jaOw4d+&a_By0Hm?L>vsvjkHe{lX``mV{y0iiG}6Za|NdSo_R7~yuwSh+Z96B+xnS++ zt#>)cCm`Jpy~*YYF8EBpb-+0+pGkSp{^TL+I#lq~JyPOl6}0wOW%fZm$Y)N5ho#_k zfWL@H{}bQ40r0BzAb&so%sSz`{bs%QL6p9nuz%LSPj${TfqdJ)5s#D+M@~5W#Ja?g z4d&lNJF}9Puz3PW1z+VF5%^Ee-Fst}d(gUBnQC1KhUcgMZcO@s{B>-e`reC-RRaAt z$3K64_zKK_2qwPBq+kK;Ps@HE(_l|3;rsoZeC8cLfXDnAP5;PwFu)A>PWQh32}muD z=I4W39B|FF3VuWJ3f$FO_P|eU4Z5*`G6<(tL3Ig_C7&Jx{_DcP`-yTDaK4z%ZFfc* z0RQ#gB6VBjF38XG+mjEXnLwX2<11@7XV1E)-wqMS70lXd> z-eq>Nrh&lE+a-GX)qypFez_KPg&8{u!ou1~#{bCvK#O0>yy`!kg@bGJ%(H!!;8c<` zJT5=FRY+mf0wFT0cH%=8$TJo z1-Tv3RZqTM1O25b@Lb3T@@d!qK`vVk^rxkd{yfSJ^v9;-ZZT3{n@hp6)kJmF+K^dNA5^u~N!1~i=29|Y6 z%Arr=-T!R=gIAS=CcFy10{=d3al#tc0``l$?rF}D4Uo@?7i#(Dr9eJHuMN9Ivw=O^ zX@2vv|0y3#KJcD~MdlBrJ%IeB-sgu8_-aymvHQW>_90xezB3TL>6Oa^!WGct=GrFf zNg$sjF=%G^8IX_kH4>QuZ6KfH0+W(62o)CT^m!#+BqMj!0g@$YR}Ev(A0 z9dX-*tH=I8OZ14m&naG5nx;&qB}NI>k$i4_`{4o<&&;&!aH$09>rJ#BYy~!$H+WS-+@4u>F})z(k&ff|=NAS1*Iap^HCH0Qs~sWVIG;~} z`19D7-QOvRCFa~AfQE&f#?Faf3K{_^``E&!Lg*QRbDuX@X1O?H4ERw{KKXv~Kl!o3Fh65HYQRtL%NaRy z8<0=14n(R)n}NQ6V?HVv>dys#3-dK>9sC2`D0A=$x8sHlmAYL@#^vEAiwA0?CM!^S za&}+A9F891M!j;_H~4FGS93e4r_&w+fxr(2)PUcCkQ=1TIP_Ujh`{*i(C zWx4-|5%e!LDrDD}nhU=E9;R&=9ECJ>a@PjSvH9@foUCsk1Wx{+4eae69BKQL) z@Ix}6+P_dF0$bCzM02u@Ls7#cUcX}mU}{qQ=;agF;bV`ynYmOppytT%s~IYl&@~bZ z>jgP*-uDgFp2PpCZ*lE|$@uSq^RAq3Cit2U=<5{MTcJag?Hsd?#JA)e$~w@ zt2YG3Ai9s9k%1LFaBHC7Q#L1gc>2Hz=QFzmebsdMc4My+y3liVIg%LQANO9$vae)d zzvzwI!bUBDeO{Ev3=j|o^E8k5NqG-Bf_U(l+R;aE1pz){JsJ}9j8PKSv`rk9rkjLj zGAf&gSp{L4;a?;wxk~WyrB=tF&~->Jb!W&Kn@=dVF#PfN6%a2^rwJIJO;{)Fe?*4A z#)D=MFGpwT%H0kG@$gK)&f_|nOv3jF8}U@AKgiD)YFWq*WBE%%z8Objm2pV0{;^P; z4=YUDOkXQ_1A;mFN0{w-7N93VcSH;@{QTQRrEuXrkWccRn(O@;z#cj^gtGAb2J&%c zm6krm1Mm$j76qT?0Ql#20#a@N3HYylZoE-a;RW~}g;ER)(>RnbX(H4}BLXkaTxc^l zP=R-Qy&pN)twP@n+$%-#mC)+xYeCfr@HYjqH+!~EgYyo5d+ZfZ2I7lNtUD}&c$I*+ z;?7K9FNT2pv*wTdZ`I-;e<-SO`CkSlR;PhJZVEJ=fS40w`g2cm!`!aFUeI(Ra7x(# z^^w{oDCVW;TD4CVMB3|oI_oGn4|jU>PdT7Kp9SeZK6e%e=Zi(2A#RIUkgz|-y5CQ{ zp$GPvHTRswNEpD6&h2e$ZTe?nMq1-IOXYFs%&W}L?u|UKc9xq2>AE9 zM38^Q1|dWH_ff=a$SN6w#oNA(5eE$ebaPq0Gq?LKC7v6D3jR z43XwSC5=+Pd!MuS-B0&@uICN+eSPn9{C?VZpR@K_YybE9uf6v<`_^__rj11Re%%@D zf8yc{VTRNnAK{O0;9uB!!9Om+I=t@!J_vv^5R{RWI75J?k$q1dT$cj+m4&Da(crrM zWNT{%Ly!UW!xG$P1dCFi%)teDlCtOm1?cjB8Ub5tD?4@vVZ6XN)5c-s@u&OJAYP$8 z7}q@+wtpBDV2Cq%vZ8yX$1)$!C0-0EaTbmKxMNA(7ohwDbYMj|^$&Ut%bpA%&*;W# zFV2|jxyWO=pSK4PXY^qSO0z0Z-rd8SCBU+%yv)OA#iHdtKu~}s!Ipu1gZ{SwzR3d~ zQGAo)$+s{nBQRWm-e^F)08lp{MSXzTB6u2APXPDjf#(IF9u6F=19k0<)CVchq3B?$ zt_z)U#hx^*T@wuc7KvU;wn+sXFKuBf1AA zNR29W@1Oh=@+}qMWeZC4697lR3kNVD4gjp448uP@2{1%iybx!2dbxY}vab9KFMvG7 z6;NRnQzlGg3NS<%>^AIw;sh8#s`@WK?ZB0SzsUp8PCnoP)XL2Zd7v*)f|7u8(V(8+ zi@IVC9ySCB{N@Fm|GDw%YMzGO?8%@qp8!LQA;VI-a>=3870d%LKXks^^%~?;SWl{L z0U49P%UVO@y z@7#)Cd_h^4UFDN7aFbv2llK710L`b)@j%!98UbE>k_T}FTk(re1t9;o^rtL! zl}{qTL4NTmRf#e{^9e=*U49P%UVNGl;t00o7oW5TQWk81y{_^JycXo!dQVD?@{i^d zj0C#;9s<1hl%U$V6~Fl8t4!Gm1NOSgC-A!B7w}1>)BAjQe#RUS)(*fyzsKtw7j&&N z0=)PnuGv|RUwjH2N7;#-K$TtP6L_5Q(|qdO+kdUW_l}kTh=K!@48(coE@lpAEB<>0Xg5Jo#nzoJ@q(s9iDfIp4eHgo$ex(NG zE5Fw3-nx{2dVTTx#>DUB8LxaO58?>=3V9FbS$O?NOUMiEa|vC|huwe!{Nht8I0r%V z=|@NUpVf{RpXP%&f@tuIPu%Bsy2>YboQYq2k}{$Yp!ozNfiAy?053izaGg8j7oWJ# zxpkFK;F#%8uh)5<7jrSC`SiW`q3?A@fES;{K^#G8e(@2A(CgisryzrAH7~sHl17jYCgezXdp174S2<|!^JZg&%R8m0h9{|^;F<5I99-r8r@mW z9Mm1gQr965y}^SasE_-5iNL+zwt(wqZXCLrm)O4>h5e6Tc_|;@4#$x`Z$;rv|NLtN zc;zKu5J#{T zoL*P?Gzd7zuldR43}pcFi5FfiT;~UWHD0{l*9Jy*_79EMU;lnU%PF1Z>G>8SMwfqu z0IztjoZMOZSI0Y?ZzI73o(=4EH9yDzH~Ezx3Z_#AV1A(a)HyEbT4w}!@yQ(go<}JC ztN8@?)#1Q^3;5muw8Q;90x+#G0s#M@95)}Mfxdh&uRysLP?rb&N&s^69jFgbE*R9K zX8?iUynuO$yMJBHOR}J!{K`w(GbsafUi!@g>NjzZS6<5J;w9uQNDDvhJ>(O29_T8c zS(m${Synolqi=<<6A@ZwXdd*@canon>a znhMM$fO!hq;hwj$ft)ztAC%+fV?K~`aHBpzxd2cP2mLAla&>?&P)>dxb;H~f2>j*+ zTsL#~ud8`U0rZn!c_|v;N#~{CJfMCP_ju(c2QFSh-rD^lzagKv^FUYm)Q``6Vl1W% z(0uwu1OGp+h8LeA7j^D9zxXtN0c9r;*y}2v`U5xlH9y&TQwC^0b&dzR_SXpT;#13# z&hq@?Qv%5U1;Acc`7{8y$uB-tE~gC8eCiwzbnUMZ;Ke6fpU(2Xnon>aS_I5kf_Vzs z;hwiDfm|@)AC%+fBYe)23cer%<+%S2Lmkjp1m+(oC%%-rq0QBQApYAGaNW%9cUSWg z_%*A1I~Qs0OF>8HrQa@ueoKdV<)xxko!y1Jwd;KOhii~e;VUQ=slZ-W`J@Ou;MY7K z=ua7-`Sim>{f}zMi%*6ijvyHP;!_>S|5CtSSNQ~g-8jGaRIrZb6N~}6{Cfm=@hJer z5!AphK3T4zEVuxBUFDN9aFAbo^4>%lpyww5G+lfb0bYE{-_W_)ujUinhqeGSzF^%4 z?QpLH|M;c#EZJ-@pFuh9`Ar<$kOw@4a@@RyeMe z%}XkvgZ#=%saq)nbYA*hBkA|@i&tKn58?=V3wisi_o3RGDGRp1URU{~3LNAYpQLtB z253IPNTAE_A;61I3EMlj;uoKMw^4S&fW5BrNe#HkFFv^hQ3hx}b&dzR_SXpT;*&Ut zBiM>xe98v-zj7C4sjGZa2M+R!Pc3^X12mstB+%ve5a7ip2N*!0@vr6+wGS0#$V0_o z-3P1O>p*32!w$@6P>y?k(*`%f0Z*YEH*XEWjXIDwpq%_}s>9}>|K{LEU`%0+{E z{(kDZGF337`gE4#{`=iP?3WdAAIj}_SMw71vrc?_A8LDm>M{%~`g`lYECl{174gbT zb%1ZM(~ysL@WM})kWbwF?kb-&fgk*u=flD%12mt0a=`x~&3W<362uXNfnR*${(FmE z<&ze0kY9YNJVF_u`2-_@F29EWFFpl>ID&2Y#V78+OWIXFX#)rO#V2L(`^jiN!APLX z?;*g8PesQ&xBAt5g4*GqV>1F5j6rDvN>fmRKR3e|3hL&d1b=pkF~pF%Fa#(Z0u&Ab z3WorNLx92|K;aOea0pO11Sm8D`iy`+BcRU+=raQPjDS8Ppw9^CGXnaIfIefO&lu=4 z2KtPFK4YNI80a$w`iy}-W1!C%=raNOOn^QUpw9&8GXeTcfIbtT&jjc*0s2gUK2xC2 z6zDSr`b>d7Q=rcj=raZSOo2XApwATOGXwg}fIc&z&kX1@1NzK>J~N=t4Cpfh`pkg7 zp+MhIpl>M92jkKg#KV88p!d=LDWv~@+M%Aai7B z`U#&4aR2@GuCBXuKnH$y-9_{MzZD7pKkV{aPZ>sb_8;=huJh#|u0fvEf!|{+1?+W| zC%V9cpXCX!-(y^Gkuner?9hDr;UWJ=G~~sn01!tI1b*?!@;qh11=#BMt(Lmtd$P>y?k^9DEO1D--TZr=KW8wp_kfpSHl z&H()n05|O7DLkN@FQ|uIqpk;n8{yYG%VmRlxNhe5 zyQ_K00NCSKUXlVh(0S?K9N2%j7_YpP0Qd&sfqVq%177~&67q?g-(BU?VBi72_~Zhf z$I*QH;i3LVHRQ!7aS%rk41V#6dmij6pA7lTr)cmTo8}XY1iJhl0=)R-0OAO?T9KEX(!%kLq;i%+&7j$m7U@rnD~ zOIP^>j;ryFPv*H40yLjsB+%ve5a7k9Fs}1jznV|*Tw4NAoDJd@+NlF|sXWU2RB%H+ zpQ=MSOHg-tL|xAYH|9U?EEf#w(NC%C`9Ln=S!cN-P-ncLt``6~@t2+D3_;!QHFf>J z^%FiPXr z;0M3nI~0I?NAu|?2mBw>oEM)0Kpa6B_{AsgdAh570$=Cg+x(;r=6#w^FcRqUdkFC2 zQ$AQtSh9Ph+T7mii?G%7|OMmKm5x5Z!;v33k zgL-8VC07Y<)V=GhPrjI{o0n2$9gwpu>nt}P)B`K1>%aR?xDVxi&eYYsG#qr4UwO#| zj0c^Ue)ov_J^bU9m&8FFK`#M~8Fui(PnM8R+R4#;1{2wK^~*|^pgYr4{6Se zPYxiCAPoHC6ZibvRX&a6GoKihlmVJgFcRqUdkFC2QzX~Akzah`UZ-}IPZ;PNzvd^q z_mlyePv46o`W{4p7oS?Hp#)X(i%$t4{}%vzUF8!F+~gOZDr+eNG@m-h16})T1bFeu zwx+ZEujUhcuG0b(2LoO~JKQ{D2vYtOf%yW;iGQH3YXg79ft+t0bsfrugL-N`bzKU` zWqKRMw4kmkJjWXLto@rzH~^J-W5WW{Gb6|_Wz z2=SBMi4~@ybi{K^#GEVIHu97k;vYeB#a*UF8!K z_`xqeNr_NBr}^}g1O5+b&Wle8!jM4}5U}>E`Q$4|X$}MSy2>YOKJ&>%oaWO%gbKf; zo4oiW4&n$J;TNB>LH@54qbzilPd0q!Q%g^pPcR1P^6wGg#V3azkU^9VSo>A;6a4)J zhG5@l2j(eghkM>K2Xf(ne^8E_kCs5L4&(zUC*O_wWDfde381l4?%Y3LP!Ch3uG>-t zBV4VsTsEjzY5;-%QUTY^-2LlnUK$1b|M_*Z0E3M*{PoY!zi0^x|NLH5N&USMLB=<( ze-K9TK9rm?xL|2W)!}$RIlBDcL;&WcXwW^FcY+5~alno;_9yN*Gc=;^+nGQ)p-ysA z45>dfMw$aTF8Fx;y&O9kN}?t4&bUdD5HB*aRh~74aPUT{-Y&) zz6LV|9dA$%27XX%U@%%hd_y^9S?ZHzU#f&WbCIX&P%aSEBl}TTl!4>f{X5Im zfx46;Z26--{Li$5^AVBiOawTL^!ymlpdMa4QwDjB<{69-y8Ipjym*!k=1qIAI8uYTSbJc!a94eTL){20%mpGeP-@eJzW#k0U6lmxv$`}-K9B^V4|JTnLH z(IG}K?tT@|pkC5vh%x(bV~lbiVrm9T_U|GeOMlox+2zk*|3taqiBfAmFw@niWF>Y?6; z3h+FCD{pYF2s)|lr8m!Y@nY9O;KE!_A0O6hd``aN z@EBrJKb=<~Vs!ae2=K})E?hi? ztc7v)SGu}#4f26IFLad;&@Tao2sQVD3jz#k#Vi26|Ax*PP+r3IbT{BMJdO_aao-nW zoj7LSlUBCiobNN~!7%v-4)%$#uCnh7QnX~%l>g8yBq-v-q?EFMvj4MHv(1XL{T0q{MFzrzErm;VkIDA#p-wEyr&NKiC#@?Yb_Rt+#>i*|KI^@sX@ z`LURF%yOBh>mna;$7{u$MV{^+u*!hvG2sR3h$n+{{f~2+teYO5^E|v(x-MD)nlATT z?7>#)vt-4xIUdgJ)0}@;aQEkCS3-iqEpDC1hihD1{=)lTf9YTGpb!)Q-yec_!2UuUV7=@8mk0e9GGBuF z|EIqD{`yXKLF2!R2aGp}2dw`D9^QTk5B4X((ccXZYWxJi?=OJy0de^8{tHHKLjC_! zehTcu=<#0^ejmURn3Bif$p5u%icJhgT`$x*wj}rWYUYFHEtcQc=K|y^Exs=Y#D3d? zy?$mtlZSBwOG}xqxm>W5!U4AXcTvgC?%WRMv%j;RAVNt(d+_@Qf7dRO=xhPDgZ|nA z1#sOY{&x!L`!oVH0yF|N0yF|N0yF|N0yF|N0yF~uFa&t*BLm>ZyNh_phf_N;*dFfB z{#`ZVg`L}X-u3;xmKFu71+)+MiGSaclBk^4*&=KY`@y}>gwphW^WR07mZK4%5ug#E z5ug#E5ug#E5ug#E5ug#E5%^y~fY}YD;J>*~@a3^Tp!Ws5$C$oDBS0fSBS0fSBS0fSBk(H_;I(f2yXUeAVBHAi;B#GW zpCZA!5z4{!9k*N&Dc-SuaNa%{npsZ`OywJl2KudhqAQnQl!ZKqEjSKqEjS@GB6|@2#7*pftk@mQW|G z!!q4&*u=}nBgp))xDY%{k~!JYZ{K23dnPY=Uf2J>>4E*=E(hMao$;o}3o>|6#<%Gg zORV7iirUl#=i4NhwLAPD7oV8&KaUTL4_HDRU>%nK-^LyK4a@&_zo32Yl8!?P5jak; zggC)EEa^C;^8JtE1KV+zbR1HMz&L{?#0l16Nyi}_hySE?6pVXV{;%)?_Jg~m^C5)@ z9B)`coM0W6bUvi>A)OBy49Evq{(s{I@Rr`6fqxq2kv;M^A}! z>M_^XJvibt0NHA`GA5Qz_X-ga4lE|NV2HMAFehTK;PfeS!uu{2&Q!h*r+;Vu|bx!-#B#TIG0bx%4 zvBLul_fo_&)96X(8tw4dy^ zV;XpTRqxd;DwSB{9P4FDnjDEG}#R5T2-Fk2M(kToT z{Jyla?8N3W>}pJuf8hyPJa1Zn&DCN0c(+S?i)}s@W4)%h78*WH!wy`3QTej3Jg2?+ zPGQRsO@vQYuUS^cy$^8g-;Q4U?SgPEr(S&ce22wogwGVic~_5%qy7bFT~e53W{bZm z3LBf0T#22K@@w}$t&Pj99Cf?qs13d<0GmY><$!Ba#z^^C{4GV{Vz@upjA zaWk$iU2{R|bVENOAn}TYBKr^9jwI zy361_@7`VA#j!vA^uX1=<~=$0r)<9?w>mS9Q$M_D$i9?A-8pq~j=Yn+`cypP$$H<= zq6+Md(%_g}^MUw~(An)ab1~fc$d(Z7O9iGVm*{VDIs=Q&Q#riSM}gD+`GjpVuU!-4 zjF;<@s-3324s!hIxi?|yqbZ0#>jf;Cn;zcgwDV_)5@pXFAZa*75^HQ2={@n`9cT(e8;q!N}RhFYBc^RYlnL4gHl`(?B z@ke}|nC7=2o@UwK%XUNjQjBmtyv(ydUe7!by?1^E z_Gsvsv%)cItZ}+K&qJ5MWm3N7^*d65Jyo05`q1$KF13GnlWn@V-agL$CC&z-m5Q zC|fwH;10qko?9*>@R$9g4@V^yWA_XV>#sgY$JRYoxaK4DjiWyxUTtDVPBuqhTa0wT zu{M+s`!Zja%q)7(kr%5KFJFN9aO6#c4E<*%D{<<%uZLaQrZE(E9eampttrKf_AZ)H zf1o#hnONA?=4^n!4Q`%xxULjS^;6vN*e4BJec25&$AYN?5;5v<>%p#rePx1{qYe~_K@%0D>17(icZ91W!%}Q zW2*f9u`uN>*84(V@4m{6Du9Mu)>2I`A z%{XE|%Fh$+-6Q3hC_f*Vlr3l@iSTwj;QM?TYn@}cVQs3;stRl;BeD0JK5F<#E!h#S ztC%>`Ht`akR*QXpv+`~~nRHCO&WzOcLip5wD7LpF+c|j2*zMXX=#KCiQ?KwzHWQ6k zY*F$V&)3&E^3v~&@?UtMd|#iY6mBXv6Yo2BhrSG1g-KQUubllz88=bgWw*o65|?`? zbT3@44pVqA_xOn?_Fm&3noXs?J37 z>4=j%yh}P-Iro{jriToEXo~mNQ`4P1pd6cF@oD~-N_BkTM8TlF*4B8;eFODPy(_S+ zaZAk(pHIgU)(H7Vm81FVVv_*=G*^@p&#OKJ8ypBg^H;5kt3q5Aisu7Q!hE-`MDtfs z>S3#c?kK*J4|xrn@>UH$oS&%HowYvv5Febgb%Yi^($4GgLq;`^ce9q9k-)i*_ZJ-Cn)2&Po}V#eDoVrdi?JgHl7~2bEy`GP=((?3s?)co0js z-$n5()3o8B{wfqdJ&PBO{g#g6*)r!?M9dl#KbG+}xhZ}KpHGs{g>$S_IsO!GT2e4J z)gCX;8*$BI?rd~<_`VybzEl3%Xo*xs z{J2u9OQ3TFM&7(LOyr?5-oH6Jt#u2I8+`4fv`4HQt3GV9u3$qtrd;0g_{t`PkGOem zpCxr@zr1?P1%VMtC?Bc`X}@~A4DF9bSe0UO$=5jcjaAN0TXhZj({tn1-G%k)I9Z^3 zGW2>GX4^J;^o&q-93z76@Af9}(p}Eh(nd8{aA?|7yDw>2R`@NQ;2sFyCte5g#UCPm z1qf~F?=%YSFFuAfA9yK<=8Myb=Puq9yT$QmVZ+9|Of$5eJ?j>+@cD`1IP34+dyW}a zj^)i3_ZZk+6BllBzkE7|iMQJhSmpJ$5>qN4CHZ(%IyT_3tp6T4#P?J4;~g_q(fY6< z10OZg3-QY?s#&6RZXIX*OpB|m{H~^O{JH;hb%nqqw4c5{CVko=2X*|@b&+0M9+qQm zdo+e`KBtPm+bJ^c>~s?EB}U3l99M?zpOxj=RGN<6>XTAdoQ|Fk1iwDwfE`EqaJHt| z;ia$qIQBmlAHz0k*Kq2uND(_?)WgP<(n;p4_xWW!T&E zxY7g(1w3VR{Vl)wdidd1^LrY#<=8E)AVcXPso2d}iIMA>i0=zmb#yzj0^yS%xPgpv zKzuj1*<8G29?It}m0GSrTIn2rE`PapNnS>mQ$HzLd2Ef13O?vkzPPF<&tXHZ#Qil(E+T$4TfoGN zj}DC2+4~9Gb)=8<#+7MUvf`D@_BNDHZs&WfT0tOuG|LLte2qr?3;V$E4?9{KIPq*e zr6|TeI+KIP*DaAn=lY3p>X_9WjS+*@aNNFabDVEE=AyN6-F*`cJm|hh)>eg4_#s)l zTotkRSnNB0;Wd{sFg5KZu|6eeKgev#$#mR^_7@iqJ-i=ajpE12b9MOnWr)un>rH&e zZAs?XPraSf<4ibO|42^?C6>NZ#4}}sTUHGz$9Bw>kMj=Dz?3Gg=~`m;@8Y6Ssu!kXuR_Ke!jlJzL|qhz@}-zUVbPa z<~Xf0Iv;`V>kdCLXTcY9TxD7Wvhy;6ghacZzOc`O_XTGB5BmHYePO zq0G$0#D`07l#4_Aiz!FP4Y0d{@DVz-r?g`gnor-yOBikvt>pNl<-hx&_XM>6Dp~6> zW_TT1uRiXX-@0m@8s6f&V5MbN8J7KG?wF+RYIvZ=djAWg6>jPMVe`rKMr>1Dws7X- zbZmT_+_#&_XuihBRbPsKU&z7#(=d6v={6fV_KkNc>RYTs@hX$LHfDC#evZ7*fk$2z zB`Ck@*JM|kK9R@0UVfET6{y7U-fste`X-AXOT6T_US|ltXt36x@Ws`b?welPr!_LM zw@Py&b1MED;(N&0caEZC(0a=#L_}nmR0hYN@lvbjr&XzO z>UXpAvC}7~;);=Hr}~>#Va~G-6rLHbjW^n-4{JI!3cu^9lXcg(9&_l{*nHnR9ouhz2$3`I15ecH4uy-)+~FK%yrYtZ5+!trPG z4u7rBYgBOC9P_@eIhB~Ah2+p3rGxPHh#N}#zgpuyO<&w6Xt!f`5@%RlWySMd(P<^q zWf49x3wrDk6GQ&I-FL)$!zaXdt(EFSveuw@RrK3^cDE18hfkvKxa_S)d{=c>+&EyO zCcaZ@Lb&psGR%48`-R@uH1XSVb#|*fY;eYFtBcXI%CM-H`tyy0(=f3U!J*OBX#FE~ zru&{T`6!-McYnnRU$p*d2vt5ly%dd?n`hC<(QRly=qdd!bJbjwFMEagRo?&Ph&$n5 zDpO1=u+2kG7+Eh<#?_L~$3@Mv!rf%cg$UOC8|o!zFS|Y+b63ojA36%{mmM5G&RzHx zJs%q!m-<;{H+rA4D`@ue!$oNQgKud$b)*;K*TaQx;*Key@!DqZsajUn58q=ha_q7~ zH5TtSV|cpj06a?0VO?!sOI%~)hSNru-(xZI>0`C3?_jRcB9kV4L-{a$!|f$sD$)MJ z`k86YzzHZHioMGZJxjK6;wgopY%1S@);|}jlzpCjK=@VV;nl*{6m?h z94^?Uo1*w}dKkV|`A_+9*V$g))^!6o?WKIw1H+dM!99c~+2ij^u~Chm`t5rohd-Xg#zHhP|Ir!rZWsX0)`v>}mYmC5kpKlu-+sc|xPx`A_+N$G6zL{qp z=wXdVCfyDAF!u}A#$1=a{Bt_CNv*fnsVOM`tcyBzQau*s=S5z+Ab)o)Sd_aWLlbXXCQ^8;umWQSi3LX1>EH>KA=%78TRf>} zFR|`It=JJg36YUn8JNqA_hrRJD4uur)36ym9N`laEP^;7Txdy^i1sjtEuJND^DZ`8nLq^~_SY9#OvjpkqhH0Aq@`oW#kKLWCCHzOdTalY1JQcpaKuvK_(H^YJ--Nnq3zbB6nNyDDsC#MRV`YFfD6h-q2A&9?aKb{~z5m$j7S=Kkg%TWur)Z4x?zQ`KiuWjFF&Yni>U2(0j zY5z>D-$&(k{WEC(QW0G{e#xKmkI@6u9a=cbKgEH&Wy&w2`E>t1v01(fXuQ%dv?a(N zME9e|4$s|YOXBez**b!s%dunUm!45FP{SWheDYMD^?pZZE4hA(K_jNwB-h|@Cmq{c zBzR-cY_#5pn6KaDun5KT)zUS`tpZUz+v|sX`f?BD=kV);_ZGcE_z2z)Vg$}a`-7@M z4`OV#*6+(fhbVH{3Nu!_fM0%4+EZtL`X&=*MI| z`ScOT%{{wi?bj*C-o(z+DZIbI&B77$9R=TJag!E+}znrrD^kDpU zZ=x)gbsoS@_H$yiwgx`=jJ1$1>pZ~jL50`+SnospFMRDg<$M|zDmQNM(dVeWm&39B zBa_j7d23HURrQg`AEUY5i~J+d^X`KRZdJn+QT&{@3DFKH{>3 z7U%4jN5$<*5uX{#(O)vPRC3`=l%K~6ug}jaM)`TZ$O-Ky!*n?MrYUCj9j#-DJ2xE; zEZM+%UU%1j*x&)$cx^BBxV?_HcuULi4*~ggm_@?%nu6!4*bT8P(Xw)c&(Xm8!mF8R zyd0_qSj--d@bQuMuP24QsaG>v67g%z`HEXb%_x2x;tGQ!bkO>6->&e*ucY+x_*X3nr#F^j$_x6q zF|9Q4?w_0{3--0aPYDh@^;W7Co2oMEIAh)|Ec8)q_yipBQ#u%*JK+j?{}s$U?{cXh znonaaPhHaqLHK+fdVfLR<7m7bS&zcLTtWO2tGY0`V4V)0xoF|IOZUsMXr+Pfv#x01 z=Y>oAYB}5BiyMv$FVC&RP8%CqzGzV#d)w*}+JEVM zFn#l-2;n2>rR})Q3e6V-CrmpqIuzYs`nq@dOiyDxP|11tw8V03MBJ4zYb11V%lk>r zi`LoV!S4#!hq~5dN{5$8O`4N|DNn6;e4K~IYmUU@@lGQVKHIL(%fC{N#;b5=)Lco& zKgILP2T6HR2p<#EL5H7vp!N0oNfVb`TA+!qjwI8ZW6Lokx$*v1lQePLXLpC*dvAkF z-0hY+^m;93HR)DC1MB^=?eM87*Y~6SQbh2T;a|>|bK>#M68VFB9nte=Q_bcaH+jT& z9T~Zu)y2m-^3ylfe4RB5J@p>AXzqdCB z_PhEvmE%v5Na(4|w=CX(f3MAClT;rXH@kE5~|h8t92 zLECE2=0xAYmTQSDUVRd+fAUQRf3)h;os$o@)%le^5JT&qEsZ-ShDM_GnAn9nuWHjQ z4jwi^Gu^gHt8wb4CpY!o|8gGg`>bI^n#%`lu#CUgpkxg^O;_!DRHZE*UUy|!L(ON* z&BW^=nUsc!I`+FYNdfIoHytZY=WLHcTtxwg*YetWoXxF_I6BEn&EVNq%_k28TvQMK0{w(E^;&#CXY?XB2 z%GZIZSWlP# z3WGeQTDbpp|0ZH??TfeZ?;OZW-QW1GGa$$S^@8xc8HvQlGt)gZFTW!i(jM8LI2J(` zieGyXI9if59?3mlt?v^dEXW->F#kBgm8Up67hoxas} zlb)myMbXBO#9fPtfg#tY#j*74QvK9zt+ga`v7XuMAuK)FbB}4PA0k8+=g&>wb*-6L zSa0Gr`t@v5I4|&^%5IXZpZL`_L^*{x5t}|oHKv4!m-hS+&C;X&rv6dbFiGZouc|sT zmYx9v7Hw~>6(qx+>+0^PXdvz`pSZwpu`?M^@zGY9X+_q}+F>uU;s&u*cZlnJwNfI> z+wfc%OV9n`*~6xdkYv_Mm_`m}>B-Dow?AI$BXO$F9n;n+4aE36s-MqZ8&09>w~;bA9l~X3gCq=xg4 zNRQ=D-NMj21APTZD+BB7Lu)=0x0Obn9`eqKjLqNgR3d9hrdu8vZXlaT*v43_xD!)M zWR3E8f0U)idu(gYc|wwjy9bIGv-AW#NT@TF5+Zw>R{Bi6)JVLXS2nNN!CZf2*YPOQI6Zt%FqP6%Hf*kQ>aB2OgTZHp*-Gq*9C4|<2!3H5LJ)i2L z@N!E@W=Kt}kv>b$pdi!kW3C90qH|j%R^9zf9C~;?v&?)JnR#Ae;EJi1q@>c`Zo6(L z5TOox9Qr*fCK7alyic?A$O)Tl+-@bw)cvCD&K@tV;kG9WBx;C}ABv{ld;Xd5_;S(0 z|0G5Z4XMvi5ws*N^u*#-A3q}_rmMcQs6%n6xmKzH7+XbQq407l2PwRDJDWQv(AI%CSx<3bufS@!N5 zQ7S%rbwf-8amX!htf-hHdC8!1&Ngu?vh;{m!CsjZB17;&;`)krMAEuZ=XbL7Y#p7h zkv@jyPxjQM?0#KcK5f`)`)>sPX>pnZvw;}t_k7=ja}K1@4xa(DE?biJ7DHBOjZGr< zC^ipmvwla&men6Q%+h0VJau#NSXMkMIFRgq=|prax%o+e?DIzB+1;_t#Ne4Zvqf#4 z$mu)ZK7TftBsDb~?(`XuLX3A?sJWV1LY((Y7P`RF)07!G+=&%Gvn0&2*u2PWUAp(3 zr7)Rr@7CFaJ)4N-Qw7tdKrF;%5KL(I;8{+fyy? zLjT#-hHd%ph^2Msr?LC>##jIA4I4?OwLw)fJARCVS-kNUCKIK$BzHJ85WAlaIJm*j zm25QbF*xy=C23VPc!r}>B5~5JcCpo`QX>UxIFWy9aZ5$=H_`D;Tx-mfVxiUd+U$$BBWo;S}+t6!RS;ISGlG(w`Q!G6P zP6bM4j*?_1zB(Ap&Z|NPbXwPJZ6{6-W*i5XuZXChF6Pe0XXe$TlHPWb%%OchOfq5V8Bv5qL!uF)y zI)^zk$w~R4X{G5TnYLWNx%lEOV!^=8BlAOwiQCUKm&UX7G#-~-vX_;A<~f=)vhisb z*%+PdqJI9J+viw%;@eJbaKt2;jf!KG*f=MgWKL|5|3K_4)JwGATTkpC z)=x|L$_O%S#l*CU4H(IEPK;Li@Qk$evf3`Y^)V+7iBCSOpT3k}GVYE^iebg!hSY88 z8wEtj6BX@28n>E>%I&QS*W0_2O7|9p1{^2JtHSCc2P6}TEe)flWa^X?TYb071+n~5 znlR|R*bqtPSG^^xby)u7)fSz-H$s@)e$=dRjC>Pu`qg^o`nN9Rln*b?gc1Zk6WWbw4elpV9#{Mx*?X5?YkI$^a?rvIV+O+?Em zmuurq+{hKi_O)7<2@*H^6tn-~4I*<@Uitw>IT3&0X5UDb9^2y&@8#-CG7sqsj$-RM zTeIHO%1ek`RzLWyb88DByiq4n>BVfare>F;^8qG#Z+TPo!o#-+OGea8jYlQKvWD?F z2`oL^#=jJHVCB`?$1S^tvh>LAAAQF7vj`dc^;O;z_hw>%cCWSJ%iYK&xrY}#cO*%7 zmu0~b{SpX=MdqW|%_%3AJ`c{g$kMaZ{LrI(T}fuSWqm9=j~&W>*HQ9HkbJB+S4!eZ z6EUl2;;`Tqvq{3*%~|^iLB5L`7}9rX5^;aUbVgH639-GPM>9Kq1}F`Edxo_h6CBjf zh}|!dgsL8Hae`#uHJfUES2Yq@O!4)1F3u)R0wyYmCRmX>W6HDAiA18`%l3H>MM?=_ zcmKWYyz0Jcl3CbD*8DZ%eGxm)U(Tu;taOAyisjEPk!$%(gr`MpR>+@0rVDoKRc zOsvyvTQYDZNeX)C-xCwHAg2$$x-KX)pPVsMKKet=b50!gQM|HC;E4qDird_jGwe9r zx~?VMNt9e(*nMlz!bW0_#lgFqrp+P8-i+<&b<2wU5Wji4`Q00Y(pK+6F~@R3STxRh z7t5aoki@2KO2*do{q4JhN7`sr$x6;&l&$As;&45y~B@ zOm;rpRIuXPJXU@lmA7gJyI*6Kvd36CeIpE3$m8OPpNVS2Z}^4#4&Jw66c>?$>joNPmO+-1$nTQaw-H{F&@RIbS#7*tYB zh)yxfX5-@}V^_J(LXxR}bWl4xA8HM)7afJM*7M@C@>^3I2+uq28~Qw+Ns8g7(Ru@{ z$O6@E{f9Os5_P4YA0JOGChCnk3fSxCxE-!*zmcqdO@BI>%`f+-M~ZVsekRf$B|J%w zY9O>s9rDJ0vmsMWmYkE#wIsVebP$hV#bL0;)_q#~FF0|y^FpJW&I1XitM>+{NLCz1 z=Y(IqBPmQ8X*O?N(%3*4X;^5>)VPrEm#7w|+FOyU3$^0rIwTOU33r3|*iz#BgOKJk zEPv*-H&l6;N-{^D@Tq6dUj=tB4V4@rMlOx$dr;4=iMX(Be{7A6J9*pwxagj>1nJ}v z+gg`)lStFso#|jvL1@h0af972-_{B#bqz_TN)U50TTk8TQP*5G1xaS#-G!@XekOLL zou07Rb2jO`|3+WEuNEY>IzXsu#ZBTtmY=7lMhVd%cr$GiOOH`S=|&9}&Zqm{+sWps z)=Wb7M3^A?@r;gv8QDn4&k=bOw`w*yO}a7;)W<3W8a1%36X02C8H*EMiXpb|Q7m$|H!;~Oj2xK?+4oD!TC8@Ztyj((*1Rktm zKL;4AdVl{R)^mVx=ZsqR`Y=N@XxxJ!A#&0uUEN~CMxt`1=#xqZ7xKh~W7PtitjM<; z2KpPCr4WawJ8V8ETT1L4mwPUTrDveHm+T|fx@9IgE{@I9h70urp0GyVF;Lu{9OAd^z4C1<@^aE-uhhH*qUPe1qLXgr#KU4T^#h!wTQW-OXz`SEmYy5yLWXWNlw_ul9Lc~~dd5!IX(_f4A)V)> zUGhKAnooP|dm$p@Miys`&};u-MMi4Ij(uXCKt%f17&~q)BW^!z`@~+4jp$dKX3EO* z=22OL*yC;*(qGclM2zecZLrf*rHPm_W|zScId}3##pR9K(+Spdg?-LZ9@mK=>3C<0 zu@yu__ts4Ix@C)o&HG6#dAf>ZO@VSEf0(S^d6u5n-8%%%>Pa$#+-A>V$FrB+6dAvSMnZ$J z>*m-qjl}K~?r#{o#*%8b9ov*nT9GPy&SEJC?~y9fqt)!9P#g;C?Jkebl3)%=mwtMI z6^D9-2Gb{~2$Mn^gU*=lX(Gy7VXME8g#MiXewx*!wCf?%r-1WnoN=sbHxo5w~4%8<| zE6;YB`yRK5y4$Di%TJUOTK%@}+``fmCvUgKnl)Y;X%Af3`6s&9tP9aw#7Ox(VTW3c zMj}t=&grEW+)1yrw6s$@tjOBKc^>41YlNDCTh5hU6~tAA*8F)aJ<}B*DbHc$pZk4v zud;bjqBA15S6m07xz)0XJjhySwoY=*lA2D+9n>)Fvx^{gq}-OJ4^JiRw-2cQ>Rdzw zgk~gWvGkl_yCcuA&6tnQ-WEIpZNbC(+XqW2TF=Tc)r2cYxqAMGu-J1U^}3)){Z zA3X6x&s$}m*1LarfzB&Fmse?5eU09~)GgK5@u<_qFDH&E9>1attE(2-Xd9=AU!4Ca zX3}aK+_CPYT5wY}_T;mP>GI2|*qwp_f~zvodA#f07;OoM+c?i-iLhFw^GDHnyophQ ze(M}GIrp`{?rdLk_!_5v5W*%Mvv%^p>*t~8=QCGaU|K{Xe3rfr^sgF+o}Wu4-#HYY z(ZG41S64H{?&~Uao?-5~`5Q0xLf^+cVCNuWDyxP+uGO=0X8F^+<++H=Fm3$Ff+u~O zpIPH&R&o!5=G0;)@fvEi4XM~3x2-{bMF=0^&u+(iJwWH%P4s<2rkq0f7!C3ikP$}j z7nThdwCa`|$HC`7+$#;!_vn4#D7_ihJszmx`4@%`ift~z`lrXfG@GW5TTYZA^g;++ zI_PSWbdU< zH(jw;D4x%rGJ1Kc488C0o**Q(*jWQ#ulS;Qx>yM|Vo~(1^Dou#V;{%t>sD)nM_Y(> zd-%K@yB>SS-+cNl%;(!$*X^&-`+}X#!z3r_q4NV|j;!XnRP??q&dIBq5t676> zw_rFbZcW3i!(2ATdm(e|DZ&l(2ghQ{fn>KkcTOQ(fN>7VoN7jMkjFO z^Ywh7o~|WwLdW1<8BEyL!__%r-6g;seN*^PZMMC+-CzZY&- zS>F#GGQOLkFY52`UePc64?yvAS2o1Mg^A*a@iggvT^2e&q2FVVG**w^*Ea>$_{#6E z;P}5@oG~ObPX~W~?ajpUfKtrrM!x;z-WvF;LAOMTt!;3dKFJz2J*%+2o38IX&H8?5 zo5>o*2n&?IHpW|ByLSzpXBeC@)%x5nbiS)s!>Oahoi>E>SdVcaf!!ws?_l<>c#c-b=G)J^WyUvP7TyGF8`~F6hJyJ%= zCdw!om3!m$dRv9;q(nxx$SS3bLX@&2D}|Pl%3u5r!MRRINUUleufxou= z#SBQIel_16SPA|VZ4Q+&Abnb6gMU6;K>kd@)FF|!1L+e^sr#AzC91D=ym%!vZ3Lay zyPZ|%(5OTHWjeN)x0TOT{A@mMj~M`zKR!DyQ3=DHN8ZXY zO7ds_(T>NY;5|^gTSw#E^#KqcUD53|CIkeJJf#vr~ofz z_PWl|ApXNS41y)S5&t1c6x&uLKOoPKj^u<*^;^^rMceK4t2qntAJr~tC!mY!HBRSp z_)#qEhV}mj-15iz!M(!_Gi}6u6m?-HqPs3DLl&2;-6n&RfZxurj*+(#JWJ-?UYw2W z!|&1&S;M8ve92S!sjN-C&95#Q9lCrJ}$W!c4VI;e#3WW%TT=o>Bf_}Z$!Vg zp@1tbW$Pd~8ZUJ1riUPm@tUA!BI@l&gD3yNV{;&aciXt>*&2}W;LX4F|L~2~_pD{P zY{;Kqxhe#EWh4LDYxJ8vEf9^LZA{dQJGg?Z|3$!Ou3)S!CGb7dtbY6f!mqLqY$q-zB7DY9W&Zfug7T@s)EUtr z6QsZY(R}H-dc+S)>Mak|XQBH4j$Z;lLtzhUYXBX5P_R~1tZ7fBp8 zG}4NFzB3CvGknj4&Q$=pca&u|2NB%BaLbq#?{MnZC9p(G;VdQv!Nv#&S8IAZ>XG>pSi!iFs zS>-*oQhX7uKaSWbUZM5jhKziFRu4-Lf|Kzd8|vQdgQ-aZM%*b%@c82PyGBkEAYOXr z+R1?m@b-{ZNDDXePoD2P>5B|eeCZFc-mq;%^Of-Jg9UfIELq>9uMArX?jwHqimx%o zLJ0BaG!M(U7wuv&tP=?2sSbe5Yr{Kgi2lgQCkHsqi-`Uat|IqU;{Ks^kz(cn<5F;D zF=jZ|4f(^Kd)Jdxs5Z#ICwykxG?yas=hd8>2?x&zkiYMM4xr9-9&9WyAYJW>7Wp?gQ%CzG0A=Ix(2SLgf_-AR003u;%RAm z^#2^C(?+6fD1Y(n8>CXsM*2+Nd3t2565-+5+eu$Odm}z?GY%jy0e7 z#BjtnxMemcC*L4Eq&Az$>MM`X8HvUIxoX9)uGyRYyPX z=>u1#rp?jE$nl)i}$j>tb?NX@yPrq19Hokj~VHfm$Z4whp^s@{n zt&_5j?}Z1<(hdw>Is|K18sw+bhrq{gQorKbt3dj!_5%M1h6WaK}ppFYj&+(zqnv2s2CguBRpfWz^Ez5ECd9U0!f zuH~vC&zF!Csk-AB;)kDHt%9nUyii`kEf`%JpD6VgXfeJbPgd85^rOJwEht4?BP4)m&X*ngqmS~J>gI-AHPP?&zq-Gjw>yPW))4+YQ4bt0`8-38 zzw5qlZqxJ$!9z2>yE}Y`fkJJH_M6ATa9`Z5cHV~=yf3E}BswwyCadp6%wDPiEh|Y< zjgn}*mI;yA?+lbL)HoHk_Z>$5W1F{pcKtv10cH&w3%YcEAnRAapfBaTg6va9DQf80 zE)LsT>0Sw49|UowG$}h6M4{!TQ&Zw+5-k6Gc6iZ!251#zTY5KF0;O%%s&ARm{#ar+ zU7->^ny&{}JdUUOqWwKljlRjU5wspuQt{92`hfUC^Tc%K{$3Txbi}ta zC~E*j3V&&MyCDW&?aeFAPSAj-H9Wrm>7M~}Ru8-5I?KU+;d0In0n|^V0dknv*`~O462TFhf=p#t!Eq?{?QLU@?LU_#k0isC)AKKhfA z4YH3xGw(vYE8-`Kk^#{R|5-10bROj`6+?JG_ef(ncw7lCew@jB^?U%_Sia=294Z2z zDPfN<5O^r4k$P>}bQl#C;3dm<8e3pAO>af9f6b`oWPg>^>fSJf|(@F$iRC@l_^IxR~ z{JYQUX7Hscz?Z|z_2W(@cvJIo@jx8H3&B1bv$K1UKO5#+Gjlj0{v)oxz4R*EJbC_> zC04j;4xs%*wg;JfIofD^->-y+YF|=@FkAW_Oa36(y)<|EiKYk?u+abf?Ti|vx|_)_ z$iD)L8(vh`@2&!DQu2J!_GtgmZ~vXk$KeXuAM!*y`LrJWXFd4!(rv~>#789`P#7J$ zf$-3e6$X9_MdM4}r8uwX#}8dKHEw(~>jQxb^|D781mTra6H%e6%J5YBajstBF|g|C zSd-aa1g!q$TAYqR`HLs#*z{;M;?Hk+Grz|TqWqQL6@E&R3+?YTw8uw5&H}Rj@%ug% zyC|Xi5@+{1;%=&fFstn;<0``-czFHKa#o};{M4$JZWMyU@tWHS-Cdi&jOpd8=)_9! zGv6WXcruDlWeMsHOb_WJSETYKiWlkg@K!~e06=`pY0B@w&%KDxyPQ|)%n?I;GtYHR z^JteWY+*Rt?JqtE-2UBv8T?)Zx>CNKdftJ-otvfLr~NXpx+pF5U8fS*opQfdeH+d9 z?GZ7H5oJ{WFU+NHa<2#Zvn)v@`AZkNzkuQ4O|Kt9sGo%@Cf4{`1>%oY0!Fbc1|raF zVONeA?GX5EcF$faNfho+PS9(-s0k%0PV2ng904DBt9`ePSAyvq!B+DQ2v4jO^RGX7 zgZLq>*N#s4+h{+^`PcQ^cQw%O?Y{b2A$Sqtvwhmo13)c`A;-^;;v*pNdBDm zD{khD1M2TfImh67h#lc`$3#hk(1%Cl=l?wQ`8*_q^1t-wmDATbk3$`XY#!hB0dP8T z$3g7<9vJ`n-TRS$axmcE!?m)V1E4?Q*C~0uG9W{_-&V64`ExRd`VT*E#OK+)zX!hD zME(;np16yp7WrE!D`&0Df9}6Z8Z%2B=t1+@Z8V*lzjz2`p>bDQJChF@j!~ z-URKB?fUGzk>s6Ew$BdcojdB75q|J&Ft)6=A-w&=e2k16_5QUdSo_oEyuLfQ7ui4gT z%>rS+gbm4{GLRdBUov(>{``g2|J2Pftuf(40M);%fNmFU=ktQtM=`Ko6cOw4ooA#o3f|Wyq9M{JVmgG zPgE6XY6&(;evRT?ndZ&a1BcLg%KoEVDJLjUzYm>aJV{6(||4dRDqDtIph z&7l15>n}9!N)I8=ynKD#*f7YU+ipF;Ee4B16ccaqYQWb6Ghm#m$aOq&D9xn|B(~3_7Z)Jg<`TBrnPbH{I6+y>}x%#_KWD@n|3NpIz5hzq2k0L;Fr% z79HXqC%D~V#o&S{Op}-7Ogf?pr9JfQ{TpV%18h&a_}dCVSE}VR?}+$M;`t(_3<0zs zRn~WXjdus)KV_dk#W(Il@x#&>J$)+|72zB6<~NlIlu#nTbIn(*9OU2 zYbSmA9)#z-#~)IVbSudEH1q6`aGgi~m2}>i?GCp9jE)@%w09f;j@Oe6ws{G`n=4PA z-&Q5Tdf_LVih)~J&-=>=d*Xw??>?-7V9fB+<^3vmyW!3_aX9s zuKpvmKn>|*c&KqZ>wogkz>hC0N#dI?rV-wOVe1_s;)?DtBK;hcI{cn zXJUT-w_ZF`rgnCYksA_S56`NI{^ETC-n}w`Vleao>kDo*47xAP+5dbz0~Gl!Cm9_o zz?m>XZ@a&UUuv6w-|O9s)?Y1G8N)9pBmOLy$+Mt#Scv?4pLPW0?LUO@&|3SsY#0NI zFOR$3nljPiusVRgLEf<+bRC_#sg{_9@>+Drwoze&40Ims8!zpS0Dx?%Rr%*AT zdW`gWcj?xi%xt6&#dmuW{Y9h?-;tEdUzE{(c2x>~S!XEG{zk5?am?aVv_Ezr(fcKz zIR@F^=^RZR90rm~MJt`t#QmT0vhq{K>hPk2^KbWaR8a0z7g1>o;0LL@nf9x1++ewAA4f)J_za4*Tp*Rtcv2#<6xKEEfFy&@0$!2 z2%htUdYskSTM#zdRa|Z8B0*0T%bCx_{oL~Jo0Nixet8?2&?z5nlusS}j=nw{iQ*k^ z?{1zoM)@@KQizC(C)%Is%Wyef_Z#g;?f+%|EVUoSU!ro}MzP;x1=KKe@AOAr4T9RO_uv-u-xX+LB1w#Q#m&y{#7mHV(|IO$_{v+^J zv`N$w#aDk)Q*uBsny;$`6`oNNlw|!_Q|ecW1Z7}#w(;IPo*a%LKBwNgm6W?LQ>pgC@Og*$SiJ;eGlz^J}6oFU(i%wt*@7$(l{b| zp9R@(d$j)T;vQkhp;_Xu)=S*4lA$r9%_Iz8o^-rNBG!Z5Lo7cHw=Dn_TPV~)oWEPX ze)5Wub?(IKkIoe(x7h#hci!rA-WtDOEl%!tHX{B!p~s1qoq0C8>@@hJ=CNIeQn=)#k9`0IOTi6Vq^dk!_uy-*9JYr(pO8bNc*s+O=pW&WXuVEF)M4IwbUs_fjS@GR8<(hW zUcv&oJ&nYJopFZpMVg(R`grEoRkBRbh`$LaeWPL z-;#6C)v;RHeHKga<+8|QUBueUiPP~FU0gIKZnof_9+8Ah~{f z{u}pPbrut8vFyZAU!rdIy5?PPR&yGBs@W+FN0e&kyt>@(8c8xTK2^oPza4?k90z!v7O z{!($ij4#d@0SzHNT$*|&t@=P27FD2d!8LRa(=<3YkwM6zbo$P8rH6@>qRkNZkC4+d zHp$YyvJLM)b?m6Q@&XoA`f0!2tSf%qedG8UW_|pU^k%@KOCi>J@FDYofO)JjhKhd6 zKJ>c6m*0P5BCV+1P}$YUbazQ{T;Pw3+m z@U8iAA|?LN{%6Fky$hIE8@BQ*$_dxn(cRwMtA{6d5cyxI42#~e`bU6a4*R1z^6@bt zXGS;l(zS6W(wq6H^cg}<_vp1ohd-jp~V4dQmij~*>39eART|JHPJ z^o}jYg5w4s^%~D(r=JHJZrS;;*Wx!{qR+kH@>_g?kW=5NvFhJIiD&L>7rKx>kDcn< zHr;d088_wE6+X004=sV9DQwGk9BcZ?wj}5C)#jX^VHjqumQ3k#-B`* zYsqCISxuxY`w@PaRCPlW#%#kWJfD~S&{)K%XG~?UA9cesCBJ4%73t%7mLd1wbh4Bc8Z8pobgQ&f=j;1pa&3#KGL^(NKXcD6l{3lAFQI-zQPNCFC6a@Q*`(jp)~iZU28f{1O>KFMoU!+nD)E ztLwgi-S<^JY~$vDXDSL=8GhHtX?}H$ZO~O?da7rdWH)B8*L|HoYX~_GwRE2*=a@)y z)ST_Bgg%SYS&1dl6nK?8Q*_9ud92NWySB&51%E13q8+?bAD^;SNGkbViSdY}1%kLa zY@grh-?@Yw^DN~b;r&b`*I?WJt+@Efv)xW|k{VZBA0S=KpT}HV(#5J2uizt;E#(?S zykC8P_owc_2W)Ta#6%DM0(M#ZCr1+@=a5m#Yg_`igqyHK&8QdJl3eEbz z%Y@Bf{4%W7J;pkCh~wYwd>*Iq!~=bY!J}4OBh7$4h3K>RU*451x1^2CXChgUCWo#Q zen@?C*h26$1%Ca@=-Wf@i2lC&JDTjNobjHR>LKAD`nba<@U>U43Y&>tqa6yJ#pFL5 zr*Gk*)jGqE7sE^>=>vBfDG7bt`Cgea@lfHJXQwK=bLOyBXf|S(?Sxx-z!RNwy7*w< zDY3oxi!m-Q{@IPAvskF!AG>FSoV%st+MyFnq&*6v+FNphBN+ME-c#a6ZXFxl7Z=ZXvX$Bn~eJ^Wt5s$dOO879|v^nS$4IZW2z(DqnDPJF<|FQcDKq`dJDaD%Y3 zjg5G{hRrHgJTqCWoi&efXDmJIJ7kN;dc2beS<%B!s4DNhU{!@jcu?d}*Y&XMZvAscYnatwOxST8V#|jrcTvcFSkP%tOj5qn(fg z&X0Du5%JY*&F8&k=e}Zmb?QTd5^cMx(?J;vI85dHGJ3g8_BA?J0Iny^003|6~R@jF9;uycq*UX8OZ zF3)?aXfLxaeyK2(V%Mv0#62hrX8WvAUjAXT=GC0TM2e2RpK_J(L(LQE?>`ZJXZ?kY z)=NZOGzgdm_I`B3rFx=WV+-~1SDlxS9p6`omD$jFYTR4E{uXo_rx5zEMrSKG5&cog?(K1V4AD`ZJ@~0_#8TP9#y4Xx{ z9xIpZ>hL1u*wTMw<0bm9^`f=jZQ*LlOl8I2+HH7cp}79T#RW`w`rjy^xq@Ff&6T>h zP9K+M>56C(D8`J<+-~c}5`8=i&ZoEV=g9EYS{b5G=cJNk{t_X_-}Ri^ciByhO-~_) zqI?ni#9Sw7=Iw}&zopO$;x)v3e|+bXuc^d#{Cjnkf@v1ppE`b}m5>u=nXx^e@E>(~ zdduyEo%NY^ZbWg>;zFmNowkx+#26@5j52>-!TIL)?JiU_z-3HJX_*EKFb>Z54!_11 zuo!{#@>)VpDc!!H*-|D_op9rYEnLm81N}njl=u_d6Afbf7O}%~zFjj9obe;af3!Uq z)5l?!tMW)fC00~9Eo2rmhh6^tvNV;D!$>WA%9-F}W>XJ1W(he>mt}+hg;V2p%x{#t z=jX6iGu1>^0ax7a*|@KjlO8Up{;n}+qypP__Mww3)jY;qcJ}IfLe9Bx+kc!yobEUK zSHESSR{5kq4zslQ`|Rd|aM2~qOy939@r5g1_J|ZE%B7FLTVqSpA^NYYC?0LoV4BCa zxt#C$NXSv9OuYP!;F&6GJnUQiu>QTDxI4jCbkd zqE@RiS8XbBeWl`!)PrMWJp7k{htxh~BGC%3=wBiHa91Qt>A$9Jc=^~fCux;MEQOyr zXdvba?&ol`Z^%_2?@D$<5MgTt{YGIC zb}}e+vxDewWKm0`|3Jv$`4lRbK=>hrP=Wf^yoc|wXytslfk|u33T-AWV0IZ{WruIu z<8{$;^VUInxa7QoXNFJ(_D87GO6c`8w#i|-*+R%^eE96;tA9+Sk9S8c8HsUO9zPJ~ z>Pdz3g$((d3NK>UyR!6*6CLsG+GoeTBK7d&gTptx6RI#9_s3ZW%4adNGs|NS2ssyi zGsmXS5cA&jRd$-tXZ-LzUHKYHJbK~Y{pRNj*b%$Um+Gf35G&_a3GF`zNOi zE7SH5VI}&r)obLriwQX|!+ndh*JXAIP@>c$O_IT%19f2DOrXRw#aP?rEbiQ*n z75+Lo{81w70_J`x{Fqp!Gk%S0&-ey0E-Il})l0nP*u&GHem`)tLS!HG;sSFU-C6jr&uH7@PPCgvWkOr%S3dNx};;U;8X3^bv@Z(4K= zUL^V`{cW}BePUd3nSPJE&IEtv-8VT`8C;3kDxT4qK0l8cwbUwY@jBHvL$Tc7nMe&+ zc9ORE&k5ewLZ4Y^@Jwmd7k(m(n8Ryt6&qqb=w5EK8t$ow=Z;dZ8hAGcOhQhkO3luz?Mx(iw%c&aK654o`DX6acp!uL6UR(qz5=?GtTHd-woK5#YA$k`>8NV=yTVj*4OGbElwKZvG&nl zz$}a&oMx8xz^UjsU-w7qL&PE&Lf9FA=63rN*x}tL<4)S->7r3FT+=T*gZ|GW|}J>)|4+ zz7q5Yi?Ld{sgCK3?SGxg7ht44XGz7S_$4!>mM4c?=M8K26?>mnQHqqoeU1J)uwZ zi^=7`5=&U}U(2%s3JX|D_tWk3PtM~<(m4(dYv|)zH0iwF-R1ZK<3*ogvA<+Lv>Gz+ z=pgVgAju`-2H}TAeu-rV7^(3eDK6gPQHz+8xgP&e++|$(%H6Vs&-!?bW_?DRGI0-p zbLGSv-#JY2Lm2&5-f`YnDRlb>kuNUN>uverxsvq2FmvMmv|43#S)oPj?v;L3j?=EV z>BW5-Im-HYWiSi>iP2)Lrs`1l^~yQS)jBb4D_`tfci>*{U?SD4_d9RNdD_q6x@(vc zfA;O#u~@>NuNlcGCfK{-*_MSBM}R(V;_hSZ7gdR!`^z8T=`@d}Jp1XGN66V&6dwKB z#6%L7&}E|`a-inn4MVHLR_p9zSl@BXXA_kZmizM9#v9r{2%~yKmYolkY)cDGtgLvfoIgE4h&`kDaH++)x z)I#8ZJ}zl2?0Hr{A4{oa2w7d9!!~VfXZi>^vhvm=CkcJHxQz5z2z?CSJy_WxM~hGR zT|T}@@C~-ViR{;kui&9Sp6na3)W>TMRrsGL?wi`dCnjb)L*SF`DV7*Qj;|NTZM{l@ zCx}Sg-@?Q1KizJK22tZxXH-w=<;-HME?lfu&#&Mb|K#LTPY`ixm6$u4S%@vz&a&6u zTEH^nK6X?Ra&FG!pq8o{H!3T*TLNyyR9%8w0cU?No(?xSEL#?{Yp-|j@sBKG`H z!v5_%^Vo;pgR0}vX1J01do4|ZCtREL=Pa5n#H$;UV(9o$UJmD5n%)(`L>lOnP}<7N ztpR2oRopbV@c91SiMfl|4d;7#p{8#5hR(tRdu9VXZFSH{+2{jiANP&7vYy}xy`^hg zxUpz<@Vzs!J`LE$Pq{T;Wei;U(@WHNq9y&xLd+ueIDExS*31nD-yEtq8}#w!oFxj$ zr)Ai6=!uJH5%@j%*2jvFL$Ta=-)(EYZ2k&u@yw8@i&dVdDDaC)3d4)=j`KwvXJ)Y`|Eqti1)S?3V-yG5P;ej72_NqsK9*#e(N+PBcOy4 z!=mn#01lN^+mD^-d`dUv#aji4_J@z!ct5gSL-k8|@j<2lQB>a_Xl|QaYL!RcA8s-( zcyv|})tgCu7|2ral7hp~$_W|_0bajt$!X%+oVJcm;&Ln(1CO0Lc+5H8X$7JGrH zXAlYMxVnJOZ{XE(zk3G%$nq`KTH~A!-6QW$OI0Q_NWDYnt&I(@3x{{-lAjOye!tF@ zO_=-{aPKlS>fwZojQ%l8s{_DmQjnu~g%=vwx3NoJ05I`#vz8yt3^+HT=vjKc8bnpT zkJz+B`_XHqIZ6gAF@!desTSRF!uICVjy9%paDIG-#`nk&xJbeALUFPjNSyjtP`)4izrS39 z#Uml)&-9|(ti+;_Kks+)4~$kt=Oe5|ciSwQkw5F|JXE=Fi0Yd%=y-kUSmj}$V5CEH z`T%%yEFJuo5ro3jj}-iMR3O8P@#p#b#(@3y&c`>NRRXHb&-uLlsNTbA_ztm^gz7!Q zC@zJhw5O5%hqE?EW~da+*NZq>H=}K}WcwH?NMS+wi{$6&??UCZcu5!`S8J18IsjB7 zZ;rKv3PJsMzuyZwt3w@QCzc(Zqd@q3)QhO+rQpas4Xlhp=i|XXDK3qJ=)9w|@#V9w zRdhaH^BALl!jF7P7*pFPa2J&>WW4Ef0|~_JyFz3@Zc@kkdEWTec(2;h4aMw zE&5u-0>$cy%P#%KrJAo#fwnX;g*Md+pd)JVLT?S(f8@_)6`2RPTt@m7r9BWcu|oAQ6Z&4MIX7jXS~Ker?bpMgI$ycSF^T{>p$KO1Ro@S|>rs zyE{HIZL=R-)_A=XuT=p)v22fO{ZD_W4L#-}{1?@iIv?M8^{F5_Z#~b`-0_tg;g4U+ zjH(+Cs)spV_-VptFRHI7@9OXR^Q;n_TpJS+AcSxA}hHT@45r2a@-I_9 z4!(_66v*pD85Ry0IoZQ^6!q1DW0o|sGsgl=;eExd8qzGwpH0&{n&_t+eRFmH463hqQE!vl zz>M%vrkOuJiW}9x=??DvCow2Swx5}uZ$w%is`s#CF?7#eK=>@huk6y!B@RQM^r(lP z9RxL>eVdhb2*IF@A1`HS)S$Jsh39^n5%9IxXJbc31-PI=cV0>p>7%N`%{8+P*+*sB zEP0*{o!7^dbAfJ2#3$HGHbULcpnApc+mC(fDo5XYI2@ijpd|xkV>(`c3Frrb&oweP zH^t!yXJTyetQstsxMLbgGYVpd&P%S86@vwH-oQiuxlg|L+JO~QH{{Q!hi4WFpP~7B z5t4oN*cz&zw3U$k^q>*x^I|6Dw`Umg_o=%wMbv(Wp*97nszQjUSB%*?-6kXqr@{`N z{o<<%6~<%CmFDKa)qRegy_bl1ul7owphWR?rSkY(+BC|i!5iX~S*KBaQIssNlx3rQ zk$*CdosS;*kE??0?)jsK$n&{5Et8P;QWz?13TOMI4ggK@?!W3!_+V?7{AlkU46fx1 z9nAXB2Tq=FF8jh=1*%%7in324eXus^;%%;IzQ*5@dgk(wKHc`^+>uPY2PX46kq+DD+}M~Q2w%#{H}4z7R}eKA&TDAhp3*jjrw<&?GvO=HAA3h z{6RsozT$k#qCsyoAo#_qlAbXL0vL;&mqv*GKJ%OA=FdnlVSZ1jEb)$3zxxVzEqkiK zdAl8}hEu5C#!@o*QA9V&Ur_>;627gdA4=fbL8f*$)Ssn#^7%lwc^>)qKl*cd&RL** z(!azt{nSnfg3za`nI{K8*yYxs4`2Bp%ag;GGov8<7Zzd8PV_JG?Y?Eb@wo!Xd%3&3 z{(GBBmQRYH~L7MBZB;V`M}q&DaFX&PxhB# z;sQdD!(vBl;DLT{v@h0b{r4{DeWUEXz{(-GK>eXR^1>k4`NCVv!k_|(yXQ`I{HLC3 z+umY&Rb`aFp8B5-(Z7Y}t4@lqy5bS42inO(op)OY;g4vgSv-jv;bDjKHeZ8KDflVI zdr?Jo5TvuHaF{a)L1{V%DYjM;)Oo9Z$xe9+$ojk6FVj^4=Frmd?hgomWCm}&vZ6=z z|HHs}RpJAxFYO9FV&tZR#{ava!4VTg`gCni<`rtBAlpl<*tT;mhv+|Wpmq_tF#w9z zYjukggrQ+V|Kt*J|BR8CSeZD{C-q5UaiQs06(|v8Hl$WVe6{hD)6j7rgg> zwA`8AIBP&5!5Gc=E@Oc73zzQVssy^*f4@`WLGgai=toak7{Z_YGnXHX7^8Sk!;NNR zw7JRSPf&Jl%lJ?Kk&nMiev>ah`TOp3LdNzQl2GG7_Qg$;ejrd4YBaMV2vxLrlFBP_ zXqzv!8sIblBA6#iDE5{DU*VhSD-CG<)o(MW^nDERXVXN-b_*S}zBsybGcnEr@d^Kz zw>c#(P(9UKK85-}L_O{Q&ROjjrfPRz22kK%k6EYC5IDN(Unu>SAAZ^pztnk41s*kM zDD!>N4=OC)1*AFE0C`3Y>iSTmPtJv2+xvsaK6L4hhCeuvK8qg-=`3hHYnIkgYWb~@ z{QJ$>idbS9;=eiCiWB!}rHJ<*F<8qy?gx1m-LJcC_+V{*4~L_n3e-B%Rp!qz3sN{w zG|-us16@FOc1Ifee@4X_^FECYvi#MImH^JvsGiFB^Q*a!w@`d}n8^pobrz7HuT6vO zW!WgcqD(piB$>Hk!}$=obv~jV`1NPs2yFp)psG9Jp1BIFJ5S1xQXK+6@cIb~iAoSa zd+?SA2jZ(MUw6xdDI@!gUUZ#^mP341MZI`+8NMi0P65oC`eXT5d{$n}77}C~y7+W?>QMw+R)2Y+72??qoDx ziXT6YQHuW{kGEid$k)&Z=zg$;C&x^xEQHA4JB@c-3Ze-mf3{)yC7Qa4>hbmjJ~gSA zR)!;a7jAQB_W`;a7p#w0a6-lf3zOmOL-5YoXhDX6LD278ohxZw4m6x=-Hxgt{CQa< zpl>dM@Nisntv#Fp@t-$30_WmhBY*BEojrBE7v-;J3C%C8HxU03sv9yA9MOR$XL`SK zXbl086t(snpM>DPXGeFCqDXN6=St4+relEJt>vqATP4tlf6iH9gW~s3zLtdZE!5wq z=;x=o{~yxlU6bb}!#0FJ2C7r?rlnP6`>15fD?E`vc<8sctufFv3CD*PDopA(xARCI-CzeO_i=+_`8 zovfn0K8@V}^{M5uzNWiZn9t8dq!Yr`sJY84FXep+z}qGOE3%e`y>9-^IMShYA(XVn5S21 zTj@|f-LL9ETYVJGS3aME@(odB?`yW!pI2O^p_HlZ-DAytKtGsiTa~*YtUeW&(D(p@ z5o1}`pv^dF+UMr{>t`v5?t8WA{Ga!$W_{CqRuzKqQ0`Sw9fb?RpX;UZe=4-m`hxA# z-l~9g6z^#^Gl!~85kBoUPi=L7B@R2LXCCHM^n$PU?j3eN_#sogSo{Zf6&w;g6P_Kp&stI@upw8za|^r4asbn;YcztD*NT=~SHQ;io7f zKd&>q-59@%_P4mbA3ad?J__$iubtJF8U!@-%$J&;^23XhYgMdV$}nQ*YLB$eD&X>v za**<>0F)cLwH?;@m9!hvhm75oaccpF+jc*Mm2Jw}#NFI+!Co%jH+` zIm)I4@mIMcKW+QLRn8y-g9Smz!!)`n7@!I-uzl|M#5V^j{``>tbfgl9GgwNN&7k>S z$#Z?cSu{B#RLI=Eeb>R}7wKgBG^;`9v>zXh+m&fAmf$oi~tpHEoOMDfS}eD{93H4HlWu`-4I z=mVG`)xL55{cvVuOu+i2&5_*XAu^Te+ zztlQG#=FAB)D-+3(#L3#-Y?!C;j``!($wAqGB995*45W+0E8US-dK(hg((-=4eU>= zL7pYE-*eQHfUoL&eBOl$z;v2#XXXYE+1{yPg2Gp7(R{f*oyll1K=U=-u~Jz>yx;qO zd@JO}qE6)$+FyOgab-NlG1LwG0~zxG=$7Uhebj$=YDy(k_FqrWsW zcp-fL$}4nq&qK67l*OtscQa2E&bu4Aj*s?$+U1O5gO~eZjsIBGk6KkodC+#d>C!M@ z?3oVBrY{FOvRAKU+aUjok4`W+rGVy3HuAykgb{?#@UXuPj|B3MVUlvio8ySDa-K7k zO$tKm`zl$DoWgNcc;~V1fz;)GVBm5rTe^o2ww1^59LZ9FQjyxpV^`+DbsG^~aZm8py<`n18I70_vuam6^kc zKli_}DsK%&^EKd8N8I3r@6qEg_WM&|!<}k9Ks83ebxuXzn z?B4)=&j!Ku;s^3B>w<8eNy@aH4TFL_k$F1B(_n9CN^)&MHDDf2VL3UA_9M#f?c_T8 zZIUeC_lFMUgax|aiIH=&ZdwQBCv!fhpoHt0X3Klc?{`8b5_qR+5zPYonn{M4#|ig>fLRjoA;N6jKr<-6}9~!B>Sw5 zZMX=ee9kj_3iwcze%?TO#~t9D1tkaD|zS&&>BEX09Jd zB#-x2zJA#KvyxG#$pV>c}4Ui6^{f$QMCE6tZ!|-xL#+zUfqJOsP@5j1x z`(cEm2FH6AZ5UF!;4*b<2Hg7OPN#CE5=4jvH;G(F`%B>)CkvCr(fYldLi@CjPdr(l zA+EO}3Or~%b30)w>`X~LS)X#hseeULs6Wft?5C2@UUArK#LMuuU;waG)?a)qDGqa* z#;cEwoPQ}iuhe~kcD63m%0E#chZzuKZ&LjTps8?`gkRIVr#_;_et_%pz z75Ds3kuOB?CG6X-(%7R&)`#}Wo9^H)98%lKXRPq{g8>%i#h?*>*jhN}`kUzYS#NpN zx9Tzj_6YD>8*^3w82CDyaTMV{tHp8@7E{x@D|eFaOHs^+y2 zJ~J)Sohjf&`K#8t*4;c_0si^XrMe@mAISP~h8y|{!g|rM>%Nb1xP!7g#bnPch@Q_) zz0Fhs{yJH;DDa_tx^FJ;VO|7U@8Fqn-H+!GAB%|iA>%lOe(!F4H~WSE>__oAaXIsS zLF+|N47U6k)P;USZ}dFQ41qhNvhTWEMBu>r)H?G;RcHp18+>=ogGZh7`)fW_0j6xt z17b&!eV#~%7#KMs`xuJM1Z4Z8^@ULG?{I#5w4ModIo7m|6|Hx;m43{OUPAh|m6+jN zN#bzLT7zWshv*02W*d7ZUI23NRSi7egG1@K9!AS|W8klLw?Jsd_Pcp*DVqnzk??CDf;9mHBN&%MhNNX=*1HhNM&mcIi0^~gom{6kUC(C21Ory=S zWgz2^&~_&ZyQ?U^4rKgYiM@sJ&hTJb7vlqjKT65hYnT3WpXkZc=YoQHWuc|6j<+!f zaX-(o=pR~k;&9jWqjN96Yr&)#-r1h{2{7OP_WmXHN`TS6;O2Hh`0O&Vx$g1=;g4Uf z{d;UD+CO|z`~10s58B@tvhdYR<3#*tIQ2Wsq`#?W+i}-YGTe*hun3@%wo0KJ&E@#5X3fna|P_ zNS}{ymUotGBKs_64ZPk^MfoDs`oOQaWz_$dP;lyGfQ>r5wBCwo1rGry;Y_CA#CrL; zddx_sn>rjH3f0K}I|)wGl{!Bo?xWCi>p0Nay8(lEm|)>)=4)J{*L^)((Lv5 zLl=<#M_}S(4`HOwoDVmjeFMVp_Jb$49c&bW-I0g(%y7d~NVr}?L0?{^a9 zd11vRk~j)xU5+tdBJQjIY3}lRHu5pYHyNc{n`-KM zr>4YA)V9VoT?Em3d6k?+Q+8j8b5uZb76 z%#VQ&Q=j}r_f>%#4;-~jZ4o}Zz7?&k4M6;-GuVqR@;~@YB~@`I?dv4jKP*a5TYbu@?1GC?0 zU#0n%fm6z_p2`g&z6#$8eUH*a^L6z7`rD^Cvd`ga@i&l0kZg}=(P^h-L6k4#_U%vn z8IIP2ml)nJtWE8Ol22_O3z7x_Ef>$u&o_wo^*(;U`z0TPGH25$FCHBN?=Q!A$RDZ# zlJ>2pw+v8xoprwCE2WIiD@E$odpCX~{=ET?I z-%WF@(>aV~;N{!^uK~~lG-TIJvImi$?cL+vmH1zPVg z4lu1ex}pHv8<|Ra1^R#$y>#98YyoJ^xQD``R0(GATfGbw8UYP!!E}#(%0PU#UpQ3) z;`7zC8iwqYh#yi$YHT-sh4N|Bmq7NWQ^;Qrnct#L-bDGr#p&~mYag0Vo&)bj#J;OQ zN%e8HBH}(SOO0~elv0G~ftO#(W+Xwp$H-(y$qX3p`?K>_Pz3-5%>IE^XuQuZ$j)x@7PM-#jS4@-dpC#T0apQ*mcSaOn5>FfXz%|6jG*wowkPagI z7=6~#bQ47S{PwvO>r8|2M`Y#E9V0iC533ni+le+L=yCAoVw2VYpkT=DUF{Hn!I5p1 zef${YsQ10HWIGGw@1eAh%^IVAfp?$&Qrk9gw4_b7AL4bS0~^}A|4ZjNTwC6F$)I&}w{Y3q2Mb4fRbLVkcajovR zr_bSkzl`W@^flpf8F;Y5;54~@d$fUXXz2#i|Fw19@mRfK-zGf#WJR(^vMMWaPMq@? zl|9OgGE%blOj!w`C?hM9LWu0WLv|{oY@%#M)_Z*3&->i{@%G>I>G+=OT>HB3>$-3J zMpIqCI@ULf%WnD4RD=k1er;_1pU)cdv;aSm%kGV`JuoLrST#mhGv;nfhSwlG#-{>* zYwRNqse9@lFnTt1qr=PGF#eD#M7Ch`7=K@hDU2mYLlv%Q7vI3{AzdUAOi}bk`)^#7 z{XSuY7C%Ex)281ere!(|!i#%IY6P=&Ifj$CsU^thj@>6%f4!^=!+EODpp@W6jGBnm zcu$UOAbJadv}9+zP$vp*VH;W#bbs&|?>*59Wb?&nvAf?MQZb+NsRqMAtaWQrustc6J*OWc9D&}I#M;Ob;RNO{MWSi-sq_EDTrdj z7;VoV<nQHNQnvOWU|IfXF1`OxDzg=R93JF?%U39GZ z?i%vBG4zkVj0ZOFI?Zc0V1&&@-t-9`szBJj_jz_%?jpSlv(NJ|oU3oR&8|Mi=0~zE zh-xvMKQ3R-xH*!b51pw;MU2)FHMMvKM{+N8Bw@<`+_o_)O`AEmj56DD_`m!+(SNJFJ(!yVP8SKQg)wQuUkia zUL=MuWL-r+TC84KrZz&ke^*+&AM~M_o6ZNS@0SRAXpvKMtKbn1-{mntu7l~J$o+dW z^>HMqvr$Kjt|E4y;H#E-GJbEg^F~LLt&0h2o}gY&I)~k3>puNGI(Q$cQZ)*Ai_zo$ zuHOgeio^RZKQcbF(_|}+MLT;kG-Ug35|`aNGPNr4ONY}F4f?2Vy>!wDy-?L>?sl{c zp~*CP&-q{%VS2lvn2q5anbgQzdWiM^->9Kz!*KRa#6CTN&9lgGi0^D%|AW9Z;)Nd{ z_@Mf7f8^I1V#i6tO!VCqr9y(NJS>b*j{g!II0~wf zd-PP+62gBGeH*Iw1`KD&H7t8F2Zv{Stasw@yNYx+orJJ^bqCIdXj5yiBh=)x!>#Y! zP)0nr`nlsK=oU8`*mu7QX$js8JTA3^Xp3Krs>g6hR~1r4Q*ijg$K*bT_AV?JncH;% zn?E9Hz4E|z9nl?-&_}{NQOcY3cVliCqxB1v<7R?2$XT(22QQ-ckQMcmu__D)cNBU) z7>xDpXEX#b`j(Im6112VtzHKj6re56Ls^A?>}X1j9y@%&AF6Wk6cXr zRYWiDVf4>fVqNGmao2~22g?LK)D+olvX8>yaVgcdmoPmPc1@hqv?4{_uB*bI zb2kvFla+97y9fH1B<3h(u?Y(KQq%88l_A2?O#Dah?jjryy~!FedUA-*MBpCa@Q-}< z_Ydu&#V6Oyv5O2fA4{v_FI_{LKXMH%CVQe5XUcOQ{4qo|UUaM6x?7ISEtns#Y1&1+ zJTm$Y{bA0%wSwAEY_95c%zQhB!)&K{OFxtX9kMraS3mIwd1wwB)wFn{MyKwQlKPvV zFp}fm@wozVYk;H$D)$k3P1UV*4Cm|9lVYBCaCp61Zj(cL7-PD4Xe>$4_gcKDMfC>K zaKlhp`>S9J5ZxPwfo_r_LW zI4g9K&`c6G=RWV#aHxk%jBB6Tq=?XyRJY{W>9BJLN|rv+C%K`f-`spA=uJ>A)l%t3 z<$A=NbTgzB^Uon#<>8+&9O*&|RkBnZ{(GzB^FzDHxa5VizC?uHYrB|bRkwkxJZ~-- z7jj20qTkszG>p;xar$q%Gxf;)M0rm<;|?NY=6!k@!`TnC){n*LVZ+;9JB&L5kW*+% zEeT3@ttZgZWD}_q^(~{N@i75d3mkx>hlHS)A|YJ+}dbhn2jidgE z`h7X)NOKg#@Q0GMzm>?*{8?NJcE7A&(fE8S_FWfaPFuaR!{Lj3HiT<1dZf<3eC%|D9KErg z$vlD0ncTj2H^1Bz^Q$wpl|_`;D8wwSE$v7JVo%TKX`Q%_ES(+o3dL}aD0>7pI%Dya zJ9_ufuNv76`i4r8qQvnsEJ-TAk-e{Gh&Qbdx;b@Ix!lAQJ*)LSaag$$DNW{}*S>Xt zynhv0| z9XUpul(QP+jj9^=)W(&Xpkqd#gQ_pqArm~|-=d&>Tt4?;!Dp za{`BX!pT(kSXXQw^Z8AclXV!*c*8mRh~xXnH-pxJiiI^~b2N_boq`8S)?1$GnP7zW z-%fib(O8ENQ!&y=I`1MLoh{CX{xH|X+G-Vx?+yNOGly~6bMs8D=uc8K-QqNj(bEm& zYxJ9?PBm|oSW8ma=&vz4V|wQ0xBKNtg~Nv75B7aT^O*XSBnzWp*<}v&Ry&1tJe`0-u;1Buy zMzljTarn$SUm^t4LrYedl_ok0RE4X3Gj(PiS(TO<;c)ju`Qm z$={#WqALf;9=4L+g3%+*!ui%nAIme(k2`+C=m`sHwu-JMMg2uZ6cXQVAbBY!mF`}? z=&!pLh<~OD8bwmNETK}0Kn;6R+>-|gOVd=?dkp8cDKYP3Qyf0VzcRQ3!+{^@UC)gr zLw9Oov>Hk`k>TwE#nuiVRD(I-T{f#J%Go2*`}S8U5`NwP&Uy0#>KcBE6zIF?&1`$@Tmm_G-&*OV2RiS4rBI8<80~R+|7)eX6mb*d z&<*;ygAk_(&GutB7sOwfIz{5}@jROk1~8nOUEDyvC>i=`Phoh4eG54!7Qm!@$^*3< z{7JesX@XXEv(WcHsYXa*8ZIX1?IP~Tyv;`pCqP$bB^rx6!3vS5>oFYKvp&y$xDlc3 zl)IrT(W?lht%Gezkq7#kZvXp&uL=4iB`Gb{ADds}7m?R<+C?mSv~C^xL%Is9(SmRs zUdS`9=8!+HGXsC{N0OkbfsN2m&l*DM82FU_mp8iLx`>8rWBzcM^PS(Zazt!zOv?t{ zM~XjpwGUx9f_>t;>-IRj_FwLkhjyV5EpIruPlSHSb2+sWxq-Y&5w&?V>W-4{eztPG zh1ENXqhA?u^~kKxSqkOPJBS(iCe2~q8-Y7+G5-Y9A9P0Y(9hF9vEn?7-4oop%sW~# zwuw}KW~DUWu|m^7TL)*f8>4fL-B0gy6{5+Z8p`tg>jXVKSQ!fZY>vY_le1_a)+w>0 z+^3#5k)ho&16!=v{B?R35;|7yjmCSC7=21JL8T|fqPRxNk*KUcX3Z|x{h+1s5d#=K z%&O1-CS!4MZOdt(8>44ePbEPtks5`0O1BtoHWA+!>0Fm8{LppM#XH0GCTJ)fzek;N zG4kyFm}hGk5z42-M4gD?#EbqptEY{_)1O$me3<{3Gb9QS=To7$W*#b0Y+hBjN=JFW z$QNy9Dt6At>ZYTD7X*q8N)Y?wavfvo2Z)C#diO96^5m0CIGSMdyt=}ihw=1_bihq} zJ!*7AemrwkZWGZHlPjnl_C;ev=%}iBOwh)xlX=@3?~zUS)?Wv9L};47ddp#aAJ{tT zcFh=vpJkA`T8`n|Hh3np_>rCv*IxYr<6 z54f$Gf)9|TLWK4?hSTI7JjG^>`Hd^DhY$7eXue6HtSkxYahkPV@aqObYAnz*wdsLQ zyDwHwd!fH?Y*r`f>#p~e_>XVzSHQLR9jO-l5X zTXqqF^2q4}4Ck@+v->ZwdXV$Jox>Llhdju8^v}U2Lc`R?L$b4ul-;{}j&0N$wQ<+r z7#uN13lg7gdGQpYIfcv_Mv*`dSM1wd1~7R+Q)&3a{LsWtsO!XSYSdGP`b5FfZ-l&# zu5zNl7hSvh!bt9^DJo!X5ORUI7~yM-P9i=+gdT0PKmHw~=SH^xl5-V@mkPcl1Yvh8XpUPGxi%H>Zf2}DfB^&s0AG)Nlj4-eAsPf z+e#!kk@MaK=m247sn&ak;m9YK*=5|o>bSB;WQTT16I9G}#=;cASQ9ebU|gWaDV1otYB2JRvIo~v4aFr2(6 z87{+E9T(IpulyOqp?ur_WIgQwxms7+XNdU))syr$^;Epky0`1nm4#TH>)j)CBee#Z zX6RN@=GjMXh*H=e#+}4Bf|nj*I1U~k%ntn_rJH`+f)g=%I+fPWRcsx((4l?ap2i2w z5c*zK9bkkiPY_XBL3N0uOrY9&$v$$k7(x&IbJAkXH+ignHz44br=7uYiuFcEkea^;tq*;Pozx~`bv$ysG5s1{?zNUTLU(wDiFV~_DU>7cIc zDTcEab(cKu7PkI#^G?^HT`;rAc`B?Eq3z5I9v=tR5f`GTJYFJhXp?itZ$2^;bS+QO zDk-`W5naySICAMPQZydN@EODTn-|CYD+7l=c%30|XcvklvN4Zbz)Mw;Yk< zgC9%iF+a^j;M$`S`MmsPa`I>7qyxu2_zdtsnI&X49yyTz;$ydlMIbc$o{xzOetMNP zb4C||ZxO%QzQ(W$m8?=Yw!dnGm}Oj8u@+$hzRp~vnASh{>0Hs=+fOe6`{M%NLP;0N zLI272kBZCo!GNAe0XL;>l0pA=!Rp`6flwIEo6=E;V#e;v!{??@zT|~>|Bi*wvfyE! zE3BsTY17aJ>9?=9)@vaxy;Kh75@G^9b74iKln$eW-;*RBr|W7#f6o4>NKRlYIA7cK zanqw6y(q%-$4ad2Gf#r=3;*HEORmEMbB8X;#b!=JDMQ6uwF8W>E=S`H)_ngyAskdGy z0DmkVjQ0EFgMJtv$A&7&%fj$&m6Qb@$yumDG{EMj5I@ZOaBlN@6CO67c_~3cKMzIm zIyl$zHbI`hXXJ81!Tu@d*8q)&`Jf*!%&$k?Z5{0YMrD4|Z#x3|V`t93Af3Dn_B(W= z^C;}~0bcZFX8(LzahUe6$P67ezjmd{L4K(0B&;v#5TK%?3YR_Yh$zrogqBm!dn6{* zLwSkEBx!cQerxWI?{UFIpwBXemRw4LfIp*6y=k5w!2WoIJHHTW3;N$|y)(Y>Rvi8 z0sFi)y>gd^3CxQvRnXkTVn1Pj3QzMmJX8zpCloy? zu(HSdIgFSi|>x2l+vN7FQ$k!c9sa4$hVy^0+?>MX_ynQY-Pm zj5>M9lWqjoJAO@R0y{rhoBd(4@{1lrLL9^Xy&h&D_Tm7pB zp$CfaF`G#}9r2$~g!s7p{9G0Eb@%*vM}ATQp1Tp*+cD*F!f)Oalv}9pJpw-Q^bpfS z%Spm-zY9;=dQZdw=bzD>iyPxxI~JS4Gb#0Mu_*qXnuHUnApEymJmGQ%;E4cXDO zXW`qjwfUFYrlHR0=%#%??7R+=m{cutpg$6w4qwic0ee?_G*zx<4&>!WrSI|dD(Ek| z#y%N$GNY34e!ug6Eu$R3{t$}JF4{d2Sbu8qdI@$PL7S_&etIY$e6snHLai+V)Bkrx zP0(N#x=LLw#--Z;rTI_Op|wDN5~{RU4@7}~?xl^c!d(LNkUsEX_Gbj~GCY2KN$&yZ zXDFpG+jCF^{=Vz-ar|Xp7}mOzxbSso8hXxp{FYgfAbd@Br{O*}uL_%#g}oEU?gzIl zxBY2f4}~`v4ZigCo2%<15 zjspD*6PhpBey^&){KwR}#F1I(M)_Fr1#CZ_HFo;k*)=7&^tpA&O6oi`Y;ddhh(QyS zw}Ps3{L^3hS*9s^{3x(@wkAgsp)(*}eCdtWYzZVG$j8Mm+&6s^vpbrt`nZL4OYa)6`;aNf0lN#bn#u9t8ewc;FlzejMI7leh4<}- zqtlT0F(I>`f4Sj!#gFIDE-Jw?x$_22BFoTGKc;ZjhfR>-sUxWgKLLNl0^jVqzX1B& zey=+J0xjT=dSskhK`Y>o`0Z@^ZCb#ewW!sX>;Lqpe3#ml5xWD!hIb=gyDd#a6siZ( zyLWit>?Z9~B}Y;Ch$EIvny*4H6gDkpE;T^Js>uQrUcer1h@W0dzX0fY(BQ|e*B}8vM zcp8QM!ttu>`b*G(u%JG>U<2f$r`Bc40RH}LqjtR$)ht0@1$&W<>~3%#&e@x?6HyzW zzb#kkTvS>`9O3zH>c4C%?7$ydymNLkeZ~T}dfqlUM>+`=bDMt>o??R^*{dEW^OuA> zNNPy~Qx+ig2;PCr`D%zumrtVepLv8!eYHXB|I9x!sXk-8kq!9MqJ1h%=@O`C+{%0G zmuvuk@+0~t#HT^N)`b_TqMH?lzX`ZT^u$d<>I_n`774s?0@d@&f4-ow#-5bhfz1N6 z`e-We5q6$QVZ%y=EDq=oO9?VKS_t$xJX!KgOf~R_sU&9kjP;;@D~BgPr9>9gJ11!6 zVuR~JKK0Uj=Dli^3~a>d^T{N13Mx@VtgM0cfV}TRApR%;(8^O4wM?9?9rP? z9jL(ftgPebv=o6qgwAg>qJ(o$zbg$VQ|eL!|M z!+7B(dkPO`pGhdY+~4)6J|A2`^;)OXOc{<~6LA{D`fm+=XMUBjH$au`ErrbwKz*`i z(wsqc0`zw|IPI*?T?h084YYg<43SpD`1%2MHt?azo3@rrS8~^&x{bEPLwav@FfcmRwms_Y~^%;Rb zP0}aMcdmi_=Np~$?cg6kUcr%RpKv{Z9@QMO6dU1luqDxh;aTi{d4n{;9?Q#uaPPT? zykeYaa*R}2>Ri2*1<3c1^&mN+WsRk zC3t={>eokJMc@xPlXesw_#b+V)5YiZuyNyu)kmzrzGsg+5j&9rW?16h(n8u=nqAgA`Z8fxY9BEd5x-K|T1h<@l{GQLr8*q(mF)%>?vE zxw6$QMi9iqXQI<9KW}ltKIx$%t5>F>hy99G|B(s8D(Az$aU4~FiATbIWGJmd+KvTe zdqVZl?Uhqvs2lkEam|RPGHO82g|<}jw~v6lXwR9qL^gtWO4`8WPuv3XG0oN!Z(0>V zJY@5_ok08un}1>%S*ll`fkKiiJkNaNfoYsW6kHHIOvIrSR)SlAQe~54CU)zgOYVqr zXbtesA~Y@)ak(HLtGahnH~*jeLsQC|GlYymzor^xrM4U$@XzVm+SU`9X9@B@Lvpa2 zc|scgPXBBo>h>f=T73zfU6qwgg`(pTFT^Z!7%QPMMqZ2@%tuH!pMmDw)-hcev7jYkHu-3m>hz2K1+WkC-BJ z1<32a`xyfvyg+}lPD2as93X!dx2tJP_5%8&&#qeFX#?sN5#`XNv@8WUR@~6fEfMSg zx7?`6n&pGTi5Uu@YGqhjtbMZX;1^^kX^`Zw)c|GGQqMM?1@ej?l@iLW2JwAloR$8^ z9S~19bpxSm`XImM{UiLBr3{=G1;FafZPrqX8hGhS$AO?8pbp0@?!JA z?5BHiAF=%TrQ=U}<-4=c&lf?*?aCXVJU_C`3KaO~RI}oDC?}wYb`C#y{{ryOcT{rD z`PqYd*7P2va`OU+r|*YdDIyy|zc9msZ?VaN5PVOUOgI)hKP>^#xn>o_55qXF8`RJ7 zuy^xGi%H&1NN6)B%8IfPVySP}@%jzso%gQHy1n$=BIpBi^EZa_f9lf*##@nmIe?yO zO7a!;+n|4&kK^c3t$)tXbJPBrt+6ix&*Wb2zaBjejY@?|1>!hioi&qL2}%gwbGLC2 z!|sRWF#6d1jJY04SyUCW=>z;(a}F{i`3(4DDLw&96aoI=Idoo?8*&rqCBdC=`eG04 zy>y0Z^1UBeA0?{x8#zlR2J0;9SrlROP&y(LSu!L-aGo&<`v{E+T&Qs~puuz#>N%+v z-T$`^VrMe>WxWUVIs3NY9q)di&*RJKXPmo%KL6YsiFyx%^%h(WZ?XVY6M;UD?ey(i zZ6JP)lX5**G8BPv-cXnN?kx1hd~+b3Ul{i1d)$<3s{;F+YE>JQ*@1HKL}|EBjZh-< zY}u0$pr6;bytdw09T4#UB=+=fQv!Xa;#rsZ)(h%o!TNh1(k-Qg=ShSkU2k0m_Vs%z z=|j6YH_WIc%+QFtIJ_7{GS>&_aTm3QM~(w~?|WQu{^i{>@WP+d((kbN z^{Q;l-a}Xb){C#X7+8*n?O)Z(R9MeL+5SP3cT_^81-O72E9q;zbriMR+n+UmfHnnMfjr5^E(pACdAp~nX^b%1-T)mJexAWzIvwV+`fi_{Kwo)rMiY3*w?2^ zrs9l!AYaW)Nb`9o3*yB?r4znNejvUl--QP1yAU`|wdCQ^uqlXhE>LUc3=dp)e7sxR z2AjWrXT#y7JO}L@ml0cFs(}iAGG)Yc0Dri{QJ_Mh@SE^`(@aM$OVWb#AKJd&JBEA` zB>cbZNV%;YT`}QzeGQoq={ZoJnm3lc3t$(3(`n*w+2N)kj!(mXCzb_Z4ob3_vK;Ep9AuOr{>)lV30pMW?3qBGy{1FOSiZjYXbFNN9R^w z&dkwCBGA9W0)z73k-BpEgADsdlt#+Xd#-q4%eN7NU z`?ziWJlK!8rI;^lJS{A%up zUdd#THf;IzjdXzR6tw-Hn&F>90eHz}(b4plD%^ccB1h`O5|nX^W1l0g4$5jZ5xCd~ z>}yiQPw4qIU|;(Vy5~RC0sDFx9-u~~2KaM^C24&e5Aqy)JWHi7N=xEUZIXc!zT*BsU|JQpF5y?QI*X||) zd2z2xj>QQ8c{v4D64!)+e8aQ->5s8_z#otI-rM=2z(3XCn|Ipg72r7o>h$2ESx8jB zV9=pN0A@duF4WVi3csc*JoW=yOnnWS(Uns4s4kD({L+kw7jl}@O-zZ(qgXw&_}7y_3kemuEMu=ID4ht_q$fr|1#e|}bG?LLhL z`fMoth3bY9z_TsFKUd$Tj9lE#0Wg>05QWJSCLU>q-P!qr?nOm!YfkkrKW?OSEtBffcAvYZ&}Q z8eV~X_J@QizDx#!J<^tn6*eXzkwS(Os$#rw!}_D=XM_-VMV1>i8(M(!ay^}9DQY3d zOr53R9q>KUPs2l=7X$t{o{x6jz6kU=PWu^S%0K6uJr(Mct_T3}Vp;Oo{=yxQZ^XKU z-4s!fg|Git7vr=t0a2#=>iI-q_Risc-6azbm(RGRpR@b{F|EB!e-ly%iP_2%O^5<| zy6Gesi95miJeAb5#?P!kUa!K;=gjtr2>daKq>uH3L4K$~KFd#ut#ALI|7bC=QwVI; zfr+cg7PheSqQ(48b!o5j!*dnXBwI?#@O?@e;cWdM&_#;fS>LY>kT*w+taJB2^{GRd z{`X#B@AEkvw|TZeJ};p$#jiw5MxaOU)Q!LePM|-lCktKZTY$WV-^m)cRB^!TpX}NS z^`;;T8=j6w3xe>P>Zne+4<4TTQmq=cI0voVQ5KnfS`XPPg+l7RfIqdtsndR;fS#{? zuQjv&iSJMoeX!AWkl$D)VuxBefP8ExaO)GP!)XG&QKCL4tRY_bqsZ|3v!NNt1u4*- zWfg?;uLyEpoKuEf3>mreUu{5}f8Rv4Vf)>DwtIWC$AN!7#eDP#ksny!2=wNtrAh(u z{hM<~dj28};eBa+lSS{cfV>3kplc@kAV1P>SbI#ZB@C1NlIxfK{gr%83A9%8_U#HP^p3DU6TSrbS)i7U|fbSpm5-%j1#m+plIIf!vQJ8W*g0pjf>x=1t&{9)&T&q(qI;17q~qXqk!fj@MSz3@4<8+^Zh z_`$ARG?)h$ytS@XV04N=-|{~5DuaSL{F_l|KCN;Z+8>@k&DjLuhP$N_rc!F~_m=cG zdrVsp7r&`Cg7qVX%H3&XRt5ei`T7_2nt$vI_J+iATY&%BkH>|jIj<7zr=*)l^D6HC0VTKv zH_^fk(Al=$^K7x8{^BoqaPbZeI8R{uTrBx+3;e4Trm0|M*wmw$Qf zYpRhD_`@Wf!bTy*2M-dvPfK9^0KLH-BO&oz@Q;)ZGQt0pV8`hvf1YUUK*X#jcgu3> zA*S{lu3tldy(iEA#ZUYK`Oi|oUw*$@VDEZ}uKkiKp#RLh$gz-dupV_qb5K&mPl`a# zNQjZT6TJ}JFk~e~)jS0~ijbH*UBU}*$9(v#EQRg+yqKkBIWq;(oL#QhKVJu#iHxfK bH@12 5 1200 - + 1000000 -10 -10 -5 10 10 5 diff --git a/tests/regression_tests/particle_restart_eigval/test.py b/tests/regression_tests/particle_restart_eigval/test.py index f9d22edb8a2..bad3f158c06 100644 --- a/tests/regression_tests/particle_restart_eigval/test.py +++ b/tests/regression_tests/particle_restart_eigval/test.py @@ -2,5 +2,5 @@ def test_particle_restart_eigval(): - harness = ParticleRestartTestHarness('particle_11_254.h5') + harness = ParticleRestartTestHarness('particle_8_60.h5') harness.main() diff --git a/tests/regression_tests/periodic/results_true.dat b/tests/regression_tests/periodic/results_true.dat index 2189597e112..626004b16be 100644 --- a/tests/regression_tests/periodic/results_true.dat +++ b/tests/regression_tests/periodic/results_true.dat @@ -1,2 +1,2 @@ k-combined: -1.623300E+00 2.572983E-02 +1.624889E+00 1.108153E-02 diff --git a/tests/regression_tests/periodic_6fold/results_true.dat b/tests/regression_tests/periodic_6fold/results_true.dat index e60182809aa..04aa308781d 100644 --- a/tests/regression_tests/periodic_6fold/results_true.dat +++ b/tests/regression_tests/periodic_6fold/results_true.dat @@ -1,2 +1,2 @@ k-combined: -1.858730E+00 6.824588E-03 +1.848492E+00 2.933785E-03 diff --git a/tests/regression_tests/periodic_hex/results_true.dat b/tests/regression_tests/periodic_hex/results_true.dat index 584089a15de..eff00b6e166 100644 --- a/tests/regression_tests/periodic_hex/results_true.dat +++ b/tests/regression_tests/periodic_hex/results_true.dat @@ -1,2 +1,2 @@ k-combined: -2.284468E+00 2.781588E-02 +2.285622E+00 2.576768E-03 diff --git a/tests/regression_tests/photon_production_fission/results_true.dat b/tests/regression_tests/photon_production_fission/results_true.dat index b17b3621a04..af325d4b02a 100644 --- a/tests/regression_tests/photon_production_fission/results_true.dat +++ b/tests/regression_tests/photon_production_fission/results_true.dat @@ -1,12 +1,12 @@ k-combined: -2.278476E+00 6.220292E-02 +2.297165E+00 1.955494E-02 tally 1: -2.664071E+00 -2.369250E+00 +2.696393E+00 +2.423937E+00 0.000000E+00 0.000000E+00 -2.664071E+00 -2.369250E+00 +2.696393E+00 +2.423937E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -18,52 +18,52 @@ tally 1: 0.000000E+00 0.000000E+00 tally 2: -2.640755E+00 -2.326201E+00 -4.245217E+08 -6.012128E+16 +2.672029E+00 +2.380251E+00 +4.288244E+08 +6.130569E+16 0.000000E+00 0.000000E+00 -2.640755E+00 -2.326201E+00 -4.245217E+08 -6.012128E+16 +2.672029E+00 +2.380251E+00 +4.288244E+08 +6.130569E+16 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.675918E+05 -9.365733E+09 +1.711908E+05 +9.769096E+09 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.675918E+05 -9.365733E+09 +1.711908E+05 +9.769096E+09 0.000000E+00 0.000000E+00 tally 3: -2.657846E+00 -2.354717E+00 -4.245217E+08 -6.012128E+16 +2.649127E+00 +2.339294E+00 +4.288244E+08 +6.130569E+16 0.000000E+00 0.000000E+00 -2.657846E+00 -2.354717E+00 -4.245217E+08 -6.012128E+16 +2.649127E+00 +2.339294E+00 +4.288244E+08 +6.130569E+16 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.675918E+05 -9.365733E+09 +1.711908E+05 +9.769096E+09 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.675918E+05 -9.365733E+09 +1.711908E+05 +9.769096E+09 0.000000E+00 0.000000E+00 diff --git a/tests/regression_tests/ptables_off/results_true.dat b/tests/regression_tests/ptables_off/results_true.dat index 1652088ce87..3742748625c 100644 --- a/tests/regression_tests/ptables_off/results_true.dat +++ b/tests/regression_tests/ptables_off/results_true.dat @@ -1,2 +1,2 @@ k-combined: -2.978318E-01 1.560055E-03 +2.968228E-01 1.770320E-03 diff --git a/tests/regression_tests/quadric_surfaces/results_true.dat b/tests/regression_tests/quadric_surfaces/results_true.dat index 382c4241e08..6fb569f5227 100644 --- a/tests/regression_tests/quadric_surfaces/results_true.dat +++ b/tests/regression_tests/quadric_surfaces/results_true.dat @@ -1,2 +1,2 @@ k-combined: -1.193830E+00 1.824184E-02 +1.216213E+00 2.789559E-02 diff --git a/tests/regression_tests/random_ray_adjoint_fixed_source/results_true.dat b/tests/regression_tests/random_ray_adjoint_fixed_source/results_true.dat index 7178e2f111b..e9aa9015b39 100644 --- a/tests/regression_tests/random_ray_adjoint_fixed_source/results_true.dat +++ b/tests/regression_tests/random_ray_adjoint_fixed_source/results_true.dat @@ -6,4 +6,4 @@ tally 2: 9.482551E+08 tally 3: 1.956327E+05 -7.654469E+09 +7.654468E+09 diff --git a/tests/regression_tests/random_ray_auto_convert/infinite_medium/results_true.dat b/tests/regression_tests/random_ray_auto_convert/infinite_medium/results_true.dat index a0d7fc545f3..c7584ab647f 100644 --- a/tests/regression_tests/random_ray_auto_convert/infinite_medium/results_true.dat +++ b/tests/regression_tests/random_ray_auto_convert/infinite_medium/results_true.dat @@ -1,2 +1,2 @@ k-combined: -7.796949E-01 1.055316E-02 +7.797820E-01 1.054725E-02 diff --git a/tests/regression_tests/random_ray_auto_convert/material_wise/results_true.dat b/tests/regression_tests/random_ray_auto_convert/material_wise/results_true.dat index ebf510cfc74..d544a27df64 100644 --- a/tests/regression_tests/random_ray_auto_convert/material_wise/results_true.dat +++ b/tests/regression_tests/random_ray_auto_convert/material_wise/results_true.dat @@ -1,2 +1,2 @@ k-combined: -7.375068E-01 7.015839E-03 +7.356667E-01 6.637270E-03 diff --git a/tests/regression_tests/random_ray_auto_convert/stochastic_slab/results_true.dat b/tests/regression_tests/random_ray_auto_convert/stochastic_slab/results_true.dat index 2f444fad3c8..75a10a22477 100644 --- a/tests/regression_tests/random_ray_auto_convert/stochastic_slab/results_true.dat +++ b/tests/regression_tests/random_ray_auto_convert/stochastic_slab/results_true.dat @@ -1,2 +1,2 @@ k-combined: -7.499679E-01 8.107614E-03 +7.551716E-01 8.117378E-03 diff --git a/tests/regression_tests/random_ray_diagonal_stabilization/results_true.dat b/tests/regression_tests/random_ray_diagonal_stabilization/results_true.dat index c6ce6aab94b..f27ad46b467 100644 --- a/tests/regression_tests/random_ray_diagonal_stabilization/results_true.dat +++ b/tests/regression_tests/random_ray_diagonal_stabilization/results_true.dat @@ -1,2 +1,2 @@ k-combined: -7.152917E-01 1.430362E-02 +7.201808E-01 1.506596E-02 diff --git a/tests/regression_tests/random_ray_fixed_source_linear/linear/results_true.dat b/tests/regression_tests/random_ray_fixed_source_linear/linear/results_true.dat index 2fc08d0a429..e90d6bfdcb8 100644 --- a/tests/regression_tests/random_ray_fixed_source_linear/linear/results_true.dat +++ b/tests/regression_tests/random_ray_fixed_source_linear/linear/results_true.dat @@ -1,6 +1,6 @@ tally 1: -2.339085E+00 -2.747304E-01 +2.339086E+00 +2.747305E-01 tally 2: 1.089827E-01 6.069324E-04 diff --git a/tests/regression_tests/random_ray_fixed_source_subcritical/flat/results_true.dat b/tests/regression_tests/random_ray_fixed_source_subcritical/flat/results_true.dat index 831eac5019d..4d2fb6c579d 100644 --- a/tests/regression_tests/random_ray_fixed_source_subcritical/flat/results_true.dat +++ b/tests/regression_tests/random_ray_fixed_source_subcritical/flat/results_true.dat @@ -83,7 +83,7 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -1.073356E+02 +1.073355E+02 4.630895E+02 1.263975E+01 6.427678E+00 @@ -107,7 +107,7 @@ tally 1: 2.388612E-03 5.941898E-01 1.414866E-02 -5.319555E+01 +5.319554E+01 1.134034E+02 1.967517E-01 1.551313E-03 @@ -122,7 +122,7 @@ tally 1: 8.044764E+01 2.602455E+02 3.474828E-01 -4.908567E-03 +4.908566E-03 9.665057E-01 3.797493E-02 1.561720E+02 diff --git a/tests/regression_tests/random_ray_fixed_source_subcritical/linear_xy/results_true.dat b/tests/regression_tests/random_ray_fixed_source_subcritical/linear_xy/results_true.dat index 49813144d26..bf602d3e2d5 100644 --- a/tests/regression_tests/random_ray_fixed_source_subcritical/linear_xy/results_true.dat +++ b/tests/regression_tests/random_ray_fixed_source_subcritical/linear_xy/results_true.dat @@ -23,7 +23,7 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -5.235812E+01 +5.235813E+01 1.099243E+02 0.000000E+00 0.000000E+00 @@ -72,12 +72,12 @@ tally 1: 0.000000E+00 0.000000E+00 9.495275E+01 -3.613372E+02 +3.613373E+02 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -5.799250E+01 +5.799251E+01 1.356178E+02 0.000000E+00 0.000000E+00 @@ -86,7 +86,7 @@ tally 1: 1.066414E+02 4.570291E+02 1.254014E+01 -6.325640E+00 +6.325641E+00 3.052020E+01 3.746918E+01 4.977302E+01 @@ -107,7 +107,7 @@ tally 1: 2.371074E-03 5.920173E-01 1.404478E-02 -5.299862E+01 +5.299863E+01 1.125576E+02 1.959914E-01 1.539191E-03 @@ -118,11 +118,11 @@ tally 1: 5.781230E-02 1.341759E-04 1.430525E-01 -8.215326E-04 +8.215327E-04 7.995734E+01 2.570099E+02 3.452934E-01 -4.844058E-03 +4.844059E-03 9.604162E-01 3.747587E-02 1.556795E+02 diff --git a/tests/regression_tests/random_ray_halton_samples/results_true.dat b/tests/regression_tests/random_ray_halton_samples/results_true.dat index 7eb307da05b..b62398935b0 100644 --- a/tests/regression_tests/random_ray_halton_samples/results_true.dat +++ b/tests/regression_tests/random_ray_halton_samples/results_true.dat @@ -21,7 +21,7 @@ tally 1: 3.807294E-03 2.376683E+00 1.151027E+00 -8.060902E-02 +8.060903E-02 1.323179E-03 1.961862E-01 7.837693E-03 @@ -109,7 +109,7 @@ tally 1: 1.195422E-03 1.864967E-01 7.080943E-03 -7.105765E+00 +7.105766E+00 1.025960E+01 8.287512E-02 1.396474E-03 @@ -133,9 +133,9 @@ tally 1: 7.031793E-01 4.562478E+00 4.165199E+00 -2.870213E+00 +2.870214E+00 1.650126E+00 -4.244068E-01 +4.244069E-01 3.608745E-02 1.032921E+00 2.137598E-01 @@ -151,7 +151,7 @@ tally 1: 1.251254E-03 1.906210E-01 7.411654E-03 -7.162706E+00 +7.162707E+00 1.042515E+01 8.273831E-02 1.391799E-03 diff --git a/tests/regression_tests/random_ray_k_eff/results_true.dat b/tests/regression_tests/random_ray_k_eff/results_true.dat index a21929d53ba..37eca77f3cf 100644 --- a/tests/regression_tests/random_ray_k_eff/results_true.dat +++ b/tests/regression_tests/random_ray_k_eff/results_true.dat @@ -24,8 +24,8 @@ tally 1: 7.808142E-02 1.242278E-03 1.900346E-01 -7.358490E-03 -7.134948E+00 +7.358491E-03 +7.134949E+00 1.034824E+01 8.272647E-02 1.391871E-03 @@ -41,7 +41,7 @@ tally 1: 3.449537E+01 1.764293E-01 6.225586E-03 -4.907292E-01 +4.907293E-01 4.816400E-02 7.567715E+00 1.145439E+01 @@ -114,7 +114,7 @@ tally 1: 8.322052E-02 1.408294E-03 2.025446E-01 -8.342070E-03 +8.342071E-03 2.094832E+01 8.816715E+01 3.234739E-02 @@ -150,15 +150,15 @@ tally 1: 7.951819E-02 1.286690E-03 1.935314E-01 -7.621556E-03 -7.119586E+00 +7.621557E-03 +7.119587E+00 1.030023E+01 8.428175E-02 1.442931E-03 2.051274E-01 8.547243E-03 2.046758E+01 -8.418767E+01 +8.418768E+01 3.181946E-02 2.034766E-04 7.873502E-02 diff --git a/tests/regression_tests/random_ray_k_eff_mesh/results_true.dat b/tests/regression_tests/random_ray_k_eff_mesh/results_true.dat index 535db3b5512..83209044ba7 100644 --- a/tests/regression_tests/random_ray_k_eff_mesh/results_true.dat +++ b/tests/regression_tests/random_ray_k_eff_mesh/results_true.dat @@ -1,7 +1,7 @@ k-combined: 8.379203E-01 8.057199E-03 tally 1: -5.080171E+00 +5.080172E+00 5.167984E+00 1.880341E+00 7.079266E-01 @@ -16,7 +16,7 @@ tally 1: 1.692643E+00 5.794069E-01 5.445214E-02 -5.995212E-04 +5.995213E-04 1.325256E-01 3.551193E-03 2.372336E+00 @@ -35,10 +35,10 @@ tally 1: 8.394042E+01 3.100485E-02 1.932097E-04 -7.671933E-02 +7.671934E-02 1.182985E-03 1.313652E+01 -3.451847E+01 +3.451846E+01 1.764978E-01 6.230420E-03 4.909196E-01 @@ -56,7 +56,7 @@ tally 1: 0.000000E+00 0.000000E+00 1.820058E+00 -6.729238E-01 +6.729239E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -96,7 +96,7 @@ tally 1: 4.104063E-01 3.377132E-02 9.988468E-01 -2.000404E-01 +2.000405E-01 1.660506E+00 5.567967E-01 5.427391E-02 @@ -147,13 +147,13 @@ tally 1: 3.699815E-03 2.368737E+00 1.143184E+00 -7.950085E-02 +7.950086E-02 1.286135E-03 1.934892E-01 -7.618268E-03 +7.618269E-03 7.119767E+00 1.030095E+01 -8.427627E-02 +8.427628E-02 1.442764E-03 2.051141E-01 8.546249E-03 diff --git a/tests/regression_tests/random_ray_linear/linear/results_true.dat b/tests/regression_tests/random_ray_linear/linear/results_true.dat index 6617c782164..4c0e14370e3 100644 --- a/tests/regression_tests/random_ray_linear/linear/results_true.dat +++ b/tests/regression_tests/random_ray_linear/linear/results_true.dat @@ -24,7 +24,7 @@ tally 1: 5.733016E-01 1.643671E-02 1.395301E+00 -9.736091E-02 +9.736092E-02 4.539598E+01 1.030472E+02 5.263055E-01 @@ -44,7 +44,7 @@ tally 1: 2.008092E+00 2.022988E-01 4.188246E+01 -8.843468E+01 +8.843469E+01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -111,7 +111,7 @@ tally 1: 9.671931E-02 4.539193E+01 1.030326E+02 -5.345253E-01 +5.345254E-01 1.428719E-02 1.300944E+00 8.463057E-02 diff --git a/tests/regression_tests/random_ray_volume_estimator_linear/hybrid/results_true.dat b/tests/regression_tests/random_ray_volume_estimator_linear/hybrid/results_true.dat index 2fc08d0a429..e90d6bfdcb8 100644 --- a/tests/regression_tests/random_ray_volume_estimator_linear/hybrid/results_true.dat +++ b/tests/regression_tests/random_ray_volume_estimator_linear/hybrid/results_true.dat @@ -1,6 +1,6 @@ tally 1: -2.339085E+00 -2.747304E-01 +2.339086E+00 +2.747305E-01 tally 2: 1.089827E-01 6.069324E-04 diff --git a/tests/regression_tests/reflective_plane/results_true.dat b/tests/regression_tests/reflective_plane/results_true.dat index a1acfaad7ed..a4d6edb677b 100644 --- a/tests/regression_tests/reflective_plane/results_true.dat +++ b/tests/regression_tests/reflective_plane/results_true.dat @@ -1,2 +1,2 @@ k-combined: -2.276857E+00 8.776678E-03 +2.279066E+00 4.793565E-03 diff --git a/tests/regression_tests/resonance_scattering/results_true.dat b/tests/regression_tests/resonance_scattering/results_true.dat index 8316e720e26..72f0933b83d 100644 --- a/tests/regression_tests/resonance_scattering/results_true.dat +++ b/tests/regression_tests/resonance_scattering/results_true.dat @@ -1,2 +1,2 @@ k-combined: -1.462509E+00 2.205413E-02 +1.462428E+00 1.828903E-02 diff --git a/tests/regression_tests/rotation/results_true.dat b/tests/regression_tests/rotation/results_true.dat index 0fb8ba981fc..6db3d329a05 100644 --- a/tests/regression_tests/rotation/results_true.dat +++ b/tests/regression_tests/rotation/results_true.dat @@ -1,2 +1,2 @@ k-combined: -4.175600E-01 1.117465E-02 +4.459219E-01 1.899168E-02 diff --git a/tests/regression_tests/salphabeta/results_true.dat b/tests/regression_tests/salphabeta/results_true.dat index d5c7f66e909..75a81075e93 100644 --- a/tests/regression_tests/salphabeta/results_true.dat +++ b/tests/regression_tests/salphabeta/results_true.dat @@ -1,2 +1,2 @@ k-combined: -8.694866E-01 2.033328E-02 +8.628529E-01 3.120924E-02 diff --git a/tests/regression_tests/score_current/results_true.dat b/tests/regression_tests/score_current/results_true.dat index ff939301d02..6bb74445ce5 100644 --- a/tests/regression_tests/score_current/results_true.dat +++ b/tests/regression_tests/score_current/results_true.dat @@ -1,58 +1,58 @@ k-combined: -2.298294E-01 3.256961E-01 +7.729082E-01 3.775399E-02 tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.150000E-01 -2.753000E-03 -1.820000E-01 -6.756000E-03 +1.200000E-01 +2.924000E-03 +1.740000E-01 +6.270000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.110000E-01 -2.601000E-03 -1.530000E-01 -4.865000E-03 -1.910000E-01 -7.535000E-03 -0.000000E+00 -0.000000E+00 -7.100000E-02 -1.079000E-03 -1.500000E-01 -4.606000E-03 -1.820000E-01 -6.756000E-03 -1.150000E-01 -2.753000E-03 -1.610000E-01 -5.383000E-03 -1.230000E-01 -3.141000E-03 +1.200000E-01 +3.044000E-03 +1.510000E-01 +4.615000E-03 +1.710000E-01 +6.067000E-03 0.000000E+00 0.000000E+00 +6.500000E-02 +9.390000E-04 +1.410000E-01 +4.001000E-03 +1.740000E-01 +6.270000E-03 +1.200000E-01 +2.924000E-03 +1.830000E-01 +6.879000E-03 +1.310000E-01 +3.541000E-03 0.000000E+00 0.000000E+00 -1.370000E-01 -3.951000E-03 -2.600000E-01 -1.366400E-02 -1.930000E-01 -7.573000E-03 0.000000E+00 0.000000E+00 -8.500000E-02 -1.533000E-03 -2.040000E-01 -8.426000E-03 -1.230000E-01 -3.141000E-03 -1.610000E-01 -5.383000E-03 +1.750000E-01 +6.659000E-03 +2.820000E-01 +1.605400E-02 +1.980000E-01 +7.932000E-03 +0.000000E+00 +0.000000E+00 +9.400000E-02 +1.912000E-03 +2.200000E-01 +9.958000E-03 +1.310000E-01 +3.541000E-03 +1.830000E-01 +6.879000E-03 0.000000E+00 0.000000E+00 0.000000E+00 @@ -61,430 +61,430 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -1.250000E-01 -3.311000E-03 -1.620000E-01 -5.346000E-03 -1.860000E-01 -6.932000E-03 +1.110000E-01 +2.639000E-03 +1.360000E-01 +3.890000E-03 +1.770000E-01 +6.551000E-03 0.000000E+00 0.000000E+00 -7.100000E-02 -1.083000E-03 -1.630000E-01 -5.617000E-03 +6.700000E-02 +9.310000E-04 +1.570000E-01 +4.975000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +1.480000E-01 +4.910000E-03 +2.720000E-01 +1.490600E-02 +1.510000E-01 +4.615000E-03 +1.200000E-01 +3.044000E-03 1.800000E-01 -7.002000E-03 -2.980000E-01 -1.815000E-02 -1.530000E-01 -4.865000E-03 -1.110000E-01 -2.601000E-03 -1.890000E-01 -7.605000E-03 -1.330000E-01 -3.743000E-03 +6.720000E-03 +1.050000E-01 +2.285000E-03 1.790000E-01 -6.495000E-03 +6.499000E-03 0.000000E+00 0.000000E+00 -1.080000E-01 -2.718000E-03 +9.300000E-02 +1.927000E-03 2.160000E-01 -9.662000E-03 -2.980000E-01 -1.815000E-02 -1.800000E-01 -7.002000E-03 -2.650000E-01 -1.435500E-02 -1.590000E-01 -5.285000E-03 -2.600000E-01 -1.366400E-02 -1.370000E-01 -3.951000E-03 -2.670000E-01 -1.469300E-02 -1.940000E-01 -7.678000E-03 -2.390000E-01 -1.155500E-02 -0.000000E+00 -0.000000E+00 -2.410000E-01 -1.255900E-02 -5.510000E-01 -7.358900E-02 -1.590000E-01 -5.285000E-03 -2.650000E-01 -1.435500E-02 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -1.620000E-01 -5.346000E-03 -1.250000E-01 -3.311000E-03 +9.418000E-03 +2.720000E-01 +1.490600E-02 +1.480000E-01 +4.910000E-03 +2.680000E-01 +1.445800E-02 +1.480000E-01 +4.488000E-03 +2.820000E-01 +1.605400E-02 +1.750000E-01 +6.659000E-03 +2.510000E-01 +1.283100E-02 1.870000E-01 -7.083000E-03 -1.410000E-01 -3.999000E-03 -2.040000E-01 -8.438000E-03 +7.731000E-03 +2.340000E-01 +1.105000E-02 0.000000E+00 0.000000E+00 -9.300000E-02 -1.829000E-03 -2.240000E-01 -1.015800E-02 +2.110000E-01 +9.215000E-03 +5.470000E-01 +7.228300E-02 +1.480000E-01 +4.488000E-03 +2.680000E-01 +1.445800E-02 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.500000E-01 -4.522000E-03 -1.630000E-01 -5.439000E-03 -1.330000E-01 -3.743000E-03 -1.890000E-01 -7.605000E-03 +1.360000E-01 +3.890000E-03 +1.110000E-01 +2.639000E-03 +1.540000E-01 +4.804000E-03 +1.280000E-01 +3.302000E-03 +2.200000E-01 +9.834000E-03 0.000000E+00 0.000000E+00 +9.900000E-02 +2.127000E-03 +1.860000E-01 +7.198000E-03 0.000000E+00 0.000000E+00 -1.650000E-01 -5.615000E-03 0.000000E+00 0.000000E+00 -7.100000E-02 -1.191000E-03 -1.550000E-01 -5.011000E-03 -1.630000E-01 -5.439000E-03 -1.500000E-01 -4.522000E-03 -1.680000E-01 -5.966000E-03 -1.290000E-01 -3.515000E-03 -1.940000E-01 -7.678000E-03 -2.670000E-01 -1.469300E-02 +1.430000E-01 +4.337000E-03 +1.580000E-01 +5.104000E-03 +1.050000E-01 +2.285000E-03 +1.800000E-01 +6.720000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.810000E-01 -6.765000E-03 +1.860000E-01 +7.178000E-03 0.000000E+00 0.000000E+00 -1.080000E-01 -2.694000E-03 -2.070000E-01 -8.673000E-03 -1.290000E-01 -3.515000E-03 -1.680000E-01 -5.966000E-03 +7.000000E-02 +1.020000E-03 +1.610000E-01 +5.237000E-03 +1.580000E-01 +5.104000E-03 +1.430000E-01 +4.337000E-03 +1.580000E-01 +5.254000E-03 +1.230000E-01 +3.091000E-03 +1.870000E-01 +7.731000E-03 +2.510000E-01 +1.283100E-02 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.410000E-01 -3.999000E-03 -1.870000E-01 -7.083000E-03 +1.980000E-01 +7.934000E-03 0.000000E+00 0.000000E+00 +9.500000E-02 +1.949000E-03 +2.150000E-01 +9.289000E-03 +1.230000E-01 +3.091000E-03 +1.580000E-01 +5.254000E-03 0.000000E+00 0.000000E+00 -1.870000E-01 -7.213000E-03 0.000000E+00 0.000000E+00 -8.200000E-02 -1.536000E-03 -1.760000E-01 -6.550000E-03 +1.280000E-01 +3.302000E-03 +1.540000E-01 +4.804000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.750000E-01 -6.163000E-03 -2.570000E-01 -1.347100E-02 +1.640000E-01 +5.424000E-03 0.000000E+00 0.000000E+00 +6.200000E-02 +8.020000E-04 +1.500000E-01 +4.588000E-03 0.000000E+00 0.000000E+00 -1.560000E-01 -4.914000E-03 -2.350000E-01 -1.135900E-02 -1.500000E-01 -4.606000E-03 -7.100000E-02 -1.079000E-03 -1.530000E-01 -4.837000E-03 -5.600000E-02 -7.260000E-04 -2.570000E-01 -1.347100E-02 -1.750000E-01 -6.163000E-03 -2.460000E-01 -1.235000E-02 -1.420000E-01 -4.326000E-03 0.000000E+00 0.000000E+00 +1.700000E-01 +5.884000E-03 +2.280000E-01 +1.084800E-02 0.000000E+00 0.000000E+00 -1.830000E-01 -7.133000E-03 -5.110000E-01 -5.696100E-02 -2.040000E-01 -8.426000E-03 -8.500000E-02 -1.533000E-03 -2.100000E-01 -8.928000E-03 -9.100000E-02 -1.807000E-03 -1.420000E-01 -4.326000E-03 -2.460000E-01 -1.235000E-02 0.000000E+00 0.000000E+00 +1.540000E-01 +4.856000E-03 +2.250000E-01 +1.047100E-02 +1.410000E-01 +4.001000E-03 +6.500000E-02 +9.390000E-04 +1.360000E-01 +3.852000E-03 +6.400000E-02 +8.700000E-04 +2.280000E-01 +1.084800E-02 +1.700000E-01 +5.884000E-03 +2.230000E-01 +1.001100E-02 +1.410000E-01 +4.185000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +1.980000E-01 +8.038000E-03 +5.030000E-01 +5.613900E-02 +2.200000E-01 +9.958000E-03 +9.400000E-02 +1.912000E-03 +2.140000E-01 +9.376000E-03 +7.300000E-02 +1.091000E-03 +1.410000E-01 +4.185000E-03 +2.230000E-01 +1.001100E-02 0.000000E+00 0.000000E+00 -1.430000E-01 -4.207000E-03 -2.120000E-01 -9.278000E-03 -1.630000E-01 -5.617000E-03 -7.100000E-02 -1.083000E-03 -1.770000E-01 -6.293000E-03 -7.900000E-02 -1.495000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.300000E-01 -1.081000E-02 -5.200000E-01 -5.835000E-02 -2.350000E-01 -1.135900E-02 -1.560000E-01 -4.914000E-03 -2.370000E-01 -1.142300E-02 -1.360000E-01 -3.882000E-03 -2.160000E-01 -9.662000E-03 -1.080000E-01 -2.718000E-03 -2.090000E-01 -8.841000E-03 -1.030000E-01 -2.271000E-03 -5.200000E-01 -5.835000E-02 -2.300000E-01 -1.081000E-02 -5.110000E-01 -5.615900E-02 -2.430000E-01 -1.228300E-02 -5.110000E-01 -5.696100E-02 -1.830000E-01 -7.133000E-03 -5.130000E-01 -6.002100E-02 -2.420000E-01 -1.209200E-02 -5.510000E-01 -7.358900E-02 -2.410000E-01 -1.255900E-02 -5.140000E-01 -5.890200E-02 -2.100000E-01 -1.004600E-02 -2.430000E-01 -1.228300E-02 -5.110000E-01 -5.615900E-02 0.000000E+00 0.000000E+00 +1.540000E-01 +4.808000E-03 +2.340000E-01 +1.119400E-02 +1.570000E-01 +4.975000E-03 +6.700000E-02 +9.310000E-04 +1.600000E-01 +5.134000E-03 +5.300000E-02 +6.110000E-04 0.000000E+00 0.000000E+00 -2.120000E-01 -9.278000E-03 -1.430000E-01 -4.207000E-03 -2.380000E-01 -1.133600E-02 -1.640000E-01 -5.444000E-03 -2.240000E-01 -1.015800E-02 +0.000000E+00 +0.000000E+00 +2.310000E-01 +1.084100E-02 +5.390000E-01 +6.215100E-02 +2.250000E-01 +1.047100E-02 +1.540000E-01 +4.856000E-03 +2.280000E-01 +1.059800E-02 +1.600000E-01 +5.214000E-03 +2.160000E-01 +9.418000E-03 9.300000E-02 -1.829000E-03 -1.950000E-01 -7.705000E-03 +1.927000E-03 +2.510000E-01 +1.271500E-02 1.030000E-01 -2.191000E-03 +2.229000E-03 +5.390000E-01 +6.215100E-02 +2.310000E-01 +1.084100E-02 +4.700000E-01 +4.972200E-02 +2.190000E-01 +9.821000E-03 +5.030000E-01 +5.613900E-02 +1.980000E-01 +8.038000E-03 +5.100000E-01 +5.929000E-02 +2.140000E-01 +9.406000E-03 +5.470000E-01 +7.228300E-02 +2.110000E-01 +9.215000E-03 +5.160000E-01 +5.892200E-02 +2.550000E-01 +1.480500E-02 +2.190000E-01 +9.821000E-03 +4.700000E-01 +4.972200E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.340000E-01 +1.119400E-02 +1.540000E-01 +4.808000E-03 +2.170000E-01 +9.509000E-03 +1.600000E-01 +5.156000E-03 +1.860000E-01 +7.198000E-03 +9.900000E-02 +2.127000E-03 +2.290000E-01 +1.058700E-02 +9.000000E-02 +1.780000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.680000E-01 -5.878000E-03 -2.230000E-01 -1.049900E-02 -1.360000E-01 -3.882000E-03 -2.370000E-01 -1.142300E-02 +1.550000E-01 +4.913000E-03 +2.380000E-01 +1.153400E-02 +1.600000E-01 +5.214000E-03 +2.280000E-01 +1.059800E-02 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +1.610000E-01 +5.237000E-03 +7.000000E-02 +1.020000E-03 +1.410000E-01 +4.071000E-03 +6.100000E-02 +8.350000E-04 +2.380000E-01 +1.153400E-02 1.550000E-01 -5.011000E-03 -7.100000E-02 -1.191000E-03 -1.510000E-01 -4.845000E-03 -6.400000E-02 -9.460000E-04 -2.230000E-01 -1.049900E-02 -1.680000E-01 -5.878000E-03 -2.490000E-01 -1.255900E-02 -1.280000E-01 -3.448000E-03 -2.420000E-01 -1.209200E-02 -5.130000E-01 -6.002100E-02 +4.913000E-03 +2.320000E-01 +1.100400E-02 +1.460000E-01 +4.498000E-03 +2.140000E-01 +9.406000E-03 +5.100000E-01 +5.929000E-02 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.070000E-01 -8.673000E-03 -1.080000E-01 -2.694000E-03 -2.000000E-01 -8.202000E-03 -9.600000E-02 -2.024000E-03 -1.280000E-01 -3.448000E-03 -2.490000E-01 -1.255900E-02 +2.150000E-01 +9.289000E-03 +9.500000E-02 +1.949000E-03 +1.870000E-01 +7.267000E-03 +1.030000E-01 +2.543000E-03 +1.460000E-01 +4.498000E-03 +2.320000E-01 +1.100400E-02 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.640000E-01 -5.444000E-03 -2.380000E-01 -1.133600E-02 +1.600000E-01 +5.156000E-03 +2.170000E-01 +9.509000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.760000E-01 -6.550000E-03 -8.200000E-02 -1.536000E-03 -1.670000E-01 -5.653000E-03 -4.900000E-02 -5.630000E-04 +1.500000E-01 +4.588000E-03 +6.200000E-02 +8.020000E-04 +1.290000E-01 +3.487000E-03 +6.000000E-02 +8.060000E-04 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.320000E-01 -3.626000E-03 -1.510000E-01 -4.831000E-03 +1.090000E-01 +2.437000E-03 +1.650000E-01 +5.655000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.370000E-01 -3.855000E-03 -1.610000E-01 -5.233000E-03 -5.600000E-02 -7.260000E-04 -1.530000E-01 -4.837000E-03 -1.610000E-01 -5.261000E-03 +1.230000E-01 +3.065000E-03 +1.570000E-01 +4.999000E-03 +6.400000E-02 +8.700000E-04 +1.360000E-01 +3.852000E-03 +1.750000E-01 +6.299000E-03 0.000000E+00 0.000000E+00 -1.510000E-01 -4.831000E-03 -1.320000E-01 -3.626000E-03 -1.490000E-01 -4.731000E-03 +1.650000E-01 +5.655000E-03 +1.090000E-01 +2.437000E-03 +1.480000E-01 +4.674000E-03 1.320000E-01 -3.514000E-03 +3.548000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.550000E-01 -4.921000E-03 -2.410000E-01 -1.168300E-02 -9.100000E-02 -1.807000E-03 -2.100000E-01 -8.928000E-03 -2.090000E-01 -8.775000E-03 +1.440000E-01 +4.282000E-03 +2.190000E-01 +9.683000E-03 +7.300000E-02 +1.091000E-03 +2.140000E-01 +9.376000E-03 +1.830000E-01 +6.803000E-03 0.000000E+00 0.000000E+00 1.320000E-01 -3.514000E-03 -1.490000E-01 -4.731000E-03 +3.548000E-03 +1.480000E-01 +4.674000E-03 0.000000E+00 0.000000E+00 0.000000E+00 @@ -493,160 +493,160 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -1.150000E-01 -2.891000E-03 -1.560000E-01 -5.154000E-03 -7.900000E-02 -1.495000E-03 -1.770000E-01 -6.293000E-03 +1.350000E-01 +3.895000E-03 +1.430000E-01 +4.297000E-03 +5.300000E-02 +6.110000E-04 1.600000E-01 -5.352000E-03 +5.134000E-03 +1.380000E-01 +4.220000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.610000E-01 -5.273000E-03 -2.310000E-01 -1.098100E-02 -1.610000E-01 -5.233000E-03 -1.370000E-01 -3.855000E-03 -1.760000E-01 -6.482000E-03 -1.340000E-01 -3.882000E-03 +1.900000E-01 +7.412000E-03 +2.420000E-01 +1.208400E-02 +1.570000E-01 +4.999000E-03 +1.230000E-01 +3.065000E-03 +1.730000E-01 +6.279000E-03 +1.330000E-01 +3.621000E-03 1.030000E-01 -2.271000E-03 -2.090000E-01 -8.841000E-03 -1.830000E-01 -6.761000E-03 +2.229000E-03 +2.510000E-01 +1.271500E-02 +2.100000E-01 +8.970000E-03 0.000000E+00 0.000000E+00 +2.420000E-01 +1.208400E-02 +1.900000E-01 +7.412000E-03 +2.610000E-01 +1.399900E-02 +2.000000E-01 +8.410000E-03 +2.190000E-01 +9.683000E-03 +1.440000E-01 +4.282000E-03 +2.690000E-01 +1.478100E-02 +1.540000E-01 +4.986000E-03 +2.550000E-01 +1.480500E-02 +5.160000E-01 +5.892200E-02 2.310000E-01 -1.098100E-02 -1.610000E-01 -5.273000E-03 -2.620000E-01 -1.380600E-02 -1.700000E-01 -5.970000E-03 -2.410000E-01 -1.168300E-02 -1.550000E-01 -4.921000E-03 -2.250000E-01 -1.014100E-02 -1.370000E-01 -3.805000E-03 -2.100000E-01 -1.004600E-02 -5.140000E-01 -5.890200E-02 -2.320000E-01 -1.093000E-02 +1.098900E-02 0.000000E+00 0.000000E+00 -1.700000E-01 -5.970000E-03 -2.620000E-01 -1.380600E-02 +2.000000E-01 +8.410000E-03 +2.610000E-01 +1.399900E-02 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.560000E-01 -5.154000E-03 -1.150000E-01 -2.891000E-03 1.430000E-01 -4.163000E-03 -1.120000E-01 -2.640000E-03 -1.030000E-01 -2.191000E-03 -1.950000E-01 -7.705000E-03 +4.297000E-03 +1.350000E-01 +3.895000E-03 1.760000E-01 -6.392000E-03 +6.534000E-03 +1.170000E-01 +2.807000E-03 +9.000000E-02 +1.780000E-03 +2.290000E-01 +1.058700E-02 +2.080000E-01 +8.854000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -9.200000E-02 -1.822000E-03 -1.370000E-01 -3.843000E-03 -1.340000E-01 -3.882000E-03 +1.230000E-01 +3.305000E-03 1.760000E-01 -6.482000E-03 +6.598000E-03 +1.330000E-01 +3.621000E-03 +1.730000E-01 +6.279000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -6.400000E-02 -9.460000E-04 -1.510000E-01 -4.845000E-03 +6.100000E-02 +8.350000E-04 +1.410000E-01 +4.071000E-03 1.800000E-01 -6.520000E-03 +6.574000E-03 0.000000E+00 0.000000E+00 -1.370000E-01 -3.843000E-03 -9.200000E-02 -1.822000E-03 -1.300000E-01 -3.604000E-03 -1.170000E-01 -2.789000E-03 -1.370000E-01 -3.805000E-03 -2.250000E-01 -1.014100E-02 +1.760000E-01 +6.598000E-03 +1.230000E-01 +3.305000E-03 +1.690000E-01 +6.135000E-03 +1.220000E-01 +3.344000E-03 +1.540000E-01 +4.986000E-03 +2.690000E-01 +1.478100E-02 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -9.600000E-02 -2.024000E-03 -2.000000E-01 -8.202000E-03 -1.820000E-01 -6.660000E-03 +1.030000E-01 +2.543000E-03 +1.870000E-01 +7.267000E-03 +1.690000E-01 +5.731000E-03 0.000000E+00 0.000000E+00 -1.170000E-01 -2.789000E-03 -1.300000E-01 -3.604000E-03 +1.220000E-01 +3.344000E-03 +1.690000E-01 +6.135000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.120000E-01 -2.640000E-03 -1.430000E-01 -4.163000E-03 +1.170000E-01 +2.807000E-03 +1.760000E-01 +6.534000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -4.900000E-02 -5.630000E-04 -1.670000E-01 -5.653000E-03 -1.670000E-01 -5.701000E-03 +6.000000E-02 +8.060000E-04 +1.290000E-01 +3.487000E-03 +1.790000E-01 +6.747000E-03 0.000000E+00 0.000000E+00 tally 2: @@ -660,12 +660,12 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -1.150000E-01 -2.753000E-03 +1.200000E-01 +2.924000E-03 0.000000E+00 0.000000E+00 -1.820000E-01 -6.756000E-03 +1.740000E-01 +6.270000E-03 0.000000E+00 0.000000E+00 0.000000E+00 @@ -676,44 +676,44 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -1.110000E-01 -2.601000E-03 +1.200000E-01 +3.044000E-03 0.000000E+00 0.000000E+00 -1.530000E-01 -4.865000E-03 +1.510000E-01 +4.615000E-03 0.000000E+00 0.000000E+00 -1.910000E-01 -7.535000E-03 +1.710000E-01 +6.067000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -7.100000E-02 -1.079000E-03 +6.500000E-02 +9.390000E-04 0.000000E+00 0.000000E+00 -1.500000E-01 -4.606000E-03 +1.410000E-01 +4.001000E-03 0.000000E+00 0.000000E+00 -1.820000E-01 -6.756000E-03 +1.740000E-01 +6.270000E-03 0.000000E+00 0.000000E+00 -1.150000E-01 -2.753000E-03 +1.200000E-01 +2.924000E-03 0.000000E+00 0.000000E+00 -1.610000E-01 -5.383000E-03 +1.830000E-01 +6.879000E-03 0.000000E+00 0.000000E+00 -1.230000E-01 -3.141000E-03 +1.310000E-01 +3.541000E-03 0.000000E+00 0.000000E+00 0.000000E+00 @@ -724,36 +724,36 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -1.370000E-01 -3.951000E-03 +1.750000E-01 +6.659000E-03 0.000000E+00 0.000000E+00 -2.600000E-01 -1.366400E-02 +2.820000E-01 +1.605400E-02 0.000000E+00 0.000000E+00 -1.930000E-01 -7.573000E-03 +1.980000E-01 +7.932000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -8.500000E-02 -1.533000E-03 +9.400000E-02 +1.912000E-03 0.000000E+00 0.000000E+00 -2.040000E-01 -8.426000E-03 +2.200000E-01 +9.958000E-03 0.000000E+00 0.000000E+00 -1.230000E-01 -3.141000E-03 +1.310000E-01 +3.541000E-03 0.000000E+00 0.000000E+00 -1.610000E-01 -5.383000E-03 +1.830000E-01 +6.879000E-03 0.000000E+00 0.000000E+00 0.000000E+00 @@ -772,28 +772,28 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -1.250000E-01 -3.311000E-03 +1.110000E-01 +2.639000E-03 0.000000E+00 0.000000E+00 -1.620000E-01 -5.346000E-03 +1.360000E-01 +3.890000E-03 0.000000E+00 0.000000E+00 -1.860000E-01 -6.932000E-03 +1.770000E-01 +6.551000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -7.100000E-02 -1.083000E-03 +6.700000E-02 +9.310000E-04 0.000000E+00 0.000000E+00 -1.630000E-01 -5.617000E-03 +1.570000E-01 +4.975000E-03 0.000000E+00 0.000000E+00 0.000000E+00 @@ -804,100 +804,100 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -1.800000E-01 -7.002000E-03 +1.480000E-01 +4.910000E-03 0.000000E+00 0.000000E+00 -2.980000E-01 -1.815000E-02 +2.720000E-01 +1.490600E-02 0.000000E+00 0.000000E+00 -1.530000E-01 -4.865000E-03 +1.510000E-01 +4.615000E-03 0.000000E+00 0.000000E+00 -1.110000E-01 -2.601000E-03 +1.200000E-01 +3.044000E-03 0.000000E+00 0.000000E+00 -1.890000E-01 -7.605000E-03 +1.800000E-01 +6.720000E-03 0.000000E+00 0.000000E+00 -1.330000E-01 -3.743000E-03 +1.050000E-01 +2.285000E-03 0.000000E+00 0.000000E+00 1.790000E-01 -6.495000E-03 +6.499000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.080000E-01 -2.718000E-03 +9.300000E-02 +1.927000E-03 0.000000E+00 0.000000E+00 2.160000E-01 -9.662000E-03 +9.418000E-03 0.000000E+00 0.000000E+00 -2.980000E-01 -1.815000E-02 +2.720000E-01 +1.490600E-02 0.000000E+00 0.000000E+00 -1.800000E-01 -7.002000E-03 +1.480000E-01 +4.910000E-03 0.000000E+00 0.000000E+00 -2.650000E-01 -1.435500E-02 +2.680000E-01 +1.445800E-02 0.000000E+00 0.000000E+00 -1.590000E-01 -5.285000E-03 +1.480000E-01 +4.488000E-03 0.000000E+00 0.000000E+00 -2.600000E-01 -1.366400E-02 +2.820000E-01 +1.605400E-02 0.000000E+00 0.000000E+00 -1.370000E-01 -3.951000E-03 +1.750000E-01 +6.659000E-03 0.000000E+00 0.000000E+00 -2.670000E-01 -1.469300E-02 +2.510000E-01 +1.283100E-02 0.000000E+00 0.000000E+00 -1.940000E-01 -7.678000E-03 +1.870000E-01 +7.731000E-03 0.000000E+00 0.000000E+00 -2.390000E-01 -1.155500E-02 +2.340000E-01 +1.105000E-02 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.410000E-01 -1.255900E-02 +2.110000E-01 +9.215000E-03 0.000000E+00 0.000000E+00 -5.510000E-01 -7.358900E-02 +5.470000E-01 +7.228300E-02 0.000000E+00 0.000000E+00 -1.590000E-01 -5.285000E-03 +1.480000E-01 +4.488000E-03 0.000000E+00 0.000000E+00 -2.650000E-01 -1.435500E-02 +2.680000E-01 +1.445800E-02 0.000000E+00 0.000000E+00 0.000000E+00 @@ -908,36 +908,36 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -1.620000E-01 -5.346000E-03 +1.360000E-01 +3.890000E-03 0.000000E+00 0.000000E+00 -1.250000E-01 -3.311000E-03 +1.110000E-01 +2.639000E-03 0.000000E+00 0.000000E+00 -1.870000E-01 -7.083000E-03 +1.540000E-01 +4.804000E-03 0.000000E+00 0.000000E+00 -1.410000E-01 -3.999000E-03 +1.280000E-01 +3.302000E-03 0.000000E+00 0.000000E+00 -2.040000E-01 -8.438000E-03 +2.200000E-01 +9.834000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -9.300000E-02 -1.829000E-03 +9.900000E-02 +2.127000E-03 0.000000E+00 0.000000E+00 -2.240000E-01 -1.015800E-02 +1.860000E-01 +7.198000E-03 0.000000E+00 0.000000E+00 0.000000E+00 @@ -948,20 +948,20 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -1.500000E-01 -4.522000E-03 +1.430000E-01 +4.337000E-03 0.000000E+00 0.000000E+00 -1.630000E-01 -5.439000E-03 +1.580000E-01 +5.104000E-03 0.000000E+00 0.000000E+00 -1.330000E-01 -3.743000E-03 +1.050000E-01 +2.285000E-03 0.000000E+00 0.000000E+00 -1.890000E-01 -7.605000E-03 +1.800000E-01 +6.720000E-03 0.000000E+00 0.000000E+00 0.000000E+00 @@ -972,44 +972,44 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -1.650000E-01 -5.615000E-03 +1.860000E-01 +7.178000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -7.100000E-02 -1.191000E-03 +7.000000E-02 +1.020000E-03 0.000000E+00 0.000000E+00 -1.550000E-01 -5.011000E-03 +1.610000E-01 +5.237000E-03 0.000000E+00 0.000000E+00 -1.630000E-01 -5.439000E-03 +1.580000E-01 +5.104000E-03 0.000000E+00 0.000000E+00 -1.500000E-01 -4.522000E-03 +1.430000E-01 +4.337000E-03 0.000000E+00 0.000000E+00 -1.680000E-01 -5.966000E-03 +1.580000E-01 +5.254000E-03 0.000000E+00 0.000000E+00 -1.290000E-01 -3.515000E-03 +1.230000E-01 +3.091000E-03 0.000000E+00 0.000000E+00 -1.940000E-01 -7.678000E-03 +1.870000E-01 +7.731000E-03 0.000000E+00 0.000000E+00 -2.670000E-01 -1.469300E-02 +2.510000E-01 +1.283100E-02 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1020,28 +1020,28 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -1.810000E-01 -6.765000E-03 +1.980000E-01 +7.934000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.080000E-01 -2.694000E-03 +9.500000E-02 +1.949000E-03 0.000000E+00 0.000000E+00 -2.070000E-01 -8.673000E-03 +2.150000E-01 +9.289000E-03 0.000000E+00 0.000000E+00 -1.290000E-01 -3.515000E-03 +1.230000E-01 +3.091000E-03 0.000000E+00 0.000000E+00 -1.680000E-01 -5.966000E-03 +1.580000E-01 +5.254000E-03 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1052,12 +1052,12 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -1.410000E-01 -3.999000E-03 +1.280000E-01 +3.302000E-03 0.000000E+00 0.000000E+00 -1.870000E-01 -7.083000E-03 +1.540000E-01 +4.804000E-03 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1068,20 +1068,20 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -1.870000E-01 -7.213000E-03 +1.640000E-01 +5.424000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -8.200000E-02 -1.536000E-03 +6.200000E-02 +8.020000E-04 0.000000E+00 0.000000E+00 -1.760000E-01 -6.550000E-03 +1.500000E-01 +4.588000E-03 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1092,12 +1092,12 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -1.750000E-01 -6.163000E-03 +1.700000E-01 +5.884000E-03 0.000000E+00 0.000000E+00 -2.570000E-01 -1.347100E-02 +2.280000E-01 +1.084800E-02 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1108,44 +1108,44 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -1.560000E-01 -4.914000E-03 +1.540000E-01 +4.856000E-03 0.000000E+00 0.000000E+00 -2.350000E-01 -1.135900E-02 +2.250000E-01 +1.047100E-02 0.000000E+00 0.000000E+00 -1.500000E-01 -4.606000E-03 +1.410000E-01 +4.001000E-03 0.000000E+00 0.000000E+00 -7.100000E-02 -1.079000E-03 +6.500000E-02 +9.390000E-04 0.000000E+00 0.000000E+00 -1.530000E-01 -4.837000E-03 +1.360000E-01 +3.852000E-03 0.000000E+00 0.000000E+00 -5.600000E-02 -7.260000E-04 +6.400000E-02 +8.700000E-04 0.000000E+00 0.000000E+00 -2.570000E-01 -1.347100E-02 +2.280000E-01 +1.084800E-02 0.000000E+00 0.000000E+00 -1.750000E-01 -6.163000E-03 +1.700000E-01 +5.884000E-03 0.000000E+00 0.000000E+00 -2.460000E-01 -1.235000E-02 +2.230000E-01 +1.001100E-02 0.000000E+00 0.000000E+00 -1.420000E-01 -4.326000E-03 +1.410000E-01 +4.185000E-03 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1156,36 +1156,36 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -1.830000E-01 -7.133000E-03 +1.980000E-01 +8.038000E-03 0.000000E+00 0.000000E+00 -5.110000E-01 -5.696100E-02 +5.030000E-01 +5.613900E-02 0.000000E+00 0.000000E+00 -2.040000E-01 -8.426000E-03 +2.200000E-01 +9.958000E-03 0.000000E+00 0.000000E+00 -8.500000E-02 -1.533000E-03 +9.400000E-02 +1.912000E-03 0.000000E+00 0.000000E+00 -2.100000E-01 -8.928000E-03 +2.140000E-01 +9.376000E-03 0.000000E+00 0.000000E+00 -9.100000E-02 -1.807000E-03 +7.300000E-02 +1.091000E-03 0.000000E+00 0.000000E+00 -1.420000E-01 -4.326000E-03 +1.410000E-01 +4.185000E-03 0.000000E+00 0.000000E+00 -2.460000E-01 -1.235000E-02 +2.230000E-01 +1.001100E-02 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1204,28 +1204,28 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -1.430000E-01 -4.207000E-03 +1.540000E-01 +4.808000E-03 0.000000E+00 0.000000E+00 -2.120000E-01 -9.278000E-03 +2.340000E-01 +1.119400E-02 0.000000E+00 0.000000E+00 -1.630000E-01 -5.617000E-03 +1.570000E-01 +4.975000E-03 0.000000E+00 0.000000E+00 -7.100000E-02 -1.083000E-03 +6.700000E-02 +9.310000E-04 0.000000E+00 0.000000E+00 -1.770000E-01 -6.293000E-03 +1.600000E-01 +5.134000E-03 0.000000E+00 0.000000E+00 -7.900000E-02 -1.495000E-03 +5.300000E-02 +6.110000E-04 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1236,100 +1236,100 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -2.300000E-01 -1.081000E-02 +2.310000E-01 +1.084100E-02 0.000000E+00 0.000000E+00 -5.200000E-01 -5.835000E-02 +5.390000E-01 +6.215100E-02 0.000000E+00 0.000000E+00 -2.350000E-01 -1.135900E-02 +2.250000E-01 +1.047100E-02 0.000000E+00 0.000000E+00 -1.560000E-01 -4.914000E-03 +1.540000E-01 +4.856000E-03 0.000000E+00 0.000000E+00 -2.370000E-01 -1.142300E-02 +2.280000E-01 +1.059800E-02 0.000000E+00 0.000000E+00 -1.360000E-01 -3.882000E-03 +1.600000E-01 +5.214000E-03 0.000000E+00 0.000000E+00 2.160000E-01 -9.662000E-03 +9.418000E-03 0.000000E+00 0.000000E+00 -1.080000E-01 -2.718000E-03 +9.300000E-02 +1.927000E-03 0.000000E+00 0.000000E+00 -2.090000E-01 -8.841000E-03 +2.510000E-01 +1.271500E-02 0.000000E+00 0.000000E+00 1.030000E-01 -2.271000E-03 +2.229000E-03 0.000000E+00 0.000000E+00 -5.200000E-01 -5.835000E-02 +5.390000E-01 +6.215100E-02 0.000000E+00 0.000000E+00 -2.300000E-01 -1.081000E-02 +2.310000E-01 +1.084100E-02 0.000000E+00 0.000000E+00 -5.110000E-01 -5.615900E-02 +4.700000E-01 +4.972200E-02 0.000000E+00 0.000000E+00 -2.430000E-01 -1.228300E-02 +2.190000E-01 +9.821000E-03 0.000000E+00 0.000000E+00 -5.110000E-01 -5.696100E-02 +5.030000E-01 +5.613900E-02 0.000000E+00 0.000000E+00 -1.830000E-01 -7.133000E-03 +1.980000E-01 +8.038000E-03 0.000000E+00 0.000000E+00 -5.130000E-01 -6.002100E-02 +5.100000E-01 +5.929000E-02 0.000000E+00 0.000000E+00 -2.420000E-01 -1.209200E-02 +2.140000E-01 +9.406000E-03 0.000000E+00 0.000000E+00 -5.510000E-01 -7.358900E-02 +5.470000E-01 +7.228300E-02 0.000000E+00 0.000000E+00 -2.410000E-01 -1.255900E-02 +2.110000E-01 +9.215000E-03 0.000000E+00 0.000000E+00 -5.140000E-01 -5.890200E-02 +5.160000E-01 +5.892200E-02 0.000000E+00 0.000000E+00 -2.100000E-01 -1.004600E-02 +2.550000E-01 +1.480500E-02 0.000000E+00 0.000000E+00 -2.430000E-01 -1.228300E-02 +2.190000E-01 +9.821000E-03 0.000000E+00 0.000000E+00 -5.110000E-01 -5.615900E-02 +4.700000E-01 +4.972200E-02 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1340,36 +1340,36 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -2.120000E-01 -9.278000E-03 +2.340000E-01 +1.119400E-02 0.000000E+00 0.000000E+00 -1.430000E-01 -4.207000E-03 +1.540000E-01 +4.808000E-03 0.000000E+00 0.000000E+00 -2.380000E-01 -1.133600E-02 +2.170000E-01 +9.509000E-03 0.000000E+00 0.000000E+00 -1.640000E-01 -5.444000E-03 +1.600000E-01 +5.156000E-03 0.000000E+00 0.000000E+00 -2.240000E-01 -1.015800E-02 +1.860000E-01 +7.198000E-03 0.000000E+00 0.000000E+00 -9.300000E-02 -1.829000E-03 +9.900000E-02 +2.127000E-03 0.000000E+00 0.000000E+00 -1.950000E-01 -7.705000E-03 +2.290000E-01 +1.058700E-02 0.000000E+00 0.000000E+00 -1.030000E-01 -2.191000E-03 +9.000000E-02 +1.780000E-03 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1380,20 +1380,20 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -1.680000E-01 -5.878000E-03 +1.550000E-01 +4.913000E-03 0.000000E+00 0.000000E+00 -2.230000E-01 -1.049900E-02 +2.380000E-01 +1.153400E-02 0.000000E+00 0.000000E+00 -1.360000E-01 -3.882000E-03 +1.600000E-01 +5.214000E-03 0.000000E+00 0.000000E+00 -2.370000E-01 -1.142300E-02 +2.280000E-01 +1.059800E-02 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1404,44 +1404,44 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -1.550000E-01 -5.011000E-03 +1.610000E-01 +5.237000E-03 0.000000E+00 0.000000E+00 -7.100000E-02 -1.191000E-03 +7.000000E-02 +1.020000E-03 0.000000E+00 0.000000E+00 -1.510000E-01 -4.845000E-03 +1.410000E-01 +4.071000E-03 0.000000E+00 0.000000E+00 -6.400000E-02 -9.460000E-04 +6.100000E-02 +8.350000E-04 0.000000E+00 0.000000E+00 -2.230000E-01 -1.049900E-02 +2.380000E-01 +1.153400E-02 0.000000E+00 0.000000E+00 -1.680000E-01 -5.878000E-03 +1.550000E-01 +4.913000E-03 0.000000E+00 0.000000E+00 -2.490000E-01 -1.255900E-02 +2.320000E-01 +1.100400E-02 0.000000E+00 0.000000E+00 -1.280000E-01 -3.448000E-03 +1.460000E-01 +4.498000E-03 0.000000E+00 0.000000E+00 -2.420000E-01 -1.209200E-02 +2.140000E-01 +9.406000E-03 0.000000E+00 0.000000E+00 -5.130000E-01 -6.002100E-02 +5.100000E-01 +5.929000E-02 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1452,28 +1452,28 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -2.070000E-01 -8.673000E-03 +2.150000E-01 +9.289000E-03 0.000000E+00 0.000000E+00 -1.080000E-01 -2.694000E-03 +9.500000E-02 +1.949000E-03 0.000000E+00 0.000000E+00 -2.000000E-01 -8.202000E-03 +1.870000E-01 +7.267000E-03 0.000000E+00 0.000000E+00 -9.600000E-02 -2.024000E-03 +1.030000E-01 +2.543000E-03 0.000000E+00 0.000000E+00 -1.280000E-01 -3.448000E-03 +1.460000E-01 +4.498000E-03 0.000000E+00 0.000000E+00 -2.490000E-01 -1.255900E-02 +2.320000E-01 +1.100400E-02 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1484,12 +1484,12 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -1.640000E-01 -5.444000E-03 +1.600000E-01 +5.156000E-03 0.000000E+00 0.000000E+00 -2.380000E-01 -1.133600E-02 +2.170000E-01 +9.509000E-03 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1500,20 +1500,20 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -1.760000E-01 -6.550000E-03 +1.500000E-01 +4.588000E-03 0.000000E+00 0.000000E+00 -8.200000E-02 -1.536000E-03 +6.200000E-02 +8.020000E-04 0.000000E+00 0.000000E+00 -1.670000E-01 -5.653000E-03 +1.290000E-01 +3.487000E-03 0.000000E+00 0.000000E+00 -4.900000E-02 -5.630000E-04 +6.000000E-02 +8.060000E-04 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1524,12 +1524,12 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -1.320000E-01 -3.626000E-03 +1.090000E-01 +2.437000E-03 0.000000E+00 0.000000E+00 -1.510000E-01 -4.831000E-03 +1.650000E-01 +5.655000E-03 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1540,44 +1540,44 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -1.370000E-01 -3.855000E-03 +1.230000E-01 +3.065000E-03 0.000000E+00 0.000000E+00 -1.610000E-01 -5.233000E-03 +1.570000E-01 +4.999000E-03 0.000000E+00 0.000000E+00 -5.600000E-02 -7.260000E-04 +6.400000E-02 +8.700000E-04 0.000000E+00 0.000000E+00 -1.530000E-01 -4.837000E-03 +1.360000E-01 +3.852000E-03 0.000000E+00 0.000000E+00 -1.610000E-01 -5.261000E-03 +1.750000E-01 +6.299000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.510000E-01 -4.831000E-03 +1.650000E-01 +5.655000E-03 0.000000E+00 0.000000E+00 -1.320000E-01 -3.626000E-03 +1.090000E-01 +2.437000E-03 0.000000E+00 0.000000E+00 -1.490000E-01 -4.731000E-03 +1.480000E-01 +4.674000E-03 0.000000E+00 0.000000E+00 1.320000E-01 -3.514000E-03 +3.548000E-03 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1588,24 +1588,24 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -1.550000E-01 -4.921000E-03 +1.440000E-01 +4.282000E-03 0.000000E+00 0.000000E+00 -2.410000E-01 -1.168300E-02 +2.190000E-01 +9.683000E-03 0.000000E+00 0.000000E+00 -9.100000E-02 -1.807000E-03 +7.300000E-02 +1.091000E-03 0.000000E+00 0.000000E+00 -2.100000E-01 -8.928000E-03 +2.140000E-01 +9.376000E-03 0.000000E+00 0.000000E+00 -2.090000E-01 -8.775000E-03 +1.830000E-01 +6.803000E-03 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1613,11 +1613,11 @@ tally 2: 0.000000E+00 0.000000E+00 1.320000E-01 -3.514000E-03 +3.548000E-03 0.000000E+00 0.000000E+00 -1.490000E-01 -4.731000E-03 +1.480000E-01 +4.674000E-03 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1636,24 +1636,24 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -1.150000E-01 -2.891000E-03 +1.350000E-01 +3.895000E-03 0.000000E+00 0.000000E+00 -1.560000E-01 -5.154000E-03 +1.430000E-01 +4.297000E-03 0.000000E+00 0.000000E+00 -7.900000E-02 -1.495000E-03 +5.300000E-02 +6.110000E-04 0.000000E+00 0.000000E+00 -1.770000E-01 -6.293000E-03 +1.600000E-01 +5.134000E-03 0.000000E+00 0.000000E+00 -1.600000E-01 -5.352000E-03 +1.380000E-01 +4.220000E-03 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1668,100 +1668,100 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -1.610000E-01 -5.273000E-03 +1.900000E-01 +7.412000E-03 0.000000E+00 0.000000E+00 -2.310000E-01 -1.098100E-02 +2.420000E-01 +1.208400E-02 0.000000E+00 0.000000E+00 -1.610000E-01 -5.233000E-03 +1.570000E-01 +4.999000E-03 0.000000E+00 0.000000E+00 -1.370000E-01 -3.855000E-03 +1.230000E-01 +3.065000E-03 0.000000E+00 0.000000E+00 -1.760000E-01 -6.482000E-03 +1.730000E-01 +6.279000E-03 0.000000E+00 0.000000E+00 -1.340000E-01 -3.882000E-03 +1.330000E-01 +3.621000E-03 0.000000E+00 0.000000E+00 1.030000E-01 -2.271000E-03 +2.229000E-03 0.000000E+00 0.000000E+00 -2.090000E-01 -8.841000E-03 +2.510000E-01 +1.271500E-02 0.000000E+00 0.000000E+00 -1.830000E-01 -6.761000E-03 +2.100000E-01 +8.970000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.310000E-01 -1.098100E-02 +2.420000E-01 +1.208400E-02 0.000000E+00 0.000000E+00 -1.610000E-01 -5.273000E-03 +1.900000E-01 +7.412000E-03 0.000000E+00 0.000000E+00 -2.620000E-01 -1.380600E-02 +2.610000E-01 +1.399900E-02 0.000000E+00 0.000000E+00 -1.700000E-01 -5.970000E-03 +2.000000E-01 +8.410000E-03 0.000000E+00 0.000000E+00 -2.410000E-01 -1.168300E-02 +2.190000E-01 +9.683000E-03 0.000000E+00 0.000000E+00 -1.550000E-01 -4.921000E-03 +1.440000E-01 +4.282000E-03 0.000000E+00 0.000000E+00 -2.250000E-01 -1.014100E-02 +2.690000E-01 +1.478100E-02 0.000000E+00 0.000000E+00 -1.370000E-01 -3.805000E-03 +1.540000E-01 +4.986000E-03 0.000000E+00 0.000000E+00 -2.100000E-01 -1.004600E-02 +2.550000E-01 +1.480500E-02 0.000000E+00 0.000000E+00 -5.140000E-01 -5.890200E-02 +5.160000E-01 +5.892200E-02 0.000000E+00 0.000000E+00 -2.320000E-01 -1.093000E-02 +2.310000E-01 +1.098900E-02 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.700000E-01 -5.970000E-03 +2.000000E-01 +8.410000E-03 0.000000E+00 0.000000E+00 -2.620000E-01 -1.380600E-02 +2.610000E-01 +1.399900E-02 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1772,32 +1772,32 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -1.560000E-01 -5.154000E-03 +1.430000E-01 +4.297000E-03 0.000000E+00 0.000000E+00 -1.150000E-01 -2.891000E-03 +1.350000E-01 +3.895000E-03 0.000000E+00 0.000000E+00 -1.430000E-01 -4.163000E-03 +1.760000E-01 +6.534000E-03 0.000000E+00 0.000000E+00 -1.120000E-01 -2.640000E-03 +1.170000E-01 +2.807000E-03 0.000000E+00 0.000000E+00 -1.030000E-01 -2.191000E-03 +9.000000E-02 +1.780000E-03 0.000000E+00 0.000000E+00 -1.950000E-01 -7.705000E-03 +2.290000E-01 +1.058700E-02 0.000000E+00 0.000000E+00 -1.760000E-01 -6.392000E-03 +2.080000E-01 +8.854000E-03 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1812,20 +1812,20 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -9.200000E-02 -1.822000E-03 +1.230000E-01 +3.305000E-03 0.000000E+00 0.000000E+00 -1.370000E-01 -3.843000E-03 +1.760000E-01 +6.598000E-03 0.000000E+00 0.000000E+00 -1.340000E-01 -3.882000E-03 +1.330000E-01 +3.621000E-03 0.000000E+00 0.000000E+00 -1.760000E-01 -6.482000E-03 +1.730000E-01 +6.279000E-03 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1836,44 +1836,44 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -6.400000E-02 -9.460000E-04 +6.100000E-02 +8.350000E-04 0.000000E+00 0.000000E+00 -1.510000E-01 -4.845000E-03 +1.410000E-01 +4.071000E-03 0.000000E+00 0.000000E+00 1.800000E-01 -6.520000E-03 +6.574000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.370000E-01 -3.843000E-03 +1.760000E-01 +6.598000E-03 0.000000E+00 0.000000E+00 -9.200000E-02 -1.822000E-03 +1.230000E-01 +3.305000E-03 0.000000E+00 0.000000E+00 -1.300000E-01 -3.604000E-03 +1.690000E-01 +6.135000E-03 0.000000E+00 0.000000E+00 -1.170000E-01 -2.789000E-03 +1.220000E-01 +3.344000E-03 0.000000E+00 0.000000E+00 -1.370000E-01 -3.805000E-03 +1.540000E-01 +4.986000E-03 0.000000E+00 0.000000E+00 -2.250000E-01 -1.014100E-02 +2.690000E-01 +1.478100E-02 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1884,28 +1884,28 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -9.600000E-02 -2.024000E-03 +1.030000E-01 +2.543000E-03 0.000000E+00 0.000000E+00 -2.000000E-01 -8.202000E-03 +1.870000E-01 +7.267000E-03 0.000000E+00 0.000000E+00 -1.820000E-01 -6.660000E-03 +1.690000E-01 +5.731000E-03 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.170000E-01 -2.789000E-03 +1.220000E-01 +3.344000E-03 0.000000E+00 0.000000E+00 -1.300000E-01 -3.604000E-03 +1.690000E-01 +6.135000E-03 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1916,12 +1916,12 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -1.120000E-01 -2.640000E-03 +1.170000E-01 +2.807000E-03 0.000000E+00 0.000000E+00 -1.430000E-01 -4.163000E-03 +1.760000E-01 +6.534000E-03 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1932,16 +1932,16 @@ tally 2: 0.000000E+00 0.000000E+00 0.000000E+00 -4.900000E-02 -5.630000E-04 +6.000000E-02 +8.060000E-04 0.000000E+00 0.000000E+00 -1.670000E-01 -5.653000E-03 +1.290000E-01 +3.487000E-03 0.000000E+00 0.000000E+00 -1.670000E-01 -5.701000E-03 +1.790000E-01 +6.747000E-03 0.000000E+00 0.000000E+00 0.000000E+00 diff --git a/tests/regression_tests/seed/results_true.dat b/tests/regression_tests/seed/results_true.dat index bcd7118253a..ff34071c10c 100644 --- a/tests/regression_tests/seed/results_true.dat +++ b/tests/regression_tests/seed/results_true.dat @@ -1,2 +1,2 @@ k-combined: -3.069730E-01 2.632099E-03 +3.015003E-01 5.094212E-03 diff --git a/tests/regression_tests/source/results_true.dat b/tests/regression_tests/source/results_true.dat index 673d27c8af8..951075bbb90 100644 --- a/tests/regression_tests/source/results_true.dat +++ b/tests/regression_tests/source/results_true.dat @@ -1,2 +1,2 @@ k-combined: -2.959436E-01 2.782384E-03 +3.034717E-01 2.799386E-03 diff --git a/tests/regression_tests/source_file/results_true.dat b/tests/regression_tests/source_file/results_true.dat index 264e3d580d4..359e0526e6a 100644 --- a/tests/regression_tests/source_file/results_true.dat +++ b/tests/regression_tests/source_file/results_true.dat @@ -1,2 +1,2 @@ k-combined: -2.983135E-01 5.116978E-03 +2.827397E-01 1.150437E-03 diff --git a/tests/regression_tests/source_mcpl_file/results_true.dat b/tests/regression_tests/source_mcpl_file/results_true.dat index 264e3d580d4..3ba1a452003 100644 --- a/tests/regression_tests/source_mcpl_file/results_true.dat +++ b/tests/regression_tests/source_mcpl_file/results_true.dat @@ -1,2 +1,2 @@ k-combined: -2.983135E-01 5.116978E-03 +2.827397E-01 1.150438E-03 diff --git a/tests/regression_tests/sourcepoint_batch/results_true.dat b/tests/regression_tests/sourcepoint_batch/results_true.dat index 2770f25ed17..3665bdd08db 100644 --- a/tests/regression_tests/sourcepoint_batch/results_true.dat +++ b/tests/regression_tests/sourcepoint_batch/results_true.dat @@ -1,3 +1,3 @@ k-combined: -3.074376E-01 3.049465E-03 -3.667754E+00 -7.701697E+00 2.213664E+00 +2.920435E-01 9.109227E-04 +1.101997E+00 -8.197502E+00 4.294606E+00 diff --git a/tests/regression_tests/sourcepoint_latest/results_true.dat b/tests/regression_tests/sourcepoint_latest/results_true.dat index bbf03de9437..97b997ae6b9 100644 --- a/tests/regression_tests/sourcepoint_latest/results_true.dat +++ b/tests/regression_tests/sourcepoint_latest/results_true.dat @@ -1,2 +1,2 @@ k-combined: -3.070134E-01 3.900396E-03 +2.940336E-01 7.338463E-04 diff --git a/tests/regression_tests/sourcepoint_restart/results_true.dat b/tests/regression_tests/sourcepoint_restart/results_true.dat index b6d85872d1e..c20b5f2a0ad 100644 --- a/tests/regression_tests/sourcepoint_restart/results_true.dat +++ b/tests/regression_tests/sourcepoint_restart/results_true.dat @@ -1,46 +1,42 @@ k-combined: -3.070134E-01 3.900396E-03 +2.940336E-01 7.338463E-04 tally 1: 1.300000E-02 -4.700000E-05 -5.741381E-03 -9.030024E-06 +3.900000E-05 +5.833114E-03 +7.476880E-06 0.000000E+00 0.000000E+00 -3.067112E-04 -9.407179E-08 -1.000000E-03 -1.000000E-06 -3.015814E-04 -9.095135E-08 +1.164000E-03 +5.072152E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.600000E-02 -1.420000E-04 -1.268989E-02 -3.464490E-05 0.000000E+00 0.000000E+00 -2.948908E-04 -8.696057E-08 -1.000000E-03 -1.000000E-06 -1.535009E-03 -1.224718E-06 -1.000000E-03 -1.000000E-06 0.000000E+00 0.000000E+00 -3.200000E-02 -2.760000E-04 -1.248507E-02 -3.714039E-05 +2.100000E-02 +1.110000E-04 +1.108363E-02 +2.889480E-05 0.000000E+00 0.000000E+00 -9.208601E-04 -4.708050E-07 +5.861433E-04 +3.435640E-07 +2.000000E-03 +2.000000E-06 +2.043490E-03 +1.793389E-06 +2.000000E-03 +2.000000E-06 +2.930717E-04 +8.589100E-08 +2.000000E-02 +9.000000E-05 +9.334862E-03 +1.853104E-05 0.000000E+00 0.000000E+00 0.000000E+00 @@ -49,110 +45,98 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -1.900000E-02 -7.500000E-05 -8.218811E-03 -1.462963E-05 0.000000E+00 0.000000E+00 -9.070399E-04 -2.743247E-07 0.000000E+00 0.000000E+00 -9.047443E-04 -8.185622E-07 -1.000000E-03 -1.000000E-06 +2.500000E-02 +1.430000E-04 +1.110224E-02 +2.872840E-05 0.000000E+00 0.000000E+00 -1.200000E-02 -5.000000E-05 -3.938144E-03 -5.245641E-06 +5.833203E-04 +1.701353E-07 0.000000E+00 0.000000E+00 -3.067112E-04 -9.407179E-08 -1.000000E-03 -1.000000E-06 -5.897816E-04 -3.478423E-07 0.000000E+00 0.000000E+00 -2.948908E-04 -8.696057E-08 -2.000000E-02 -1.260000E-04 -9.079852E-03 -2.657486E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +5.000000E-03 +1.100000E-05 +1.752967E-03 +1.027501E-06 0.000000E+00 0.000000E+00 +2.930717E-04 +8.589100E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -3.100000E-02 -2.010000E-04 -1.336346E-02 -3.645780E-05 0.000000E+00 0.000000E+00 -2.102710E-03 -1.524522E-06 -1.000000E-03 -1.000000E-06 -9.083133E-04 -4.632477E-07 -1.000000E-03 -1.000000E-06 +1.800000E-02 +7.600000E-05 +7.300761E-03 +1.254112E-05 0.000000E+00 0.000000E+00 -2.300000E-02 -1.090000E-04 -1.334578E-02 -3.590708E-05 +5.841969E-04 +1.706438E-07 0.000000E+00 0.000000E+00 -6.031628E-04 -3.638054E-07 -1.000000E-03 -1.000000E-06 -1.530605E-03 -1.035684E-06 +5.861433E-04 +3.435640E-07 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +3.000000E-02 +1.820000E-04 +1.518122E-02 +4.757233E-05 +0.000000E+00 +0.000000E+00 +2.927374E-04 +8.569516E-08 2.000000E-03 2.000000E-06 -6.082927E-04 -1.850231E-07 -1.400000E-02 -4.200000E-05 -7.257468E-03 -1.221578E-05 +2.631167E-03 +3.503358E-06 +1.000000E-03 +1.000000E-06 +2.920435E-04 +8.528942E-08 +2.500000E-02 +1.390000E-04 +9.343205E-03 +1.910189E-05 0.000000E+00 0.000000E+00 -2.948908E-04 -8.696057E-08 +2.039724E-03 +1.271217E-06 +1.000000E-03 +1.000000E-06 +5.861433E-04 +3.435640E-07 0.000000E+00 0.000000E+00 -8.980536E-04 -4.507660E-07 -2.000000E-03 -4.000000E-06 0.000000E+00 0.000000E+00 -9.000000E-03 -2.700000E-05 -5.193169E-03 -1.187321E-05 +2.400000E-02 +1.340000E-04 +1.080336E-02 +2.533338E-05 0.000000E+00 0.000000E+00 -3.054379E-04 -9.329231E-08 +2.914595E-04 +8.494867E-08 0.000000E+00 0.000000E+00 0.000000E+00 @@ -161,14 +145,14 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -1.000000E-02 -2.400000E-05 -5.170002E-03 -7.361546E-06 +1.400000E-02 +5.200000E-05 +6.711472E-03 +1.216306E-05 0.000000E+00 0.000000E+00 -6.003287E-04 -1.802529E-07 +5.847809E-04 +1.709846E-07 0.000000E+00 0.000000E+00 0.000000E+00 @@ -177,12 +161,14 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -2.800000E-02 -1.720000E-04 -1.699195E-02 -6.267008E-05 +1.700000E-02 +7.900000E-05 +6.413106E-03 +1.186604E-05 0.000000E+00 0.000000E+00 +5.861433E-04 +3.435640E-07 0.000000E+00 0.000000E+00 0.000000E+00 @@ -191,48 +177,76 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 +2.900000E-02 +1.770000E-04 +1.314196E-02 +3.678739E-05 0.000000E+00 0.000000E+00 -2.800000E-02 -1.980000E-04 -1.338750E-02 -4.565234E-05 +1.167073E-03 +5.110971E-07 0.000000E+00 0.000000E+00 -9.183134E-04 -4.676871E-07 +5.861433E-04 +3.435640E-07 1.000000E-03 1.000000E-06 -1.537188E-03 -2.362946E-06 -2.000000E-03 -4.000000E-06 0.000000E+00 0.000000E+00 2.900000E-02 -1.950000E-04 -1.178615E-02 -3.130999E-05 +1.830000E-04 +1.372758E-02 +4.360498E-05 +0.000000E+00 +0.000000E+00 +5.858090E-04 +1.715862E-07 +0.000000E+00 0.000000E+00 0.000000E+00 -3.015814E-04 -9.095135E-08 0.000000E+00 0.000000E+00 -1.179563E-03 -1.391369E-06 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.200000E-02 +1.000000E-04 +9.631311E-03 +2.001285E-05 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.829191E-04 +3.397947E-07 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +8.000000E-03 +1.800000E-05 +4.094439E-03 +4.622331E-06 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.170949E-03 +1.371123E-06 1.000000E-03 1.000000E-06 0.000000E+00 0.000000E+00 -1.500000E-02 -6.500000E-05 -5.706064E-03 -9.729293E-06 +9.000000E-03 +5.100000E-05 +4.664405E-03 +1.087264E-05 0.000000E+00 0.000000E+00 -2.948908E-04 -8.696057E-08 0.000000E+00 0.000000E+00 0.000000E+00 @@ -241,94 +255,128 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -3.000000E-03 -9.000000E-06 -1.516642E-03 -1.579637E-06 0.000000E+00 0.000000E+00 +2.200000E-02 +1.300000E-04 +9.644227E-03 +2.300481E-05 0.000000E+00 0.000000E+00 +8.771587E-04 +4.270487E-07 0.000000E+00 0.000000E+00 -5.897816E-04 -3.478423E-07 +2.927374E-04 +8.569516E-08 1.000000E-03 1.000000E-06 0.000000E+00 0.000000E+00 -2.900000E-02 -1.970000E-04 -1.274851E-02 -3.511219E-05 +2.500000E-02 +1.630000E-04 +1.257283E-02 +3.946813E-05 0.000000E+00 0.000000E+00 -9.057666E-04 -4.601298E-07 +1.171259E-03 +8.583084E-07 0.000000E+00 0.000000E+00 +1.462299E-03 +1.112414E-06 +1.000000E-03 +1.000000E-06 0.000000E+00 0.000000E+00 +2.400000E-02 +1.400000E-04 +1.138156E-02 +3.565057E-05 0.000000E+00 0.000000E+00 +2.930717E-04 +8.589100E-08 0.000000E+00 0.000000E+00 -1.800000E-02 -7.600000E-05 -8.803642E-03 -1.730938E-05 0.000000E+00 0.000000E+00 -3.074376E-04 -9.451786E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +9.000000E-03 +2.100000E-05 +4.383252E-03 +6.073597E-06 0.000000E+00 0.000000E+00 +8.707460E-04 +7.581986E-07 0.000000E+00 0.000000E+00 -2.900000E-02 -1.830000E-04 -1.245316E-02 -3.378176E-05 0.000000E+00 0.000000E+00 -6.016020E-04 -1.810324E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +1.100000E-02 +3.500000E-05 +6.410606E-03 +1.066242E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 1.000000E-03 1.000000E-06 -6.090190E-04 -1.854692E-07 +8.756564E-04 +4.254898E-07 +1.000000E-03 +1.000000E-06 +0.000000E+00 +0.000000E+00 +1.600000E-02 +6.000000E-05 +7.287540E-03 +1.279808E-05 +0.000000E+00 0.000000E+00 +5.822922E-04 +1.695337E-07 0.000000E+00 0.000000E+00 +1.172287E-03 +1.374256E-06 +1.000000E-03 +1.000000E-06 0.000000E+00 0.000000E+00 +3.400000E-02 +2.820000E-04 +1.603656E-02 +6.229803E-05 0.000000E+00 -6.031628E-04 -3.638054E-07 +0.000000E+00 +1.460479E-03 +7.689660E-07 1.000000E-03 1.000000E-06 +1.165972E-03 +6.797578E-07 +0.000000E+00 +0.000000E+00 0.000000E+00 0.000000E+00 1.900000E-02 -8.700000E-05 -8.781239E-03 -1.676750E-05 +8.500000E-05 +7.880322E-03 +1.387762E-05 0.000000E+00 0.000000E+00 -6.128755E-04 -1.878102E-07 +8.755466E-04 +4.261064E-07 0.000000E+00 0.000000E+00 0.000000E+00 @@ -337,140 +385,138 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -3.500000E-02 -2.930000E-04 -1.372494E-02 -4.750881E-05 +8.000000E-03 +2.400000E-05 +4.666802E-03 +4.926489E-06 0.000000E+00 0.000000E+00 -9.090396E-04 -2.755502E-07 +5.829191E-04 +3.397947E-07 0.000000E+00 0.000000E+00 -5.897816E-04 -3.478423E-07 -1.000000E-03 -1.000000E-06 +8.759908E-04 +4.256857E-07 +2.000000E-03 +2.000000E-06 0.000000E+00 0.000000E+00 -3.700000E-02 -3.270000E-04 -1.492073E-02 -5.134793E-05 +1.500000E-02 +5.900000E-05 +8.180347E-03 +1.622409E-05 +0.000000E+00 +0.000000E+00 +2.902487E-04 +8.424429E-08 0.000000E+00 0.000000E+00 -6.134225E-04 -3.762872E-07 -1.000000E-03 -1.000000E-06 -3.074376E-04 -9.451786E-08 -1.000000E-03 -1.000000E-06 -6.148751E-04 -3.780714E-07 -3.500000E-02 -2.810000E-04 -1.728232E-02 -6.621096E-05 0.000000E+00 0.000000E+00 -1.214477E-03 -3.688425E-07 0.000000E+00 0.000000E+00 -1.481145E-03 -1.482321E-06 +0.000000E+00 +0.000000E+00 +2.300000E-02 +1.390000E-04 +7.593262E-03 +1.708292E-05 +0.000000E+00 +0.000000E+00 +1.750169E-03 +1.193182E-06 1.000000E-03 1.000000E-06 -3.015814E-04 -9.095135E-08 -8.000000E-03 -1.800000E-05 -3.359923E-03 -3.081200E-06 +1.461465E-03 +7.684663E-07 +2.000000E-03 +4.000000E-06 +2.914595E-04 +8.494867E-08 +2.300000E-02 +1.350000E-04 +9.639670E-03 +2.161898E-05 0.000000E+00 0.000000E+00 -3.067112E-04 -9.407179E-08 +8.781869E-04 +4.288534E-07 0.000000E+00 0.000000E+00 +8.707460E-04 +7.581986E-07 +1.000000E-03 +1.000000E-06 0.000000E+00 0.000000E+00 +2.100000E-02 +1.090000E-04 +9.045520E-03 +2.171457E-05 0.000000E+00 0.000000E+00 +5.851152E-04 +1.711804E-07 0.000000E+00 0.000000E+00 -7.000000E-03 -1.500000E-05 -3.660087E-03 -3.561493E-06 0.000000E+00 0.000000E+00 -9.203130E-04 -4.713637E-07 -1.000000E-03 -1.000000E-06 -3.015814E-04 -9.095135E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -3.500000E-02 -2.630000E-04 -1.425810E-02 -4.231681E-05 +1.800000E-02 +7.000000E-05 +1.167023E-02 +2.840320E-05 0.000000E+00 0.000000E+00 -1.516059E-03 -4.597939E-07 0.000000E+00 0.000000E+00 -1.221752E-03 -1.492677E-06 1.000000E-03 1.000000E-06 +5.854747E-04 +3.427806E-07 0.000000E+00 0.000000E+00 -2.300000E-02 -1.110000E-04 -1.184871E-02 -2.918189E-05 0.000000E+00 0.000000E+00 -6.016020E-04 -1.810324E-07 +3.000000E-03 +5.000000E-06 +1.171618E-03 +6.863446E-07 0.000000E+00 0.000000E+00 +2.930717E-04 +8.589100E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.600000E-02 -6.600000E-05 -7.879913E-03 -1.566560E-05 0.000000E+00 0.000000E+00 -9.039098E-04 -2.724298E-07 +1.300000E-02 +4.500000E-05 +6.428153E-03 +9.064469E-06 0.000000E+00 0.000000E+00 -3.067112E-04 -9.407179E-08 +2.927374E-04 +8.569516E-08 1.000000E-03 1.000000E-06 +2.920435E-04 +8.528942E-08 0.000000E+00 0.000000E+00 -9.000000E-03 -1.900000E-05 -4.256120E-03 -4.431220E-06 0.000000E+00 0.000000E+00 +1.800000E-02 +9.000000E-05 +8.185938E-03 +1.967120E-05 0.000000E+00 0.000000E+00 0.000000E+00 @@ -481,12 +527,12 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -6.000000E-03 -1.200000E-05 -2.739774E-03 -2.333986E-06 0.000000E+00 0.000000E+00 +9.000000E-03 +2.300000E-05 +4.665341E-03 +5.426897E-06 0.000000E+00 0.000000E+00 0.000000E+00 @@ -497,12 +543,12 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -1.400000E-02 -6.800000E-05 -7.182805E-03 -1.822340E-05 0.000000E+00 0.000000E+00 +4.000000E-03 +4.000000E-06 +3.497088E-03 +3.563633E-06 0.000000E+00 0.000000E+00 0.000000E+00 @@ -513,60 +559,60 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -1.500000E-02 -6.300000E-05 -5.734575E-03 -9.207711E-06 0.000000E+00 0.000000E+00 -6.070193E-04 -1.842437E-07 +5.000000E-03 +9.000000E-06 +2.333045E-03 +1.870613E-06 0.000000E+00 0.000000E+00 -8.846723E-04 -7.826452E-07 -1.000000E-03 -1.000000E-06 0.000000E+00 0.000000E+00 -2.000000E-02 -8.800000E-05 -9.416733E-03 -2.020354E-05 0.000000E+00 0.000000E+00 -6.003287E-04 -1.802529E-07 +1.172287E-03 +1.374256E-06 0.000000E+00 0.000000E+00 -6.031628E-04 -3.638054E-07 0.000000E+00 0.000000E+00 -3.015814E-04 -9.095135E-08 -7.000000E-03 -1.500000E-05 -2.413278E-03 -1.439032E-06 +8.000000E-03 +2.200000E-05 +6.413648E-03 +1.136518E-05 0.000000E+00 0.000000E+00 -2.948908E-04 -8.696057E-08 +2.927374E-04 +8.569516E-08 0.000000E+00 0.000000E+00 +2.930717E-04 +8.589100E-08 +1.000000E-03 +1.000000E-06 0.000000E+00 0.000000E+00 +2.000000E-02 +1.200000E-04 +7.007604E-03 +1.379504E-05 0.000000E+00 0.000000E+00 +5.851152E-04 +1.711804E-07 0.000000E+00 0.000000E+00 -7.000000E-03 -1.500000E-05 -4.226919E-03 -4.173413E-06 +5.861433E-04 +3.435640E-07 +1.000000E-03 +1.000000E-06 0.000000E+00 0.000000E+00 +1.700000E-02 +7.900000E-05 +7.281401E-03 +1.600845E-05 0.000000E+00 0.000000E+00 0.000000E+00 @@ -577,144 +623,108 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -2.300000E-02 -1.590000E-04 -1.125341E-02 -3.890966E-05 0.000000E+00 0.000000E+00 -5.964722E-04 -1.779119E-07 +7.000000E-03 +1.900000E-05 +3.218959E-03 +4.543526E-06 0.000000E+00 0.000000E+00 -8.846723E-04 -7.826452E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -3.200000E-02 -2.260000E-04 -1.458976E-02 -4.416780E-05 0.000000E+00 0.000000E+00 -1.814806E-03 -9.096012E-07 -1.000000E-03 -1.000000E-06 -6.031628E-04 -3.638054E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -3.100000E-02 -2.050000E-04 -1.241089E-02 -3.545088E-05 +9.000000E-03 +1.900000E-05 +4.380921E-03 +4.182826E-06 0.000000E+00 0.000000E+00 -1.515620E-03 -1.191731E-06 0.000000E+00 0.000000E+00 -6.121491E-04 -1.873641E-07 -1.000000E-03 -1.000000E-06 0.000000E+00 0.000000E+00 -1.000000E-02 -2.600000E-05 -3.936862E-03 -3.749154E-06 0.000000E+00 0.000000E+00 -2.948908E-04 -8.696057E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +2.100000E-02 +1.050000E-04 +9.932207E-03 +2.153598E-05 0.000000E+00 0.000000E+00 +5.854747E-04 +3.427806E-07 0.000000E+00 0.000000E+00 -1.100000E-02 -4.700000E-05 -5.084641E-03 -9.248485E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +1.800000E-02 +7.400000E-05 +7.301011E-03 +1.358176E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.500000E-02 -1.770000E-04 -1.021860E-02 -3.099521E-05 +5.804973E-04 +3.369772E-07 +1.000000E-03 +1.000000E-06 0.000000E+00 0.000000E+00 -3.074376E-04 -9.451786E-08 +2.300000E-02 +1.610000E-04 +9.647933E-03 +2.657983E-05 0.000000E+00 0.000000E+00 -9.086007E-04 -4.570977E-07 -2.000000E-03 -2.000000E-06 -3.054379E-04 -9.329231E-08 -1.500000E-02 -5.300000E-05 -7.584986E-03 -1.188822E-05 0.000000E+00 0.000000E+00 -6.128755E-04 -1.878102E-07 -1.000000E-03 -1.000000E-06 -6.108758E-04 -3.731692E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.800000E-02 -8.600000E-05 -6.672457E-03 -1.369871E-05 0.000000E+00 0.000000E+00 -6.141488E-04 -1.885896E-07 0.000000E+00 0.000000E+00 +9.000000E-03 +1.900000E-05 +2.915422E-03 +2.203274E-06 0.000000E+00 0.000000E+00 +8.775182E-04 +4.280701E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.100000E-02 -2.700000E-05 -3.927884E-03 -3.728010E-06 0.000000E+00 0.000000E+00 -3.074376E-04 -9.451786E-08 0.000000E+00 0.000000E+00 +1.000000E-03 +1.000000E-06 +2.920435E-04 +8.528942E-08 0.000000E+00 0.000000E+00 0.000000E+00 @@ -727,6 +737,11 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 +5.000000E-03 +9.000000E-06 +2.044244E-03 +1.796182E-06 +0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -737,15 +752,15 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -9.000000E-03 -1.900000E-05 -4.217645E-03 -4.306910E-06 0.000000E+00 +1.800000E-02 +9.000000E-05 +6.718632E-03 +1.203946E-05 0.000000E+00 -6.128755E-04 -1.878102E-07 0.000000E+00 +2.927374E-04 +8.569516E-08 0.000000E+00 0.000000E+00 0.000000E+00 @@ -753,38 +768,27 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -1.700000E-02 -7.700000E-05 -7.235504E-03 -1.301737E-05 0.000000E+00 +1.100000E-02 +2.700000E-05 +4.675703E-03 +5.128811E-06 0.000000E+00 -3.054379E-04 -9.329231E-08 +0.000000E+00 +2.914595E-04 +8.494867E-08 0.000000E+00 0.000000E+00 -1.531736E-03 -8.439742E-07 -3.000000E-03 -3.000000E-06 -3.074376E-04 -9.451786E-08 -9.000000E-03 -1.900000E-05 -3.018298E-03 -2.174671E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -6.141488E-04 -1.885896E-07 1.000000E-03 1.000000E-06 -3.074376E-04 -9.451786E-08 +2.914595E-04 +8.494867E-08 0.000000E+00 0.000000E+00 0.000000E+00 @@ -797,14 +801,14 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 +1.000000E-03 +1.000000E-06 +2.920435E-04 +8.528942E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.000000E-03 -2.000000E-06 -1.203204E-03 -7.241295E-07 0.000000E+00 0.000000E+00 0.000000E+00 @@ -813,14 +817,14 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 +8.000000E-03 +2.600000E-05 +4.670349E-03 +6.133014E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -7.000000E-03 -1.500000E-05 -4.818843E-03 -6.474375E-06 0.000000E+00 0.000000E+00 0.000000E+00 @@ -829,50 +833,46 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 +1.500000E-02 +6.100000E-05 +8.751735E-03 +1.922421E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.400000E-02 -1.180000E-04 -1.088639E-02 -2.567229E-05 0.000000E+00 0.000000E+00 -3.067112E-04 -9.407179E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +1.400000E-02 +5.000000E-05 +6.126605E-03 +9.620966E-06 0.000000E+00 0.000000E+00 -1.300000E-02 -5.100000E-05 -6.002667E-03 -1.149916E-05 +5.835031E-04 +1.702381E-07 0.000000E+00 0.000000E+00 -3.015814E-04 -9.095135E-08 0.000000E+00 0.000000E+00 -3.067112E-04 -9.407179E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +2.000000E-03 +2.000000E-06 +5.847809E-04 +1.709846E-07 0.000000E+00 0.000000E+00 -2.948908E-04 -8.696057E-08 0.000000E+00 0.000000E+00 -2.948908E-04 -8.696057E-08 0.000000E+00 0.000000E+00 0.000000E+00 @@ -881,6 +881,10 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 +2.000000E-03 +2.000000E-06 +8.719569E-04 +4.219258E-07 0.000000E+00 0.000000E+00 0.000000E+00 @@ -893,56 +897,52 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 +9.000000E-03 +2.700000E-05 +4.085939E-03 +4.936434E-06 0.000000E+00 0.000000E+00 +2.930717E-04 +8.589100E-08 0.000000E+00 0.000000E+00 -1.300000E-02 -4.700000E-05 -5.486562E-03 -7.965488E-06 0.000000E+00 0.000000E+00 -3.054379E-04 -9.329231E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +9.000000E-03 +2.500000E-05 +5.846700E-03 +9.739696E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -8.000000E-03 -1.800000E-05 -3.334317E-03 -3.609712E-06 0.000000E+00 0.000000E+00 -2.948908E-04 -8.696057E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +1.300000E-02 +3.900000E-05 +6.128455E-03 +1.013669E-05 0.000000E+00 0.000000E+00 -2.000000E-02 -1.080000E-04 -9.741552E-03 -2.552400E-05 +5.851152E-04 +1.711804E-07 0.000000E+00 0.000000E+00 -3.015814E-04 -9.095135E-08 +1.172287E-03 +1.374256E-06 0.000000E+00 0.000000E+00 -1.800986E-03 -1.622276E-06 -1.000000E-03 -1.000000E-06 0.000000E+00 0.000000E+00 0.000000E+00 @@ -962,11 +962,11 @@ tally 1: 0.000000E+00 0.000000E+00 tally 2: -5.818100E-01 -6.772558E-02 -6.344749E-01 -8.054355E-02 -3.690180E+00 -2.724752E+00 -4.102635E+01 -3.367908E+02 +5.554367E-01 +6.170503E-02 +6.055520E-01 +7.334109E-02 +3.526894E+00 +2.488065E+00 +3.925114E+01 +3.081807E+02 diff --git a/tests/regression_tests/statepoint_batch/__init__.py b/tests/regression_tests/statepoint_batch/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/regression_tests/statepoint_batch/geometry.xml b/tests/regression_tests/statepoint_batch/geometry.xml deleted file mode 100644 index bc56030e18b..00000000000 --- a/tests/regression_tests/statepoint_batch/geometry.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/tests/regression_tests/statepoint_batch/materials.xml b/tests/regression_tests/statepoint_batch/materials.xml deleted file mode 100644 index 2472a747174..00000000000 --- a/tests/regression_tests/statepoint_batch/materials.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/tests/regression_tests/statepoint_batch/results_true.dat b/tests/regression_tests/statepoint_batch/results_true.dat deleted file mode 100644 index f5c855d7778..00000000000 --- a/tests/regression_tests/statepoint_batch/results_true.dat +++ /dev/null @@ -1,2 +0,0 @@ -k-combined: -3.048864E-01 1.689118E-03 diff --git a/tests/regression_tests/statepoint_batch/settings.xml b/tests/regression_tests/statepoint_batch/settings.xml deleted file mode 100644 index e2f8dad47b3..00000000000 --- a/tests/regression_tests/statepoint_batch/settings.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - eigenvalue - 10 - 5 - 1000 - - - - -4 -4 -4 4 4 4 - - - - diff --git a/tests/regression_tests/statepoint_batch/test.py b/tests/regression_tests/statepoint_batch/test.py deleted file mode 100644 index 323b28fc659..00000000000 --- a/tests/regression_tests/statepoint_batch/test.py +++ /dev/null @@ -1,18 +0,0 @@ -from tests.testing_harness import TestHarness - - -class StatepointTestHarness(TestHarness): - def __init__(self): - super().__init__(None) - - def _test_output_created(self): - """Make sure statepoint files have been created.""" - sps = ('statepoint.03.h5', 'statepoint.06.h5', 'statepoint.09.h5') - for sp in sps: - self._sp_name = sp - TestHarness._test_output_created(self) - - -def test_statepoint_batch(): - harness = StatepointTestHarness() - harness.main() diff --git a/tests/regression_tests/statepoint_restart/results_true.dat b/tests/regression_tests/statepoint_restart/results_true.dat index aebc971f49c..b919b300058 100644 --- a/tests/regression_tests/statepoint_restart/results_true.dat +++ b/tests/regression_tests/statepoint_restart/results_true.dat @@ -1,66 +1,60 @@ k-combined: -3.070134E-01 3.900396E-03 +2.940336E-01 7.338463E-04 tally 1: 1.300000E-02 -4.700000E-05 +3.900000E-05 1.300000E-02 -4.700000E-05 -5.741381E-03 -9.030024E-06 +3.900000E-05 +5.833114E-03 +7.476880E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -3.067112E-04 -9.407179E-08 -1.000000E-03 -1.000000E-06 -1.000000E-03 -1.000000E-06 -3.015814E-04 -9.095135E-08 +1.164000E-03 +5.072152E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.600000E-02 -1.420000E-04 -2.600000E-02 -1.420000E-04 -1.268989E-02 -3.464490E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.948908E-04 -8.696057E-08 -1.000000E-03 -1.000000E-06 -1.000000E-03 -1.000000E-06 -1.535009E-03 -1.224718E-06 -1.000000E-03 -1.000000E-06 -1.000000E-03 -1.000000E-06 0.000000E+00 0.000000E+00 -3.200000E-02 -2.760000E-04 -3.200000E-02 -2.760000E-04 -1.248507E-02 -3.714039E-05 +2.100000E-02 +1.110000E-04 +2.100000E-02 +1.110000E-04 +1.108363E-02 +2.889480E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -9.208601E-04 -4.708050E-07 +5.861433E-04 +3.435640E-07 +2.000000E-03 +2.000000E-06 +3.000000E-03 +5.000000E-06 +2.043490E-03 +1.793389E-06 +2.000000E-03 +2.000000E-06 +2.000000E-03 +2.000000E-06 +2.930717E-04 +8.589100E-08 +2.000000E-02 +9.000000E-05 +2.000000E-02 +9.000000E-05 +9.334862E-03 +1.853104E-05 0.000000E+00 0.000000E+00 0.000000E+00 @@ -73,68 +67,48 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -1.900000E-02 -7.500000E-05 -1.900000E-02 -7.500000E-05 -8.218811E-03 -1.462963E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -9.070399E-04 -2.743247E-07 0.000000E+00 0.000000E+00 +2.500000E-02 +1.430000E-04 +2.500000E-02 +1.430000E-04 +1.110224E-02 +2.872840E-05 0.000000E+00 0.000000E+00 -9.047443E-04 -8.185622E-07 -1.000000E-03 -1.000000E-06 -1.000000E-03 -1.000000E-06 0.000000E+00 0.000000E+00 -1.200000E-02 -5.000000E-05 -1.200000E-02 -5.000000E-05 -3.938144E-03 -5.245641E-06 +5.833203E-04 +1.701353E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -3.067112E-04 -9.407179E-08 -1.000000E-03 -1.000000E-06 -2.000000E-03 -4.000000E-06 -5.897816E-04 -3.478423E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.948908E-04 -8.696057E-08 -2.000000E-02 -1.260000E-04 -2.000000E-02 -1.260000E-04 -9.079852E-03 -2.657486E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +5.000000E-03 +1.100000E-05 +5.000000E-03 +1.100000E-05 +1.752967E-03 +1.027501E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +2.930717E-04 +8.589100E-08 0.000000E+00 0.000000E+00 0.000000E+00 @@ -145,132 +119,120 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -3.100000E-02 -2.010000E-04 -3.100000E-02 -2.010000E-04 -1.336346E-02 -3.645780E-05 0.000000E+00 0.000000E+00 +1.800000E-02 +7.600000E-05 +1.800000E-02 +7.600000E-05 +7.300761E-03 +1.254112E-05 0.000000E+00 0.000000E+00 -2.102710E-03 -1.524522E-06 -1.000000E-03 -1.000000E-06 -1.000000E-03 -1.000000E-06 -9.083133E-04 -4.632477E-07 -1.000000E-03 -1.000000E-06 -1.000000E-03 -1.000000E-06 0.000000E+00 0.000000E+00 -2.300000E-02 -1.090000E-04 -2.300000E-02 -1.090000E-04 -1.334578E-02 -3.590708E-05 +5.841969E-04 +1.706438E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -6.031628E-04 -3.638054E-07 -1.000000E-03 -1.000000E-06 -1.000000E-03 -1.000000E-06 -1.530605E-03 -1.035684E-06 -2.000000E-03 -2.000000E-06 -2.000000E-03 -2.000000E-06 -6.082927E-04 -1.850231E-07 -1.400000E-02 -4.200000E-05 -1.400000E-02 -4.200000E-05 -7.257468E-03 -1.221578E-05 +5.861433E-04 +3.435640E-07 +0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.948908E-04 -8.696057E-08 0.000000E+00 +3.000000E-02 +1.820000E-04 +3.000000E-02 +1.820000E-04 +1.518122E-02 +4.757233E-05 0.000000E+00 0.000000E+00 0.000000E+00 -8.980536E-04 -4.507660E-07 +0.000000E+00 +2.927374E-04 +8.569516E-08 2.000000E-03 -4.000000E-06 +2.000000E-06 2.000000E-03 -4.000000E-06 +2.000000E-06 +2.631167E-03 +3.503358E-06 +1.000000E-03 +1.000000E-06 +1.000000E-03 +1.000000E-06 +2.920435E-04 +8.528942E-08 +2.500000E-02 +1.390000E-04 +2.500000E-02 +1.390000E-04 +9.343205E-03 +1.910189E-05 0.000000E+00 0.000000E+00 -9.000000E-03 -2.700000E-05 -9.000000E-03 -2.700000E-05 -5.193169E-03 -1.187321E-05 0.000000E+00 0.000000E+00 +2.039724E-03 +1.271217E-06 +1.000000E-03 +1.000000E-06 +1.000000E-03 +1.000000E-06 +5.861433E-04 +3.435640E-07 0.000000E+00 0.000000E+00 -3.054379E-04 -9.329231E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +2.400000E-02 +1.340000E-04 +2.400000E-02 +1.340000E-04 +1.080336E-02 +2.533338E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +2.914595E-04 +8.494867E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.000000E-02 -2.400000E-05 -1.000000E-02 -2.400000E-05 -5.170002E-03 -7.361546E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -6.003287E-04 -1.802529E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +1.400000E-02 +5.200000E-05 +1.400000E-02 +5.200000E-05 +6.711472E-03 +1.216306E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +5.847809E-04 +1.709846E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.800000E-02 -1.720000E-04 -2.800000E-02 -1.720000E-04 -1.699195E-02 -6.267008E-05 0.000000E+00 0.000000E+00 0.000000E+00 @@ -279,76 +241,66 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 +1.700000E-02 +7.900000E-05 +1.700000E-02 +7.900000E-05 +6.413106E-03 +1.186604E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +5.861433E-04 +3.435640E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.800000E-02 -1.980000E-04 -2.800000E-02 -1.980000E-04 -1.338750E-02 -4.565234E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -9.183134E-04 -4.676871E-07 -1.000000E-03 -1.000000E-06 -1.000000E-03 -1.000000E-06 -1.537188E-03 -2.362946E-06 -2.000000E-03 -4.000000E-06 -2.000000E-03 -4.000000E-06 0.000000E+00 0.000000E+00 2.900000E-02 -1.950000E-04 +1.770000E-04 2.900000E-02 -1.950000E-04 -1.178615E-02 -3.130999E-05 +1.770000E-04 +1.314196E-02 +3.678739E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -3.015814E-04 -9.095135E-08 +1.167073E-03 +5.110971E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.179563E-03 -1.391369E-06 +5.861433E-04 +3.435640E-07 1.000000E-03 1.000000E-06 1.000000E-03 1.000000E-06 0.000000E+00 0.000000E+00 -1.500000E-02 -6.500000E-05 -1.500000E-02 -6.500000E-05 -5.706064E-03 -9.729293E-06 +2.900000E-02 +1.830000E-04 +2.900000E-02 +1.830000E-04 +1.372758E-02 +4.360498E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.948908E-04 -8.696057E-08 +5.858090E-04 +1.715862E-07 0.000000E+00 0.000000E+00 0.000000E+00 @@ -361,12 +313,12 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -3.000000E-03 -9.000000E-06 -3.000000E-03 -9.000000E-06 -1.516642E-03 -1.579637E-06 +2.200000E-02 +1.000000E-04 +2.200000E-02 +1.000000E-04 +9.631311E-03 +2.001285E-05 0.000000E+00 0.000000E+00 0.000000E+00 @@ -377,26 +329,20 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -5.897816E-04 -3.478423E-07 -1.000000E-03 -1.000000E-06 -1.000000E-03 -1.000000E-06 +5.829191E-04 +3.397947E-07 0.000000E+00 0.000000E+00 -2.900000E-02 -1.970000E-04 -2.900000E-02 -1.970000E-04 -1.274851E-02 -3.511219E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -9.057666E-04 -4.601298E-07 +8.000000E-03 +1.800000E-05 +8.000000E-03 +1.800000E-05 +4.094439E-03 +4.622331E-06 0.000000E+00 0.000000E+00 0.000000E+00 @@ -407,20 +353,24 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 +1.170949E-03 +1.371123E-06 +1.000000E-03 +1.000000E-06 +1.000000E-03 +1.000000E-06 0.000000E+00 0.000000E+00 -1.800000E-02 -7.600000E-05 -1.800000E-02 -7.600000E-05 -8.803642E-03 -1.730938E-05 +9.000000E-03 +5.100000E-05 +9.000000E-03 +5.100000E-05 +4.664405E-03 +1.087264E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -3.074376E-04 -9.451786E-08 0.000000E+00 0.000000E+00 0.000000E+00 @@ -433,162 +383,190 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -2.900000E-02 -1.830000E-04 -2.900000E-02 -1.830000E-04 -1.245316E-02 -3.378176E-05 +0.000000E+00 +0.000000E+00 +2.200000E-02 +1.300000E-04 +2.200000E-02 +1.300000E-04 +9.644227E-03 +2.300481E-05 +0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +8.771587E-04 +4.270487E-07 0.000000E+00 -6.016020E-04 -1.810324E-07 0.000000E+00 0.000000E+00 0.000000E+00 +2.927374E-04 +8.569516E-08 +1.000000E-03 +1.000000E-06 +1.000000E-03 +1.000000E-06 +0.000000E+00 0.000000E+00 +2.500000E-02 +1.630000E-04 +2.500000E-02 +1.630000E-04 +1.257283E-02 +3.946813E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +1.171259E-03 +8.583084E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +1.462299E-03 +1.112414E-06 1.000000E-03 1.000000E-06 1.000000E-03 1.000000E-06 -6.090190E-04 -1.854692E-07 0.000000E+00 0.000000E+00 +2.400000E-02 +1.400000E-04 +2.400000E-02 +1.400000E-04 +1.138156E-02 +3.565057E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +2.930717E-04 +8.589100E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -6.031628E-04 -3.638054E-07 -1.000000E-03 -1.000000E-06 -1.000000E-03 -1.000000E-06 0.000000E+00 0.000000E+00 -1.900000E-02 -8.700000E-05 -1.900000E-02 -8.700000E-05 -8.781239E-03 -1.676750E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -6.128755E-04 -1.878102E-07 0.000000E+00 0.000000E+00 +9.000000E-03 +2.100000E-05 +9.000000E-03 +2.100000E-05 +4.383252E-03 +6.073597E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +8.707460E-04 +7.581986E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -3.500000E-02 -2.930000E-04 -3.500000E-02 -2.930000E-04 -1.372494E-02 -4.750881E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -9.090396E-04 -2.755502E-07 +0.000000E+00 +0.000000E+00 +1.100000E-02 +3.500000E-05 +1.100000E-02 +3.500000E-05 +6.410606E-03 +1.066242E-05 +0.000000E+00 +0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -5.897816E-04 -3.478423E-07 1.000000E-03 1.000000E-06 1.000000E-03 1.000000E-06 +8.756564E-04 +4.254898E-07 +1.000000E-03 +1.000000E-06 +1.000000E-03 +1.000000E-06 +0.000000E+00 +0.000000E+00 +1.600000E-02 +6.000000E-05 +1.600000E-02 +6.000000E-05 +7.287540E-03 +1.279808E-05 +0.000000E+00 0.000000E+00 0.000000E+00 -3.700000E-02 -3.270000E-04 -3.700000E-02 -3.270000E-04 -1.492073E-02 -5.134793E-05 0.000000E+00 +5.822922E-04 +1.695337E-07 0.000000E+00 0.000000E+00 0.000000E+00 -6.134225E-04 -3.762872E-07 +0.000000E+00 +1.172287E-03 +1.374256E-06 1.000000E-03 1.000000E-06 1.000000E-03 1.000000E-06 -3.074376E-04 -9.451786E-08 +0.000000E+00 +0.000000E+00 +3.400000E-02 +2.820000E-04 +3.400000E-02 +2.820000E-04 +1.603656E-02 +6.229803E-05 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.460479E-03 +7.689660E-07 1.000000E-03 1.000000E-06 1.000000E-03 1.000000E-06 -6.148751E-04 -3.780714E-07 -3.500000E-02 -2.810000E-04 -3.500000E-02 -2.810000E-04 -1.728232E-02 -6.621096E-05 +1.165972E-03 +6.797578E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.214477E-03 -3.688425E-07 0.000000E+00 0.000000E+00 +1.900000E-02 +8.500000E-05 +1.900000E-02 +8.500000E-05 +7.880322E-03 +1.387762E-05 0.000000E+00 0.000000E+00 -1.481145E-03 -1.482321E-06 -1.000000E-03 -1.000000E-06 -1.000000E-03 -1.000000E-06 -3.015814E-04 -9.095135E-08 -8.000000E-03 -1.800000E-05 -8.000000E-03 -1.800000E-05 -3.359923E-03 -3.081200E-06 0.000000E+00 0.000000E+00 +8.755466E-04 +4.261064E-07 0.000000E+00 0.000000E+00 -3.067112E-04 -9.407179E-08 0.000000E+00 0.000000E+00 0.000000E+00 @@ -599,72 +577,114 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 +8.000000E-03 +2.400000E-05 +8.000000E-03 +2.400000E-05 +4.666802E-03 +4.926489E-06 0.000000E+00 0.000000E+00 -7.000000E-03 -1.500000E-05 -7.000000E-03 -1.500000E-05 -3.660087E-03 -3.561493E-06 0.000000E+00 0.000000E+00 +5.829191E-04 +3.397947E-07 0.000000E+00 0.000000E+00 -9.203130E-04 -4.713637E-07 -1.000000E-03 -1.000000E-06 -1.000000E-03 -1.000000E-06 -3.015814E-04 -9.095135E-08 0.000000E+00 0.000000E+00 +8.759908E-04 +4.256857E-07 +2.000000E-03 +2.000000E-06 +2.000000E-03 +2.000000E-06 0.000000E+00 0.000000E+00 +1.500000E-02 +5.900000E-05 +1.500000E-02 +5.900000E-05 +8.180347E-03 +1.622409E-05 0.000000E+00 0.000000E+00 -3.500000E-02 -2.630000E-04 -3.500000E-02 -2.630000E-04 -1.425810E-02 -4.231681E-05 0.000000E+00 0.000000E+00 +2.902487E-04 +8.424429E-08 0.000000E+00 0.000000E+00 -1.516059E-03 -4.597939E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.221752E-03 -1.492677E-06 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.300000E-02 +1.390000E-04 +2.300000E-02 +1.390000E-04 +7.593262E-03 +1.708292E-05 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.750169E-03 +1.193182E-06 1.000000E-03 1.000000E-06 1.000000E-03 1.000000E-06 -0.000000E+00 -0.000000E+00 +1.461465E-03 +7.684663E-07 +2.000000E-03 +4.000000E-06 +2.000000E-03 +4.000000E-06 +2.914595E-04 +8.494867E-08 2.300000E-02 -1.110000E-04 +1.350000E-04 2.300000E-02 -1.110000E-04 -1.184871E-02 -2.918189E-05 +1.350000E-04 +9.639670E-03 +2.161898E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -6.016020E-04 -1.810324E-07 +8.781869E-04 +4.288534E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +8.707460E-04 +7.581986E-07 +1.000000E-03 +1.000000E-06 +1.000000E-03 +1.000000E-06 +0.000000E+00 +0.000000E+00 +2.100000E-02 +1.090000E-04 +2.100000E-02 +1.090000E-04 +9.045520E-03 +2.171457E-05 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.851152E-04 +1.711804E-07 0.000000E+00 0.000000E+00 0.000000E+00 @@ -673,44 +693,46 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -1.600000E-02 -6.600000E-05 -1.600000E-02 -6.600000E-05 -7.879913E-03 -1.566560E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -9.039098E-04 -2.724298E-07 +1.800000E-02 +7.000000E-05 +1.800000E-02 +7.000000E-05 +1.167023E-02 +2.840320E-05 +0.000000E+00 +0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -3.067112E-04 -9.407179E-08 1.000000E-03 1.000000E-06 1.000000E-03 1.000000E-06 +5.854747E-04 +3.427806E-07 0.000000E+00 0.000000E+00 -9.000000E-03 -1.900000E-05 -9.000000E-03 -1.900000E-05 -4.256120E-03 -4.431220E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +3.000000E-03 +5.000000E-06 +3.000000E-03 +5.000000E-06 +1.171618E-03 +6.863446E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +2.930717E-04 +8.589100E-08 0.000000E+00 0.000000E+00 0.000000E+00 @@ -721,36 +743,44 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -6.000000E-03 -1.200000E-05 -6.000000E-03 -1.200000E-05 -2.739774E-03 -2.333986E-06 0.000000E+00 0.000000E+00 +1.300000E-02 +4.500000E-05 +1.300000E-02 +4.500000E-05 +6.428153E-03 +9.064469E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +2.927374E-04 +8.569516E-08 +1.000000E-03 +1.000000E-06 +1.000000E-03 +1.000000E-06 +2.920435E-04 +8.528942E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +1.800000E-02 +9.000000E-05 +1.800000E-02 +9.000000E-05 +8.185938E-03 +1.967120E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.400000E-02 -6.800000E-05 -1.400000E-02 -6.800000E-05 -7.182805E-03 -1.822340E-05 0.000000E+00 0.000000E+00 0.000000E+00 @@ -763,76 +793,60 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 +9.000000E-03 +2.300000E-05 +9.000000E-03 +2.300000E-05 +4.665341E-03 +5.426897E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.500000E-02 -6.300000E-05 -1.500000E-02 -6.300000E-05 -5.734575E-03 -9.207711E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -6.070193E-04 -1.842437E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -8.846723E-04 -7.826452E-07 -1.000000E-03 -1.000000E-06 -1.000000E-03 -1.000000E-06 0.000000E+00 0.000000E+00 -2.000000E-02 -8.800000E-05 -2.000000E-02 -8.800000E-05 -9.416733E-03 -2.020354E-05 0.000000E+00 0.000000E+00 +4.000000E-03 +4.000000E-06 +4.000000E-03 +4.000000E-06 +3.497088E-03 +3.563633E-06 0.000000E+00 0.000000E+00 -6.003287E-04 -1.802529E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -6.031628E-04 -3.638054E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -3.015814E-04 -9.095135E-08 -7.000000E-03 -1.500000E-05 -7.000000E-03 -1.500000E-05 -2.413278E-03 -1.439032E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.948908E-04 -8.696057E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +5.000000E-03 +9.000000E-06 +5.000000E-03 +9.000000E-06 +2.333045E-03 +1.870613E-06 0.000000E+00 0.000000E+00 0.000000E+00 @@ -841,114 +855,104 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -7.000000E-03 -1.500000E-05 -7.000000E-03 -1.500000E-05 -4.226919E-03 -4.173413E-06 0.000000E+00 0.000000E+00 +1.172287E-03 +1.374256E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +8.000000E-03 +2.200000E-05 +8.000000E-03 +2.200000E-05 +6.413648E-03 +1.136518E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +2.927374E-04 +8.569516E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +2.930717E-04 +8.589100E-08 +1.000000E-03 +1.000000E-06 +1.000000E-03 +1.000000E-06 0.000000E+00 0.000000E+00 -2.300000E-02 -1.590000E-04 -2.300000E-02 -1.590000E-04 -1.125341E-02 -3.890966E-05 +2.000000E-02 +1.200000E-04 +2.000000E-02 +1.200000E-04 +7.007604E-03 +1.379504E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -5.964722E-04 -1.779119E-07 +5.851152E-04 +1.711804E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -8.846723E-04 -7.826452E-07 +5.861433E-04 +3.435640E-07 +1.000000E-03 +1.000000E-06 +1.000000E-03 +1.000000E-06 0.000000E+00 0.000000E+00 +1.700000E-02 +7.900000E-05 +1.700000E-02 +7.900000E-05 +7.281401E-03 +1.600845E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -3.200000E-02 -2.260000E-04 -3.200000E-02 -2.260000E-04 -1.458976E-02 -4.416780E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.814806E-03 -9.096012E-07 -1.000000E-03 -1.000000E-06 -1.000000E-03 -1.000000E-06 -6.031628E-04 -3.638054E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -3.100000E-02 -2.050000E-04 -3.100000E-02 -2.050000E-04 -1.241089E-02 -3.545088E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.515620E-03 -1.191731E-06 +7.000000E-03 +1.900000E-05 +7.000000E-03 +1.900000E-05 +3.218959E-03 +4.543526E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -6.121491E-04 -1.873641E-07 -1.000000E-03 -1.000000E-06 -1.000000E-03 -1.000000E-06 0.000000E+00 0.000000E+00 -1.000000E-02 -2.600000E-05 -1.000000E-02 -2.600000E-05 -3.936862E-03 -3.749154E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.948908E-04 -8.696057E-08 0.000000E+00 0.000000E+00 0.000000E+00 @@ -957,16 +961,16 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 +9.000000E-03 +1.900000E-05 +9.000000E-03 +1.900000E-05 +4.380921E-03 +4.182826E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.100000E-02 -4.700000E-05 -1.100000E-02 -4.700000E-05 -5.084641E-03 -9.248485E-06 0.000000E+00 0.000000E+00 0.000000E+00 @@ -981,70 +985,68 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 +2.100000E-02 +1.050000E-04 +2.100000E-02 +1.050000E-04 +9.932207E-03 +2.153598E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.500000E-02 -1.770000E-04 -2.500000E-02 -1.770000E-04 -1.021860E-02 -3.099521E-05 +5.854747E-04 +3.427806E-07 +0.000000E+00 +0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -3.074376E-04 -9.451786E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -9.086007E-04 -4.570977E-07 -2.000000E-03 -2.000000E-06 -2.000000E-03 -2.000000E-06 -3.054379E-04 -9.329231E-08 -1.500000E-02 -5.300000E-05 -1.500000E-02 -5.300000E-05 -7.584986E-03 -1.188822E-05 0.000000E+00 0.000000E+00 +1.800000E-02 +7.400000E-05 +1.800000E-02 +7.400000E-05 +7.301011E-03 +1.358176E-05 +0.000000E+00 0.000000E+00 0.000000E+00 -6.128755E-04 -1.878102E-07 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.804973E-04 +3.369772E-07 1.000000E-03 1.000000E-06 1.000000E-03 1.000000E-06 -6.108758E-04 -3.731692E-07 0.000000E+00 0.000000E+00 +2.300000E-02 +1.610000E-04 +2.300000E-02 +1.610000E-04 +9.647933E-03 +2.657983E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.800000E-02 -8.600000E-05 -1.800000E-02 -8.600000E-05 -6.672457E-03 -1.369871E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -6.141488E-04 -1.885896E-07 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1055,20 +1057,20 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 +9.000000E-03 +1.900000E-05 +9.000000E-03 +1.900000E-05 +2.915422E-03 +2.203274E-06 0.000000E+00 0.000000E+00 -1.100000E-02 -2.700000E-05 -1.100000E-02 -2.700000E-05 -3.927884E-03 -3.728010E-06 0.000000E+00 0.000000E+00 +8.775182E-04 +4.280701E-07 0.000000E+00 0.000000E+00 -3.074376E-04 -9.451786E-08 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1079,6 +1081,12 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 +1.000000E-03 +1.000000E-06 +1.000000E-03 +1.000000E-06 +2.920435E-04 +8.528942E-08 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1097,6 +1105,12 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 +5.000000E-03 +9.000000E-06 +5.000000E-03 +9.000000E-06 +2.044244E-03 +1.796182E-06 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1105,78 +1119,56 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -9.000000E-03 -1.900000E-05 -9.000000E-03 -1.900000E-05 -4.217645E-03 -4.306910E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -6.128755E-04 -1.878102E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +1.800000E-02 +9.000000E-05 +1.800000E-02 +9.000000E-05 +6.718632E-03 +1.203946E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +2.927374E-04 +8.569516E-08 0.000000E+00 0.000000E+00 -1.700000E-02 -7.700000E-05 -1.700000E-02 -7.700000E-05 -7.235504E-03 -1.301737E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -3.054379E-04 -9.329231E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.531736E-03 -8.439742E-07 -3.000000E-03 -3.000000E-06 -3.000000E-03 -3.000000E-06 -3.074376E-04 -9.451786E-08 -9.000000E-03 -1.900000E-05 -9.000000E-03 -1.900000E-05 -3.018298E-03 -2.174671E-06 0.000000E+00 0.000000E+00 +1.100000E-02 +2.700000E-05 +1.100000E-02 +2.700000E-05 +4.675703E-03 +5.128811E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +2.914595E-04 +8.494867E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -6.141488E-04 -1.885896E-07 -1.000000E-03 -1.000000E-06 -1.000000E-03 -1.000000E-06 -3.074376E-04 -9.451786E-08 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1185,6 +1177,12 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 +1.000000E-03 +1.000000E-06 +1.000000E-03 +1.000000E-06 +2.914595E-04 +8.494867E-08 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1201,14 +1199,14 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -2.000000E-03 -2.000000E-06 -2.000000E-03 -2.000000E-06 -1.203204E-03 -7.241295E-07 0.000000E+00 0.000000E+00 +1.000000E-03 +1.000000E-06 +1.000000E-03 +1.000000E-06 +2.920435E-04 +8.528942E-08 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1225,14 +1223,14 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -7.000000E-03 -1.500000E-05 -7.000000E-03 -1.500000E-05 -4.818843E-03 -6.474375E-06 0.000000E+00 0.000000E+00 +8.000000E-03 +2.600000E-05 +8.000000E-03 +2.600000E-05 +4.670349E-03 +6.133014E-06 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1249,18 +1247,16 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -2.400000E-02 -1.180000E-04 -2.400000E-02 -1.180000E-04 -1.088639E-02 -2.567229E-05 0.000000E+00 0.000000E+00 +1.500000E-02 +6.100000E-05 +1.500000E-02 +6.100000E-05 +8.751735E-03 +1.922421E-05 0.000000E+00 0.000000E+00 -3.067112E-04 -9.407179E-08 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1273,24 +1269,24 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -1.300000E-02 -5.100000E-05 -1.300000E-02 -5.100000E-05 -6.002667E-03 -1.149916E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -3.015814E-04 -9.095135E-08 +1.400000E-02 +5.000000E-05 +1.400000E-02 +5.000000E-05 +6.126605E-03 +9.620966E-06 +0.000000E+00 +0.000000E+00 0.000000E+00 0.000000E+00 +5.835031E-04 +1.702381E-07 0.000000E+00 0.000000E+00 -3.067112E-04 -9.407179E-08 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1301,14 +1297,18 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -2.948908E-04 -8.696057E-08 +2.000000E-03 +2.000000E-06 +2.000000E-03 +2.000000E-06 +5.847809E-04 +1.709846E-07 +0.000000E+00 +0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.948908E-04 -8.696057E-08 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1321,6 +1321,12 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 +2.000000E-03 +2.000000E-06 +2.000000E-03 +2.000000E-06 +8.719569E-04 +4.219258E-07 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1339,48 +1345,46 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 +9.000000E-03 +2.700000E-05 +9.000000E-03 +2.700000E-05 +4.085939E-03 +4.936434E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +2.930717E-04 +8.589100E-08 0.000000E+00 0.000000E+00 -1.300000E-02 -4.700000E-05 -1.300000E-02 -4.700000E-05 -5.486562E-03 -7.965488E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -3.054379E-04 -9.329231E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 +9.000000E-03 +2.500000E-05 +9.000000E-03 +2.500000E-05 +5.846700E-03 +9.739696E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -8.000000E-03 -1.800000E-05 -8.000000E-03 -1.800000E-05 -3.334317E-03 -3.609712E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.948908E-04 -8.696057E-08 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1389,32 +1393,28 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 +1.300000E-02 +3.900000E-05 +1.300000E-02 +3.900000E-05 +6.128455E-03 +1.013669E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -2.000000E-02 -1.080000E-04 -2.000000E-02 -1.080000E-04 -9.741552E-03 -2.552400E-05 +5.851152E-04 +1.711804E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -3.015814E-04 -9.095135E-08 +1.172287E-03 +1.374256E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -1.800986E-03 -1.622276E-06 -1.000000E-03 -1.000000E-06 -1.000000E-03 -1.000000E-06 0.000000E+00 0.000000E+00 0.000000E+00 @@ -1442,11 +1442,11 @@ tally 1: 0.000000E+00 0.000000E+00 tally 2: -5.818100E-01 -6.772558E-02 -6.344749E-01 -8.054355E-02 -3.690180E+00 -2.724752E+00 -4.102635E+01 -3.367908E+02 +5.554367E-01 +6.170503E-02 +6.055520E-01 +7.334109E-02 +3.526894E+00 +2.488065E+00 +3.925114E+01 +3.081807E+02 diff --git a/tests/regression_tests/statepoint_sourcesep/results_true.dat b/tests/regression_tests/statepoint_sourcesep/results_true.dat index bbf03de9437..97b997ae6b9 100644 --- a/tests/regression_tests/statepoint_sourcesep/results_true.dat +++ b/tests/regression_tests/statepoint_sourcesep/results_true.dat @@ -1,2 +1,2 @@ k-combined: -3.070134E-01 3.900396E-03 +2.940336E-01 7.338463E-04 diff --git a/tests/regression_tests/stride/results_true.dat b/tests/regression_tests/stride/results_true.dat index a654111500a..825de376675 100644 --- a/tests/regression_tests/stride/results_true.dat +++ b/tests/regression_tests/stride/results_true.dat @@ -1,2 +1,2 @@ k-combined: -2.978080E-01 6.106774E-03 +2.953207E-01 2.874356E-03 diff --git a/tests/regression_tests/surface_source/surface_source_true.h5 b/tests/regression_tests/surface_source/surface_source_true.h5 index d056d7328405bedf704f8690fd3b8056ec8aa101..2c2a19038fc1b6d9c7785e8a7f591e9e5c7fd959 100644 GIT binary patch delta 17120 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 Tbzz-5f4SO>oo}#+&4TX#jWsSQ delta 17152 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 diff --git a/tests/regression_tests/surface_source/surface_source_true.mcpl b/tests/regression_tests/surface_source/surface_source_true.mcpl index 7c819ac9bd02179ed93a1cd94b6fd8237a7b0893..ca4b7c2c4257c88baff2f9bd1c2a756fdfde92bf 100644 GIT binary patch delta 3894 zcmZYAF>tbX9l&wlQkyDTth9|vl`6!jspWl375k#1qVg(lrETs^l`2-Od0X02<*lir z#)^uX-r;-CkZJ~o3>h-yybKH(7&2sFV93CbfgwYBmw_Pz-ethgxthE0eg1II|Bm19 z8FKc|yWaWNUH3ngfBNRttLdBXhTikPzbkj{k6(G<&b0?0f8xnUebc!8!tLvgH(tG7 zfBnVRUaeMMd;Qw=NB;ls+EssdYRNl=}AUA1+gp|YW5Ft3&> z8fZt+yOY6$WoERkb)D-->Gvjsh?-i_p}Gp+lMK$d%#^mYrv5$u2pV(ApriwBDs*o$ z7<2L78$sY)*NJ*cye}EdSYtu^>gZA<<=>wS;@Z%$RyEb0a^YkUQ(ILHwX69Ln42wT zHBd#-51N@}PPomC&b6-eeP(8phl(g1F*ldl#9rifW$hn|+)GoNeOCB??*aLRJD)+d{DKD7Q{Dbzv3Wppp ztBG35JY)ylW>n`&&)dmEH-aXQd_H7E;g6V|%iL1e`IKi|`l$Jx8(iZ7`wV@|?9N4& zxT(N-%o8pu5i>Un>~WtnUb3eA!*&u|2;P1;5sZA^^Es}mR+am>eXz}#E>u~=&lWE@;Jl)ru@jcL z!);D@&WzHZH9wm?;vpjnf6n||W|vz$<0(^GS~5QyJW$^`^r#&;msE7#WWW=RofDt8 z1Lqp++-HxMoH75H9XM~W!()yZOWBKam8;z49xqZ0!G9i01lg#oi`b9%o8TQ zXnq!0XHOmHOU;~X%xBE6edlA282gg>ovU1Bn|qvU=$upbNqb?77aVY2(d%YshC5v6 zghwnZlC=}Au&Z1UNENYH|&NjwmIOC(Yzfvms#ODcX-4J)8Dig z=M`?V%X6MFqSUv{&ov&h&#>mcZGM)x#Z8`a%q1-r%+ERx*kedD6;-%les*}m5fh4k z$8NaFeQxlQ$IL7CT{~fwQ|>aS3$>Lknwu@oYeyALG*ITIooHQU9cf#Ur_9Y2rqxwb zXF62sd*;@fmetpm=1S&fiA!pfZUkcTBsaKjyiG;P!KgpyYg@O@)5orLfLL^;FbMfeJtL zW7uIrBh?fy`!QVQC8x}5_jxac7u;j^M_vwFjQMWB9HT$>l2~S&M;tOz@nSr)!V0_G z;Tb1P{lrW1>>AhE=QihF@Pb%k+OwN%a?C?6{?v{7B6K*i`NAq)? zG53yGWu#@7T;T<~%(`>N7E^z+ORh2MPM>AY{n;*A;yE{&c4y2c7yn{*)){uE$7RmA z#X{Tc>~P7Q5gUyE)%-ee4taLZxyUI4=Kf}OwwZ8e$QtMWZhp3S$pQ23L^sUO43Al6 f%$;>sxy@Z3@q*{fx*K`hE}7;IS2$tw?N|N_q0^+p delta 3830 zcmXBSF>tbX9l&whVnrJ*ucZ|&w$wbasMu1AN)?q_RIJp}9;Z=bMMX<1RaB~}VvQPY zREE4f8KRegAwz}?@yw7RLxv0)GGxdQ?=moOCj&zU1_lN{cfNPu`~080&p*fSN4EcY z=YRik=dI_SsaCStCoAK--uC~0w{Jas$9=b-xc`pq?VaHEYVfa<<{e?>&LB){OKn~2 zNWon}|E?fxsjNe7YOX7lB0-o^PfcCwK*_fU;fxIywXY3Lb)mxDL733G>N?k+ZtmU; zci$0&Yntdxxo8l^v>YvmryOYEok2KejaltzRU@4!^R6I_s-vog+E)6WAUtM-ZB=xn zt$Tw0f~g(L478~$wUm0d(b?pj2P|o(o{H}=I`^5>R1FpGH8$6I!E+{5zjrCTKeiaI z#Ek9vj5mzk3c|eeGS@iZF2|fO`(EpCUS*d%JmrWBsws1y6|lpojunYp11sF-7KglM zTEYF+!4{9=K_7=IYp&7**1;?FoKu=TP!5}Hyw5tg&r42OR6SvI?(u>*Oep_;YvCH# zdB$_bJ`jX)=NwnqKwUahEroFt6OA)v(Jmo-(F|!1%0jz#Wb`Vpf?Ye-%7Y$2qEFRTcTL@mb-JTTJU( zZ3T}Soh=?d8uT$&S*5fUu*WN2GNt54jL!xSxX&4p0E`^E3w5M4|&Nslbv%?`JC%xgweQSg+t zu*Jitf)huar@Uc7`OjGi*Lcn|#ufX# zm2jE69B{%hbIN|fnCd#8@RZS_6*yN}ag$rT z;x$vx*ot$LEgtZYGtOCJv1BdW;Ds)olT3Wc_^flEdz|uy1?9hNe6I1FXN)WM72|W6 zyBu)BF>}h6jm}k`@`MYDe%0u#a);X-amb81NqbY}Q%Op4N4vbLF2mw$`+w-RH|;tZHQEW#%+c zS7V(j`+||Rs+vx;qv(r9W|f&2gMW8kT#1C!Z}>1OeA9=~y6QUDp5n_s3|E$y!l#xO z!(Gp7n&?cqZ~0Et)v1=X@NFN8HIBK%tVUW@<~u$VJB;dBRYg|pTU*-pe9hpymaIb! z{cO&(pO;?pJ+a3lUcMCc{a5mPz9%-g$$d_ByWHS} z7tH;@YP{QJou}O6!Vj&6HRip$!!?e0#!SO%yxZY2k2zrEN7lj$v)kLw>>X&$N|$o zH9p&1aOa4%R?z>M4SFnd#7$;gxnhT@whgk$14Ue$vBKidZIK(i<^==y62GuT)_KT1 z&UwR0>*SuihUgEdL zXPt*U=bZ7b@!jod#rcxEOu9E=gSp=son20O%7Xg~ZyKL9?s11B-8kpn&HUaf+2I+F z8FM$XW}QrPz&6J`V)o6T{|B4&Ysx&~CZn!ivC7mRZIVq6dBBRRGj6f?C!=$Nfjbv$ zF|lrR);Z@MOYYpT$NZm-&ow69Ib(ydzZjp(oN~Z|J7cc1_Lg;Wm2r1YxWed$RkF$n zx0!Qi$S%`=wMw?R;LZ`VZw39o*`&uZN8Dt_l`D3b`nyfC$%rcloT=hm>{$Ufc+J3_ UmJT&n$$LFrD(Rhu_Is`W0l@o@)c^nh diff --git a/tests/regression_tests/surface_source_write/case-01/results_true.dat b/tests/regression_tests/surface_source_write/case-01/results_true.dat index 8979eb55477..d4d1d1e5ad4 100644 --- a/tests/regression_tests/surface_source_write/case-01/results_true.dat +++ b/tests/regression_tests/surface_source_write/case-01/results_true.dat @@ -1,2 +1,2 @@ k-combined: -5.169123E-02 9.144814E-03 +5.642735E-02 1.494035E-02 diff --git a/tests/regression_tests/surface_source_write/case-01/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-01/surface_source_true.h5 index 82dca4d032eec7395fe8697247c7dc4fb01f51e1..a43646158be4af47fc9ed18b9c2e2246ca57e35e 100644 GIT binary patch delta 6332 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

8Ps3wt{)|lwChWr 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 PQlfn_{VsfZ4t)1X13I-#{y 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 fIbtUfR=hm1DWH4{y87gfrX|1|kOEHDC< diff --git a/tests/regression_tests/surface_source_write/case-03/results_true.dat b/tests/regression_tests/surface_source_write/case-03/results_true.dat index 8979eb55477..d4d1d1e5ad4 100644 --- a/tests/regression_tests/surface_source_write/case-03/results_true.dat +++ b/tests/regression_tests/surface_source_write/case-03/results_true.dat @@ -1,2 +1,2 @@ k-combined: -5.169123E-02 9.144814E-03 +5.642735E-02 1.494035E-02 diff --git a/tests/regression_tests/surface_source_write/case-03/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-03/surface_source_true.h5 index 04a6ac9a387d5bd34e4d46fc533e90171d779238..4dd5f821af92dd98395a8df31af2a81fab8a8ba4 100644 GIT binary patch delta 14582 zcmb_?c|26#AGaZ9tjR7VOR^?p59eO%P)hbFOIm1=7L_(CS`@{!uax$zZHk-e4khUJpSl*Kc6%2^ZqR7bGAzj=cI;nric;eRH_#EzmOt^5eOv8 zb&|}SY1$tpokS))pb&T&uX4O(!eL4PuMGE$mO#-Kv>^;qN}1{*n)(-|S&**gWT~;{ zT$Ev68@iqjuEI|yG*Nc*Gr%wMlX?2{b8d46Qtpr>=uO6HO_otm0Y(!a0Wz`q%cH}o z^ED}VN#gW2`81(SRAEoX5j{$%AVa7nNTxHxy5?c)I#!5G{B-8YCL!TPGe5!F5ZUOG zSiiyaBsM)So1T|V&&Q_cW7G4q>G|380xUh<&VwRkf-EIlgyD-O5i(KJNk|GQTfy=L zS(HqS$$7X=YK1*zT9o076j3r~$Rf&w=zKbmlo-u=*LA;DzIL&G8AcPvc&~g<43WcK zB1G|(wjm@_w8a^HI*5~r4P2*f@A~Sq;M2v)ghC2Ig3(4!f=swc31GgSp}p2qN|~=i z5@cfaQm$j$H>{!T;-!0DnAVLXB}h_>&_IcoWWZ1*$;8N=@8?GQs8e=HFf37#A`{M0 zc1tnZT%^5X?63fdva|J(o~;(Asii2*(#vT1J#RieMI~52!5}0tCMol^2m_Sn`Sf05 zWyr)PgMHzfziQ7A!Q1_hV@ha^y5No4+v!G>Mkxkc!v$m_XL01?ekn`brSsW(lwrNo zgA=rnO!O5R&ndP1F@uD4w~*FIiZZ-#8IkYe*|g8IG_sW43vK?d_KW^gD)*mKdCcUC zi?oQJi@c+%>DY8CO^e9Htf|`-KX5Qi%3b6`W#BRaAubb!)48?z0r}Uq8Ecs8V@_$NI z*rjxP-4~OIkCPr0&$u49mr4`%v|Re!K8u$cB}U1H7`C+VW(&;=cy^8}k%=$G(+w(S z3w@T5iC5mK6}_1)%%=&pT!QjyXhM9LYAKV6RfypBlQbcoL)U0R@}g6>N_ttGVpYgQ zm~y;$_3VcJG@+hL%iF!PAxmh&Te+{5ug`92s7fXZy(t>Yq6u;CaWr9M>kmFPYb;op z`}MHYkWj*w6Phpls-0%De}CMl#3c##1W4lk*-FnGxdUzm>Oi_)w4dC z>L@0}er`f(){vz5EY+ez&>N;KCC`49(j*g|KP1%soQJXk97Ypvi7-@jy<@{--9;0Q zY?)a1QbC2~BZp-)AGLRj*E%A2B|!hTTc??M(VXLd|Cal@gIhAL*F1=bfyTZ`Cs z-au1trw(30EiqpO(KK%?3lq}YO<2C7YSS!P*k5ix&z&Vy(IFFFQg-Vw>!=QGq^hG? zr2DQoq%~VOY$)2gHUvw`JY9Nt$gFp|ueEf^v($DhHO74<+xsdkx5D(a2nCc>JqC{w zz1a|}SwEro5T{Qj#yd{Pt49dprO|=a2P~4kvZ3qgOr=(kiRHP6A`|np zW@Rf)C{S}$|K_OgtZbzTf65L`{!q_g2^~zx#4+Bm>{k<#EMd9{nQ)atFl8{3GbPgr zVhlW43>fdY0oeO`mY&X#YDOkzAd{+_9xh~cp#o->Y`X#7N-s7my;&F-rZ=13hoz_Y zp@ou(6F!^x9`?*K;~GjPL{gejx*4(Nw0X+UrXfH>jzz=YoJB?6J$x7(}ypM-ZEIun$)Uj!hH`y z%$9hpWeE{0GI2^_Tm3atCl=acn$W{M4Cx+Mo+%+%xtR06)q(>N`{TWFoPfy1rIy{; z@S9mKkUo=br?@xQfo8YK{KWemkZ@z=rOP*(!Rb=lJA)ka(6uit$!e#x@Y2MMWpP=V zqL|$(9;$YVbHY@}+<&5|y(>7mHo_RsVs1+IW-0xJ(^kh`a8jKUg-ZNn=OC6f2B&OP zdx4j~qT;Ub^>C7UuhhM_3Gm%diV5h9MGoq_{#3o`Jp+Q7$irshQN6%Njk|dfW+fEZ zIv`kGPDGsemGc6^v%vIXE}?sOdZB+@hP0`FBY6AaRedJsW+ZCZ^p_0iOYQm zb-aFr7a1-rqdu}HQ5pFE#QntwC1dOM;UQt?Yj1CT=}6NA*cUH1BB(Nshot0;)LJW_ za8PqABu49TE3A1cAD05ULCywQ*=8FjRCqya>6}}NI4+S+h=YsqJnp!7B5?@;2^DJ) zsjMFWOumtMk_T)Ai@`(+G^QXuK=xhP2uN-M-Fg{eGNqR2`h-n-dsML$5}cWFNDMp< z%32#hw0)rYm9d7w#uzV_2;+XC1L5Jn!SUF=3pXwP18Y>4HIc7&f{O`8uY5GxL4>o| zwxZ+NAUQUFt(UwqS`>TQZXtcn$58k%6n)qIU6-#-f|uF#RCCKoc z=A0{lS-qR<`Hi!^Bfwz4GkJ3QFVu7SVIAMz4a_v|QVyrKfO+v&mEZ0rgBFRnuP=)P z&}9=L9y@1QEy%_$O_y7%wulpnKRL~{NOuIce)weWv13`y;D@#_4`)|15acHh4!&xI zrUlCJNj@vktq-UCn`f<@g(&e3Qu;P`uBE3;0T1tcOE2aMQvU@a=$_OgKqrRioz?S78sgvk;>Oz z0{;q&lM1`KK$y<-oy+r3bp02<#shQ^ENA91yvHn*?yCpmU1IpC8`R%Dh}Z05dJmD4 z&O4c7Uk48j_wRn=SPLSC&p*9v+6@Mz6w~K>EJvM(tW9HQa2bV{9oP1p#bwy?7ap0X z?kV>$A2M_1g~R0!A8p%UZ2XSLP0^iDe!o?G*y>`K{L*u4wdh_%{fdMK(SemSA`~MB z8)9@FTDxG@YFZR?=%38NT|Eo&j87uW3_dBRT}lJODe`T7&onM>D7Idp?7(YJj?1X|&R z&E-nAQXhcDmB&IgsZ?b0S%fN)jG-OLRBLMz){Yx;YzK=@-_1`_KaCyLcB~&0T_?+unC;oDPDS zD9&bLWbi3PV}Th=FUm3SYXG*vForz+2vz|y$zX08wQpfY*;X(y>(@TsxnMX&sP z)Nsx=PaawrX-R{P76}TfE~US&b7^qiINB@FY>oLNh?3J7sPP;bXtnlf;thR zN0o0G+^%#r;htbEqHkd*ah?`M+I+|Wnemf#LbV&!up5eGlyA(~$%&{(o!a^CX9tLE z;!jZUZ-&=Sd88>c)`HLnnG)k|nrNR=>8h$(CQ7oIxWGidN@r;;GjEZ>Q`8l9#`WNu zQG~{)t`>M_D)^0gdM&ti!1mkeymok|YCy(^q>Fa!n|Aq5!=>%nFm_UuR5>Yq*Dc)J zI(~rDAHQ7ZnreV^xz4*LfnG3KuXl4oycz&cP2ID$l`uf2z9^_62HCs3=$`m$mWk4| zRFKl|dZ#A3pND#&LLk0dfs1)xPZfYk5eurHOcY7v;TjA#hx9ch13(!FQ zLAe6DpRgd#XR}b{=^mm6fghoZNz`~!s0D8V_djvlj2`S9qTw=U3wB)V^DA_6Dg=?c zs*c|z_wrJi`2XU2dxOXcG1|DI%@+U3cWQwMw|)i>lP`kUyn1c$I1S#N>&|<;su4IW zQE>D7)B^%-56SJYE`rmy*41;Jutuv6*LKd-4q8MoTp2nc4o=3C!4Da6at=~b;6sEo zef^UjfA}Y^LckHFB{W>-x{Mw7)B2^SBnmjG2RKK+cAOc;2T7a&ue8_PUU*Mic!Y03 zDllz+&}#Fe3A}f7x*OhagIfG11SQcjh&f0WV70DMw@iYP(+7e!FRMC(@*-Rbv3;bT zUML|K9VBPn2;Ekyrv}W4fnAqJ#}d^HP`j>^s-I_280Ug?lxHs{-@I-nAexHZKRf-} zKxfFRIPai-cxDdImRQnv;I16BF1RHJmRX+HdnRRrIy_O1%cP@VzFYV|KI7mh;1n>C zJ~9SsmFKN|7yc6;EmV;IkauG%EbUS{qbB+m2ES_TkGJdwR%_MwY;@{ErsB>#c;WzX z8)dOAgqDjIL4@M#B7^m{h^;S9uN&Hre*eY$;NUE&_$(0*8-*n{p0axf&a6Kxok8jZ zzo?JZDkBx{=A=@X*9DcV4$#&HG(vr(SSM&E_e{>Vlm zb@9+QQ_tTVRJxk2SgHw;bZZE>8&P3xDVU&Jy)$4$bes^ zDyNO_wE@k3yTySQpye{i5%YOsY*$04CYbEzIAu9K(N;2gAkbkP!2yE zCfa_3loMjhH`eY$%sh?Beqye8NmXXUJz044th?th{CRB9?zPM)?z#8E4?XU1Rl=wp zkrO7wQmFV)*sHFj59a6h)QMeINB)j)nXVJNg--!03|wKx^PtbfckXQikdU3Pzs=(Z zj*D1jWUPA74er|CxLo+O3F;Bzds9lvz{xqAM~+WF0eneGPY>B;&){NPJRFx|U#RQhZ?pZ!3y5;1I2?t zy+Y;3Jef=&c=IjC4ku4wN6K`PUn7j#nC1!e95=wH33VnqDy70-PGhEUV41g+fy;dSePsi(Dqn1wQhOA3AAlaJ-o9*rx{#|G5ff-TmqFGdi^%v!3fW2Z8qE+sXX~ULPJcR z)Z6R5&gC}s!=H-a<@>%zK&*OwP-JyCyk>5Z5?j;)Bk!TdGAB@U)rNLOU0!W`v82Ov z0ONV)V%d+#{9$l%zvqQfCjvqjlMsPbKS-$;nt>fJCx}e?4n8QT?EW~}365Rmavh2? zN3U$(pm8f5JDDo?7CQ1J+V6R$JS>E%pc}Bc(Ha2dQX;BN%j=lJP*mMc{GCltMQe_=HtMnM`m=8HhPb61bEAGvuNYkUB z`JEuFpmqPe4{bnq)%L=OzEY^RF*$JVgARCUPHf@GaaFW{lWcIy&=~ioJ`<4fd?R{u zc?w|!e)O&K(4nN`R!6jRIxf3N1=~-==)S#F1_j@2*sJ6=087d*uZc6%Les;vv>kJG zaH|a{XE)gpvMHsT=p)|{^MX}(*zl9fT*!)J-}R5LY=?@mc_DXma$|PTi>4w{W%WwHE zB90zA!WFDiYKB)7Bg|^29h==!zs^A_)`}Ikj!gX%Wmj^>ui$Ow|4*yW=O(YZ{h5>M z-x*tGEsAG6^w8HX_ICmIa~>(Hq(8ya zBZ>7Z!w0|tF&@3rgij0^43 z&05mncpWFl6Tx0!xuxiWS3oC3SG1}XKN|p`YFu->6|4+O8$RbIg)W}BI^ zA$v2|4b>fJhP$O#bQWk;!RcSwzK{AlfltxxU9UR=5&cWUpCSe#@bbrw8>G+m&G^L> z6BkiJHKa5QxT%M^KI!m=egKkhj=#Iw(Fw{=H+KE~NIQe$?z#T;ksSK-!$hV`q9H!; z&9PW7vh6sytegkwPjV{R?aGBnUEk!a_@)Ic`r4FsSmizBu9aQK@uC~3K1U>c6FwqQ zam#I^4y?qZWP#gD|Br=#DnWNMKVoxDOtxEDF?6y*D5*#JG8Jru~0)E#|Ei4Ughl@aJV_(2Y`jB?D1EQ}1^o80;BQJP1FP!|)!qjU0_(cuA?F{t@SSnB zV(gVP@E$C#*Vy^UOU+nplr^~?-6PHV(%hk{ZF4(;L$rPXj@rq&Ng3M;6pI#Djb)+9358Li# zUdl4>0Xtlr#C9#b4;*Aq-1a@z2SU##9!wzCgW;}kpmDw?x{7#4EYS?x`Z&+rwAixY z1~q(lh;K+`dX38M%J1Zm@E3O2X>TzOX@lO>;n3nH+6n$~y>}{Obx`~QYL~W97Im1M z@4tAc2T;NU>4fdrfVVb(SSunyL|Q}%Tjz?l;~i{o)kc#|9>W(E()v-J?J(zc_=rG$ zCtR<0#MmJM~5dwv zl%#<7ue?(7M*AVD>~dbkfkx!cpiq{xo+TcZmDrKsyIUE%PV5B^N18U7ye3e`C5*E} z`g?(!Q*{2;=U?E{t|tNOxay$FAw`AAeb(q3a^DKUeOh?ttzt7#nY*sO``Q#9lRYVU zwSJ8?V1B37zEcO$HcP!|sI z`fZcg=Vn+At!BgZOB7Eb@%Q3*op4ConOmv+^Y(5 z+|jhtQ7f_9U0kpgE}5^3_{Mhu;xl4Rz|W7+T=D$t;Ek53-~E6rqQPRk2Ckt5?6)D9 zP*V5Px8x3N{F4;S$=Y8=K~qnF^X+rznJI+?=Reb3YtsSd@RDyy!W-d!&dS1WzePYz2~jPGGO{U_X>MbIBcEGYq!f-j=HU zx(8-w)N@>QO^0^#T;$|Eo54>F+f9jYZP3J%`7b<%l<=jR6Pu0BCxb}e-v43xk{UGT z5NEYF3smdK{MqWB4)2|{nS1YV7pyKi?AAeT1<#ANiThB0AT9C!TZ=zh;PaF-8?OF4 z^{vYfk3lKJobAC!2blPXtKRk}VR>CJvrKaFD76k=+a@WsFuV@PJc>4cp6!S9o?Iv4 zTzdhZS6$d}kKD?!`P_>ik5I2SX*7#y&P8s8i>$5wLxCsS^2|<~_Q9YRY2-b%i7}ZeIA3<$lXHXb@9n8$@}vqUe0-X8^cNtWD$9aO9dK+>>Apo0t&p_& z%6KX40U*isl9TW$86OPyYy-*X`}wcm?t+1vsuQP$tkDJA3ry_N z=i%$Db!@m@0;jhW&FhD8P4+o1tiu56&BV)#Zary((P3wP^|>~{{qi@7mCCJf^(wQx zj9tr7&_xj2d_w^*&mPz);68U7H4toqm)7tdSQtU1CLg*nojo-GkuxT0`urE6{MmDT zgCD-aOo80H@f!fDBGZ2|NPu+&;>pI&Dyv*1sgai&>TR8XXf)y8{1@)AasO^^X%*aR zcHB$Pq(n^#1mxP~DEoBVb{tnj)Ft@n`bHpup2_h&oeMe4VmTVUdZFrAf_IRh1sV!A zJXT%D^5%LrzW={)H?ZN#{Ty3ZoiPk5cxnd@@v*)#h%;)3Mq4_7il5cNsbawg&L2ph8TsF zz;?ZxIZ9`m;2xbtn$dDSpnCZmhllHRP)g$#6a62oTIJ2gZbQcHKT#V>fIUy2k+cu( zHWB+HQD1jU+bDJB!qCL2T_xK~K-1@-lEk0^_)KE4hgOdPN|x%f$Z(RvqvXSeJ9H&- z-rmN3=$!-}Bhh%RqDrcIN8Wtb2(LI^=M%fo2}2yoSNsmw!y6a(M-Br~^uxDnEmG%M zW675d7lha(t%<1vChJV$pb|GiSCgm*vJO*~w=@9)U(KaMv|DIuy@f8-rviHUo682Z ztD$YZMBpE~2MR^P5Uf}R z-uW+A^^%!`^X)qSdw8{PChXx75$}G|0X!=*zg}3=0SD_MjgOZaqQ)I@xxvJRIA1?D zzDPk}Qs0wvpz6&NHhR_l zPkBR8%0clG-kUgUQ}>!_NOT{EJ*F4}&UUO?z z))}BYSMIgM{y``a_#oiaffgiBIN)&F9rGEZv6SD0tsAdSLhhda39uxh`r$?SU+~ha z#>FqG<)C3lvDcSf&EU3I{Oa5vUGP`<{*T3*jnE?&7b07ou;n~fj+y11NwWFvoGVNE z1Q6c5-$76r-%wHig-f_le5Om@01pW}?rC!9tE%&&lbP9=l+5(5C4 zqC6=&cMyJB*8G$|cs@F{HQn>VtgCu1tT64B>#nFcHU=fflWTb@nxNy7KTll5yWpjj zT;9|C?QlhV;Zo0~abP$-FeUHrFJ!$jC0T%W9nL&KpTi6f;~BWf;@#uSAw0o#1Vx`Y zxqJmJqb{$Xh?E1T1z!mvT7BS@)*AUYr@upM`-u7tdI)Op*#7J8S?sv6l-c~-O5EJD@oUG`Q8lH-`iDuZSEit5j!;B)$KD} z?6qPP3CRZ9^~v9)?`ooLs#XOXX0gLRy<@Q4DA{J;%~OuE>lYe)5#9J6^tS~_{&9N& zg|3V09lACMy9Kx=;=X0WEh}`LYx(ui&o}($_tDv58%=p;onx z+j-L_8~$iYq2HDoP<%L7rLV6SUg1}@3VT}udP={Y6QL|aiGLRKnp`(W@Qo%Pn>)>m zinj_2u&xQl)Ij=gjs`$kaadI8VjaAAb%3g}r5En2UuiA>s04Jw(fe7vhG>t6#uo-* z%$@vLDpEJT8aIl13&_ram61NZu=r-0)dS;h__T9c?)j-^FqYHW(oH)iFIW`v@V$^6 zDit9kd!6P^W-79O$9FHsY+=jOX&CnSNtuuZH;N*{oj7-Kf8K-)G#N=1Yf?ieGb51>UMrAN^>B zqDo=@ueB;+=gt@xubmyR&MoJNte+Mtdw5Cw6&fz{OA;YAT#Xuw*tC`^7=5!ad(Uk? zCf|SJa^!ui&!OQm&-K`GJ677|Z4wy6(@OqvTH>eObE*8-RqOVDZ-tgWtfUvhqLi~>LieG4KDh34KXUy;4kW^7G}lGy%RbGD(p)yHYh>5(91xsKeaBayR5!r-BJW?g zyLL?5Dbd(5qr{HOamJX?<>CWBzQ2s-9{<6#9%-(0@;-FyGiZPx7b}~WfMZSGO0_lv cAW%}^?G8UP)S_VH>4o&@i_M(c2($71KOTb8Y3nb!W6bpdH+mvKfHUMf0^)x~=j zh4`!d_Ms=T+SEjWIm|xA4B_3_vhJ+I2GpY>EFoEh!eoXG&CfA(oG69(>EvBsQE~a{ zpJ0869CQWPzrpkZ9C|?xyPh(j;Lp%>=R3v=javGq(l_e)X;bE&zKEMGKBQi$13 zq9mkhDccv6*%V?^QR-UK(pA){*(_f?o=xH2u0WlbEz1NVNl}P8Z&v+Kd*8|aWhg`F zy1_Pwct8Pni73@)t~H^Wsyl}@ro$WxaTaf+O`?w>8~(){3ZZ~XkY@E!kfsn8QMb)B zPcoWX)XJIWpfrWJZ81;C_Vuf&y9Ak@7iSD3L)|SyCeWxiWLPjX848i&pCcCItx4S_ z&9X#|L?J{{14yhsXBkb59X3I;IoW#8{Zl=Mp(au3b9D%2R0&yTh)T16f`Bl^C-j*MnMsq-|J2j!TbG>V=5S(vf-8bYcEWwUq~#r#&Q&*c6rpDKGJgB zrLr6&&SSTkle2q1g}5nkG{18B@H7%O+YTqbN{_;iG{b0#?K9YhOjpk4TKP90lQ60f4z?2Hm z5Q8azPBsubd8-iU;X9?W2@yDkE>0;Od(9ByrWmVJh-ZQZg)~q;h$e+7HLt$U=a23t z)E^8V|6iUH*P;;RJcpxPP1Nzaw3JHMlA)f}A`{Z7xmv86)2u}y1X71InFVASRZ&oe zO4B4WWz0BwyqH4F&Ar($B7w5wh@?#+o(MHocD-)RE}fwaA=f_>YcrJ8*gooH2u(Y? z=F~eOc)>v#RIHg}U=e8XzX8Yk$tzr6dkW3*S5bFD|O2VBjbl0U2#;G@SXO;?G z3Q=AeeJzmUibJmW--*GDvt}7J^UyR?Hql#`51DNNWNS&Ve| zVz-$fyNwv`^)7+7wZ`y<(T$UrV5XV`vu*v$D-cB8y5F3BL~W% zP<>yR;MQ(POgOh;X;?FeHkQ^>r@Ek~(m{P|4ol%np)1wV%$i_G9WrLQOx%P*ctIsB zW2TlHTaDS`PL(jRX6l*TDW(+S$eV*v4~od_G<(btmM=eIcy-i=oo1w^jNJ2;|2?dk z#TJG#gyVvtxz8qK*uu`G6v9=it{IDwgBgWM5M$uUX25FW24L-VY&|P@mr;lb$S-x@ z)cNco+|4PB9rg`#rj=eCR&HQpV3->?^xkYea|~~UQR94kg;Kl!Fryfu5Dro$P?i~F zltR3-$+~HqmI9l{Wt2kLMWv%m9-BDE@x}5d)PhWSO?_g)!l|&J5U(E0kgIOjVSC)z zl0wj-hFP-uBw133qC*#ECm1bZulfSZ8DVz&fVql?BU`weA>6pgukrFSXExd=hEUBi z6zLjQ#fu;BbaEJV->;mvDDn#|OYaqxxjPD<7|8`YNwomo_(QjI+gbpySZf!(y9K6- z@s7$BOdzAJeyc5m(dn?p!kCXEj6dH$?4fb36I64B0{?yjjj0x(IV%mCXCKW1Hck4n zulyR|9>t~UXmBsQv~yhLEy)G7`Jo#LU9s~AHbeZ>u){J&vTdKF3XeCzva?;fx`jN5 z*01CKJvn_~-{a*-_lh^*XdpNJebq;hUTIfc_Q(*WTPg4M(omabhZRU7iyc2zkphUe zhFb4gyAX`K00;NXTl2g=E}4Q!+ZV2GdGi)e#irQDu|_MK;R0)dYGGmr6pz_=E!wF8 zj8zHkh*YpZ6?7sE>=+9wV|VkfxWS;dDl-1pOhu%vHup%;yTiJ~xuKcgxKQumctu zER*6#m}&vqbBmCTW;fn}xAQ*?CY|mAg=#8A*B;T~c=(}*A*XfGt;SHw_YbRuShxh% zvs$fazH#Z#880GNlm>6@a}oS2?zW?O#61k$nVpEx^iInd@!$Kvdn#cZ=yfddIIqh^ z%X<+psFK+aE@Y2h_p+)4YwCrDgU>&O`56a0(oKFLcM2qLo%zFTVHT~KhY`u>DrK`U z!fXp5fr(nVYd&((qOX*JFT}gx&V$DK;EVy#=`j$qUW*2bULJd1Q*$1$< zNHw;-`z>%GlA~Vjxo`|)_pi7iz0TTV+rQ6*BI1-x)Cox=AQ^4+=7gW0VbuNYN0%ni z0qJwm!>% zDXsO13j$g|M8x_@T^V&WiE=e@1!H$VbCocQy4Kd3DEt1Xb`kRshsD6mZkCxrTwDuy zhOL&(_qN32*UGx=rXNhrH>eoWT`2~j#JGKKx(cjkZ<{wA~6pee)*KXB<;i5tV%JV)bJNw9NsKAY` zrq0!~V&Y;h6`6LaQOWr+n#bxkkj0mKsiTLBwrG1;%wC;-Xs}!2ZKGZi46u&(l2WOL z3cJ@V?2|xH6`I_eX%vPpFsnsp%w;|I3&ksPX7@nUg`Qp@}l_bL( zt@IpPKairnSfbSa8;txIc)QrK4?Np4W=wr@7YGPP?zag142~Pec)hN&M7_5?yZUG8 zli;9yrL{P>K7|`$iW$k&Sm4xbF%0q)FG+8fZUK&^zH#-g-QdiFESiPveef(`PK|q= z8k&{%G~nYOlqIp!_xmDGveG*-L_J1(Hfd%Cqcp${pARbT)^L#{z%q ze1qel@YIFdBBFTEAX^3^)-4%q0IIapFC7T0L6v&GXHHZvc+r;g#_i@3RPU=xFYk2d zvyN_39Joedbv3IqCt$>L)1P7uU-1O5S3*4>5gWe1})2fH^HW!mE`glRQ5+n;}p&BcTAZ@9iDJ^VKri`tCU zoVYHrn>tn>;G!{y6QJ!{74}+b4^cy6R|wrWOTM;~13v*<|Fl4`hjQ?d7( zG#Y$T?Ae~_WrS8c&u*(^rXc1!5*BwyQm$Oybo&Q5Bcdb!ewIM=$HBVL&Hh_o+%hThkiKuu5_X6T=bNoOymm0Rlnz$=6wACV)aZkWXZ&?nHa?L8( z|D%A0&$D>eN6Gk|9Uw(iKZTG*he~gYXo0+Mwohm`RWCOUdO$tq1*Y*&5m6 z={*m#{9So$U4P66xU<2*b1=0OIzO1J>9wH)mMl>1++NTIUF;*vgl;|p+!vSHdT5HE z0gvwlWIi&$3$ffZiec-(;nUGu=`eQeDZ1I`JA7eLay}`p7l;`L@mcL^06u(brnjG_ z1LTcCoK1WovZ|C6BEd!7DJYFqFgXqrd4x+$3pb46!SZ-MT};8T9&E^QzuvdH0>rjY zT5hhQgOo)oeK%~o;Fh=a*Wh9)P)}NORP97DzQ%Im+SJHepAz_n<8n3UM|ROn;RUL3 z8DX*+Bwc!X&TwTV%qUKJCz{a(&xWT}n}s|9+#w>m@+wvTiM#3-pAS#OH;8niOV0Pk z&rA*N`$8M#XI%cU2~k=4>tq8w$bGW(`psS#UKd+EFUJ@?_V})yR3~<-#r!az!w*qX z=X~$G@zX?hgt_f{Ts6a+c6p%6vZ(hfe17)=b?|%@q-;L5>ITvQU1fXg*W9TBA-d@E z7tdc!Z~w9IT!2mW#*M|*&R;%3Kysv$2L%6$@}batmLVw@pXvqFb8D@M7f;xlPMT@s zVI)5t`c!cbG9jOedd$P@kms<&&R<$XGlrYS6bsOTTBRNd&g%mg=!@pTraIW06qUN- zc_&;9+n<1lCNNiZDrR59H)M(R5LfnHFrBOzt^xgNWa@&{4ZFQ``nc%s&x()-1gDMi_Ce zbsKik;iCNwCH_r+#K2<;o~Vi(>{>23ie#sX($2ZMZ&wKvMVM-Vzv3p-6AVHf9Pru0 z*ilLA$i%DPyp?pkH1H9XI7U2L6ozo!5Tk{bUf$Ld&d#6tOKRfmW2)fl$(9S&Hy(k* z*SI$p#`XiPW2F_T7nRYFhcSE7+8IgBdhMZv`D4GJ^u_&$>tM~3<8NbxhJfv-T1_p{ z9!ODykHUv5Ag^t3S>}UAP$j+1jr_?P-C@fwh+1LSs@PFinZ=HW8n6x!+Nn?0F{5K4 z2kRxKIl*le9l&_eY8RhTwx5*S9PXS4+3@{s@8;t4a&XVecpXn)KMX?55*L41jCSrA z9e$k0&K^$O6@=)$JZ)n$*@!&6-7I}bYZRoN8*F!6&;)h~MD8Ev}r!T~pG!+iqQX5;BBYOO_@eA6*Z}PfkqMGJA`)d0U zQYHM|AXV2~{uPv|bO>pubimhd28y0l*CF=HQXRL2Q}B3E#b)o^NdwOneXTHR{cdHx zrvkJ`TQ(;i;G)CrPv57|bGku{_NC8tQ;SEV{`A{mNr zGaXa!2cJhaI$Jqc0y8lIb9?(P=>GG}-YbPIP$k=Y~IdDV1HN4M^{R{_=A@}r}@xv3%>;0X{t&1TTv31uqzt;xB z9~S6Vm~_C+c1Dx4%u&=?d*?Y_Wp)U0zVNww_fqSK&u5&ciQ<|@86|8$zi0T;E{!DZx|1vVSKOfGGD#af_3%Q=iTiuAro_6k zkr`a%yR=`Msuwpxlb$>}k@pg|+`Ph_q22*%_jTwL*0zDGXH?$Kinl;%U;M%p3bDlk zn>Q^EzW={)7jxh?yFBd@<9-HJb=J=-NEAbuY5`;{|9+=yU@y3}MnTTp@iQKi@58$}kEWZ4#=aYUuW93;~G)Lp~S6&t3!%q8sW(A8dO>2qsf)8C_0e;F2YWs`>El^TX0e)i z@Re`e5xiBHw$ps8he>SZ47dBl~71wS!Z8f$HA!EDbmAhWAG%({sxXwAblW1u$ zLu6LXwxxZH<;8wh1Mw56bkDBq6D|PvC(~u8e09;@oD*s%3O+v_hvomM%T#|5qlK(p>L2#L2h67%6lwOB z!!9WG@_c(ec-<4Gs#7qGkVQX$p z-x_jUKYjqcbJlzjN2Z|WmI+mxFFhc)f^=`|ry3CSyALW}sfNctsAXS&@DnMOnvfP; zi(O-5?l$CbH|5nQGr}DNvoML>OZPno z{oX5rxcokY2Fi|K^4emk?Fv8Tv(X6dDWmBZb;ig|8(+1gntF(OygOLp-3)xj8_I>U zx&hDg4cBhzJ%tuBaj|~>bf6&S`+;x~pa=N*eb2-w;V{P7jsN;X6XlC@djU1sLS;Ct z8s1m5)j0B~7wDbXP*)p$3G(UfE=_W7U|f8DFe!;%fo}0AmC^TLyoqPMk1*kI)q?Km zay6j|AQzgP^mukHo=wB@#ONiSB`~8c&+21-F}y%LWby#^gA)VlOG)n*qc`%0g#7JP z@ET&ufh+AL;{K?ceZePqFgSAj&=fFJ+5zvVG{OZiuJLJoU%|~iwv`{0 zXvhxd1f`Bi8+@%=%7M$X(dBJIyUiv z_A@rYtg2$hm?ddrT8h1?_V3f8i2Ih>7g8F+f2DW4Lnlx+YcamxnTC-uDwn(Uk(AR5 z;+)FzfCCRg|J-y)_FTB<wv|c_5LXfB0+P=+(f~qe(+01 z-RERO1uQ?=U47$#61u&4Byj%*_Qr%2eKW9JZAX7yS~Y<$^t4S2pDn!jatQD~)TEdh zRKX~-G_d4l4|Iqk#w%FW0e_Y4KT}TYpmUn@>o@OYZy*pBA`k1i+H2@(rPna%U0ZNy zyoDW1=t{dJp_(=zb)q^tIJFMapPHE!n0y5)xAo)Et)9p(%b z6tRt;)AX%5=XVwr(aH<#?=`{x6#IkME!Z~(A{plB!85(^!9Ekamfu;B*ZAcwLy-pH z{-KsfJJ}i?N3QArB2jlrGcPtQIou%k#5%p}1QGc$9O2@+tcB%$0mP<4zk9K56<9NC z(*c6zR}f64Pg*r}0PeR{cjJ{cP}1@1viGi8;xWG*E0ZVnx4*MBX$NkRK?Y4r1^

9~Z3h2uxPwu_`YUdKuQ+}EC2UHt6_FFSG|=04 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

gACz3xS$)7QvAJ4q~sq5iSLU?Z+ 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 diff --git a/tests/regression_tests/surface_source_write/case-05/results_true.dat b/tests/regression_tests/surface_source_write/case-05/results_true.dat index 8979eb55477..d4d1d1e5ad4 100644 --- a/tests/regression_tests/surface_source_write/case-05/results_true.dat +++ b/tests/regression_tests/surface_source_write/case-05/results_true.dat @@ -1,2 +1,2 @@ k-combined: -5.169123E-02 9.144814E-03 +5.642735E-02 1.494035E-02 diff --git a/tests/regression_tests/surface_source_write/case-05/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-05/surface_source_true.h5 index 1a2f54d765811bd10a2758e20a11b7ea1f242ae5..ee00af3895c349d60d786af1aedef39e05e24d20 100644 GIT binary patch delta 16296 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�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 ZGW~B#GSJ)n@9qo(Vl%43UYmve{vZ9g!(IRY diff --git a/tests/regression_tests/surface_source_write/case-07/results_true.dat b/tests/regression_tests/surface_source_write/case-07/results_true.dat index 8979eb55477..d4d1d1e5ad4 100644 --- a/tests/regression_tests/surface_source_write/case-07/results_true.dat +++ b/tests/regression_tests/surface_source_write/case-07/results_true.dat @@ -1,2 +1,2 @@ k-combined: -5.169123E-02 9.144814E-03 +5.642735E-02 1.494035E-02 diff --git a/tests/regression_tests/surface_source_write/case-07/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-07/surface_source_true.h5 index f84a5c4c5b6d8cb4ad3f720e40580f6309998d57..60527c99bc259a790c1f71ca36c7a00bef7fdf3f 100644 GIT binary patch delta 16301 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 delta 16328 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 diff --git a/tests/regression_tests/surface_source_write/case-08/results_true.dat b/tests/regression_tests/surface_source_write/case-08/results_true.dat index 8979eb55477..d4d1d1e5ad4 100644 --- a/tests/regression_tests/surface_source_write/case-08/results_true.dat +++ b/tests/regression_tests/surface_source_write/case-08/results_true.dat @@ -1,2 +1,2 @@ k-combined: -5.169123E-02 9.144814E-03 +5.642735E-02 1.494035E-02 diff --git a/tests/regression_tests/surface_source_write/case-08/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-08/surface_source_true.h5 index 1fd302a7cd429beaee0a203ec8e5f1da09cb09d9..3088597c08e1b4af24b1004f8d62e66bd18d6631 100644 GIT binary patch delta 16492 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 delta 16528 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 P!D9IFE&iy17We-E%q{pF diff --git a/tests/regression_tests/surface_source_write/case-09/results_true.dat b/tests/regression_tests/surface_source_write/case-09/results_true.dat index 8979eb55477..d4d1d1e5ad4 100644 --- a/tests/regression_tests/surface_source_write/case-09/results_true.dat +++ b/tests/regression_tests/surface_source_write/case-09/results_true.dat @@ -1,2 +1,2 @@ k-combined: -5.169123E-02 9.144814E-03 +5.642735E-02 1.494035E-02 diff --git a/tests/regression_tests/surface_source_write/case-09/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-09/surface_source_true.h5 index af832f060cbfbfd6f25fd6cd2049a5bfef55e307..81cdbfb0fe8919edb309daee668fd689fc71507b 100644 GIT binary patch delta 15744 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 delta 15900 zcmaL7cRbbq`#)}WC>4@j_Lfm(M33_{IwxdRDpFKNJ6c8wNy@04(vC_J$tWxII3h$c z%E&zS-g}dj6;}h@dgtUE`tNhOu#X7icq@ZoLH8rE4Wjf!t6_bGvll{Fx2s60t+L7m65>4 zNML6qtYRdf7zrGV1Wrc6YDU5uMgrFg0%dNl>#*Y4*6Re(l{n8^Al zUx#HmEshgjPt}k1dMqmNP=ZXeQw%PWVE zR|#C@@MN0Bh4JuHWW#R2GDqVcY@p&Nk-B7dWP}aN2;@E=Lw_SfpP!*GfO~Dok1N}# z&fJD$=BG#{z%WLLp)btP7g_F8B*qJ1QJMh>QLl_fm$XS1Y!fb50Q3J)H~%M9>_4gE z|4Ef#q*6ps6DHa2o`L2Uk_aT*tzB{jDXLqn!l?^_|3?&QMj*1-GXF`H{ZFdge^TZD zldABaRK@?KD&gEVVNfr=Tw(Ju9>v|`p}J@7CM=5k`gLV0ku2_({~j8<8H*~$B}yM7 zeNXp!ZZRyXLdIEzYbDWxM10hi^X%%b6`TkpqPBuM zM6B7fca!N#OhE}Os_KR%!GERnz9gc$=1#Wul_R@J#Q2w=uWPL&T1sM3wHRIfdn<_< zB%-SDvCM}QB6;#U|2_F2DJ)7+_8TGEMrwKT!z3d5dFYj)r(`1edp(?^EQYxp_gb2= z4)k#nQq(?WcJeY<=2tj38R|;BFM~w|EQpF6x+1a+JWV40Q{1j~Lsw_nR1UIO3hv$| zKQy5FA+0G3cO3}+JO?;pZ9i;aABNdG^*tdo0XRtNvS8f40b!3#t$Ftcp|_Jn52tMf zmQcG8T(>#g(oxJy0!s%U0xa4(1$Hh5HgBAKQBE=2? zT#}p`s>|?KbVi3BZpe`0o8j_URJb=wje4s55(-(n#thoswX9vT^cb2tsO_+hG%2Nypy!S#VnqHe6&Pzrtg#hb}Kknk+CU*D<;4ur5D$)8n2%q(7p zofnp*OOK`EvEvX0z#NJ5Q=nLPH}16pW<}qGp>Mj}r&u4Yghl0Aq2`3ove3czP{BDF zz|9#y_E5&6I8T(G->E_Vn5+@Yep}&`*zi+uu*5G~y4NCh3Wu$&@1!z;h zR>>!eu(N*bYyuDxqo!IPx+{58y3an7M{D|-1_BR zT}@-+U=-=L~`$iVn-J~J>^;83Nm)MblT<1lQejr~D{(v0rhF?c zpm6CxLJkKgD%>huvNk|@t<`u*-REyCF{PcosDqf&--#UgyNw8=PD@*~78C;tode%= zFX^6^2?1`#Fw6aj?1J23S6+r%H-O2?)n{&t8zADUw>8a-NB`%!j*-ttF9Db3Xb|-DC*-$;K&v_fa%%ac~wjAhn?LKEVCOv~$gbnX{?FXP}y5RRCNB+Ue5ZU;%7oUP! zp3KBP{{`GpUdjSqgG<)Mpp@@Q&<=TIZd?qru#hBfeWpZ&=4J23OeY%Prwp7tdw4rs z%klVUq-Za6n`Te`$NnDjDceVGc54Lz0rm^(0t(0khw3PC#m>%!6WW165wo&htaLTn zK1XA^nRV1yUx*F;q`@cU`|~-P?(nU$;@!Q4ZNMRGJ(HnE4P<_}+54JRCqUM8Z{&Tu z3PJ8(4cGnuaWzIhhF?bYHuH`zK*vZMcY|A?oCxA_Uw+%O zX%_5Nz4e^;Ofx*}U2@9uY&Y00xzpiPQyqAb-qRT*FO6*AKN}LYLS5@|68adFpLJ_z z#R@7nZiGZVbBgfe_zEfy?$AyQ%D7=o+l3WWUYr8Jpk!Px9K%tlWO+9*$QwEwJAU)+ zFtANIiB09CDVz!IJQQ$v+i(Mr!v|+tGFyO3g*>~QyZs=eBd6d%^fts?c(&jV372$~ zCFAlj;EL%c)DIE{p|!Z+TiL`8+LA+$JG-yv>g$72-`|#RJpKi0-nyN;xuFScRlNld z|B^+@q@CikR^ak8;2It9+3qel0=F7X=@7Z*pl>2%^Ou?fhu9)-RoAydYy5_H68+tf zIXf&bfs_+h0qnBMYq;B77&;~|)-8^jWO4kJ=uDjE( zccBZDa21eHQ8JhWCoZnWguf>OMYg?dGSLI@#Hg_obAJ-xwbdM5{jG*{?2VSJ_%Kr<2YZuj7I;Uk>(FXjaThbof`OB!dj8-wb-cBPs(XZesN z!ypCfwNRZ}+ZgILWpo2w)G5AL?^a?qq}O^yL~@vdwsK4TUtiXFbb@N5JWC|Wo=&(r}{ zxl63r+96mRu45qZJPy=IT^q~6AxDMd|DPO%@rwcln zX)SQ=M37l#j~*mdfvyKd2HYP*m!35Ln1nmu$$E%+Pr^I7^ZXw=7r`nqYp(*~pRmcM zd+4P=JB+JD9h4I82K8%7K2#${$TeHt+zqTsOSok3QKI4Eo|*wv*ln9`^fPi~B>$`Y zzy2lba0~5UYWq7}Gs8G|4q|%V*^KNS1`}VL{Zm{zK#leZeCD<^_y=6K&`k2fE>yiF>k6iRigz45;B6Y`AA{Oa)>gnuTa6>QchAO|l?a*5TbEJY@= zys8X(7|)r+rq3+Ewgm!Wx}>2N^KhvXRRx< zJuLc5dLi@KhSRacFynB3mJ~i341BtpMLKHUF2b-!GZOKaTVaqa`tkNZqtL{Un4R6+ z4HE=JOFQ?rf~&4t8F8`Y;G$ssn;158#I%3+hw5nZgB96JHL189I36p&ydP&}Md7!d zfuC7wLCxELMDSMu6aD>L5tL`WG&KKr3>xgIktUM#r{~9WVaJs;FzB%B64R-FpaFYk zUQA^@!fozyYt~Yi?nhcwT-t?o!w~k)?mKxA-3KH* z4Kpmq^5KPyv2&hH&A{k=fCIJ{Aa!a+*pT#bv`KTLaXBUdG0#8V)*F9fsKy z6kk{PF0lwo{sg*i4Cw&t2$#>nJ5xZ5;H58YR}Eqx^IjuJl)^wAKY@+>13BDlg&j2;OYan)5Co>a~ZfOb|>pH|~};Cg&s?Bs`TFmaB({OZ&o zsIQIfZ_Y{wX8Q3VLrlMcUiZ~ZE*3tdn}6YvAlLGqzJoztkFSPV8WDx?nAf8Oxke(G zXD8yjxTb+{yqTEYu`alCy}!)NfhG`;#ql}nTr=P(N^kO9ZGfz?P>x8%h|=|-N7FvF zCG=062ayY4_2c^NGR7A8L19_IfY`R#}Xk*`eWv7X39oaAUI{-G3QzuVf0ajoulDDO=~Z zd1rP)W(q(2;Wx2m^6Xi#@VwL|-|h|=cw*w5IA0gMwdI@aRU-sZlao{;1}f5b3&v;f zL#3B4Z}A)j@mKl2HOKYR<(0ayBU18F4@`CG+LNbJ0XOf8jrn&alapEca1ykD`*7IECL^xv;PL- zIEk$mmVKaQ)5{!(c00tus8nrH7NP6ch(W&-=*K~Ktroy(g}(7!?|R_R8Es+z_V3`i z3s2*&cjZ9yXGy~5fi{TsiAeQ&*#$qE*mlhf=po`S{3E`CWn5zh+^(7y`)(E@P|>e0 zR1I&2!xEeI_Kx=fLIP_V?~_Jw$1ZH+L{mDjy~u(w*Xjok1OENv<1t2J)a>UC_b=n_ zV!#a;2&>thF#*D4s_}C-rl4rV>GYG%Do|!Rz^!it5*&f{Lmd zP~x@&;n`LyBDA=SOM957={Kf*gWP%NLBMtp|GY|m9&Wb`uYLY{66pIJ!nqH*qBaLwK{ebuX`0KA{4`EHf-n4%6)ck^T2J#IsU*>r<~o7l3vyJ>*b z_V3B0>k*P8FoUzV>1gN(AX0wtTSt{c*!oU*$U0$Pm1`Y%HLaAcxV8-<0iAN(4^@$V zu?1g|)?IY}HDSOV-?3?~@NG93y)W(+9JBzEP5)K~dlO*-ZvR?K83OpLq8QoytPiLa z?io=Oegl8r4>S|}q=4LyZorf+nk@Ml`Ltky%RPuewU;fbX^V@~sigD+>28kGX>q+G z$d2aKF4FsWhY5W{GQ4E*4FM46V-)f+EnuorZcJ5$^gViRFWt?_ z*rqszLm1{OIJZNTyOA00!66JP-?Oq|W2_jR*B;yqX;cgjv89|J%o%3-{h`K`XE70! zclhaAX_bPwtIWs|vwE=CDPJm;jQ|9&hn*~Kdf~bI=Bg*QRDf2Vu}}Ph>d5uA==s)R z@;#AkaSH}qmec(j$}h^`$PZ@^i(DdDHEQB-67Un0yo+F3^ZhT}I4xy3v33x|MzQex ztjz{;>OraIClruszF@vD0n0mrB?Iomp?rSpiEg+$ZYaq0>H)%Wk&EZvW^!|oZvkX^tr&3MrE0f5oo@pf!2?9B z^CA%Myy~bCnGAYFT>V`fn*nc7(9Hg}K5#Jn7GM5JKiJ^Gzkx+b9eIDlW&iz?67cZKEJUN9yo9KQNr>A$bBoP zk|8w+qGmKERE(>j=2LF&wTtRV(87~^&11{*?qk4>xw*%ye%}Z+VsoI?yh06xSckyX zQNx2_ik)z}IASo&w;7gRjGJ7}T2L-cj?vrXl$&^)xzrAlfDH2;b*RU;*^)Wb&`91d|J75;A0Vp&>r z{n8R)w20-R`#|87&Kz8?WvX?!Vi<_Ymg)OX55b)(7j9+o55P{N1H$=#Y5-HW(|_f%g_0o6)-{qfV)v>go3<+Io%7FGg0JWEcZ z`I`W*PXXK1@jmFtZ@clfdmXxa+T9{5Uz#3S4lv-}yeywryL}3jR$bWDx2A)R%gb$= zb9TBMaLNP;PD}MdnHKr&TTT$5%KBn%3H85dy`o_)ozSgIxa3?6BW`r3R?uDdTws+d z`ATa8{lyFa;(GWN*^VyQl|36HBvTI?olDJkIuXFNzucAX6Fg{-x+AW~0-yiSTnr=%vJY`-S__}66f@gXcJP=g&rk$%5#0UDwuq1VYsImS-J^c0Hntz2c7xDxw zOh=?chYnoXwT=6hl1Yrfy1IEcp>;aV@$eGOw;t~dWWrnQ?(&sbTMYxG%`48Nbpb_t z?$UFle8LG|)3p_s?}P2WY1h7NTm^9D*ZBSuI7eUGNoQn~4m>iLWOF&56`v%Lfp0Y)a{ZNh8h26kMC35awvIoT4Vew$(5sDs>IrMGjJ3goWCg z%klU<*f9p84nN4U#LYncH%_;Hc9Wi&4t<+=YFZ7e&da@fUEB{r__x|Kf6Adfo04+g zY8IsRJjU+H5IH21UE~lIkJc<6S&S}`tb;F3{ut0sXTekG9C)wey|%q(E#MFHtfz8n zB*=K{VcXW(3rw_g#_^?z=qR)%_g(#C|II3BbM0~=Xi|5>=@4P7zP#e zyp2!(=f5=57r+9U+K}b9vKG)E-i-CzxdA!y*u_k^9!;0g#Ov#twHlOai?g zYb_15Rw4QV#7Xsk%kt7fE)93n<@B{PO7ozg;;wy(?I3&$j-CBpK?IxKeURH@abWA8 zVPD_jA;@8$zJ1Iv3Y2XL;}+bjg$%iLiX#!rTM#XWO~Z{_Fw*wXMtCfRI1D0dJtiqwf2la4|tf)CLwX40p{d~-N~)cMvO)7Y-^wt0+H=Td&;2U zR&SRS&ZubvVbuk5+-a?}^@`R?@_sH+RSNNED~x&1bb@Ck5A)VM>;Ti*mbbENHzFdp z7M)wZsx4hg{!i|mhPyk|^Yb;QS+MieXJ*A}k{&5P^zkqk{OnW*m=ZC(9r>{Xiq?7t zp6@RQueD7b-!pGVt|ms>eXCx|y_3bI<=$zq;+G~^Hho?InlTPvKmF-~o1BdcJ3AM^ zm!|G5_@RDq;f!yfA?pihCTjGa&$u48UXJ|p?hYDJay}lJn6#{3#%y|tilRT~w*??* z^e1cZMHhVk$@t0C$Re1$SuZ4VybXMFvhfONZUXFIO%~&Jwu4!+ z%vvIb9 zcUtpnW{L8^5q@=52vg+&bHUqloW$YpM1h10V3v&_@?af{w|Aj;EqGDExAW$f0m#)CuXr-h91-yh zGkLoLmoYEz-|%SHrQta^94OdYA=?C~2FAyIxo|A!Nfp$30uOQq*Tdbu2kb2Jv|b=LSDuk_;A?dUzg1Ta2&Pv%Dm4!xcf>= zV{%U|5Pvj&i+7rHZZ=B`r25&1$_PCiyi1|1yfNHzr=);5%w#8 zbh}sJuBWXKYKt*Q{FmZSghn1lrN(=zp-KGwaYT3!WNplgip*^zEsW2~O*sU}))BT4 z@~ahaq!_W`6?P#>;kZ*e!hwwyU5Zg-wn=XTN{_Ri zPkVNP%-e5_L&~b*c<iQxmc@U}xAE|!kpn*PM&lRCHO-3Qxnww#c$JV!IjbP`&4R5hL zqrm~da_!;1OPwRYFRkvyl>a6T1JOjP^FpDcJjP=s0S7D=eH~Yb@JZ01lMk1OqdA;FGVhwFR%A zp+g&Il!(3mw_<1I8s%XSw__q&W0QLil+`J`mT-6h7|G`J-C=D6N2;!FKekqa^t>*P zJu4#63Zk13y-B1Eu9=B8;{Pgz{6?u^-mS1TF9Yz$dp9qfv+9P|TZ6_To-TlGw{Q{- z>Ft1!mU=3{u^1MOGzvHFYyp=X-hTNj*$a+u{@QpOXNF7_zixlM0++GeA&@yqsZf9j zvA+qIiVd2;SFh^{P;wHuXobPgDQ$2;&(6_~tpkR)e)(-ps0YKwg4eBX?nE4g)|_vn z;F8ZYd}JSDk{u5i;~jy~e?#`?KVd-=4uqMlA1MVk_qsonWcNeCh2e{>H8sF4c5+Ws zpe$nFqTsGWvJx%r&R8_DS(&FxYq1fuuThyE5Fvu+Dj)Om>e~V4Y}89u*a3E4d)uRp z?E>4<`)d8^O(}4&3GGGc^a9@)i!`GKIV20a z<;8~;RuW*a{{08DSHrSj;t*zRI~gi|&bsA%qE=gupK_IfRd46U$Ww+ zz^-@Z4|Ii^K>`2h4v&F;xHY_>qDH(P)(!=vZGQg|t{%bO*U(T!E?)L{1Xsu_%z*o& zQ~I08?rxZ3du@l=^99gb!duZ>KuRhZotNnwt$-8XQ+8}R)(wV_%kXw4w1RV=@~>L2 zl|hP$GiO&ue#VlN=Bzv(j?6)j)OYLlao%|_`#P{@-M2cJ!h7y}=Jr0YE?|}1Bk?pi zq5OkG<~iwdy8E1{sHy~FAvqzqZH1pP;vOuhI^;b|1Sjy%HHC^(;4?EW;k->FP^FCc zt^8sKP<4E&vhGzqaE-?-4q1|(1uM2^9h^=^8*EE%Ebm&@FC%X7YyUGXb2U&hx7NMJ zf=Jt0(QziacfVdu2HK}A^3_jOf*e&d{ObpUV8a19Pp?uXgv;>zm06O0X+=tmgqS*Ln&0A_x^T6^=3 z5wcKn&o_33-RR{VRQr9jo{MKonStYXZn1gtjsd~-hLJ?kQm9*@apyT z0Bxgx_Hn#!g%+nGB(g6WBYD<;4&+mC$x&uAIm)=@i=Jk(8~{9`ESqea>*1AzJHz}T z<>1CugMr#9Klmwe|1w-sxA_4%1_6ZP{_x?%?!TTs*jzE zkSGPi&57@b2bl4czd7*N-S~r|0tew^f|wf|F@_2FI2~e_i=Bt-Q}O0(-u=+lVT*>v zXdO`N3cmJ;IT;pW@+_DML-2%(Qql9m1W@;;c_ZtqZOG#tuSbzz%P*qh47hO~_$wTL zh;R+z;{ouq7zkDc49{oPf>HT*?2Z#%(3U;>16IBRSYPo`=?RMk8(ZIBm6Df3uCQ8! zj-6Z1Wl1pLCOA!e4iT>h#)&U{dX0(TU8kI zuNUv_C(S>t^J!?;d`U8g@E;X2@4WjYWmI^|=hyN$;^H^-U)vpC8Xf?3I zb6*$ezV=sm4_6!<|NV45>){sIa5S{oeqAN_=;W&)Y25=)wk-_y?%#=ESgwC|&|9ub zVXSHDNT1!Wmqvt>)*lxyOI1LP;rEqAg*nh!pK1M=bS>mAiz+-h*#ip-O6Ko9Y=Bd; z<~^oPMu^`C`s3#L6%$r!McxURSrk7Bf6~YPOBug)9ofr#iecP94hB-oJnW~&MG5%)!s8S zyP@5AIR-;6k#a4M72XYHRD*K7Yt5Mlt-$MNnXp5g31X&H878SszUGoQbr}YEZx2na z+jD;&h8$RCu-xkgJo}YjE_@+CH?)`AM)}Q%Y5M)Z#*pRb z6vnD5?nKijPdq8pkjXq*AwEX48y^2E>e#+5wV;&ikk#c8(q`%DwC7`Y8%zc_cjiW0 zASSBM=hqlW($@uJRaO2qi_KJ7 z>W4Opg%ztq4Uut`j-br=eMZR4`%M3ys0 z^g1sp?x$%n-PyfF(EE6|PLDtxXyy8H<0PpnuGaIIT<70G5F+C1M$9b-_xlFT``&&8 zm7n-K9&~R-etl8#x-q%z4~!LJ8fyDNbPy4oE%(hq|EL1S6;rV-IioOTuiCNijyB@eBX+`Gi&=MqABygCBeF zM$dq~ScBn_Rira6<%b^n(l|@JCQ$^QD5GB4Y$=4w4>M0DkZLPhFKFBseq@LUKtoPM zUzdJpVyqCm_jD>yX|Nrr?6(!&^S6lxj92?wIwoY^0%g_Bq*H4f;g^_rzf$K;7<8NH zUQ(rld`mR{uKs=--G3P?#A5P34`1gZ0#Tt?Q5>^Pz=kC|TjfI@iJD z?atvs@aluhcH?J_Al-CbXwsqy5^I(B^~rm3)|DI|)M*-^wj9z*`+5Y1ffhHMc@;Yc ztWAV3)?ey@UVeA%HKa1YD!ts(u{-O)sNR#v8>j)8=C!%OofC^BNVuyB#FOI=`8kkQ zX+~QY;m_y5YkY4*=oU9iUT_EGk|<{ql8`<7 zzVBp9=r?A}Q$Jy*|3rbEfGZ##Wv4#IFF&S`V5Pffqq|>CchA0jPp$nvCjlox zY~!TzkK!UQMt&t)a;>30_2DAm3!HZRQTW+Qe8MV7Ei5A!MtW}jj1QC~9$^!t@Zcia z3Yz0mh#}ldLd0?t@XbtN7EgBT5VvzJeaXs0z&#+^@+`gMPrj`qe&nH=#zSo56DE%G zsNtH4QoKvAAiMMMN2F=rv-c5ZcQO_ZAXmusfn3oPGL>9+C_SXliTCoEHl z9(+QScZBHQN#!TtTXj!_?)|Awyv0j3NtphnU;zTY7sfO^%G4)%@=;$}L;sSEAYlc$ zwZt(YbNp_unXl!h->;66UtRG%av`~GDaf+)$dppK|5+;kKTFsD&r${Y zQi={yVg&rl0jM*f`UXZqC6Mp}*B0pVnpf+JXpfS+(34|md6Btjl) z3mOt_rG=x&@7*J+OE0}=BTb-wMzLno|ALZ~A>bWypEv&IMVABp3b}A%-|UWe(hAGQ zjgkw)yZQth><}zeP;#Z_)GF*3yFn9%|Nqn6?VNx5CJPDakGym^;3A|xWhIFpI_|nL z={l60uPJ7l$%W#NUh#Z-J_2RrRF1xhGen~-?>UNgXkgB|cu-_AzycLPSFgmhS4 z0Wu#CS08vj2rTdSrH9C5f~)OpTWdQT!0_DGia#F-sIdpPLrRAThU!F2+hC3}AVzLj zGB0%lVdbsJMgqPv|4ew&H??KI3MLm$u?2s5KP$W#q7*u=%RrQSx8-}PO_{0$Ts9G> zwp5R#8Ue4GmEp}EC5;V29?RcQFg%yIUBQKr`gWcZsMb&Al`Uc*e0i{10_Cmjk|#TzKL|ps~EG?Q)@-76I>S z5{&fCtY3^CO0=x{|G~n52>kW*n!zEGv1t`C?tf^}f(#lRyv)3}5wtna6(&9FhParm zx9`NXfv|Fm$0H29sBO9_LFuv@7M09ITMH2#@qcFL(iuq(NnB;#5~~o?dfm(BiUYvI zM@HsI=uS9CdRp!>&g2)iezQYB4Jk{!RFlIcF)DO@#(Tq#sCRRZM`CR?w%N9V}LEitc>ANBfu} zMNH1cu7{PzmsqC_@D?vN{#-e_uu+_T#{2wmXy_jh5_MdBkL+Jqub|pWxYq+Bp6kEg zrP2j1IS3pq3Hbt2q6%$2q}HP)QDK(i)NY&NSF4HY0U|n1CwuSQp9Al{G?7e9=iu$@ zdCyO9HUrabzc<%N4}wd#N-gCtyo1fwPwx62lR*AQWNQX+)Q<Tw_40<4JC|7+{J%P>$D~?mSY$5&z;L@1LNvEER4NvfRmjtGV;CyZYo+I zm%Pgm-5>7L?d7@eztmzMZzJ)J1zZXE()A8;gP>KtTyh(0qHbodL=1KP+ISRR*%F-dH%^ z>;qpA-^=^=et~8>#SK-t?Qo5wXYsv!dGwm^;KP{}O)f~+d`& zX-$sQI=)$`xn2g#V?*1Ra+^R+if@>`$N(s4DN7%aT7&AXI_SniwvjfE7NW4*^1RNO zq}K=evMar^4>KalH!dH}`rQq}TiKsW`?SIPmtEgVw={tBFFpy)bZ$fk<;tyUSFjVN zW4G2ws#aq|1Fb!fk;^1QOM@nGU;mOyL2o;Jyb$ogB(nkBKV?xB_N@zEsT~&E#j1sN zpZMqai;PPfcSI=c{GIkOAM73nVR=8InHHKMH`8^eWH12cnzSFx3f2MOwo&VPu(9Elv)EO{!pZFzboN=6aU>CIB&KgmKmRm~CCC(T59V%dJax}_Va@7~kJ>h}xw zN`Rmo1LRmJ=dasIwO)r<`fW$>#d)R5cBsLx-Z|tC)_S< zA+;^QMqb+4yPzNVS)7qPw50_8d$^;C>B1JY_H0AXqLYzLq~MAz;W92=3wCa}EL6lu zI>k8ov-`>z*08Z0Y@#0A1MsOj&&2ArX<$>^iw^VgR`A)*{z>SNIlB1|&Oezf9j#&4 zQld67eeOd>8xGkh)Azf_yA$*TTE%+$55X&|SoTG+{sJ!R{dWYke}xsM*R>Nx%u$tjEWQOHqCVJE)CigqbteHS_&>sc_bo$l zrhUN7R{5BheJ`>Qd*#I|8-THspiJ-ljn!~`#5iLyN?q${l`LHuJz~@o{>)=_Fff)? ze;0~_Ej&VAmo2lvm7P~bvsrrp@H`U4JktfM&)>gk6|RBG>qTqZMQdSBBuPiq+a8mecGWrijetw<9WY-QmZJfSb%PE6RN3tzWH1$IF6AZ3j2<^z%X87Ui(1T(O!UXdY4+i`w6$K`60*U+YXf`a{jT~ z7a-w-|6D60?H4CA6szfRxo-HJ;!7HXF9nDH?KdjHtnVsxv)aj#4S$N%{4;pk2{sN{ z%KGW|0t363R~K}qk-&Sa8Ip{>FomTlR&Nk6zFspr3svqW?DJM*K*kV%|K{FiI2g}Z z-ICA=jhu39I7Zq*s7yvc=$sa+b|2?+r`Zs*T4u>=+|rd;J>2NQMglZzx54>ygP+G? zRo-ro+dIF3%On04d-o1QmE!Yt!Nz5fL$c(Ad$uw9Ti@ZqSZ0V#V)}gMiFbX>R0D4u z3ZFH?mqVF&9*@SdL0DMW-zacL8JVBi_pedl;i5NCCP)fPxI9EQ6M)|`u}fOjLus)! zC{Gn>wG!*dPcNBPyNxy}Vt%ef=`0z9FQ`I)W+cqI-(E{-6zCeVRh}vx0m{`1*qT9^;y1?*RhwELtb@68PukS~{sk&8dz#Em z``|jCZJS2(+hJ+U>l?3Fzd@}uPwmQHJM`us!{2TP*JFO8vc!}&P&=&E(o6m{2NG{z zdF|&?e+zit3mBTQPj%4OGb-LeKCG0I%v@8a3!K*JiKrWTs-M^YtkNvP#?1* z99HA3X}8*dB^x6MPyPjRr4^3m&GmqD_n4eUZQuq6kJU@zOHcnPLS{i zu$H_@n>hJ}rUyxGSH=e6x?kY6wVGb0bq#!c^jZIdO9L>Zi>F#V)C2JoahZ0`i(2$+ z$|OjOxch{Nj@gC37Q05lnlXmc5e-uyH@J{(Q+6+OoYN0wdtC}*n~j{yOWJ{YRK(}8 zP4eiw%(&|t)@xu+rA=qFB(Xo7>1_ul<_4rS!GBE;2rlY4$(!2=w5)uKFAbJMC9f1e z?ibzg_Nu7ji4aA!h>@WCP|pApQ-==s1OEffRNMs2+gpgJbOyftV7hS+3B>dj))lVQ-!m48u??lni>*-IXGITL5 z^yxTX&rh*>^qrC9(-T#(g&%VOl6L&2N+0tUfT)H8f^QAH2gOouMfSmif28*P;uAzO zKW8PL9W}$)QMR&l4+njj4hortW>Poo{#(U_JdgC2s{GpnFBF%3O3LhjhF_eATgOVl z#-e}ip5H{!ju!5{=!n@OJ4$mKP`aY@El-o(Pxb=rA}Xz@lCL zyd3h>RR-^D%Ynyg?KoWyYoI{vM}F*_7}gaTQj{7V>(FS8n~;T7iqFx^*j z%i}-~L=8KXN)v~{rvMe5M8|SiTyc72M|}otjbcAkJjsO4mPx&mw2;A8+MDR~-e~5{ z!}EO_)F?`0c7RA@mRhLiuew+_?TL@Kk5Km{ojsV za~45mXYY;L)vI7ifyJ$NJ!D^4(b<@kDPZ!LgEykLA&oCkB9Mf0Fk$k?>VX7K>Fc2o6TIuu-+A=6VG3VsmdUIffiVZ9I7Is zqRUJ=%T%Djc0Lyfe+bFC*WCju!&-Xh^T^9M=KkoP36kjV+}TfJNqX3bXF?HgokjO) z)k+p*DA~U3s1p++61~Sk=0iJJ_p|lwS%uG#xj|wFLwX-je1iz>ex8Tih}E>XacV0j z-ewAlv1VA{j&IX2I{07BA%=2r^`dCb;ml5eml_f~-qZ$lTmI`)j&6l-%tfxHoSQ~I zot%u+Eg@hirqm|zE{^${BHa&@oC2J;3e15Z+=mfvzjpAaX+gX^pbM@8X)S&OO`vCY zP%Sq^&}h3@+1_K4SQD7hNyT%CtK@p`xiK^ zp1YARZx}qyc9|bLf}$@1C(iv7lEkKiTPQ|8X1!GYS^Wq2vuiVZYib49)}*KO*?$CV zX-pY)7|(}U26Zw~ci)1~K(_2|kT-I?&9AA1HZ#GtqpubKk;EfF8D$7`MtW-} zBljkTF#kIE^waGelYVf>(O%$)_%mQ5apBSKbA#ag)uhwU@l9Z?w+ghZ*@#-nh`p|QP=}_YX7%o*XiGgOXr)DXFls=z3o;?tN3R<%sz7AII#Jzb&pX7jx-}= z@Fj3)065#c9@BMyPN`Mmqe&7W4yQ(2(dg7Vnp#hMQH zkk~0%sSo|oH*GRGwg`__w<2372Ox=R;hz;4-+R)M%s0tPax;O3BTTRZ%CFHvcE|Msd?LR7!0$Y0 zB6B?>z{?c%es&-SuPcjn2iu7j4(2!`Vx$9gQDH+@PqMTLLH)-hXzf4X@aWogTB{)e zg^60W=IsFY)h&00JF`1xzj%fqk`xtW#o*K&0I24EH!2dQJN&>!p4KizPeC z=$3hn8_)4*gU|V&PIoe{;XA>|I)N1`A^xznjOypbsA!S_q$}mr&rCnpU(Hfx{|Zb-J}li zrsSaDF48#C9_O>aG;cFDCvu>})yY!cuX%O~is*gy4G0>h;UiAkzORD6^}hmb?VEv)Sw>2NP{2Ru98-$=tF@jTCaog6d`@Ruuc zpj@QSb3+%Rx|=zEt#6T$WhU=pe?X~i2b~qY9AW!Pc!ywYtMymM<>d|OLDHRd4_|e{ zo55G62c4SXNvQ|;n)MxUo0ajm>?4{e=*0=_jgiLs8CN21r#W6hZ0Mpt2W62)nWecA z59~B2CsNXOE@eu5b}r^su-meo1bmV9yKi&ql=Kkg8;?b=CEB>F;fTZ#_oeS*-O0IH zk>1VO_A8P!tR4hhF45Lw=^cL$0={zq4}8wA!_4)fGgm`=>QsPC1<3NzR3v|gP#?3B zdawT(+qU}?>}TQ=?0eM>+^RqQytTa>jx>fFgp}){2Hmmw0eJC6Tx!+`@g(4#-}|~g zI;V-n+a5|^dv6Xh_Ychi`Ac>25mM9e_WKsucceq0lZ}YA24tvT7NNFLOAJCZ3p7@Yh&e{*+7Yqz8sWXz6C?lVKv~jKa(+xWlk51L4 z#+kZ4%R=t8EEtBQVulK#zYCkn(0f+Kb zKfS_gIA~usQYbVGkOktal53}7foj`p_JB3$!v0LRTPt=@nJB%?aml{w>T^?2cqXNR zrMeZ`$^U)jDA)^cZ)NiQ$KC}EyNWlsZHNV9nSQC?=BJUJ2E-H&^5!RP8<4h)r?K-} zw>j(Or%^z&R)dp2(cbYVXrFY(TdXfNxoab+?n~>R zM^~_8rp(QY(zHJ*Zf%24Pt5OBY^#L24^H!OruG7dZkI5Ea1D$+X7|Z!O&d_v#sB$I zBZ)rV6rnJvJb{gWEHqk6*KWVL!L^_2vDhrt`Y%!{xF5dj*mNaEtO!Q_ym;&N)m}I$ zvgh~bzz(Q?CRil*3Xhg{M8+Gp;Mz&;M9+1RQtaG#-;Lj$`u`aQ;b%^;N>WwOJzgh%rn z>9?9W$isYEn~-WdXDmG>`vLn+VzKwWdQf^cUtw@?0N!O+GzQd&4A!eFN}B)7C9g31y%U zPCmHZb+I zqrH#3XfIk982_0|5*4{5CJ{}Rlje#XbnKpfH5PAw{SOAed{x1{MLJ+;{S@_O)2ISJN$$SH`PH0^}(re#V57a*@w(Xhd1nW(|GT!i!M13+|s}`&< znwyRs!xaNuuj4M5_xTQ*A2Lq!C8Vv!-t)}E@1Pmx$*wmk1Ls;jU~fuAtP$3x!6 z=;k7?FmbBU)Q-}hM@0Ajdtt>YQ4%1^PUg0vQ=m-jM+xEM093xX*C*7p39PDWF)&a4 z4!f=8+btjB(bJrXU*c4jTZERBqjA)|8sK_g6`OvNUi=)JeGxDUJEHDZ-M`!d-A}X0 z)chELhNqv1h$4v~@5A_m1Sa5_BMjF~l>F z&TYDqc27PPcH|j)ePV8fyfrhYx5bi&c7rpbvq@s;U+#2T|x3#^i5n8I-D z()@5UU%vx|M@0O6oeH>NR$|js>@alpc;4#~m&?g zPSJrr^~d<0{pa3}PmsA_%{b#FW`tTTD@pSDYAp}Lc;LylCCk#NA0(dP-Z5(04mpKX zE*&_4qH`vO*?Y)*X_F25q@Ac=!@y<%Y%7EGGEQCU$ZtlZzIdPGOj!pg3%Ol)va1W0 z`qr$w@uC*o|HZlX=f4KTJ-OU&?M8~nQ}~L|>Ckxns@K|uazNwx&M;HaB=9eIAaA?D zF0BF#rKPJKNo|KX|Ba!RM&xP!r3N*zaye9UEKyN?g}$P6xGKfBTLe|xFnvWVaam%` zc#=>~=mg)!B-nCm3YO~B!b3B0{x2hj;9hsZv@`s+=qtt6ivyJrW0o9}o;R-prcU zj5Puqm!_cKOx-Z=usDlucq|f@Od5G(v7F(Qpb4;aJ+@#}`S|)b4AL84$l@c_SY^ULqVi`F~pKou6F}zBaCN-UK*Qs_f_udw`aI!plj~yo}b5Nx; z^l#Qg$Z|4|x|Tru5PnPcl}^HnbqOC$VX6Oy%kTkhI!yuWvFQT#r%- z4U3UOQN@JUfhH8blyOy>XzMSc<9YMM7xO=$FvOMDRA&V22sBy!$g>SrJTe&|SbhY1 zaSpPdQ~E)r?86N_BkJh%otThowh`DasSIUs_K;8dG`w#HklKHpmUbea%TS5`q; zrgjxS`a4w#t-U4#)BiSsD-W-2n~(mD{7HAnA9kUjP(ojp&cs(L4?=@er(hXt_K*9z zEC}^6E9tTHHHD1{zu+gL#_&RSANVDhB>P*l3GOp1A2_sC5xpuHc3O1vvUK$6!GgKG zLlT#;!JbrhlJQt}*CfoyUlaMks|MaJFOn~PI|S?HBa+UzkwCq2h0qpFEmX$Bk>{Rk z1h#6VPY-5AGiFNUWBWOXAx~>T44%B-?U`&+Gui=S11DPC>pDQuJbq^Izh;mEJUIl- zO;9;6a83hRb|L!oVE^V*mGWH7q@cQk1BaZJbuf5+J9O=lZs;zvX_UeB2govhRp)8n(qzs|_qcP`BnNmLz81+S!a&m(dGqy(aDV@2c<=l* z=77l`K&6E{HGyFSxVU6KtIsY+yoVnK$LmBbb}1AWQY5BHis-zl@wF-$gJq2`{>AUD z1TQ2$R5=w6fW}P|2Ry&mLgX^Hu1kF-_`HER%=WAW`tR5S?UU9K*fd;;#%k#ra8z8| z82v%>Z<1)I;9RqD9lXoAr*h7|2Bv>3<~3(0zD@$y>Dw7Dw2RABy}1^G@g0=Czxl zEmQS;J65n$reo*m5~nYv+61V@tR#|obnMC8ahS)Ln7?qe4kUQD9{clW5WaqJO<3kn zF7)lLX5yW&MGf|ji7zmWVC+;V%gVdCWlwb04ZJruUB4nnD3SuO$X0a z0kMts3qo9d;8F0u#8#d*=yYVr(CV--I#^Wh<-#$_D-mD*MjSmBp+HG#k*if{tpH% zuSv_p(%^R<)G~Nh!$O*R@dT++_=oWo@HVjwF(x1E@~iu|PWu~-2lxX`I$f~)TE>NY ze-+S4`yssxEBMm#ur%D;Hha0xU7v!F=Sp7tE6riJW$Zm}Y!dD8_g@BH&l44J_+WC# z*!x~6omH6Zi{FH%#-?o8LFP-=ff6_xiyPv)onx8j4&NICK5NJP&YRzdk$;GaCC1$_ zj$26T9KIX;$N9eIKu{6jdC9GQR7D9b*YeqYoy?AwF4Ls=klt2%g_rt?!04lr@Fj=O zpw^l+C5;;b(?LSFBxkEYPsGE(Fs^d2rbw^sbCxXnNBecL#|j_Pq8Ybz%@tB;u=JgT z4}FL62Su^PI*DI1NvI>g5^^#0p`X&bK;&+26?>;H$PxN-P4F!c42gG}@_QRk~5D@z1z}7$O(c_=$ zG>plt3Fk^YD+czZ&KofHB5(t zLU_yV0>kjst=d-q7iQ@7w?Q}MSMW8a=li;Hv%GT&IKBVPKC!Xobzf{w*j5pr0{Fdf z{rxoMJ{Z62&`9@qJ4`+se(+$C1^Rw6WyhTrxPT7#U6qiI8qx(tw!bkp5yj5jk@oIx z6wMW_fKk`>j^g9K!rm?`fm2q)FzV+Br`4z+YCU;UQGZ1rh|u9aFCjWcR^(##=anY4 z8kLaO=?Qu2Cb)J`!N-F$F>HY;uQQ|1xpsl;g0YGbl1~t!=%gbideP19H_q1Rt`SSi>N-zmh1Ez z#o~<~SI5>pNV}wRNgh_xY;ipwr}`Aktou29W4ajLBF6Q-QyPLu@Qc@X=c?gav!i)m zdi2oTcJ^CxRDYoK+0?AD|JM4KhYluz>pn@3=ZmvJx~5!JFuAvVIBXT_!dVS&40`FA zPm}>6-qkNZ-*G{Iv|7k(kWpwy?>Ez_nVKcuVJXuOH++pwo2oCzR4uJDJ*8p&9UeF? z*xf(<3m`vce`(JTLi1u~VTSt6Xz{Iz_v$NJ-;@s5E=Q3=d0jsZ53$VGJ$!N1G_a3t zko9o?2y8NIS$k8+2Yk~KEkB)Tfj7EJtD=f!(3?jDht$l~u-?{;4%dGz@BW>sc@THt zK%+fa{vSiY)}s4W^;KY#1Z=N<&<`t`?!>{OMj+_IwR&5zExJ?5l)oHNMu#nQ ztslU!nz6|lM_Pyssk#!sd?L%|ZZx~AVFj$edeUalu@44`l0K(dwt;}x^=kXO$S3fx zOS3#l(!i#q=9Dqloh$XR)_(&)rRL|eJhdXoYyOq9ioF^B zSbH@zj9`pT*bD3o-@be>fN$gJrk*38k4^%< zuvJf=tnUM?6$uMM)*~QUah^#``5QQNGI5ioLKb{r_5A7dO?h1c4QW zOjfMMfF5@NkgQ-ZG)^9bW{O)haalv~#d9U2MBi%YaZ#C54CFy&&sA=(a|r0R_avgK zllmeVO5@Wq#I(?#n19~uxoVykqDbqUUG*k61|+G0k7dmj^7)ngbDpOOR8xa%sdu8*>fz>fTAjNL}HOAfksY0&FO1YVB9v;d7%KjLU!c zvi)x*Tzz(W&hJDi$U&|?s4QI}c?@}3?J-0mielE;b7U*2xO)U>5zmsS-1 z9Pd2^Zaho!-4u@M~6FkOE}s6OfxXP%c%NNJB`-0kb=J)?Uc^{0o>$rqWK+)q3F(#xABLDAh zBd?FBqVI(d^SI|}VQSLnaxd7o1Xnxvf`gyUm|7lA&~Opeu*{zVa}8kUtG#3O>TR$; zIYEQO-vb^QT1O3~SfkCy%{wpt+=zv~6D9O7p4o`Xo*w}3?y@pOYqbCaMHk5}eIsCW zf7+#?T@PT{gQoWQ!Zh&w&~K4n&9q27nBMnNvetvwEC z9T1++OdcK)a!0CmulHE7qiE&fI)yYi|XtcBKNoW?H@6NMHIWmBPA|$+Ub7S zU8$hXh;T5j7SOK3HcOGNmv^i5-#&*;2fU6v6uS-A)JksoI6VTom~vj`EApTkx5v}A z4ij(k3Q+=wKGBN=on9!|Rsi;!*DG%f?Z6y-`a{VJ6=EMWG&DP-ulNDThnhW2VCsN- z$}VZybIt(%RiA1Y5)&4igpw3;rK2lhaO$VE)GAWOoT=RWeMW@(n3Z%a#_eJ7L-P4j zmY2=blN}HVN@qOrz7@t+&tDz6p8+oJ-%)T>mjMuCSp_I+x>D41QQ?`YHmC&~xf3?& zbkGJRi&@Lk%R~H{pm2ztZ`f)F83a?0@T*+leP(xiT z{&rH>?Vc&sFP*|6WeJ;qAAZ;m96kwFnpXA#O|uOXho;*=dw>no&|z}!RiQT5t4tQn zU^IG^8HQr)+?UwVdNa!%2VdJ7GGgqaVvdDo7EXaX9X0L2EEOQOieL`c^}yl`i{oaR zouF$2;}<6jUX<_5m2dlnm(Q1aEaB2FHOUgQHe(BLir7{!oUpuW6aIGNJ66GT7#x+{ zzYX^jOvR}Fy*4onm^-+3^gotE$4#E^Fh90@7}}EtxOA0Z6Ex13U0&dP6}M<_&YlEk z4$lon`VB&kch{`UrJKNokGT^vPm{pfh^ zlYL*4oxJ0Yx%$ofegAy>qwAb|?&m(s=RD6j_dG)hWu}BOzZN30y58Bd>#Z0um_RTj zM{_J=n?1wCL?HX~i;{~uGzrdR0_QT;`987?CyDTtT*65k!c3Osw4$|Xw}&`M?2pAR zEH~&U@8=Yu^|^A92(08dF4`k(jE^XBlL*yhe{R}s;^J*g2o8oeCqtWyq0PP6roH_R z4~Za7ZswuMf0LKQ+?Y$Y;gz7>-NQ>FYUHe+RQlOPj^Pldbrw@QowwS4CZ3cbAK(2wD*;$RT_ShKS`O5tp!rS;u(mlQ;4%JjuaNA}EmU`4`3HIX+_^A-0 zAQ8gImjq}-1j$+(M9Fr1n*Y9uUywv=VuA=U3eqG?tUMjS*oS^LiLpDC_AI(DJVk*TqOgM0)d|LTV?~w;1vYMr5p4;Xiv7|Fd`HfA%Ud z_F{Ipxr{`70pg41Z9|p7aefA=F#Yd8Ij%Wg4bC}0>*b)_cWcNDtYV>S|iSg4}L+-6~7Yk%VeRVDi@vm&1T^8H>5wU_GT1uj!X zNPYU=pyJ<7LrD_xj;&H*+P|H#)K2XVzua1C=OUq(lZdq?T>H*ZJ8|zGq;~FP4ZUC7 zyJ%P~Dlw?!Uam>ZM9&m$_;)xWMIt_Qez!9E-_B>$PJUAU*cWOiE-b$^iFozM5SNPO zqF#C!@O5cTVqw^igj4@wG*O4|P+`41O2fchX-M|pCr0*`(ICc%Z!^@S0@0~#L^hJN z`d4jiyfGH!G-Wl3lJ37RI~ysJA^(Euf@Mj>luKS2swmcp4L2bV%Ucl+leOd)=C7R` z>EGrDITA55^Kt!%0J<1YWaLRirrlGU-^nR0>UxpdY24K5qd zbMeQmB=`B(@XS-td;yn-{Cx!=e!6O)uIHycuQ#Tm1zEs|I-?Xch$p%F%hn6HE?VTe z0*N?bDfMO)sLxXb8@`qtA}dPfSJWg*%cEa7;+=6IOR~M(f~_!htQgc$HIX;c_Q-;C zHTFb&Z(i#%1o8KzU^Q{YPWHAn9&;-x~Wy*ngGHGkZ!C9V{-^9;;TuB_L5H z5F!6k)FK#@1y<4msWn+^rPV)e8-}*+VtZkpD3OR?wRUH#N%PDz2D8)p1%WxqC5qr5 zunmh3V}lZBA0u0MoxPY)#Czp-_7ND~M*6j4(qZ$gblj`Ta7 z5}FSkH07JrNCbWIja9VQx{#aItY~f8?boYF#F3nU%SpMKi=-A)ClMyd-s-ex+!mj~ zOya)SreWA=kcjt@8RZ>MmoG+_4;m!udV`=z!|+^u2E*98*rs6&X_AOS-aELS_J}TG zxN4CI0pvKXh51O)A`uTrC&sdei{k{@&Ioi}o0|3SEt5;Xt+xm?p-m$2l2M%nATJ#f z@nphDQ$;uXMMLFLJC)3Xk?x6=^Ro#PlXc#620brJmMMh&f(0pkyrR!X;Tr?V;|@YC zKr1>hA+x;&0JF{O!Vk8)|2U)hX!lK6rcogyIjeO{n#xRv2_v#}ghqdDuqO zK%%``=-c^5Sa7{tOY0j8qBe8hulI95IQH5C>9NcKXAUzr|12#BDJARj3X=8F=C$$% zJyn$OnLu2Sj-6bi2Z*-%YCr!?X&el)9|u*XVbL@6iA~vP>K3cg#zN^0c@W~Vp%ZLx zxTCqop%Kalg{gZa{{j~U*F4A>(MI#xG-LXxv7El<;=nA+S~d6nlw%q&b$rKrVSEa@ z@7fxvWitrM`Exfk`=&u2-;cbR(s{u7?0flmftBcyi$+0Av^0TPmXkrc8zYHFcRij0 zzA#sBjSxFRyUjsKH4EBi5?2I%EH4|1z0wW7DJkYYNN$D`X9HgaUC~1K7($^PR9t!@ z=UTue{0}|{;&(L3YwrF8(C*Z6i_~JGeEr}*q?k4UqB2Gwdaf-2PIX+rk4L_RS?>Zm zQ;cSiXW4>JuKpu0_y6EAF$MKGs`Kp|r&|+octq3)N+clf+MAp1PuIZ9FZZ2U9or1V zYI0w7J9UDCJ)el*r7FP}*#~31#-sBD-!vcf8F87V|L*dPo`w(8HSPwFPSK+XvhM5G zEz@f|q1)5?*S^k=pnt%5%BO*L(4w!kPj~xSu;Jy?w%I@yK)x;@giQeM`4nn!e>7g> zv{Mr_tYSJ__#hqdaNPbLBiRG}C)nQ9uq8ktwJkAH{w*Lhbjz%ks4^N$x))9;JXSKw`V2t(o1rkTxS&==eosyd_Pt%2_g#X!ckbUqGAgR0vfAFLV z{LcNRM@sF75=&1ng)+?OMzXl}TAK4PnRzgozekQy+%~p@Pi&dDJA0WZ3j0DLj%o}* zy@LWj8nk1fzg4uSkYWXtIq0;aUjRWBDUvz!C{&p-YVk5icY;IIpwy@tcUAw5DITK} z1jJF}{5?V6U(h2kf3KZi7toh8_1fE53+|_NcU)9jfhN{I?--)u(sL>wx!K5yc-;@w zN&d4E!uAgaTybs9pD6=Ci2OlEuH!ElnH>DF%Dx}G-!*1Pe)Ak~aEF~RJ6r?K8%B73 zDK$sEeBR&tmtF!4GT&{>%dCrMMrhp}2ya8SLzCHW@KyG<$WDHoX<$mz_#G(6|Ce6U$IbX;1 zsY39^mA4k@=!O}5zs-=RWTf!Zy3d7n*dh(H=u!sh&K;iC_yZ@vw@Xn8JiNHSkzIqK z9y&wyU?t^Bx*cI7C{@mK|9rU*d}#li#qd$fV1(glp)nrnARNKAD_vJS`GOK_Ma6L{2%(qtCx~cM@{;!|; zrzPv*(n&H%$HM*H_}kVod}h90-pnUsUkA2+c752tu^3$Im^I&7+6>|q6#L_ByWy@M z&0oN+e4rfbbVlhy-n?`ej~H>+&#-y3g#Lj@7pfE47fsimGQPrE{ymc)Y(x}S&s?mB z0n8WkA3pAbXKSxjEcAruS@B2Q|=6w zn&2f0pyG4A%Z?{2{OXr3L1lpj}4y*gca>d~1Ud_0{ z$W9m*V6@$Q?+~)rCwO$-m@&=<*?BgQ?aVa^_sAjUv~{8!#*h=7BDYSQs)dzr&i{zu z8V0u2RjO*dy^tgelh6JxhOD;73(}GrK&gn2i)OVI>TAo+iLS+Va@gWtenDau@^f=Q z?69bCA8Js>{k(!fA4@{`QI_^Gx_^;Z2~8q_YNH_W#!!d7R3kXR5q3;pp$jxbihlgQ zwgGHoYv4WAu7XC~`6dHX)Not{23%2d?`6*K=AeVX(U08uT{!;sd&Y;8r(2-zOD&U( z4~Z~i=MCebx7}cSYC<2$a1&~}Q#J8n$_hNbDKg;ZTib3rt<6rEh*Iay`PxnQ17*k3 zdmX36O5opmq1vXxR#2eW$)z6O3BTkF=Dx3}MK-K?YVUKFg!5-5W@|@&ALB9Q0Z=ot z&2g<`2{7T~Fx{}B8@f(kJ$mdTx*t%+&zGw$$UuA0*8+F2+ z>kMX>n4+ko`u-bQ@{6;P@f(fZp}^F1qiL8{mm4kNiO&{cd3Py1K$3^zGuk>U_C?d0GroN`b zmdAIQ-zj&3s$-oR->TZdy{n2pmPDJOlwW&8WWHg0aBTLe{eQw;#ejQ}skKzEY8c8& z)lj;@D9%&nTQoK(X%4cZJA?K#o#%~Ws- z)J7{h1(@LZi4x?k?pd7C4G!s^2L5TOaP1YLjE#jYP_0SFQ?*qXb*^mM<9zD3@Fm%CRovz*ptdaQtk=F)(3-Dj(%RDmiYR9+>t|blywpJtksrf|WHVFU zmy?=!rq{*n7prC2Qhd4qShx)saIyD5n~=GTj9*;z0KORgq+xoCMl8tk^Eu43ePL^DRrqblrhPC!*0CNooGUZ>9&U;5a?2OhbrYFK z!6vE^gZrg=!V8tSrhsJdquAF=tLVf>eoGU>b=<$hckN%-mS^R`C}N;d5*z>*29;Nf z6|F+!vWB_*)+yo>%9sIH#FNK0xo2@t#~E-uY$9+Dn8^9UXNnC_s&S9Re)l#I)$1Ic z*xw2sAF?eelcONMj`!s{XRYycwVDB!W!t77_e-Ynfc1s>vd_3TDXeco=l&GHSJc0Xp{6`BXqon17zyd=?y4?Ze;Ut#>fjJak(IC>PJH907u z?upGGDHFiYOz_TytHp40YGRga#Q@9-UhngbwFKm;8ei)`9zumfoMOx*8}!`qWb!MK zm3X!?Wl%b~(6#bO>maW5eGM1Sz@ko==U(R*FBJxwg2ZDu8wbFQsIvFP`eImkv8N*L zq#U}hY2@&Ut(v$o7rcw3x1X80y?zRh{*>)2-mkcoJ`7l2sgg|eO5tUbM4*%23+*lw zqh;3C0zbul)A3g{P~oPmx}E#A@wFGSfXK3N-RL>|w#4%{=-ZqfIMK4`EYxOQELUYa z5V}wie*9@IY<_EEl5Nxq6cco#(LL_S0rMlF1>zTRQlYq{v_Gt5T>ZB^=RAT!Ca}UTL3GQl#7j}&9ml5iO=U07EJZ^!a9y#{$CuJA2E8}PF;&6GX z4KEtuK>UV)hh~51xCqZXQ#9aeA51!Cw65jvC&+4;en6k69=MiOv8X??LMM<1x-()H zxV-ew+H^ZezOhQ_zCc7Ke}`^zUemH*eh$RCShr`DZ7FbCvi&5%ycHZLH_xuE?*z<0 zN}or|tDs`%A4$jBNi3;? zcN)MY_Xy$9#x&q|nAvKBK_7?=t*H?bSc|S*F2$T0XpV;zO9tuk4|^HTef&+=mSR;} zk;{{k1z!5QX{pS8fq9(G4XT-g@N~AFiq`39P*PC^^8Ht%ih6m*OY_WeR?)xK)2;dR ztUO_7crlK<YB$c2vAiBvhH zSmRogFs%jOZ0h@y)CEJhj*M#eO@bVWWW}eFo#31InJ--)5N@zmKVX&D4r zjtY&3X^q}Bo7dW5{$rLkgVs7z9loy<6aBd3mvvzMlNt!HRH&JnTT*%5A{N@M!pKe5YR~Tyu0PPb2&nc+0Ld+tWG- zsw;`abnu{ zHG2l!m%=POF^TL5t(yZeGH_@xjrk3ZZ@f8RZ_)!Q>i>*$N7n$(j=7u14QoOBCHA1P zSWPr}qO7Yl8%r>lbsezfTfzf3^Honr!3?H3z%bA%0N;7OC zZe>-&GHJc6@i;y7=o^88^-XGc`g3HE4!JMj>2k^+*ZRvvmbDT=t$;mUuxH(lerS1% zAi1Qw8qBGS9a;D54LG8^R$nIKD#EN{x8GiS3-0?(m~;{8f)^6}hrwM3-b-!AX91J& zqjfJEo8Y<&Mv1IUy->0-$6`}aI?Smw?8&Y2Kz`odX!GsVbzHhl4AT8kOj+GEwa9pG z#Ym4Jwqb~z-r(0*ya#Rw&)r=iKL~ymJhj!No{4GQ{9W(0nG=@vr~+T&J5CJRP5bn#yF!JvvyUwkhxV z1;wqlzFDcFhhDYL2w9&ji@VO1W=kgKh3meas#IlIsY~d? zf#=D?OPb)Ue)W#yj~d{!vwGX~-b^5scapg#gN^Y#x|wY6Y(;d~_rt{Ig8;q{bi*du zy{7@wy&}KicO_jZ!T=K`!@Z9Ewc{ZCu8I0EGv@#$uTXJasSL1gx~_W1=u*`A@9HN0 z1S5P0>`s<-u_CH2>pypJ3AQd?ShTy42vOt`mxU8jS66BkiQ&8487*vWhlL=|`S+8n zT{Vdbzjm4E|5ljCq&=;)nM7Di_TRkl^!3doqHXz|7O|_^^U~ACOi()=`iNl98EIUl zZCH-5<7Ho~oI3{sRVe%ZN{qlZuK+?uZ$HpJf!@ivQwCni2NqCv4f6^rL?V?G^9 zc}X>Zq$Bd{>BKF-Bc9Uix4s`7wfHXbP1y)te@m-hpISOaKXUlrb3rDi#Ij3UC+b-c z+Iy%6E$7~ZhpJRURK54aZId8ofH04F_uA4qPxo2@t zMkf+o-oeRAbzxsT+fnw6=Z!z4Ou{!;13q%|w8LRvTjmq2U6ASVht#K>uR+fV9y>Dw zcJ$_0^|e8W5el0C^o1TBw@0c#PO6g{p|O9|ZI$fHvFQCwh}5AVG;HJr&@LMa4jvkW z99z=#N6cfv&s7opVvYuA+Xty@d;3&zqOww}7{v579M)G>PJ;kj1)X80b`a1g|9*FQ z9k}K2iN{y88$_nD%F4Rb!_V0fmoiHXQROcy`Sx5_$DPD@Jia{YS@>VCQZQZ5R5zqi zK^GF~BqdH-v;Ty*RvbI@PK*NHPCl+r${PUSW$k5&Gu)^c*Qw0c8w}^2gz=RfBUed9 zcGZ^#Cd!E12wbMjifoVAO_>^R2jk1k!(v{xL&@rs7Y_6lgE&K5?*xKA>iypD%TYE} zoLocye2@1Wa{Aatu;Wm|Nh3L{2nkX&P$OIsRqBf zl$LVe=|j3!e|GYGVS`(We%#Hv;75nURb9GM1mw=$;e;IL5!_P9a{D=3nQFL_a*OTy zz9yg)O&%zH*#(rpIxBprdxUI*;W0l0cj0tq#QoqG$^B}anX*6+geT)>@1BTSVB)=| z^3PZc)CmvgDM{!AKd%@^d7Pk@7UTt`iJT{)lgn!anVPY)PKMa4epl+nVwtK50~^Ns`y;kfQE0J)f;G9#AQ70 z9@8)Snv(Ml9*`w$2%_#{7v!cK4XHaCKhO-e8?)!3>Gkl-`u&_cEjwTy+PbX6Uk-f* zTFyM8%1b}?U$S6V`X%%&+c{?GAGlqaoxR*@IR-#d+t!W=>Jq!{@v5m8kGep}IREID zsA3QwJsLm|*GD6lv@CD>M_$I0ZZ_j2-8J{vDYW-+AWyei^ApzfLxYEr)n2SWpz0WM zRoJ8(NM&qh-E^}BS{4`?SNfMDOq%iBhOLWsU_7IiI&kKV!HG&-UdnqtVv6Q4P*8d~ zXY{2Coc<-Lt9+>&gnwSDe0^sl*dg-yb*7IJn!BbJXwv+K%^W@~)37vVZ49_w3dv8I zFQ4|C0mfny8{H(j;E!GVgSVcm0IxU134Rc50X4tGx^GL)BE{a!Ld8@RJS!aJ7c#8h088eS+kFQhC==z9J*e3SMVH>+9-v{1I0c=ntpB}u z49`#Z#lrQ|x30o*k12>ci*Jd|{zF%lGW_#~pHW>Ch->_6U@cM%g9xiqTBp0=67iT8 zQw|LzR{dV^w$c$i^Ux05tAihNvmEXPC!7x44dw3xA*r?ZkGTv2Cby)o;@6U4)pP$1 zk$3W-wuIeMr4wA}&+9vtPPHr^X)_+Urw49N9^oP&bjL<*@svtg!bW*3^SAk>ej6BF z&vIbr?P=rce;c;mwwJMGj4MU}&w_Y5Q}g=%zyf zRnj!RV9|o{9RKE{?(oz1SSeR~&yWkQ;R|A<$t7YP-*6#tzSi|TKcf$d%?=%Isj33* z&&Jj_UQk5m+yz|cXEi-pGveBaS6s>mICT)e=O^G3iB^!~b@Z+Uq?Ro8{F-8x>IK41 z`=ktv^I&`0l6O*yyr||aruP1Sj48ZeMtXGPUo9u8RXzdw%$SbI%j4&LlyRBjbfb^6T9j94+aPiT1$Jmz)oT(Xln`uVCmuL2d99)%d|4xVI<#AXFEQ~JhU%jJAo zTx4h&i;R!uG{HBiA01vjDg<86;XOp79uWL=W2!~{N_3-S<1ed!>>!G}o%YAwy!#p{ zLemfp)J_viz)4F{*W1L#pH&Pgo5;&no%jM?w4~a+c`^XM^_At^wzEUy4qCh)qdK21 zuNVWabHaE;i)9%egcYK&2{P9W(v;BszfHQ;(xN7^Pe2qTm&)5Dj22TJlBJWG~S zp!@XMoCnKRpgomGtHr5W(@%WntK3BmDqQ97eVmzv*VWQ$|Gta^F{!_aT*KKQ>+%5i z(>ML_w4v-BvjY|IMN?qVY4fd!N}KibNs$pe^Dbl1u^``m%YC0RczEmgU3vV>2<lL3XQ=s{RynT~JGEZJ#Ugm_WHj&)RX|VvnVaVwwhv{*HE@>^H4-esd1nzb^=Dv$nUbP6 zL;T8G3PU)pXEi1|Ao7s&;T>uTwV2a|n$D3pu)Qy6sbD#^=#9F;#f(Q&oAjhkXY=7^ za7tPJgv_aTaAdzc^j#Mt1Zo- z_BH_*xv;LkxOkCrThb5jZ3dd1S58F{-oRX|tc`>w>PJ>})x3MTNuc)8FJYF4I;hsG zJj--l>_`BUP6i{ihG2tkLCFjhPg1nG^@f%5Ve_5IFGu=7hRa!rq^dfo?tJ($o9I_~ z`izvu^u->;BcO$+?wB{eAjOI=3|^*w<&2UY1$$4F{1V$T4gVBH^Raj|!}_h^Keq6e zf|omkmE|0|p-=1VK#!9tYAsWEV6&+fKDlV+8lMt6KIuMVp^yagJls}{!d+{a_(oP# zL;jz!xjtjvFefK}=2~Pu99P`XZM)L~H4FC4ERMy#tY8|+V;X%5yXSXm$pAbnq;fyP zei#hYrS1-n>IaueGewo}bHSk@apQFk6|nZDpSnjZ8=4{eLMrY9b{L4!Y{i1T>EZbA z&}(mwkztT9Cu+w0gL>GD{o_D1Eb7RkNne52y+3JvLtWq~*Z7^)XIjCEaI2QdZRTjg ziqj>Zw&~zgRAB*^etBD$ZvNsD{@s|8f3RIgqlk^Nhn28;b}Su+Z+g#?sn`WIi+^Q3 z{M-fuWhZu^-Uynak=*J=*2DKsinK-6xeFgF6{bP2^r4Zk1_EV9Eh+EiXeaD;scSNN z+yoAwNQTaaS|}Jl8Dp@^8vXmXWc1F-#RFMd5l7Mv8UN$i9dOS>f8pP$uK|)v9N%v+ z0N<8+^dx8Gz&GD2gkNNofFIZDg9j>=Pz4d^drO8jaIP{Iai}Zouzj&|vBbx>DT!ao zhDcdeQxsSDvkLyT;fYl8krfWNSk6si|1tDJej5dRv=Si}*W@l3<$ zXE;c)Hd^ld1TVRv+}>=d5T#ZR*Qh5xYpS&eZd~IkA)or7vr29WSGYO4^LmA;D3b=B za_B`I^nLB7Mh>B0$vt4f#}x5c?PZb%%zgMj&-ip%=sKInh_3D3;0Z1~$O zu;`0dE!Y}fR(7}592NJq_b^FV{2_@N#%Gy?v)7+1&H*77$H)s)t?=HnX=RE5KnqB*ErBl~hpU>?)rL2+-_Uybzsq3R4ientEgguKpMtU^{oxDy+iJXOcd@| zMqC%S;KpsmGoWo{LSGhfaX~rwu3>8KR35maM*W%4s$8fRneLNBJx^^psC!)^${h7G zk&#J2Oz_F3K^Ju4+7ujVK5WRt;tEFZnS`l%NrJm(GT z7|I0?pYPURd$$3k+46?J{%eEo?GYNJyj_92L<^%wr2Dj|<(*-0^z8$QX5&%#F0gg~ zZJ8#Rtyo~`tI!Gv<;#0J2U=mym8@?4U6+vG7VVYOO}_YKqt}Vh$vd+>_E7BHcTml= z<(JtZJRc(KoyKA|iZ?-%$2n0Avh^@*Hp=|z&rYB}`RvEn%s+_Z>+1)!e_GAk4%3lV zC(^le^xHK>W(xA7zk5nz4(@eXZ=v#|3bgP1n3&kv34@qP0>bglFpJeg+WL$=s(p!a z^E2OKF@!EgUL6b1^u$B`a4kE3reA(FfX5R95K0@UPBHIi-`oX*C-tx9AGibGtnNf_ zzapWr7r3o5L?m%bVZZD$hzSqfmgvij7=F&1m3lu1;soy=vOYZkuE>oUp3p1;2hwea z)lcR@^`7QJa`+&!wao8^)9W=j3av`SXDs%-Kg%?9wVYI;9>hWx@~?el)^7 zkQG|(V;NNqp1cX|DbAk)_hTd!<*Wz5w!IcZHwDY!v3DablpYGx8*tVLwExgoUZ)LBM0ls~V2ynIPlkh$WF)H}TA|oTuHR5Dr}( zKUT4B7;#K@I!LKn3_>)NCvj{uL7d~THgE}7Qq&)~BtA{_PIfN#a8Y{3VsNg?~f@9EU8~dFn>j%Ie6aAX( zqs8F-`NFOpPxHX07QSZxcX?n%dq$Ssi7EyZ6N=^NE=~es>XCAZ zc7ojO6k3~ua@|qFl@yi?TveBRGv=pOd-dwcM5NZj+A{Yab>BOnS*-7?q2`Ooo4x_9 z*b82GN;1V_&fgPu9;cII;m|$5nZLhV;Wi$Q0hy>Cm?PpLabw^kG`J~udw)$abjg=$Na*7iu&V?x{6FWuepzW$t%EZ_PV)5XxOG3s`S_z#yS=Q zBk!iW=8}D&@FUZmg;a@-~{Ng&-`8dXa?z7!S39`@O+qHZ1 zboO<_GvB+rHN9Gau&-5#d6qHy%E{{eEn5qmn+sJgd)~SGt{jD*VqF|JK9~YWxAS!9 zz3KoCVQzpaqaN1e?HW1h(+vJZZ}L}JBZT@HRg`DWt;OfHB`vF3#qPf($$}(+%Ng&I z8)0^ghKmxlO8RSYWvr`v7XZKNk%sTZ&{oHNk3_j3YCLk&A-Qi2&Ty;w->MokN~!K) z+tCAe3U{z+>QcY>idF4&41IVhk7gyQn Uq2BhUB_p9~^Oh3&ubP(s0|V?-tpET3 diff --git a/tests/regression_tests/surface_source_write/case-11/results_true.dat b/tests/regression_tests/surface_source_write/case-11/results_true.dat index 8979eb55477..d4d1d1e5ad4 100644 --- a/tests/regression_tests/surface_source_write/case-11/results_true.dat +++ b/tests/regression_tests/surface_source_write/case-11/results_true.dat @@ -1,2 +1,2 @@ k-combined: -5.169123E-02 9.144814E-03 +5.642735E-02 1.494035E-02 diff --git a/tests/regression_tests/surface_source_write/case-11/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-11/surface_source_true.h5 index 0edb06c88e9a4ba368a72bb0a16b698553dee244..b5b6036201bede37dac2749563dcd68620584009 100644 GIT binary patch delta 1887 zcmZ9Mc|4SP9LLQebC4W^bu3zGRGY;*qu(=<=7nD6$|jO?T^m-Hb{8q7EsGvfvt5YF zhNeSN{DxdvOU@A;j+!;OCQS{k%zB3LdNt2Kzt`{gyngTJ^Z9nEq zgb~Pkatn{%o%4i10=|ig&Mwm;_M0?^-_cS?#^DOl^CVq@SS&^#keCYVJ(p`C9UIWg zBn`=#0hx(U&_^T5I+AS~nTcyb+sO+T1oOKWs4Zn-w?b75jir2tn?UmxPdF4N{=O4R zp)OpeQ<=D2G>)q4IsY}^MOo5?ams$MD`Ofi>`9zGefS?~I!D3N{XMf=F7^RWgX)x? z2tF8X`Y5*Zdk&wxix{_x^hPfGH@xOOsJeSY$C zd1t~B*SIhJullpg5Z$hQOBOmA4OO^#PVI*dm)x|$0l zT&$LiMJ{1+1)s!LO7$~Q1&E@zQ^>6 zHc3EYiEho_TwfGWBoTSY$i06vhhBO>ENx4~ATVy?L6v)JYK z7O))?9G>hSfOYG85evUINIPw&a9chL5nM0|b~(OFiVcuayGs9qyPrb?49H{c>UN)5 z!VlP;YMkMBK|Q5`;lNGh(B9{ATZG;OoZ8W!f)6S|3Nm)*-)2r>MZg-0D4C%|RW(fm zw^_*haLcs3{28Q_aq&$jcz_*2-I;fV2evg@-K(#<1?scX(?aYbkw+)LkBzkzN@s{H zqpjuNU&e(55&Rq!K2pU}Ko(w;5dUhw!pcN%$h=U?B3$Bu>-xbzSQYT#Y;Ux>i9!ze zkQti8(wWAzVf`eFEzqg|e3bG`YL%m$%F%DzE&z{6_)asQZ1^B3q~USmBseD!UhGY7 z04|v}uGM?#jPMb0iuqdM(tm@BdA7MPiu#X7CnDLo-1fUS-b1^0fw|P6F_6!D8*Q)K z3Ea(aR^F$3K-8{l9b57bfR{xyqLUjLpev{}tSi-IOuJ^RTdg}R0E=ch8ssyqUpwX? zHM0E98LeLM>K~%x3d2UgHRPgkYU99-5@f)&bYyH&Ec~s(MjoA1mIR_FnQ&hsDFtt| z40-LhFNTma0!Zhx{iF5x@Yu>ypM=hwr5JRh_cjg|kAsHvYF1YZ#_V)?T(_GiSjc}Wa<)g_fID}D|>9@?p0zWX(N)nd7g$t{9U2Gjg4tNGyI zrN?2LghL=1-m*IB)Q-?P1YH0C delta 2822 zcmZvec|4SB8^>piWrk$QdXg;(bq-1yr2Cn?UP3BDC0nPom*td5l4Pl*Je4JBDoxrL zSqt4$O%madElXw?g9+Is$=l3$eBR@8o`0^-eLv6r{rs-qbzS#NhF;97D|#YC)ocS_ z0!beyeDrgY2|fgo3|aJtxQghGr2W1*DiYy%JPS=Gsu9lMaA+aXR-)Kxp$bxKf!-!6 z3HCHewjzf#(SxMLf@KWJ7SBX$NWueyzg8pg};9PG#a z^DO#+9oM03Ooa)RGIP$BDfGa$b2ZIYK^#CM+dmu))Im!0>)mz6htXps0aj`rmgbsa zZqYUb-E-b3=6;tzgs+LnLQdFWMS2P>VlBGQ^XP|a1uCySJwL(POS+Gb1SW$fx#wA5 zBBoIw`L9@Y!mzcaswk4w8usk<%_&SZqeEA-hxe`%co)gwtV|jq{+jx!(5+lRk?K*D zzb8(iTnI`q7n;DYg*5s~QrVRAHS}WKem0CB&!Oz~nSg~G-e038^Z^<2Ut-qBT7i$4 zp812EX88hL%8cZu79mi|~wRkpzZto5*NxS78MgbVIk8>bsl;9pG_P*fJ417f?s~cJwN> zfY68<>jjh~F#YXD0rc)d+ohD8e>piHYi|DL9CRwcOkilWpHEyLg=1{q*d4AV-2fwX z9BU5ecLM_t*~+8-9biwmy>!j-D`0JSerT?uI6zraivPwfu_W&`8ycSD4h_&>`Bc^vc$@QkUe9J0JSwM9^YH@*Xx5)%ijq3OeN)Q}rdK-B zWUl|FyK@VAMq1G9q}dXQ&PJ>>mSwMg+o#6{KI?UEc#9H{N*g1*Ye6Sikyv!|TQCWd9n4chDiJV~UaJr+BBl1Hq zz3!3=Gf4ztxm|XU8L9-X@jVYKiu<6-^k85|VV3% zhj3f^ft;Gi64&+?c;ad7pwgwk!I_{9{Y?{RVP=*~ie~Qs{8H;K=I0iUY&w6!;34{w1EKSrS87eF<`h) z%YS9^2;W$KkE;KpPB>hLrR~%Wtx7Q1o^^l54bQ;pciLYmh8i^h6Il)sD;D1Y@iLR` zT?!w;YJH1H56whyH2qw~DiR)`RH7o^CUrqG6^gE-SN66*-71m%)7iDaWZ=N$@-PIC)Fs_+4+G?Bi|8b=xOBTi>4}5spEVIe{L;i zy~bvvw{0S|W1GaB$Hb<;9%qkpA@|}1V>cBZ7%>absV`#z_4L>(b?0h$<*XygV6g%c zLZ)rr`XiEmeetpDXR%oD%x&oNvEl&Xn#ibjB2P9Z^S}!2Hp!g^JWyU1lwD`{4$Rm; z6|o3m!w9eEBTJhYNYbuO)2l&SFsvQMn~for7Ic~h0hz@j$F3{$l9O;q+R>E1Gy0xE zYL%y@+QetrN?rXVyZa05Ka_Z#6IzXosH*yxY+_>XNzJ7yi(boS+2|hS(U3vL`^m<46rIG{_k5rE2XdWqcvlH0KA3gaG1AIuLpCl z>u~_tY&yxrbIuHed>*O)5?cbEH|CvRlH3EXx{%ISX>p)z3X{s%x*L(Sdh#GOsg{rB zx3Pwxd8Dt|w1yQ+FcK*Xrz+7-DDK5N{-~q{dPU-nrd1Vzdm8IbrcwKVld{H=pZZCN zZ29yJce7b-Sjcsne9epzN2b(j0&OO7h;W~X%oH7=n;3Ni^Yl3Q0?&pgs-SM- zqE@K-uFs63q(zY;eM<93O0kqTVZ7OBl<26-P@jUcIL5niUt?P51l;3l5MM~Z7hMU{7A2cO~=#6t8e}-PK}BIf>GPO~uCebzk%=W$$^|{{aQqMX&$> diff --git a/tests/regression_tests/surface_source_write/case-12/results_true.dat b/tests/regression_tests/surface_source_write/case-12/results_true.dat index d793a7e4216..ad927bdf304 100644 --- a/tests/regression_tests/surface_source_write/case-12/results_true.dat +++ b/tests/regression_tests/surface_source_write/case-12/results_true.dat @@ -1,2 +1,2 @@ k-combined: -4.752377E-02 6.773395E-03 +4.929000E-02 8.212396E-03 diff --git a/tests/regression_tests/surface_source_write/case-12/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-12/surface_source_true.h5 index ea81a8250956ba2f3da07bde3332e13963cd26d7..ca5eda8e2f347a1fc88b05db6690ad5b6d3a2a51 100644 GIT binary patch literal 33344 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 MnYIUOOW3*p1=$pDP5=M^ literal 33344 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 S)+dnMzTq}Zl@F~h?f(H)kZg?r diff --git a/tests/regression_tests/surface_source_write/case-13/results_true.dat b/tests/regression_tests/surface_source_write/case-13/results_true.dat index d793a7e4216..ad927bdf304 100644 --- a/tests/regression_tests/surface_source_write/case-13/results_true.dat +++ b/tests/regression_tests/surface_source_write/case-13/results_true.dat @@ -1,2 +1,2 @@ k-combined: -4.752377E-02 6.773395E-03 +4.929000E-02 8.212396E-03 diff --git a/tests/regression_tests/surface_source_write/case-13/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-13/surface_source_true.h5 index 08bd809852152200e5449745616feef691f46778..ca5eda8e2f347a1fc88b05db6690ad5b6d3a2a51 100644 GIT binary patch literal 33344 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 MnYIUOOW3*p1=$pDP5=M^ literal 33344 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 Q0?F+gZo^dh(CX6uAE<_K5&!@I diff --git a/tests/regression_tests/surface_source_write/case-14/results_true.dat b/tests/regression_tests/surface_source_write/case-14/results_true.dat index d793a7e4216..ad927bdf304 100644 --- a/tests/regression_tests/surface_source_write/case-14/results_true.dat +++ b/tests/regression_tests/surface_source_write/case-14/results_true.dat @@ -1,2 +1,2 @@ k-combined: -4.752377E-02 6.773395E-03 +4.929000E-02 8.212396E-03 diff --git a/tests/regression_tests/surface_source_write/case-14/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-14/surface_source_true.h5 index 50cddcc70cd27df73f37a288d86a906d7e7694fe..c9900bd9c0469d3b171f863f0518d6c94e966027 100644 GIT binary patch literal 33344 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 LX?w7?gq{0eR8MbD literal 33344 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 TdaX|&xqZWJm?|GyUE2QxR|Ib8 diff --git a/tests/regression_tests/surface_source_write/case-15/results_true.dat b/tests/regression_tests/surface_source_write/case-15/results_true.dat index d793a7e4216..ad927bdf304 100644 --- a/tests/regression_tests/surface_source_write/case-15/results_true.dat +++ b/tests/regression_tests/surface_source_write/case-15/results_true.dat @@ -1,2 +1,2 @@ k-combined: -4.752377E-02 6.773395E-03 +4.929000E-02 8.212396E-03 diff --git a/tests/regression_tests/surface_source_write/case-15/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-15/surface_source_true.h5 index 3e15017a16626a9a66d903daf1721a97dc3c4058..5bdb39b2a703fa7cdb17a153a688cb0ea7664f1c 100644 GIT binary patch delta 17 YcmaDL@IYXL2s_I?%~KhhCD=U}0X^3S1ONa4 delta 17 YcmaDL@IYXL2s=ysjEuC+66_v~06HE8kN^Mx diff --git a/tests/regression_tests/surface_source_write/case-16/results_true.dat b/tests/regression_tests/surface_source_write/case-16/results_true.dat index a97be70caec..cd0619c1fe9 100644 --- a/tests/regression_tests/surface_source_write/case-16/results_true.dat +++ b/tests/regression_tests/surface_source_write/case-16/results_true.dat @@ -1,2 +1,2 @@ k-combined: -1.496403E+00 3.891283E-02 +1.403371E+00 1.456192E-02 diff --git a/tests/regression_tests/surface_source_write/case-16/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-16/surface_source_true.h5 index 49f67e8205608e848f0130ed65b472ff98eee046..79ac8e1a65e38b5b4c38de45c29da9f3579a35aa 100644 GIT binary patch literal 33344 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 J^epFt{|~;VRf_-s literal 33344 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 NWw4>&FGk7_{y#Mh($ diff --git a/tests/regression_tests/surface_source_write/case-19/results_true.dat b/tests/regression_tests/surface_source_write/case-19/results_true.dat index a97be70caec..cd0619c1fe9 100644 --- a/tests/regression_tests/surface_source_write/case-19/results_true.dat +++ b/tests/regression_tests/surface_source_write/case-19/results_true.dat @@ -1,2 +1,2 @@ k-combined: -1.496403E+00 3.891283E-02 +1.403371E+00 1.456192E-02 diff --git a/tests/regression_tests/surface_source_write/case-19/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-19/surface_source_true.h5 index f4261451dd4832b3ebf2e4f014543c850f8f35de..db8a49dceeb20ebdc3e0c1b8db765cafed5a4052 100644 GIT binary patch delta 17 YcmaDL@IYXL2s_I|%~KhhCD=U}0X^~t2LJ#7 delta 17 YcmaDL@IYXL2s=yKjEuC+66_v~06JX-mjD0& diff --git a/tests/regression_tests/surface_source_write/case-20/results_true.dat b/tests/regression_tests/surface_source_write/case-20/results_true.dat index 7ccce7cc3a6..44293cf0919 100644 --- a/tests/regression_tests/surface_source_write/case-20/results_true.dat +++ b/tests/regression_tests/surface_source_write/case-20/results_true.dat @@ -1,2 +1,2 @@ k-combined: -1.149925E+00 2.542255E-01 +1.266853E+00 4.552028E-02 diff --git a/tests/regression_tests/surface_source_write/case-20/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-20/surface_source_true.h5 index fa8a4e628728deb4c4d75b03cd31f0aefe6d132c..01d2abf61cf8a41876d664d1aa505a967e7559cb 100644 GIT binary patch literal 33344 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

`BTVz zMB)c?1qpIG95ua!pEsVu;}p{9 z#_cYhfCb4xjcWC{`%mhpT$(D17i8z$d9tvt z8ho#&DRBQf2v1mhowJjS1A2ZLM4YaK$B*K3!aP^_q71u$(dVFjXKR|VesD>?+PAf% z31+Vb-Lp{`fxm*C`D?4&;Jz=rJ1-C=BPy>w)FW>35X>JfJiGa6O1{sx0+X7B2&*Z&Y!Xj1B^Cvgz~u>-!zsX$VxDOPUCSdE-qe@ zbpH-nsY^$`kKo#)9C9Xh_XNI!jva&i?O!Xw!|870mM0UiM&^)mm&65Gd zqpH*%i4*Pz<8x9vn=ZvX?}EJz!6oTzIL<`|p-1jP-@xMdYFPE*Y(OWje%1W?Bovbu zd}tF$gl3Yu^~oxJgrFW>JUy~(k@1Z#*%gGzd!&naQ z!J)vTqe(zW;iH)MXcPEz-u|rIt8qABlQnD2n+&+Y@b7yLKHAbQwpWHAB#U@ z!o?|w((+m+*Sew2@2LhckqlTo=UH!PFaj4$?VbchBj}OH??P+-!36W?44&Qio6guC zmHY-A{oV8r99)1Kn<+7}m0yi-8a2a%6_&Yz`Q@ZX<#u%&w5_A;z< zs7n9bL+uzF2cgBs&ZNJ}1W}K8 zTs#LG;MChF{*Eqyro9$n&lAf=(67(qaRjxgkN%(~au=#UOP41)ld?O%H zepJX+(hc8xlxs?&hTy^(i97J*1>~`9WU&AH?Q=*R4v71kEkNcntGRD#xcliPgR{dv>#X^Z{Wo?q!m}HRSP$u4 zK!RE2_Btw1hKp0sJ)hUO(>(yVK(dTjm}$y{=-3GRh#8kE#L>+M4)k?G6tF89Fdl5(FvmjjSg?^nDedmR0KxKc8tgDj3 z=5WWwyDzmE0l8-fw(m1zw6o8JgrbY#v96$++ossY$LK%6)5i@Mf$Z zRtrj>9uw+;^YsQ_=8yLwdx7iAzA<@%9P=%}X8QIj#q+DGBXCvp@VTddaDE+fO&k4j zp%}!$y1=NS=Wx<%I(W!_655}?ImmFX1OY33(}CmL=cISa7>`&kg6Jw&rj96Fzw!8! z#~jgOeQ;-@jzV-MGM0p{8lbn$IYOK?5L}r8 zPoC*fSGl}C-tMmtCFan1W9?DjX1Mp+vbfuWPT=k(^sX2+3=Oq9B`=(0MssEaAL@-f zOpvn`H`vOi1fsT&7ktK{O(MId&o$gQI}gV{^sniAf%5ycqW+{BsP<}#)@bh#+-;Gi zwWpyT@kCQRXgW){9^LZ2EzXJVrtY-aDUcjdvuJ68Yll2{a6M(3+YfGxaYzYTm&2KN zONQ0v15kWCb5t|_IZ(eYB^KGpPEZd%hxR#Z#`za-q2STsfk>%2xMep#sw!)-6A@7F zt4Gf1@8#fX&+}&iB9m}r%;`z|t3&ANW7CCUf`sEpWLrWu)7`vfjQek|&zEj@E+1)_ z#^%ql#;OZvgbIMF;=X&r5~DEZtBA8T=z@=8BH83`WB@38&C=C>`y7{_wl&Cb3-JAD z#$D(^j@iIdV#IIdc*G7(K#kSI!WpWGaHWJaSpQfx^evz@+c&_B2poUgU8g~qL%ykF zD+#F2DcyO004}h3)E`bG$84-$pYQBO%{!%$1rJaj&stUa32Pfh1n;cR7pJ>iGd|!G z380S{Gl9sJ*N?x%QNjN5Y7cYb7%&KznMRW&smO9;ufkP!PrLzTTcZ} zVEsDA%rP@lu^Ce6$ZNgoD}!9O#*Vp_jKPX8t9kks=^*2VUH5)p!sC1KIl02M;hcOZ z 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 I=Y?4Z0NP<`Z~y=R diff --git a/tests/regression_tests/surface_source_write/case-21/results_true.dat b/tests/regression_tests/surface_source_write/case-21/results_true.dat index 7ccce7cc3a6..44293cf0919 100644 --- a/tests/regression_tests/surface_source_write/case-21/results_true.dat +++ b/tests/regression_tests/surface_source_write/case-21/results_true.dat @@ -1,2 +1,2 @@ k-combined: -1.149925E+00 2.542255E-01 +1.266853E+00 4.552028E-02 diff --git a/tests/regression_tests/surface_source_write/case-21/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-21/surface_source_true.h5 index ed87ce849f788a346f99843aa263565fd326040c..4e5dcb446c26e85f1f763b75e4a33e490c1ba01d 100644 GIT binary patch delta 17 YcmaDL@IYXL2s_Io%~KhhCD=U}0X_Q$2mk;8 delta 17 YcmaDL@IYXL2s=yWjEuC+66_v~06KUDng9R* diff --git a/tests/regression_tests/surface_source_write/case-a01/results_true.dat b/tests/regression_tests/surface_source_write/case-a01/results_true.dat index 8979eb55477..d4d1d1e5ad4 100644 --- a/tests/regression_tests/surface_source_write/case-a01/results_true.dat +++ b/tests/regression_tests/surface_source_write/case-a01/results_true.dat @@ -1,2 +1,2 @@ k-combined: -5.169123E-02 9.144814E-03 +5.642735E-02 1.494035E-02 diff --git a/tests/regression_tests/surface_source_write/case-a01/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-a01/surface_source_true.h5 index 4a86235679085a07458904653a5b63f162059dea..8dc148ee07eb33d6374387e557b35914f5fa2003 100644 GIT binary patch delta 3450 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 S3!@oVZf5V?%PoXBH~tSAEx4rs delta 3364 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 diff --git a/tests/regression_tests/surface_source_write/case-d01/results_true.dat b/tests/regression_tests/surface_source_write/case-d01/results_true.dat index 7fb415cb159..26b9e30a3b9 100644 --- a/tests/regression_tests/surface_source_write/case-d01/results_true.dat +++ b/tests/regression_tests/surface_source_write/case-d01/results_true.dat @@ -1,2 +1,2 @@ k-combined: -9.263843E-01 3.193920E-02 +9.288719E-01 6.877101E-03 diff --git a/tests/regression_tests/surface_source_write/case-d01/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-d01/surface_source_true.h5 index 8f8cd604dc7d685af08c32e69922749b6ad01654..9a66315ffe508938cf4b2499f980718982314636 100644 GIT binary patch delta 9388 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 delta 9466 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 Hqv8I4L9t>9 delta 18778 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 aQMJeStOfD{|Cz_a|9q$Cvjm9y`~LvP#Sf?e diff --git a/tests/regression_tests/surface_source_write/case-d03/results_true.dat b/tests/regression_tests/surface_source_write/case-d03/results_true.dat index 7fb415cb159..26b9e30a3b9 100644 --- a/tests/regression_tests/surface_source_write/case-d03/results_true.dat +++ b/tests/regression_tests/surface_source_write/case-d03/results_true.dat @@ -1,2 +1,2 @@ k-combined: -9.263843E-01 3.193920E-02 +9.288719E-01 6.877101E-03 diff --git a/tests/regression_tests/surface_source_write/case-d03/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-d03/surface_source_true.h5 index 8435e8f17f4284ea1916ca71e048265aea650f19..0af7160d4bb91e4efc535bbfa8bb2aa24851db7e 100644 GIT binary patch delta 15119 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 Dx83k^ delta 15115 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%0Aql-Vqu@~HIk$GDEq6hZIj&^OD zR6tdyUnf?+rxx}}G*kAvzHNKl91G%hs8uRBL==nGKJ-vINdn8@WVH6==20;JYem10(15FcnKXt5_inXHg+h||d|LxsRnBu%oGm!GV@TEomzTnqu zv1>wiUNmkS1ou06tgE=X!EYn|*Pglg@Ty|Z*3B96sN-Hfm!Gy)xZlzi$CZ|xl&n7q zP`;wvsPeu=0IBXOO1#m>uk3?sOIsX&8RO?L)xm->XJ&Pri4aA4PdwHawO7Xc$qtt) z1Z&}1L(xx1UyTCcP9#ak;u~l^8=PBthYPLl{h4qf<|>Y*EjJehp1m@>TNLxWx9$`C zL_m_APnk$|%45wnfjqU1!*JcK)p4(9jqvzodL!RgH6YQZ_u8c5X4FrF&$h8H49D)I znX&*6&To4@)PvK(bo&c_6ESm@*`mWQWw7Goo!+tu1@L06XWXZ_aTw`)-Gun@4b;0< zI|FYFAz!>UymQGqf@AS=Gfw}wjgIXJC6zm}5bn(QmJa!_Z}?sA)>%+_JWOO{_A2@^+lkF6!h9|hDUP}{IQIDF2Gs;0Lek(w!gYBci{vQK5Hzxo9 delta 18496 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 delta 17721 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 diff --git a/tests/regression_tests/surface_source_write/case-d07/results_true.dat b/tests/regression_tests/surface_source_write/case-d07/results_true.dat index 63ea1a64d41..5a4ea66898d 100644 --- a/tests/regression_tests/surface_source_write/case-d07/results_true.dat +++ b/tests/regression_tests/surface_source_write/case-d07/results_true.dat @@ -1,2 +1,2 @@ k-combined: -9.756086E-01 4.639209E-02 +9.947197E-01 3.711779E-02 diff --git a/tests/regression_tests/surface_source_write/case-d07/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-d07/surface_source_true.h5 index 0332f2dcfb100123fe3a4ee3a47f690be2af1bab..7ab2e2c807b2f2080745d6cd48db84e75f9a21b0 100644 GIT binary patch literal 33344 zcmeI5c|25K{Qr@iP-Jbf@B1>eaLx#o$P!X1SqqU6p_NivRMM`{u97xOwsS2}LdceV z-}fYu{CqxUG%@2F;uyACuqTMcSbyrKd^hW4f6gTABqk|GdXfI?qMQUwFaH2~*N}3v+#a zn&~Ua`IA%9+cf-4GkeVB6aPQ`f64;u^@;zwiZw|!*`MB%{ETc2?|lcn9Gqm>iDCDjaC+LCgNBdpsQ$Na>%F|N3kI>0*2|`JXxb*JlrqE+d!m|NPX3>{Ld5ZG0 z4AWlwPrad|qx-i;F_Y@q0@__9cWxxR_rF;zCVBZk7q`je@7cV`M2~hl2eR*blIklZ z@Bi=5ZGj@cOZ$u`C9o>NS6r(EzJZxf5U^^&)^%lJ-N@$VE3vm>bzm)@Uqdcq8Olo* z0`%-Q#0_eTBh#0O>&#p~`0wY)j_^<4;NL=}z+W^CzgRo+6_NA*b~9kvAyXAB=!2S$ z%IbTFOF@*bv1S0Bac1~K*l zmCldF45$ZKx2s^wx4#pr4rl`~Q|8&BGZ^{>@20A5??Buu^{&%i9D>&@Uh{e$_rh`Z zP;h4I$!|fSdX@T5%$sNJb+CPc$MvKf78|_(!T8h}5W*rO!8Fx}I-3@44mGF%yuOYL zmM?q(<>;CAUpRdd$D!2oPn-15i>!xn!RrH>_FQnc*`{=hnD1o!L8y0){XuyH+RG#4 zQN6YqNww9VPU~m|u|INm>a_|JYk!4Wr8us{ahzwe{aNj{n`f;Bo)gS$9N5ey z1Wao43i-uD%Q0Ige{PPbk7!x%kdnE8*&i`SJ+{lh_Wi0Tntm^NO*h0!*cme`t8aNzZ233gIIg6;|A{;Q^dJ9uk;~_m>yMT$ zb}{&CDzE9f>EvuVg_XE@8{Mk|mAA6ZTwkXlMI+xf3(;PbweyB6N1`I}&dvHASLpO` z9LhM#GlQAsSZ~pmt+o?^`)r;(c%S-Xma|CJ^ni*-7xI}1WLMpg5AI~zt68jT28xZ} z7yVL@CYlQ|SUEplKX1+qmH-8B<&QLqC}2q`51r_hrt2$vkj0c>Cs^UTU&jAbD?0uz zci)H5EL3QwaCk?V6!Di8&y>|N2ONj8e8_sJ;sKO>M5q$eQO1H#2DBHos9@=VVZk41 zno#3g#(vAVC?vaN?^a#D9z-9Ke&yWWDF`PeI<%Xd=gm1+#iG!UEzH@aq`Z?4d-V&jT-Qt$l=TmP6cSe?G-R6D)>TUJZ zleN2%$gh(hCK+{!OEsf!+}d{>$Dy>F0Lc&V2>~8UjnO-rI0YiF`n~DlRl}5_*O{o& zZlIfg$}#532cQ}MkZ^5HBYG3?#c?;S0d(I;Tw|R1_KzGv5`gU2dQT;Ex5q1ET(Pk{ z_VLTGnYRhB@ST}+DMvGM?PR}_VpNB|nOk?XOtb-|9B1DbRl>x}MO+eUj!rlZW&5*~ z#KE_#1h`h_{T2~UM$990<(=ZHDezik{q0R@Jz$F?FRwIX4LVrHo@qB(4B8WhmoMI9 zMQpe>7J9)D-#?(V+cFXdpAg`#?^ae-BO37J$oqY!UnJm`x^T>L&;f=2hc1#`)cQu*ivlfGy#eozIJ%y_2uwn z`_RxQ?G$8&Ex-6NrynFb{qSQkEk>R18Z+0sEFqph(f>qG*$&5{j5{kx9CG<23}P`G zofIIM6Ro5MnnLRkx5T6l@1{;*IPrlgY-cktEod_eC?gy+r49-5MNr_k&<%B}3~J zX=2&60viica~y}#4@AiTQkoMadQ)hPFy<^dkXRwXO6Fj^H-i&z);1&04Vxc7No@rY z;Y*i(Z>&ef)rYnTp0XtNNEP%v$uq=pDC?;NDSxuvT+Y4~i;`J^D3$O&6(j5V7ATpUF9N0D4AMX z5GbRJ&Bb|jci}_94O?*>N`Ir)Gb4`xpRB6Bwzpag(u=PVbe86ZZtpii%(7hmwor z5AV-xMlHL-lio*t1{XfRzm_u90Zf{>rtYoT2Xo8_J*gdjf7C?2AM1RvY4YSA?u|)(#wpQja_tfJ+Hj z;IXRp!4ry5S@}+f8*3xb8jSYr@@+<}6J~8*3XNb&V0~C^NGoC-t{VIGh7+QTVuPeao_s=+Z{B!SJGCSX;SIw5PfXkgox1Z=ZiUxRi#G;W$;>qX~!qfXs= zP=H*jbdGU;)*}vILw@E{_+xv>WxjPUAtYLod}+QRmsMqXvtF z$hx3+u3DSqhf3tqusA~6q6z&nNfojc=>!W4Gdh;I$r2N9ZB7~GSo24YDoGDH?vTI# z@Cp8vc|UHiF(J zm4pDgc4Vr$;#p65BN7d<7tc#25*5en-+y?FAJ0+x0eS9>s$YdA9=+owd58Rp1WyDm{Ftr>KynBwe4>DMc%^pJJ@ z5Z&%|bKwxmw_Y9}#;6Vh#zM9AF7=~zlgkc#l1>JptC-es50?Nnd$)>C5dq@ftsYyf zU+|Osb7mY&$yrUs>DrLEGVm-NM&?CZ4Qu$k>IbXhCqe~R6{1ATdr3z|dQr&RW0g8n z6^OuE%b&4=DRPq=K8JZRWDB zTpGX!**KXO-{NG!X5Jt`IyQxmyavUnwdq04vdC`a@WDgn_00xQ!C9zvWStlhi?bv& zY%<3A0cAWO72NDNt4^cws-yrfq*2bQ;yWTR%Za$WSpM|)S7@EGSHZ(OT|mnyCglTT z3py14B>aL;60BIoEBo~;zTN&CC$VMA#(ghF09S+G_lDZeS^HHI6SfvPb)w_aRZ+(k z>VW^H{%~i9>2aiSdwR7=61?-G|Mj-8!}Hctkzi3Se!dRv*q$V*Pd7o5J3YQT>((xF zqyyBwC#<`6qa2mz94qyzZbpTzpL2c=65v=@;G%+oi}U6br16+nxvqeua=~cdN`?Yi zB}_l(*7j#G4rG2JiDFFw1i-}Y&{{J?L{(267Z!7DW&AFr)rpGGwZJ37%({;e17>DbA!+B+Q- zi5xmfxXeRLb9QGbWpl*o`5Q;v-Nk}|jt4fx+==5TRGQT_7pMLMw2nBx2gjlGH)=i3 z@V$@4<)tt_YF+j{Ww}|-geh-qIk5{=_5`xo?r%g40ih+n!R6>s;0ObgE;I4;>cO6q zJotH}zj0;^NPxu+nL#2BoKQ63wBs3TB{&ynz4YhC?P&*adMMi;YCVJR8m_y4;KpKP zl@D*!lElc|Me16s*C&^t-CIU(Z*=QLRzyLaz`#mia8h;4dr3v2TzXzyc^5vuQR-Pc zlhN$fsHm5H>8oO(9idy`z{Wbuxr#zoIrFpvol5yF1C93JY?Q`3-|kKT_$R{4&Ou^q zaT05?ktag9y>7i_Y{>1S{&to?)RL7c{oWz(kgfYwTI|m!QTEKztri(S%{eieP zkDaVb7m9OQ(NovQ2R(%RE**gMuW0DOKDxi{T=*S2O)8$~d}VmN2t(jYao}h23wC+ojCK zW@lxw;LRTd?;Cc2=3dJe{u4E*V>!{@x1$kcw@n@K;kp6da+?oJ%PyWbr%d+EuQfDF zA^m0VO+`GaaE4C6!q!W!X4Az$h|@q-W^6g&zZiR}rMwT7eOw<8ER#V)7$;rBDPUfR zR;2VJ#{&`tC63HQ7230_V#@IMnJE3uoQf0vhM+03jux}%9yFeMmW{~zSmi7>{i?_X z@jSnLC&f$AuWdiCy^!n&$pZ>KW9=G5jtah2c4}*JdMMik3o1^|(wNT;MZfFmS;H$2 zUhqi6(H*&A1fOEGTU$iZ#J>rYYDJAanf7nbhuE$U!&1bzrWP)RM|a~mlzw1I#o5^2 z9k%n+tiNFw?k30CTwRKte|#x-HA@qCCC9y0e|I<9duowxmd1OqfCUB%=M=i0aARbSso49zXdz{|!1>nBhR4My zM_4Fdepe$}m}bxf)w)3i{QSmyow^&aWSs^h{>U&~&=(q%m4z_Xf2^YX5U6DxlHnzH`b;_@dS z5TIv6P2k-l^6*{rp+32*QW)Q!V_lY&Js^6$$}7eXpRi#wZxi72`x~4NOLU=@eK+fc+h2iK zZ?Ez3KWqUDFNZ1|qSGWAI1TQFU4I_W*^@Zr#N*YX(ln;U*wRdO2d4!m%gH+F^|I`oGGvDs>6m4yVfvTHY0JblfOR3m4W_)%T2PU z??0++xs^z}dJECg(Jdm;{?GA%?9XIBAlr-g?()lz8Wv-52}P%Se=1?)$M)#+dG>?H zZ+Hy4Zl$1G5nkC+H%gIH+2ZsTehuQ?6h5E+Q2aQH(%8>J;pdepIgI42 zN$C>_rW;38)Sz;z#dWF1ucUqj0h+gbdHYy$0MJ*^l9MjrBoQDkGw-#YI^ELr?thlhQCBFqlDC%TB?P>?# z^7hb1CYFI8o4R-IV1`6HeUnKCXPrNCSg1InY|#hhqrRX8zAopDE{n}_?DFe(-VSU; z7tiS#?*7&eT8($?NLWyhs%|7pwmnlIo@`2+lDkfvH)mhwzB5c#q8M4nIseLMpPA+1 zT%21uuUlm_@aqpK{hC@2d(vy)4N1b7tNquLX}jdeJS^{cNLzMw3sU%cLeeU)935p- zvr~&~M~>?*cG8S2Bi{IVXxRTJKJHMq+vIg9B zcUGVt@a#WubX>d<@zbnzxFpvBJOgQk70Z($_nR|g@k03V>EAf5YwYqO*VD}AzwQlS zJtQTI&Bf8~y&t(J70(N$Fy+T~n|)YX!RG{%#~Xgu zg4_H>*?K0epq2Bad6?8i*iYCzej_Lp=Laq`h5YZjCu+`&{Rl8}y}E7R`6U>K)VszB zOcKt;(bia4=)m|A$DypJ)SQ{GK)?j)L@f=^C_~+W=8u@y1Q@e>vhArx2RfrBb!Vc! z4a6%aJ>g!FfBx;E%p+Z>@e`%^ObanqJ`|dCl0bgRQfB4UvbBXtOSV7_M`}L~A8c8Bs)!|&6kW%j- zrg5im9Ljh=t>-%H#!o%ycl}hlvF+Cr= zi=(tmSd}>Cvi;~-_MiKs)a$seF5@UQVHLqH?LJ}LN~?~M$_M+jWzamK)dN^BDlm95 z{so-yifDMO(+5JGR6_O2OySP^D=q7SZ{YM$#sg~3!=bmYt8*1*^WSNA^WfkWB{*Di z@rvl>3dAsq$%t+0Mz;gEb}Wso1t(h9y_Og?2Bj77{hQFR&8Hcg5b9vB?C3t0tH7 zRw@%y+!Doacx}OPDC-aP`nR->=WgLoMKIdBSmsEh-~MnU?U_uFO9hHv^Sn3lL@QD) zEq`_SOB-r5^LWUtypYIL(!u&P1>fJH)U%jMPee){lMYcBlZxjoz9FlOk?mGvQK7Nb z?KxT-Sjt?W)`BcJn8O#I>H@1bl-yv6{0w~u`>{LhE;v1ZNsP#ymH=DdWDm=?MeQjHb%w;)jZD;=mkA_z8>3)FODf&im zx#-@BT0%3>qF-|`!^Rt)aS!>>YmHyO^EVF3PO!0e zJF)7!_>SH1giuIb^TGLZdaK``eP^ZyUkzJ{a!UT5zn+V;ExapBpza+`4`sZj)+3ai z>BGEK4X%+mU-!Io3AvnLYTTWEb%rj~nwrHOT~&#mON|6dPd1=esou)gEs`*(NLfqY z;lsQ+I*}vWErfs9n`4KI`X+hbp}9D%KSwGXw7PMedH4Y)+~FE`;2aCgw2kR&3YZ=* z=ym%zMs%V3H86CML^3$qxL|nMWHTz44D!=v(je}dioMb-YKG%b#sl*D(V6lmZwXk| zhwuo6lhRm}_e2@{3t`NFL)GDNaua%(wq8+ikGH~qvUJcBgxTV}5dqIgy_D8=e zz4#-Cyxx*LJ~30j&{vqk2=^W{C9V@@f-__SHo)=SX>CLgs5Lh>Hz~~pKNleV1rGJ- z+|%sga_#|W%x#q+v;H8CLm3a2kR*`DTluNpsds#tAv;r@WuVvKY(K75i721HtsaDQ zZOv9>`~nUijyii!y9=mi+U7`pItTizymzz2_05~Z5?1LRs=y6BTiWWg@_(x`c`MV+pFSh4KBj zzi~nnXt;UVC191sr@c|mzvJ~1$yVaM%0lpMPw|R+|6as=GKxNm{|h=tQ&Y5cxiB%> zdHE(NgkKN)UmWsy!LvCqZkN0SB;se>r zA3kt1-hO_r+8%;`i_Qy%ia$0B^6wRhNT{7}Ia<8hv z(sg}#8*O?)L)Vb-<#!*z^T<CBcfY|n&GWEJvP@mp^+*MQn z&c)e4b8us__Mh)JQtKge_URs~X%b!l%^2fSg!n|D$FnCAUxTv|Jmw=NF3}J6|9ryo zLHINHxvlZg10f0c{s6Rg(z%QCYsz+kn)6D+U+w(o*?CKNovtg&>9oR}91+*_pPv%S zaU9D2C^bhb=}=}HRE0uP(Bx6y?6@AY3()-P9@7oZOYHa5;K%{m?tK@z7@AOV!lfmT zmY;;}p9hnFq~Y(+QTm$z$q&f>=COa|ecyIfDC!|3H@0I|7v@HjZ{K&J5p*rM+`6Hq z1ANV^vNp}GMkYsIgnX6#27N+zGMGWIVsGuMDq?B^ek^sz6eC zVcqiileyZ}sEf6l-(Rp2#fzorT`%kgd?vo-;pY=T0+M`E5ZF6!4k=&AuN}(HETjlc zD)sLaUf_Y&MPILQ`1gYKLDlx}Zj}QX*>illk3Iu?x{D13z8N5kPWol2@p95U&CLBQ zNepF1i58clfjln*AK_;xh>UM6K z@QaNJZ}z=QI6aj0M`%XCY&}(NT`0S`>$kmRJD2$lTob{1Gv&+F317gT6h5!0xL#zi zZQ$Ou75S*$RdWSvkrFYX#pCGh%Qb)GtRQj7$!5=$R-Bg>&gwDVS6pIbxdb~(qnDa* zv=jX}7k=~w;{d8{*#2yT)hEQ0Bo_YEQ3vi6?Xpo*$oL~igbX01vRZN{x8*IK<-8GX zUG;@g03!w`9*HqJ)vYB&P1TDF0rM3}*AWx~A#j z7vv-Dg_Z6o2bN2-yTy%r&}h)U=svehu)SZBVc+)obB<&On3rHGkg@pi2^+`d7>PS4 zXaA?7Tg?SaaDGi$PpS1Nn5xm6#Hhf8O4V5Nh}m@x*o~jx4H+0y(a`gnR^{9QuqWt} zOZd~L;Jf7T?dP7)fUHjEVU4T2^X4?{Xyfpb`@KGXu-}2R$y0q!&cS#?>Eo@EI1XhT zrPlLcgMGsb$=@6Sah(gEuIiY4pjhyh?p9P~%yZcORR_9yWao*uG&z8=&2C2^M--4x z5#W~WpFgMm$FZuDwX^exFbooU&h6&K=zyhe-KAQz39D_B-Ovl1pNbBDD$PWu=T7EL zN-`1y8@jA@qwvpTQ0ft#DQK!K0W*qX9=s~lj#`6Rf4XFSMLnZ?qxJ~(pqne6n#Z$b zp>LC0yZWkT54A7y6uV2OuZ28hbvNr*a zDnD(~eAxk@UIW<9o`n*B)U9S>ZUYV1jl?dU)grE7%lMo+sEMnml;aW!D$dLy0h6&G z9vsS1z~~tkA==1s)c<8q#As|cTFdq8o9OmxG_n-aI2&0BdLMR~9@zw0F#iH7BHZ(-e2ASI8@#VOEv-u|^M z1Xn(ken8E68!0)Nv6%)|Wr=@V;yF(C1B|J?^e&IvCzN|8O@jS+2};XSSI^DrLxKu? zuc9gmL>f5yOY4{(jzbxDq)7%Nk4qRT_4+l8=pk9x-5=SC@xyMg=urOna-D2++sv)Mg{=w65^i2FyR?WHdwTp9$8P-m4rRL_M*@)jP1gG+ z-Q_X??8E+!m3o{lh^&jCslCoou^v5s@M-CZ74HEJTVnad#U4aU*hfq|t3-6w=H64T zV)RG5Dd2^WjtCgW^`R#vTm;*)H{|p=d3ub_s<`yOO(F?7%&r~xUuCf^Nt7!S($ zhOcw)+CsEBJt5_hx(>&o^aJJp=D_udWd&EZ$YShbQcDMgnr|u{m}oLa|68n49B6goBAI(mo}f}S@K2}WBnTE`9h8tQ}R>R z^eOH~uER+~{!PW;2=AT8+X|nd&P6Y)8})7Blb&>DMf13MbFOiP`x!R!!x=mQGmREX z$d~(!I#$zS2lZn?flT(&mh4_6D4BmFvbF|nqq`QrR+R?IFVi~g71@I0Q1;)b^}Kef zv$Z$-y*{X(h8x=-)&%-GtilhcmVsleJ_VxpdO>5S=q{uCpMiN=iko(7A}})Ib*ug{ zJa0~=A&YM^QG=`p8YPVj#TzKWxi~9#DG|QWivF=ZrPf2T7u1uSSg02(hsDt_%XoRI zL7kh){9K=V!2QQr@6Bu*P+yRk`kRsGAV_*})zm9f;vhqi6wt@VQA$71n8|o{`$ixn z<(^qJQj8C_G;p6J#~mod((JO@z72@7H)Sf`#=yE|Utjllc7RE5sTkD&RpLI`x`{V- z_;^6s{t#vfGdpWdu5kT`7Q((K3&ip%uwXN139$9t6P|*doyaWkkf!LumuM9GMLQbZ z3hov2U)+4ioR}%3z58xF{<#85&KeSj91rfsuUuZzstif_V~xf}4I&b?z$lP8aGzo= zcxcY);!5a1Us2SeW&Mq)_DaoeopJ(kbWqQIQHbRq{ebiaiAFd$P}eOs|IxxUOHQElm^~f0T3eeLRjssfSbm zvz*KEZy#<;Ux0-l92~!4$T!Q;VK~3<-1P4_R1K+q4SA7=#)mvxFUb#prj0fu3GZcz z=#6t-IuY*&lpHuqnAuUhaIvlCE*-Y=USw{Ap~x(!@6OnXWADnr(_6#iq2{H?z&vI% z7TXKPhZIay!XQx~Ea_nKF#i52B}b2nGjmA5Ea}f*7%^bQ{D0&b{@|5^$qwyMvcD3s z47rC%8eK)j{_ZDUJ?#VW&4wJe-U-8{o|(j^33^=pp^T&J{+k0&eiwZmJ1Ki@d_SAU1IhV6tkJCDJ-r0p)bLS|U0wyYA9cJHL)(r9E|`k%u4w_0 zA>;LC&)-9an~~Zd9r67T$~bCB#UbY}d0jPj-H-xyQRY$WHz{_w&XLC}P_PN@XP4Zs zg0-PTTV?#l7B?cj4g8F^TaLh|Z>;$2g<^4jO<6u0NgQ%Lb!Hd0;O$$6l`(brNN%hk z#~rMYod$B8FGgWXo0`_X%tM2b%`Gpi`cZ^#@1u+d8;DryDeku-`1?$hb~C2pkoA}f zYUq7q=Yr#hx;GyH%VCGe;|rZay=eQwBE9FoD#3Caz6H78OVKOaC($u)*okL|%o(vQSZ-d|rRF8#O1hR{Oz+7b4jErGZR-m?9+Uo0B7*#Bc5U3coIZvYt|NN-b?@ z1gsWdJ4>IgUr)cB%)yd!Jo+QFih;$J=ofCF3xsXdemBwCgc46*iETY*NaP;beA@h! zK28s1`IwOGMozZe?zt_HBr}_TQNfM-se-bYkGt-TlHqqid9lQzf{g=+Nl&>%c~1s< zxu>n!^;0R_>mdJeqU;2YL+RJ#^`m5eEA>7f0+oJq20qzV>Aw@h47&r?UmUJOVY@x;Z=ggE70bA60!~R8w z2zI+>oL93#3Y&|wUF*)humsgV#%t>GA#;i}!W<)IMX?uhgU!QjQW#x`+sQlrec*KY zwXRbStI#nYfBSraI3yY^s>#0XDYy`$Auw*BHg8T?OQ+WHJdxS*@zQx{P?sP#C+E{j z8>Jq}RXEPy%Ez^3X!N+3=q%^k)k^zKzvM8_x*zhv7kj`XKXcZ%Y1tq!Pmf{Gk9yQt zbb8g<7xzFv$5y&go%wS}c7rZmKW-mSRKucwN?k3L*O-&@{(w)teVH~+4`n;|hw_0n z1BLJimpZo2#--6>Km!x7S6!qa(vLUiKRf@&%+)A`lzgig+=ira;$X38ecEq+X zZe0Gct`%i%w)w7zRiVTlMn2~HR&-N5e5%xdmpCC#|7>TqCoUdP#<$Hg8O?6DFYS6h z*d>d7HxL)gJE}U%DNdJMo}F+5Iox6{IAYt3;L3{qw94IpUA~rhdHfp)O_}seDNLL< z=h$bpU~{e6c`@uUyMF#PIu&d#P9et;52gV8{Sr#MQR@-8RP`}bS$r6yyeBy|Ps z_4xg}8j(dv{BT@HqemwwKKu43t!fMSS|a13CX@{1)2`6dX|Ut`;BTB60}?RlGhFAm zdsyJBP_Lo09XxO@&gDj8{CQmw9EY;qrq)B|xIL?u3zA$2@38H;-LRDx7VjzN>}~4= zSEe>z@Lkjjv^JTPL|nZKTr>PT+!#|ptKyxz*532yL>Gh4TJJhZ--`f)e;mnRx+yp( zXBRR3o~@)TPLJ!%m;QG>AGMw{1MMQAp)>bY{#k!JmhR{LC~#D1`1G`yN6Th*LNg+t%GOJ-fon#FP2AS&u3K6xzF#4XFZ_P zULTqH?KM!7Qeg3_n?Hwv=HpN#of;(b29xe-6kip^mM;uG#M4#>^t9DlAMR@c{Gu5; zSJtx?`?oqN@+L z-PXT9fW%jv_rIDQ2P#-EKh&#>0ZbcLciFVgpObHNgt)BZcmJSX_&7Hw5}uRunRxX1 z=v6(Ozx}PA+PiF(K41Eq6P+1(JN%jMgn+acVEDwX6-8rQS}$&f*vPl>sJ-MDR! zY~pQjWr0nI@SXW{TxEt5N$N%sh>8vLBk8C?BM6eIKI;w;wruE;2F zjMMYC_9t_Xl%9yc8f+Eb*^%)1Hz#^iWv14tOmudScu<>d4|s3`+0~9V09Q8J^*20U zfL8}ESopCcTsXA;#i>jyB|K=0Qof3-}~Fu+si#m~sU??7Kx zl!Zxiv>}@;LHL*l0iS)Bx5pY^2bQ+H$GW-x_Z-EYdVzu)gyG${yJ;t_mqL_i@X(%l z`ga`qSt52WYXK*^^EO|O$_0}m8Aoo{yaqA-spHkno9C^^*zDc?zK!y;{=6evNyXx> zG6p&6x4c_ii@YznM?HVsi#V>?Dl1RDLc3^eGib(Th(ROe@(O+U=j$os&eoX%q1qB4 zr`7q%e69&_)aGYL<^d_J_eO@Hch3MgW}n}4@=84DRLW|)m>Yv^wBJmXpWq@&zqY=# zmNxW{ddSa#kwLCK*^Fz~O2e6B0>p(yj|~Yx9-p2rl|Lw#^?Q8!zR0gTJ#i5>8n?u8LF534 zT*6%sqq6}IJ6nM}ZzNb*t~X$3^$LdG^l5)qI)4sH2OM=72)cAd9TQurzsu=0*PI*< za}TF58~nOxO24MoL*~Fk!sGgP`oJ53{Iu+|OX14XsVy{1dx6Kg=4$V$O3{1#Nv_q!uaBqX>>$|&IIWJ4VIfa8GY&1U3o37l*F9(V8O+7`_TZwz=(q+h z9#Ga(YCTrGE0It=GaP*0&~H1&H>+zQ4@|FbNJnXIdA9K$&C|bIWB9tYr3;vcu<>Sh z$P?v*n)}2h@aqpK=ilt9?56j|JIi7BA~(mE7Kl2urI^`mSz30_{%^SQ{%4*WAlqdDj8|JO-t|k@F{~5f?4* zSE)+CCrkyW`M-*jbz#h6dFtqW7sz?$Hzli739yR`mtX!_hjy`?uT0+l63}9as;e4V zapgnV-(e-`p(CXtgz(`PptoZ?7T6im{9t0&eTu(lqblXI_H?O5Cr{C!PIKcLn_<`h;2T=kexgJGRoG0PPg zu-E$&9xb`hgS2eEO>A<;fPjs&CVM~wawWz*=Zt*~oQO=t{{0fT^7$KQooNSyM}siA z{NYdMzCPs-;&XDIa~^M!Hj~3~{#HIAuMcG;49))D6Z9VA<0q_^z${n{DmGZvBF3Y} zeQe8m5#g@$EjXBqe0`$67!BM5let&ZPQLhGa6%s|J&NI*)gwie8Z35Gz_MOA%01iu z8F-Ce9egX=4+2zNQuWKSkgWgZP??iA0W+qY5_?>I-g;Wuco**R;GX4}oxd({BS;bR zI)VajI)6rYcMR;eQtU^o?4%p6cxHm;lL9x5wx0()X=N>U0`YP5Z+0X5fnx8Oq$nnJ zcqG>&_u&yaOp?Adf`z>mh_Y^F1l}?7&g?A&yo1_Ta~}#5&n?+FkTiB27vCuT zV9`v*v(Gb;Ia`EJO>LMScfXH#X>&YJ4hv0scE`WA1TEA2=qVD@1H!H>y&KV1jpPM9 zTh1^K!&asKt&!3}^X7CvFaPQn`n#TPw`5#;L|$P|&QaeAnY9?6Luof^J-^BpjGKsx z!kl$Np9xvRq;j2J*L7a!>40b{a*GO0kj=;h#@Tu?@jLnvJHxL_da2FOWQ^7sQMC({ zag>{6H*&)LeW2`e&F}SmLqB;BF5y;(VRY|LCm7ZNMGev9-i_73es_7h=ISo6v;Upe z$Bk==*DDHjCN|;k&r#|jKPN%vkje)uzPj+KaX16C@ZPlP?9?JGeCeZShSTd%2BHNI zcaMT$J;Cyd(g*{V@g6i>uD9z*?h>zTP!g zfn@GL7g6iy+7D=akLb~_n(e@E$nc9KxsEV5{I1l zoo4kc?-$0HG;ElkZ~VQ^TQk8ba8+R=a^ERnx%f&mSkvxO-`&{)N-Z6px{r3j$NPnM zR}12Qm+fyH7XDQgDKSzQNeA3uxFxusNfn!m!xQCmyuA|tJ6V+FL#>CDKPFv9!?Vdx z12Z~ZpR#n9GS<47HRio;JL*B}lFpuKM<6@UUz{xq@S8J&P#Jn+e=TQ?_ac1%fU+s2TGiS(P+gGnXtH)}2Wr41hB<8T{hVY@PdSn%Ca8@t82L#;2EEn44fSxPOg2Gx> z#N9(%uk7u^->0S2LtckZ=8*Kjb7jo*E0UDpY2Ku44jC4*1?(ch@otw(NuzSlHe1LLoi zU(=XcgUFn9?*|oEnRlaQS`rqZ`!h=KF)>-|SC8JG+uU8mH~@u2s!W`V@NtyV-wj`Iz7)OeXKI#S{2HjPda_q7xF2N> z#(ECk=Yx6qEk9iA+W%-bYCU8-(I{;Heqnr_0PUK!tm_D*%PjVYu7 z`?Gp6p_>}e*sro42@y%6>{M#I@ZBjKhtjW?lPpG#JJ@5f5i4GP81-)F^JQ#`7@2$2 zyX3lyV*v4yJm!TVhi@ysq_JQ~QBOfwO^}}sh#ur|AdEoT? zjkC**+e1LIX13j-KU2GKxjP>=7iUk+^4(7LLCcn9Ln)2wH|)}V`_6Hu^e;jW`^l@fy_yY2sJl}Z9-8Wn6@3z%SH@#gK0dV z8@(*>;xO>}3QGN7+#XB(G;hxOi?8Y}U;kcL<$GGiC}zp@-?5m3GooF{^DE{Hj`KHv zvxH&`JEP^vI6CoiO3W9r`9K*)e8@;C)qXlVC0mP>Flf-Z_EeP{tkMnT)Bn1T62{hi5cA zT$t9a@4*~qG|*!#Bm1z(^!8SdJ%Vx@9>AMWnh(!OPky4FF+WbAAwIM=gnE4 zaWc+zn-VqyCSabuDI%J6!gF%`Z|@EYuECG%|5iR~vO_C`ugPF}TVMY&c9Vc)p3~;U zE5YSGD9bKX!PF-fEE-ahadvG$mfL8B^3JY>3%-_km>o&L>7k5oqB9v$Z3!6bfqkZv z(Tib@`;VzZV#@GYR(2BS^xym7%#Rf-{q_=^;@WU)@#bz&yE|F(u(v)jh`45>XWLF3 zhq8RcNCa}bz-)w7>#zGg-z$yQ>TSCqhqb^nIVGelr$;}jF*BZhP2olV(fkJCfhuaY8h$O*4)v9B5J?{(e|?8+ad z1;w$Vx+`NJb54MX@q`wh;yx69sQY4@P9<`jNV{!oISzS_S${gD_~-SBa%2E0J+bwZ zPOz#ZoH-=Gn_oDsoIWr?sjX7ye|i^y>?gN157G|+?nN&(S9GU z+#liXMyZFq-k&^=bfI^V=rMsM(8K6-@Z^;xWFDpybcZKasS#PxZQ#DLzZ5v_C^E(9 zyV2D1y+X@qD`04?C1?Ko-|LJ1R{2&TICGwW=?3Q?+L`tlJ^x{N_Z&wn*mmiZ+rsvCbbR3HF1OHC#B0mM);Dbbfuyx#9ub|O=ggwz*IZhGVCmVE zW8>-IBP2*%8U2d$x4&_6_=?PT87RV(mBM+xJpve6&ywhd)@adAuvbynTDGYPMc+Fg zRGUTh$dz84bpGWlNf?Zb~xDg8~A#33hyYrBVrg}GsE z;N?3`mA}XJ0()A9HagUzLz3rp$78a=uNZ;xBw{bJA9yOyJ}OV#Y?PmLBmv(arL^0h zo+~)C|H}2V_WYQBsB^L=dh&&3t*@;E1yuWq(JLB!nDyq91FCNiRr6Gp7ldBG{Jc+a-zJA_k6pzxp0Ulx8RRDR!e? zT@#L@TbhtaWu2XK^-`j>?SlyqlU+CtWqi{jamaC$od4d_L6cK?D`1Dex(~Xb0i(3D z)SOSYPLJ!FeZogyqn{VP*{ZvDf>GVZyBFE(;NJHAkLYZ#;5d}=dJTy~PE5Rb?|f*{ zfP{+zccO(x$^L-R>JJoGc=Q0S`x>kfPM<;l*ThHdcdL<_%3A9&K|oBcJo+Wb5dT~O zW%*G59RZRac$p?tevP6A94%pRnz$j1jk}lCzg;<9KYLpy)*kHy@26hqh!f|hcSzSqq9AIkC}2_?%Rr3Bc<%+z5$tO19Hk1VOaDU2~++8=6U*@0*j zlT%C>*CN-bWBJS4+ra9V+XS7xGJ(tc@JA)Oob%?4b(w8ryey8r9k%4n>Cu?}`*8of zG~iqu_M{lcb&6s*4rPDzPkKfw!@s!;sKQTN!R`gixv}b<842?Do50HJpQW1vx&f9A zQxvsR!LEuehvo0CA@1T+mRMkU0Otpkb|Za(|6#XrpR7zeS$??E*~g~t7e5Tl(;kYH z8vy&au5cgeF90ymzkFR&5VE&R+HAAy49u{x)Y#IE_cuxok&58G^B_<5wb}Vztg}A$ z@W`$aba(sDYZ*3~NX#sYdu>!Vg1&oMPHRq&8)d3}Juh()KWTgo&3TP~evY#K=u&ZJ z4hfKD%l&&NN0c$0w^qxRpH+m<3a-nj-)sXGK?>hOI%^SM|IIePC(U57^~dV(7xjrh zwdL<$b@TqC-PTfZ-c~Cc(ca{Q`%=GFFWn;s$y~|p^|=p?Fd(?*C(Fw(JwUlUb#x!vBCl2*Alp9^-ee7*ej-0yzED;{1cY~M3r zAe1lBd9DQQe7VoLIJqAke7lit-(xP~h{zV%{MtkF=8(U?a@L7wpRFaKNdN2jE=5Ur zt~qCIz{P$*=UFdwsqQUM4OqTwLrDobWw@7C&~^l_&_25-{0V-3htdzI^^iFkjiWU} zKSiNzu-^TCS~2+M+p(0(pdXoB{OHn>mI(^QOV^lmmIC`1A4c3vpg%V8o{anFZoNbIZmwz?S!*TvrKH;oYoGm9*v2&|Uwz7=<=4|MF zKJ}!s1#P*)c;l>VB~Z|5uhP|MM{i`>Ek+Ag!fN-e$$) z%7=oga4t?)M2}Lve-Ta(WxMc)^1<55nbZxrRpFYbOX)(MDv&i+Z%>|Y1xSr95tA{^ z2l_3vIuBR(p@_xL_Z*XDfV{QtRVOzW&zmD#1J1DBP{hdoK+}j_Eneu1=HlqW>i&># zT{sS9KZIJ(YRdRwn8?;-esZK5)eatc^GT=@2`pq* zt+Orz4qk-|zP5p1E`9p69&Y=Y8Ji%sFRjSMzz zqMAAq&mT|8UsLfhOm8vbJN|$A|C9x^H=+N!iYZw&?oSzHKf{gTdc@7e%4WZ%#bL*P zcj11C;0OFKQx3Af&h~3oDrIv1OjL|h|7`7@Y>s(2+u+%AO>NLm{m1#!=k?F$MgQ&# zB>O)#xn@vPPamm3{sErSQYq8^vx{fS@$IHkm#O~?dGSo;|BsDMEac#+V>m>>l!|{| z2$ebumHOn=?>+wgJQWtm>0HTw{q`jJU}7}+pE>;3Z{5g;;br_ke>J`5|7ML-!S3JJ zv=@;-{73R%_Bm#M=wCn6{Ey=)cm3CI-47o7_Yj2g!2j6(uiskRI9YhuSnogRa>UJf z>Nnc|@jJ?0>{E_I_wNo7Dpxlb+x_;|R63Lg{Kq!sZ=5Y$j@er|&D1IV|2Up<7v*mk zO?mA<^@fIq=HD8{NUmqJ)Cb7!T!*{&zgcjSz5Jhp0|M=A4fS?xq@KzF_kCw_eWm36 z|LyrLkmG*)i0-%~R<RErwU{T=QIW9RbJ~ zKIz1A7HosQRi)>L3LwpCq!>%;02kxWzKxSD2aWxKpR|fMpb{22{?=Kx1dhY>NmBiz zXS$yJ(N)+g9sXjq`V5E7H_3uVnHv-SkhaS-y&i=4d@cF;s1*vR`D6}ymO&k_eJ^({ zUxJd@xg(ov9Tv=)J_QnTQ%k1S=b0LExTN5A4+sCeoaJ5a>oWEpB5>^fqUVD8nUMou zrExvji_jN8=-UO6TjTm`8=5M?-GpP?MLSv`ACKTQcb9swzew+8dY=KZ?#i9>D$VZ+ z9D54RKW*$kFT8y2l%F3qj1E_t1Be2+o$@@daX=^0Y3)xE)?~`{-;!1 zl3!hdT_kN`lL1xq<61=6Ur29G4ju=l^Tppu7@Nw@MV_DeuzQBxhxiT&VcZ#8`BW`> zU~&8hu&ON^=D(M9c5KK3;S5eI86uU?{Ku|?0#8i{oP*@-{_%t9jyRq_o{}&f|A9!O z;$@f(bp*empa`ZHtkinpQ5~SIf4XzOXes=3AZx?xuWiu7#q%6uZBK>OJr0Ie= zi9Sb8KHV;hWn^YelpmJErpcsvIjZXn3%J#-2^`9LI$O`!R$ex@TxtZ*|HMn=u-z>g ztYw_PP*kJ_1g(#<8av$%S!y_wT(_1%H@YFqjRAtFv)9!gX4?Y<4y7L~p3ZRQ9f?!O zbmb|8IOj-?b@K0gc8t1KKY#y@eBiVDs}fguH{9HA%ub(@2lX5-{8UWsL2kuXyjy$4 zaZb*X8N&2NqVdrS*?iI%&M_YGFH-bTz-VOS8b_SILA#A_uS^CsLD{#``%YYJh2i0! zb|?8tpv;j$)awGx=j3qA;*j%4UL{@KwZ&HwIcYP{_u7>i6NTNbUs<~W8|HfIigpE1 z4B4qz|M@L6vs`Z#Q%*u3Ewh}otu-caDC;TbERM*fu9yA%Js>Q-Flu`j148Cvy^|@M z%a>Ha{041qzrlQPBJb+<27^u*n;8|yMx};Ibcb%zjMpM?DE;7{{_sC9yq->fA|acv zovkbE7RPRdmcwJ~#1Z!@x*NCD+W_YU_vXMSRd91aj*q~#DZl9RPTQqKLO<(qTsLDR z)*nhe+|wCT;g<#6jXy@-gHZquJOlpR%Mdab>v9*q*Zk=_;5ArNBWGC-tO_=sIDD!L zQau+j{2Eumq?tu+E416;V7l&; z0{9so`sr75i=`3n`^|Hmxn?c;Ynv8-^HzHThcX^4o5k4}DXF#RwKB#Y5y5TwdM3^x z*=aj%3zjrMhc?!Gi8|Hrr-5l}<7hKjk!pK9rc4CQ&0&{Rv$i2{Y{_~56L$p3+ju)P z{gH%hkj*d=WuwOqd#`$wS2hXaM74wUk~@KkH4l#reI-0m#QMo%JP)+I9azq3XN*<_ zjCkMNW=Y^smX8pbLrh4>fnj6gvOx{x@?gdh{c=gfr24dABug9Mc8a>Q>1aKO@uEu| z_*M!Q`KzbwO=Ls6&iNe<+<$gX&T=AvO6Yp*zDaRGYsx9MNU-_k&ZM6Q+tTVjY1=PFO)vJm+^B3p;83>P!ekEa2XFhZC);fl5u6jU zLJib=S3@qz@%21s662*vXs1q%1ANBejBC~AE*oRcF;=HLm} zq#Ijs6AQ*7a#3?Ig@1;}urBPqbII~+(Buj}4)b~>!ICH$9vQU^?M8Cx%M_!|Hh zR9~2>)`5;(hx+&$Yh+MME2iPjD*}fy9!QhzModW92wPCc1uH&8FK790Kdwpm^=zg3 zq+=U!p2*?vIGPRjB?B4be3*MZvrbuZOg@SHa}Aeb<}vq`~y>ru*@f^=Gyo z-N&WCnNDSfW6F{f@=8Vq6S@^GyV$S|>hgQ+2%2bwBVQs?_T{Gomn%A}9a|H@2FDz@ zUP*Sr9I_6~Tw%$#v#lz~h;Yqn+XBgXIp(>QCaHY#1kT^e=Vi;H^N-_Y5Z@e+jBK+% z*Cl0NtTSmHn!)9*&lR`UT0XS`h@bKtUof-0%qQq zhP4XG?vO_8z0xA~W{4rvUy!g~YM1-*k6Xa?LiuYewX(oy+XvwzFPg#YfwAB;CSR~F zW-$K#4n~5Wzj4U?C%*(0bh)QI-DoWr`)}PsWa85yQm`}qG zX{5*`_aI%1G$so!_@4G^fU4>-$K$-(;EfwM(k+wn;kZ&$Wk*Rk$heUzZ|5%gzvW;( ziw_sFvn|J-m9n3*sTDy~zO-kxpKO8O!@SM}#1}(9->P317d3&h46*Gm#HG-LGt$cU zJLm3S%S{WI-QU5IP2U)P>sf|bGmD(kwVL8w|MqCBe2I?+eb@OOD9TV{xc&CBhNXNi(ZJ^8M15cS2}^e;a475P zYz{epOxD}i_e-oiqF<`CtK-pfOl~8|6{Br~pO$9c?M^5Mb<(t6HCc6V1=ZJp`+jTC zUpBtGeyYu_N1kjqJfVR-@M~lT)GU4A;JN0ks|Cd=v@!Q&F`HX)O7jBZ*D#CShfz z>#uAu(ZI}>o;2I9T?r3b)#;70bio^4kFP#>_8r=nt@mUr+=xmALU)5nZ32h#`bTLN zC#djZsP8*2Yzg}am83%>xjvAPi0^J+*R8LF%wBn;*=HKTQE4-o3j1cz%_zNtJ5d3Z zD;{oIEucW)P}*(wcqM$@bJyyK2r?mIb1p^w_e}W&>Uba0sx1RlnkTmJR5S$|o_DXX zo$dzJ4rz|NHz}a5aUa(2;&+>qqe|9;_YcVSLIy7Jt@?_JW9RM1N3W(!VLLWGqV;{& z4X~w*t4>#zz$kfPAn~Z=&K4>S^yRhC<_M!51P*1~AY#QJuwgQ0$7O#MdMgT`^*r zk@;dLK@X)L;A6u0^$(tZ!nsE-Qm5IG%5%w2`n;4dZ_t z`ep5~#Xpp!dPoaho!kIAREX^lrC+a_r3dHy65Hbvw74H;nJ$0rORtWc9PwVa@pcc~ zGA`tnEAt+BuVz@wHINU~EFDYQMETK5V@DIy7(PP0rsS-d#cAJ~uRC5Sk7ujZA+xHQnR;WO7?$tJ$b~gox_OyA{0LwAUU?&*+zy(vMoTrGG{Dyn zMee(?siO)*uf=RH68rI#@tW))Gc-eX(^A2cya)>$Bj*D)CT#i(5<E_C9&Dinhnc@Qe)&t<%4 z`DZwOcR3ZjhGXFtWtZ>akJ^Ez&XdGU`bK#A^~*aqkG(@aY~hjn@q^gE{u?Ku(PaCP zm_fi^>pomt)iz_lQuOWa9GfI~ABfcwR;IMuVjrJ@05Hx;Ii^^uVK z1(!I!_ug7C$5K-MQA-y;){-6anqGyl$kl+lJ|ao++R4Hy0Q-mhxcmkUGDc1}`%GH3|8Uw5fk}aWM@4 zq1`w>*a3QvpA;;5QvtNGcNX1>YfxWXNd)Wi&%rMCpgKGr?|@adZi4UA(@ z=DkM<9LjjGnash*|La?7wlVDF!KxZ+`Zk8};5@|hP@(ccoo8Szr}TMy-3GX}>tUeG z$_~J>X~+HC#d_#HhmlvqNyKq-O1o_#bMPdYIX!E(}%)tnK%6RDc^bTM&!hq529O$Olc?Xko%SJR*s$nhGp1KBLiB$~f$NZ!|S}EfE52ask zBijr2w?}zN)gLr_AxzFr3K0;+bzvK_IhaDa>)<4luPK{w3n)74d4H#8Eo7&8MH|em~9R0@_6}Tp#D~12sCqa^QqK@2%qe1TQyT zed{IM4ZfB?tr+zM=p!{Zlky+L@}cx=U9uj$A9DVZ?bAJbXt2$Wqt-9(Pqh#KzDUU8 z_~E?mzcPWMGJUe7%T2h}*yf9$Q7>?zKj2HHr-D|v4PNzLUaAF-uIBE1OTt^())BG}^s_)@~IsT?X!QIsS2t$V-zcC9nhU@Wkv`25BECcR- z-F){q)x!(*7F_0&tk80WnfN;C-)R=SYhNDp10^+CpsufkWvByKn$GRa=_( zG5YAhO#bn-Hos(^sUqLMt=7@A{t9hh-&NH9(FIuV|JHr~qznjV%g782>7bnHa8LDa zB8ReHWjc#9`0$2e`%5ltdN&Ev7tiPp5l}{GZ6EBbeUS%KMFg@G4%ETL$(!pDwGL21 z=jM`L&4+IFI(fa})lq^T$~d}*%)#Sdt zAorqt7+6-tr+lmjmW&+U9jPXb@|TBh;NHETz@dx>=CkzRI!271X=JMxfb_aMGI!|Z zk&vL;exZUYh<$pRrF}RB8n@mYJHD$6FtzLUTXC;J`(i>xZJ3GU%WDZ_`5+W3(FZQZpBRTB3c`sS8>90vh^wX_2XC&KUf zzU!@cnr0>X(x%S|X`j14YDwnc$>CUzf@B6xOz@Msm5m?!3`es^y(RiyHI#T=?UA** z4$|j`M}64b3V`(_$>QFWzunq0`C6Bkpoh}0t#AN2m9W)1EqYZDnf^$^M4}taE=s6j znl~*3&uz{CzIR9_sXx2mp&CB>;eBOLrmP{bKYlA3vsOFp{a?lp@O}Y~6S*xHd0!2~ z>kpQ~aVaAGh7dAr6|$Fxt`e*|*d^6?p#%C*bK6GxlmZUR3g(baLrCi;#-H2hj}i1x z`Zey)IA_m+>kslX7-#fEJ6C;J6)AxXhPdK4#ljoo)j1TPYe7(Suz3ZCHuFYpihN5CyHO+OX>m&bvqM;Cq=FyQ!}k84Y@@h#45}JCZ0^54?uKzO{@q!BC>I{b45WN5rslU#c!cWj>p*;sEww8}P7w4^#selo{`dzHwpbqsk44I0)eU!@ zHJem1YtCPLOn>wOsZT-N526aqRe=Dm& zMH4hGv$oU_D1=c*z4va_ZvjE)Bd&yXJ_l%^$H+sy{{<&}--p2Q;+cA?c~j@}5oJbX zK8{V;`9&`BD+zii{b079fk_*?(|uF(9m&Bk}qBh)3|&EgU&fmv{*b681$DA!c{nTKQQR(JdI&=vxRG9JvR*TqfQ~EW_bcQqU9Y?pj+n--C!+Db!f6#?l z29a2qbf|w<2^7xOWv(6T2AOIf?40DCz<1x)rbfYY$g`RcVk;Xx2=VQ299)Oa*26!n zTDUQ<0kISw5lU6<~2p^rL1nW(K8C?fokGp;iP6net z?AN}DE(iPKWKykpyP>d^ST;w$Bm>C$1N%A5BrS8C^x41AFU3r+-@)-pY}iL zu0Oc~`@ON_n6+O!d|HWkb4b1i=jvz&gvJ|SvD5|kbqpHl$H|C$4PwOcYf3$wl-W;h z`|S>Hzcb2*)Ti<`$BVEd(_fRY%5M8i9sexg7aMmg@OBySG#{gK&+3Bids~)iF{z?I zhZ$LRD`^qzMyUs1kA&mo*}6BNkw3wX^hC$jayAU-y!TLVyQTaVESEd7jESlQTrNDl zsrd?-{Ecf^%}t8HCZt zw}ET5f$z+W>cPr7_)uB57_x@?NE&WwM;O_hYL(8o%&BK7Ie)w#cl|CpM*UJ9IZu~P zU*I7>!)fHpwrcP%1dMf9@>=h^@KETw#i&~k*vl-$v)_Urc@(QlvxGHt!5s4WBV&0c zOO$uWBO6FHx2}|_VDoXV@?F}f!~BuJp^Q7T^~C(luOL;iBbudLU&6MJK-_K`5lLwe z>g%Dv?xL3yzTcteH_p#1Z??mOn9n)URuPn&W%RU^8W*eCe8$Ca{?U z^-A}inQz1_O`Ev89D1SO(!$GY3*Ny+ofj%zDW?J6fV`rZ)57RVIo_v>E)nO0|Hi@3 zzanCZh5HG1?6tA+$^%NO^KvZB)@%z)v?J)DtUt5$;N|lkYumM1rw7X3UA3sDO%yXW zxSzYprxmd3kA)vLeg|G(P+qjxy%~y>9kOIv%!t-db3c6?ago5GEFXMb8rgj0goM<& z?}>h<(g~YOWhTZ(q_Dk_x+`_u+u_hp=hjU%6>w2%-0{5bN^s|%?TE`2FGRKaz0Bf6 zUJK?h2i!4D;Z?=Ry0FotW104o63BcU>niD#lIr&a4rRQat%v2>MYHrU^%;(wNVNB1 z8CgW?-e(@k%RR7}q5RF|kk4Sr_}9Ceit|9>{>0pYp2G+i)3s+?>5>-A!F9+?-jl4! z(!gwLH#47Yo1YV?aPM%gP%?o-=?Am*;2fh8T{ZrPs@UO}aN2X@a>(r;=GL;RjlkoD z#YVr8TKLMDQMhKZ5w^(~kmx_Y2MiZukH#`hE|}x=Np$nK?wR#F*j~%Z&qe3O=H;wC z$$ee^8v~&q^0)FK>p`|1GjG4jEsS|Qxv*SPOcwDG^t+gt+751EJ#nwrR6_bEl@-lS z6@Zte9g*55hz`% ztHE!RShu9KO7KE(t@8PhHmG0Jn$BcHLZ51#Y}vtSMz9-YJiymml5ymOgs{fvvwU5> z9Aor$J)ti}g<*p`ZL5X4K+z{fHxY|!xOTYt!dNZ_m`-Y*3A!_eD0B>;s|%|na47Z6 zwce6O&_-j~RXI%et>A6lSSoA-Edy(PVFxIf8tFONSqBS$TzNM>jDfx|{Y#DL)Hv$ffmD@t#^&F%s3K z#oYv(U+5ONKS>AbO4~erUB?&95llJ#bM3{M^_G~+*(Yu`$ED}x+!|V%eS9;^oc=aj z&w4sf@1j*R>qjwRfsY$rsLNrSvnDJOOuoa@2iIM(lx>If$6BMM-5Owj$wn^(Oi<*nrNBOyb|k@YLzv;t&fE!e~Q87BOy zUc)#BX2MM9o<~7AEzrB-o8|97$pp=Mw37pFG!MWO_FVd$KfmG4pjHce^u*-*+CCZc(2)na<|+*VnL@y z&_h{&q{tjRXHp*x!x z`G-T_Z~HUsLKir;f~~2R^a=6zbE9gfU!C4^Z{Zv{*C3j^Mf_Oi z(biQP*%~2kKhnx|Tdb98;EQLuf|rCd02NC@@#w8iNKHC|CSPBHI!9iYr7&Vz=IH>^GoqfWabAK|s0#n(?rj zM2bSNNRT=0-1~9ly<_yijZoz|;{m>o3-{|CW?(4i*D|ch=c2hNDv#lI`|LU{7P~GL zJ~Yb_U-za8UiK|nEiByyf30>g&&(7;q4mJdJsxwn3oB;X4aR*ee-io&@GVNSQ*@KX zImnBKyR3dAsbI^yi%i!(G{T=>5sx#^+Cb#R8h-Wfi%@m#M=gwM#PK)Ec%Vk+;C91$ zzPn|2bmQfZ*giO7ms`t=@EtwS=vmol8l92G$ zW!}X5Whlof)Ms(N=#0hMg*VS|Tub&@E4%O_L-af%tc8`ppYv*s%a>*trQ?tk+SmYY zTvwBKl6{W|NCg#6U`~X1@HdXt2GL@3M=9imhKV;1U_qu&V=9i$!+D%3u&Uq9kHDd{ z+iX2co3thAf+UdO%6qi;yP0P=??O1&`6_fmvwedKZzYRC^8TaeH|(u|+hq?-&|3Wj zKK9DRysEDk%pspYrs~h5ctE8U$iy6_qmCBBhD)V0R=@3mRZ{%AIqOn^o}s09d3rYd z&SDYG(4dWGKRV>YO1qK3q3kDWOlLgve$HX9QRu)TWaI1pv^+5?jLbz$?W`oWrap(Q z2BA57el)_|Lt%2!zuEve8MrBXoeX;Xz+kF>#@u>V&k!g#rq-PlT7`z1aUuJkcVsJi z$|FK1baE_VJz)2~?=(Hy8DPh+sJAUu-++m`@dpNvK2VWNI+&f*NYFzW56E9kJ#~Wo zx^qxn?D!d=oN03l?i%O8z1e&-< zQFGTHa-f*G&&cQ5@n$nuL1dar!dBXGcs>-6L2mWm7{s`80sD~G=_1-*c*novZhY5M z*qChgtwT2*@oBVGuD3cz&_mh&;OlDedg_uN9N+$X0v5fzOfOCQXMITfj;s7fsB6I; zgF)H4k~a9j;k?U0emV4(R}KDIA&&~>DFzi*&wYKmnQShcL)L+$72V8zrKNzK{NmOu zB_e`!4hC>EFxG)8Z@TS@VHHqD)VcTGmNp<^(qMI0O$$*H+x6?`oeY8=%JSJl=HQ7# z!4HoMch!;idY3JGNB;Cj^nSg0DQHv;#MmE)nog7gj6^k@o!Jg|`;J)JHg7sf?QjpZK?n5Fo8o^f3}kIpJg}98O=VlEJ+Yy4~!N!nHm?{zvqR?XP0g; zet6jf>_`w$b-a9L!+05JzkB+4S?ez3Yw9hw96I8>+21(o&0kgkQ#Hg+ZLrJJqYjU! zn5re)wPpS7z=%^mZBV8Fez(*jO|2=0L+_7;cz2#fqNSB;c-(>rdMNF-onS92s`!MG z6>;Y#K;Rb|WzG1R^>3KtaVrkBB8?Pp=mPRHo9kA`*%#BQ+1_>KG@c|Qh&;peC?5C4I#X&5XARcB703JBUKapGe44p0u(G z6Lo~1rzi3A4ngE##^!>GVh4D?=VWGZZ8luBJIq+Tbq#7yS6^!)L*!8E z!JoB&_jg3GUGWo~%*ajVoyL6Wo#gOG!io;ux-9O|1*rQaR(t<`4z088uBR`qhW3V+ zj_2AcqsO@)(+OnFeSa3du4Z=qK}T05or$8wz6+^XoYLh(B0YI5COkT!q6gnb8NmSH zr!?&K!?F(is7?Ujw;IZ%F6`gePwXF1)}LKuOW}HSccwk<-mZX6?xaqgf0WOw58=fjTmpnQ*}VOU)$R7qY_Y02CM)XMmR#9uE* z4=LK`K4Uyc@Hfi-&VI5GynIyBR1aRgGV>e<fqwh7swDuI<@B!aJj9F*lM@ zQ=0;l9l!2>eYjz2o$t1|rp9()aD|2EOREC9$+MwbLXx-+l2VW5EY8Mw*Uwf5XRb?; z(Xa{o?&Ts#+@rTU9Axq!ug>^E+KU}v#3kSA?RYJ`u|ml6eY+f56!(nxH6*U9q3n-Z zlR3EErazJp!J&%4of#U~joQPxX`~VG{o7{wqOMkOZkWTFzn}r$f&LMzlM8{u?=ua$ z(URx^AKl9DSLR+fNX#D>M?#w71<>{P#E={NyN~5dC}3Q5%fQa5b(RGgTtZJ+Q^9LJ zIiLG>t-M2({TC;1TnK@;PjSNJgw zZXsz7$5xP4E4<|)TReEWi2}O2iNV~nNotrazodel!SfKNvU4AL* zSnSiQzrqTJ;DnX^ryPblsDIP=(w0}9(5n6nL)aK{m;AFr=Ux>N{F*Wz zFp*7#$Ag0pU*xAN%+wp~mEU{52U2RtyXqTPFXj}&;zgP^p)8%SHD`rNS7Rkmu^!u- zWT}asJoc#OQ}^8UboTt*Mz4yXl)dWMcMYbWs15@{)`zhCT4is_)(%gd7VDBUD}c`@ zTOS^LQ4jR$wzN~JUPYWr&MM{cKPKp*^y?*LJ$Q0y=ko2(J!Y=wkqSlb*XD|f<7M0m2Y^ufusbT)c4B;@NlZilQZk}k}A6xK^?#y;p+L!39G)H8d&BtyULoM|H$!uc&=TYk6{HjO7?R7zr-PM)j*V||(@JI^)4 z^KAy=GSUsOs{FG6^>tZPho{{3qcib7e#(9oFIfjMAtArstyp=aOahB(*xh2-p^kV` zTUmcwRSz;V*4yt)wFU-b3>;sFJAnKKPsX$=CY0mk{sYNe#C{^B-S~(ADof$;)f4ty z$ne!FRJUh8N7h~Da_3morHG`FX7K`m|T49+{?{Jp;L)iDY z$XVz;f<7`i)SGv9C&8~N^(>pk@wZX7eELoe!Fl(ZDx5Yb%Obh%@7k8p6oF2G$LaJQ zZ9qQe#HUxL-S_(pSG%7C?B5Ry+4cDq=V< zDE~{$`tnyWoa@&y+n4z;&kPd-aXmopNlnrgG#2Rz7NPGL3nOqS>*@08jArH+L?q)g zPMeD&=pMs;$JG>OI5Ac>;-BPepv}>+N^$d2a4+;n`L~#M$TMU-#Ncube6OW`bSs8| z5U>BnDG@hnB>?pJ`Po;sqDoJ}y1N@c$_S2J9%?tdfX z6&UR3{6R__+ z`#j}D9SEZmVl8sHh1@$L{XsydnqW7|@)4Up@tNy-?rkPPk}rp5`k~GBK?N)B@*;!A z!@GHw<-^K|wOOZb^*}#kA={g#7%(WY5$DcbiKe<*KYH_kIA21k2OlTL{ou~p+{vXk z3THUazkE(zdxZzdVw1f8Zm=2Vx-K5`tjPxaw*^zeSzF-m=1`4Pp(f)(%8|mJcWDp{%Ep)LEW)12nLRdtXN2l~VZi zN<>-0a0`^zlcT=c)c_5wn~rGqFG2%{Z>`y}f9`QHxmle0&PpcQY6V;ew%11zJv?Pc zqhkT2(i2@^r1|0YWie$S+=am|Wlb)uE4ew=y2}b_TVL=f`qB-89?EzyoAdCZXK_Ff zH&(E^!+Y@8jNi@6ITM>66W|d^;82#&Y|fe$tvlbbx5Iu5)GU$BMD)zGE%shT@Teu=uNp# z^I`LGez7zq#hdpK^ialw*?Mq}%x@0`e=eOGVOmPiuN!WE zUM@~(PaURD`rqgC&DKNPA2k%&xB|g-?J8T}^V>rOagNIW{`6ZuWY+ZP6OZkI2X;2{ zS)@fkJ-)pL5w!0?h+=fox#Wd&@chLBxAf9h^J8Vc%TuZBmt$64-WO0*kX-&n1iLT4_HLwk zCmgvL)!kE60lybsNs_r*2!pi8Lxn9vK>YZCx6&!K1#|RuHlW(?xiGRWjPLqnJkx~A zyqt8I&RrP=YY3db)l-|zdy@A0ES=%pju{Ucs#d|gujzd+IoJp@zVxuimX-s}nq}%1 zoz39+!%e2`#xH=N&d#tazZTBHb!h52d`YO2#4vs<_NLe_FdygJNXM1rA%viZvfZBT zZ#YM7z5LB`Iw|Z5ht5#(`7R)Ms%B_!SsO6@DR6`@^gXE3p<1@3rxCI-L_DlAhyiu{ zuX&?Y&;@g1?0@?AEbWEn4>h0n_^z0-U+0#9rJEAUK+Gqe<>S_M@Y0jgqtrLr;7b0R zAo%$k5dDZ59oL(CUG!`{l|P|#_|$z$`vZmD4h8<00N@LFPlKaltcW8FaNw&E_3lRk293H2LrLjV+nB`ln?W|g*$8J!Hm zwbxnn5UPUU`sU*#`6Pu`weKM4`CIvn)>t<_Es;hf(uGr5t^RPLmT6{XHKc*KJ6+h> zSNTwNgGhMXh90Oc5bwt-{0>Ahv1<1GUN}ca`j}Q?ts26ZXY|@$k`vd1&Bu`wzGc>2 zu#=$YZ|ycJ-lA}}Qw`A!e3C4u@Q35Py8VOBq6Tp1)dik>_eQ8LEw&eJt_QwCZ*A9$ zJ^)wrGq!jBSU3kS568XN?L_y{AU6bLxK(=9u=zOmJ?P&hX&Mmp{LSA&{1b!Ojc1a44sTv$ZQW}zR!eAq6IAEtB%dm*-Z{Mwz|55RNF&fL1; zR1p21S@L?!O7wakRqiUzL;{C0UMr9}#Ds(-^Di=@ZdSk&?*1AmJMbIUGps!~tlA4E zyNz16ozH~brxuwxf)DWC8ac=NnZJRuW}nctRb2!QrQHICW!(mqR-mV#e}K@Qw` zufUOOwtB7obB|NZ=D65l>=l7>NJvzW_L`;4IB#B#%+Z^q-On@#eoYy7W^;01aI2ns zD~CiyX^xhS%-9dv7Ex=hIkj%kxBmFgqbusc)c}hMbTM znA7G{6?Q~yW}XS*?n?go{y7&mABVfYsr;MmdIE>C{>;|H$?}lD@y~Nqk*^O}yOq5- zv7Ge0>nT5~!Cp@O;ojCp$oSc@R*I(yUVM2Y?q*65sNOL8s6T4qoUb~HmlmB+L!z`b z4lI)SGk!TAr$8m1qs)2(K@Vjdovr6!L&vS?Gir!s^y^)n8~@BZmsH;p4Q^?McT`R; z?XQAxkh6T3?05?(z2U0q;_44JGzy>Jb86uns?F`|NnPU*udh*$Pdhvd9svrxQlGE4 zUj`nu>V11A{FH8{{&2fTBi(XF@SLVv#;%x_vkysoD#pB2*?&b$vXi8L-sQo-0A z?=+kCvteW&<`&5;uJx%E=1BJ3<%_R|H2YlIPIT8mlTn)VwmMs6eZdM*DNEwGC8gbz zX9&|9s}-{LoV=}ueQV`&kh#w{!&!AcDCz-E9ZcSvV&UxC0i|E|g(@iL0iEU(TTV&o zA$B&0^HoFVz7C$v!OuTeGnw`Fb_vY=$F3VW>FU^gobDB`%ry4DCB#w6cs&<~9aL~T z-IBxvrH7=JZBfHGZo9RQhO~jT?^JSZSENAFA$`mB%bI}M=e^A@TcnZklBRc$9OiBp z=HMVsNr9S|%#4T3%hBu8I5BXTl~7M9zlx@UjZ!yXC9=6R!E2F7^p zUS{+bLg^dF1Vm~(;Q;jXq2;Pa3}j-Tv7RN)|4{n13fZsm`AwI?jIrNqK0w8*{l5dN zhRNqg!r<3H`d;pK5K&Zd&ERzvcx7l^dE-JQFxay?>&~Uq$aY%&8#hIc6YTak4mCsX z{#UB)c>YNCP+{JQU&}H5s9(W(R&nqgf2#-ka5qp$ds3LuT@3Wi4R#!f+ku)l7{>1W zLVTVOWjs(N>%o%|^U|0@r~lk9Q)Ztln2d5`6VIsc&=xmAqbHv`+19l{b@~)_c&!ln z>Fj3gs4ziUNPPy;eZ=`~%Ij@>9VE{Axo(BF?Sq+dM6A7SRo6)=KFqF}{^gTC@K#C6e>@nAM5TQOrL{0aF8HxY|NB>~TBxTvg>!Hnm!2J?_GD7Uh8%MaSC6w| z^Km-tS4qBHe3jtWly;k~2j>Wi`?R{Js9>u;2Wy(I;=tVFp5Bjq+yi(Q+vl8-%>i%r zpYpvIkp}%8lCO5XU5?a?6!fI*BR(%}0UU(+9)obyV*%_!y(1E=ATuw=Zm+hKe`GsB z4`qCttw&DWM*{g?I#W+$ZrwiJxoZ?^u?$5kM!p4?mmfSN=G_U9{O~P4Tyq$grZhr*9|Wx*-C6Rwpv;nU`-;xTftJt_MS6!y5K}&6z{1~y7+%wKmic^g!JPFr z`K6NuGzgi8?R?Wy+2A+;#>{R~-Eqd?W+twW!%4j`Wc|0!txl)l=j#U?-z2O=FGmgx zUa)c}a479Y{$ggH7T4p|ywt=PGw$Fs!iXpc~15IIj z9pv%8EIM&~6KaF-m{YqF+XYIyq0<@9dM6=pl5Ommxj5#~x|`B1l~gS$z=3+K>k_C-8(oVm_IKHd`2)ks!E0*|vV zy%-w{C7Lcy1&1D>G`9KFCoCWOX^VT&Z{3O-=R3&q?kXVIjnc0-%u+HEaWeSl%Y~hIB4XtcQV(k7Dzs&{R6)a+Z<(B5v_S2ZwsKn)`Ou8V=W1(XiO&V4 zET4^J4t^c{o$*fLYH4MhgUCMj(=+l`L*6=kh?DqM2U>Zv%$?8H13F2qF3$R?^(a-N zDxRG?QPJ+U4@Kc~*V8Rz&aCkRr08+?{?l5V$Tgb%o!L7CF#GS3U6~nGK*1{Pjj(tT z7+GnV`V4kJJ&mQK4|njR$*j!`H>-&4Hl-hIC3A=g3GtXn^R+*~hGZ|R9g6T1#B}l> z7ktkxhvUyix-(a_!Ll2!I-ck2fZeG@OV%=Rqcq(iIYk$b5X$FooCjG>_jrs&aJylr z&rfb+?;!&>dQ=nnXttMEB0o@2qB>AxSU zf#jUJOO>@x7UN-p8|`Gcu$pC6W~K}+fH~BEy=i+EG(VO`@NoLH311}#$#=fkJ6qgQsGoxPbwvB`Oi$C#@ zYLn90Au$|?EN5@GD{-eJ*$W`&T? z+09i)y9@0981`{xSqV(u@9?fZX)RjJ#a7TRM(iI@#_QSZ);E87qpce~GhdI?E%tB8 z*jxy`Y^&vY&ecFE>94+g_EYl>icfB*nNR&4n^4`cV|(flxvlixZ<|z?dqs=8*I$DeERcucMsf)m9G|jf))%9;^dzbMCdOQyZd6 zrfd7e5n}>}vV085b|WSvWRI$Csg61?c2M;log0rLrs|is-ZP;G=&mDeH$NN?dS3fq zC9Tc}`3819oUvaKBk77C*FA~rP$)S@WB}g&oR{$Lyb~{lP46ZlkxvhO%Z*V&2DcpD zdIMtcWz*1>7d`dhQo@r=9Yos@D*0<=}VROt_hhl{a^_!|jFqqDy?#7Xg;68*b9D5W>RFiJPC-=muiF$$GqESzvGG zNF43c67cB(Bek!sD%w;SyK-E555aDfddz0&2`?4f{8VwK{$Mm&B*_RHCT#eU!b94r z`(AH4D{o>lY=_nv?p{a3K0)4aQ$(?299g{osB`cVX99<^{n5@|L7F0v&C(GH}#J>wiSw07*Gyc=R-cWsC zq>l@kSoStqa3w8*bNBr^W&8VS8Bne1r#nsG4g^2G=Z-h81z(B=1y9LRqqbW#BafdT z-j6})Zx&<@o(R@8^H8H42$S>)JyCTQ4Cf4cG|@Lsw1AB-&ndS1e+7%$Y{U88nn9hd zfep8*94h#PHTd^(;(cJ094j0^P6ZFI8}Kp81m>qy<;R3aAT zlmLN((bJyWy8!3+dQVHC4d~S%2cwHui1(vYa;#@@@O-g1Mz>>bOx+KLO7JbYzYGcg zMKXPr(+t)>2uhO`Yz2d3;T(5bjK)VK2Z zR#vQiGh3?1CQ)oRW?bgIya&j-|626^K`}h-T*gx>atSQ4&+rqE%|VRCk7lp0nR~rI zF%D2sSzU_o5!R-{$ht7S*r4Esb#ln)VTYV_(|jOjp~PFfvkTg_g%o{TT?Sgr6l1w1 z)zD?@gp-3*b`kuVvYryJOaJIOBb*#%{9+mMl*=_O%!C;`8gkl;?Q%D;GkM41{;m|r zGhN*AC^89Z$nMK~$BUw3^{e`i`x_BBly;*dXHB#p2?5V(?Y@a$8k;Pah#KkxizL6ah$-cXPx-dC^?&c2UaV|N)fuoyww zK9_gP5h;VrnzKrcATXLuuBo5}=9Kqvj)GF4SjXFqo!3D_tL457UZ1-?U4)y8kdm;_ zW$GfTg35^Lt6_Pn1SzCVQhA^A?pFA|%12Jw>}%82vhj*mYqjLKEjMg~DB=ix-l_~4# zY&|bd4e(uCuZE>gT0fNY`g5H+T)r`I8&d|ndn9xsNO0=;6nUqvFi)-X=5wmNJ{Vzu zEPXC}?MvX??dfdJv>8cQ9NUGnA6!Kc?jxtN?>b8$^KlxKqE_)@#QQ$}W;dL(TC%lJ z;Q}|JA2xV-vGvq=$cbKtPR%wDn)1d@!@35rKYp=TI;jdqu6n3n@-ZBIVc(*=?$OYK z%STh^L!#EbnfX7&GF2q%>mq(^J`Qtcc!h1Y7`fjveLoXrziPJK@;Q_kkGFCnWL-#Y zu0?_6!)4g72kmA4J+0vMGuh*8TN~gPnD(sdaWQ<6FI#uT_9eKJQgSOCoqIjsY!043 zL#OSrlR=`$YBBXs{p||#a_+8*AJ=SFAn2it2Xk?1f(=4!K8hiGw7$#N?^nbUyxu%) sZs~;E>4$9G{?DB6+bavLtNFyZaDO;=MA^wf9}JJS>k@4b09Q@iRR910 diff --git a/tests/regression_tests/surface_source_write/case-d08/results_true.dat b/tests/regression_tests/surface_source_write/case-d08/results_true.dat index 63ea1a64d41..5a4ea66898d 100644 --- a/tests/regression_tests/surface_source_write/case-d08/results_true.dat +++ b/tests/regression_tests/surface_source_write/case-d08/results_true.dat @@ -1,2 +1,2 @@ k-combined: -9.756086E-01 4.639209E-02 +9.947197E-01 3.711779E-02 diff --git a/tests/regression_tests/surface_source_write/case-d08/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-d08/surface_source_true.h5 index 2525592045ca7912138b0559d72d9d4eaeda6118..5da7564d1f4cd31322ff9a471a6dea175ba8fd0a 100644 GIT binary patch literal 33344 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 Bgy8@H literal 33344 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 diff --git a/tests/regression_tests/surface_source_write/case-e01/results_true.dat b/tests/regression_tests/surface_source_write/case-e01/results_true.dat index 8979eb55477..d4d1d1e5ad4 100644 --- a/tests/regression_tests/surface_source_write/case-e01/results_true.dat +++ b/tests/regression_tests/surface_source_write/case-e01/results_true.dat @@ -1,2 +1,2 @@ k-combined: -5.169123E-02 9.144814E-03 +5.642735E-02 1.494035E-02 diff --git a/tests/regression_tests/surface_source_write/case-e01/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-e01/surface_source_true.h5 index 3047519e8c52b140a4756266192c9d81c7d204fb..0dda15ed2192739435e8f50e126831021d620f53 100644 GIT binary patch delta 16344 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 aVrYY7u{m!7cDBerf3wGbaon^L@c#gi>OkTE delta 16353 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 aVrYY7u{m!7cDBerf3wGbaon^L@c#hNxIp6o delta 16353 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 TJDVPnIAXpS-rknc>Fxgq)!}e; literal 33344 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

l11eivG9^A2FF@o(kK8lbM?n}(nd+t7C#e>`?H zC6C)gdGK=|xNVf6k)K$De&^fTD6DwFa;qE2&u7ybxseT<>-PJ~OtzUXh^T>XbIBxI74S-f2E^v>Q+dKd( z-#@rNdTB^j%8i$t=S}3$pUa_fFekOBW2V6=uy}!eJX0wPR=U(7 z-GrqAvaX#KlQAn0?px1zPh6=DK8ntz%zLklo>gGUN;pEc1HA7~%7f?O-XZe%u0Ozo*!suD$6BDRzI&-Os)jl?)epW= zAajWQhgQ*4brXM0B%Lphy-S|1Um@@I;ti|1A@|)`%!c%ldU<||3O|?Dx6d>(8*WqOWqHTV=3xy;CZ86jVurT+b7$HKF`5_`Nw|s zCnLDt5RY-ogE`f+u!SD1bE?i(0mBEu@)5VH!NR)jch+;&L*ETsZ=K&Ijb366otJAp zb3et;mrOSht=${GHkjXWR}q0>(fFnYN|~IQ2FaS-E9c_x}14*rw?z z_1rEB$ff2O)h%oXo<3{uJkAtEAAkAf(63CM{}BBFeofv_r(&0SeRb0`8L_nrx=$i| zi{LBIGqWl-cfcFT`Jh@L8Jz8i@!WGa8zu~T9*`UCN028cn+1oINp?V#ho7u4+8q@u zi7rN4EcyV0Yowc!Tou%aKSXt(h}%aSBCI7>rGYt>#|0UmPt5=8Uwkt9)`+BzTOMvS zA?J@0IrzU%f$Ob9HMHmZi3T90J;=TA(nS8|KQAgGAE_vFtM3g+IWAM#ZJ7#&+HWVu z?d$}WT8|d08uTK_je-4dT1`mu;JTW$H~f4FSu83N+s@z9HnINcka^L@?#mc>*_ib@ z+^G{BU1{I+=*bs&@`H#?^>`dG`D`|nsW=-ov*9{p`C#V!H$3kNkAXg9AK=U0HogCY z%<1VCe%Cn);+gElTQ}vwRK3W`+wyH7*3UF4+PVPTDhl8%PZmS>MO>C*yGV}Th~v+0 zvLF;@CU5F&CPqwkyPK#R7b7zDHWlfIF&8u@^8D|+RPs}BorJmj|~wGUAq`g5W9?{BFuj6gZNL9d#G^VNu9$kVCl znUv551WJRnm@Wl?TgJBTL#$;$&y;OVjVw?lj4xaTEwQ7Pa-G};$tU1xtWt+FkYLL%o5&bC6 z#O<1|`jN3jei4lPaNm*7@b@}}&%47F(Zc0$SZZC|ouLxom1ckZ7;iHaI(nINEWsaP z1)N^|ZYN24BXa1^N#J^06Y|wtazO(~q(q;UIm3cgItU!fJU4NUp!(y^c$2rl$vyL8 z0bdfB_h8)m!2A~omUr^Uu=P8|47r?AMd+3C+7vGp5-?N7n!YiZCu5S8N01^ z+PSU`AmV1ej`d}*)3x|03)e(`-0}?^xtuM?Xg@{MD2F`$5IMp$4qiCizU8~mb2@&* zGPjn#mv}!4mWfFK_7kxncbA*=tNv_Q&A_^X9}xQw-u}2ec)f#x0}F=@`r+ly^RdB;s^O*TOII%1wS%Z#2lMwb zyn+L_zDu7dErLud9+YH7uR)U^-@i5cEqR@fs5g8)jE;jjGOoN3S;>Ow$aZcOIMNMI z^hc*3C}@MthxXQnX6HkPeH8*#!r8F)e)oY+e}FD)LNa(Zk>}fZn@{Qu*B8!NaOwJm zScB>F=-5|?2DNql1krQ#mKR@3g=pAuYpPl&%*ruecKOr?sM+yqVPmcw>Ma%akohoq z9hTTW!~b=-c=JgcN z!aP`ZTCW_J2gCZp`^%LaL03V$$jjm{V8y9JjBdf5P;p}8r2m2(TDrw5?4<~KzCh%N z;{aNDCZaV0p;&S}6o$Tpo@z5LHUfGpNxulVi;;Mmynf?c2iyzKWiRID;V zZ&Me~MeG57{;! edE5?HrP?A#ixAW#=z`r^N%DD3qCE8bS^o!D&vB6e diff --git a/tests/regression_tests/surface_tally/results_true.dat b/tests/regression_tests/surface_tally/results_true.dat index 8b27fa19280..70d5cad2c64 100644 --- a/tests/regression_tests/surface_tally/results_true.dat +++ b/tests/regression_tests/surface_tally/results_true.dat @@ -1,52 +1,52 @@ mean,std. dev. -2.1200000e-02,1.8366636e-03 -7.4900000e-02,2.2083176e-03 -1.6220000e-01,2.8079253e-03 -6.4010000e-01,8.6838931e-03 -2.1000000e-03,3.7859389e-04 -5.6000000e-03,6.8637534e-04 -1.1700000e-02,1.4456832e-03 -4.1700000e-02,2.7041121e-03 -2.1200000e-02,1.8366636e-03 -7.4900000e-02,2.2083176e-03 -1.6220000e-01,2.8079253e-03 -6.4010000e-01,8.6838931e-03 -2.1000000e-03,3.7859389e-04 -5.6000000e-03,6.8637534e-04 -1.1700000e-02,1.4456832e-03 -4.1700000e-02,2.7041121e-03 -5.2000000e-03,5.9254629e-04 -3.9200000e-02,2.5508169e-03 -4.0200000e-02,1.9310331e-03 -4.1360000e-01,8.0072190e-03 -0.0000000e+00,0.0000000e+00 -1.9000000e-03,3.7859389e-04 +2.4100000e-02,2.1052844e-03 +6.7900000e-02,2.3211587e-03 +1.6860000e-01,4.4800794e-03 +6.4710000e-01,9.1826527e-03 +2.2000000e-03,3.8873013e-04 +5.5000000e-03,1.0979779e-03 +1.2500000e-02,1.5438048e-03 +4.4700000e-02,1.9035055e-03 +2.4100000e-02,2.1052844e-03 +6.7900000e-02,2.3211587e-03 +1.6860000e-01,4.4800794e-03 +6.4710000e-01,9.1826527e-03 +2.2000000e-03,3.8873013e-04 +5.5000000e-03,1.0979779e-03 +1.2500000e-02,1.5438048e-03 +4.4700000e-02,1.9035055e-03 +7.3000000e-03,9.8938814e-04 +3.6600000e-02,2.5086517e-03 +4.1600000e-02,2.4864075e-03 +4.2380000e-01,9.6087923e-03 1.0000000e-04,1.0000000e-04 -1.6400000e-02,1.2840907e-03 --5.2000000e-03,5.9254629e-04 --3.9200000e-02,2.5508169e-03 --4.0200000e-02,1.9310331e-03 --4.1360000e-01,8.0072190e-03 -0.0000000e+00,0.0000000e+00 --1.9000000e-03,3.7859389e-04 +1.5000000e-03,4.5338235e-04 +3.0000000e-04,1.5275252e-04 +1.7300000e-02,1.4609738e-03 +-7.3000000e-03,9.8938814e-04 +-3.6600000e-02,2.5086517e-03 +-4.1600000e-02,2.4864075e-03 +-4.2380000e-01,9.6087923e-03 -1.0000000e-04,1.0000000e-04 --1.6400000e-02,1.2840907e-03 -1.6000000e-02,2.1602469e-03 -3.5700000e-02,2.9441090e-03 -1.2200000e-01,3.5932035e-03 -2.2650000e-01,9.2463206e-03 +-1.5000000e-03,4.5338235e-04 +-3.0000000e-04,1.5275252e-04 +-1.7300000e-02,1.4609738e-03 +1.6800000e-02,1.5902481e-03 +3.1300000e-02,3.8094911e-03 +1.2700000e-01,5.0990195e-03 +2.2330000e-01,9.3631073e-03 2.1000000e-03,3.7859389e-04 -3.7000000e-03,8.1717671e-04 -1.1600000e-02,1.4772347e-03 -2.5300000e-02,1.9723083e-03 +4.0000000e-03,1.0540926e-03 +1.2200000e-02,1.6110728e-03 +2.7400000e-02,1.6613248e-03 0.0000000e+00,0.0000000e+00 --2.9700000e-02,2.4587711e-03 +-3.2000000e-02,1.5634719e-03 0.0000000e+00,0.0000000e+00 --3.5090000e-01,3.7161808e-03 +-3.5400000e-01,7.5938572e-03 0.0000000e+00,0.0000000e+00 --2.1000000e-03,4.8189441e-04 +-3.0000000e-03,6.4978629e-04 0.0000000e+00,0.0000000e+00 --2.1400000e-02,1.7397318e-03 +-2.1800000e-02,1.4892205e-03 0.0000000e+00,0.0000000e+00 0.0000000e+00,0.0000000e+00 0.0000000e+00,0.0000000e+00 diff --git a/tests/regression_tests/survival_biasing/results_true.dat b/tests/regression_tests/survival_biasing/results_true.dat index 74027220170..932414e98b1 100644 --- a/tests/regression_tests/survival_biasing/results_true.dat +++ b/tests/regression_tests/survival_biasing/results_true.dat @@ -1,20 +1,20 @@ k-combined: -9.879232E-01 8.097582E-03 +9.517646E-01 1.303111E-02 tally 1: -4.295331E+01 -3.691096E+02 -1.802724E+01 -6.501934E+01 -2.193500E+00 -9.627609E-01 -1.893529E+00 -7.174104E-01 -4.902380E+00 -4.808570E+00 -3.418014E-02 -2.337353E-04 -3.667128E+08 -2.690758E+16 +4.164635E+01 +3.470110E+02 +1.724300E+01 +5.949580E+01 +2.124917E+00 +9.034789E-01 +1.844790E+00 +6.809026E-01 +4.784396E+00 +4.579552E+00 +3.348849E-02 +2.243613E-04 +3.573090E+08 +2.554338E+16 tally 2: -1.802724E+01 -6.501934E+01 +1.724300E+01 +5.949580E+01 diff --git a/tests/regression_tests/tallies/results_true.dat b/tests/regression_tests/tallies/results_true.dat index 3dda0f9b211..1d3aca4b079 100644 --- a/tests/regression_tests/tallies/results_true.dat +++ b/tests/regression_tests/tallies/results_true.dat @@ -1 +1 @@ -ddfbb0a6f5498eb8ff33bb10beb64e244b015861919eb4837bd82855e5fd87c3ff97dfa382d3d60afa81c48d9f857fba8e0b99263e7b35587f8adb75be1fc0ec \ No newline at end of file +d01c3accd5b4de2aa166a77df28cfe42f5738a44c2480752fcfae7564a507362fff006b6dffb7b1dfe248e14bacef0070cabacea5d75c5996653e5605f7c7384 \ No newline at end of file diff --git a/tests/regression_tests/tally_aggregation/results_true.dat b/tests/regression_tests/tally_aggregation/results_true.dat index 172adbfe132..ee6263373ad 100644 --- a/tests/regression_tests/tally_aggregation/results_true.dat +++ b/tests/regression_tests/tally_aggregation/results_true.dat @@ -1,97 +1,97 @@ -[[1.6001087e-05 5.4190949e-04] - [3.2669968e-01 1.7730523e-01] - [1.8149266e-02 7.1525113e-01]], [[1.6239097e-05 5.7499676e-04] - [3.1268633e-01 1.7004165e-01] - [1.8873778e-02 7.0217885e-01]], [[1.6693071e-05 5.4106379e-04] - [3.3370208e-01 1.8051505e-01] - [1.9081306e-02 7.3647547e-01]], [[1.6725399e-05 5.1680146e-04] - [3.2854628e-01 1.7757185e-01] - [1.9529646e-02 7.1005198e-01]][[2.4751834e-07 4.3304565e-05] - [8.6840718e-03 4.3442535e-03] - [3.7054051e-04 6.5678133e-03]], [[4.0830852e-07 4.9612204e-05] - [1.2104241e-02 5.9708675e-03] - [7.1398189e-04 8.2408482e-03]], [[2.6546344e-07 2.5234256e-05] - [6.5083211e-03 3.2185071e-03] - [4.2935150e-04 5.1707774e-03]], [[2.7845203e-07 3.4453402e-05] - [3.3125427e-03 1.7749508e-03] - [6.0407731e-04 7.5353872e-03]][[1.0455251e-06 8.1938339e-04] - [1.1392765e+00 5.6434761e-01] - [1.4142831e-06 5.1213654e-01]], [[2.2009815e-06 1.0418935e-03] - [1.4422554e-01 1.0070647e-01] - [3.9488382e-05 7.9236390e-01]], [[1.4606612e-05 2.2374470e-04] - [1.2962842e-02 2.9268594e-02] - [3.1339824e-04 1.1056439e+00]], [[4.7805535e-05 8.9749934e-05] - [5.1694597e-03 1.1111105e-02] - [7.5279695e-02 4.5381305e-01]][[1.4955183e-08 1.1461548e-05] - [1.6454649e-02 8.1236707e-03] - [2.0028007e-08 6.8264819e-03]], [[1.6334486e-07 7.7617340e-05] - [2.1161584e-03 1.4078445e-03] - [1.4742097e-05 6.9350862e-03]], [[1.7444757e-07 1.9026095e-06] - [1.4084092e-04 2.0345990e-04] - [5.9546175e-06 8.6038812e-03]], [[5.6449127e-07 1.0110585e-06] - [5.9158873e-05 1.2485325e-04] - [1.0936497e-03 5.0836702e-03]][[0.2866239 0.2725595]], [[0.2729156 0.258831 ]], [[0.2920724 0.2755922]], [[0.287667 0.2703207]], [[0.035617 0.2232707]], [[0.0353082 0.2224423]], [[0.0370072 0.2288263]], [[0.0363348 0.219573 ]], [[0.0033013 0.2850034]], [[0.0032726 0.2771099]], [[0.0034234 0.2951295]], [[0.0032935 0.2778935]], [[0.0193227 0.1122647]], [[0.0200799 0.1144123]], [[0.0202971 0.1179836]], [[0.0207973 0.1203534]][[0.0086351 0.0061876]], [[0.0120688 0.0074831]], [[0.0064221 0.0035246]], [[0.0030483 0.0024267]], [[0.0009185 0.0033896]], [[0.0009223 0.0039584]], [[0.0010541 0.0038599]], [[0.0012934 0.0028331]], [[6.5126963e-05 2.8486593e-03]], [[7.1110242e-05 4.3424340e-03]], [[5.8911409e-05 2.4296178e-03]], [[8.4278765e-05 6.4182190e-03]], [[0.0003712 0.0020298]], [[0.0007152 0.0036115]], [[0.0004298 0.0019677]], [[0.0006046 0.0021965]][[2.0694650e-04] - [4.2868191e-01] - [1.3029457e-01]], [[1.9694048e-04] - [4.0809445e-01] - [1.2345525e-01]], [[2.1012645e-04] - [4.3670571e-01] - [1.3074883e-01]], [[2.0641548e-04] - [4.3014207e-01] - [1.2763930e-01]], [[0.0002584] - [0.0608867] - [0.1977426]], [[0.0003026] - [0.0602368] - [0.1972111]], [[0.0002509] - [0.0624699] - [0.2031126]], [[0.0002322] - [0.0613385] - [0.1943371]], [[5.9349736e-05] - [1.0506741e-02] - [2.7773865e-01]], [[5.7855564e-05] - [1.0389781e-02] - [2.6993483e-01]], [[6.1819700e-05] - [1.0910874e-02] - [2.8758020e-01]], [[5.9326317e-05] - [1.0424040e-02] - [2.7070367e-01]], [[3.3192167e-05] - [3.9295320e-03] - [1.2762462e-01]], [[3.3882691e-05] - [4.0069157e-03] - [1.3045143e-01]], [[3.4882103e-05] - [4.1306182e-03] - [1.3411514e-01]], [[3.5598508e-05] - [4.2134987e-03] - [1.3690156e-01]][[6.4505980e-06] - [9.6369034e-03] - [4.4700460e-03]], [[8.2880772e-06] - [1.3456171e-02] - [4.5368708e-03]], [[3.9384462e-06] - [7.1570638e-03] - [1.5629142e-03]], [[2.3565894e-06] - [3.4040401e-03] - [1.8956916e-03]], [[4.2806047e-05] - [1.1841015e-03] - [3.3058802e-03]], [[4.8905696e-05] - [1.0360139e-03] - [3.9298937e-03]], [[2.4911390e-05] - [1.2173305e-03] - [3.8114473e-03]], [[3.4347869e-05] - [1.5820360e-03] - [2.6824612e-03]], [[1.0809230e-06] - [1.0316180e-04] - [2.8475354e-03]], [[6.4150814e-07] - [1.1179425e-04] - [4.3415770e-03]], [[7.3848139e-07] - [9.3111666e-05] - [2.4285475e-03]], [[1.2349384e-06] - [1.7152843e-04] - [6.4164799e-03]], [[4.5871493e-07] - [5.4732075e-05] - [2.0627309e-03]], [[8.1649994e-07] - [9.7699188e-05] - [3.6803256e-03]], [[4.5174850e-07] - [5.3913784e-05] - [2.0133571e-03]], [[5.0962865e-07] - [6.0338032e-05] - [2.2773911e-03]] \ No newline at end of file +[[1.6242805e-05 6.2367673e-04] + [3.2895972e-01 1.7786452e-01] + [1.8044266e-02 7.0451122e-01]], [[1.6113947e-05 5.3572864e-04] + [3.1517504e-01 1.7132833e-01] + [1.8305682e-02 7.0072832e-01]], [[1.6472052e-05 5.5758006e-04] + [3.2362364e-01 1.7597163e-01] + [1.9107080e-02 7.2600440e-01]], [[1.6693277e-05 4.9204218e-04] + [3.2429262e-01 1.7600573e-01] + [1.9042489e-02 7.3053854e-01]][[2.9719061e-07 8.0925438e-05] + [8.9432259e-03 4.4057583e-03] + [5.2078407e-04 6.4387688e-03]], [[1.8572081e-07 2.5667235e-05] + [1.1641603e-02 5.7444311e-03] + [2.6983611e-04 6.9783224e-03]], [[2.4994113e-07 5.4993390e-05] + [6.0464041e-03 3.0764811e-03] + [2.6559118e-04 7.7279780e-03]], [[2.5965232e-07 3.9618775e-05] + [1.0409564e-02 5.1850104e-03] + [2.7831231e-04 7.4480576e-03]][[1.0329435e-06 8.0861229e-04] + [1.1265142e+00 5.5796079e-01] + [1.3965860e-06 5.0338828e-01]], [[2.2264172e-06 1.0828687e-03] + [1.4724979e-01 1.0226107e-01] + [2.2656045e-05 7.7618945e-01]], [[1.4789782e-05 2.2786630e-04] + [1.3149968e-02 2.9847169e-02] + [3.2638111e-04 1.1287326e+00]], [[4.7472938e-05 8.9680338e-05] + [5.1371041e-03 1.1101179e-02] + [7.4149084e-02 4.5347217e-01]][[1.7024771e-08 1.2878244e-05] + [1.8795566e-02 9.2705008e-03] + [2.2676749e-08 7.0878353e-03]], [[2.2018925e-07 1.0785388e-04] + [2.6715019e-03 1.6657928e-03] + [1.2479083e-05 8.6814018e-03]], [[2.0928485e-07 1.6600875e-06] + [1.1859535e-04 1.7969083e-04] + [1.5981387e-05 8.0489308e-03]], [[4.0016381e-07 7.6799157e-07] + [4.4157583e-05 9.4462925e-05] + [7.0115110e-04 3.8678806e-03]][[0.287433 0.2698042]], [[0.2751228 0.2598525]], [[0.280516 0.2648274]], [[0.2834448 0.2676736]], [[0.0370558 0.2148509]], [[0.0355217 0.2130189]], [[0.0385317 0.2292804]], [[0.0361655 0.2223832]], [[0.0033268 0.2855078]], [[0.0033498 0.2849403]], [[0.003359 0.2901157]], [[0.0034555 0.2982438]], [[0.0192046 0.1128364]], [[0.0195026 0.1147807]], [[0.0203405 0.1183102]], [[0.0202861 0.1187358]][[0.00889 0.0051934]], [[0.0115249 0.0072767]], [[0.0058841 0.0040289]], [[0.0103341 0.0063266]], [[0.0009734 0.0048865]], [[0.0016425 0.0038133]], [[0.0013902 0.0048875]], [[0.0012487 0.0039808]], [[1.8994140e-05 2.0410145e-03]], [[6.8277082e-05 3.4865782e-03]], [[6.1643568e-05 4.9220817e-03]], [[7.4147413e-05 4.9263295e-03]], [[0.0005213 0.0024208]], [[0.0002702 0.0014313]], [[0.0002665 0.0022006]], [[0.0002788 0.0014892]][[2.0601081e-04] + [4.2976789e-01] + [1.2726338e-01]], [[1.9802070e-04] + [4.1138025e-01] + [1.2339705e-01]], [[2.0174854e-04] + [4.1947542e-01] + [1.2566615e-01]], [[2.0386518e-04] + [4.2385140e-01] + [1.2706310e-01]], [[0.0003403] + [0.0624955] + [0.189071 ]], [[0.0002601] + [0.060449 ] + [0.1878314]], [[0.0002765] + [0.0652412] + [0.2022944]], [[0.0002082] + [0.0613252] + [0.1970153]], [[6.0336874e-05] + [1.0617692e-02] + [2.7815664e-01]], [[5.9823997e-05] + [1.0664059e-02] + [2.7756620e-01]], [[6.0873689e-05] + [1.0744209e-02] + [2.8266963e-01]], [[6.1621521e-05] + [1.0971177e-02] + [2.9066650e-01]], [[3.3297391e-05] + [3.9432037e-03] + [1.2806450e-01]], [[3.3863464e-05] + [4.0100180e-03] + [1.3023932e-01]], [[3.4930617e-05] + [4.1344613e-03] + [1.3448129e-01]], [[3.5061805e-05] + [4.1505999e-03] + [1.3483614e-01]][[5.8259835e-06] + [9.9096571e-03] + [2.7933778e-03]], [[8.0858909e-06] + [1.2843377e-02] + [4.5631248e-03]], [[4.3418585e-06] + [6.5638646e-03] + [2.7874612e-03]], [[6.9046376e-06] + [1.1531588e-02] + [3.7205377e-03]], [[8.0710599e-05] + [1.0889301e-03] + [4.8613465e-03]], [[2.4337121e-05] + [1.8861117e-03] + [3.6987509e-03]], [[5.4812439e-05] + [1.7085353e-03] + [4.7852139e-03]], [[3.9007791e-05] + [1.4998363e-03] + [3.8929646e-03]], [[7.5757577e-07] + [2.5845755e-05] + [2.0409391e-03]], [[1.0302346e-06] + [1.1907557e-04] + [3.4852130e-03]], [[9.1936614e-07] + [1.2974020e-04] + [4.9207575e-03]], [[5.6493555e-07] + [1.2113685e-04] + [4.9253980e-03]], [[5.4602210e-07] + [6.5233025e-05] + [2.4754550e-03]], [[3.2091909e-07] + [3.8472906e-05] + [1.4560974e-03]], [[4.8475651e-07] + [5.9108262e-05] + [2.2158781e-03]], [[3.3737822e-07] + [4.0544269e-05] + [1.5145628e-03]] \ No newline at end of file diff --git a/tests/regression_tests/tally_arithmetic/results_true.dat b/tests/regression_tests/tally_arithmetic/results_true.dat index 143930dd419..baa38058713 100644 --- a/tests/regression_tests/tally_arithmetic/results_true.dat +++ b/tests/regression_tests/tally_arithmetic/results_true.dat @@ -1,49 +1,49 @@ -[2.04229e-07 1.28343e-13 1.79333e-04 1.12698e-10 1.89952e-07 1.51210e-07 - 1.66796e-04 1.32777e-04 6.13215e-03 3.85362e-09 3.14746e-03 1.97795e-09 - 5.70345e-03 4.54021e-03 2.92742e-03 2.33037e-03 2.10575e-07 1.28419e-13 - 1.84905e-04 1.12764e-10 1.89188e-07 1.50221e-07 1.66125e-04 1.31908e-04 - 6.32269e-03 3.85587e-09 3.24526e-03 1.97911e-09 5.68054e-03 4.51051e-03 - 2.91566e-03 2.31512e-03 1.96696e-07 1.26388e-13 1.72718e-04 1.10981e-10 - 1.91283e-07 1.53449e-07 1.67964e-04 1.34743e-04 5.90596e-03 3.79491e-09 - 3.03136e-03 1.94782e-09 5.74342e-03 4.60742e-03 2.94794e-03 2.36486e-03 - 2.06016e-07 1.35264e-13 1.80901e-04 1.18775e-10 2.05873e-07 1.65814e-07 - 1.80776e-04 1.45600e-04 6.18579e-03 4.06142e-09 3.17499e-03 2.08461e-09 - 6.18150e-03 4.97869e-03 3.17279e-03 2.55542e-03 2.36089e-03 4.46651e-05 - 1.02263e-02 1.93469e-04 1.18179e-04 2.62375e-05 5.11897e-04 1.13649e-04 - 2.56040e-02 4.84394e-04 4.55595e-02 8.61927e-04 1.28165e-03 2.84546e-04 - 2.28056e-03 5.06320e-04 2.29877e-03 4.53056e-05 9.95724e-03 1.96244e-04 - 1.14803e-04 2.57898e-05 4.97276e-04 1.11710e-04 2.49302e-02 4.91340e-04 - 4.43606e-02 8.74289e-04 1.24504e-03 2.79691e-04 2.21542e-03 4.97680e-04 - 2.31940e-03 4.34238e-05 1.00466e-02 1.88093e-04 1.17620e-04 2.69280e-05 - 5.09479e-04 1.16640e-04 2.51540e-02 4.70932e-04 4.47588e-02 8.37974e-04 - 1.27560e-03 2.92034e-04 2.26979e-03 5.19644e-04 2.19212e-03 4.53914e-05 - 9.49530e-03 1.96615e-04 1.09609e-04 2.41615e-05 4.74778e-04 1.04657e-04 - 2.37736e-02 4.92271e-04 4.23026e-02 8.75944e-04 1.18871e-03 2.62032e-04 - 2.11519e-03 4.66257e-04][2.04229e-07 1.28343e-13 1.79333e-04 1.12698e-10 1.89952e-07 1.51210e-07 - 1.66796e-04 1.32777e-04 6.13215e-03 3.85362e-09 3.14746e-03 1.97795e-09 - 5.70345e-03 4.54021e-03 2.92742e-03 2.33037e-03 2.10575e-07 1.28419e-13 - 1.84905e-04 1.12764e-10 1.89188e-07 1.50221e-07 1.66125e-04 1.31908e-04 - 6.32269e-03 3.85587e-09 3.24526e-03 1.97911e-09 5.68054e-03 4.51051e-03 - 2.91566e-03 2.31512e-03 1.96696e-07 1.26388e-13 1.72718e-04 1.10981e-10 - 1.91283e-07 1.53449e-07 1.67964e-04 1.34743e-04 5.90596e-03 3.79491e-09 - 3.03136e-03 1.94782e-09 5.74342e-03 4.60742e-03 2.94794e-03 2.36486e-03 - 2.06016e-07 1.35264e-13 1.80901e-04 1.18775e-10 2.05873e-07 1.65814e-07 - 1.80776e-04 1.45600e-04 6.18579e-03 4.06142e-09 3.17499e-03 2.08461e-09 - 6.18150e-03 4.97869e-03 3.17279e-03 2.55542e-03 2.36089e-03 4.46651e-05 - 1.02263e-02 1.93469e-04 1.18179e-04 2.62375e-05 5.11897e-04 1.13649e-04 - 2.56040e-02 4.84394e-04 4.55595e-02 8.61927e-04 1.28165e-03 2.84546e-04 - 2.28056e-03 5.06320e-04 2.29877e-03 4.53056e-05 9.95724e-03 1.96244e-04 - 1.14803e-04 2.57898e-05 4.97276e-04 1.11710e-04 2.49302e-02 4.91340e-04 - 4.43606e-02 8.74289e-04 1.24504e-03 2.79691e-04 2.21542e-03 4.97680e-04 - 2.31940e-03 4.34238e-05 1.00466e-02 1.88093e-04 1.17620e-04 2.69280e-05 - 5.09479e-04 1.16640e-04 2.51540e-02 4.70932e-04 4.47588e-02 8.37974e-04 - 1.27560e-03 2.92034e-04 2.26979e-03 5.19644e-04 2.19212e-03 4.53914e-05 - 9.49530e-03 1.96615e-04 1.09609e-04 2.41615e-05 4.74778e-04 1.04657e-04 - 2.37736e-02 4.92271e-04 4.23026e-02 8.75944e-04 1.18871e-03 2.62032e-04 - 2.11519e-03 4.66257e-04][0.0057 0.00454 0.00293 0.00233 0.00568 0.00451 0.00292 0.00232 0.00574 - 0.00461 0.00295 0.00236 0.00618 0.00498 0.00317 0.00256 0.00128 0.00028 - 0.00228 0.00051 0.00125 0.00028 0.00222 0.0005 0.00128 0.00029 0.00227 - 0.00052 0.00119 0.00026 0.00212 0.00047][0.00018 0.00017 0.00315 0.00293 0.00018 0.00017 0.00325 0.00292 0.00017 - 0.00017 0.00303 0.00295 0.00018 0.00018 0.00317 0.00317 0.01023 0.00051 - 0.04556 0.00228 0.00996 0.0005 0.04436 0.00222 0.01005 0.00051 0.04476 - 0.00227 0.0095 0.00047 0.0423 0.00212][0.00293 0.00292 0.00295 0.00317 0.00228 0.00222 0.00227 0.00212] \ No newline at end of file +[2.29467e-07 1.47622e-13 2.02646e-04 1.30367e-10 2.20704e-07 1.76624e-07 + 1.94907e-04 1.55980e-04 6.32267e-03 4.06754e-09 3.26873e-03 2.10286e-09 + 6.08121e-03 4.86666e-03 3.14390e-03 2.51599e-03 1.89223e-07 1.06256e-13 + 1.67106e-04 9.38364e-11 1.53300e-07 1.20220e-07 1.35382e-04 1.06168e-04 + 5.21380e-03 2.92775e-09 2.69546e-03 1.51361e-09 4.22399e-03 3.31252e-03 + 2.18374e-03 1.71253e-03 2.21146e-07 1.41349e-13 1.95297e-04 1.24828e-10 + 2.11844e-07 1.69398e-07 1.87083e-04 1.49598e-04 6.09339e-03 3.89470e-09 + 3.15020e-03 2.01351e-09 5.83710e-03 4.66754e-03 3.01770e-03 2.41305e-03 + 1.92872e-07 1.05908e-13 1.70328e-04 9.35293e-11 1.59466e-07 1.25399e-07 + 1.40827e-04 1.10741e-04 5.31433e-03 2.91817e-09 2.74744e-03 1.50865e-09 + 4.39388e-03 3.45520e-03 2.27158e-03 1.78629e-03 2.44601e-03 4.71663e-05 + 1.09430e-02 2.11013e-04 1.23573e-04 2.76181e-05 5.52841e-04 1.23558e-04 + 2.71755e-02 5.24023e-04 4.76200e-02 9.18253e-04 1.37291e-03 3.06841e-04 + 2.40577e-03 5.37681e-04 2.29014e-03 4.44674e-05 1.02456e-02 1.98938e-04 + 1.12524e-04 2.48126e-05 5.03411e-04 1.11007e-04 2.54438e-02 4.94038e-04 + 4.45855e-02 8.65710e-04 1.25016e-03 2.75671e-04 2.19067e-03 4.83061e-04 + 2.38500e-03 4.58053e-05 1.06700e-02 2.04924e-04 1.22899e-04 2.95267e-05 + 5.49826e-04 1.32097e-04 2.64977e-02 5.08903e-04 4.64323e-02 8.91758e-04 + 1.36542e-03 3.28045e-04 2.39265e-03 5.74839e-04 2.17094e-03 4.21772e-05 + 9.71236e-03 1.88693e-04 1.09358e-04 2.46984e-05 4.89245e-04 1.10496e-04 + 2.41194e-02 4.68594e-04 4.22648e-02 8.21124e-04 1.21498e-03 2.74402e-04 + 2.12902e-03 4.80839e-04][2.29467e-07 1.47622e-13 2.02646e-04 1.30367e-10 2.20704e-07 1.76624e-07 + 1.94907e-04 1.55980e-04 6.32267e-03 4.06754e-09 3.26873e-03 2.10286e-09 + 6.08121e-03 4.86666e-03 3.14390e-03 2.51599e-03 1.89223e-07 1.06256e-13 + 1.67106e-04 9.38364e-11 1.53300e-07 1.20220e-07 1.35382e-04 1.06168e-04 + 5.21380e-03 2.92775e-09 2.69546e-03 1.51361e-09 4.22399e-03 3.31252e-03 + 2.18374e-03 1.71253e-03 2.21146e-07 1.41349e-13 1.95297e-04 1.24828e-10 + 2.11844e-07 1.69398e-07 1.87083e-04 1.49598e-04 6.09339e-03 3.89470e-09 + 3.15020e-03 2.01351e-09 5.83710e-03 4.66754e-03 3.01770e-03 2.41305e-03 + 1.92872e-07 1.05908e-13 1.70328e-04 9.35293e-11 1.59466e-07 1.25399e-07 + 1.40827e-04 1.10741e-04 5.31433e-03 2.91817e-09 2.74744e-03 1.50865e-09 + 4.39388e-03 3.45520e-03 2.27158e-03 1.78629e-03 2.44601e-03 4.71663e-05 + 1.09430e-02 2.11013e-04 1.23573e-04 2.76181e-05 5.52841e-04 1.23558e-04 + 2.71755e-02 5.24023e-04 4.76200e-02 9.18253e-04 1.37291e-03 3.06841e-04 + 2.40577e-03 5.37681e-04 2.29014e-03 4.44674e-05 1.02456e-02 1.98938e-04 + 1.12524e-04 2.48126e-05 5.03411e-04 1.11007e-04 2.54438e-02 4.94038e-04 + 4.45855e-02 8.65710e-04 1.25016e-03 2.75671e-04 2.19067e-03 4.83061e-04 + 2.38500e-03 4.58053e-05 1.06700e-02 2.04924e-04 1.22899e-04 2.95267e-05 + 5.49826e-04 1.32097e-04 2.64977e-02 5.08903e-04 4.64323e-02 8.91758e-04 + 1.36542e-03 3.28045e-04 2.39265e-03 5.74839e-04 2.17094e-03 4.21772e-05 + 9.71236e-03 1.88693e-04 1.09358e-04 2.46984e-05 4.89245e-04 1.10496e-04 + 2.41194e-02 4.68594e-04 4.22648e-02 8.21124e-04 1.21498e-03 2.74402e-04 + 2.12902e-03 4.80839e-04][0.00608 0.00487 0.00314 0.00252 0.00422 0.00331 0.00218 0.00171 0.00584 + 0.00467 0.00302 0.00241 0.00439 0.00346 0.00227 0.00179 0.00137 0.00031 + 0.00241 0.00054 0.00125 0.00028 0.00219 0.00048 0.00137 0.00033 0.00239 + 0.00057 0.00121 0.00027 0.00213 0.00048][0.0002 0.00019 0.00327 0.00314 0.00017 0.00014 0.0027 0.00218 0.0002 + 0.00019 0.00315 0.00302 0.00017 0.00014 0.00275 0.00227 0.01094 0.00055 + 0.04762 0.00241 0.01025 0.0005 0.04459 0.00219 0.01067 0.00055 0.04643 + 0.00239 0.00971 0.00049 0.04226 0.00213][0.00314 0.00218 0.00302 0.00227 0.00241 0.00219 0.00239 0.00213] \ No newline at end of file diff --git a/tests/regression_tests/tally_assumesep/results_true.dat b/tests/regression_tests/tally_assumesep/results_true.dat index 9d7cbf9c3d4..c9ccf092824 100644 --- a/tests/regression_tests/tally_assumesep/results_true.dat +++ b/tests/regression_tests/tally_assumesep/results_true.dat @@ -1,11 +1,11 @@ k-combined: -5.683578E-01 1.129170E-02 +6.268465E-01 1.154810E-02 tally 1: -6.753950E+00 -9.188305E+00 +7.828708E+00 +1.230478E+01 tally 2: -2.278558E-01 -1.052944E-02 +2.582239E-01 +1.360117E-02 tally 3: -1.179091E+01 -2.795539E+01 +1.339335E+01 +3.663458E+01 diff --git a/tests/regression_tests/tally_nuclides/results_true.dat b/tests/regression_tests/tally_nuclides/results_true.dat index 7f9641fc8f5..93a0e03fb5d 100644 --- a/tests/regression_tests/tally_nuclides/results_true.dat +++ b/tests/regression_tests/tally_nuclides/results_true.dat @@ -1,28 +1,28 @@ k-combined: -1.132463E+00 5.721067E-02 +9.732610E-01 1.400780E-02 tally 1: -7.310528E+00 -1.080411E+01 -1.664215E+00 -5.567756E-01 -1.609340E+00 -5.205634E-01 -5.646313E+00 -6.459724E+00 -7.310528E+00 -1.080411E+01 -1.664215E+00 -5.567756E-01 -1.609340E+00 -5.205634E-01 -5.646313E+00 -6.459724E+00 +7.123025E+00 +1.021136E+01 +1.619905E+00 +5.258592E-01 +1.561322E+00 +4.881841E-01 +5.503120E+00 +6.105683E+00 +7.123025E+00 +1.021136E+01 +1.619905E+00 +5.258592E-01 +1.561322E+00 +4.881841E-01 +5.503120E+00 +6.105683E+00 tally 2: -7.310528E+00 -1.080411E+01 -1.664215E+00 -5.567756E-01 -1.609340E+00 -5.205634E-01 -5.646313E+00 -6.459724E+00 +7.123025E+00 +1.021136E+01 +1.619905E+00 +5.258592E-01 +1.561322E+00 +4.881841E-01 +5.503120E+00 +6.105683E+00 diff --git a/tests/regression_tests/tally_slice_merge/results_true.dat b/tests/regression_tests/tally_slice_merge/results_true.dat index 58b15fc18d9..4b2cbdf1adf 100644 --- a/tests/regression_tests/tally_slice_merge/results_true.dat +++ b/tests/regression_tests/tally_slice_merge/results_true.dat @@ -1,45 +1,45 @@ cell energy low [eV] energy high [eV] nuclide score mean std. dev. -0 21 0.00e+00 6.25e-01 U235 fission 1.75e-01 1.20e-02 cell energy low [eV] energy high [eV] nuclide score mean std. dev. -0 21 0.00e+00 6.25e-01 U235 nu-fission 4.26e-01 2.92e-02 cell energy low [eV] energy high [eV] nuclide score mean std. dev. -0 21 0.00e+00 6.25e-01 U238 fission 2.41e-07 1.61e-08 cell energy low [eV] energy high [eV] nuclide score mean std. dev. -0 21 0.00e+00 6.25e-01 U238 nu-fission 6.00e-07 4.01e-08 cell energy low [eV] energy high [eV] nuclide score mean std. dev. -0 21 6.25e-01 2.00e+07 U235 fission 2.89e-02 2.07e-03 cell energy low [eV] energy high [eV] nuclide score mean std. dev. -0 21 6.25e-01 2.00e+07 U235 nu-fission 7.07e-02 5.06e-03 cell energy low [eV] energy high [eV] nuclide score mean std. dev. -0 21 6.25e-01 2.00e+07 U238 fission 1.41e-02 5.76e-04 cell energy low [eV] energy high [eV] nuclide score mean std. dev. -0 21 6.25e-01 2.00e+07 U238 nu-fission 3.95e-02 1.90e-03 cell energy low [eV] energy high [eV] nuclide score mean std. dev. -0 27 0.00e+00 6.25e-01 U235 fission 1.51e-01 1.17e-02 cell energy low [eV] energy high [eV] nuclide score mean std. dev. -0 27 0.00e+00 6.25e-01 U235 nu-fission 3.69e-01 2.86e-02 cell energy low [eV] energy high [eV] nuclide score mean std. dev. -0 27 0.00e+00 6.25e-01 U238 fission 2.07e-07 1.47e-08 cell energy low [eV] energy high [eV] nuclide score mean std. dev. -0 27 0.00e+00 6.25e-01 U238 nu-fission 5.17e-07 3.66e-08 cell energy low [eV] energy high [eV] nuclide score mean std. dev. -0 27 6.25e-01 2.00e+07 U235 fission 2.57e-02 2.43e-03 cell energy low [eV] energy high [eV] nuclide score mean std. dev. -0 27 6.25e-01 2.00e+07 U235 nu-fission 6.30e-02 5.92e-03 cell energy low [eV] energy high [eV] nuclide score mean std. dev. -0 27 6.25e-01 2.00e+07 U238 fission 1.47e-02 1.41e-03 cell energy low [eV] energy high [eV] nuclide score mean std. dev. -0 27 6.25e-01 2.00e+07 U238 nu-fission 4.12e-02 4.02e-03 cell energy low [eV] energy high [eV] nuclide score mean std. dev. -0 21 0.00e+00 6.25e-01 U235 fission 1.75e-01 1.20e-02 -1 21 0.00e+00 6.25e-01 U235 nu-fission 4.26e-01 2.92e-02 -2 21 0.00e+00 6.25e-01 U238 fission 2.41e-07 1.61e-08 -3 21 0.00e+00 6.25e-01 U238 nu-fission 6.00e-07 4.01e-08 -4 21 6.25e-01 2.00e+07 U235 fission 2.89e-02 2.07e-03 -5 21 6.25e-01 2.00e+07 U235 nu-fission 7.07e-02 5.06e-03 -6 21 6.25e-01 2.00e+07 U238 fission 1.41e-02 5.76e-04 -7 21 6.25e-01 2.00e+07 U238 nu-fission 3.95e-02 1.90e-03 -8 27 0.00e+00 6.25e-01 U235 fission 1.51e-01 1.17e-02 -9 27 0.00e+00 6.25e-01 U235 nu-fission 3.69e-01 2.86e-02 -10 27 0.00e+00 6.25e-01 U238 fission 2.07e-07 1.47e-08 -11 27 0.00e+00 6.25e-01 U238 nu-fission 5.17e-07 3.66e-08 -12 27 6.25e-01 2.00e+07 U235 fission 2.57e-02 2.43e-03 -13 27 6.25e-01 2.00e+07 U235 nu-fission 6.30e-02 5.92e-03 -14 27 6.25e-01 2.00e+07 U238 fission 1.47e-02 1.41e-03 -15 27 6.25e-01 2.00e+07 U238 nu-fission 4.12e-02 4.02e-03 +0 21 0.00e+00 6.25e-01 U235 fission 1.93e-01 1.92e-02 cell energy low [eV] energy high [eV] nuclide score mean std. dev. +0 21 0.00e+00 6.25e-01 U235 nu-fission 4.70e-01 4.67e-02 cell energy low [eV] energy high [eV] nuclide score mean std. dev. +0 21 0.00e+00 6.25e-01 U238 fission 2.65e-07 2.60e-08 cell energy low [eV] energy high [eV] nuclide score mean std. dev. +0 21 0.00e+00 6.25e-01 U238 nu-fission 6.60e-07 6.47e-08 cell energy low [eV] energy high [eV] nuclide score mean std. dev. +0 21 6.25e-01 2.00e+07 U235 fission 3.39e-02 1.53e-03 cell energy low [eV] energy high [eV] nuclide score mean std. dev. +0 21 6.25e-01 2.00e+07 U235 nu-fission 8.30e-02 3.75e-03 cell energy low [eV] energy high [eV] nuclide score mean std. dev. +0 21 6.25e-01 2.00e+07 U238 fission 1.70e-02 2.01e-03 cell energy low [eV] energy high [eV] nuclide score mean std. dev. +0 21 6.25e-01 2.00e+07 U238 nu-fission 4.76e-02 6.12e-03 cell energy low [eV] energy high [eV] nuclide score mean std. dev. +0 27 0.00e+00 6.25e-01 U235 fission 7.61e-02 5.84e-03 cell energy low [eV] energy high [eV] nuclide score mean std. dev. +0 27 0.00e+00 6.25e-01 U235 nu-fission 1.85e-01 1.42e-02 cell energy low [eV] energy high [eV] nuclide score mean std. dev. +0 27 0.00e+00 6.25e-01 U238 fission 1.06e-07 7.62e-09 cell energy low [eV] energy high [eV] nuclide score mean std. dev. +0 27 0.00e+00 6.25e-01 U238 nu-fission 2.64e-07 1.90e-08 cell energy low [eV] energy high [eV] nuclide score mean std. dev. +0 27 6.25e-01 2.00e+07 U235 fission 1.69e-02 1.34e-03 cell energy low [eV] energy high [eV] nuclide score mean std. dev. +0 27 6.25e-01 2.00e+07 U235 nu-fission 4.15e-02 3.29e-03 cell energy low [eV] energy high [eV] nuclide score mean std. dev. +0 27 6.25e-01 2.00e+07 U238 fission 1.10e-02 1.69e-03 cell energy low [eV] energy high [eV] nuclide score mean std. dev. +0 27 6.25e-01 2.00e+07 U238 nu-fission 3.13e-02 5.35e-03 cell energy low [eV] energy high [eV] nuclide score mean std. dev. +0 21 0.00e+00 6.25e-01 U235 fission 1.93e-01 1.92e-02 +1 21 0.00e+00 6.25e-01 U235 nu-fission 4.70e-01 4.67e-02 +2 21 0.00e+00 6.25e-01 U238 fission 2.65e-07 2.60e-08 +3 21 0.00e+00 6.25e-01 U238 nu-fission 6.60e-07 6.47e-08 +4 21 6.25e-01 2.00e+07 U235 fission 3.39e-02 1.53e-03 +5 21 6.25e-01 2.00e+07 U235 nu-fission 8.30e-02 3.75e-03 +6 21 6.25e-01 2.00e+07 U238 fission 1.70e-02 2.01e-03 +7 21 6.25e-01 2.00e+07 U238 nu-fission 4.76e-02 6.12e-03 +8 27 0.00e+00 6.25e-01 U235 fission 7.61e-02 5.84e-03 +9 27 0.00e+00 6.25e-01 U235 nu-fission 1.85e-01 1.42e-02 +10 27 0.00e+00 6.25e-01 U238 fission 1.06e-07 7.62e-09 +11 27 0.00e+00 6.25e-01 U238 nu-fission 2.64e-07 1.90e-08 +12 27 6.25e-01 2.00e+07 U235 fission 1.69e-02 1.34e-03 +13 27 6.25e-01 2.00e+07 U235 nu-fission 4.15e-02 3.29e-03 +14 27 6.25e-01 2.00e+07 U238 fission 1.10e-02 1.69e-03 +15 27 6.25e-01 2.00e+07 U238 nu-fission 3.13e-02 5.35e-03 sum(distribcell) energy low [eV] energy high [eV] nuclide score mean std. dev. 0 (0, 100, 2000, 30000) 0.00e+00 6.25e-01 U235 fission 0.00e+00 0.00e+00 1 (0, 100, 2000, 30000) 0.00e+00 6.25e-01 U235 nu-fission 0.00e+00 0.00e+00 2 (0, 100, 2000, 30000) 0.00e+00 6.25e-01 U238 fission 0.00e+00 0.00e+00 3 (0, 100, 2000, 30000) 0.00e+00 6.25e-01 U238 nu-fission 0.00e+00 0.00e+00 -4 (0, 100, 2000, 30000) 6.25e-01 2.00e+07 U235 fission 9.53e-07 9.53e-07 -5 (0, 100, 2000, 30000) 6.25e-01 2.00e+07 U235 nu-fission 2.37e-06 2.37e-06 -6 (0, 100, 2000, 30000) 6.25e-01 2.00e+07 U238 fission 1.25e-08 1.25e-08 -7 (0, 100, 2000, 30000) 6.25e-01 2.00e+07 U238 nu-fission 3.15e-08 3.15e-08 +4 (0, 100, 2000, 30000) 6.25e-01 2.00e+07 U235 fission 0.00e+00 0.00e+00 +5 (0, 100, 2000, 30000) 6.25e-01 2.00e+07 U235 nu-fission 0.00e+00 0.00e+00 +6 (0, 100, 2000, 30000) 6.25e-01 2.00e+07 U238 fission 0.00e+00 0.00e+00 +7 (0, 100, 2000, 30000) 6.25e-01 2.00e+07 U238 nu-fission 0.00e+00 0.00e+00 8 (500, 5000, 50000) 0.00e+00 6.25e-01 U235 fission 0.00e+00 0.00e+00 9 (500, 5000, 50000) 0.00e+00 6.25e-01 U235 nu-fission 0.00e+00 0.00e+00 10 (500, 5000, 50000) 0.00e+00 6.25e-01 U238 fission 0.00e+00 0.00e+00 @@ -49,19 +49,19 @@ 14 (500, 5000, 50000) 6.25e-01 2.00e+07 U238 fission 0.00e+00 0.00e+00 15 (500, 5000, 50000) 6.25e-01 2.00e+07 U238 nu-fission 0.00e+00 0.00e+00 sum(mesh) energy low [eV] energy high [eV] nuclide score mean std. dev. -0 ((1, 1), (1, 2)) 0.00e+00 6.25e-01 U235 fission 6.73e-03 3.04e-03 -1 ((1, 1), (1, 2)) 0.00e+00 6.25e-01 U235 nu-fission 1.64e-02 7.40e-03 -2 ((1, 1), (1, 2)) 0.00e+00 6.25e-01 U238 fission 8.80e-09 3.84e-09 -3 ((1, 1), (1, 2)) 0.00e+00 6.25e-01 U238 nu-fission 2.19e-08 9.56e-09 -4 ((1, 1), (1, 2)) 6.25e-01 2.00e+07 U235 fission 9.89e-04 3.53e-04 -5 ((1, 1), (1, 2)) 6.25e-01 2.00e+07 U235 nu-fission 2.42e-03 8.60e-04 -6 ((1, 1), (1, 2)) 6.25e-01 2.00e+07 U238 fission 5.01e-04 2.13e-04 -7 ((1, 1), (1, 2)) 6.25e-01 2.00e+07 U238 nu-fission 1.36e-03 5.86e-04 -8 ((2, 1), (2, 2)) 0.00e+00 6.25e-01 U235 fission 1.63e-02 4.21e-03 -9 ((2, 1), (2, 2)) 0.00e+00 6.25e-01 U235 nu-fission 3.98e-02 1.02e-02 -10 ((2, 1), (2, 2)) 0.00e+00 6.25e-01 U238 fission 2.28e-08 5.90e-09 -11 ((2, 1), (2, 2)) 0.00e+00 6.25e-01 U238 nu-fission 5.67e-08 1.47e-08 -12 ((2, 1), (2, 2)) 6.25e-01 2.00e+07 U235 fission 1.79e-03 4.31e-04 -13 ((2, 1), (2, 2)) 6.25e-01 2.00e+07 U235 nu-fission 4.37e-03 1.05e-03 -14 ((2, 1), (2, 2)) 6.25e-01 2.00e+07 U238 fission 7.51e-04 2.51e-04 -15 ((2, 1), (2, 2)) 6.25e-01 2.00e+07 U238 nu-fission 2.02e-03 6.71e-04 +0 ((1, 1), (1, 2)) 0.00e+00 6.25e-01 U235 fission 1.94e-03 1.03e-03 +1 ((1, 1), (1, 2)) 0.00e+00 6.25e-01 U235 nu-fission 4.74e-03 2.50e-03 +2 ((1, 1), (1, 2)) 0.00e+00 6.25e-01 U238 fission 2.74e-09 1.42e-09 +3 ((1, 1), (1, 2)) 0.00e+00 6.25e-01 U238 nu-fission 6.83e-09 3.53e-09 +4 ((1, 1), (1, 2)) 6.25e-01 2.00e+07 U235 fission 9.02e-04 3.69e-04 +5 ((1, 1), (1, 2)) 6.25e-01 2.00e+07 U235 nu-fission 2.24e-03 9.12e-04 +6 ((1, 1), (1, 2)) 6.25e-01 2.00e+07 U238 fission 1.44e-03 8.42e-04 +7 ((1, 1), (1, 2)) 6.25e-01 2.00e+07 U238 nu-fission 4.37e-03 2.64e-03 +8 ((2, 1), (2, 2)) 0.00e+00 6.25e-01 U235 fission 1.27e-02 2.76e-03 +9 ((2, 1), (2, 2)) 0.00e+00 6.25e-01 U235 nu-fission 3.09e-02 6.72e-03 +10 ((2, 1), (2, 2)) 0.00e+00 6.25e-01 U238 fission 1.70e-08 3.57e-09 +11 ((2, 1), (2, 2)) 0.00e+00 6.25e-01 U238 nu-fission 4.25e-08 8.90e-09 +12 ((2, 1), (2, 2)) 6.25e-01 2.00e+07 U235 fission 1.43e-03 1.69e-04 +13 ((2, 1), (2, 2)) 6.25e-01 2.00e+07 U235 nu-fission 3.52e-03 4.20e-04 +14 ((2, 1), (2, 2)) 6.25e-01 2.00e+07 U238 fission 1.37e-03 2.98e-04 +15 ((2, 1), (2, 2)) 6.25e-01 2.00e+07 U238 nu-fission 4.16e-03 1.08e-03 diff --git a/tests/regression_tests/torus/results_true.dat b/tests/regression_tests/torus/results_true.dat index 42fb209cce3..84cd3c7a444 100644 --- a/tests/regression_tests/torus/results_true.dat +++ b/tests/regression_tests/torus/results_true.dat @@ -1,2 +1,2 @@ k-combined: -7.667201E-01 1.136882E-02 +7.666453E-01 1.478848E-02 diff --git a/tests/regression_tests/trace/results_true.dat b/tests/regression_tests/trace/results_true.dat index bbf03de9437..97b997ae6b9 100644 --- a/tests/regression_tests/trace/results_true.dat +++ b/tests/regression_tests/trace/results_true.dat @@ -1,2 +1,2 @@ k-combined: -3.070134E-01 3.900396E-03 +2.940336E-01 7.338463E-04 diff --git a/tests/regression_tests/track_output/results_true.dat b/tests/regression_tests/track_output/results_true.dat index 148b54a30b0..205ac3eecb6 100644 --- a/tests/regression_tests/track_output/results_true.dat +++ b/tests/regression_tests/track_output/results_true.dat @@ -143,76 +143,74 @@ neutron [((-9.469716e-01, -2.580266e-01, 1.414357e-01), (1.438873e-01, 2.365819e ((-3.920090e+00, -9.211794e-01, 2.908406e+00), (9.094673e-01, -3.659267e-01, -1.974002e-01), 9.547085e+00, 1.260840e-07, 1.000000e+00, 23, 2387, 1) ((-3.729948e+00, -9.976834e-01, 2.867135e+00), (-2.916959e-01, -6.494657e-01, -7.022164e-01), 8.590591e+00, 1.750036e-07, 1.000000e+00, 23, 2387, 1) ((-3.744933e+00, -1.031047e+00, 2.831062e+00), (-2.916959e-01, -6.494657e-01, -7.022164e-01), 8.590591e+00, 1.876753e-07, 0.000000e+00, 23, 2387, 1)] -neutron [((6.474155e+00, -5.192870e+00, 5.413003e+00), (-9.112559e-01, 3.950037e-01, 1.165536e-01), 2.052226e+06, 4.450859e-05, 1.000000e+00, 21, 2367, 2) - ((6.037250e+00, -5.003484e+00, 5.468885e+00), (-9.112559e-01, 3.950037e-01, 1.165536e-01), 2.052226e+06, 4.450883e-05, 1.000000e+00, 22, 2367, 3) - ((5.942573e+00, -4.962444e+00, 5.480995e+00), (-9.112559e-01, 3.950037e-01, 1.165536e-01), 2.052226e+06, 4.450889e-05, 1.000000e+00, 23, 2367, 1) - ((5.861800e+00, -4.927431e+00, 5.491326e+00), (-9.518772e-01, -3.064714e-01, -2.259335e-03), 1.141417e+06, 4.450893e-05, 1.000000e+00, 23, 2367, 1) - ((5.725160e+00, -4.971424e+00, 5.491002e+00), (-9.518772e-01, -3.064714e-01, -2.259335e-03), 1.141417e+06, 4.450903e-05, 1.000000e+00, 23, 2366, 1) - ((5.494150e+00, -5.045802e+00, 5.490454e+00), (-9.518772e-01, -3.064714e-01, -2.259335e-03), 1.141417e+06, 4.450919e-05, 1.000000e+00, 22, 2366, 3) - ((5.392865e+00, -5.078412e+00, 5.490213e+00), (-9.518772e-01, -3.064714e-01, -2.259335e-03), 1.141417e+06, 4.450927e-05, 1.000000e+00, 21, 2366, 2) - ((4.612759e+00, -5.329579e+00, 5.488362e+00), (-9.518772e-01, -3.064714e-01, -2.259335e-03), 1.141417e+06, 4.450982e-05, 1.000000e+00, 22, 2366, 3) - ((4.511474e+00, -5.362189e+00, 5.488121e+00), (-9.518772e-01, -3.064714e-01, -2.259335e-03), 1.141417e+06, 4.450989e-05, 1.000000e+00, 23, 2366, 1) - ((4.089400e+00, -5.498082e+00, 5.487119e+00), (-9.518772e-01, -3.064714e-01, -2.259335e-03), 1.141417e+06, 4.451019e-05, 1.000000e+00, 23, 2365, 1) - ((3.384113e+00, -5.725160e+00, 5.485445e+00), (-9.518772e-01, -3.064714e-01, -2.259335e-03), 1.141417e+06, 4.451069e-05, 1.000000e+00, 23, 2351, 1) - ((2.453640e+00, -6.024740e+00, 5.483237e+00), (-9.518772e-01, -3.064714e-01, -2.259335e-03), 1.141417e+06, 4.451136e-05, 1.000000e+00, 23, 2350, 1) - ((2.086813e+00, -6.142846e+00, 5.482366e+00), (-9.518772e-01, -3.064714e-01, -2.259335e-03), 1.141417e+06, 4.451162e-05, 1.000000e+00, 22, 2350, 3) - ((1.993594e+00, -6.172859e+00, 5.482145e+00), (-9.518772e-01, -3.064714e-01, -2.259335e-03), 1.141417e+06, 4.451168e-05, 1.000000e+00, 21, 2350, 2) - ((1.325704e+00, -6.387896e+00, 5.480560e+00), (8.986086e-01, -4.380574e-01, -2.466265e-02), 9.775839e+05, 4.451216e-05, 1.000000e+00, 21, 2350, 2) - ((2.061403e+00, -6.746538e+00, 5.460368e+00), (-7.778764e-01, 2.051975e-01, -5.939716e-01), 7.813219e+05, 4.451276e-05, 1.000000e+00, 21, 2350, 2) - ((1.122794e+00, -6.498940e+00, 4.743664e+00), (-7.778764e-01, 2.051975e-01, -5.939716e-01), 7.813219e+05, 4.451375e-05, 1.000000e+00, 22, 2350, 3) - ((1.036483e+00, -6.476172e+00, 4.677758e+00), (-7.778764e-01, 2.051975e-01, -5.939716e-01), 7.813219e+05, 4.451384e-05, 1.000000e+00, 23, 2350, 1) - ((8.178800e-01, -6.418507e+00, 4.510837e+00), (-7.778764e-01, 2.051975e-01, -5.939716e-01), 7.813219e+05, 4.451407e-05, 1.000000e+00, 23, 2349, 1) - ((5.725264e-01, -6.353784e+00, 4.323490e+00), (-7.778764e-01, 2.051975e-01, -5.939716e-01), 7.813219e+05, 4.451432e-05, 1.000000e+00, 22, 2349, 3) - ((4.668297e-01, -6.325902e+00, 4.242782e+00), (-7.778764e-01, 2.051975e-01, -5.939716e-01), 7.813219e+05, 4.451444e-05, 1.000000e+00, 21, 2349, 2) - ((-2.989816e-01, -6.123888e+00, 3.658023e+00), (-7.778764e-01, 2.051975e-01, -5.939716e-01), 7.813219e+05, 4.451524e-05, 1.000000e+00, 22, 2349, 3) - ((-4.046782e-01, -6.096006e+00, 3.577315e+00), (-7.778764e-01, 2.051975e-01, -5.939716e-01), 7.813219e+05, 4.451535e-05, 1.000000e+00, 23, 2349, 1) - ((-8.040445e-01, -5.990656e+00, 3.272367e+00), (-7.989001e-01, -1.890553e-01, -5.709787e-01), 6.637114e+05, 4.451577e-05, 1.000000e+00, 23, 2349, 1) - ((-8.178800e-01, -5.993930e+00, 3.262478e+00), (-7.989001e-01, -1.890553e-01, -5.709787e-01), 6.637114e+05, 4.451579e-05, 1.000000e+00, 23, 2348, 1) - ((-1.077910e+00, -6.055465e+00, 3.076633e+00), (-4.487118e-01, 1.670254e-01, -8.779295e-01), 4.550608e+05, 4.451608e-05, 1.000000e+00, 23, 2348, 1) - ((-1.215611e+00, -6.004208e+00, 2.807214e+00), (7.872329e-01, 4.481433e-01, -4.235941e-01), 3.544207e+03, 4.451641e-05, 1.000000e+00, 23, 2348, 1) - ((-1.100296e+00, -5.938564e+00, 2.745166e+00), (8.468456e-01, 5.310954e-01, 2.811230e-02), 2.812308e+03, 4.451819e-05, 1.000000e+00, 23, 2348, 1) - ((-8.178800e-01, -5.761448e+00, 2.754541e+00), (8.468456e-01, 5.310954e-01, 2.811230e-02), 2.812308e+03, 4.452273e-05, 1.000000e+00, 23, 2349, 1) - ((-7.600184e-01, -5.725160e+00, 2.756462e+00), (8.468456e-01, 5.310954e-01, 2.811230e-02), 2.812308e+03, 4.452366e-05, 1.000000e+00, 23, 2363, 1) - ((-2.947156e-01, -5.433347e+00, 2.771908e+00), (8.468456e-01, 5.310954e-01, 2.811230e-02), 2.812308e+03, 4.453115e-05, 1.000000e+00, 22, 2363, 3) - ((-2.073333e-01, -5.378546e+00, 2.774809e+00), (8.468456e-01, 5.310954e-01, 2.811230e-02), 2.812308e+03, 4.453256e-05, 1.000000e+00, 21, 2363, 2) - ((-1.746705e-01, -5.358062e+00, 2.775893e+00), (5.278774e-01, 5.459205e-01, 6.506276e-01), 2.806481e+03, 4.453309e-05, 1.000000e+00, 21, 2363, 2) - ((-8.681718e-02, -5.267205e+00, 2.884176e+00), (-8.975500e-01, 8.968076e-04, -4.409119e-01), 2.764929e+03, 4.453536e-05, 1.000000e+00, 21, 2363, 2) - ((-3.684221e-01, -5.266924e+00, 2.745840e+00), (-8.975500e-01, 8.968076e-04, -4.409119e-01), 2.764929e+03, 4.453967e-05, 1.000000e+00, 22, 2363, 3) - ((-4.840903e-01, -5.266809e+00, 2.689019e+00), (-8.975500e-01, 8.968076e-04, -4.409119e-01), 2.764929e+03, 4.454144e-05, 1.000000e+00, 23, 2363, 1) - ((-8.178800e-01, -5.266475e+00, 2.525049e+00), (-8.975500e-01, 8.968076e-04, -4.409119e-01), 2.764929e+03, 4.454656e-05, 1.000000e+00, 23, 2362, 1) - ((-1.151175e+00, -5.266142e+00, 2.361321e+00), (-8.975500e-01, 8.968076e-04, -4.409119e-01), 2.764929e+03, 4.455166e-05, 1.000000e+00, 22, 2362, 3) - ((-1.266464e+00, -5.266027e+00, 2.304687e+00), (-8.975500e-01, 8.968076e-04, -4.409119e-01), 2.764929e+03, 4.455343e-05, 1.000000e+00, 21, 2362, 2) - ((-2.005772e+00, -5.265288e+00, 1.941509e+00), (-8.975500e-01, 8.968076e-04, -4.409119e-01), 2.764929e+03, 4.456475e-05, 1.000000e+00, 22, 2362, 3) - ((-2.121061e+00, -5.265173e+00, 1.884875e+00), (-8.975500e-01, 8.968076e-04, -4.409119e-01), 2.764929e+03, 4.456652e-05, 1.000000e+00, 23, 2362, 1) - ((-2.393759e+00, -5.264900e+00, 1.750915e+00), (-9.607392e-01, -1.749373e-01, 2.153533e-01), 1.619469e+03, 4.457070e-05, 1.000000e+00, 23, 2362, 1) - ((-2.453640e+00, -5.275804e+00, 1.764337e+00), (-9.607392e-01, -1.749373e-01, 2.153533e-01), 1.619469e+03, 4.457182e-05, 1.000000e+00, 23, 2361, 1) - ((-2.470658e+00, -5.278903e+00, 1.768152e+00), (-6.728606e-01, -2.916408e-01, -6.798561e-01), 4.873672e+02, 4.457214e-05, 1.000000e+00, 23, 2361, 1) - ((-3.463692e+00, -5.709318e+00, 7.647939e-01), (-2.249302e-01, -9.330150e-01, -2.808728e-01), 1.790901e+02, 4.462047e-05, 1.000000e+00, 23, 2361, 1) - ((-3.467511e+00, -5.725160e+00, 7.600247e-01), (-2.249302e-01, -9.330150e-01, -2.808728e-01), 1.790901e+02, 4.462139e-05, 1.000000e+00, 23, 2347, 1) - ((-3.533785e+00, -6.000066e+00, 6.772677e-01), (-2.249302e-01, -9.330150e-01, -2.808728e-01), 1.790901e+02, 4.463730e-05, 1.000000e+00, 22, 2347, 3) - ((-3.562245e+00, -6.118119e+00, 6.417291e-01), (-2.249302e-01, -9.330150e-01, -2.808728e-01), 1.790901e+02, 4.464414e-05, 1.000000e+00, 21, 2347, 2) - ((-3.723934e+00, -6.788806e+00, 4.398272e-01), (-2.249302e-01, -9.330150e-01, -2.808728e-01), 1.790901e+02, 4.468297e-05, 1.000000e+00, 22, 2347, 3) - ((-3.752394e+00, -6.906859e+00, 4.042885e-01), (-2.249302e-01, -9.330150e-01, -2.808728e-01), 1.790901e+02, 4.468981e-05, 1.000000e+00, 23, 2347, 1) - ((-3.848515e+00, -7.305571e+00, 2.842613e-01), (6.498448e-01, -3.243413e-01, -6.873896e-01), 2.328979e+01, 4.471290e-05, 1.000000e+00, 23, 2347, 1) - ((-3.737619e+00, -7.360920e+00, 1.669579e-01), (6.498448e-01, -3.243413e-01, -6.873896e-01), 2.328979e+01, 4.473846e-05, 1.000000e+00, 11, 1750, 1) - ((-3.574308e+00, -7.442429e+00, -5.788041e-03), (6.794505e-01, 3.157106e-01, -6.623246e-01), 1.590416e+01, 4.477611e-05, 1.000000e+00, 11, 1750, 1) - ((-3.398889e+00, -7.360920e+00, -1.767852e-01), (6.794505e-01, 3.157106e-01, -6.623246e-01), 1.590416e+01, 4.482292e-05, 1.000000e+00, 23, 2347, 1) - ((-2.904712e+00, -7.131298e+00, -6.585068e-01), (-4.836985e-02, -2.819627e-01, -9.582053e-01), 2.610202e+00, 4.495477e-05, 1.000000e+00, 23, 2347, 1) - ((-2.923579e+00, -7.241285e+00, -1.032280e+00), (-6.089807e-01, -2.347428e-01, -7.576532e-01), 1.671268e+00, 4.512933e-05, 1.000000e+00, 23, 2347, 1) - ((-3.012516e+00, -7.275567e+00, -1.142930e+00), (3.221931e-01, -5.406104e-01, -7.771306e-01), 1.275709e-01, 4.521100e-05, 1.000000e+00, 23, 2347, 1) - ((-2.984032e+00, -7.323360e+00, -1.211633e+00), (-8.629564e-01, -1.719529e-01, -4.751195e-01), 1.897424e-02, 4.538995e-05, 1.000000e+00, 23, 2347, 1) - ((-3.172528e+00, -7.360920e+00, -1.315413e+00), (-8.629564e-01, -1.719529e-01, -4.751195e-01), 1.897424e-02, 4.653641e-05, 1.000000e+00, 11, 1750, 1) - ((-3.602328e+00, -7.446562e+00, -1.552049e+00), (-1.530190e-01, -7.092235e-01, 6.881767e-01), 1.306951e-02, 4.915052e-05, 1.000000e+00, 11, 1750, 1) - ((-3.625236e+00, -7.552741e+00, -1.449021e+00), (-1.976867e-01, 2.826475e-01, 9.386322e-01), 2.145548e-02, 5.009730e-05, 1.000000e+00, 11, 1750, 1) - ((-3.636290e+00, -7.536936e+00, -1.396537e+00), (1.266676e-01, 2.265353e-01, -9.657314e-01), 1.815564e-02, 5.037329e-05, 1.000000e+00, 11, 1750, 1) - ((-3.621792e+00, -7.511008e+00, -1.507069e+00), (-4.275550e-01, 8.441443e-01, 3.234457e-01), 1.848049e-02, 5.098741e-05, 1.000000e+00, 11, 1750, 1) - ((-3.697811e+00, -7.360920e+00, -1.449560e+00), (-4.275550e-01, 8.441443e-01, 3.234457e-01), 1.848049e-02, 5.193300e-05, 1.000000e+00, 23, 2347, 1) - ((-3.703436e+00, -7.349814e+00, -1.445305e+00), (-7.832167e-01, 5.713670e-01, 2.451762e-01), 1.861705e-02, 5.200297e-05, 1.000000e+00, 23, 2347, 1) - ((-3.823243e+00, -7.262414e+00, -1.407801e+00), (-9.917106e-01, 5.069164e-02, 1.180695e-01), 4.703334e-02, 5.281350e-05, 1.000000e+00, 23, 2347, 1) - ((-4.089400e+00, -7.248809e+00, -1.376113e+00), (-9.917106e-01, 5.069164e-02, 1.180695e-01), 4.703334e-02, 5.370820e-05, 1.000000e+00, 23, 2346, 1) - ((-4.131081e+00, -7.246679e+00, -1.371151e+00), (9.108632e-01, -4.018526e-01, 9.403556e-02), 8.057393e-02, 5.384831e-05, 1.000000e+00, 23, 2346, 1) - ((-4.089400e+00, -7.265067e+00, -1.366848e+00), (9.108632e-01, -4.018526e-01, 9.403556e-02), 8.057393e-02, 5.396486e-05, 1.000000e+00, 23, 2347, 1) - ((-3.872134e+00, -7.360920e+00, -1.344418e+00), (9.108632e-01, -4.018526e-01, 9.403556e-02), 8.057393e-02, 5.457240e-05, 1.000000e+00, 11, 1750, 1) - ((-3.673062e+00, -7.448746e+00, -1.323866e+00), (2.976906e-01, 8.174443e-01, -4.931178e-01), 5.538060e-02, 5.512905e-05, 1.000000e+00, 11, 1750, 1) - ((-3.658580e+00, -7.408978e+00, -1.347855e+00), (-5.409120e-01, 8.345378e-01, -1.046943e-01), 8.122576e-02, 5.527851e-05, 1.000000e+00, 11, 1750, 1) - ((-3.682981e+00, -7.371331e+00, -1.352578e+00), (-5.409120e-01, 8.345378e-01, -1.046943e-01), 8.122576e-02, 5.539295e-05, 0.000000e+00, 11, 1750, 1)] +neutron [((6.474155e+00, -5.192870e+00, 5.413003e+00), (-9.597810e-01, -1.081529e-01, -2.590820e-01), 9.900333e+05, 4.450859e-05, 1.000000e+00, 21, 2367, 2) + ((6.142109e+00, -5.230286e+00, 5.323371e+00), (-9.597810e-01, -1.081529e-01, -2.590820e-01), 9.900333e+05, 4.450884e-05, 1.000000e+00, 22, 2367, 3) + ((6.041244e+00, -5.241652e+00, 5.296144e+00), (-9.597810e-01, -1.081529e-01, -2.590820e-01), 9.900333e+05, 4.450892e-05, 1.000000e+00, 23, 2367, 1) + ((6.004037e+00, -5.245845e+00, 5.286100e+00), (-6.195407e-01, -7.332723e-01, -2.801446e-01), 5.512347e+05, 4.450895e-05, 1.000000e+00, 23, 2367, 1) + ((5.725160e+00, -5.575916e+00, 5.159997e+00), (-6.195407e-01, -7.332723e-01, -2.801446e-01), 5.512347e+05, 4.450939e-05, 1.000000e+00, 23, 2366, 1) + ((5.599064e+00, -5.725160e+00, 5.102979e+00), (-6.195407e-01, -7.332723e-01, -2.801446e-01), 5.512347e+05, 4.450958e-05, 1.000000e+00, 23, 2352, 1) + ((5.296886e+00, -6.082810e+00, 4.966340e+00), (-6.195407e-01, -7.332723e-01, -2.801446e-01), 5.512347e+05, 4.451006e-05, 1.000000e+00, 22, 2352, 3) + ((5.240003e+00, -6.150135e+00, 4.940619e+00), (-6.195407e-01, -7.332723e-01, -2.801446e-01), 5.512347e+05, 4.451015e-05, 1.000000e+00, 21, 2352, 2) + ((4.752072e+00, -6.727638e+00, 4.719986e+00), (3.721510e-02, -9.647240e-01, 2.606197e-01), 5.045933e+05, 4.451092e-05, 1.000000e+00, 21, 2352, 2) + ((4.764028e+00, -7.037568e+00, 4.803713e+00), (3.721510e-02, -9.647240e-01, 2.606197e-01), 5.045933e+05, 4.451124e-05, 1.000000e+00, 22, 2352, 3) + ((4.767580e+00, -7.129630e+00, 4.828584e+00), (3.721510e-02, -9.647240e-01, 2.606197e-01), 5.045933e+05, 4.451134e-05, 1.000000e+00, 23, 2352, 1) + ((4.776502e+00, -7.360920e+00, 4.891066e+00), (3.721510e-02, -9.647240e-01, 2.606197e-01), 5.045933e+05, 4.451158e-05, 1.000000e+00, 23, 2338, 1) + ((4.778286e+00, -7.407162e+00, 4.903559e+00), (4.571054e-01, -3.889563e-01, 7.998548e-01), 1.818775e+05, 4.451163e-05, 1.000000e+00, 23, 2338, 1) + ((4.981926e+00, -7.580442e+00, 5.259893e+00), (4.571054e-01, -3.889563e-01, 7.998548e-01), 1.818775e+05, 4.451239e-05, 1.000000e+00, 22, 2338, 3) + ((5.154144e+00, -7.726985e+00, 5.561246e+00), (4.571054e-01, -3.889563e-01, 7.998548e-01), 1.818775e+05, 4.451303e-05, 1.000000e+00, 21, 2338, 2) + ((5.313757e+00, -7.862801e+00, 5.840540e+00), (4.571054e-01, -3.889563e-01, 7.998548e-01), 1.818775e+05, 4.451362e-05, 1.000000e+00, 22, 2338, 3) + ((5.485976e+00, -8.009344e+00, 6.141893e+00), (4.571054e-01, -3.889563e-01, 7.998548e-01), 1.818775e+05, 4.451426e-05, 1.000000e+00, 23, 2338, 1) + ((5.725160e+00, -8.212869e+00, 6.560424e+00), (4.571054e-01, -3.889563e-01, 7.998548e-01), 1.818775e+05, 4.451515e-05, 1.000000e+00, 23, 2339, 1) + ((6.004950e+00, -8.450945e+00, 7.050007e+00), (4.571054e-01, -3.889563e-01, 7.998548e-01), 1.818775e+05, 4.451618e-05, 1.000000e+00, 22, 2339, 3) + ((6.360530e+00, -8.753512e+00, 7.672210e+00), (4.571054e-01, -3.889563e-01, 7.998548e-01), 1.818775e+05, 4.451750e-05, 1.000000e+00, 23, 2339, 1) + ((6.646303e+00, -8.996680e+00, 8.172263e+00), (4.571054e-01, -3.889563e-01, 7.998548e-01), 1.818775e+05, 4.451856e-05, 1.000000e+00, 23, 2326, 1) + ((6.702490e+00, -9.044491e+00, 8.270582e+00), (2.028534e-01, -9.607965e-01, -1.889990e-01), 1.805295e+04, 4.451877e-05, 1.000000e+00, 23, 2326, 1) + ((6.745127e+00, -9.246436e+00, 8.230857e+00), (2.028534e-01, -9.607965e-01, -1.889990e-01), 1.805295e+04, 4.451990e-05, 1.000000e+00, 22, 2326, 3) + ((6.767219e+00, -9.351070e+00, 8.210274e+00), (2.028534e-01, -9.607965e-01, -1.889990e-01), 1.805295e+04, 4.452049e-05, 1.000000e+00, 21, 2326, 2) + ((6.885344e+00, -9.910562e+00, 8.100216e+00), (-3.857824e-01, -3.006310e-01, -8.722344e-01), 1.795765e+04, 4.452362e-05, 1.000000e+00, 21, 2326, 2) + ((6.803615e+00, -9.974251e+00, 7.915431e+00), (-7.096135e-01, 6.778415e-01, 1.923009e-01), 1.779137e+04, 4.452476e-05, 1.000000e+00, 21, 2326, 2) + ((6.334815e+00, -9.526441e+00, 8.042473e+00), (-1.748210e-01, 9.821705e-01, -6.912779e-02), 1.729669e+04, 4.452835e-05, 1.000000e+00, 21, 2326, 2) + ((6.304853e+00, -9.358111e+00, 8.030625e+00), (-1.748210e-01, 9.821705e-01, -6.912779e-02), 1.729669e+04, 4.452929e-05, 1.000000e+00, 22, 2326, 3) + ((6.288777e+00, -9.267793e+00, 8.024268e+00), (-1.748210e-01, 9.821705e-01, -6.912779e-02), 1.729669e+04, 4.452979e-05, 1.000000e+00, 23, 2326, 1) + ((6.275105e+00, -9.190979e+00, 8.018862e+00), (-7.174952e-01, 5.897531e-01, -3.706642e-01), 9.207900e+03, 4.453022e-05, 1.000000e+00, 23, 2326, 1) + ((6.038719e+00, -8.996680e+00, 7.896743e+00), (-7.174952e-01, 5.897531e-01, -3.706642e-01), 9.207900e+03, 4.453271e-05, 1.000000e+00, 23, 2339, 1) + ((6.028805e+00, -8.988531e+00, 7.891621e+00), (-2.989803e-01, 4.300779e-01, -8.518473e-01), 5.653774e+03, 4.453281e-05, 1.000000e+00, 23, 2339, 1) + ((5.725160e+00, -8.551743e+00, 7.026484e+00), (-2.989803e-01, 4.300779e-01, -8.518473e-01), 5.653774e+03, 4.454257e-05, 1.000000e+00, 23, 2338, 1) + ((5.507324e+00, -8.238390e+00, 6.405832e+00), (-2.989803e-01, 4.300779e-01, -8.518473e-01), 5.653774e+03, 4.454958e-05, 1.000000e+00, 22, 2338, 3) + ((5.417387e+00, -8.109017e+00, 6.149584e+00), (-2.989803e-01, 4.300779e-01, -8.518473e-01), 5.653774e+03, 4.455247e-05, 1.000000e+00, 21, 2338, 2) + ((5.162017e+00, -7.741671e+00, 5.421991e+00), (7.522612e-01, -1.036919e-01, -6.506543e-01), 5.619608e+03, 4.456069e-05, 1.000000e+00, 21, 2338, 2) + ((5.184146e+00, -7.744722e+00, 5.402851e+00), (7.522612e-01, -1.036919e-01, -6.506543e-01), 5.619608e+03, 4.456097e-05, 1.000000e+00, 22, 2338, 3) + ((5.348057e+00, -7.767315e+00, 5.261079e+00), (7.522612e-01, -1.036919e-01, -6.506543e-01), 5.619608e+03, 4.456307e-05, 1.000000e+00, 23, 2338, 1) + ((5.581568e+00, -7.799502e+00, 5.059108e+00), (2.029690e-01, -4.131185e-01, -8.877706e-01), 3.359662e+03, 4.456606e-05, 1.000000e+00, 23, 2338, 1) + ((5.707007e+00, -8.054818e+00, 4.510447e+00), (3.894183e-01, -7.459178e-01, -5.403333e-01), 3.303714e+03, 4.457377e-05, 1.000000e+00, 23, 2338, 1) + ((5.725160e+00, -8.089590e+00, 4.485259e+00), (3.894183e-01, -7.459178e-01, -5.403333e-01), 3.303714e+03, 4.457436e-05, 1.000000e+00, 23, 2339, 1) + ((5.750893e+00, -8.138881e+00, 4.449554e+00), (-2.879829e-01, -5.545287e-01, -7.807456e-01), 1.744626e+03, 4.457519e-05, 1.000000e+00, 23, 2339, 1) + ((5.725160e+00, -8.188431e+00, 4.379790e+00), (-2.879829e-01, -5.545287e-01, -7.807456e-01), 1.744626e+03, 4.457674e-05, 1.000000e+00, 23, 2338, 1) + ((5.532143e+00, -8.560097e+00, 3.856504e+00), (-6.185172e-01, -7.766669e-01, -1.192687e-01), 8.559363e+02, 4.458834e-05, 1.000000e+00, 23, 2338, 1) + ((5.522333e+00, -8.572415e+00, 3.854613e+00), (-3.452053e-01, -2.657159e-02, -9.381510e-01), 9.272133e+01, 4.458873e-05, 1.000000e+00, 23, 2338, 1) + ((5.366948e+00, -8.584375e+00, 3.432328e+00), (4.761677e-01, -8.187551e-01, -3.207871e-01), 6.522002e+00, 4.462253e-05, 1.000000e+00, 23, 2338, 1) + ((5.516634e+00, -8.841755e+00, 3.331487e+00), (4.237115e-01, -6.022578e-01, 6.765752e-01), 9.968491e-01, 4.471152e-05, 1.000000e+00, 23, 2338, 1) + ((5.625629e+00, -8.996680e+00, 3.505530e+00), (4.237115e-01, -6.022578e-01, 6.765752e-01), 9.968491e-01, 4.489779e-05, 1.000000e+00, 23, 2325, 1) + ((5.723424e+00, -9.135684e+00, 3.661687e+00), (7.706137e-01, -6.358415e-01, 4.312932e-02), 5.695813e-01, 4.506493e-05, 1.000000e+00, 23, 2325, 1) + ((5.725160e+00, -9.137116e+00, 3.661784e+00), (7.706137e-01, -6.358415e-01, 4.312932e-02), 5.695813e-01, 4.506708e-05, 1.000000e+00, 23, 2326, 1) + ((6.079210e+00, -9.429247e+00, 3.681599e+00), (7.706137e-01, -6.358415e-01, 4.312932e-02), 5.695813e-01, 4.550721e-05, 1.000000e+00, 22, 2326, 3) + ((6.147194e+00, -9.485341e+00, 3.685404e+00), (7.706137e-01, -6.358415e-01, 4.312932e-02), 5.695813e-01, 4.559172e-05, 1.000000e+00, 21, 2326, 2) + ((6.864744e+00, -1.007740e+01, 3.725564e+00), (3.888690e-01, -8.895503e-01, -2.397522e-01), 5.691123e-01, 4.648372e-05, 1.000000e+00, 21, 2326, 2) + ((6.908415e+00, -1.017730e+01, 3.698639e+00), (3.888690e-01, -8.895503e-01, -2.397522e-01), 5.691123e-01, 4.659135e-05, 1.000000e+00, 22, 2326, 3) + ((6.945959e+00, -1.026318e+01, 3.675492e+00), (3.888690e-01, -8.895503e-01, -2.397522e-01), 5.691123e-01, 4.668388e-05, 1.000000e+00, 23, 2326, 1) + ((6.966122e+00, -1.030930e+01, 3.663060e+00), (7.493672e-01, -6.303754e-01, 2.026713e-01), 2.849108e-01, 4.673357e-05, 1.000000e+00, 23, 2326, 1) + ((7.350254e+00, -1.063244e+01, 3.766951e+00), (7.493672e-01, -6.303754e-01, 2.026713e-01), 2.849108e-01, 4.742789e-05, 1.000000e+00, 23, 2311, 1) + ((7.360920e+00, -1.064141e+01, 3.769836e+00), (7.493672e-01, -6.303754e-01, 2.026713e-01), 2.849108e-01, 4.744717e-05, 1.000000e+00, 23, 2312, 1) + ((7.449168e+00, -1.071565e+01, 3.793703e+00), (1.626881e-01, -1.480918e-01, 9.755006e-01), 2.025207e-01, 4.760667e-05, 1.000000e+00, 23, 2312, 1) + ((7.495754e+00, -1.075805e+01, 4.073039e+00), (-1.185723e-01, -6.787603e-01, 7.247241e-01), 2.121260e-01, 4.806671e-05, 1.000000e+00, 23, 2312, 1) + ((7.451135e+00, -1.101347e+01, 4.345753e+00), (-4.978877e-01, -3.750598e-02, 8.664301e-01), 2.609768e-02, 4.865741e-05, 1.000000e+00, 23, 2312, 1) + ((7.426401e+00, -1.101533e+01, 4.388795e+00), (9.221835e-01, -6.407530e-02, 3.814078e-01), 2.031765e-02, 4.887973e-05, 1.000000e+00, 23, 2312, 1) + ((7.438938e+00, -1.101621e+01, 4.393980e+00), (8.832139e-01, 3.563144e-01, 3.049151e-01), 2.038019e-02, 4.894869e-05, 1.000000e+00, 23, 2312, 1) + ((7.578355e+00, -1.095996e+01, 4.442112e+00), (5.564936e-01, 8.135970e-01, 1.684482e-01), 4.869241e-02, 4.974811e-05, 1.000000e+00, 23, 2312, 1) + ((7.802376e+00, -1.063244e+01, 4.509922e+00), (5.564936e-01, 8.135970e-01, 1.684482e-01), 4.869241e-02, 5.106705e-05, 1.000000e+00, 23, 2327, 1) + ((7.826035e+00, -1.059785e+01, 4.517083e+00), (-8.227572e-01, -5.662268e-01, 4.957651e-02), 8.202195e-02, 5.120634e-05, 1.000000e+00, 23, 2327, 1) + ((7.775776e+00, -1.063244e+01, 4.520112e+00), (-8.227572e-01, -5.662268e-01, 4.957651e-02), 8.202195e-02, 5.136055e-05, 1.000000e+00, 23, 2312, 1) + ((7.360920e+00, -1.091795e+01, 4.545109e+00), (-8.227572e-01, -5.662268e-01, 4.957651e-02), 8.202195e-02, 5.263343e-05, 1.000000e+00, 23, 2311, 1) + ((7.180267e+00, -1.104227e+01, 4.555995e+00), (5.568446e-01, -6.706436e-01, -4.900625e-01), 5.560680e-02, 5.318772e-05, 1.000000e+00, 23, 2311, 1) + ((7.207392e+00, -1.107494e+01, 4.532123e+00), (9.936512e-01, 4.529367e-02, -1.029847e-01), 8.142074e-02, 5.333706e-05, 1.000000e+00, 23, 2311, 1) + ((7.252245e+00, -1.107290e+01, 4.527474e+00), (9.936512e-01, 4.529367e-02, -1.029847e-01), 8.142074e-02, 5.345144e-05, 0.000000e+00, 23, 2311, 1)] diff --git a/tests/regression_tests/translation/results_true.dat b/tests/regression_tests/translation/results_true.dat index f832aa29643..6e03d2224fe 100644 --- a/tests/regression_tests/translation/results_true.dat +++ b/tests/regression_tests/translation/results_true.dat @@ -1,2 +1,2 @@ k-combined: -4.087580E-01 4.466279E-03 +4.076610E-01 6.454244E-03 diff --git a/tests/regression_tests/trigger_batch_interval/results_true.dat b/tests/regression_tests/trigger_batch_interval/results_true.dat index 0571d6c4f9e..92fa99d87af 100644 --- a/tests/regression_tests/trigger_batch_interval/results_true.dat +++ b/tests/regression_tests/trigger_batch_interval/results_true.dat @@ -1,28 +1,28 @@ k-combined: -9.767929E-01 5.327552E-03 +9.863217E-01 6.499354E-03 tally 1: -1.405471E+01 -1.976444E+01 -3.186139E+00 -1.015515E+00 -3.088643E+00 -9.542994E-01 -1.086857E+01 -1.182011E+01 -1.405471E+01 -1.976444E+01 -3.186139E+00 -1.015515E+00 -3.088643E+00 -9.542994E-01 -1.086857E+01 -1.182011E+01 +1.423436E+01 +2.027258E+01 +3.223740E+00 +1.039872E+00 +3.125016E+00 +9.771709E-01 +1.101062E+01 +1.212977E+01 +1.423436E+01 +2.027258E+01 +3.223740E+00 +1.039872E+00 +3.125016E+00 +9.771709E-01 +1.101062E+01 +1.212977E+01 tally 2: -1.405471E+01 -1.976444E+01 -3.186139E+00 -1.015515E+00 -3.088643E+00 -9.542994E-01 -1.086857E+01 -1.182011E+01 +1.423436E+01 +2.027258E+01 +3.223740E+00 +1.039872E+00 +3.125016E+00 +9.771709E-01 +1.101062E+01 +1.212977E+01 diff --git a/tests/regression_tests/trigger_no_batch_interval/results_true.dat b/tests/regression_tests/trigger_no_batch_interval/results_true.dat index 0571d6c4f9e..92fa99d87af 100644 --- a/tests/regression_tests/trigger_no_batch_interval/results_true.dat +++ b/tests/regression_tests/trigger_no_batch_interval/results_true.dat @@ -1,28 +1,28 @@ k-combined: -9.767929E-01 5.327552E-03 +9.863217E-01 6.499354E-03 tally 1: -1.405471E+01 -1.976444E+01 -3.186139E+00 -1.015515E+00 -3.088643E+00 -9.542994E-01 -1.086857E+01 -1.182011E+01 -1.405471E+01 -1.976444E+01 -3.186139E+00 -1.015515E+00 -3.088643E+00 -9.542994E-01 -1.086857E+01 -1.182011E+01 +1.423436E+01 +2.027258E+01 +3.223740E+00 +1.039872E+00 +3.125016E+00 +9.771709E-01 +1.101062E+01 +1.212977E+01 +1.423436E+01 +2.027258E+01 +3.223740E+00 +1.039872E+00 +3.125016E+00 +9.771709E-01 +1.101062E+01 +1.212977E+01 tally 2: -1.405471E+01 -1.976444E+01 -3.186139E+00 -1.015515E+00 -3.088643E+00 -9.542994E-01 -1.086857E+01 -1.182011E+01 +1.423436E+01 +2.027258E+01 +3.223740E+00 +1.039872E+00 +3.125016E+00 +9.771709E-01 +1.101062E+01 +1.212977E+01 diff --git a/tests/regression_tests/trigger_no_status/results_true.dat b/tests/regression_tests/trigger_no_status/results_true.dat index fb6c0086ab2..a62e1b3fe5b 100644 --- a/tests/regression_tests/trigger_no_status/results_true.dat +++ b/tests/regression_tests/trigger_no_status/results_true.dat @@ -1,28 +1,28 @@ k-combined: -9.682315E-01 3.302924E-03 +9.858966E-01 1.500542E-02 tally 1: -7.046510E+00 -9.932168E+00 -1.591675E+00 -5.067777E-01 -1.541572E+00 -4.753743E-01 -5.454835E+00 -5.951990E+00 -7.046510E+00 -9.932168E+00 -1.591675E+00 -5.067777E-01 -1.541572E+00 -4.753743E-01 -5.454835E+00 -5.951990E+00 +7.081828E+00 +1.003709E+01 +1.607382E+00 +5.171386E-01 +1.559242E+00 +4.866413E-01 +5.474447E+00 +5.997737E+00 +7.081828E+00 +1.003709E+01 +1.607382E+00 +5.171386E-01 +1.559242E+00 +4.866413E-01 +5.474447E+00 +5.997737E+00 tally 2: -7.046510E+00 -9.932168E+00 -1.591675E+00 -5.067777E-01 -1.541572E+00 -4.753743E-01 -5.454835E+00 -5.951990E+00 +7.081828E+00 +1.003709E+01 +1.607382E+00 +5.171386E-01 +1.559242E+00 +4.866413E-01 +5.474447E+00 +5.997737E+00 diff --git a/tests/regression_tests/trigger_statepoint_restart/inputs_true.dat b/tests/regression_tests/trigger_statepoint_restart/inputs_true.dat index 01755ad8bc2..59a8297009d 100644 --- a/tests/regression_tests/trigger_statepoint_restart/inputs_true.dat +++ b/tests/regression_tests/trigger_statepoint_restart/inputs_true.dat @@ -16,7 +16,7 @@ 15 10 - 0.003 + 0.002 std_dev diff --git a/tests/regression_tests/trigger_statepoint_restart/results_true.dat b/tests/regression_tests/trigger_statepoint_restart/results_true.dat index 3e6619d74db..3cb1e230ddc 100644 --- a/tests/regression_tests/trigger_statepoint_restart/results_true.dat +++ b/tests/regression_tests/trigger_statepoint_restart/results_true.dat @@ -1,5 +1,5 @@ k-combined: -3.014717E-01 2.864764E-03 +2.948661E-01 1.949846E-03 tally 1: -8.946107E+01 -7.281263E+02 +5.515170E+01 +4.349007E+02 diff --git a/tests/regression_tests/trigger_statepoint_restart/test.py b/tests/regression_tests/trigger_statepoint_restart/test.py index 8144c1d0b0a..b242f7f1cff 100644 --- a/tests/regression_tests/trigger_statepoint_restart/test.py +++ b/tests/regression_tests/trigger_statepoint_restart/test.py @@ -29,7 +29,7 @@ def model(): settings.inactive = 10 settings.particles = 400 # Choose a sufficiently low threshold to enable use of trigger - settings.keff_trigger = {'type': 'std_dev', 'threshold': 0.003} + settings.keff_trigger = {'type': 'std_dev', 'threshold': 0.002} settings.trigger_max_batches = 1000 settings.trigger_batch_interval = 1 settings.trigger_active = True @@ -41,10 +41,10 @@ def model(): tallies = openmc.Tallies([t]) # Put it all together - model = openmc.model.Model(materials=materials, - geometry=geometry, - settings=settings, - tallies=tallies) + model = openmc.Model(materials=materials, + geometry=geometry, + settings=settings, + tallies=tallies) return model diff --git a/tests/regression_tests/trigger_tallies/results_true.dat b/tests/regression_tests/trigger_tallies/results_true.dat index 0571d6c4f9e..92fa99d87af 100644 --- a/tests/regression_tests/trigger_tallies/results_true.dat +++ b/tests/regression_tests/trigger_tallies/results_true.dat @@ -1,28 +1,28 @@ k-combined: -9.767929E-01 5.327552E-03 +9.863217E-01 6.499354E-03 tally 1: -1.405471E+01 -1.976444E+01 -3.186139E+00 -1.015515E+00 -3.088643E+00 -9.542994E-01 -1.086857E+01 -1.182011E+01 -1.405471E+01 -1.976444E+01 -3.186139E+00 -1.015515E+00 -3.088643E+00 -9.542994E-01 -1.086857E+01 -1.182011E+01 +1.423436E+01 +2.027258E+01 +3.223740E+00 +1.039872E+00 +3.125016E+00 +9.771709E-01 +1.101062E+01 +1.212977E+01 +1.423436E+01 +2.027258E+01 +3.223740E+00 +1.039872E+00 +3.125016E+00 +9.771709E-01 +1.101062E+01 +1.212977E+01 tally 2: -1.405471E+01 -1.976444E+01 -3.186139E+00 -1.015515E+00 -3.088643E+00 -9.542994E-01 -1.086857E+01 -1.182011E+01 +1.423436E+01 +2.027258E+01 +3.223740E+00 +1.039872E+00 +3.125016E+00 +9.771709E-01 +1.101062E+01 +1.212977E+01 diff --git a/tests/regression_tests/triso/results_true.dat b/tests/regression_tests/triso/results_true.dat index fa1842e547d..d9850e410ff 100644 --- a/tests/regression_tests/triso/results_true.dat +++ b/tests/regression_tests/triso/results_true.dat @@ -1,2 +1,2 @@ k-combined: -1.716873E+00 5.266107E-02 +1.604832E+00 2.031393E-03 diff --git a/tests/regression_tests/uniform_fs/results_true.dat b/tests/regression_tests/uniform_fs/results_true.dat index 46654b49b43..f7ceecfb733 100644 --- a/tests/regression_tests/uniform_fs/results_true.dat +++ b/tests/regression_tests/uniform_fs/results_true.dat @@ -1,2 +1,2 @@ k-combined: -3.685309E-01 1.861720E-03 +3.675645E-01 4.342970E-03 diff --git a/tests/regression_tests/universe/results_true.dat b/tests/regression_tests/universe/results_true.dat index bbf03de9437..97b997ae6b9 100644 --- a/tests/regression_tests/universe/results_true.dat +++ b/tests/regression_tests/universe/results_true.dat @@ -1,2 +1,2 @@ k-combined: -3.070134E-01 3.900396E-03 +2.940336E-01 7.338463E-04 diff --git a/tests/regression_tests/white_plane/results_true.dat b/tests/regression_tests/white_plane/results_true.dat index 6f1d064eb36..ffb19491ddd 100644 --- a/tests/regression_tests/white_plane/results_true.dat +++ b/tests/regression_tests/white_plane/results_true.dat @@ -1,2 +1,2 @@ k-combined: -2.279719E+00 5.380792E-03 +2.274312E+00 4.223342E-03 diff --git a/tests/unit_tests/test_deplete_resultslist.py b/tests/unit_tests/test_deplete_resultslist.py index 308eccf7bbd..9a4699a4fdd 100644 --- a/tests/unit_tests/test_deplete_resultslist.py +++ b/tests/unit_tests/test_deplete_resultslist.py @@ -21,14 +21,14 @@ def test_get_activity(res): t_ref = np.array([0.0, 1296000.0, 2592000.0, 3888000.0]) a_ref = np.array( - [1.25167956e+06, 3.71938527e+11, 4.43264300e+11, 3.55547176e+11]) + [1.25167956e+06, 3.69842310e+11, 3.70099291e+11, 3.53629755e+11]) np.testing.assert_allclose(t, t_ref) np.testing.assert_allclose(a, a_ref) # Check by_nuclide a_xe135_ref = np.array( - [2.106574218e+05, 1.227519888e+11, 1.177491828e+11, 1.031986176e+11]) + [2.10657422e+05, 1.12825236e+11, 1.09055177e+11, 1.07491257e+11]) t_nuc, a_nuc = res.get_activity("1", by_nuclide=True) a_xe135 = np.array([a_nuc_i["Xe135"] for a_nuc_i in a_nuc]) @@ -43,7 +43,7 @@ def test_get_atoms(res): t_ref = np.array([0.0, 1296000.0, 2592000.0, 3888000.0]) n_ref = np.array( - [6.67473282e+08, 3.88942731e+14, 3.73091215e+14, 3.26987387e+14]) + [6.67473282e+08, 3.57489567e+14, 3.45544042e+14, 3.40588723e+14]) np.testing.assert_allclose(t, t_ref) np.testing.assert_allclose(n, n_ref) @@ -71,7 +71,7 @@ def test_get_decay_heat(res): t_ref = np.array([0.0, 1296000.0, 2592000.0, 3888000.0]) dh_ref = np.array( - [1.27933813e-09, 5.85347232e-03, 7.38773010e-03, 5.79954067e-03]) + [1.27933813e-09, 5.95370258e-03, 6.01335600e-03, 5.69831173e-03]) t, dh = res.get_decay_heat("1") @@ -80,7 +80,7 @@ def test_get_decay_heat(res): # Check by nuclide dh_xe135_ref = np.array( - [1.27933813e-09, 7.45481920e-04, 7.15099509e-04, 6.26732849e-04]) + [1.27933813e-09, 6.85196014e-04, 6.62300168e-04, 6.52802366e-04]) t_nuc, dh_nuc = res.get_decay_heat("1", by_nuclide=True) dh_nuc_xe135 = np.array([dh_nuc_i["Xe135"] for dh_nuc_i in dh_nuc]) @@ -95,7 +95,7 @@ def test_get_mass(res): t_ref = np.array([0.0, 1296000.0, 2592000.0, 3888000.0]) n_ref = np.array( - [6.67473282e+08, 3.88942731e+14, 3.73091215e+14, 3.26987387e+14]) + [6.67473282e+08, 3.57489567e+14, 3.45544042e+14, 3.40588723e+14]) # Get g n_ref *= openmc.data.atomic_mass('Xe135') / openmc.data.AVOGADRO @@ -123,8 +123,8 @@ def test_get_reaction_rate(res): t, r = res.get_reaction_rate("1", "Xe135", "(n,gamma)") t_ref = [0.0, 1296000.0, 2592000.0, 3888000.0] - n_ref = [6.67473282e+08, 3.88942731e+14, 3.73091215e+14, 3.26987387e+14] - xs_ref = [2.53336104e-05, 4.21747011e-05, 3.48616127e-05, 3.61775563e-05] + n_ref = [6.67473282e+08, 3.57489567e+14, 3.45544042e+14, 3.40588723e+14] + xs_ref = [3.10220818e-05, 3.36754072e-05, 3.12740350e-05, 3.86717693e-05] np.testing.assert_allclose(t, t_ref) np.testing.assert_allclose(r, np.array(n_ref) * xs_ref) @@ -136,8 +136,8 @@ def test_get_keff(res): t_min, k = res.get_keff(time_units='min') t_ref = [0.0, 1296000.0, 2592000.0, 3888000.0] - k_ref = [1.1596402556, 1.1914183335, 1.2292570871, 1.1797030302] - u_ref = [0.0270680649, 0.0219163444, 0.024268508 , 0.0221401194] + k_ref = [1.1773089172, 1.2231748584, 1.1611455694, 1.1714783649] + u_ref = [0.0384666252, 0.0311915665, 0.0226370102, 0.0315964732] np.testing.assert_allclose(t, t_ref) np.testing.assert_allclose(t_min * 60, t_ref) diff --git a/tests/unit_tests/test_lib.py b/tests/unit_tests/test_lib.py index 8080ce89483..43bc5a8f6f0 100644 --- a/tests/unit_tests/test_lib.py +++ b/tests/unit_tests/test_lib.py @@ -161,16 +161,21 @@ def test_properties_temperature(lib_init): def test_cell_density(lib_init): cell = openmc.lib.cells[1] - cell.set_density(1.5, 0) - assert cell.get_density(0) == pytest.approx(1.5) - cell.set_density(2.0) - assert cell.get_density() == pytest.approx(2.0) + print('density', cell.get_density()) + orig_density = cell.get_density() + try: + cell.set_density(1.5, 0) + assert cell.get_density(0) == pytest.approx(1.5) + cell.set_density(2.0) + assert cell.get_density() == pytest.approx(2.0) + finally: + cell.set_density(orig_density) def test_properties_cell_density(lib_init): # Cell density should be 2.0 from above test cell = openmc.lib.cells[1] - assert cell.get_density() == pytest.approx(2.0) + orig_density = cell.get_density() # Export properties and change density openmc.lib.export_properties('properties.h5') @@ -179,7 +184,7 @@ def test_properties_cell_density(lib_init): # Import properties and check that density is restored openmc.lib.import_properties('properties.h5') - assert cell.get_density() == pytest.approx(2.0) + assert cell.get_density() == pytest.approx(orig_density) def test_new_cell(lib_init): diff --git a/tests/unit_tests/test_settings.py b/tests/unit_tests/test_settings.py index 7f202bcdc73..6611e4227e0 100644 --- a/tests/unit_tests/test_settings.py +++ b/tests/unit_tests/test_settings.py @@ -67,6 +67,7 @@ def test_export_to_xml(run_in_tmpdir): ) } s.max_particle_events = 100 + s.max_secondaries = 1_000_000 s.source_rejection_fraction = 0.01 # Make sure exporting XML works @@ -144,4 +145,5 @@ def test_export_to_xml(run_in_tmpdir): assert s.random_ray['distance_active'] == 100.0 assert s.random_ray['ray_source'].space.lower_left == [-1., -1., -1.] assert s.random_ray['ray_source'].space.upper_right == [1., 1., 1.] + assert s.max_secondaries == 1_000_000 assert s.source_rejection_fraction == 0.01 diff --git a/tests/unit_tests/test_statepoint_batches.py b/tests/unit_tests/test_statepoint_batches.py new file mode 100644 index 00000000000..bf54e18786d --- /dev/null +++ b/tests/unit_tests/test_statepoint_batches.py @@ -0,0 +1,26 @@ +from pathlib import Path + +import openmc + + +def test_statepoint_batches(run_in_tmpdir): + # Create a minimal model + mat = openmc.Material() + mat.add_nuclide('U235', 1.0) + mat.set_density('g/cm3', 4.5) + sphere = openmc.Sphere(r=10.0, boundary_type='vacuum') + cell = openmc.Cell(fill=mat, region=-sphere) + model = openmc.Model() + model.geometry = openmc.Geometry([cell]) + model.settings.batches = 10 + model.settings.inactive = 5 + model.settings.particles = 100 + + # Specify when statepoints should be written + model.settings.statepoint = {'batches': [3, 6, 9]} + + # Run model and ensure that statepoints are created + model.run() + sp_files = ['statepoint.03.h5', 'statepoint.06.h5', 'statepoint.09.h5'] + for f in sp_files: + assert Path(f).is_file() From ca63da91b9b0fc0cd144766155b533318d4a71c3 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 19 Sep 2025 15:00:51 -0500 Subject: [PATCH 112/159] Ensure n_dimension_ attribute is set for unstructured meshes. (#3575) --- include/openmc/mesh.h | 3 +-- src/mesh.cpp | 5 +++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index c15e2569777..fbc6c46f50c 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -666,9 +666,8 @@ class UnstructuredMesh : public Mesh { public: // Constructors - UnstructuredMesh() {}; + UnstructuredMesh() { n_dimension_ = 3; }; UnstructuredMesh(pugi::xml_node node); - UnstructuredMesh(const std::string& filename); static const std::string mesh_type; virtual std::string get_mesh_type() const override; diff --git a/src/mesh.cpp b/src/mesh.cpp index 3e4ff1a3eca..b7396a25abe 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -590,6 +590,8 @@ Position StructuredMesh::sample_element( UnstructuredMesh::UnstructuredMesh(pugi::xml_node node) : Mesh(node) { + n_dimension_ = 3; + // check the mesh type if (check_for_node(node, "type")) { auto temp = get_node_value(node, "type", true, true); @@ -2519,7 +2521,9 @@ MOABMesh::MOABMesh(pugi::xml_node node) : UnstructuredMesh(node) } MOABMesh::MOABMesh(const std::string& filename, double length_multiplier) + : UnstructuredMesh() { + n_dimension_ = 3; filename_ = filename; set_length_multiplier(length_multiplier); initialize(); @@ -3242,6 +3246,7 @@ LibMesh::LibMesh(libMesh::MeshBase& input_mesh, double length_multiplier) LibMesh::LibMesh(const std::string& filename, double length_multiplier) : adaptive_(false) { + n_dimension_ = 3; set_mesh_pointer_from_filename(filename); set_length_multiplier(length_multiplier); initialize(); From 8f36ff2b3a5e2984254f7b21f1b92af7ad18140b Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 23 Sep 2025 10:06:29 -0500 Subject: [PATCH 113/159] Update find_package calls in OpenMCConfig.cmake (#3572) --- cmake/OpenMCConfig.cmake.in | 11 +++++++---- openmc/deplete/transfer_rates.py | 2 +- tests/regression_tests/unstructured_mesh/test.py | 2 +- tests/unit_tests/dagmc/test_plot.py | 5 ++++- tests/unit_tests/test_model.py | 4 ++++ tests/unit_tests/test_region.py | 4 ++++ tests/unit_tests/test_universe.py | 4 ++++ tests/unit_tests/weightwindows/test_wwinp_reader.py | 2 +- tools/ci/gha-script.sh | 5 ++++- 9 files changed, 30 insertions(+), 9 deletions(-) diff --git a/cmake/OpenMCConfig.cmake.in b/cmake/OpenMCConfig.cmake.in index 3fe0c1bcd16..837a39c7833 100644 --- a/cmake/OpenMCConfig.cmake.in +++ b/cmake/OpenMCConfig.cmake.in @@ -1,9 +1,12 @@ get_filename_component(OpenMC_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY) -find_package(fmt REQUIRED HINTS ${OpenMC_CMAKE_DIR}/../fmt) -find_package(pugixml REQUIRED HINTS ${OpenMC_CMAKE_DIR}/../pugixml) -find_package(xtl REQUIRED HINTS ${OpenMC_CMAKE_DIR}/../xtl) -find_package(xtensor REQUIRED HINTS ${OpenMC_CMAKE_DIR}/../xtensor) +# Compute the install prefix from this file's location +get_filename_component(_OPENMC_PREFIX "${OpenMC_CMAKE_DIR}/../../.." ABSOLUTE) + +find_package(fmt CONFIG REQUIRED HINTS ${_OPENMC_PREFIX}) +find_package(pugixml CONFIG REQUIRED HINTS ${_OPENMC_PREFIX}) +find_package(xtl CONFIG REQUIRED HINTS ${_OPENMC_PREFIX}) +find_package(xtensor CONFIG REQUIRED HINTS ${_OPENMC_PREFIX}) if(@OPENMC_USE_DAGMC@) find_package(DAGMC REQUIRED HINTS @DAGMC_DIR@) endif() diff --git a/openmc/deplete/transfer_rates.py b/openmc/deplete/transfer_rates.py index 4c28c7d1506..4f2b9aba5f5 100644 --- a/openmc/deplete/transfer_rates.py +++ b/openmc/deplete/transfer_rates.py @@ -366,7 +366,7 @@ def set_external_source_rate( rate : float External source rate in units of mass per time. A positive or negative value corresponds to a feed or removal rate, respectively. - units : {'g/s', 'g/min', 'g/h', 'g/d', 'g/a'} + rate_units : {'g/s', 'g/min', 'g/h', 'g/d', 'g/a'} Units for values specified in the `rate` argument. 's' for seconds, 'min' for minutes, 'h' for hours, 'a' for Julian years. timesteps : list of int, optional diff --git a/tests/regression_tests/unstructured_mesh/test.py b/tests/regression_tests/unstructured_mesh/test.py index 0082198ddec..7607531d8ba 100644 --- a/tests/regression_tests/unstructured_mesh/test.py +++ b/tests/regression_tests/unstructured_mesh/test.py @@ -256,7 +256,7 @@ def model(): def test_unstructured_mesh_tets(model, test_opts): # skip the test if the library is not enabled if test_opts['library'] == 'moab' and not openmc.lib._dagmc_enabled(): - pytest.skip("DAGMC (and MOAB) mesh not enbaled in this build.") + pytest.skip("DAGMC (and MOAB) mesh not enabled in this build.") if test_opts['library'] == 'libmesh' and not openmc.lib._libmesh_enabled(): pytest.skip("LibMesh is not enabled in this build.") diff --git a/tests/unit_tests/dagmc/test_plot.py b/tests/unit_tests/dagmc/test_plot.py index 62022b25837..6ce1d79a22d 100644 --- a/tests/unit_tests/dagmc/test_plot.py +++ b/tests/unit_tests/dagmc/test_plot.py @@ -64,5 +64,8 @@ def test_plotting_geometry_filled_with_dagmc_universe(request): cell2 = openmc.Cell(fill=csg_material, region=+sphere1 & -sphere2) geometry = openmc.Geometry([cell1, cell2]) - geometry.plot() + + # Close plot to avoid warning + import matplotlib.pyplot as plt + plt.close() diff --git a/tests/unit_tests/test_model.py b/tests/unit_tests/test_model.py index 12f0df6c9e5..60f8b1a25a3 100644 --- a/tests/unit_tests/test_model.py +++ b/tests/unit_tests/test_model.py @@ -654,6 +654,10 @@ def test_model_plot(): test_mask = (image_data == white) | (image_data == red) assert np.all(test_mask), "Colors other than white or red found in overlap plot image" + # Close plots to avoid warning + import matplotlib.pyplot as plt + plt.close('all') + def test_model_id_map_initialization(run_in_tmpdir): model = openmc.examples.pwr_assembly() diff --git a/tests/unit_tests/test_region.py b/tests/unit_tests/test_region.py index cbcd1983125..cb9fa171bb6 100644 --- a/tests/unit_tests/test_region.py +++ b/tests/unit_tests/test_region.py @@ -248,6 +248,10 @@ def test_plot(): c_before = openmc.Cell() region.plot() + # Close plot to avoid warning + import matplotlib.pyplot as plt + plt.close() + # Ensure that calling plot doesn't affect cell ID space c_after = openmc.Cell() assert c_after.id - 1 == c_before.id diff --git a/tests/unit_tests/test_universe.py b/tests/unit_tests/test_universe.py index 46d4ec3f734..efe8552a648 100644 --- a/tests/unit_tests/test_universe.py +++ b/tests/unit_tests/test_universe.py @@ -99,6 +99,10 @@ def test_plot(run_in_tmpdir, sphere_model): pixels=100, ) + # Close plots to avoid warning + import matplotlib.pyplot as plt + plt.close('all') + def test_get_nuclides(uo2): c = openmc.Cell(fill=uo2) diff --git a/tests/unit_tests/weightwindows/test_wwinp_reader.py b/tests/unit_tests/weightwindows/test_wwinp_reader.py index 28548a4486f..637463bb2e6 100644 --- a/tests/unit_tests/weightwindows/test_wwinp_reader.py +++ b/tests/unit_tests/weightwindows/test_wwinp_reader.py @@ -140,4 +140,4 @@ def test_wwinp_reader_failures(wwinp_data, request): filename, expected_failure = wwinp_data with pytest.raises(expected_failure): - _ = openmc.wwinp_to_wws(request.node.path.parent / filename) + _ = openmc.WeightWindowsList.from_wwinp(request.node.path.parent / filename) diff --git a/tools/ci/gha-script.sh b/tools/ci/gha-script.sh index c0f754c32c8..c1d29213772 100755 --- a/tools/ci/gha-script.sh +++ b/tools/ci/gha-script.sh @@ -15,4 +15,7 @@ if [[ $EVENT == 'y' ]]; then fi # Run unit tests and then regression tests -pytest --cov=openmc -v $args tests/unit_tests tests/regression_tests +pytest --cov=openmc -v $args \ + tests/test_matplotlib_import.py \ + tests/unit_tests \ + tests/regression_tests From ed433fe1cd9479e355cfe4ca6ec8fb99359f98cf Mon Sep 17 00:00:00 2001 From: Kevin Sawatzky <66632997+nuclearkevin@users.noreply.github.com> Date: Tue, 23 Sep 2025 11:31:14 -0500 Subject: [PATCH 114/159] Fix performance regression in libMesh unstructured mesh tallies (#3577) --- include/openmc/mesh.h | 47 ++++++++++++++---- src/mesh.cpp | 109 ++++++++++++++++++++++++------------------ 2 files changed, 100 insertions(+), 56 deletions(-) diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index fbc6c46f50c..a56705c9ec5 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -990,25 +990,26 @@ class LibMesh : public UnstructuredMesh { libMesh::MeshBase* mesh_ptr() const { return m_; }; -private: - void initialize() override; - void set_mesh_pointer_from_filename(const std::string& filename); - void build_eqn_sys(); - +protected: // Methods //! Translate a bin value to an element reference - const libMesh::Elem& get_element_from_bin(int bin) const; + virtual const libMesh::Elem& get_element_from_bin(int bin) const; //! Translate an element pointer to a bin index - int get_bin_from_element(const libMesh::Elem* elem) const; + virtual int get_bin_from_element(const libMesh::Elem* elem) const; + + libMesh::MeshBase* m_; //!< pointer to libMesh MeshBase instance, always set + //!< during intialization +private: + void initialize() override; + void set_mesh_pointer_from_filename(const std::string& filename); + void build_eqn_sys(); // Data members unique_ptr unique_m_ = nullptr; //!< pointer to the libMesh MeshBase instance, only used if mesh is //!< created inside OpenMC - libMesh::MeshBase* m_; //!< pointer to libMesh MeshBase instance, always set - //!< during intialization vector> pl_; //!< per-thread point locators unique_ptr @@ -1022,8 +1023,34 @@ class LibMesh : public UnstructuredMesh { libMesh::BoundingBox bbox_; //!< bounding box of the mesh libMesh::dof_id_type first_element_id_; //!< id of the first element in the mesh +}; + +class AdaptiveLibMesh : public LibMesh { +public: + // Constructor + AdaptiveLibMesh( + libMesh::MeshBase& input_mesh, double length_multiplier = 1.0); + + // Overridden methods + int n_bins() const override; + + void add_score(const std::string& var_name) override; + + void set_score_data(const std::string& var_name, const vector& values, + const vector& std_dev) override; + + void write(const std::string& filename) const override; + +protected: + // Overridden methods + int get_bin_from_element(const libMesh::Elem* elem) const override; + + const libMesh::Elem& get_element_from_bin(int bin) const override; + +private: + // Data members + const libMesh::dof_id_type num_active_; //!< cached number of active elements - const bool adaptive_; //!< whether this mesh has adaptivity enabled or not std::vector bin_to_elem_map_; //!< mapping bin indices to dof indices for active //!< elements diff --git a/src/mesh.cpp b/src/mesh.cpp index b7396a25abe..58d218b9cad 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -3219,7 +3219,7 @@ void MOABMesh::write(const std::string& base_filename) const const std::string LibMesh::mesh_lib_type = "libmesh"; -LibMesh::LibMesh(pugi::xml_node node) : UnstructuredMesh(node), adaptive_(false) +LibMesh::LibMesh(pugi::xml_node node) : UnstructuredMesh(node) { // filename_ and length_multiplier_ will already be set by the // UnstructuredMesh constructor @@ -3230,7 +3230,6 @@ LibMesh::LibMesh(pugi::xml_node node) : UnstructuredMesh(node), adaptive_(false) // create the mesh from a pointer to a libMesh Mesh LibMesh::LibMesh(libMesh::MeshBase& input_mesh, double length_multiplier) - : adaptive_(input_mesh.n_active_elem() != input_mesh.n_elem()) { if (!dynamic_cast(&input_mesh)) { fatal_error("At present LibMesh tallies require a replicated mesh. Please " @@ -3244,7 +3243,6 @@ LibMesh::LibMesh(libMesh::MeshBase& input_mesh, double length_multiplier) // create the mesh from an input file LibMesh::LibMesh(const std::string& filename, double length_multiplier) - : adaptive_(false) { n_dimension_ = 3; set_mesh_pointer_from_filename(filename); @@ -3307,21 +3305,6 @@ void LibMesh::initialize() auto first_elem = *m_->elements_begin(); first_element_id_ = first_elem->id(); - // if the mesh is adaptive elements aren't guaranteed by libMesh to be - // contiguous in ID space, so we need to map from bin indices (defined over - // active elements) to global dof ids - if (adaptive_) { - bin_to_elem_map_.reserve(m_->n_active_elem()); - elem_to_bin_map_.resize(m_->n_elem(), -1); - for (auto it = m_->active_elements_begin(); it != m_->active_elements_end(); - it++) { - auto elem = *it; - - bin_to_elem_map_.push_back(elem->id()); - elem_to_bin_map_[elem->id()] = bin_to_elem_map_.size() - 1; - } - } - // bounding box for the mesh for quick rejection checks bbox_ = libMesh::MeshTools::create_bounding_box(*m_); libMesh::Point ll = bbox_.min(); @@ -3379,7 +3362,7 @@ std::string LibMesh::library() const int LibMesh::n_bins() const { - return m_->n_active_elem(); + return m_->n_elem(); } int LibMesh::n_surface_bins() const @@ -3402,14 +3385,6 @@ int LibMesh::n_surface_bins() const void LibMesh::add_score(const std::string& var_name) { - if (adaptive_) { - warning(fmt::format( - "Exodus output cannot be provided as unstructured mesh {} is adaptive.", - this->id_)); - - return; - } - if (!equation_systems_) { build_eqn_sys(); } @@ -3445,14 +3420,6 @@ void LibMesh::remove_scores() void LibMesh::set_score_data(const std::string& var_name, const vector& values, const vector& std_dev) { - if (adaptive_) { - warning(fmt::format( - "Exodus output cannot be provided as unstructured mesh {} is adaptive.", - this->id_)); - - return; - } - if (!equation_systems_) { build_eqn_sys(); } @@ -3496,14 +3463,6 @@ void LibMesh::set_score_data(const std::string& var_name, void LibMesh::write(const std::string& filename) const { - if (adaptive_) { - warning(fmt::format( - "Exodus output cannot be provided as unstructured mesh {} is adaptive.", - this->id_)); - - return; - } - write_message(fmt::format( "Writing file: {}.e for unstructured mesh {}", filename, this->id_)); libMesh::ExodusII_IO exo(*m_); @@ -3537,8 +3496,7 @@ int LibMesh::get_bin(Position r) const int LibMesh::get_bin_from_element(const libMesh::Elem* elem) const { - int bin = - adaptive_ ? elem_to_bin_map_[elem->id()] : elem->id() - first_element_id_; + int bin = elem->id() - first_element_id_; if (bin >= n_bins() || bin < 0) { fatal_error(fmt::format("Invalid bin: {}", bin)); } @@ -3553,7 +3511,7 @@ std::pair, vector> LibMesh::plot( const libMesh::Elem& LibMesh::get_element_from_bin(int bin) const { - return adaptive_ ? m_->elem_ref(bin_to_elem_map_.at(bin)) : m_->elem_ref(bin); + return m_->elem_ref(bin); } double LibMesh::volume(int bin) const @@ -3561,6 +3519,65 @@ double LibMesh::volume(int bin) const return this->get_element_from_bin(bin).volume(); } +AdaptiveLibMesh::AdaptiveLibMesh( + libMesh::MeshBase& input_mesh, double length_multiplier) + : LibMesh(input_mesh, length_multiplier), num_active_(m_->n_active_elem()) +{ + // if the mesh is adaptive elements aren't guaranteed by libMesh to be + // contiguous in ID space, so we need to map from bin indices (defined over + // active elements) to global dof ids + bin_to_elem_map_.reserve(num_active_); + elem_to_bin_map_.resize(m_->n_elem(), -1); + for (auto it = m_->active_elements_begin(); it != m_->active_elements_end(); + it++) { + auto elem = *it; + + bin_to_elem_map_.push_back(elem->id()); + elem_to_bin_map_[elem->id()] = bin_to_elem_map_.size() - 1; + } +} + +int AdaptiveLibMesh::n_bins() const +{ + return num_active_; +} + +void AdaptiveLibMesh::add_score(const std::string& var_name) +{ + warning(fmt::format( + "Exodus output cannot be provided as unstructured mesh {} is adaptive.", + this->id_)); +} + +void AdaptiveLibMesh::set_score_data(const std::string& var_name, + const vector& values, const vector& std_dev) +{ + warning(fmt::format( + "Exodus output cannot be provided as unstructured mesh {} is adaptive.", + this->id_)); +} + +void AdaptiveLibMesh::write(const std::string& filename) const +{ + warning(fmt::format( + "Exodus output cannot be provided as unstructured mesh {} is adaptive.", + this->id_)); +} + +int AdaptiveLibMesh::get_bin_from_element(const libMesh::Elem* elem) const +{ + int bin = elem_to_bin_map_[elem->id()]; + if (bin >= n_bins() || bin < 0) { + fatal_error(fmt::format("Invalid bin: {}", bin)); + } + return bin; +} + +const libMesh::Elem& AdaptiveLibMesh::get_element_from_bin(int bin) const +{ + return m_->elem_ref(bin_to_elem_map_.at(bin)); +} + #endif // OPENMC_LIBMESH_ENABLED //============================================================================== From 66e7d8634cbace480e3f6ccf58ca959ecfbf614e Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Wed, 24 Sep 2025 11:02:19 +0200 Subject: [PATCH 115/159] Remove several TODOs related to C++17 support (#3574) Co-authored-by: Paul Romano --- src/bank.cpp | 13 +++---------- src/event.cpp | 13 +++++++------ src/hdf5_interface.cpp | 3 +-- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/src/bank.cpp b/src/bank.cpp index 9955939f6e6..3e806b3c0b7 100644 --- a/src/bank.cpp +++ b/src/bank.cpp @@ -79,16 +79,9 @@ void sort_fission_bank() // Perform exclusive scan summation to determine starting indices in fission // bank for each parent particle id - int64_t tmp = simulation::progeny_per_particle[0]; - simulation::progeny_per_particle[0] = 0; - for (int64_t i = 1; i < simulation::progeny_per_particle.size(); i++) { - int64_t value = simulation::progeny_per_particle[i - 1] + tmp; - tmp = simulation::progeny_per_particle[i]; - simulation::progeny_per_particle[i] = value; - } - - // TODO: C++17 introduces the exclusive_scan() function which could be - // used to replace everything above this point in this function. + std::exclusive_scan(simulation::progeny_per_particle.begin(), + simulation::progeny_per_particle.end(), + simulation::progeny_per_particle.begin(), 0); // We need a scratch vector to make permutation of the fission bank into // sorted order easy. Under normal usage conditions, the fission bank is diff --git a/src/event.cpp b/src/event.cpp index aa3987504b6..f33e132d0af 100644 --- a/src/event.cpp +++ b/src/event.cpp @@ -1,4 +1,5 @@ #include "openmc/event.h" + #include "openmc/material.h" #include "openmc/simulation.h" #include "openmc/timer.h" @@ -73,17 +74,17 @@ void process_calculate_xs_events(SharedArray& queue) { simulation::time_event_calculate_xs.start(); - // TODO: If using C++17, perform a parallel sort of the queue - // by particle type, material type, and then energy, in order to - // improve cache locality and reduce thread divergence on GPU. Prior - // to C++17, std::sort is a serial only operation, which in this case - // makes it too slow to be practical for most test problems. + // TODO: If using C++17, we could perform a parallel sort of the queue by + // particle type, material type, and then energy, in order to improve cache + // locality and reduce thread divergence on GPU. However, the parallel + // algorithms typically require linking against an additional library (Intel + // TBB). Prior to C++17, std::sort is a serial only operation, which in this + // case makes it too slow to be practical for most test problems. // // std::sort(std::execution::par_unseq, queue.data(), queue.data() + // queue.size()); int64_t offset = simulation::advance_particle_queue.size(); - ; #pragma omp parallel for schedule(runtime) for (int64_t i = 0; i < queue.size(); i++) { diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index e90aa74901e..bf1f79549e0 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -225,8 +225,7 @@ void get_name(hid_t obj_id, std::string& name) { size_t size = 1 + H5Iget_name(obj_id, nullptr, 0); name.resize(size); - // TODO: switch to name.data() when using C++17 - H5Iget_name(obj_id, &name[0], size); + H5Iget_name(obj_id, name.data(), size); } int get_num_datasets(hid_t group_id) From 767db7e6a090b88a594be72b4971d71e3c2be243 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville <54550047+JoffreyDorville@users.noreply.github.com> Date: Thu, 25 Sep 2025 14:58:29 -0500 Subject: [PATCH 116/159] Fix IFP implementation (#3580) --- include/openmc/ifp.h | 7 +++---- include/openmc/particle_data.h | 1 + src/ifp.cpp | 4 ++-- src/particle.cpp | 1 + src/physics.cpp | 9 +++------ tests/regression_tests/ifp/results_true.dat | 4 ++-- .../case-03/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-04/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-05/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-07/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-08/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-09/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-10/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-12/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-13/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-14/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-a01/surface_source_true.h5 | Bin 6720 -> 6720 bytes .../case-d07/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-d08/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-e01/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-e02/surface_source_true.h5 | Bin 33344 -> 33344 bytes .../case-e03/surface_source_true.h5 | Bin 33344 -> 33344 bytes 22 files changed, 12 insertions(+), 14 deletions(-) diff --git a/include/openmc/ifp.h b/include/openmc/ifp.h index 633a262d5f9..01904d13c94 100644 --- a/include/openmc/ifp.h +++ b/include/openmc/ifp.h @@ -68,15 +68,14 @@ vector _ifp(const T& value, const vector& data) //! //! Add the IFP information in the IFP banks using the same index //! as the one used to append the fission site to the fission bank. +//! The information stored are the delayed group number and lifetime +//! of the neutron that created the fission event. //! Multithreading protection is guaranteed by the index returned by the //! thread_safe_append call in physics.cpp. //! -//! Needs to be done after the delayed group is found. -//! //! \param[in] p Particle -//! \param[in] site Fission site //! \param[in] idx Bank index from the thread_safe_append call in physics.cpp -void ifp(const Particle& p, const SourceSite& site, int64_t idx); +void ifp(const Particle& p, int64_t idx); //! Resize the IFP banks used in the simulation void resize_simulation_ifp_banks(); diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index 1a22f583798..afcd5647674 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -631,6 +631,7 @@ class ParticleData : public GeometryState { int& event_mt() { return event_mt_; } // MT number of collision const int& event_mt() const { return event_mt_; } int& delayed_group() { return delayed_group_; } // delayed group + const int& delayed_group() const { return delayed_group_; } const int& parent_nuclide() const { return parent_nuclide_; } int& parent_nuclide() { return parent_nuclide_; } // Parent nuclide diff --git a/src/ifp.cpp b/src/ifp.cpp index 1f81f26f6ea..cc4a76538b1 100644 --- a/src/ifp.cpp +++ b/src/ifp.cpp @@ -28,13 +28,13 @@ bool is_generation_time_or_both() return false; } -void ifp(const Particle& p, const SourceSite& site, int64_t idx) +void ifp(const Particle& p, int64_t idx) { if (is_beta_effective_or_both()) { const auto& delayed_groups = simulation::ifp_source_delayed_group_bank[p.current_work() - 1]; simulation::ifp_fission_delayed_group_bank[idx] = - _ifp(site.delayed_group, delayed_groups); + _ifp(p.delayed_group(), delayed_groups); } if (is_generation_time_or_both()) { const auto& lifetimes = diff --git a/src/particle.cpp b/src/particle.cpp index f5ad45d80fe..402af2498a5 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -144,6 +144,7 @@ void Particle::from_source(const SourceSite* src) time() = src->time; time_last() = src->time; parent_nuclide() = src->parent_nuclide; + delayed_group() = src->delayed_group; // Convert signed surface ID to signed index if (src->surf_id != SURFACE_NONE) { diff --git a/src/physics.cpp b/src/physics.cpp index f667fd586d7..e947fecbb9c 100644 --- a/src/physics.cpp +++ b/src/physics.cpp @@ -246,18 +246,15 @@ void create_fission_sites(Particle& p, int i_nuclide, const Reaction& rx) } // Iterated Fission Probability (IFP) method if (settings::ifp_on) { - ifp(p, site, idx); + ifp(p, idx); } } else { p.secondary_bank().push_back(site); } - // Set the delayed group on the particle as well - p.delayed_group() = site.delayed_group; - // Increment the number of neutrons born delayed - if (p.delayed_group() > 0) { - nu_d[p.delayed_group() - 1]++; + if (site.delayed_group > 0) { + nu_d[site.delayed_group - 1]++; } // Write fission particles to nuBank diff --git a/tests/regression_tests/ifp/results_true.dat b/tests/regression_tests/ifp/results_true.dat index 1d8f69e1251..466ca1f015e 100644 --- a/tests/regression_tests/ifp/results_true.dat +++ b/tests/regression_tests/ifp/results_true.dat @@ -3,7 +3,7 @@ k-combined: tally 1: 9.109384E-08 5.667165E-16 -6.500000E-02 -6.710000E-04 +5.200000E-02 +5.420000E-04 1.489000E+01 1.480036E+01 diff --git a/tests/regression_tests/surface_source_write/case-03/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-03/surface_source_true.h5 index 4dd5f821af92dd98395a8df31af2a81fab8a8ba4..228f5a7d0a462f38a1cb0b4274dd40d4307b5a12 100644 GIT binary patch delta 42 ycmX@m!gQd8X@dwmbDaH^&Eo8yDU2+W6SL1VvTZiZaTj7_nw%&gzj;R83T6N=*$u}4 delta 48 zcmX@m!gQd8X@dwm^EJ&=o5k5ZQzln%3QR7@J}|k1S75V4j)f42F}XlMVDpT+70dv* Csu83B diff --git a/tests/regression_tests/surface_source_write/case-04/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-04/surface_source_true.h5 index ee00af3895c349d60d786af1aedef39e05e24d20..c276af40f4302f4e0feff883bc3c472bc4df7ca1 100644 GIT binary patch delta 38 ucmX@m!gQd8X@dwmbDaH^&Eo8xzKkrB7lxc?WZPUAI-ie`X|rI8z8nArx(svx delta 44 xcmX@m!gQd8X@dwm^L5Qro5k5XeJ59N3QXP*a$vFtm%!$R&|0h5VN3AkPef$3?Q>mU+^N7 CA`&M6 diff --git a/tests/regression_tests/surface_source_write/case-12/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-12/surface_source_true.h5 index ca5eda8e2f347a1fc88b05db6690ad5b6d3a2a51..6c6925daba145c5db52a281023dd017f066e0732 100644 GIT binary patch delta 117 zcmX@m!gQd8X@dwmbAtVq&Eo8lQj<>z2yAXpOweRx+5FH;oYOG=2mfb3*B|@WHpD(y z^yT0FTL*$W*`i%Qt;HW$zbo>Udt8lS9X5&5&?bMwZEqk?BA) UUPkiE0XaX*)4?VeR9xT$0J0-9HUIzs delta 124 zcmV-?0E7R)gaW{X0a}p%0E$1%ExYoLO_SJDO6Vllc)#~laN_h elc)#~qxe}KlPD=5lYkHilOT9N0RppOc;5**VKsyR diff --git a/tests/regression_tests/surface_source_write/case-13/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-13/surface_source_true.h5 index ca5eda8e2f347a1fc88b05db6690ad5b6d3a2a51..6c6925daba145c5db52a281023dd017f066e0732 100644 GIT binary patch delta 117 zcmX@m!gQd8X@dwmbAtVq&Eo8lQj<>z2yAXpOweRx+5FH;oYOG=2mfb3*B|@WHpD(y z^yT0FTL*$W*`i%Qt;HW$zbo>Udt8lS9X5&5&?bMwZEqk?BA) UUPkiE0XaX*)4?VeR9xT$0J0-9HUIzs delta 124 zcmV-?0E7R)gaW{X0a}p%0E$1%ExYoLO_SJDO6Vllc)#~laN_h elc)#~qxe}KlPD=5lYkHilOT9N0RppOc;5**VKsyR diff --git a/tests/regression_tests/surface_source_write/case-14/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-14/surface_source_true.h5 index c9900bd9c0469d3b171f863f0518d6c94e966027..6c6925daba145c5db52a281023dd017f066e0732 100644 GIT binary patch delta 117 zcmX@m!gQd8X@dwmbAtVq&Eo8lQj<>z2yAXpOweRx+5FH;oYOG=2mfb3*B|@WHpD(y z^yT0FTL*$W*`i%Qt;HW$zbo>Udt8lS9X5&5&?bMwZEqk?BA) UUPkiE0XaX*)4?VeR9xT$0J0-9HUIzs delta 124 zcmV-?0E7R)gaW{X0a}p%0E$1%ExYoLO_SJDO6Vllc)#~laN_h elc)#~qxe}KlPD=5lYkHilOT9N0RppOc;5**(=~+v diff --git a/tests/regression_tests/surface_source_write/case-a01/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-a01/surface_source_true.h5 index 8dc148ee07eb33d6374387e557b35914f5fa2003..da36fc505eb67f3da0ce48511464b699a4fb16e3 100644 GIT binary patch delta 70 zcmX?La=>JR2s?9<{gut)?19XaFK}f{&fv<}Y{8wtJxM%c@(i(z$rph10tuivkhTEQ W1(F$)IV3$8StcLklb?J;G6MjFG#L5- delta 68 zcmX?La=>JR2s`s*%~PAj*#ns;U*O7^oWYf`*@8QPdy;s@X>K%LUYL4oBy*u_xg7`DD%%1Q6vVXpy?7Ij1?@Ya&Zg=K`&foCGUf}-ZSJHay^ESNO9MxAl*L;*XO>X>K%KZYL4oBy*u{Hg7`DD%%1Q6ynnu+?7Ij1?@Ya&ZgX>K%LUYL4oBy*u_xg7`DD%%1Q6vVXpy?7Ij1?@Ya&Zg=K`&foCGUf}-ZSJHay^ESNO9MxAl*L;*XO>X>K%KZYL4oBy*u{Hg7`DD%%1Q6ynnu+?7Ij1?@Ya&ZgLSiQX diff --git a/tests/regression_tests/surface_source_write/case-e01/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-e01/surface_source_true.h5 index 0dda15ed2192739435e8f50e126831021d620f53..bbfbd152bc6543871750cb9bd4cc55fe0899754a 100644 GIT binary patch delta 39 vcmX@m!gQd8X@dwm^L6Jdo5k5Vtr*!hD>|xkF|us-^iEf2WZJy3sGlDI{ml#e delta 45 zcmV+|0Mh@!gaW{X0|xkF|us-^iEf2WZJy3sGlDI{ml#e delta 45 zcmV+|0Mh@!gaW{X0|fgu z`(V+RfBSD82<~KyeqpaT&*);s!pHXSSC}u~^zD?rU%;v3d2LP(9h)bH)H5-%Y!(bR wXPmsFL|}77#0D`CW3ohk#N->+5}P~ne=vh(CKPKhf^3?6!Bk-Ljgkyr09VyO4FCWD delta 148 zcmX@m!gQd8X@dwm^Bj*eo5k5BRTx<|8*2A+P4 Date: Fri, 26 Sep 2025 12:27:05 -0400 Subject: [PATCH 117/159] Multi-group capability for kinetics parameter calculations with Iterated Fission Probability (#3425) Co-authored-by: GuySten Co-authored-by: Paul Romano --- docs/source/usersguide/kinetics.rst | 25 +++++++- openmc/model/model.py | 41 +++++++++++-- openmc/statepoint.py | 58 +++++++++++++++++++ src/tallies/tally.cpp | 3 +- src/tallies/tally_scoring.cpp | 9 +++ .../ifp/groupwise/__init__.py | 0 .../ifp/groupwise/inputs_true.dat | 43 ++++++++++++++ .../ifp/groupwise/results_true.dat | 21 +++++++ tests/regression_tests/ifp/groupwise/test.py | 40 +++++++++++++ tests/regression_tests/ifp/total/__init__.py | 0 .../ifp/{ => total}/inputs_true.dat | 0 .../ifp/{ => total}/results_true.dat | 0 .../regression_tests/ifp/{ => total}/test.py | 1 - tests/unit_tests/test_ifp.py | 39 +++++++++++++ 14 files changed, 272 insertions(+), 8 deletions(-) create mode 100644 tests/regression_tests/ifp/groupwise/__init__.py create mode 100644 tests/regression_tests/ifp/groupwise/inputs_true.dat create mode 100644 tests/regression_tests/ifp/groupwise/results_true.dat create mode 100644 tests/regression_tests/ifp/groupwise/test.py create mode 100644 tests/regression_tests/ifp/total/__init__.py rename tests/regression_tests/ifp/{ => total}/inputs_true.dat (100%) rename tests/regression_tests/ifp/{ => total}/results_true.dat (100%) rename tests/regression_tests/ifp/{ => total}/test.py (99%) diff --git a/docs/source/usersguide/kinetics.rst b/docs/source/usersguide/kinetics.rst index bdf26d341b5..9024ff8227a 100644 --- a/docs/source/usersguide/kinetics.rst +++ b/docs/source/usersguide/kinetics.rst @@ -67,6 +67,23 @@ are needed to compute kinetics parameters in OpenMC: Obtaining kinetics parameters ----------------------------- +The ``Model`` class can be used to automatically generate all IFP tallies using +the Python API with :attr:`openmc.Settings.ifp_n_generation` greater than 0 and +the :meth:`openmc.Model.add_ifp_kinetics_tallies` method:: + + model = openmc.Model(geometry, settings=settings) + model.add_kinetics_parameters_tallies(num_groups=6) # Add 6 precursor groups + +Alternatively, each of the tallies can be manually defined using group-wise or +total :math:`\beta_{\text{eff}}` specified by providing a 6-group +:class:`openmc.DelayedGroupFilter`:: + + beta_tally = openmc.Tally(name="group-beta-score") + beta_tally.scores = ["ifp-beta-numerator"] + + # Add DelayedGroupFilter to enable group-wise tallies + beta_tally.filters = [openmc.DelayedGroupFilter(list(range(1, 7)))] + Here is an example showing how to declare the three available IFP scores in a single tally:: @@ -95,6 +112,12 @@ for ``ifp-denominator``: \beta_{\text{eff}} = \frac{S_{\text{ifp-beta-numerator}}}{S_{\text{ifp-denominator}}} +The kinetics parameters can be retrieved directly from a statepoint file using +the :meth:`openmc.StatePoint.ifp_results` method:: + + with openmc.StatePoint(output_path) as sp: + generation_time, beta_eff = sp.get_kinetics_parameters() + .. only:: html .. rubric:: References @@ -107,4 +130,4 @@ for ``ifp-denominator``: of the Iterated Fission Probability Method in OpenMC to Compute Adjoint-Weighted Kinetics Parameters", International Conference on Mathematics and Computational Methods Applied to Nuclear Science and Engineering (M&C 2025), Denver, April 27-30, - 2025 (to be presented). + 2025. diff --git a/openmc/model/model.py b/openmc/model/model.py index 59e6fa511e5..c1ffafafd3f 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -1,7 +1,7 @@ from __future__ import annotations from collections.abc import Iterable, Sequence import copy -from functools import lru_cache +from functools import cache from pathlib import Path import math from numbers import Integral, Real @@ -160,7 +160,7 @@ def is_initialized(self) -> bool: return False @property - @lru_cache(maxsize=None) + @cache def _materials_by_id(self) -> dict: """Dictionary mapping material ID --> material""" if self.materials: @@ -170,14 +170,14 @@ def _materials_by_id(self) -> dict: return {mat.id: mat for mat in mats} @property - @lru_cache(maxsize=None) + @cache def _cells_by_id(self) -> dict: """Dictionary mapping cell ID --> cell""" cells = self.geometry.get_all_cells() return {cell.id: cell for cell in cells.values()} @property - @lru_cache(maxsize=None) + @cache def _cells_by_name(self) -> dict[int, openmc.Cell]: # Get the names maps, but since names are not unique, store a set for # each name key. In this way when the user requests a change by a name, @@ -190,7 +190,7 @@ def _cells_by_name(self) -> dict[int, openmc.Cell]: return result @property - @lru_cache(maxsize=None) + @cache def _materials_by_name(self) -> dict[int, openmc.Material]: if self.materials is None: mats = self.geometry.get_all_materials().values() @@ -203,6 +203,37 @@ def _materials_by_name(self) -> dict[int, openmc.Material]: result[mat.name].add(mat) return result + def add_kinetics_parameters_tallies(self, num_groups: int | None = None): + """Add tallies for calculating kinetics parameters using the IFP method. + + This method adds tallies to the model for calculating two kinetics + parameters, the generation time and the effective delayed neutron + fraction (beta effective). After a model is run, these parameters can be + determined through the :meth:`openmc.StatePoint.ifp_results` method. + + Parameters + ---------- + num_groups : int, optional + Number of precursor groups to filter the delayed neutron fraction. + If None, only the total effective delayed neutron fraction is + tallied. + + """ + if not any('ifp-time-numerator' in t.scores for t in self.tallies): + gen_time_tally = openmc.Tally(name='IFP time numerator') + gen_time_tally.scores = ['ifp-time-numerator'] + self.tallies.append(gen_time_tally) + if not any('ifp-beta-numerator' in t.scores for t in self.tallies): + beta_tally = openmc.Tally(name='IFP beta numerator') + beta_tally.scores = ['ifp-beta-numerator'] + if num_groups is not None: + beta_tally.filters = [openmc.DelayedGroupFilter(list(range(1, num_groups + 1)))] + self.tallies.append(beta_tally) + if not any('ifp-denominator' in t.scores for t in self.tallies): + denom_tally = openmc.Tally(name='IFP denominator') + denom_tally.scores = ['ifp-denominator'] + self.tallies.append(denom_tally) + @classmethod def from_xml( cls, diff --git a/openmc/statepoint.py b/openmc/statepoint.py index 715becf4882..29c11921cbf 100644 --- a/openmc/statepoint.py +++ b/openmc/statepoint.py @@ -1,4 +1,5 @@ from datetime import datetime +from collections import namedtuple import glob import re import os @@ -8,6 +9,7 @@ import numpy as np from pathlib import Path from uncertainties import ufloat +from uncertainties.unumpy import uarray import openmc import openmc.checkvalue as cv @@ -15,6 +17,9 @@ _VERSION_STATEPOINT = 18 +KineticsParameters = namedtuple("KineticsParameters", ["generation_time", "beta_effective"]) + + class StatePoint: """State information on a simulation at a certain point in time (at the end of a given batch). Statepoints can be used to analyze tally results as well @@ -710,3 +715,56 @@ def link_with_summary(self, summary): tally_filter.paths = cell.paths self._summary = summary + + def get_kinetics_parameters(self) -> KineticsParameters: + """Get kinetics parameters from IFP tallies. + + This method searches the tallies in the statepoint for the tallies + required to compute kinetics parameters using the Iterated Fission + Probability (IFP) method. + + Returns + ------- + KineticsParameters + A named tuple containing the generation time and effective delayed + neutron fraction. If the necessary tallies for one or both + parameters are not found, that parameter is returned as None. + + """ + + denom_tally = None + gen_time_tally = None + beta_tally = None + for tally in self.tallies.values(): + if 'ifp-denominator' in tally.scores: + denom_tally = self.get_tally(scores=['ifp-denominator']) + if 'ifp-time-numerator' in tally.scores: + gen_time_tally = self.get_tally(scores=['ifp-time-numerator']) + if 'ifp-beta-numerator' in tally.scores: + beta_tally = self.get_tally(scores=['ifp-beta-numerator']) + + if denom_tally is None: + return KineticsParameters(None, None) + + def get_ufloat(tally, score): + return uarray(tally.get_values(scores=[score]), + tally.get_values(scores=[score], value='std_dev')) + + denom_values = get_ufloat(denom_tally, 'ifp-denominator') + if gen_time_tally is None: + generation_time = None + else: + gen_time_values = get_ufloat(gen_time_tally, 'ifp-time-numerator') + gen_time_values /= denom_values*self.keff + generation_time = gen_time_values.flatten()[0] + + if beta_tally is None: + beta_effective = None + else: + beta_values = get_ufloat(beta_tally, 'ifp-beta-numerator') + beta_values /= denom_values + beta_effective = beta_values.flatten() + if beta_effective.size == 1: + beta_effective = beta_effective[0] + + return KineticsParameters(generation_time, beta_effective) diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index ae0bffe6eb0..b9c615ecb5b 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -560,7 +560,8 @@ void Tally::set_scores(const vector& scores) // Make sure a delayed group filter wasn't used with an incompatible // score. if (delayedgroup_filter_ != C_NONE) { - if (score_str != "delayed-nu-fission" && score_str != "decay-rate") + if (score_str != "delayed-nu-fission" && score_str != "decay-rate" && + score_str != "ifp-beta-numerator") fatal_error("Cannot tally " + score_str + "with a delayedgroup filter"); } diff --git a/src/tallies/tally_scoring.cpp b/src/tallies/tally_scoring.cpp index 0df80a2398f..67e851644a9 100644 --- a/src/tallies/tally_scoring.cpp +++ b/src/tallies/tally_scoring.cpp @@ -964,6 +964,15 @@ void score_general_ce_nonanalog(Particle& p, int i_tally, int start_index, if (delayed_groups.size() == settings::ifp_n_generation) { if (delayed_groups[0] > 0) { score = p.wgt_last(); + if (tally.delayedgroup_filter_ != C_NONE) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_]; + const DelayedGroupFilter& filt { + *dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + score_fission_delayed_dg(i_tally, delayed_groups[0] - 1, + score, score_index, p.filter_matches()); + continue; + } } } } diff --git a/tests/regression_tests/ifp/groupwise/__init__.py b/tests/regression_tests/ifp/groupwise/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/ifp/groupwise/inputs_true.dat b/tests/regression_tests/ifp/groupwise/inputs_true.dat new file mode 100644 index 00000000000..6d7e20717b9 --- /dev/null +++ b/tests/regression_tests/ifp/groupwise/inputs_true.dat @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + eigenvalue + 1000 + 20 + 5 + + + -10.0 -10.0 -10.0 10.0 10.0 10.0 + + + true + + + 5 + + + + 1 2 3 4 5 6 + + + ifp-time-numerator + + + 1 + ifp-beta-numerator + + + ifp-denominator + + + diff --git a/tests/regression_tests/ifp/groupwise/results_true.dat b/tests/regression_tests/ifp/groupwise/results_true.dat new file mode 100644 index 00000000000..ea66a8de3c9 --- /dev/null +++ b/tests/regression_tests/ifp/groupwise/results_true.dat @@ -0,0 +1,21 @@ +k-combined: +1.006559E+00 5.389391E-03 +tally 1: +9.109384E-08 +5.667165E-16 +tally 2: +3.000000E-03 +9.000000E-06 +0.000000E+00 +0.000000E+00 +2.100000E-02 +1.370000E-04 +2.800000E-02 +2.220000E-04 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +tally 3: +1.489000E+01 +1.480036E+01 diff --git a/tests/regression_tests/ifp/groupwise/test.py b/tests/regression_tests/ifp/groupwise/test.py new file mode 100644 index 00000000000..a1a0ebefb8d --- /dev/null +++ b/tests/regression_tests/ifp/groupwise/test.py @@ -0,0 +1,40 @@ +"""Test the Iterated Fission Probability (IFP) method to compute adjoint-weighted +kinetics parameters using dedicated tallies.""" + +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + +@pytest.fixture() +def ifp_model(): + # Material + material = openmc.Material(name="core") + material.add_nuclide("U235", 1.0) + material.set_density('g/cm3', 16.0) + + # Geometry + radius = 10.0 + sphere = openmc.Sphere(r=radius, boundary_type="vacuum") + cell = openmc.Cell(region=-sphere, fill=material) + geometry = openmc.Geometry([cell]) + + # Settings + settings = openmc.Settings() + settings.particles = 1000 + settings.batches = 20 + settings.inactive = 5 + settings.ifp_n_generation = 5 + + model = openmc.Model(settings=settings, geometry=geometry) + + space = openmc.stats.Box(*cell.bounding_box) + model.settings.source = openmc.IndependentSource( + space=space, constraints={'fissionable': True}) + model.add_kinetics_parameters_tallies(num_groups=6) + return model + + +def test_iterated_fission_probability(ifp_model): + harness = PyAPITestHarness("statepoint.20.h5", model=ifp_model) + harness.main() diff --git a/tests/regression_tests/ifp/total/__init__.py b/tests/regression_tests/ifp/total/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/ifp/inputs_true.dat b/tests/regression_tests/ifp/total/inputs_true.dat similarity index 100% rename from tests/regression_tests/ifp/inputs_true.dat rename to tests/regression_tests/ifp/total/inputs_true.dat diff --git a/tests/regression_tests/ifp/results_true.dat b/tests/regression_tests/ifp/total/results_true.dat similarity index 100% rename from tests/regression_tests/ifp/results_true.dat rename to tests/regression_tests/ifp/total/results_true.dat diff --git a/tests/regression_tests/ifp/test.py b/tests/regression_tests/ifp/total/test.py similarity index 99% rename from tests/regression_tests/ifp/test.py rename to tests/regression_tests/ifp/total/test.py index 6969a54c49d..18b89cfc0b7 100644 --- a/tests/regression_tests/ifp/test.py +++ b/tests/regression_tests/ifp/total/test.py @@ -6,7 +6,6 @@ from tests.testing_harness import PyAPITestHarness - @pytest.fixture() def ifp_model(): model = openmc.Model() diff --git a/tests/unit_tests/test_ifp.py b/tests/unit_tests/test_ifp.py index 8d0fd98010d..e527f162460 100644 --- a/tests/unit_tests/test_ifp.py +++ b/tests/unit_tests/test_ifp.py @@ -47,3 +47,42 @@ def test_exceptions(options, error, run_in_tmpdir, geometry): tallies = openmc.Tallies([tally]) model = openmc.Model(geometry=geometry, settings=settings, tallies=tallies) model.run() + + +@pytest.mark.parametrize( + "num_groups, use_auto_tallies", + [ + (None, True), + (None, False), + (6, True), + (6, False), + ], +) +def test_get_kinetics_parameters(run_in_tmpdir, geometry, num_groups, use_auto_tallies): + # Create basic model + model = openmc.Model(geometry=geometry) + model.settings.particles = 1000 + model.settings.batches = 20 + model.settings.inactive = 5 + model.settings.ifp_n_generation = 5 + + # Add IFP tallies either via the convenience method or manually + if use_auto_tallies: + model.add_kinetics_parameters_tallies(num_groups=num_groups) + else: + for score in ["ifp-time-numerator", "ifp-beta-numerator", "ifp-denominator"]: + tally = openmc.Tally() + tally.scores = [score] + if score == "ifp-beta-numerator" and num_groups is not None: + tally.filters = [openmc.DelayedGroupFilter(list(range(1, num_groups + 1)))] + model.tallies.append(tally) + + # Run and get kinetics parameters + sp_file = model.run() + with openmc.StatePoint(sp_file) as sp: + params = sp.get_kinetics_parameters() + assert isinstance(params, openmc.KineticsParameters) + assert params.generation_time is not None + assert params.beta_effective is not None + if num_groups is not None: + assert len(params.beta_effective) == num_groups From 4011b7a5515e4098f179d825313f574680d3a68f Mon Sep 17 00:00:00 2001 From: Perry <100789850+yrrepy@users.noreply.github.com> Date: Fri, 26 Sep 2025 09:46:23 -0700 Subject: [PATCH 118/159] Optional separation of mesh-material-volume calc from get_homogenized_materials (#3581) Co-authored-by: Paul Romano --- openmc/mesh.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/openmc/mesh.py b/openmc/mesh.py index 2e9abd1b65c..9601207e912 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -287,6 +287,7 @@ def get_homogenized_materials( model: openmc.Model, n_samples: int | tuple[int, int, int] = 10_000, include_void: bool = True, + material_volumes: MeshMaterialVolumes | None = None, **kwargs ) -> list[openmc.Material]: """Generate homogenized materials over each element in a mesh. @@ -305,8 +306,12 @@ def get_homogenized_materials( the x, y, and z dimensions. include_void : bool, optional Whether homogenization should include voids. + material_volumes : MeshMaterialVolumes, optional + Previously computed mesh material volumes to use for homogenization. + If not provided, they will be computed by calling + :meth:`material_volumes`. **kwargs - Keyword-arguments passed to :meth:`MeshBase.material_volumes`. + Keyword-arguments passed to :meth:`material_volumes`. Returns ------- @@ -314,7 +319,10 @@ def get_homogenized_materials( Homogenized material in each mesh element """ - vols = self.material_volumes(model, n_samples, **kwargs) + if material_volumes is None: + vols = self.material_volumes(model, n_samples, **kwargs) + else: + vols = material_volumes mat_volume_by_element = [vols.by_element(i) for i in range(vols.num_elements)] # Create homogenized material for each element @@ -424,7 +432,6 @@ def material_volumes( # Restore original tallies model.tallies = original_tallies - return volumes From feefcc671307fc84e7357430f5f07343503919e5 Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Wed, 1 Oct 2025 00:09:15 +0200 Subject: [PATCH 119/159] Adding tally filter type option to statepoint get_tally (#3584) Co-authored-by: Jon Shimwell Co-authored-by: GuySten <62616591+GuySten@users.noreply.github.com> --- openmc/statepoint.py | 9 +++- tests/unit_tests/test_statepoint.py | 65 +++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 tests/unit_tests/test_statepoint.py diff --git a/openmc/statepoint.py b/openmc/statepoint.py index 29c11921cbf..a763db3971b 100644 --- a/openmc/statepoint.py +++ b/openmc/statepoint.py @@ -536,7 +536,7 @@ def add_volume_information(self, volume_calc): def get_tally(self, scores=[], filters=[], nuclides=[], name=None, id=None, estimator=None, exact_filters=False, exact_nuclides=False, exact_scores=False, - multiply_density=None, derivative=None): + multiply_density=None, derivative=None, filter_type=None): """Finds and returns a Tally object with certain properties. This routine searches the list of Tallies and returns the first Tally @@ -580,6 +580,9 @@ def get_tally(self, scores=[], filters=[], nuclides=[], to the same value as this parameter. derivative : openmc.TallyDerivative, optional TallyDerivative object to match. + filter_type : type, optional + If not None, the Tally must have at least one Filter that is an + instance of this type. For example `openmc.MeshFilter`. Returns ------- @@ -653,6 +656,10 @@ def get_tally(self, scores=[], filters=[], nuclides=[], if not contains_filters: continue + if filter_type is not None: + if not any(isinstance(f, filter_type) for f in test_tally.filters): + continue + # Determine if Tally has the queried Nuclide(s) if nuclides: if not all(nuclide in test_tally.nuclides for nuclide in nuclides): diff --git a/tests/unit_tests/test_statepoint.py b/tests/unit_tests/test_statepoint.py new file mode 100644 index 00000000000..7ffaf7ec2cc --- /dev/null +++ b/tests/unit_tests/test_statepoint.py @@ -0,0 +1,65 @@ +import openmc + + +def test_get_tally_filter_type(run_in_tmpdir): + """Test various ways of retrieving tallies from a StatePoint object.""" + + mat = openmc.Material() + mat.add_nuclide("H1", 1.0) + mat.set_density("g/cm3", 10.0) + + sphere = openmc.Sphere(r=10.0, boundary_type="vacuum") + cell = openmc.Cell(fill=mat, region=-sphere) + geometry = openmc.Geometry([cell]) + + settings = openmc.Settings() + settings.particles = 10 + settings.batches = 2 + settings.run_mode = "fixed source" + + reg_mesh = openmc.RegularMesh().from_domain(cell) + tally1 = openmc.Tally(tally_id=1) + mesh_filter = openmc.MeshFilter(reg_mesh) + tally1.filters = [mesh_filter] + tally1.scores = ["flux"] + + tally2 = openmc.Tally(tally_id=2, name="heating tally") + cell_filter = openmc.CellFilter(cell) + tally2.filters = [cell_filter] + tally2.scores = ["heating"] + + tallies = openmc.Tallies([tally1, tally2]) + model = openmc.Model( + geometry=geometry, materials=[mat], settings=settings, tallies=tallies + ) + + sp_filename = model.run() + + sp = openmc.StatePoint(sp_filename) + + tally_found = sp.get_tally(filter_type=openmc.MeshFilter) + assert tally_found.id == 1 + + tally_found = sp.get_tally(filter_type=openmc.CellFilter) + assert tally_found.id == 2 + + tally_found = sp.get_tally(filters=[mesh_filter]) + assert tally_found.id == 1 + + tally_found = sp.get_tally(filters=[cell_filter]) + assert tally_found.id == 2 + + tally_found = sp.get_tally(scores=["heating"]) + assert tally_found.id == 2 + + tally_found = sp.get_tally(name="heating tally") + assert tally_found.id == 2 + + tally_found = sp.get_tally(name=None) + assert tally_found.id == 1 + + tally_found = sp.get_tally(id=1) + assert tally_found.id == 1 + + tally_found = sp.get_tally(id=2) + assert tally_found.id == 2 From 3ac64d9a01d8d10ff126d62d8a9f618fbd803f4b Mon Sep 17 00:00:00 2001 From: John Tramm Date: Thu, 2 Oct 2025 11:02:43 -0500 Subject: [PATCH 120/159] Random Ray Base Source Region Refactor (#3576) --- include/openmc/constants.h | 5 + .../openmc/random_ray/flat_source_domain.h | 47 ++- .../openmc/random_ray/linear_source_domain.h | 2 +- include/openmc/random_ray/random_ray.h | 1 - .../openmc/random_ray/random_ray_simulation.h | 9 +- include/openmc/random_ray/source_region.h | 1 - src/random_ray/flat_source_domain.cpp | 375 ++++++++++-------- src/random_ray/linear_source_domain.cpp | 47 +-- src/random_ray/random_ray.cpp | 123 +++--- src/random_ray/random_ray_simulation.cpp | 166 +++----- src/random_ray/source_region.cpp | 28 +- src/settings.cpp | 1 - .../random_ray_low_density/__init__.py | 0 .../random_ray_low_density/inputs_true.dat | 244 ++++++++++++ .../random_ray_low_density/results_true.dat | 9 + .../random_ray_low_density/test.py | 60 +++ .../results_true.dat | 8 +- 17 files changed, 690 insertions(+), 436 deletions(-) create mode 100644 tests/regression_tests/random_ray_low_density/__init__.py create mode 100644 tests/regression_tests/random_ray_low_density/inputs_true.dat create mode 100644 tests/regression_tests/random_ray_low_density/results_true.dat create mode 100644 tests/regression_tests/random_ray_low_density/test.py diff --git a/include/openmc/constants.h b/include/openmc/constants.h index df13da3707d..a0d1646131c 100644 --- a/include/openmc/constants.h +++ b/include/openmc/constants.h @@ -68,6 +68,11 @@ constexpr double MIN_HITS_PER_BATCH {1.5}; // prevent extremely large adjoint source terms from being generated. constexpr double ZERO_FLUX_CUTOFF {1e-22}; +// The minimum macroscopic cross section value considered non-void for the +// random ray solver. Materials with any group with a cross section below this +// value will be converted to pure void. +constexpr double MINIMUM_MACRO_XS {1e-6}; + // ============================================================================ // MATH AND PHYSICAL CONSTANTS diff --git a/include/openmc/random_ray/flat_source_domain.h b/include/openmc/random_ray/flat_source_domain.h index 78351fcc5f5..d4e80273460 100644 --- a/include/openmc/random_ray/flat_source_domain.h +++ b/include/openmc/random_ray/flat_source_domain.h @@ -27,8 +27,9 @@ class FlatSourceDomain { //---------------------------------------------------------------------------- // Methods - virtual void update_neutron_source(double k_eff); - double compute_k_eff(double k_eff_old) const; + virtual void update_single_neutron_source(SourceRegionHandle& srh); + virtual void update_all_neutron_sources(); + void compute_k_eff(); virtual void normalize_scalar_flux_and_volumes( double total_active_distance_per_iteration); @@ -41,7 +42,7 @@ class FlatSourceDomain { void output_to_vtk() const; void convert_external_sources(); void count_external_source_regions(); - void set_adjoint_sources(const vector& forward_flux); + void set_adjoint_sources(); void flux_swap(); virtual double evaluate_flux_at_point(Position r, int64_t sr, int g) const; double compute_fixed_source_normalization_factor() const; @@ -54,9 +55,8 @@ class FlatSourceDomain { bool is_target_void); void apply_mesh_to_cell_and_children(int32_t i_cell, int32_t mesh_idx, int32_t target_material_id, bool is_target_void); - void prepare_base_source_regions(); SourceRegionHandle get_subdivided_source_region_handle( - int64_t sr, int mesh_bin, Position r, double dist, Direction u); + SourceRegionKey sr_key, Position r, Direction u); void finalize_discovered_source_regions(); void apply_transport_stabilization(); int64_t n_source_regions() const @@ -67,6 +67,10 @@ class FlatSourceDomain { { return source_regions_.n_source_regions() * negroups_; } + int64_t lookup_base_source_region_idx(const GeometryState& p) const; + SourceRegionKey lookup_source_region_key(const GeometryState& p) const; + int64_t lookup_mesh_bin(int64_t sr, Position r) const; + int lookup_mesh_idx(int64_t sr) const; //---------------------------------------------------------------------------- // Static Data members @@ -86,6 +90,7 @@ class FlatSourceDomain { //---------------------------------------------------------------------------- // Public Data members + double k_eff_ {1.0}; // Eigenvalue bool mapped_all_tallies_ {false}; // If all source regions have been visited int64_t n_external_source_regions_ {0}; // Total number of source regions with @@ -110,14 +115,6 @@ class FlatSourceDomain { // The abstract container holding all source region-specific data SourceRegionContainer source_regions_; - // Base source region container. When source region subdivision via mesh - // is in use, this container holds the original (non-subdivided) material - // filled cell instance source regions. These are useful as they can be - // initialized with external source and mesh domain information ahead of time. - // Then, dynamically discovered source regions can be initialized by cloning - // their base region. - SourceRegionContainer base_source_regions_; - // Parallel hash map holding all source regions discovered during // a single iteration. This is a threadsafe data structure that is cleaned // out after each iteration and stored in the "source_regions_" container. @@ -134,8 +131,17 @@ class FlatSourceDomain { // Map that relates a SourceRegionKey to the external source index. This map // is used to check if there are any point sources within a subdivided source // region at the time it is discovered. - std::unordered_map - point_source_map_; + std::unordered_map, SourceRegionKey::HashFunctor> + external_point_source_map_; + + // Map that relates a base source region index to the external source index. + // This map is used to check if there are any volumetric sources within a + // subdivided source region at the time it is discovered. + std::unordered_map> external_volumetric_source_map_; + + // Map that relates a base source region index to a mesh index. This map + // is used to check which subdivision mesh is present in a source region. + std::unordered_map mesh_map_; // If transport corrected MGXS data is being used, there may be negative // in-group scattering cross sections that can result in instability in MOC @@ -147,12 +153,11 @@ class FlatSourceDomain { //---------------------------------------------------------------------------- // Methods void apply_external_source_to_source_region( - Discrete* discrete, double strength_factor, SourceRegionHandle& srh); - void apply_external_source_to_cell_instances(int32_t i_cell, - Discrete* discrete, double strength_factor, int target_material_id, - const vector& instances); - void apply_external_source_to_cell_and_children(int32_t i_cell, - Discrete* discrete, double strength_factor, int32_t target_material_id); + int src_idx, SourceRegionHandle& srh); + void apply_external_source_to_cell_instances(int32_t i_cell, int src_idx, + int target_material_id, const vector& instances); + void apply_external_source_to_cell_and_children( + int32_t i_cell, int src_idx, int32_t target_material_id); virtual void set_flux_to_flux_plus_source(int64_t sr, double volume, int g); void set_flux_to_source(int64_t sr, int g); virtual void set_flux_to_old_flux(int64_t sr, int g); diff --git a/include/openmc/random_ray/linear_source_domain.h b/include/openmc/random_ray/linear_source_domain.h index 67fdd99f880..0098c782001 100644 --- a/include/openmc/random_ray/linear_source_domain.h +++ b/include/openmc/random_ray/linear_source_domain.h @@ -20,7 +20,7 @@ class LinearSourceDomain : public FlatSourceDomain { public: //---------------------------------------------------------------------------- // Methods - void update_neutron_source(double k_eff) override; + void update_single_neutron_source(SourceRegionHandle& srh) override; void normalize_scalar_flux_and_volumes( double total_active_distance_per_iteration) override; diff --git a/include/openmc/random_ray/random_ray.h b/include/openmc/random_ray/random_ray.h index abf2a26881f..40c67ef9549 100644 --- a/include/openmc/random_ray/random_ray.h +++ b/include/openmc/random_ray/random_ray.h @@ -48,7 +48,6 @@ class RandomRay : public Particle { static double distance_active_; // Active ray length static unique_ptr ray_source_; // Starting source for ray sampling static RandomRaySourceShape source_shape_; // Flag for linear source - static bool mesh_subdivision_enabled_; // Flag for mesh subdivision static RandomRaySampleMethod sample_method_; // Flag for sampling method //---------------------------------------------------------------------------- diff --git a/include/openmc/random_ray/random_ray_simulation.h b/include/openmc/random_ray/random_ray_simulation.h index b94e7401b3e..3dec48bf266 100644 --- a/include/openmc/random_ray/random_ray_simulation.h +++ b/include/openmc/random_ray/random_ray_simulation.h @@ -21,11 +21,7 @@ class RandomRaySimulation { // Methods void compute_segment_correction_factors(); void apply_fixed_sources_and_mesh_domains(); - void prepare_fixed_sources_adjoint(vector& forward_flux, - SourceRegionContainer& forward_source_regions, - SourceRegionContainer& forward_base_source_regions, - std::unordered_map& - forward_source_region_map); + void prepare_fixed_sources_adjoint(); void simulate(); void output_simulation_results() const; void instability_check( @@ -45,9 +41,6 @@ class RandomRaySimulation { // Contains all flat source region data unique_ptr domain_; - // Random ray eigenvalue - double k_eff_ {1.0}; - // Tracks the average FSR miss rate for analysis and reporting double avg_miss_rate_ {0.0}; diff --git a/include/openmc/random_ray/source_region.h b/include/openmc/random_ray/source_region.h index 5c5b31f392e..0f5a747fff8 100644 --- a/include/openmc/random_ray/source_region.h +++ b/include/openmc/random_ray/source_region.h @@ -308,7 +308,6 @@ class SourceRegion { //---------------------------------------------------------------------------- // Constructors SourceRegion(int negroups, bool is_linear); - SourceRegion(const SourceRegionHandle& handle, int64_t parent_sr); SourceRegion() = default; //---------------------------------------------------------------------------- diff --git a/src/random_ray/flat_source_domain.cpp b/src/random_ray/flat_source_domain.cpp index 40923883088..1bf27e1eda1 100644 --- a/src/random_ray/flat_source_domain.cpp +++ b/src/random_ray/flat_source_domain.cpp @@ -53,24 +53,6 @@ FlatSourceDomain::FlatSourceDomain() : negroups_(data::mg.num_energy_groups_) // Initialize source regions. bool is_linear = RandomRay::source_shape_ != RandomRaySourceShape::FLAT; source_regions_ = SourceRegionContainer(negroups_, is_linear); - source_regions_.assign( - base_source_regions, SourceRegion(negroups_, is_linear)); - - // Initialize materials - int64_t source_region_id = 0; - for (int i = 0; i < model::cells.size(); i++) { - Cell& cell = *model::cells[i]; - if (cell.type_ == Fill::MATERIAL) { - for (int j = 0; j < cell.n_instances(); j++) { - source_regions_.material(source_region_id++) = cell.material(j); - } - } - } - - // Sanity check - if (source_region_id != base_source_regions) { - fatal_error("Unexpected number of source regions"); - } // Initialize tally volumes if (volume_normalized_flux_tallies_) { @@ -118,34 +100,24 @@ void FlatSourceDomain::accumulate_iteration_flux() } } -// Compute new estimate of scattering + fission sources in each source region -// based on the flux estimate from the previous iteration. -void FlatSourceDomain::update_neutron_source(double k_eff) +void FlatSourceDomain::update_single_neutron_source(SourceRegionHandle& srh) { - simulation::time_update_src.start(); - - double inverse_k_eff = 1.0 / k_eff; - -// Reset all source regions to zero (important for void regions) -#pragma omp parallel for - for (int64_t se = 0; se < n_source_elements(); se++) { - source_regions_.source(se) = 0.0; + // Reset all source regions to zero (important for void regions) + for (int g = 0; g < negroups_; g++) { + srh.source(g) = 0.0; } // Add scattering + fission source -#pragma omp parallel for - for (int64_t sr = 0; sr < n_source_regions(); sr++) { - int material = source_regions_.material(sr); - if (material == MATERIAL_VOID) { - continue; - } + int material = srh.material(); + if (material != MATERIAL_VOID) { + double inverse_k_eff = 1.0 / k_eff_; for (int g_out = 0; g_out < negroups_; g_out++) { double sigma_t = sigma_t_[material * negroups_ + g_out]; double scatter_source = 0.0; double fission_source = 0.0; for (int g_in = 0; g_in < negroups_; g_in++) { - double scalar_flux = source_regions_.scalar_flux_old(sr, g_in); + double scalar_flux = srh.scalar_flux_old(g_in); double sigma_s = sigma_s_[material * negroups_ * negroups_ + g_out * negroups_ + g_in]; double nu_sigma_f = nu_sigma_f_[material * negroups_ + g_in]; @@ -154,18 +126,30 @@ void FlatSourceDomain::update_neutron_source(double k_eff) scatter_source += sigma_s * scalar_flux; fission_source += nu_sigma_f * scalar_flux * chi; } - source_regions_.source(sr, g_out) = + srh.source(g_out) = (scatter_source + fission_source * inverse_k_eff) / sigma_t; } } // Add external source if in fixed source mode if (settings::run_mode == RunMode::FIXED_SOURCE) { -#pragma omp parallel for - for (int64_t se = 0; se < n_source_elements(); se++) { - source_regions_.source(se) += source_regions_.external_source(se); + for (int g = 0; g < negroups_; g++) { + srh.source(g) += srh.external_source(g); } } +} + +// Compute new estimate of scattering + fission sources in each source region +// based on the flux estimate from the previous iteration. +void FlatSourceDomain::update_all_neutron_sources() +{ + simulation::time_update_src.start(); + +#pragma omp parallel for + for (int64_t sr = 0; sr < n_source_regions(); sr++) { + SourceRegionHandle srh = source_regions_.get_source_region_handle(sr); + update_single_neutron_source(srh); + } simulation::time_update_src.stop(); } @@ -320,7 +304,7 @@ int64_t FlatSourceDomain::add_source_to_scalar_flux() // Generates new estimate of k_eff based on the differences between this // iteration's estimate of the scalar flux and the last iteration's estimate. -double FlatSourceDomain::compute_k_eff(double k_eff_old) const +void FlatSourceDomain::compute_k_eff() { double fission_rate_old = 0; double fission_rate_new = 0; @@ -365,7 +349,7 @@ double FlatSourceDomain::compute_k_eff(double k_eff_old) const p[sr] = sr_fission_source_new; } - double k_eff_new = k_eff_old * (fission_rate_new / fission_rate_old); + double k_eff_new = k_eff_ * (fission_rate_new / fission_rate_old); double H = 0.0; // defining an inverse sum for better performance @@ -385,7 +369,7 @@ double FlatSourceDomain::compute_k_eff(double k_eff_old) const // Adds entropy value to shared entropy vector in openmc namespace. simulation::entropy.push_back(H); - return k_eff_new; + k_eff_ = k_eff_new; } // This function is responsible for generating a mapping between random @@ -652,7 +636,6 @@ void FlatSourceDomain::random_ray_tally() "random ray mode."); break; } - // Apply score to the appropriate tally bin Tally& tally {*model::tallies[task.tally_idx]}; #pragma omp atomic @@ -726,21 +709,21 @@ void FlatSourceDomain::output_to_vtk() const print_plot(); // Outer loop over plots - for (int p = 0; p < model::plots.size(); p++) { + for (int plt = 0; plt < model::plots.size(); plt++) { // Get handle to OpenMC plot object and extract params - Plot* openmc_plot = dynamic_cast(model::plots[p].get()); + Plot* openmc_plot = dynamic_cast(model::plots[plt].get()); // Random ray plots only support voxel plots if (!openmc_plot) { warning(fmt::format("Plot {} is invalid plot type -- only voxel plotting " "is allowed in random ray mode.", - p)); + plt)); continue; } else if (openmc_plot->type_ != Plot::PlotType::voxel) { warning(fmt::format("Plot {} is invalid plot type -- only voxel plotting " "is allowed in random ray mode.", - p)); + plt)); continue; } @@ -794,23 +777,11 @@ void FlatSourceDomain::output_to_vtk() const continue; } - int i_cell = p.lowest_coord().cell(); - int64_t sr = source_region_offsets_[i_cell] + p.cell_instance(); - if (RandomRay::mesh_subdivision_enabled_) { - int mesh_idx = base_source_regions_.mesh(sr); - int mesh_bin; - if (mesh_idx == C_NONE) { - mesh_bin = 0; - } else { - mesh_bin = model::meshes[mesh_idx]->get_bin(p.r()); - } - SourceRegionKey sr_key {sr, mesh_bin}; - auto it = source_region_map_.find(sr_key); - if (it != source_region_map_.end()) { - sr = it->second; - } else { - sr = -1; - } + SourceRegionKey sr_key = lookup_source_region_key(p); + int64_t sr = -1; + auto it = source_region_map_.find(sr_key); + if (it != source_region_map_.end()) { + sr = it->second; } voxel_indices[z * Ny * Nx + y * Nx + x] = sr; @@ -967,13 +938,17 @@ void FlatSourceDomain::output_to_vtk() const } void FlatSourceDomain::apply_external_source_to_source_region( - Discrete* discrete, double strength_factor, SourceRegionHandle& srh) + int src_idx, SourceRegionHandle& srh) { - srh.external_source_present() = 1; - + auto s = model::external_sources[src_idx].get(); + auto is = dynamic_cast(s); + auto discrete = dynamic_cast(is->energy()); + double strength_factor = is->strength(); const auto& discrete_energies = discrete->x(); const auto& discrete_probs = discrete->prob(); + srh.external_source_present() = 1; + for (int i = 0; i < discrete_energies.size(); i++) { int g = data::mg.get_group_index(discrete_energies[i]); srh.external_source(g) += discrete_probs[i] * strength_factor; @@ -981,8 +956,7 @@ void FlatSourceDomain::apply_external_source_to_source_region( } void FlatSourceDomain::apply_external_source_to_cell_instances(int32_t i_cell, - Discrete* discrete, double strength_factor, int target_material_id, - const vector& instances) + int src_idx, int target_material_id, const vector& instances) { Cell& cell = *model::cells[i_cell]; @@ -1000,16 +974,13 @@ void FlatSourceDomain::apply_external_source_to_cell_instances(int32_t i_cell, if (target_material_id == C_NONE || cell_material_id == target_material_id) { int64_t source_region = source_region_offsets_[i_cell] + j; - SourceRegionHandle srh = - source_regions_.get_source_region_handle(source_region); - apply_external_source_to_source_region(discrete, strength_factor, srh); + external_volumetric_source_map_[source_region].push_back(src_idx); } } } void FlatSourceDomain::apply_external_source_to_cell_and_children( - int32_t i_cell, Discrete* discrete, double strength_factor, - int32_t target_material_id) + int32_t i_cell, int src_idx, int32_t target_material_id) { Cell& cell = *model::cells[i_cell]; @@ -1017,14 +988,14 @@ void FlatSourceDomain::apply_external_source_to_cell_and_children( vector instances(cell.n_instances()); std::iota(instances.begin(), instances.end(), 0); apply_external_source_to_cell_instances( - i_cell, discrete, strength_factor, target_material_id, instances); + i_cell, src_idx, target_material_id, instances); } else if (target_material_id == C_NONE) { std::unordered_map> cell_instance_list = cell.get_contained_cells(0, nullptr); for (const auto& pair : cell_instance_list) { int32_t i_child_cell = pair.first; - apply_external_source_to_cell_instances(i_child_cell, discrete, - strength_factor, target_material_id, pair.second); + apply_external_source_to_cell_instances( + i_child_cell, src_idx, target_material_id, pair.second); } } } @@ -1070,36 +1041,17 @@ void FlatSourceDomain::convert_external_sources() "point source at {}", sp->r())); } - int i_cell = gs.lowest_coord().cell(); - int64_t sr = source_region_offsets_[i_cell] + gs.cell_instance(); - - if (RandomRay::mesh_subdivision_enabled_) { - // If mesh subdivision is enabled, we need to determine which subdivided - // mesh bin the point source coordinate is in as well - int mesh_idx = source_regions_.mesh(sr); - int mesh_bin; - if (mesh_idx == C_NONE) { - mesh_bin = 0; - } else { - mesh_bin = model::meshes[mesh_idx]->get_bin(gs.r()); - } - // With the source region and mesh bin known, we can use the - // accompanying SourceRegionKey as a key into a map that stores the - // corresponding external source index for the point source. Notably, we - // do not actually apply the external source to any source regions here, - // as if mesh subdivision is enabled, they haven't actually been - // discovered & initilized yet. When discovered, they will read from the - // point_source_map to determine if there are any point source terms - // that should be applied. - SourceRegionKey key {sr, mesh_bin}; - point_source_map_[key] = es; - } else { - // If we are not using mesh subdivision, we can apply the external - // source directly to the source region as we do for volumetric domain - // constraint sources. - SourceRegionHandle srh = source_regions_.get_source_region_handle(sr); - apply_external_source_to_source_region(energy, strength_factor, srh); - } + SourceRegionKey key = lookup_source_region_key(gs); + + // With the source region and mesh bin known, we can use the + // accompanying SourceRegionKey as a key into a map that stores the + // corresponding external source index for the point source. Notably, we + // do not actually apply the external source to any source regions here, + // as if mesh subdivision is enabled, they haven't actually been + // discovered & initilized yet. When discovered, they will read from the + // external_source_map to determine if there are any external source + // terms that should be applied. + external_point_source_map_[key].push_back(es); } else { // If not a point source, then use the volumetric domain constraints to @@ -1107,42 +1059,25 @@ void FlatSourceDomain::convert_external_sources() if (is->domain_type() == Source::DomainType::MATERIAL) { for (int32_t material_id : domain_ids) { for (int i_cell = 0; i_cell < model::cells.size(); i_cell++) { - apply_external_source_to_cell_and_children( - i_cell, energy, strength_factor, material_id); + apply_external_source_to_cell_and_children(i_cell, es, material_id); } } } else if (is->domain_type() == Source::DomainType::CELL) { for (int32_t cell_id : domain_ids) { int32_t i_cell = model::cell_map[cell_id]; - apply_external_source_to_cell_and_children( - i_cell, energy, strength_factor, C_NONE); + apply_external_source_to_cell_and_children(i_cell, es, C_NONE); } } else if (is->domain_type() == Source::DomainType::UNIVERSE) { for (int32_t universe_id : domain_ids) { int32_t i_universe = model::universe_map[universe_id]; Universe& universe = *model::universes[i_universe]; for (int32_t i_cell : universe.cells_) { - apply_external_source_to_cell_and_children( - i_cell, energy, strength_factor, C_NONE); + apply_external_source_to_cell_and_children(i_cell, es, C_NONE); } } } } } // End loop over external sources - -// Divide the fixed source term by sigma t (to save time when applying each -// iteration) -#pragma omp parallel for - for (int64_t sr = 0; sr < n_source_regions(); sr++) { - int material = source_regions_.material(sr); - if (material == MATERIAL_VOID) { - continue; - } - for (int g = 0; g < negroups_; g++) { - double sigma_t = sigma_t_[material * negroups_ + g]; - source_regions_.external_source(sr, g) /= sigma_t; - } - } } void FlatSourceDomain::flux_swap() @@ -1159,13 +1094,23 @@ void FlatSourceDomain::flatten_xs() const int a = 0; n_materials_ = data::mg.macro_xs_.size(); - for (auto& m : data::mg.macro_xs_) { + for (int i = 0; i < n_materials_; i++) { + auto& m = data::mg.macro_xs_[i]; for (int g_out = 0; g_out < negroups_; g_out++) { if (m.exists_in_model) { double sigma_t = m.get_xs(MgxsType::TOTAL, g_out, NULL, NULL, NULL, t, a); sigma_t_.push_back(sigma_t); + if (sigma_t < MINIMUM_MACRO_XS) { + Material* mat = model::materials[i].get(); + warning(fmt::format( + "Material \"{}\" (id: {}) has a group {} total cross section " + "({:.3e}) below the minimum threshold " + "({:.3e}). Material will be treated as pure void.", + mat->name(), mat->id(), g_out, sigma_t, MINIMUM_MACRO_XS)); + } + double nu_sigma_f = m.get_xs(MgxsType::NU_FISSION, g_out, NULL, NULL, NULL, t, a); nu_sigma_f_.push_back(nu_sigma_f); @@ -1206,7 +1151,7 @@ void FlatSourceDomain::flatten_xs() } } -void FlatSourceDomain::set_adjoint_sources(const vector& forward_flux) +void FlatSourceDomain::set_adjoint_sources() { // Set the adjoint external source to 1/forward_flux. If the forward flux is // negative, zero, or extremely close to zero, set the adjoint source to zero, @@ -1220,7 +1165,7 @@ void FlatSourceDomain::set_adjoint_sources(const vector& forward_flux) double max_flux = 0.0; #pragma omp parallel for reduction(max : max_flux) for (int64_t se = 0; se < n_source_elements(); se++) { - double flux = forward_flux[se]; + double flux = source_regions_.scalar_flux_final(se); if (flux > max_flux) { max_flux = flux; } @@ -1230,7 +1175,7 @@ void FlatSourceDomain::set_adjoint_sources(const vector& forward_flux) #pragma omp parallel for for (int64_t sr = 0; sr < n_source_regions(); sr++) { for (int g = 0; g < negroups_; g++) { - double flux = forward_flux[sr * negroups_ + g]; + double flux = source_regions_.scalar_flux_final(sr, g); if (flux <= ZERO_FLUX_CUTOFF * max_flux) { source_regions_.external_source(sr, g) = 0.0; } else { @@ -1239,6 +1184,7 @@ void FlatSourceDomain::set_adjoint_sources(const vector& forward_flux) if (flux > 0.0) { source_regions_.external_source_present(sr) = 1; } + source_regions_.scalar_flux_final(sr, g) = 0.0; } } @@ -1265,7 +1211,6 @@ void FlatSourceDomain::set_adjoint_sources(const vector& forward_flux) source_regions_.external_source_present(sr) = 0; } } - // Divide the fixed source term by sigma t (to save time when applying each // iteration) #pragma omp parallel for @@ -1326,13 +1271,14 @@ void FlatSourceDomain::apply_mesh_to_cell_instances(int32_t i_cell, if ((target_material_id == C_NONE && !is_target_void) || cell_material_id == target_material_id) { int64_t sr = source_region_offsets_[i_cell] + j; - if (source_regions_.mesh(sr) != C_NONE) { - // print out the source region that is broken: + // Check if the key is already present in the mesh_map_ + if (mesh_map_.find(sr) != mesh_map_.end()) { fatal_error(fmt::format("Source region {} already has mesh idx {} " "applied, but trying to apply mesh idx {}", - sr, source_regions_.mesh(sr), mesh_idx)); + sr, mesh_map_[sr], mesh_idx)); } - source_regions_.mesh(sr) = mesh_idx; + // If the SR has not already been assigned, then we can write to it + mesh_map_[sr] = mesh_idx; } } } @@ -1402,18 +1348,9 @@ void FlatSourceDomain::apply_meshes() } } -void FlatSourceDomain::prepare_base_source_regions() -{ - std::swap(source_regions_, base_source_regions_); - source_regions_.negroups() = base_source_regions_.negroups(); - source_regions_.is_linear() = base_source_regions_.is_linear(); -} - SourceRegionHandle FlatSourceDomain::get_subdivided_source_region_handle( - int64_t sr, int mesh_bin, Position r, double dist, Direction u) + SourceRegionKey sr_key, Position r, Direction u) { - SourceRegionKey sr_key {sr, mesh_bin}; - // Case 1: Check if the source region key is already present in the permanent // map. This is the most common condition, as any source region visited in a // previous power iteration will already be present in the permanent map. If @@ -1475,9 +1412,8 @@ SourceRegionHandle FlatSourceDomain::get_subdivided_source_region_handle( gs.r() = r + TINY_BIT * u; gs.u() = {1.0, 0.0, 0.0}; exhaustive_find_cell(gs); - int gs_i_cell = gs.lowest_coord().cell(); - int64_t sr_found = source_region_offsets_[gs_i_cell] + gs.cell_instance(); - if (sr_found != sr) { + int64_t sr_found = lookup_base_source_region_idx(gs); + if (sr_found != sr_key.base_source_region_id) { discovered_source_regions_.unlock(sr_key); SourceRegionHandle handle; handle.is_numerical_fp_artifact_ = true; @@ -1485,9 +1421,9 @@ SourceRegionHandle FlatSourceDomain::get_subdivided_source_region_handle( } // Sanity check on mesh bin - int mesh_idx = base_source_regions_.mesh(sr); + int mesh_idx = lookup_mesh_idx(sr_key.base_source_region_id); if (mesh_idx == C_NONE) { - if (mesh_bin != 0) { + if (sr_key.mesh_bin != 0) { discovered_source_regions_.unlock(sr_key); SourceRegionHandle handle; handle.is_numerical_fp_artifact_ = true; @@ -1496,7 +1432,7 @@ SourceRegionHandle FlatSourceDomain::get_subdivided_source_region_handle( } else { Mesh* mesh = model::meshes[mesh_idx].get(); int bin_found = mesh->get_bin(r + TINY_BIT * u); - if (bin_found != mesh_bin) { + if (bin_found != sr_key.mesh_bin) { discovered_source_regions_.unlock(sr_key); SourceRegionHandle handle; handle.is_numerical_fp_artifact_ = true; @@ -1508,26 +1444,60 @@ SourceRegionHandle FlatSourceDomain::get_subdivided_source_region_handle( // condition only occurs the first time the source region is discovered // (typically in the first power iteration). In this case, we need to handle // creation of the new source region and its storage into the parallel map. - // The new source region is created by copying the base source region, so as - // to inherit material, external source, and some flux properties etc. We - // also pass the base source region id to allow the new source region to - // know which base source region it is derived from. - SourceRegion* sr_ptr = discovered_source_regions_.emplace( - sr_key, {base_source_regions_.get_source_region_handle(sr), sr}); - discovered_source_regions_.unlock(sr_key); + // Additionally, we need to determine the source region's material, initialize + // the starting scalar flux guess, and apply any known external sources. + + // Call the basic constructor for the source region and store in the parallel + // map. + bool is_linear = RandomRay::source_shape_ != RandomRaySourceShape::FLAT; + SourceRegion* sr_ptr = + discovered_source_regions_.emplace(sr_key, {negroups_, is_linear}); SourceRegionHandle handle {*sr_ptr}; - // Check if the new source region contains a point source and apply it if so - auto it2 = point_source_map_.find(sr_key); - if (it2 != point_source_map_.end()) { - int es = it2->second; - auto s = model::external_sources[es].get(); - auto is = dynamic_cast(s); - auto energy = dynamic_cast(is->energy()); - double strength_factor = is->strength(); - apply_external_source_to_source_region(energy, strength_factor, handle); - int material = handle.material(); - if (material != MATERIAL_VOID) { + // Determine the material + int gs_i_cell = gs.lowest_coord().cell(); + Cell& cell = *model::cells[gs_i_cell]; + int material = cell.material(gs.cell_instance()); + + // If material total XS is extremely low, just set it to void to avoid + // problems with 1/Sigma_t + for (int g = 0; g < negroups_; g++) { + double sigma_t = sigma_t_[material * negroups_ + g]; + if (sigma_t < MINIMUM_MACRO_XS) { + material = MATERIAL_VOID; + break; + } + } + + handle.material() = material; + + // Store the mesh index (if any) assigned to this source region + handle.mesh() = mesh_idx; + + if (settings::run_mode == RunMode::FIXED_SOURCE) { + // Determine if there are any volumetric sources, and apply them. + // Volumetric sources are specifc only to the base SR idx. + auto it_vol = + external_volumetric_source_map_.find(sr_key.base_source_region_id); + if (it_vol != external_volumetric_source_map_.end()) { + const vector& vol_sources = it_vol->second; + for (int src_idx : vol_sources) { + apply_external_source_to_source_region(src_idx, handle); + } + } + + // Determine if there are any point sources, and apply them. + // Point sources are specific to the source region key. + auto it_point = external_point_source_map_.find(sr_key); + if (it_point != external_point_source_map_.end()) { + const vector& point_sources = it_point->second; + for (int src_idx : point_sources) { + apply_external_source_to_source_region(src_idx, handle); + } + } + + // Divide external source term by sigma_t + if (material != C_NONE) { for (int g = 0; g < negroups_; g++) { double sigma_t = sigma_t_[material * negroups_ + g]; handle.external_source(g) /= sigma_t; @@ -1535,6 +1505,21 @@ SourceRegionHandle FlatSourceDomain::get_subdivided_source_region_handle( } } + // Compute the combined source term + update_single_neutron_source(handle); + + // Unlock the parallel map. Note: we may be tempted to release + // this lock earlier, and then just use the source region's lock to protect + // the flux/source initialization stages above. However, the rest of the code + // only protects updates to the new flux and volume fields, and assumes that + // the source is constant for the duration of transport. Thus, using just the + // source region's lock by itself would result in other threads potentially + // reading from the source before it is computed, as they won't use the lock + // when only reading from the SR's source. It would be expensive to protect + // those operations, whereas generating the SR is only done once, so we just + // hold the map's bucket lock until the source region is fully initialized. + discovered_source_regions_.unlock(sr_key); + return handle; } @@ -1620,4 +1605,52 @@ void FlatSourceDomain::apply_transport_stabilization() } } +// Determines the base source region index (i.e., a material filled cell +// instance) that corresponds to a particular location in the geometry. Requires +// that the "gs" object passed in has already been initialized and has called +// find_cell etc. +int64_t FlatSourceDomain::lookup_base_source_region_idx( + const GeometryState& gs) const +{ + int i_cell = gs.lowest_coord().cell(); + int64_t sr = source_region_offsets_[i_cell] + gs.cell_instance(); + return sr; +} + +// Determines the index of the mesh (if any) that has been applied +// to a particular base source region index. +int FlatSourceDomain::lookup_mesh_idx(int64_t sr) const +{ + int mesh_idx = C_NONE; + auto mesh_it = mesh_map_.find(sr); + if (mesh_it != mesh_map_.end()) { + mesh_idx = mesh_it->second; + } + return mesh_idx; +} + +// Determines the source region key that corresponds to a particular location in +// the geometry. This takes into account both the base source region index as +// well as the mesh bin if a mesh is applied to this source region for +// subdivision. +SourceRegionKey FlatSourceDomain::lookup_source_region_key( + const GeometryState& gs) const +{ + int64_t sr = lookup_base_source_region_idx(gs); + int64_t mesh_bin = lookup_mesh_bin(sr, gs.r()); + return SourceRegionKey {sr, mesh_bin}; +} + +// Determines the mesh bin that corresponds to a particular base source region +// index and position. +int64_t FlatSourceDomain::lookup_mesh_bin(int64_t sr, Position r) const +{ + int mesh_idx = lookup_mesh_idx(sr); + int mesh_bin = 0; + if (mesh_idx != C_NONE) { + mesh_bin = model::meshes[mesh_idx]->get_bin(r); + } + return mesh_bin; +} + } // namespace openmc diff --git a/src/random_ray/linear_source_domain.cpp b/src/random_ray/linear_source_domain.cpp index 81412164eca..e1ad68e3d87 100644 --- a/src/random_ray/linear_source_domain.cpp +++ b/src/random_ray/linear_source_domain.cpp @@ -34,25 +34,18 @@ void LinearSourceDomain::batch_reset() } } -void LinearSourceDomain::update_neutron_source(double k_eff) +void LinearSourceDomain::update_single_neutron_source(SourceRegionHandle& srh) { - simulation::time_update_src.start(); - - double inverse_k_eff = 1.0 / k_eff; - -// Reset all source regions to zero (important for void regions) -#pragma omp parallel for - for (int64_t se = 0; se < n_source_elements(); se++) { - source_regions_.source(se) = 0.0; + // Reset all source regions to zero (important for void regions) + for (int g = 0; g < negroups_; g++) { + srh.source(g) = 0.0; } -#pragma omp parallel for - for (int64_t sr = 0; sr < n_source_regions(); sr++) { - int material = source_regions_.material(sr); - if (material == MATERIAL_VOID) { - continue; - } - MomentMatrix invM = source_regions_.mom_matrix(sr).inverse(); + // Add scattering + fission source + int material = srh.material(); + if (material != MATERIAL_VOID) { + double inverse_k_eff = 1.0 / k_eff_; + MomentMatrix invM = srh.mom_matrix().inverse(); for (int g_out = 0; g_out < negroups_; g_out++) { double sigma_t = sigma_t_[material * negroups_ + g_out]; @@ -64,8 +57,8 @@ void LinearSourceDomain::update_neutron_source(double k_eff) for (int g_in = 0; g_in < negroups_; g_in++) { // Handles for the flat and linear components of the flux - double flux_flat = source_regions_.scalar_flux_old(sr, g_in); - MomentArray flux_linear = source_regions_.flux_moments_old(sr, g_in); + double flux_flat = srh.scalar_flux_old(g_in); + MomentArray flux_linear = srh.flux_moments_old(g_in); // Handles for cross sections double sigma_s = @@ -81,7 +74,7 @@ void LinearSourceDomain::update_neutron_source(double k_eff) } // Compute the flat source term - source_regions_.source(sr, g_out) = + srh.source(g_out) = (scatter_flat + fission_flat * inverse_k_eff) / sigma_t; // Compute the linear source terms. In the first 10 iterations when the @@ -91,25 +84,21 @@ void LinearSourceDomain::update_neutron_source(double k_eff) // very small/noisy or have poorly developed spatial moments, so we zero // the source gradients (effectively making this a flat source region // temporarily), so as to improve stability. - if (simulation::current_batch > 10 && - source_regions_.source(sr, g_out) >= 0.0) { - source_regions_.source_gradients(sr, g_out) = + if (simulation::current_batch > 10 && srh.source(g_out) >= 0.0) { + srh.source_gradients(g_out) = invM * ((scatter_linear + fission_linear * inverse_k_eff) / sigma_t); } else { - source_regions_.source_gradients(sr, g_out) = {0.0, 0.0, 0.0}; + srh.source_gradients(g_out) = {0.0, 0.0, 0.0}; } } } + // Add external source if in fixed source mode if (settings::run_mode == RunMode::FIXED_SOURCE) { -// Add external source to flat source term if in fixed source mode -#pragma omp parallel for - for (int64_t se = 0; se < n_source_elements(); se++) { - source_regions_.source(se) += source_regions_.external_source(se); + for (int g = 0; g < negroups_; g++) { + srh.source(g) += srh.external_source(g); } } - - simulation::time_update_src.stop(); } void LinearSourceDomain::normalize_scalar_flux_and_volumes( diff --git a/src/random_ray/random_ray.cpp b/src/random_ray/random_ray.cpp index 27f674c4278..89a91449df7 100644 --- a/src/random_ray/random_ray.cpp +++ b/src/random_ray/random_ray.cpp @@ -237,7 +237,6 @@ double RandomRay::distance_inactive_; double RandomRay::distance_active_; unique_ptr RandomRay::ray_source_; RandomRaySourceShape RandomRay::source_shape_ {RandomRaySourceShape::FLAT}; -bool RandomRay::mesh_subdivision_enabled_ {false}; RandomRaySampleMethod RandomRay::sample_method_ {RandomRaySampleMethod::PRNG}; RandomRay::RandomRay() @@ -336,71 +335,60 @@ void RandomRay::event_advance_ray() void RandomRay::attenuate_flux(double distance, bool is_active, double offset) { - // Determine source region index etc. - int i_cell = lowest_coord().cell(); - - // The base source region is the spatial region index - int64_t sr = domain_->source_region_offsets_[i_cell] + cell_instance(); + // Lookup base source region index + int64_t sr = domain_->lookup_base_source_region_idx(*this); // Perform ray tracing across mesh - if (mesh_subdivision_enabled_) { - // Determine the mesh index for the base source region, if any - int mesh_idx = domain_->base_source_regions_.mesh(sr); - - if (mesh_idx == C_NONE) { - // If there's no mesh being applied to this cell, then - // we just attenuate the flux as normal, and set - // the mesh bin to 0 - attenuate_flux_inner(distance, is_active, sr, 0, r()); - } else { - // If there is a mesh being applied to this cell, then - // we loop over all the bin crossings and attenuate - // separately. - Mesh* mesh = model::meshes[mesh_idx].get(); - - // We adjust the start and end positions of the ray slightly - // to accomodate for floating point precision issues that tend - // to occur at mesh boundaries that overlap with geometry lattice - // boundaries. - Position start = r() + (offset + TINY_BIT) * u(); - Position end = start + (distance - 2.0 * TINY_BIT) * u(); - double reduced_distance = (end - start).norm(); - - // Ray trace through the mesh and record bins and lengths - mesh_bins_.resize(0); - mesh_fractional_lengths_.resize(0); - mesh->bins_crossed(start, end, u(), mesh_bins_, mesh_fractional_lengths_); - - // Loop over all mesh bins and attenuate flux - for (int b = 0; b < mesh_bins_.size(); b++) { - double physical_length = reduced_distance * mesh_fractional_lengths_[b]; - attenuate_flux_inner( - physical_length, is_active, sr, mesh_bins_[b], start); - start += physical_length * u(); - } - } + // Determine the mesh index for the base source region, if any + int mesh_idx = domain_->lookup_mesh_idx(sr); + + if (mesh_idx == C_NONE) { + // If there's no mesh being applied to this cell, then + // we just attenuate the flux as normal, and set + // the mesh bin to 0 + attenuate_flux_inner(distance, is_active, sr, 0, r()); } else { - attenuate_flux_inner(distance, is_active, sr, C_NONE, r()); + // If there is a mesh being applied to this cell, then + // we loop over all the bin crossings and attenuate + // separately. + Mesh* mesh = model::meshes[mesh_idx].get(); + + // We adjust the start and end positions of the ray slightly + // to accomodate for floating point precision issues that tend + // to occur at mesh boundaries that overlap with geometry lattice + // boundaries. + Position start = r() + (offset + TINY_BIT) * u(); + Position end = start + (distance - 2.0 * TINY_BIT) * u(); + double reduced_distance = (end - start).norm(); + + // Ray trace through the mesh and record bins and lengths + mesh_bins_.resize(0); + mesh_fractional_lengths_.resize(0); + mesh->bins_crossed(start, end, u(), mesh_bins_, mesh_fractional_lengths_); + + // Loop over all mesh bins and attenuate flux + for (int b = 0; b < mesh_bins_.size(); b++) { + double physical_length = reduced_distance * mesh_fractional_lengths_[b]; + attenuate_flux_inner( + physical_length, is_active, sr, mesh_bins_[b], start); + start += physical_length * u(); + } } } void RandomRay::attenuate_flux_inner( double distance, bool is_active, int64_t sr, int mesh_bin, Position r) { + SourceRegionKey sr_key {sr, mesh_bin}; SourceRegionHandle srh; - if (mesh_subdivision_enabled_) { - srh = domain_->get_subdivided_source_region_handle( - sr, mesh_bin, r, distance, u()); - if (srh.is_numerical_fp_artifact_) { - return; - } - } else { - srh = domain_->source_regions_.get_source_region_handle(sr); + srh = domain_->get_subdivided_source_region_handle(sr_key, r, u()); + if (srh.is_numerical_fp_artifact_) { + return; } switch (source_shape_) { case RandomRaySourceShape::FLAT: - if (this->material() == MATERIAL_VOID) { + if (srh.material() == MATERIAL_VOID) { attenuate_flux_flat_source_void(srh, distance, is_active, r); } else { attenuate_flux_flat_source(srh, distance, is_active, r); @@ -408,7 +396,7 @@ void RandomRay::attenuate_flux_inner( break; case RandomRaySourceShape::LINEAR: case RandomRaySourceShape::LINEAR_XY: - if (this->material() == MATERIAL_VOID) { + if (srh.material() == MATERIAL_VOID) { attenuate_flux_linear_source_void(srh, distance, is_active, r); } else { attenuate_flux_linear_source(srh, distance, is_active, r); @@ -439,7 +427,7 @@ void RandomRay::attenuate_flux_flat_source( n_event()++; // Get material - int material = this->material(); + int material = srh.material(); // MOC incoming flux attenuation + source contribution/attenuation equation for (int g = 0; g < negroups_; g++) { @@ -490,7 +478,7 @@ void RandomRay::attenuate_flux_flat_source_void( // The number of geometric intersections is counted for reporting purposes n_event()++; - int material = this->material(); + int material = srh.material(); // If ray is in the active phase (not in dead zone), make contributions to // source region bookkeeping @@ -537,7 +525,7 @@ void RandomRay::attenuate_flux_linear_source( // The number of geometric intersections is counted for reporting purposes n_event()++; - int material = this->material(); + int material = srh.material(); Position& centroid = srh.centroid(); Position midpoint = r + u() * (distance / 2.0); @@ -810,27 +798,12 @@ void RandomRay::initialize_ray(uint64_t ray_id, FlatSourceDomain* domain) cell_born() = lowest_coord().cell(); } + SourceRegionKey sr_key = domain_->lookup_source_region_key(*this); + SourceRegionHandle srh = + domain_->get_subdivided_source_region_handle(sr_key, r(), u()); + // Initialize ray's starting angular flux to starting location's isotropic // source - int i_cell = lowest_coord().cell(); - int64_t sr = domain_->source_region_offsets_[i_cell] + cell_instance(); - - SourceRegionHandle srh; - if (mesh_subdivision_enabled_) { - int mesh_idx = domain_->base_source_regions_.mesh(sr); - int mesh_bin; - if (mesh_idx == C_NONE) { - mesh_bin = 0; - } else { - Mesh* mesh = model::meshes[mesh_idx].get(); - mesh_bin = mesh->get_bin(r()); - } - srh = - domain_->get_subdivided_source_region_handle(sr, mesh_bin, r(), 0.0, u()); - } else { - srh = domain_->source_regions_.get_source_region_handle(sr); - } - if (!srh.is_numerical_fp_artifact_) { for (int g = 0; g < negroups_; g++) { angular_flux_[g] = srh.source(g); diff --git a/src/random_ray/random_ray_simulation.cpp b/src/random_ray/random_ray_simulation.cpp index 388a778b840..d475b2593ea 100644 --- a/src/random_ray/random_ray_simulation.cpp +++ b/src/random_ray/random_ray_simulation.cpp @@ -47,97 +47,82 @@ void openmc_run_random_ray() if (mpi::master) validate_random_ray_inputs(); - // Declare forward flux so that it can be saved for later adjoint simulation - vector forward_flux; - SourceRegionContainer forward_source_regions; - SourceRegionContainer forward_base_source_regions; - std::unordered_map - forward_source_region_map; + // Initialize Random Ray Simulation Object + RandomRaySimulation sim; - { - // Initialize Random Ray Simulation Object - RandomRaySimulation sim; + // Initialize fixed sources, if present + sim.apply_fixed_sources_and_mesh_domains(); - // Initialize fixed sources, if present - sim.apply_fixed_sources_and_mesh_domains(); + // Begin main simulation timer + simulation::time_total.start(); - // Begin main simulation timer - simulation::time_total.start(); + // Execute random ray simulation + sim.simulate(); - // Execute random ray simulation - sim.simulate(); + // End main simulation timer + simulation::time_total.stop(); - // End main simulation timer - simulation::time_total.stop(); - - // Normalize and save the final forward flux - sim.domain()->serialize_final_fluxes(forward_flux); - - double source_normalization_factor = - sim.domain()->compute_fixed_source_normalization_factor() / - (settings::n_batches - settings::n_inactive); + // Normalize and save the final forward flux + double source_normalization_factor = + sim.domain()->compute_fixed_source_normalization_factor() / + (settings::n_batches - settings::n_inactive); #pragma omp parallel for - for (uint64_t i = 0; i < forward_flux.size(); i++) { - forward_flux[i] *= source_normalization_factor; - } + for (uint64_t se = 0; se < sim.domain()->n_source_elements(); se++) { + sim.domain()->source_regions_.scalar_flux_final(se) *= + source_normalization_factor; + } - forward_source_regions = sim.domain()->source_regions_; - forward_source_region_map = sim.domain()->source_region_map_; - forward_base_source_regions = sim.domain()->base_source_regions_; + // Finalize OpenMC + openmc_simulation_finalize(); - // Finalize OpenMC - openmc_simulation_finalize(); - - // Output all simulation results - sim.output_simulation_results(); - } + // Output all simulation results + sim.output_simulation_results(); ////////////////////////////////////////////////////////// // Run adjoint simulation (if enabled) ////////////////////////////////////////////////////////// - if (adjoint_needed) { - reset_timers(); + if (!adjoint_needed) { + return; + } - // Configure the domain for adjoint simulation - FlatSourceDomain::adjoint_ = true; + reset_timers(); - if (mpi::master) - header("ADJOINT FLUX SOLVE", 3); + // Configure the domain for adjoint simulation + FlatSourceDomain::adjoint_ = true; - // Initialize OpenMC general data structures - openmc_simulation_init(); + if (mpi::master) + header("ADJOINT FLUX SOLVE", 3); - // Initialize Random Ray Simulation Object - RandomRaySimulation adjoint_sim; + // Initialize OpenMC general data structures + openmc_simulation_init(); - // Initialize adjoint fixed sources, if present - adjoint_sim.prepare_fixed_sources_adjoint(forward_flux, - forward_source_regions, forward_base_source_regions, - forward_source_region_map); + sim.domain()->k_eff_ = 1.0; - // Transpose scattering matrix - adjoint_sim.domain()->transpose_scattering_matrix(); + // Initialize adjoint fixed sources, if present + sim.prepare_fixed_sources_adjoint(); - // Swap nu_sigma_f and chi - adjoint_sim.domain()->nu_sigma_f_.swap(adjoint_sim.domain()->chi_); + // Transpose scattering matrix + sim.domain()->transpose_scattering_matrix(); - // Begin main simulation timer - simulation::time_total.start(); + // Swap nu_sigma_f and chi + sim.domain()->nu_sigma_f_.swap(sim.domain()->chi_); - // Execute random ray simulation - adjoint_sim.simulate(); + // Begin main simulation timer + simulation::time_total.start(); - // End main simulation timer - simulation::time_total.stop(); + // Execute random ray simulation + sim.simulate(); - // Finalize OpenMC - openmc_simulation_finalize(); + // End main simulation timer + simulation::time_total.stop(); - // Output all simulation results - adjoint_sim.output_simulation_results(); - } + // Finalize OpenMC + openmc_simulation_finalize(); + + // Output all simulation results + sim.output_simulation_results(); } // Enforces restrictions on inputs in random ray mode. While there are @@ -348,7 +333,6 @@ void validate_random_ray_inputs() // when generating weight windows with FW-CADIS and an overlaid mesh. /////////////////////////////////////////////////////////////////// if (RandomRay::source_shape_ == RandomRaySourceShape::LINEAR && - RandomRay::mesh_subdivision_enabled_ && variance_reduction::weight_windows.size() > 0) { warning( "Linear sources may result in negative fluxes in small source regions " @@ -366,7 +350,6 @@ void openmc_reset_random_ray() FlatSourceDomain::mesh_domain_map_.clear(); RandomRay::ray_source_.reset(); RandomRay::source_shape_ = RandomRaySourceShape::FLAT; - RandomRay::mesh_subdivision_enabled_ = false; RandomRay::sample_method_ = RandomRaySampleMethod::PRNG; } @@ -412,20 +395,11 @@ void RandomRaySimulation::apply_fixed_sources_and_mesh_domains() } } -void RandomRaySimulation::prepare_fixed_sources_adjoint( - vector& forward_flux, SourceRegionContainer& forward_source_regions, - SourceRegionContainer& forward_base_source_regions, - std::unordered_map& - forward_source_region_map) +void RandomRaySimulation::prepare_fixed_sources_adjoint() { + domain_->source_regions_.adjoint_reset(); if (settings::run_mode == RunMode::FIXED_SOURCE) { - if (RandomRay::mesh_subdivision_enabled_) { - domain_->source_regions_ = forward_source_regions; - domain_->source_region_map_ = forward_source_region_map; - domain_->base_source_regions_ = forward_base_source_regions; - domain_->source_regions_.adjoint_reset(); - } - domain_->set_adjoint_sources(forward_flux); + domain_->set_adjoint_sources(); } } @@ -445,22 +419,18 @@ void RandomRaySimulation::simulate() simulation::total_weight = 1.0; // Update source term (scattering + fission) - domain_->update_neutron_source(k_eff_); + domain_->update_all_neutron_sources(); - // Reset scalar fluxes, iteration volume tallies, and region hit flags to - // zero + // Reset scalar fluxes, iteration volume tallies, and region hit flags + // to zero domain_->batch_reset(); - // At the beginning of the simulation, if mesh subvivision is in use, we + // At the beginning of the simulation, if mesh subdivision is in use, we // need to swap the main source region container into the base container, // as the main source region container will be used to hold the true // subdivided source regions. The base container will therefore only // contain the external source region information, the mesh indices, // material properties, and initial guess values for the flux/source. - if (RandomRay::mesh_subdivision_enabled_ && - simulation::current_batch == 1 && !FlatSourceDomain::adjoint_) { - domain_->prepare_base_source_regions(); - } // Start timer for transport simulation::time_transport.start(); @@ -476,11 +446,9 @@ void RandomRaySimulation::simulate() simulation::time_transport.stop(); - // If using mesh subdivision, add any newly discovered source regions - // to the main source region container. - if (RandomRay::mesh_subdivision_enabled_) { - domain_->finalize_discovered_source_regions(); - } + // Add any newly discovered source regions to the main source region + // container. + domain_->finalize_discovered_source_regions(); // Normalize scalar flux and update volumes domain_->normalize_scalar_flux_and_volumes( @@ -494,10 +462,10 @@ void RandomRaySimulation::simulate() if (settings::run_mode == RunMode::EIGENVALUE) { // Compute random ray k-eff - k_eff_ = domain_->compute_k_eff(k_eff_); + domain_->compute_k_eff(); // Store random ray k-eff into OpenMC's native k-eff variable - global_tally_tracklength = k_eff_; + global_tally_tracklength = domain_->k_eff_; } // Execute all tallying tasks, if this is an active batch @@ -507,12 +475,6 @@ void RandomRaySimulation::simulate() // estimate domain_->accumulate_iteration_flux(); - // Generate mapping between source regions and tallies - if (!domain_->mapped_all_tallies_ && - !RandomRay::mesh_subdivision_enabled_) { - domain_->convert_source_regions_to_tallies(0); - } - // Use above mapping to contribute FSR flux data to appropriate // tallies domain_->random_ray_tally(); @@ -522,7 +484,7 @@ void RandomRaySimulation::simulate() domain_->flux_swap(); // Check for any obvious insabilities/nans/infs - instability_check(n_hits, k_eff_, avg_miss_rate_); + instability_check(n_hits, domain_->k_eff_, avg_miss_rate_); } // End MPI master work // Finalize the current batch @@ -571,7 +533,7 @@ void RandomRaySimulation::instability_check( } if (k_eff > 10.0 || k_eff < 0.01 || !(std::isfinite(k_eff))) { - fatal_error("Instability detected"); + fatal_error(fmt::format("Instability detected: k-eff = {:.5f}", k_eff)); } } } diff --git a/src/random_ray/source_region.cpp b/src/random_ray/source_region.cpp index 1205b995a6f..3b06f0ed09f 100644 --- a/src/random_ray/source_region.cpp +++ b/src/random_ray/source_region.cpp @@ -48,7 +48,7 @@ SourceRegion::SourceRegion(int negroups, bool is_linear) } scalar_flux_new_.assign(negroups, 0.0); - source_.resize(negroups); + source_.assign(negroups, 0.0); scalar_flux_final_.assign(negroups, 0.0); tally_task_.resize(negroups); @@ -60,25 +60,6 @@ SourceRegion::SourceRegion(int negroups, bool is_linear) } } -SourceRegion::SourceRegion(const SourceRegionHandle& handle, int64_t parent_sr) - : SourceRegion(handle.negroups_, handle.is_linear_) -{ - material_ = handle.material(); - mesh_ = handle.mesh(); - parent_sr_ = parent_sr; - for (int g = 0; g < scalar_flux_new_.size(); g++) { - scalar_flux_old_[g] = handle.scalar_flux_old(g); - source_[g] = handle.source(g); - } - - if (settings::run_mode == RunMode::FIXED_SOURCE) { - external_source_present_ = handle.external_source_present(); - for (int g = 0; g < scalar_flux_new_.size(); g++) { - external_source_[g] = handle.external_source(g); - } - } -} - //============================================================================== // SourceRegionContainer implementation //============================================================================== @@ -259,9 +240,12 @@ void SourceRegionContainer::adjoint_reset() MomentMatrix {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}); std::fill(mom_matrix_t_.begin(), mom_matrix_t_.end(), MomentMatrix {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}); - std::fill(scalar_flux_old_.begin(), scalar_flux_old_.end(), 0.0); + if (settings::run_mode == RunMode::FIXED_SOURCE) { + std::fill(scalar_flux_old_.begin(), scalar_flux_old_.end(), 0.0); + } else { + std::fill(scalar_flux_old_.begin(), scalar_flux_old_.end(), 1.0); + } std::fill(scalar_flux_new_.begin(), scalar_flux_new_.end(), 0.0); - std::fill(scalar_flux_final_.begin(), scalar_flux_final_.end(), 0.0); std::fill(source_.begin(), source_.end(), 0.0f); std::fill(external_source_.begin(), external_source_.end(), 0.0f); std::fill(source_gradients_.begin(), source_gradients_.end(), diff --git a/src/settings.cpp b/src/settings.cpp index 03d42bb9da1..f9a2469db93 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -346,7 +346,6 @@ void get_run_parameters(pugi::xml_node node_base) } FlatSourceDomain::mesh_domain_map_[mesh_id].emplace_back( type, domain_id); - RandomRay::mesh_subdivision_enabled_ = true; } } } diff --git a/tests/regression_tests/random_ray_low_density/__init__.py b/tests/regression_tests/random_ray_low_density/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/random_ray_low_density/inputs_true.dat b/tests/regression_tests/random_ray_low_density/inputs_true.dat new file mode 100644 index 00000000000..c4fd06f421e --- /dev/null +++ b/tests/regression_tests/random_ray_low_density/inputs_true.dat @@ -0,0 +1,244 @@ + + + + mgxs.h5 + + + + + + + + + + + + + + + + + + + + + 2.5 2.5 2.5 + 12 12 12 + 0.0 0.0 0.0 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + + + + + + + + + + fixed source + 90 + 10 + 5 + + + 100.0 1.0 + + + universe + 1 + + + multi-group + + 500.0 + 100.0 + + + 0.0 0.0 0.0 30.0 30.0 30.0 + + + True + + + + + 1 + + + 2 + + + 3 + + + 3 + flux + tracklength + + + 2 + flux + tracklength + + + 1 + flux + tracklength + + + diff --git a/tests/regression_tests/random_ray_low_density/results_true.dat b/tests/regression_tests/random_ray_low_density/results_true.dat new file mode 100644 index 00000000000..a4b3ee1bcde --- /dev/null +++ b/tests/regression_tests/random_ray_low_density/results_true.dat @@ -0,0 +1,9 @@ +tally 1: +5.973607E-01 +7.155477E-02 +tally 2: +3.206216E-02 +2.063375E-04 +tally 3: +2.096415E-03 +8.804963E-07 diff --git a/tests/regression_tests/random_ray_low_density/test.py b/tests/regression_tests/random_ray_low_density/test.py new file mode 100644 index 00000000000..1b4ffb78183 --- /dev/null +++ b/tests/regression_tests/random_ray_low_density/test.py @@ -0,0 +1,60 @@ +import os + +import numpy as np +import openmc +from openmc.examples import random_ray_three_region_cube + +from tests.testing_harness import TolerantPyAPITestHarness + + +class MGXSTestHarness(TolerantPyAPITestHarness): + def _cleanup(self): + super()._cleanup() + f = 'mgxs.h5' + if os.path.exists(f): + os.remove(f) + + +def test_random_ray_low_density(): + model = random_ray_three_region_cube() + + # Rebuild the MGXS library to have a material with very + # low macroscopic cross sections + ebins = [1e-5, 20.0e6] + groups = openmc.mgxs.EnergyGroups(group_edges=ebins) + + void_sigma_a = 4.0e-6 + void_sigma_s = 3.0e-4 + void_mat_data = openmc.XSdata('void', groups) + void_mat_data.order = 0 + void_mat_data.set_total([void_sigma_a + void_sigma_s]) + void_mat_data.set_absorption([void_sigma_a]) + void_mat_data.set_scatter_matrix( + np.rollaxis(np.array([[[void_sigma_s]]]), 0, 3)) + + absorber_sigma_a = 0.75 + absorber_sigma_s = 0.25 + absorber_mat_data = openmc.XSdata('absorber', groups) + absorber_mat_data.order = 0 + absorber_mat_data.set_total([absorber_sigma_a + absorber_sigma_s]) + absorber_mat_data.set_absorption([absorber_sigma_a]) + absorber_mat_data.set_scatter_matrix( + np.rollaxis(np.array([[[absorber_sigma_s]]]), 0, 3)) + + multiplier = 0.0000001 + source_sigma_a = void_sigma_a * multiplier + source_sigma_s = void_sigma_s * multiplier + source_mat_data = openmc.XSdata('source', groups) + source_mat_data.order = 0 + source_mat_data.set_total([source_sigma_a + source_sigma_s]) + source_mat_data.set_absorption([source_sigma_a]) + source_mat_data.set_scatter_matrix( + np.rollaxis(np.array([[[source_sigma_s]]]), 0, 3)) + + mg_cross_sections_file = openmc.MGXSLibrary(groups) + mg_cross_sections_file.add_xsdatas( + [source_mat_data, void_mat_data, absorber_mat_data]) + mg_cross_sections_file.export_to_hdf5() + + harness = MGXSTestHarness('statepoint.10.h5', model) + harness.main() diff --git a/tests/regression_tests/random_ray_point_source_locator/results_true.dat b/tests/regression_tests/random_ray_point_source_locator/results_true.dat index 1785dda574a..8c6f358dd31 100644 --- a/tests/regression_tests/random_ray_point_source_locator/results_true.dat +++ b/tests/regression_tests/random_ray_point_source_locator/results_true.dat @@ -1,9 +1,9 @@ tally 1: -2.633900E+00 -2.948207E+00 +2.633923E+00 +2.948228E+00 tally 2: -1.440463E-01 -3.294032E-03 +1.440456E-01 +3.293984E-03 tally 3: 9.425207E-03 1.089748E-05 From 1dacf4fd2bba3582b575b6abf92b5d201eabab37 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 2 Oct 2025 11:48:02 -0500 Subject: [PATCH 121/159] Add missing documentation on in depletion chain file format (#3590) Co-authored-by: Paul Romano Co-authored-by: April Novak --- docs/source/io_formats/depletion_chain.rst | 21 +++++++++++++++++++++ openmc/data/decay.py | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/source/io_formats/depletion_chain.rst b/docs/source/io_formats/depletion_chain.rst index 89c76525f89..74413e7b610 100644 --- a/docs/source/io_formats/depletion_chain.rst +++ b/docs/source/io_formats/depletion_chain.rst @@ -56,6 +56,27 @@ attributes: .. _io_chain_reaction: +-------------------- +```` Element +-------------------- + +The ```` element represents photon and electron sources associated with +the decay of a nuclide and contains information to construct an +:class:`openmc.stats.Univariate` object that represents this emission as an +energy distribution. This element has the following attributes: + + :type: + The type of :class:`openmc.stats.Univariate` source term. + + :particle: + The type of particle emitted, e.g., 'photon' or 'electron' + + :parameters: + The parameters of the source term, e.g., for a + :class:`openmc.stats.Discrete` source, the energies (in [eV]) at which the + particles are emitted and their relative intensities in [Bq/atom] (in other + words, decay constants). + ---------------------- ```` Element ---------------------- diff --git a/openmc/data/decay.py b/openmc/data/decay.py index 1a11d3614fe..c8a0bb5e7e9 100644 --- a/openmc/data/decay.py +++ b/openmc/data/decay.py @@ -591,7 +591,7 @@ def decay_photon_energy(nuclide: str) -> Univariate | None: openmc.stats.Univariate or None Distribution of energies in [eV] of photons emitted from decay, or None if no photon source exists. Note that the probabilities represent - intensities, given as [Bq]. + intensities, given as [Bq/atom] (in other words, decay constants). """ if not _DECAY_PHOTON_ENERGY: chain_file = openmc.config.get('chain_file') From 91a19cf2201731017e02e445f2fc5995cfa51c46 Mon Sep 17 00:00:00 2001 From: Perry <100789850+yrrepy@users.noreply.github.com> Date: Thu, 2 Oct 2025 14:50:42 -0700 Subject: [PATCH 122/159] Ensure weight_windows_file information is read from XML (#3587) Co-authored-by: Paul Romano --- openmc/settings.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openmc/settings.py b/openmc/settings.py index 7bbc31c192a..dc6fa99f9cb 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -1641,6 +1641,7 @@ def _create_weight_windows_subelement(self, root, mesh_memo=None): if mesh_memo is not None: mesh_memo.add(ww.mesh.id) + def _create_weight_windows_on_subelement(self, root): if self._weight_windows_on is not None: elem = ET.SubElement(root, "weight_windows_on") elem.text = str(self._weight_windows_on).lower() @@ -2074,10 +2075,16 @@ def _weight_windows_from_xml_element(self, root, meshes=None): ww = WeightWindows.from_xml_element(elem, meshes) self.weight_windows.append(ww) + def _weight_windows_on_from_xml_element(self, root): text = get_text(root, 'weight_windows_on') if text is not None: self.weight_windows_on = text in ('true', '1') + def _weight_windows_file_from_xml_element(self, root): + text = get_text(root, 'weight_windows_file') + if text is not None: + self.weight_windows_file = text + def _weight_window_checkpoints_from_xml_element(self, root): elem = root.find('weight_window_checkpoints') if elem is None: @@ -2214,6 +2221,7 @@ def to_xml_element(self, mesh_memo=None): self._create_log_grid_bins_subelement(element) self._create_write_initial_source_subelement(element) self._create_weight_windows_subelement(element, mesh_memo) + self._create_weight_windows_on_subelement(element) self._create_weight_window_generators_subelement(element, mesh_memo) self._create_weight_windows_file_element(element) self._create_weight_window_checkpoints_subelement(element) @@ -2324,6 +2332,8 @@ def from_xml_element(cls, elem, meshes=None): settings._log_grid_bins_from_xml_element(elem) settings._write_initial_source_from_xml_element(elem) settings._weight_windows_from_xml_element(elem, meshes) + settings._weight_windows_on_from_xml_element(elem) + settings._weight_windows_file_from_xml_element(elem) settings._weight_window_generators_from_xml_element(elem, meshes) settings._weight_window_checkpoints_from_xml_element(elem) settings._max_history_splits_from_xml_element(elem) From 7806703c2614853be2d2ce49860e673e208145da Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Oct 2025 22:52:39 +0000 Subject: [PATCH 123/159] Fix random ray source region mesh export when using model.export_to_xml() (#3579) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jtramm <1009059+jtramm@users.noreply.github.com> Co-authored-by: Paul Romano --- openmc/settings.py | 20 +++++++++++---- .../inputs_true.dat | 4 +-- .../random_ray_adjoint_k_eff/inputs_true.dat | 4 +-- .../cell/inputs_true.dat | 2 +- .../material/inputs_true.dat | 2 +- .../universe/inputs_true.dat | 2 +- .../linear/inputs_true.dat | 2 +- .../linear_xy/inputs_true.dat | 2 +- .../flat/inputs_true.dat | 2 +- .../linear/inputs_true.dat | 2 +- .../False/inputs_true.dat | 2 +- .../True/inputs_true.dat | 2 +- .../flat/inputs_true.dat | 2 +- .../linear_xy/inputs_true.dat | 2 +- .../random_ray_halton_samples/inputs_true.dat | 2 +- .../random_ray_k_eff/inputs_true.dat | 2 +- .../random_ray_k_eff_mesh/inputs_true.dat | 2 +- .../random_ray_linear/linear/inputs_true.dat | 2 +- .../linear_xy/inputs_true.dat | 2 +- .../random_ray_low_density/inputs_true.dat | 2 +- .../inputs_true.dat | 2 +- .../random_ray_void/flat/inputs_true.dat | 2 +- .../random_ray_void/linear/inputs_true.dat | 2 +- .../hybrid/inputs_true.dat | 2 +- .../naive/inputs_true.dat | 2 +- .../simulation_averaged/inputs_true.dat | 2 +- .../hybrid/inputs_true.dat | 2 +- .../naive/inputs_true.dat | 2 +- .../simulation_averaged/inputs_true.dat | 2 +- .../weightwindows_fw_cadis/inputs_true.dat | 2 +- .../flat/inputs_true.dat | 2 +- .../linear/inputs_true.dat | 2 +- tests/unit_tests/test_settings.py | 25 ++++++++++++++++++- 33 files changed, 72 insertions(+), 39 deletions(-) diff --git a/openmc/settings.py b/openmc/settings.py index dc6fa99f9cb..8f8aedc93e8 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -1715,9 +1715,15 @@ def _create_random_ray_subelement(self, root, mesh_memo=None): domain_elem = ET.SubElement(mesh_elem, 'domain') domain_elem.set('id', str(domain.id)) domain_elem.set('type', domain.__class__.__name__.lower()) - if mesh_memo is not None and mesh.id not in mesh_memo: + # See if a element already exists -- if not, add it + path = f"./mesh[@id='{mesh.id}']" + if root.find(path) is None: root.append(mesh.to_xml_element()) - mesh_memo.add(mesh.id) + if mesh_memo is not None: + mesh_memo.add(mesh.id) + elif isinstance(value, bool): + subelement = ET.SubElement(element, key) + subelement.text = str(value).lower() else: subelement = ET.SubElement(element, key) subelement.text = str(value) @@ -2110,7 +2116,7 @@ def _max_tracks_from_xml_element(self, root): if text is not None: self.max_tracks = int(text) - def _random_ray_from_xml_element(self, root): + def _random_ray_from_xml_element(self, root, meshes=None): elem = root.find('random_ray') if elem is not None: self.random_ray = {} @@ -2137,7 +2143,11 @@ def _random_ray_from_xml_element(self, root): elif child.tag == 'source_region_meshes': self.random_ray['source_region_meshes'] = [] for mesh_elem in child.findall('mesh'): - mesh = MeshBase.from_xml_element(mesh_elem) + mesh_id = int(get_text(mesh_elem, 'id')) + if meshes and mesh_id in meshes: + mesh = meshes[mesh_id] + else: + mesh = MeshBase.from_xml_element(mesh_elem) domains = [] for domain_elem in mesh_elem.findall('domain'): domain_id = int(get_text(domain_elem, "id")) @@ -2339,7 +2349,7 @@ def from_xml_element(cls, elem, meshes=None): settings._max_history_splits_from_xml_element(elem) settings._max_tracks_from_xml_element(elem) settings._max_secondaries_from_xml_element(elem) - settings._random_ray_from_xml_element(elem) + settings._random_ray_from_xml_element(elem, meshes) settings._use_decay_photons_from_xml_element(elem) settings._source_rejection_fraction_from_xml_element(elem) diff --git a/tests/regression_tests/random_ray_adjoint_fixed_source/inputs_true.dat b/tests/regression_tests/random_ray_adjoint_fixed_source/inputs_true.dat index 30e62a8853e..0adfc548848 100644 --- a/tests/regression_tests/random_ray_adjoint_fixed_source/inputs_true.dat +++ b/tests/regression_tests/random_ray_adjoint_fixed_source/inputs_true.dat @@ -212,8 +212,8 @@ 0.0 0.0 0.0 30.0 30.0 30.0 - True - True + true + true naive diff --git a/tests/regression_tests/random_ray_adjoint_k_eff/inputs_true.dat b/tests/regression_tests/random_ray_adjoint_k_eff/inputs_true.dat index cd4e92aa1ba..073348c41e7 100644 --- a/tests/regression_tests/random_ray_adjoint_k_eff/inputs_true.dat +++ b/tests/regression_tests/random_ray_adjoint_k_eff/inputs_true.dat @@ -85,8 +85,8 @@ -1.26 -1.26 -1 1.26 1.26 1 - True - True + true + true diff --git a/tests/regression_tests/random_ray_fixed_source_domain/cell/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_domain/cell/inputs_true.dat index 4b8af76aaf6..9f1987f3acc 100644 --- a/tests/regression_tests/random_ray_fixed_source_domain/cell/inputs_true.dat +++ b/tests/regression_tests/random_ray_fixed_source_domain/cell/inputs_true.dat @@ -212,7 +212,7 @@ 0.0 0.0 0.0 30.0 30.0 30.0 - True + true diff --git a/tests/regression_tests/random_ray_fixed_source_domain/material/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_domain/material/inputs_true.dat index 82fe48b614c..b4f57dbfa8a 100644 --- a/tests/regression_tests/random_ray_fixed_source_domain/material/inputs_true.dat +++ b/tests/regression_tests/random_ray_fixed_source_domain/material/inputs_true.dat @@ -212,7 +212,7 @@ 0.0 0.0 0.0 30.0 30.0 30.0 - True + true diff --git a/tests/regression_tests/random_ray_fixed_source_domain/universe/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_domain/universe/inputs_true.dat index c4fd06f421e..ab91f74e50d 100644 --- a/tests/regression_tests/random_ray_fixed_source_domain/universe/inputs_true.dat +++ b/tests/regression_tests/random_ray_fixed_source_domain/universe/inputs_true.dat @@ -212,7 +212,7 @@ 0.0 0.0 0.0 30.0 30.0 30.0 - True + true diff --git a/tests/regression_tests/random_ray_fixed_source_linear/linear/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_linear/linear/inputs_true.dat index 4085b0c7a1f..220fa7db643 100644 --- a/tests/regression_tests/random_ray_fixed_source_linear/linear/inputs_true.dat +++ b/tests/regression_tests/random_ray_fixed_source_linear/linear/inputs_true.dat @@ -212,7 +212,7 @@ 0.0 0.0 0.0 30.0 30.0 30.0 - True + true linear diff --git a/tests/regression_tests/random_ray_fixed_source_linear/linear_xy/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_linear/linear_xy/inputs_true.dat index ff085d2fbb6..f8c4430852f 100644 --- a/tests/regression_tests/random_ray_fixed_source_linear/linear_xy/inputs_true.dat +++ b/tests/regression_tests/random_ray_fixed_source_linear/linear_xy/inputs_true.dat @@ -212,7 +212,7 @@ 0.0 0.0 0.0 30.0 30.0 30.0 - True + true linear_xy diff --git a/tests/regression_tests/random_ray_fixed_source_mesh/flat/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_mesh/flat/inputs_true.dat index 12c4d74edbf..c84e544fcc4 100644 --- a/tests/regression_tests/random_ray_fixed_source_mesh/flat/inputs_true.dat +++ b/tests/regression_tests/random_ray_fixed_source_mesh/flat/inputs_true.dat @@ -212,7 +212,7 @@ 0.0 0.0 0.0 30.0 30.0 30.0 - True + true diff --git a/tests/regression_tests/random_ray_fixed_source_mesh/linear/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_mesh/linear/inputs_true.dat index 07adb1ff551..05c4846e6b4 100644 --- a/tests/regression_tests/random_ray_fixed_source_mesh/linear/inputs_true.dat +++ b/tests/regression_tests/random_ray_fixed_source_mesh/linear/inputs_true.dat @@ -212,7 +212,7 @@ 0.0 0.0 0.0 30.0 30.0 30.0 - True + true diff --git a/tests/regression_tests/random_ray_fixed_source_normalization/False/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_normalization/False/inputs_true.dat index ab077ae8aaf..0c870e10067 100644 --- a/tests/regression_tests/random_ray_fixed_source_normalization/False/inputs_true.dat +++ b/tests/regression_tests/random_ray_fixed_source_normalization/False/inputs_true.dat @@ -212,7 +212,7 @@ 0.0 0.0 0.0 30.0 30.0 30.0 - False + false diff --git a/tests/regression_tests/random_ray_fixed_source_normalization/True/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_normalization/True/inputs_true.dat index c4fd06f421e..ab91f74e50d 100644 --- a/tests/regression_tests/random_ray_fixed_source_normalization/True/inputs_true.dat +++ b/tests/regression_tests/random_ray_fixed_source_normalization/True/inputs_true.dat @@ -212,7 +212,7 @@ 0.0 0.0 0.0 30.0 30.0 30.0 - True + true diff --git a/tests/regression_tests/random_ray_fixed_source_subcritical/flat/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_subcritical/flat/inputs_true.dat index a124b5e3c45..0c05a71df32 100644 --- a/tests/regression_tests/random_ray_fixed_source_subcritical/flat/inputs_true.dat +++ b/tests/regression_tests/random_ray_fixed_source_subcritical/flat/inputs_true.dat @@ -115,7 +115,7 @@ -1.26 -1.26 -1 1.26 1.26 1 - False + false flat diff --git a/tests/regression_tests/random_ray_fixed_source_subcritical/linear_xy/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_subcritical/linear_xy/inputs_true.dat index cc557afb6e4..a67495bf169 100644 --- a/tests/regression_tests/random_ray_fixed_source_subcritical/linear_xy/inputs_true.dat +++ b/tests/regression_tests/random_ray_fixed_source_subcritical/linear_xy/inputs_true.dat @@ -115,7 +115,7 @@ -1.26 -1.26 -1 1.26 1.26 1 - False + false linear_xy diff --git a/tests/regression_tests/random_ray_halton_samples/inputs_true.dat b/tests/regression_tests/random_ray_halton_samples/inputs_true.dat index 3f058e45c21..36d5f6f2276 100644 --- a/tests/regression_tests/random_ray_halton_samples/inputs_true.dat +++ b/tests/regression_tests/random_ray_halton_samples/inputs_true.dat @@ -85,7 +85,7 @@ -1.26 -1.26 -1 1.26 1.26 1 - True + true halton diff --git a/tests/regression_tests/random_ray_k_eff/inputs_true.dat b/tests/regression_tests/random_ray_k_eff/inputs_true.dat index 33c9cac339c..545bd1d4578 100644 --- a/tests/regression_tests/random_ray_k_eff/inputs_true.dat +++ b/tests/regression_tests/random_ray_k_eff/inputs_true.dat @@ -85,7 +85,7 @@ -1.26 -1.26 -1 1.26 1.26 1 - True + true diff --git a/tests/regression_tests/random_ray_k_eff_mesh/inputs_true.dat b/tests/regression_tests/random_ray_k_eff_mesh/inputs_true.dat index f0822d9f964..98badea18d8 100644 --- a/tests/regression_tests/random_ray_k_eff_mesh/inputs_true.dat +++ b/tests/regression_tests/random_ray_k_eff_mesh/inputs_true.dat @@ -85,7 +85,7 @@ -1.26 -1.26 -1 1.26 1.26 1 - True + true diff --git a/tests/regression_tests/random_ray_linear/linear/inputs_true.dat b/tests/regression_tests/random_ray_linear/linear/inputs_true.dat index 00699655722..a43a66e71c7 100644 --- a/tests/regression_tests/random_ray_linear/linear/inputs_true.dat +++ b/tests/regression_tests/random_ray_linear/linear/inputs_true.dat @@ -85,7 +85,7 @@ -1.26 -1.26 -1 1.26 1.26 1 - True + true linear diff --git a/tests/regression_tests/random_ray_linear/linear_xy/inputs_true.dat b/tests/regression_tests/random_ray_linear/linear_xy/inputs_true.dat index 81527c4793f..7f76f2fd1c1 100644 --- a/tests/regression_tests/random_ray_linear/linear_xy/inputs_true.dat +++ b/tests/regression_tests/random_ray_linear/linear_xy/inputs_true.dat @@ -85,7 +85,7 @@ -1.26 -1.26 -1 1.26 1.26 1 - True + true linear_xy diff --git a/tests/regression_tests/random_ray_low_density/inputs_true.dat b/tests/regression_tests/random_ray_low_density/inputs_true.dat index c4fd06f421e..ab91f74e50d 100644 --- a/tests/regression_tests/random_ray_low_density/inputs_true.dat +++ b/tests/regression_tests/random_ray_low_density/inputs_true.dat @@ -212,7 +212,7 @@ 0.0 0.0 0.0 30.0 30.0 30.0 - True + true diff --git a/tests/regression_tests/random_ray_point_source_locator/inputs_true.dat b/tests/regression_tests/random_ray_point_source_locator/inputs_true.dat index 82ad979da89..088f803bfa8 100644 --- a/tests/regression_tests/random_ray_point_source_locator/inputs_true.dat +++ b/tests/regression_tests/random_ray_point_source_locator/inputs_true.dat @@ -211,7 +211,7 @@ 0.0 0.0 0.0 30.0 30.0 30.0 - True + true diff --git a/tests/regression_tests/random_ray_void/flat/inputs_true.dat b/tests/regression_tests/random_ray_void/flat/inputs_true.dat index ea8c22f0b54..aa28e7b68bf 100644 --- a/tests/regression_tests/random_ray_void/flat/inputs_true.dat +++ b/tests/regression_tests/random_ray_void/flat/inputs_true.dat @@ -212,7 +212,7 @@ 0.0 0.0 0.0 30.0 30.0 30.0 - True + true flat diff --git a/tests/regression_tests/random_ray_void/linear/inputs_true.dat b/tests/regression_tests/random_ray_void/linear/inputs_true.dat index a089604ef4e..e4b2f22fa27 100644 --- a/tests/regression_tests/random_ray_void/linear/inputs_true.dat +++ b/tests/regression_tests/random_ray_void/linear/inputs_true.dat @@ -212,7 +212,7 @@ 0.0 0.0 0.0 30.0 30.0 30.0 - True + true linear diff --git a/tests/regression_tests/random_ray_volume_estimator/hybrid/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator/hybrid/inputs_true.dat index 089534d744a..8e8a8ed9b81 100644 --- a/tests/regression_tests/random_ray_volume_estimator/hybrid/inputs_true.dat +++ b/tests/regression_tests/random_ray_volume_estimator/hybrid/inputs_true.dat @@ -212,7 +212,7 @@ 0.0 0.0 0.0 30.0 30.0 30.0 - True + true hybrid diff --git a/tests/regression_tests/random_ray_volume_estimator/naive/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator/naive/inputs_true.dat index 56507df045e..1e25b97da66 100644 --- a/tests/regression_tests/random_ray_volume_estimator/naive/inputs_true.dat +++ b/tests/regression_tests/random_ray_volume_estimator/naive/inputs_true.dat @@ -212,7 +212,7 @@ 0.0 0.0 0.0 30.0 30.0 30.0 - True + true naive diff --git a/tests/regression_tests/random_ray_volume_estimator/simulation_averaged/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator/simulation_averaged/inputs_true.dat index 0331b562cdb..78c16269763 100644 --- a/tests/regression_tests/random_ray_volume_estimator/simulation_averaged/inputs_true.dat +++ b/tests/regression_tests/random_ray_volume_estimator/simulation_averaged/inputs_true.dat @@ -212,7 +212,7 @@ 0.0 0.0 0.0 30.0 30.0 30.0 - True + true simulation_averaged diff --git a/tests/regression_tests/random_ray_volume_estimator_linear/hybrid/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator_linear/hybrid/inputs_true.dat index 343833210e9..47a8a718249 100644 --- a/tests/regression_tests/random_ray_volume_estimator_linear/hybrid/inputs_true.dat +++ b/tests/regression_tests/random_ray_volume_estimator_linear/hybrid/inputs_true.dat @@ -212,7 +212,7 @@ 0.0 0.0 0.0 30.0 30.0 30.0 - True + true linear hybrid diff --git a/tests/regression_tests/random_ray_volume_estimator_linear/naive/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator_linear/naive/inputs_true.dat index 50be43eb386..80a9ada4d5b 100644 --- a/tests/regression_tests/random_ray_volume_estimator_linear/naive/inputs_true.dat +++ b/tests/regression_tests/random_ray_volume_estimator_linear/naive/inputs_true.dat @@ -212,7 +212,7 @@ 0.0 0.0 0.0 30.0 30.0 30.0 - True + true linear naive diff --git a/tests/regression_tests/random_ray_volume_estimator_linear/simulation_averaged/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator_linear/simulation_averaged/inputs_true.dat index 3d64ab97837..4f032a62a82 100644 --- a/tests/regression_tests/random_ray_volume_estimator_linear/simulation_averaged/inputs_true.dat +++ b/tests/regression_tests/random_ray_volume_estimator_linear/simulation_averaged/inputs_true.dat @@ -212,7 +212,7 @@ 0.0 0.0 0.0 30.0 30.0 30.0 - True + true linear simulation_averaged diff --git a/tests/regression_tests/weightwindows_fw_cadis/inputs_true.dat b/tests/regression_tests/weightwindows_fw_cadis/inputs_true.dat index 448b4b145c1..5fa6505ddf4 100644 --- a/tests/regression_tests/weightwindows_fw_cadis/inputs_true.dat +++ b/tests/regression_tests/weightwindows_fw_cadis/inputs_true.dat @@ -227,7 +227,7 @@ 0.0 0.0 0.0 30.0 30.0 30.0 - True + true naive diff --git a/tests/regression_tests/weightwindows_fw_cadis_mesh/flat/inputs_true.dat b/tests/regression_tests/weightwindows_fw_cadis_mesh/flat/inputs_true.dat index d82aa6fae86..ceb89e6e34c 100644 --- a/tests/regression_tests/weightwindows_fw_cadis_mesh/flat/inputs_true.dat +++ b/tests/regression_tests/weightwindows_fw_cadis_mesh/flat/inputs_true.dat @@ -227,7 +227,7 @@ 0.0 0.0 0.0 30.0 30.0 30.0 - True + true diff --git a/tests/regression_tests/weightwindows_fw_cadis_mesh/linear/inputs_true.dat b/tests/regression_tests/weightwindows_fw_cadis_mesh/linear/inputs_true.dat index 9e4b21d27be..c7691e950c4 100644 --- a/tests/regression_tests/weightwindows_fw_cadis_mesh/linear/inputs_true.dat +++ b/tests/regression_tests/weightwindows_fw_cadis_mesh/linear/inputs_true.dat @@ -227,7 +227,7 @@ 0.0 0.0 0.0 30.0 30.0 30.0 - True + true diff --git a/tests/unit_tests/test_settings.py b/tests/unit_tests/test_settings.py index 6611e4227e0..a6abca41a03 100644 --- a/tests/unit_tests/test_settings.py +++ b/tests/unit_tests/test_settings.py @@ -59,12 +59,23 @@ def test_export_to_xml(run_in_tmpdir): s.electron_treatment = 'led' s.write_initial_source = True s.weight_window_checkpoints = {'surface': True, 'collision': False} + source_region_mesh = openmc.RegularMesh() + source_region_mesh.dimension = [2, 2, 2] + source_region_mesh.lower_left = [-2, -2, -2] + source_region_mesh.upper_right = [2, 2, 2] + root_universe = openmc.Universe() s.random_ray = { 'distance_inactive': 10.0, 'distance_active': 100.0, 'ray_source': openmc.IndependentSource( space=openmc.stats.Box((-1., -1., -1.), (1., 1., 1.)) - ) + ), + 'source_region_meshes': [(source_region_mesh, [root_universe])], + 'volume_estimator': 'hybrid', + 'source_shape': 'linear', + 'volume_normalized_flux_tallies': True, + 'adjoint': False, + 'sample_method': 'halton' } s.max_particle_events = 100 s.max_secondaries = 1_000_000 @@ -145,5 +156,17 @@ def test_export_to_xml(run_in_tmpdir): assert s.random_ray['distance_active'] == 100.0 assert s.random_ray['ray_source'].space.lower_left == [-1., -1., -1.] assert s.random_ray['ray_source'].space.upper_right == [1., 1., 1.] + assert 'source_region_meshes' in s.random_ray + assert len(s.random_ray['source_region_meshes']) == 1 + mesh_and_domains = s.random_ray['source_region_meshes'][0] + recovered_mesh = mesh_and_domains[0] + assert recovered_mesh.dimension == (2, 2, 2) + assert recovered_mesh.lower_left == [-2., -2., -2.] + assert recovered_mesh.upper_right == [2., 2., 2.] + assert s.random_ray['volume_estimator'] == 'hybrid' + assert s.random_ray['source_shape'] == 'linear' + assert s.random_ray['volume_normalized_flux_tallies'] + assert not s.random_ray['adjoint'] + assert s.random_ray['sample_method'] == 'halton' assert s.max_secondaries == 1_000_000 assert s.source_rejection_fraction == 0.01 From 8a62e7e3235f75810cf59fc80bd69951e0d99fda Mon Sep 17 00:00:00 2001 From: Thomas Kittelmann Date: Fri, 3 Oct 2025 01:27:36 +0200 Subject: [PATCH 124/159] Fix caching issue when using NCrystal materials (#3538) Co-authored-by: Jose Ignacio Marquez Damian <22483345+marquezj@users.noreply.github.com> Co-authored-by: Paul Romano --- include/openmc/particle_data.h | 7 ++++--- openmc/plotter.py | 2 +- src/particle.cpp | 4 +++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index afcd5647674..5383487d4fa 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -154,9 +154,10 @@ struct NuclideMicroXS { // Energy and temperature last used to evaluate these cross sections. If // these values have changed, then the cross sections must be re-evaluated. - double last_E {0.0}; //!< Last evaluated energy - double last_sqrtkT {0.0}; //!< Last temperature in sqrt(Boltzmann constant - //!< * temperature (eV)) + double last_E {0.0}; //!< Last evaluated energy + double last_sqrtkT {0.0}; //!< Last temperature in sqrt(Boltzmann constant + //!< * temperature (eV)) + double ncrystal_xs {-1.0}; //!< NCrystal cross section }; //============================================================================== diff --git a/openmc/plotter.py b/openmc/plotter.py index 693cdaca4eb..abd8ab6dd4b 100644 --- a/openmc/plotter.py +++ b/openmc/plotter.py @@ -501,7 +501,7 @@ def _calculate_cexs_nuclide(this, types, temperature=294., sab_name=None, elif ncrystal_cfg: import NCrystal nc_scatter = NCrystal.createScatter(ncrystal_cfg) - nc_func = nc_scatter.crossSectionNonOriented + nc_func = nc_scatter.xsect nc_emax = 5 # eV # this should be obtained from NCRYSTAL_MAX_ENERGY energy_grid = np.union1d(np.geomspace(min(energy_grid), 1.1*nc_emax, diff --git a/src/particle.cpp b/src/particle.cpp index 402af2498a5..06ccf462a18 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -855,10 +855,12 @@ void Particle::update_neutron_xs( // If the cache doesn't match, recalculate micro xs if (this->E() != micro.last_E || this->sqrtkT() != micro.last_sqrtkT || - i_sab != micro.index_sab || sab_frac != micro.sab_frac) { + i_sab != micro.index_sab || sab_frac != micro.sab_frac || + ncrystal_xs != micro.ncrystal_xs) { data::nuclides[i_nuclide]->calculate_xs(i_sab, i_grid, sab_frac, *this); // If NCrystal is being used, update micro cross section cache + micro.ncrystal_xs = ncrystal_xs; if (ncrystal_xs >= 0.0) { data::nuclides[i_nuclide]->calculate_elastic_xs(*this); ncrystal_update_micro(ncrystal_xs, micro); From 50071aa3bd29b78f28fe4c8efe211cb5f65778ee Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Fri, 3 Oct 2025 17:09:57 +0200 Subject: [PATCH 125/159] Speed up time correction factors (#3592) Co-authored-by: Jon Shimwell Co-authored-by: Paul Romano --- openmc/deplete/d1s.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/openmc/deplete/d1s.py b/openmc/deplete/d1s.py index 311bc2d69cd..f51dea41606 100644 --- a/openmc/deplete/d1s.py +++ b/openmc/deplete/d1s.py @@ -108,14 +108,15 @@ def time_correction_factors( # Create a 2D array for the time correction factors h = np.zeros((n_timesteps, n_nuclides)) - for i, (dt, rate) in enumerate(zip(timesteps, source_rates)): - # Precompute the exponential terms. Since (1 - exp(-x)) is susceptible to - # roundoff error, use expm1 instead (which computes exp(x) - 1) - g = np.exp(-decay_rate*dt) - one_minus_g = -np.expm1(-decay_rate*dt) + # Precompute all exponential terms with same shape as h + decay_dt = decay_rate[np.newaxis, :] * timesteps[:, np.newaxis] + g = np.exp(-decay_dt) + one_minus_g = -np.expm1(-decay_dt) + # Apply recurrence relation step by step + for i in range(len(timesteps)): # Eq. (4) in doi:10.1016/j.fusengdes.2019.111399 - h[i + 1] = rate*one_minus_g + h[i]*g + h[i + 1] = source_rates[i] * one_minus_g[i] + h[i] * g[i] return {nuclides[i]: h[:, i] for i in range(n_nuclides)} From 2c15480cc9f1f7407b2fecd8b47a56f976b7bd02 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 6 Oct 2025 07:45:26 -0500 Subject: [PATCH 126/159] Add user setting for free gas threshold (#3593) --- docs/source/io_formats/settings.rst | 10 ++++++++++ include/openmc/physics.h | 7 ------- include/openmc/settings.h | 2 ++ openmc/settings.py | 28 ++++++++++++++++++++++++++++ src/finalize.cpp | 1 + src/physics.cpp | 2 +- src/settings.cpp | 5 +++++ tests/unit_tests/test_settings.py | 2 ++ 8 files changed, 49 insertions(+), 8 deletions(-) diff --git a/docs/source/io_formats/settings.rst b/docs/source/io_formats/settings.rst index 26673faac2e..720846c8519 100644 --- a/docs/source/io_formats/settings.rst +++ b/docs/source/io_formats/settings.rst @@ -178,6 +178,16 @@ history-based parallelism. *Default*: false +-------------------------------- +```` Element +-------------------------------- + +The ```` element specifies the energy multiplier, expressed +in units of :math:`kT`, that determines when the free gas scattering approach is +used for elastic scattering. Values must be positive. + + *Default*: 400.0 + ----------------------------------- ```` Element ----------------------------------- diff --git a/include/openmc/physics.h b/include/openmc/physics.h index f62f43a02f2..2472d979939 100644 --- a/include/openmc/physics.h +++ b/include/openmc/physics.h @@ -10,13 +10,6 @@ namespace openmc { -//============================================================================== -// Constants -//============================================================================== - -// Monoatomic ideal-gas scattering treatment threshold -constexpr double FREE_GAS_THRESHOLD {400.0}; - //============================================================================== // Non-member functions //============================================================================== diff --git a/include/openmc/settings.h b/include/openmc/settings.h index 999b1b83f87..78bfa088e6f 100644 --- a/include/openmc/settings.h +++ b/include/openmc/settings.h @@ -147,6 +147,8 @@ extern std::unordered_set source_write_surf_id; //!< Surface ids where sources will be written extern double source_rejection_fraction; //!< Minimum fraction of source sites //!< that must be accepted +extern double free_gas_threshold; //!< Threshold multiplier for free gas + //!< scattering treatment extern int max_history_splits; //!< maximum number of particle splits for weight windows diff --git a/openmc/settings.py b/openmc/settings.py index 8f8aedc93e8..2f8a2b12485 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -84,6 +84,10 @@ class Settings: history-based parallelism. .. versionadded:: 0.12 + free_gas_threshold : float + Energy multiplier (in units of :math:`kT`) below which the free gas + scattering treatment is applied for elastic scattering. If not + specified, a value of 400.0 is used. generations_per_batch : int Number of generations per batch ifp_n_generation : int @@ -376,6 +380,7 @@ def __init__(self, **kwargs): self._seed = None self._stride = None self._survival_biasing = None + self._free_gas_threshold = None # Shannon entropy mesh self._entropy_mesh = None @@ -1255,6 +1260,17 @@ def source_rejection_fraction(self, source_rejection_fraction: float): cv.check_less_than('source_rejection_fraction', source_rejection_fraction, 1) self._source_rejection_fraction = source_rejection_fraction + @property + def free_gas_threshold(self) -> float | None: + return self._free_gas_threshold + + @free_gas_threshold.setter + def free_gas_threshold(self, free_gas_threshold: float | None): + if free_gas_threshold is not None: + cv.check_type('free gas threshold', free_gas_threshold, Real) + cv.check_greater_than('free gas threshold', free_gas_threshold, 0.0) + self._free_gas_threshold = free_gas_threshold + def _create_run_mode_subelement(self, root): elem = ET.SubElement(root, "run_mode") elem.text = self._run_mode.value @@ -1733,6 +1749,11 @@ def _create_source_rejection_fraction_subelement(self, root): element = ET.SubElement(root, "source_rejection_fraction") element.text = str(self._source_rejection_fraction) + def _create_free_gas_threshold_subelement(self, root): + if self._free_gas_threshold is not None: + element = ET.SubElement(root, "free_gas_threshold") + element.text = str(self._free_gas_threshold) + def _eigenvalue_from_xml_element(self, root): elem = root.find('eigenvalue') if elem is not None: @@ -2171,6 +2192,11 @@ def _source_rejection_fraction_from_xml_element(self, root): if text is not None: self.source_rejection_fraction = float(text) + def _free_gas_threshold_from_xml_element(self, root): + text = get_text(root, 'free_gas_threshold') + if text is not None: + self.free_gas_threshold = float(text) + def to_xml_element(self, mesh_memo=None): """Create a 'settings' element to be written to an XML file. @@ -2241,6 +2267,7 @@ def to_xml_element(self, mesh_memo=None): self._create_random_ray_subelement(element, mesh_memo) self._create_use_decay_photons_subelement(element) self._create_source_rejection_fraction_subelement(element) + self._create_free_gas_threshold_subelement(element) # Clean the indentation in the file to be user-readable clean_indentation(element) @@ -2352,6 +2379,7 @@ def from_xml_element(cls, elem, meshes=None): settings._random_ray_from_xml_element(elem, meshes) settings._use_decay_photons_from_xml_element(elem) settings._source_rejection_fraction_from_xml_element(elem) + settings._free_gas_threshold_from_xml_element(elem) return settings diff --git a/src/finalize.cpp b/src/finalize.cpp index 25b471a8bd2..76babbb96c6 100644 --- a/src/finalize.cpp +++ b/src/finalize.cpp @@ -85,6 +85,7 @@ int openmc_finalize() settings::time_cutoff = {INFTY, INFTY, INFTY, INFTY}; settings::entropy_on = false; settings::event_based = false; + settings::free_gas_threshold = 400.0; settings::gen_per_batch = 1; settings::legendre_to_tabular = true; settings::legendre_to_tabular_points = -1; diff --git a/src/physics.cpp b/src/physics.cpp index e947fecbb9c..3a17077b368 100644 --- a/src/physics.cpp +++ b/src/physics.cpp @@ -851,7 +851,7 @@ Direction sample_target_velocity(const Nuclide& nuc, double E, Direction u, // otherwise, use free gas model } else { - if (E >= FREE_GAS_THRESHOLD * kT && nuc.awr_ > 1.0) { + if (E >= settings::free_gas_threshold * kT && nuc.awr_ > 1.0) { return {}; } else { sampling_method = ResScatMethod::cxs; diff --git a/src/settings.cpp b/src/settings.cpp index f9a2469db93..325256cdc50 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -126,6 +126,7 @@ SolverType solver_type {SolverType::MONTE_CARLO}; std::unordered_set sourcepoint_batch; std::unordered_set statepoint_batch; double source_rejection_fraction {0.05}; +double free_gas_threshold {400.0}; std::unordered_set source_write_surf_id; int64_t ssw_max_particles; int64_t ssw_max_files; @@ -651,6 +652,10 @@ void read_settings_xml(pugi::xml_node root) std::stod(get_node_value(root, "source_rejection_fraction")); } + if (check_for_node(root, "free_gas_threshold")) { + free_gas_threshold = std::stod(get_node_value(root, "free_gas_threshold")); + } + // Survival biasing if (check_for_node(root, "survival_biasing")) { survival_biasing = get_node_value_bool(root, "survival_biasing"); diff --git a/tests/unit_tests/test_settings.py b/tests/unit_tests/test_settings.py index a6abca41a03..fe618fd2d65 100644 --- a/tests/unit_tests/test_settings.py +++ b/tests/unit_tests/test_settings.py @@ -80,6 +80,7 @@ def test_export_to_xml(run_in_tmpdir): s.max_particle_events = 100 s.max_secondaries = 1_000_000 s.source_rejection_fraction = 0.01 + s.free_gas_threshold = 800.0 # Make sure exporting XML works s.export_to_xml() @@ -170,3 +171,4 @@ def test_export_to_xml(run_in_tmpdir): assert s.random_ray['sample_method'] == 'halton' assert s.max_secondaries == 1_000_000 assert s.source_rejection_fraction == 0.01 + assert s.free_gas_threshold == 800.0 From 3dfa34d2c6a239cad327baa5565cd71f158fc299 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 6 Oct 2025 21:14:37 -0500 Subject: [PATCH 127/159] Switch to using coveralls github action for reporting (#3594) --- .github/workflows/ci.yml | 35 +++++++++++++++++++++++++++++++---- pyproject.toml | 4 ++-- tools/ci/gha-script.sh | 2 +- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 38a49b60219..d75a64d662c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,6 +75,7 @@ jobs: LIBMESH: ${{ matrix.libmesh }} NPY_DISABLE_CPU_FEATURES: "AVX512F AVX512_SKX" OPENBLAS_NUM_THREADS: 1 + PYTEST_ADDOPTS: --cov=openmc --cov-report=lcov:coverage-python.lcov # libfabric complains about fork() as a result of using Python multiprocessing. # We can work around it with RDMAV_FORK_SAFE=1 in libfabric < 1.13 and with # FI_EFA_FORK_SAFE=1 in more recent versions. @@ -171,11 +172,37 @@ jobs: uses: mxschmitt/action-tmate@v3 timeout-minutes: 10 - - name: after_success + - name: Generate C++ coverage (gcovr) shell: bash run: | - cpp-coveralls -i src -i include -e src/external --exclude-pattern "/usr/*" --dump cpp_cov.json - coveralls --merge=cpp_cov.json --service=github + # Produce LCOV directly from gcov data in the build tree + gcovr \ + --root "$GITHUB_WORKSPACE" \ + --object-directory "$GITHUB_WORKSPACE/build" \ + --filter "$GITHUB_WORKSPACE/src" \ + --filter "$GITHUB_WORKSPACE/include" \ + --exclude "$GITHUB_WORKSPACE/src/external/.*" \ + --exclude "$GITHUB_WORKSPACE/src/include/openmc/external/.*" \ + --gcov-ignore-errors source_not_found \ + --gcov-ignore-errors output_error \ + --gcov-ignore-parse-errors suspicious_hits.warn \ + --print-summary \ + --lcov -o coverage-cpp.lcov || true + + - name: Merge C++ and Python coverage + shell: bash + run: | + # Merge C++ and Python LCOV into a single file for upload + cat coverage-cpp.lcov coverage-python.lcov > coverage.lcov + + - name: Upload coverage to Coveralls + if: ${{ hashFiles('coverage.lcov') != '' }} + uses: coverallsapp/github-action@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + parallel: true + flag-name: C++ and Python + path-to-lcov: coverage.lcov finish: needs: main @@ -184,5 +211,5 @@ jobs: - name: Coveralls Finished uses: coverallsapp/github-action@v2 with: - github-token: ${{ secrets.github_token }} + github-token: ${{ secrets.GITHUB_TOKEN }} parallel-finished: true diff --git a/pyproject.toml b/pyproject.toml index 7705de8dae4..53d429d943f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,8 +48,8 @@ docs = [ "sphinxcontrib-svg2pdfconverter", "sphinx-rtd-theme" ] -test = ["packaging", "pytest", "pytest-cov", "colorama", "openpyxl"] -ci = ["cpp-coveralls", "coveralls"] +test = ["packaging", "pytest", "pytest-cov>=4.0", "colorama", "openpyxl"] +ci = ["coverage>=7.4", "gcovr>=7.2"] vtk = ["vtk"] [project.urls] diff --git a/tools/ci/gha-script.sh b/tools/ci/gha-script.sh index c1d29213772..b40238ffb56 100755 --- a/tools/ci/gha-script.sh +++ b/tools/ci/gha-script.sh @@ -15,7 +15,7 @@ if [[ $EVENT == 'y' ]]; then fi # Run unit tests and then regression tests -pytest --cov=openmc -v $args \ +pytest -v $args \ tests/test_matplotlib_import.py \ tests/unit_tests \ tests/regression_tests From 58c7fbeac67a3e6db0e26a9dc2db421da3abf933 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 14 Oct 2025 07:47:26 -0500 Subject: [PATCH 128/159] Re-run flaky tests when needed (#3604) --- pyproject.toml | 9 ++++++++- tests/unit_tests/test_deplete_activation.py | 1 + tests/unit_tests/test_stats.py | 10 ++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 53d429d943f..2d67e834012 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,14 @@ docs = [ "sphinxcontrib-svg2pdfconverter", "sphinx-rtd-theme" ] -test = ["packaging", "pytest", "pytest-cov>=4.0", "colorama", "openpyxl"] +test = [ + "packaging", + "pytest", + "pytest-cov>=4.0", + "pytest-rerunfailures", + "colorama", + "openpyxl", +] ci = ["coverage>=7.4", "gcovr>=7.2"] vtk = ["vtk"] diff --git a/tests/unit_tests/test_deplete_activation.py b/tests/unit_tests/test_deplete_activation.py index cbe00d680c9..eace1976ef6 100644 --- a/tests/unit_tests/test_deplete_activation.py +++ b/tests/unit_tests/test_deplete_activation.py @@ -49,6 +49,7 @@ def model(): ("flux", {'energies': ENERGIES, 'reactions': ['(n,gamma)']}, 1e-5), ("flux", {'energies': ENERGIES, 'reactions': ['(n,gamma)'], 'nuclides': ['W186', 'H3']}, 1e-2), ]) +@pytest.mark.flaky(reruns=1) def test_activation(run_in_tmpdir, model, reaction_rate_mode, reaction_rate_opts, tolerance): # Determine (n.gamma) reaction rate using initial run sp = model.run() diff --git a/tests/unit_tests/test_stats.py b/tests/unit_tests/test_stats.py index 386181f34d2..abf143f12a2 100644 --- a/tests/unit_tests/test_stats.py +++ b/tests/unit_tests/test_stats.py @@ -16,6 +16,7 @@ def assert_sample_mean(samples, expected_mean): assert np.abs(expected_mean - samples.mean()) < 4*std_dev +@pytest.mark.flaky(reruns=1) def test_discrete(): x = [0.0, 1.0, 10.0] p = [0.3, 0.2, 0.5] @@ -104,6 +105,7 @@ def test_clip_discrete(): d.clip(5) +@pytest.mark.flaky(reruns=1) def test_uniform(): a, b = 10.0, 20.0 d = openmc.stats.Uniform(a, b) @@ -127,6 +129,7 @@ def test_uniform(): assert_sample_mean(samples, exp_mean) +@pytest.mark.flaky(reruns=1) def test_powerlaw(): a, b, n = 10.0, 100.0, 2.0 d = openmc.stats.PowerLaw(a, b, n) @@ -148,6 +151,7 @@ def test_powerlaw(): assert_sample_mean(samples, exp_mean) +@pytest.mark.flaky(reruns=1) def test_maxwell(): theta = 1.2895e6 d = openmc.stats.Maxwell(theta) @@ -171,6 +175,7 @@ def test_maxwell(): assert samples_2.mean() != samples.mean() +@pytest.mark.flaky(reruns=1) def test_watt(): a, b = 0.965e6, 2.29e-6 d = openmc.stats.Watt(a, b) @@ -194,6 +199,7 @@ def test_watt(): assert_sample_mean(samples, exp_mean) +@pytest.mark.flaky(reruns=1) def test_tabular(): # test linear-linear sampling x = np.array([0.0, 5.0, 7.0, 10.0]) @@ -270,6 +276,7 @@ def test_legendre(): d.to_xml_element('distribution') +@pytest.mark.flaky(reruns=1) def test_mixture(): d1 = openmc.stats.Uniform(0, 5) d2 = openmc.stats.Uniform(3, 7) @@ -425,6 +432,7 @@ def test_point(): assert d.xyz == pytest.approx(p) +@pytest.mark.flaky(reruns=1) def test_normal(): mean = 10.0 std_dev = 2.0 @@ -444,6 +452,7 @@ def test_normal(): assert_sample_mean(samples, mean) +@pytest.mark.flaky(reruns=1) def test_muir(): mean = 10.0 mass = 5.0 @@ -463,6 +472,7 @@ def test_muir(): assert_sample_mean(samples, mean) +@pytest.mark.flaky(reruns=1) def test_combine_distributions(): # Combine two discrete (same data as in test_merge_discrete) x1 = [0.0, 1.0, 10.0] From e9077b1372fa1e3ee4adbcfcbfaeaf04a340c8dd Mon Sep 17 00:00:00 2001 From: jiankai-yu Date: Tue, 14 Oct 2025 16:21:11 -0400 Subject: [PATCH 129/159] Allow V0 in atomic_mass function (for ENDF/B-VII.0 data) (#3607) --- openmc/data/data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/data/data.py b/openmc/data/data.py index 2142a5dc905..5ecadd37be0 100644 --- a/openmc/data/data.py +++ b/openmc/data/data.py @@ -324,7 +324,7 @@ def atomic_mass(isotope): # isotopes of their element (e.g. C0), calculate the atomic mass as # the sum of the atomic mass times the natural abundance of the isotopes # that make up the element. - for element in ['C', 'Zn', 'Pt', 'Os', 'Tl']: + for element in ['C', 'Zn', 'Pt', 'Os', 'Tl', 'V']: isotope_zero = element.lower() + '0' _ATOMIC_MASS[isotope_zero] = 0. for iso, abundance in isotopes(element): From b94b49611365a7eb9bd1a107861e5ae5c44b977d Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Thu, 16 Oct 2025 01:51:59 +0200 Subject: [PATCH 130/159] Ability to source electron/positrons directly (#3404) Co-authored-by: Paul Romano --- .../methods/charged_particles_physics.rst | 362 ++++++++++++++++++ docs/source/methods/index.rst | 3 +- docs/source/methods/photon_physics.rst | 338 ---------------- include/openmc/nuclide.h | 4 +- openmc/source.py | 7 +- src/finalize.cpp | 4 +- src/nuclide.cpp | 4 +- src/particle.cpp | 2 +- src/simulation.cpp | 21 +- src/source.cpp | 6 + .../electron_heating/__init__.py | 0 .../electron_heating/inputs_true.dat | 32 ++ .../electron_heating/results_true.dat | 3 + .../regression_tests/electron_heating/test.py | 40 ++ 14 files changed, 472 insertions(+), 354 deletions(-) create mode 100644 docs/source/methods/charged_particles_physics.rst create mode 100644 tests/regression_tests/electron_heating/__init__.py create mode 100644 tests/regression_tests/electron_heating/inputs_true.dat create mode 100644 tests/regression_tests/electron_heating/results_true.dat create mode 100644 tests/regression_tests/electron_heating/test.py diff --git a/docs/source/methods/charged_particles_physics.rst b/docs/source/methods/charged_particles_physics.rst new file mode 100644 index 00000000000..5d763074fd8 --- /dev/null +++ b/docs/source/methods/charged_particles_physics.rst @@ -0,0 +1,362 @@ +.. _methods_charged_particle_physics: + +======================== +Charged Particle Physics +======================== + +OpenMC neglects the spatial transport of charged particles (electrons and +positrons), assuming they deposit all their energy locally and produce +bremsstrahlung photons at their birth location. This approximation, called +thick-target bremsstrahlung (TTB) approximation is justified by the fact that +charged particles have much shorter stopping ranges compared to neutrons and +photons, especially in high-density materials. + +----------------------------- +Charged Particle Interactions +----------------------------- + +Bremsstrahlung +-------------- + +When a charged particle is decelerated in the field of an atom, some of its +kinetic energy is converted into electromagnetic radiation known as +bremsstrahlung, or 'braking radiation'. In each event, an electron or positron +with kinetic energy :math:`T` generates a photon with an energy :math:`E` +between :math:`0` and :math:`T`. Bremsstrahlung is described by a cross section +that is differential in photon energy, in the direction of the emitted photon, +and in the final direction of the charged particle. However, in Monte Carlo +simulations it is typical to integrate over the angular variables to obtain a +single differential cross section with respect to photon energy, which is often +expressed in the form + +.. math:: + :label: bremsstrahlung-dcs + + \frac{d\sigma_{\text{br}}}{dE} = \frac{Z^2}{\beta^2} \frac{1}{E} + \chi(Z, T, \kappa), + +where :math:`\kappa = E/T` is the reduced photon energy and :math:`\chi(Z, T, +\kappa)` is the scaled bremsstrahlung cross section, which is experimentally +measured. + +Because electrons are attracted to atomic nuclei whereas positrons are +repulsed, the cross section for positrons is smaller, though it approaches that +of electrons in the high energy limit. To obtain the positron cross section, we +multiply :eq:`bremsstrahlung-dcs` by the :math:`\kappa`-independent factor used +in Salvat_, + +.. math:: + :label: positron-factor + + \begin{aligned} + F_{\text{p}}(Z,T) = + & 1 - \text{exp}(-1.2359\times 10^{-1}t + 6.1274\times 10^{-2}t^2 - 3.1516\times 10^{-2}t^3 \\ + & + 7.7446\times 10^{-3}t^4 - 1.0595\times 10^{-3}t^5 + 7.0568\times 10^{-5}t^6 \\ + & - 1.8080\times 10^{-6}t^7), + \end{aligned} + +where + +.. math:: + :label: positron-factor-t + + t = \ln\left(1 + \frac{10^6}{Z^2}\frac{T}{\text{m}_\text{e}c^2} \right). + +:math:`F_{\text{p}}(Z,T)` is the ratio of the radiative stopping powers for +positrons and electrons. Stopping power describes the average energy loss per +unit path length of a charged particle as it passes through matter: + +.. math:: + :label: stopping-power + + -\frac{dT}{ds} = n \int E \frac{d\sigma}{dE} dE \equiv S(T), + +where :math:`n` is the number density of the material and :math:`d\sigma/dE` is +the cross section differential in energy loss. The total stopping power +:math:`S(T)` can be separated into two components: the radiative stopping +power :math:`S_{\text{rad}}(T)`, which refers to energy loss due to +bremsstrahlung, and the collision stopping power :math:`S_{\text{col}}(T)`, +which refers to the energy loss due to inelastic collisions with bound +electrons in the material that result in ionization and excitation. The +radiative stopping power for electrons is given by + +.. math:: + :label: radiative-stopping-power + + S_{\text{rad}}(T) = n \frac{Z^2}{\beta^2} T \int_0^1 \chi(Z,T,\kappa) + d\kappa. + + +To obtain the radiative stopping power for positrons, +:eq:`radiative-stopping-power` is multiplied by :eq:`positron-factor`. + +While the models for photon interactions with matter described above can safely +assume interactions occur with free atoms, sampling the target atom based on +the macroscopic cross sections, molecular effects cannot necessarily be +disregarded for charged particle treatment. For compounds and mixtures, the +bremsstrahlung cross section is calculated using Bragg's additivity rule as + +.. math:: + :label: material-bremsstrahlung-dcs + + \frac{d\sigma_{\text{br}}}{dE} = \frac{1}{\beta^2 E} \sum_i \gamma_i Z^2_i + \chi(Z_i, T, \kappa), + +where the sum is over the constituent elements and :math:`\gamma_i` is the +atomic fraction of the :math:`i`-th element. Similarly, the radiative stopping +power is calculated using Bragg's additivity rule as + +.. math:: + :label: material-radiative-stopping-power + + S_{\text{rad}}(T) = \sum_i w_i S_{\text{rad},i}(T), + +where :math:`w_i` is the mass fraction of the :math:`i`-th element and +:math:`S_{\text{rad},i}(T)` is found for element :math:`i` using +:eq:`radiative-stopping-power`. The collision stopping power, however, is a +function of certain quantities such as the mean excitation energy :math:`I` and +the density effect correction :math:`\delta_F` that depend on molecular +properties. These quantities cannot simply be summed over constituent elements +in a compound, but should instead be calculated for the material. The Bethe +formula can be used to find the collision stopping power of the material: + +.. math:: + :label: material-collision-stopping-power + + S_{\text{col}}(T) = \frac{2 \pi r_e^2 m_e c^2}{\beta^2} N_A \frac{Z}{A_M} + [\ln(T^2/I^2) + \ln(1 + \tau/2) + F(\tau) - \delta_F(T)], + +where :math:`N_A` is Avogadro's number, :math:`A_M` is the molar mass, +:math:`\tau = T/m_e`, and :math:`F(\tau)` depends on the particle type. For +electrons, + +.. math:: + :label: F-electron + + F_{-}(\tau) = (1 - \beta^2)[1 + \tau^2/8 - (2\tau + 1) \ln2], + +while for positrons + +.. math:: + :label: F-positron + + F_{+}(\tau) = 2\ln2 - (\beta^2/12)[23 + 14/(\tau + 2) + 10/(\tau + 2)^2 + + 4/(\tau + 2)^3]. + +The density effect correction :math:`\delta_F` takes into account the reduction +of the collision stopping power due to the polarization of the material the +charged particle is passing through by the electric field of the particle. +It can be evaluated using the method described by Sternheimer_, where the +equation for :math:`\delta_F` is + +.. math:: + :label: density-effect-correction + + \delta_F(\beta) = \sum_{i=1}^n f_i \ln[(l_i^2 + l^2)/l_i^2] - + l^2(1-\beta^2). + +Here, :math:`f_i` is the oscillator strength of the :math:`i`-th transition, +given by :math:`f_i = n_i/Z`, where :math:`n_i` is the number of electrons in +the :math:`i`-th subshell. The frequency :math:`l` is the solution of the +equation + +.. math:: + :label: density-effect-l + + \frac{1}{\beta^2} - 1 = \sum_{i=1}^{n} \frac{f_i}{\bar{\nu}_i^2 + l^2}, + +where :math:`\bar{v}_i` is defined as + +.. math:: + :label: density-effect-nubar + + \bar{\nu}_i = h\nu_i \rho / h\nu_p. + +The plasma energy :math:`h\nu_p` of the medium is given by + +.. math:: + :label: plasma-frequency + + h\nu_p = \sqrt{\frac{(hc)^2 r_e \rho_m N_A Z}{\pi A}}, + +where :math:`A` is the atomic weight and :math:`\rho_m` is the density of the +material. In :eq:`density-effect-nubar`, :math:`h\nu_i` is the oscillator +energy, and :math:`\rho` is an adjustment factor introduced to give agreement +between the experimental values of the oscillator energies and the mean +excitation energy. The :math:`l_i` in :eq:`density-effect-correction` are +defined as + +.. math:: + :label: density-effect-li + + \begin{aligned} + l_i &= (\bar{\nu}_i^2 + 2/3f_i)^{1/2} ~~~~&\text{for}~~ \bar{\nu}_i > 0 \\ + l_n &= f_n^{1/2} ~~~~&\text{for}~~ \bar{\nu}_n = 0, + \end{aligned} + +where the second case applies to conduction electrons. For a conductor, +:math:`f_n` is given by :math:`n_c/Z`, where :math:`n_c` is the effective +number of conduction electrons, and :math:`v_n = 0`. The adjustment factor +:math:`\rho` is determined using the equation for the mean excitation energy: + +.. math:: + :label: mean-excitation-energy + + \ln I = \sum_{i=1}^{n-1} f_i \ln[(h\nu_i\rho)^2 + 2/3f_i(h\nu_p)^2]^{1/2} + + f_n \ln (h\nu_pf_n^{1/2}). + +.. _ttb: + + +Thick-Target Bremsstrahlung Approximation ++++++++++++++++++++++++++++++++++++++++++ + +Since charged particles lose their energy on a much shorter distance scale than +neutral particles, not much error should be introduced by neglecting to +transport electrons. However, the bremsstrahlung emitted from high energy +electrons and positrons can travel far from the interaction site. Thus, even +without a full electron transport mode it is necessary to model bremsstrahlung. +We use a thick-target bremsstrahlung (TTB) approximation based on the models in +Salvat_ and Kaltiaisenaho_ for generating bremsstrahlung photons, which assumes +the charged particle loses all its energy in a single homogeneous material +region. + +To model bremsstrahlung using the TTB approximation, we need to know the number +of photons emitted by the charged particle and the energy distribution of the +photons. These quantities can be calculated using the continuous slowing down +approximation (CSDA). The CSDA assumes charged particles lose energy +continuously along their trajectory with a rate of energy loss equal to the +total stopping power, ignoring fluctuations in the energy loss. The +approximation is useful for expressing average quantities that describe how +charged particles slow down in matter. For example, the CSDA range approximates +the average path length a charged particle travels as it slows to rest: + +.. math:: + :label: csda-range + + R(T) = \int^T_0 \frac{dT'}{S(T')}. + +Actual path lengths will fluctuate around :math:`R(T)`. The average number of +photons emitted per unit path length is given by the inverse bremsstrahlung +mean free path: + +.. math:: + :label: inverse-bremsstrahlung-mfp + + \lambda_{\text{br}}^{-1}(T,E_{\text{cut}}) + = n\int_{E_{\text{cut}}}^T\frac{d\sigma_{\text{br}}}{dE}dE + = n\frac{Z^2}{\beta^2}\int_{\kappa_{\text{cut}}}^1\frac{1}{\kappa} + \chi(Z,T,\kappa)d\kappa. + +The lower limit of the integral in :eq:`inverse-bremsstrahlung-mfp` is non-zero +because the bremsstrahlung differential cross section diverges for small photon +energies but is finite for photon energies above some cutoff energy +:math:`E_{\text{cut}}`. The mean free path +:math:`\lambda_{\text{br}}^{-1}(T,E_{\text{cut}})` is used to calculate the +photon number yield, defined as the average number of photons emitted with +energy greater than :math:`E_{\text{cut}}` as the charged particle slows down +from energy :math:`T` to :math:`E_{\text{cut}}`. The photon number yield is +given by + +.. math:: + :label: photon-number-yield + + Y(T,E_{\text{cut}}) = \int^{R(T)}_{R(E_{\text{cut}})} + \lambda_{\text{br}}^{-1}(T',E_{\text{cut}})ds = \int_{E_{\text{cut}}}^T + \frac{\lambda_{\text{br}}^{-1}(T',E_{\text{cut}})}{S(T')}dT'. + +:math:`Y(T,E_{\text{cut}})` can be used to construct the energy spectrum of +bremsstrahlung photons: the number of photons created with energy between +:math:`E_1` and :math:`E_2` by a charged particle with initial kinetic energy +:math:`T` as it comes to rest is given by :math:`Y(T,E_1) - Y(T,E_2)`. + +To simulate the emission of bremsstrahlung photons, the total stopping power +and bremsstrahlung differential cross section for positrons and electrons must +be calculated for a given material using :eq:`material-bremsstrahlung-dcs` and +:eq:`material-radiative-stopping-power`. These quantities are used to build the +tabulated bremsstrahlung energy PDF and CDF for that material for each incident +energy :math:`T_k` on the energy grid. The following algorithm is then applied +to sample the photon energies: + +1. For an incident charged particle with energy :math:`T`, sample the number of + emitted photons as + + .. math:: + + N = \lfloor Y(T,E_{\text{cut}}) + \xi_1 \rfloor. + +2. Rather than interpolate the PDF between indices :math:`k` and :math:`k+1` + for which :math:`T_k < T < T_{k+1}`, which is computationally expensive, use + the composition method and sample from the PDF at either :math:`k` or + :math:`k+1`. Using linear interpolation on a logarithmic scale, the PDF can + be expressed as + + .. math:: + + p_{\text{br}}(T,E) = \pi_k p_{\text{br}}(T_k,E) + \pi_{k+1} + p_{\text{br}}(T_{k+1},E), + + where the interpolation weights are + + .. math:: + + \pi_k = \frac{\ln T_{k+1} - \ln T}{\ln T_{k+1} - \ln T_k},~~~ + \pi_{k+1} = \frac{\ln T - \ln T_k}{\ln T_{k+1} - \ln T_k}. + + Sample either the index :math:`i = k` or :math:`i = k+1` according to the + point probabilities :math:`\pi_{k}` and :math:`\pi_{k+1}`. + +3. Determine the maximum value of the CDF :math:`P_{\text{br,max}}`. + +3. Sample the photon energies using the inverse transform method with the + tabulated CDF :math:`P_{\text{br}}(T_i, E)` i.e., + + .. math:: + + E = E_j \left[ (1 + a_j) \frac{\xi_2 P_{\text{br,max}} - + P_{\text{br}}(T_i, E_j)} {E_j p_{\text{br}}(T_i, E_j)} + 1 + \right]^{\frac{1}{1 + a_j}} + + where the interpolation factor :math:`a_j` is given by + + .. math:: + + a_j = \frac{\ln p_{\text{br}}(T_i,E_{j+1}) - \ln p_{\text{br}}(T_i,E_j)} + {\ln E_{j+1} - \ln E_j} + + and :math:`P_{\text{br}}(T_i, E_j) \le \xi_2 P_{\text{br,max}} \le + P_{\text{br}}(T_i, E_{j+1})`. + +We ignore the range of the electron or positron, i.e., the bremsstrahlung +photons are produced in the same location that the charged particle was +created. The direction of the photons is assumed to be the same as the +direction of the incident charged particle, which is a reasonable approximation +at higher energies when the bremsstrahlung radiation is emitted at small +angles. + + +Electron-Positron Annihilation +------------------------------ + +When a positron collides with an electron, both particles are annihilated and +generally two photons with equal energy are created. If the kinetic energy of +the positron is high enough, the two photons can have different energies, and +the higher-energy photon is emitted preferentially in the direction of flight +of the positron. It is also possible to produce a single photon if the +interaction occurs with a bound electron, and in some cases three (or, rarely, +even more) photons can be emitted. However, the annihilation cross section is +largest for low-energy positrons, and as the positron energy decreases, the +angular distribution of the emitted photons becomes isotropic. + +In OpenMC, we assume the most likely case in which a low-energy positron (which +has already lost most of its energy to bremsstrahlung radiation) interacts with +an electron which is free and at rest. Two photons with energy equal to the +electron rest mass energy :math:`m_e c^2 = 0.511` MeV are emitted isotropically +in opposite directions. + + +.. _Kaltiaisenaho: https://aaltodoc.aalto.fi/bitstream/handle/123456789/21004/master_Kaltiaisenaho_Toni_2016.pdf + +.. _Salvat: https://doi.org/10.1787/32da5043-en + +.. _Sternheimer: https://doi.org/10.1103/PhysRevB.26.6067 diff --git a/docs/source/methods/index.rst b/docs/source/methods/index.rst index 75c421c8773..121d04b1ded 100644 --- a/docs/source/methods/index.rst +++ b/docs/source/methods/index.rst @@ -14,6 +14,7 @@ Theory and Methodology random_numbers neutron_physics photon_physics + charged_particles_physics tallies eigenvalue depletion @@ -21,4 +22,4 @@ Theory and Methodology parallelization cmfd variance_reduction - random_ray \ No newline at end of file + random_ray diff --git a/docs/source/methods/photon_physics.rst b/docs/source/methods/photon_physics.rst index 22d2c7f26a0..d2bd3ac7609 100644 --- a/docs/source/methods/photon_physics.rst +++ b/docs/source/methods/photon_physics.rst @@ -667,342 +667,6 @@ and Auger electrons: 5. Repeat from step 1 for vacancy left by the transition electron. -Electron-Positron Annihilation ------------------------------- - -When a positron collides with an electron, both particles are annihilated and -generally two photons with equal energy are created. If the kinetic energy of -the positron is high enough, the two photons can have different energies, and -the higher-energy photon is emitted preferentially in the direction of flight -of the positron. It is also possible to produce a single photon if the -interaction occurs with a bound electron, and in some cases three (or, rarely, -even more) photons can be emitted. However, the annihilation cross section is -largest for low-energy positrons, and as the positron energy decreases, the -angular distribution of the emitted photons becomes isotropic. - -In OpenMC, we assume the most likely case in which a low-energy positron (which -has already lost most of its energy to bremsstrahlung radiation) interacts with -an electron which is free and at rest. Two photons with energy equal to the -electron rest mass energy :math:`m_e c^2 = 0.511` MeV are emitted isotropically -in opposite directions. - -Bremsstrahlung --------------- - -When a charged particle is decelerated in the field of an atom, some of its -kinetic energy is converted into electromagnetic radiation known as -bremsstrahlung, or 'braking radiation'. In each event, an electron or positron -with kinetic energy :math:`T` generates a photon with an energy :math:`E` -between :math:`0` and :math:`T`. Bremsstrahlung is described by a cross section -that is differential in photon energy, in the direction of the emitted photon, -and in the final direction of the charged particle. However, in Monte Carlo -simulations it is typical to integrate over the angular variables to obtain a -single differential cross section with respect to photon energy, which is often -expressed in the form - -.. math:: - :label: bremsstrahlung-dcs - - \frac{d\sigma_{\text{br}}}{dE} = \frac{Z^2}{\beta^2} \frac{1}{E} - \chi(Z, T, \kappa), - -where :math:`\kappa = E/T` is the reduced photon energy and :math:`\chi(Z, T, -\kappa)` is the scaled bremsstrahlung cross section, which is experimentally -measured. - -Because electrons are attracted to atomic nuclei whereas positrons are -repulsed, the cross section for positrons is smaller, though it approaches that -of electrons in the high energy limit. To obtain the positron cross section, we -multiply :eq:`bremsstrahlung-dcs` by the :math:`\kappa`-independent factor used -in Salvat_, - -.. math:: - :label: positron-factor - - \begin{aligned} - F_{\text{p}}(Z,T) = - & 1 - \text{exp}(-1.2359\times 10^{-1}t + 6.1274\times 10^{-2}t^2 - 3.1516\times 10^{-2}t^3 \\ - & + 7.7446\times 10^{-3}t^4 - 1.0595\times 10^{-3}t^5 + 7.0568\times 10^{-5}t^6 \\ - & - 1.8080\times 10^{-6}t^7), - \end{aligned} - -where - -.. math:: - :label: positron-factor-t - - t = \ln\left(1 + \frac{10^6}{Z^2}\frac{T}{\text{m}_\text{e}c^2} \right). - -:math:`F_{\text{p}}(Z,T)` is the ratio of the radiative stopping powers for -positrons and electrons. Stopping power describes the average energy loss per -unit path length of a charged particle as it passes through matter: - -.. math:: - :label: stopping-power - - -\frac{dT}{ds} = n \int E \frac{d\sigma}{dE} dE \equiv S(T), - -where :math:`n` is the number density of the material and :math:`d\sigma/dE` is -the cross section differential in energy loss. The total stopping power -:math:`S(T)` can be separated into two components: the radiative stopping -power :math:`S_{\text{rad}}(T)`, which refers to energy loss due to -bremsstrahlung, and the collision stopping power :math:`S_{\text{col}}(T)`, -which refers to the energy loss due to inelastic collisions with bound -electrons in the material that result in ionization and excitation. The -radiative stopping power for electrons is given by - -.. math:: - :label: radiative-stopping-power - - S_{\text{rad}}(T) = n \frac{Z^2}{\beta^2} T \int_0^1 \chi(Z,T,\kappa) - d\kappa. - - -To obtain the radiative stopping power for positrons, -:eq:`radiative-stopping-power` is multiplied by :eq:`positron-factor`. - -While the models for photon interactions with matter described above can safely -assume interactions occur with free atoms, sampling the target atom based on -the macroscopic cross sections, molecular effects cannot necessarily be -disregarded for charged particle treatment. For compounds and mixtures, the -bremsstrahlung cross section is calculated using Bragg's additivity rule as - -.. math:: - :label: material-bremsstrahlung-dcs - - \frac{d\sigma_{\text{br}}}{dE} = \frac{1}{\beta^2 E} \sum_i \gamma_i Z^2_i - \chi(Z_i, T, \kappa), - -where the sum is over the constituent elements and :math:`\gamma_i` is the -atomic fraction of the :math:`i`-th element. Similarly, the radiative stopping -power is calculated using Bragg's additivity rule as - -.. math:: - :label: material-radiative-stopping-power - - S_{\text{rad}}(T) = \sum_i w_i S_{\text{rad},i}(T), - -where :math:`w_i` is the mass fraction of the :math:`i`-th element and -:math:`S_{\text{rad},i}(T)` is found for element :math:`i` using -:eq:`radiative-stopping-power`. The collision stopping power, however, is a -function of certain quantities such as the mean excitation energy :math:`I` and -the density effect correction :math:`\delta_F` that depend on molecular -properties. These quantities cannot simply be summed over constituent elements -in a compound, but should instead be calculated for the material. The Bethe -formula can be used to find the collision stopping power of the material: - -.. math:: - :label: material-collision-stopping-power - - S_{\text{col}}(T) = \frac{2 \pi r_e^2 m_e c^2}{\beta^2} N_A \frac{Z}{A_M} - [\ln(T^2/I^2) + \ln(1 + \tau/2) + F(\tau) - \delta_F(T)], - -where :math:`N_A` is Avogadro's number, :math:`A_M` is the molar mass, -:math:`\tau = T/m_e`, and :math:`F(\tau)` depends on the particle type. For -electrons, - -.. math:: - :label: F-electron - - F_{-}(\tau) = (1 - \beta^2)[1 + \tau^2/8 - (2\tau + 1) \ln2], - -while for positrons - -.. math:: - :label: F-positron - - F_{+}(\tau) = 2\ln2 - (\beta^2/12)[23 + 14/(\tau + 2) + 10/(\tau + 2)^2 + - 4/(\tau + 2)^3]. - -The density effect correction :math:`\delta_F` takes into account the reduction -of the collision stopping power due to the polarization of the material the -charged particle is passing through by the electric field of the particle. -It can be evaluated using the method described by Sternheimer_, where the -equation for :math:`\delta_F` is - -.. math:: - :label: density-effect-correction - - \delta_F(\beta) = \sum_{i=1}^n f_i \ln[(l_i^2 + l^2)/l_i^2] - - l^2(1-\beta^2). - -Here, :math:`f_i` is the oscillator strength of the :math:`i`-th transition, -given by :math:`f_i = n_i/Z`, where :math:`n_i` is the number of electrons in -the :math:`i`-th subshell. The frequency :math:`l` is the solution of the -equation - -.. math:: - :label: density-effect-l - - \frac{1}{\beta^2} - 1 = \sum_{i=1}^{n} \frac{f_i}{\bar{\nu}_i^2 + l^2}, - -where :math:`\bar{v}_i` is defined as - -.. math:: - :label: density-effect-nubar - - \bar{\nu}_i = h\nu_i \rho / h\nu_p. - -The plasma energy :math:`h\nu_p` of the medium is given by - -.. math:: - :label: plasma-frequency - - h\nu_p = \sqrt{\frac{(hc)^2 r_e \rho_m N_A Z}{\pi A}}, - -where :math:`A` is the atomic weight and :math:`\rho_m` is the density of the -material. In :eq:`density-effect-nubar`, :math:`h\nu_i` is the oscillator -energy, and :math:`\rho` is an adjustment factor introduced to give agreement -between the experimental values of the oscillator energies and the mean -excitation energy. The :math:`l_i` in :eq:`density-effect-correction` are -defined as - -.. math:: - :label: density-effect-li - - \begin{aligned} - l_i &= (\bar{\nu}_i^2 + 2/3f_i)^{1/2} ~~~~&\text{for}~~ \bar{\nu}_i > 0 \\ - l_n &= f_n^{1/2} ~~~~&\text{for}~~ \bar{\nu}_n = 0, - \end{aligned} - -where the second case applies to conduction electrons. For a conductor, -:math:`f_n` is given by :math:`n_c/Z`, where :math:`n_c` is the effective -number of conduction electrons, and :math:`v_n = 0`. The adjustment factor -:math:`\rho` is determined using the equation for the mean excitation energy: - -.. math:: - :label: mean-excitation-energy - - \ln I = \sum_{i=1}^{n-1} f_i \ln[(h\nu_i\rho)^2 + 2/3f_i(h\nu_p)^2]^{1/2} + - f_n \ln (h\nu_pf_n^{1/2}). - -.. _ttb: - -Thick-Target Bremsstrahlung Approximation -+++++++++++++++++++++++++++++++++++++++++ - -Since charged particles lose their energy on a much shorter distance scale than -neutral particles, not much error should be introduced by neglecting to -transport electrons. However, the bremsstrahlung emitted from high energy -electrons and positrons can travel far from the interaction site. Thus, even -without a full electron transport mode it is necessary to model bremsstrahlung. -We use a thick-target bremsstrahlung (TTB) approximation based on the models in -Salvat_ and Kaltiaisenaho_ for generating bremsstrahlung photons, which assumes -the charged particle loses all its energy in a single homogeneous material -region. - -To model bremsstrahlung using the TTB approximation, we need to know the number -of photons emitted by the charged particle and the energy distribution of the -photons. These quantities can be calculated using the continuous slowing down -approximation (CSDA). The CSDA assumes charged particles lose energy -continuously along their trajectory with a rate of energy loss equal to the -total stopping power, ignoring fluctuations in the energy loss. The -approximation is useful for expressing average quantities that describe how -charged particles slow down in matter. For example, the CSDA range approximates -the average path length a charged particle travels as it slows to rest: - -.. math:: - :label: csda-range - - R(T) = \int^T_0 \frac{dT'}{S(T')}. - -Actual path lengths will fluctuate around :math:`R(T)`. The average number of -photons emitted per unit path length is given by the inverse bremsstrahlung -mean free path: - -.. math:: - :label: inverse-bremsstrahlung-mfp - - \lambda_{\text{br}}^{-1}(T,E_{\text{cut}}) - = n\int_{E_{\text{cut}}}^T\frac{d\sigma_{\text{br}}}{dE}dE - = n\frac{Z^2}{\beta^2}\int_{\kappa_{\text{cut}}}^1\frac{1}{\kappa} - \chi(Z,T,\kappa)d\kappa. - -The lower limit of the integral in :eq:`inverse-bremsstrahlung-mfp` is non-zero -because the bremsstrahlung differential cross section diverges for small photon -energies but is finite for photon energies above some cutoff energy -:math:`E_{\text{cut}}`. The mean free path -:math:`\lambda_{\text{br}}^{-1}(T,E_{\text{cut}})` is used to calculate the -photon number yield, defined as the average number of photons emitted with -energy greater than :math:`E_{\text{cut}}` as the charged particle slows down -from energy :math:`T` to :math:`E_{\text{cut}}`. The photon number yield is -given by - -.. math:: - :label: photon-number-yield - - Y(T,E_{\text{cut}}) = \int^{R(T)}_{R(E_{\text{cut}})} - \lambda_{\text{br}}^{-1}(T',E_{\text{cut}})ds = \int_{E_{\text{cut}}}^T - \frac{\lambda_{\text{br}}^{-1}(T',E_{\text{cut}})}{S(T')}dT'. - -:math:`Y(T,E_{\text{cut}})` can be used to construct the energy spectrum of -bremsstrahlung photons: the number of photons created with energy between -:math:`E_1` and :math:`E_2` by a charged particle with initial kinetic energy -:math:`T` as it comes to rest is given by :math:`Y(T,E_1) - Y(T,E_2)`. - -To simulate the emission of bremsstrahlung photons, the total stopping power -and bremsstrahlung differential cross section for positrons and electrons must -be calculated for a given material using :eq:`material-bremsstrahlung-dcs` and -:eq:`material-radiative-stopping-power`. These quantities are used to build the -tabulated bremsstrahlung energy PDF and CDF for that material for each incident -energy :math:`T_k` on the energy grid. The following algorithm is then applied -to sample the photon energies: - -1. For an incident charged particle with energy :math:`T`, sample the number of - emitted photons as - - .. math:: - - N = \lfloor Y(T,E_{\text{cut}}) + \xi_1 \rfloor. - -2. Rather than interpolate the PDF between indices :math:`k` and :math:`k+1` - for which :math:`T_k < T < T_{k+1}`, which is computationally expensive, use - the composition method and sample from the PDF at either :math:`k` or - :math:`k+1`. Using linear interpolation on a logarithmic scale, the PDF can - be expressed as - - .. math:: - - p_{\text{br}}(T,E) = \pi_k p_{\text{br}}(T_k,E) + \pi_{k+1} - p_{\text{br}}(T_{k+1},E), - - where the interpolation weights are - - .. math:: - - \pi_k = \frac{\ln T_{k+1} - \ln T}{\ln T_{k+1} - \ln T_k},~~~ - \pi_{k+1} = \frac{\ln T - \ln T_k}{\ln T_{k+1} - \ln T_k}. - - Sample either the index :math:`i = k` or :math:`i = k+1` according to the - point probabilities :math:`\pi_{k}` and :math:`\pi_{k+1}`. - -3. Determine the maximum value of the CDF :math:`P_{\text{br,max}}`. - -3. Sample the photon energies using the inverse transform method with the - tabulated CDF :math:`P_{\text{br}}(T_i, E)` i.e., - - .. math:: - - E = E_j \left[ (1 + a_j) \frac{\xi_2 P_{\text{br,max}} - - P_{\text{br}}(T_i, E_j)} {E_j p_{\text{br}}(T_i, E_j)} + 1 - \right]^{\frac{1}{1 + a_j}} - - where the interpolation factor :math:`a_j` is given by - - .. math:: - - a_j = \frac{\ln p_{\text{br}}(T_i,E_{j+1}) - \ln p_{\text{br}}(T_i,E_j)} - {\ln E_{j+1} - \ln E_j} - - and :math:`P_{\text{br}}(T_i, E_j) \le \xi_2 P_{\text{br,max}} \le - P_{\text{br}}(T_i, E_{j+1})`. - -We ignore the range of the electron or positron, i.e., the bremsstrahlung -photons are produced in the same location that the charged particle was -created. The direction of the photons is assumed to be the same as the -direction of the incident charged particle, which is a reasonable approximation -at higher energies when the bremsstrahlung radiation is emitted at small -angles. .. _photon_production: @@ -1070,5 +734,3 @@ emitted photon. .. _Kaltiaisenaho: https://aaltodoc.aalto.fi/bitstream/handle/123456789/21004/master_Kaltiaisenaho_Toni_2016.pdf .. _Salvat: https://doi.org/10.1787/32da5043-en - -.. _Sternheimer: https://doi.org/10.1103/PhysRevB.26.6067 diff --git a/include/openmc/nuclide.h b/include/openmc/nuclide.h index 329c776d032..60b88a153bc 100644 --- a/include/openmc/nuclide.h +++ b/include/openmc/nuclide.h @@ -164,8 +164,8 @@ namespace data { // Minimum/maximum transport energy for each particle type. Order corresponds to // that of the ParticleType enum -extern array energy_min; -extern array energy_max; +extern array energy_min; +extern array energy_max; //! Minimum temperature in [K] that nuclide data is available at extern double temperature_min; diff --git a/openmc/source.py b/openmc/source.py index c463ccb2756..9b730cf1de2 100644 --- a/openmc/source.py +++ b/openmc/source.py @@ -260,7 +260,7 @@ class IndependentSource(SourceBase): time distribution of source sites strength : float Strength of the source - particle : {'neutron', 'photon'} + particle : {'neutron', 'photon', 'electron', 'positron'} Source particle type domains : iterable of openmc.Cell, openmc.Material, or openmc.Universe Domains to reject based on, i.e., if a sampled spatial location is not @@ -299,7 +299,7 @@ class IndependentSource(SourceBase): .. versionadded:: 0.14.0 - particle : {'neutron', 'photon'} + particle : {'neutron', 'photon', 'electron', 'positron'} Source particle type constraints : dict Constraints on sampled source particles. Valid keys include @@ -404,7 +404,8 @@ def particle(self): @particle.setter def particle(self, particle): - cv.check_value('source particle', particle, ['neutron', 'photon']) + cv.check_value('source particle', particle, + ['neutron', 'photon', 'electron', 'positron']) self._particle = particle def populate_xml_element(self, element): diff --git a/src/finalize.cpp b/src/finalize.cpp index 76babbb96c6..659f390b39a 100644 --- a/src/finalize.cpp +++ b/src/finalize.cpp @@ -156,8 +156,8 @@ int openmc_finalize() simulation::entropy_mesh = nullptr; simulation::ufs_mesh = nullptr; - data::energy_max = {INFTY, INFTY}; - data::energy_min = {0.0, 0.0}; + data::energy_max = {INFTY, INFTY, INFTY, INFTY}; + data::energy_min = {0.0, 0.0, 0.0, 0.0}; data::temperature_min = 0.0; data::temperature_max = INFTY; model::root_universe = -1; diff --git a/src/nuclide.cpp b/src/nuclide.cpp index 7cb84640d62..5ae6e30ee24 100644 --- a/src/nuclide.cpp +++ b/src/nuclide.cpp @@ -31,8 +31,8 @@ namespace openmc { //============================================================================== namespace data { -array energy_min {0.0, 0.0}; -array energy_max {INFTY, INFTY}; +array energy_min {0.0, 0.0, 0.0, 0.0}; +array energy_max {INFTY, INFTY, INFTY, INFTY}; double temperature_min {INFTY}; double temperature_max {0.0}; std::unordered_map nuclide_map; diff --git a/src/particle.cpp b/src/particle.cpp index 06ccf462a18..6b4c332a8b2 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -232,7 +232,7 @@ void Particle::event_advance() // Sample a distance to collision if (type() == ParticleType::electron || type() == ParticleType::positron) { - collision_distance() = 0.0; + collision_distance() = material() == MATERIAL_VOID ? INFINITY : 0.0; } else if (macro_xs().total == 0.0) { collision_distance() = INFINITY; } else { diff --git a/src/simulation.cpp b/src/simulation.cpp index b9986f47375..f55546c4ce8 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -674,8 +674,9 @@ void calculate_work() void initialize_data() { // Determine minimum/maximum energy for incident neutron/photon data - data::energy_max = {INFTY, INFTY}; - data::energy_min = {0.0, 0.0}; + data::energy_max = {INFTY, INFTY, INFTY, INFTY}; + data::energy_min = {0.0, 0.0, 0.0, 0.0}; + for (const auto& nuc : data::nuclides) { if (nuc->grid_.size() >= 1) { int neutron = static_cast(ParticleType::neutron); @@ -703,11 +704,21 @@ void initialize_data() // than the current minimum/maximum if (data::ttb_e_grid.size() >= 1) { int photon = static_cast(ParticleType::photon); + int electron = static_cast(ParticleType::electron); + int positron = static_cast(ParticleType::positron); int n_e = data::ttb_e_grid.size(); + + const std::vector charged = {electron, positron}; + for (auto t : charged) { + data::energy_min[t] = std::exp(data::ttb_e_grid(1)); + data::energy_max[t] = std::exp(data::ttb_e_grid(n_e - 1)); + } + data::energy_min[photon] = - std::max(data::energy_min[photon], std::exp(data::ttb_e_grid(1))); - data::energy_max[photon] = std::min( - data::energy_max[photon], std::exp(data::ttb_e_grid(n_e - 1))); + std::max(data::energy_min[photon], data::energy_min[electron]); + + data::energy_max[photon] = + std::min(data::energy_max[photon], data::energy_max[electron]); } } } diff --git a/src/source.cpp b/src/source.cpp index 12323f7bd7e..f6aa665ebd3 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -290,6 +290,12 @@ IndependentSource::IndependentSource(pugi::xml_node node) : Source(node) } else if (temp_str == "photon") { particle_ = ParticleType::photon; settings::photon_transport = true; + } else if (temp_str == "electron") { + particle_ = ParticleType::electron; + settings::photon_transport = true; + } else if (temp_str == "positron") { + particle_ = ParticleType::positron; + settings::photon_transport = true; } else { fatal_error(std::string("Unknown source particle type: ") + temp_str); } diff --git a/tests/regression_tests/electron_heating/__init__.py b/tests/regression_tests/electron_heating/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/electron_heating/inputs_true.dat b/tests/regression_tests/electron_heating/inputs_true.dat new file mode 100644 index 00000000000..ec8e5a83764 --- /dev/null +++ b/tests/regression_tests/electron_heating/inputs_true.dat @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + fixed source + 10000 + 1 + + + 10000000.0 1.0 + + + + 1000.0 + + + + + heating + + + diff --git a/tests/regression_tests/electron_heating/results_true.dat b/tests/regression_tests/electron_heating/results_true.dat new file mode 100644 index 00000000000..4f54ceaa4d5 --- /dev/null +++ b/tests/regression_tests/electron_heating/results_true.dat @@ -0,0 +1,3 @@ +tally 1: +1.000000E+07 +1.000000E+14 diff --git a/tests/regression_tests/electron_heating/test.py b/tests/regression_tests/electron_heating/test.py new file mode 100644 index 00000000000..e7a58560c49 --- /dev/null +++ b/tests/regression_tests/electron_heating/test.py @@ -0,0 +1,40 @@ +import pytest +import openmc + +from tests.testing_harness import PyAPITestHarness + + +@pytest.fixture +def water_model(): + # Define materals and geometry + water = openmc.Material() + water.add_nuclide("H1", 2.0) + water.add_nuclide("O16", 1.0) + water.set_density("g/cc", 1.0) + sphere = openmc.Sphere(r=1.0, boundary_type="reflective") + sph = openmc.Cell(fill=water, region=-sphere) + geometry = openmc.Geometry([sph]) + source = openmc.IndependentSource( + energy=openmc.stats.delta_function(10.0e6), + particle="electron" + ) + + # Define settings + settings = openmc.Settings() + settings.particles = 10000 + settings.batches = 1 + settings.cutoff = {"energy_photon": 1000.0} + settings.run_mode = "fixed source" + settings.source = source + + # Define tallies + tally = openmc.Tally() + tally.scores = ["heating"] + tallies = openmc.Tallies([tally]) + + return openmc.Model(geometry=geometry, settings=settings, tallies=tallies) + + +def test_electron_heating_calc(water_model): + harness = PyAPITestHarness("statepoint.1.h5", water_model) + harness.main() From 055ea15a2ddf40f6fff4eef52990e79b37652f64 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 17 Oct 2025 18:09:37 -0500 Subject: [PATCH 131/159] Clip mixture distributions based on mean times integral (#3603) --- openmc/stats/univariate.py | 76 ++++++++++++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 12 deletions(-) diff --git a/openmc/stats/univariate.py b/openmc/stats/univariate.py index d6cf19f2b87..c48cc00757c 100644 --- a/openmc/stats/univariate.py +++ b/openmc/stats/univariate.py @@ -295,6 +295,20 @@ def integral(self): """ return np.sum(self.p) + def mean(self) -> float: + """Return mean of the discrete distribution + + The mean is the weighted average of the discrete values. + + .. versionadded:: 0.15.3 + + Returns + ------- + float + Mean of discrete distribution + """ + return np.sum(self.x * self.p) / np.sum(self.p) + def clip(self, tolerance: float = 1e-6, inplace: bool = False) -> Discrete: r"""Remove low-importance points from discrete distribution. @@ -413,6 +427,18 @@ def sample(self, n_samples=1, seed=None): rng = np.random.RandomState(seed) return rng.uniform(self.a, self.b, n_samples) + def mean(self) -> float: + """Return mean of the uniform distribution + + .. versionadded:: 0.15.3 + + Returns + ------- + float + Mean of uniform distribution + """ + return 0.5 * (self.a + self.b) + def to_xml_element(self, element_name: str): """Return XML representation of the uniform distribution @@ -1123,7 +1149,7 @@ def from_xml_element(cls, elem: ET.Element): """ interpolation = get_text(elem, 'interpolation') - params = get_elem_list(elem, "parameters", float) + params = get_elem_list(elem, "parameters", float) m = (len(params) + 1)//2 # +1 for when len(params) is odd x = params[:m] p = params[m:] @@ -1347,6 +1373,30 @@ def integral(self): for p, dist in zip(self.probability, self.distribution) ]) + def mean(self) -> float: + """Return mean of the mixture distribution + + The mean is the weighted average of the means of the component + distributions, weighted by probability * integral. + + .. versionadded:: 0.15.3 + + Returns + ------- + float + Mean of the mixture distribution + """ + # Weight each component by its probability and integral + weights = [p*dist.integral() for p, dist in + zip(self.probability, self.distribution)] + total_weight = sum(weights) + + if total_weight == 0: + return 0.0 + + return sum([w*dist.mean() for w, dist in + zip(weights, self.distribution)]) / total_weight + def clip(self, tolerance: float = 1e-6, inplace: bool = False) -> Mixture: r"""Remove low-importance points / distributions @@ -1369,14 +1419,14 @@ def clip(self, tolerance: float = 1e-6, inplace: bool = False) -> Mixture: Distribution with low-importance points / distributions removed """ - # Determine integral of original distribution to compare later - original_integral = self.integral() + # Calculate mean * integral for original distribution to compare later. + original_mean_integral = self.mean() * self.integral() # Determine indices for any distributions that contribute non-negligibly - # to overall intensity - intensities = [prob*dist.integral() for prob, dist in - zip(self.probability, self.distribution)] - indices = _intensity_clip(intensities, tolerance=tolerance) + # to overall mean * integral + mean_integrals = [prob*dist.mean()*dist.integral() for prob, dist in + zip(self.probability, self.distribution)] + indices = _intensity_clip(mean_integrals, tolerance=tolerance) # Clip mixture of distributions probability = self.probability[indices] @@ -1397,12 +1447,14 @@ def clip(self, tolerance: float = 1e-6, inplace: bool = False) -> Mixture: # Create new distribution new_dist = type(self)(probability, distribution) - # Show warning if integral of new distribution is not within - # tolerance of original - diff = (original_integral - new_dist.integral())/original_integral + # Show warning if mean * integral of new distribution is not within + # tolerance of original. For energy distributions, mean * integral + # represents total energy. + new_mean_integral = new_dist.mean() * new_dist.integral() + diff = (original_mean_integral - new_mean_integral)/original_mean_integral if diff > tolerance: - warn("Clipping mixture distribution resulted in an integral that is " - f"lower by a fraction of {diff} when tolerance={tolerance}.") + warn("Clipping mixture distribution resulted in a mean*integral " + f"that is lower by a fraction of {diff} when tolerance={tolerance}.") return new_dist From 3ac5d6f8f540a0bfbe53b8264ae1d691093853ec Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Mon, 20 Oct 2025 18:45:14 +0200 Subject: [PATCH 132/159] Allow Path objects in MGXSLibrary.export_to_hdf5 (#3608) Co-authored-by: Paul Romano --- openmc/mgxs_library.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openmc/mgxs_library.py b/openmc/mgxs_library.py index b840563cffc..ce86f97dfb2 100644 --- a/openmc/mgxs_library.py +++ b/openmc/mgxs_library.py @@ -11,7 +11,7 @@ import openmc.mgxs from openmc.mgxs import SCATTER_TABULAR, SCATTER_LEGENDRE, SCATTER_HISTOGRAM from .checkvalue import check_type, check_value, check_greater_than, \ - check_iterable_type, check_less_than, check_filetype_version + check_iterable_type, check_less_than, check_filetype_version, PathLike ROOM_TEMPERATURE_KELVIN = 294.0 @@ -2506,7 +2506,7 @@ def export_to_hdf5(self, filename='mgxs.h5', libver='earliest'): Parameters ---------- - filename : str + filename : str or PathLike Filename of file, default is mgxs.h5. libver : {'earliest', 'latest'} Compatibility mode for the HDF5 file. 'latest' will produce files @@ -2514,7 +2514,7 @@ def export_to_hdf5(self, filename='mgxs.h5', libver='earliest'): """ - check_type('filename', filename, str) + check_type('filename', filename, PathLike) # Create and write to the HDF5 file file = h5py.File(filename, "w", libver=libver) From c31032cf25f5a8c839c20fdb1017cbbf6de88278 Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Wed, 22 Oct 2025 02:03:27 +0300 Subject: [PATCH 133/159] load mesh objects from weight_windows.h5 file (#3598) Co-authored-by: Patrick Shriwise --- include/openmc/mesh.h | 22 ++ src/mesh.cpp | 363 ++++++++++++++++++++++------ src/weight_windows.cpp | 4 + tests/cpp_unit_tests/CMakeLists.txt | 1 + tests/cpp_unit_tests/test_mesh.cpp | 257 ++++++++++++++++++++ 5 files changed, 576 insertions(+), 71 deletions(-) create mode 100644 tests/cpp_unit_tests/test_mesh.cpp diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index a56705c9ec5..5c9272e93b9 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -132,8 +132,14 @@ class Mesh { // Constructors and destructor Mesh() = default; Mesh(pugi::xml_node node); + Mesh(hid_t group); virtual ~Mesh() = default; + // Factory method for creating meshes from either an XML node or HDF5 group + template + static const std::unique_ptr& create( + T dataset, const std::string& mesh_type, const std::string& mesh_library); + // Methods //! Perform any preparation needed to support point location within the mesh virtual void prepare_for_point_location() {}; @@ -258,6 +264,7 @@ class StructuredMesh : public Mesh { public: StructuredMesh() = default; StructuredMesh(pugi::xml_node node) : Mesh {node} {}; + StructuredMesh(hid_t group) : Mesh {group} {}; virtual ~StructuredMesh() = default; using MeshIndex = std::array; @@ -423,6 +430,7 @@ class PeriodicStructuredMesh : public StructuredMesh { public: PeriodicStructuredMesh() = default; PeriodicStructuredMesh(pugi::xml_node node) : StructuredMesh {node} {}; + PeriodicStructuredMesh(hid_t group) : StructuredMesh {group} {}; Position local_coords(const Position& r) const override { @@ -442,6 +450,7 @@ class RegularMesh : public StructuredMesh { // Constructors RegularMesh() = default; RegularMesh(pugi::xml_node node); + RegularMesh(hid_t group); // Overridden methods int get_index_in_direction(double r, int i) const override; @@ -481,6 +490,8 @@ class RegularMesh : public StructuredMesh { //! Return the volume for a given mesh index double volume(const MeshIndex& ijk) const override; + int set_grid(); + // Data members double volume_frac_; //!< Volume fraction of each mesh element double element_volume_; //!< Volume of each mesh element @@ -492,6 +503,7 @@ class RectilinearMesh : public StructuredMesh { // Constructors RectilinearMesh() = default; RectilinearMesh(pugi::xml_node node); + RectilinearMesh(hid_t group); // Overridden methods int get_index_in_direction(double r, int i) const override; @@ -534,6 +546,7 @@ class CylindricalMesh : public PeriodicStructuredMesh { // Constructors CylindricalMesh() = default; CylindricalMesh(pugi::xml_node node); + CylindricalMesh(hid_t group); // Overridden methods virtual MeshIndex get_indices(Position r, bool& in_mesh) const override; @@ -598,6 +611,7 @@ class SphericalMesh : public PeriodicStructuredMesh { // Constructors SphericalMesh() = default; SphericalMesh(pugi::xml_node node); + SphericalMesh(hid_t group); // Overridden methods virtual MeshIndex get_indices(Position r, bool& in_mesh) const override; @@ -668,6 +682,7 @@ class UnstructuredMesh : public Mesh { // Constructors UnstructuredMesh() { n_dimension_ = 3; }; UnstructuredMesh(pugi::xml_node node); + UnstructuredMesh(hid_t group); static const std::string mesh_type; virtual std::string get_mesh_type() const override; @@ -774,6 +789,7 @@ class MOABMesh : public UnstructuredMesh { // Constructors MOABMesh() = default; MOABMesh(pugi::xml_node); + MOABMesh(hid_t group); MOABMesh(const std::string& filename, double length_multiplier = 1.0); MOABMesh(std::shared_ptr external_mbi); @@ -943,6 +959,7 @@ class LibMesh : public UnstructuredMesh { public: // Constructors LibMesh(pugi::xml_node node); + LibMesh(hid_t group); LibMesh(const std::string& filename, double length_multiplier = 1.0); LibMesh(libMesh::MeshBase& input_mesh, double length_multiplier = 1.0); @@ -1069,6 +1086,11 @@ class AdaptiveLibMesh : public LibMesh { //! \param[in] root XML node void read_meshes(pugi::xml_node root); +//! Read meshes from an HDF5 file +// +//! \param[in] group HDF5 group ("meshes" group) +void read_meshes(hid_t group); + //! Write mesh data to an HDF5 group // //! \param[in] group HDF5 group diff --git a/src/mesh.cpp b/src/mesh.cpp index 58d218b9cad..610d057cf08 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -230,6 +230,42 @@ void MaterialVolumes::add_volume_unsafe( // Mesh implementation //============================================================================== +template +const std::unique_ptr& Mesh::create( + T dataset, const std::string& mesh_type, const std::string& mesh_library) +{ + // Determine mesh type. Add to model vector and map + if (mesh_type == RegularMesh::mesh_type) { + model::meshes.push_back(make_unique(dataset)); + } else if (mesh_type == RectilinearMesh::mesh_type) { + model::meshes.push_back(make_unique(dataset)); + } else if (mesh_type == CylindricalMesh::mesh_type) { + model::meshes.push_back(make_unique(dataset)); + } else if (mesh_type == SphericalMesh::mesh_type) { + model::meshes.push_back(make_unique(dataset)); +#ifdef OPENMC_DAGMC_ENABLED + } else if (mesh_type == UnstructuredMesh::mesh_type && + mesh_library == MOABMesh::mesh_lib_type) { + model::meshes.push_back(make_unique(dataset)); +#endif +#ifdef OPENMC_LIBMESH_ENABLED + } else if (mesh_type == UnstructuredMesh::mesh_type && + mesh_library == LibMesh::mesh_lib_type) { + model::meshes.push_back(make_unique(dataset)); +#endif + } else if (mesh_type == UnstructuredMesh::mesh_type) { + fatal_error("Unstructured mesh support is not enabled or the mesh " + "library is invalid."); + } else { + fatal_error(fmt::format("Invalid mesh type: {}", mesh_type)); + } + + // Map ID to position in vector + model::mesh_map[model::meshes.back()->id_] = model::meshes.size() - 1; + + return model::meshes.back(); +} + Mesh::Mesh(pugi::xml_node node) { // Read mesh id @@ -238,6 +274,17 @@ Mesh::Mesh(pugi::xml_node node) name_ = get_node_value(node, "name"); } +Mesh::Mesh(hid_t group) +{ + // Read mesh ID + read_attribute(group, "id", id_); + + // Read mesh name + if (object_exists(group, "name")) { + read_dataset(group, "name", name_); + } +} + void Mesh::set_id(int32_t id) { assert(id >= 0 || id == C_NONE); @@ -265,7 +312,13 @@ void Mesh::set_id(int32_t id) // Update ID and entry in the mesh map id_ = id; - model::mesh_map[id] = model::meshes.size() - 1; + + // find the index of this mesh in the model::meshes vector + // (search in reverse because this mesh was likely just added to the vector) + auto it = std::find_if(model::meshes.rbegin(), model::meshes.rend(), + [this](const std::unique_ptr& mesh) { return mesh.get() == this; }); + + model::mesh_map[id] = std::distance(model::meshes.begin(), it.base()) - 1; } vector Mesh::volumes() const @@ -627,6 +680,46 @@ UnstructuredMesh::UnstructuredMesh(pugi::xml_node node) : Mesh(node) } } +UnstructuredMesh::UnstructuredMesh(hid_t group) : Mesh(group) +{ + n_dimension_ = 3; + + // check the mesh type + if (object_exists(group, "type")) { + std::string temp; + read_dataset(group, "type", temp); + if (temp != mesh_type) { + fatal_error(fmt::format("Invalid mesh type: {}", temp)); + } + } + + // check if a length unit multiplier was specified + if (object_exists(group, "length_multiplier")) { + read_dataset(group, "length_multiplier", length_multiplier_); + } + + // get the filename of the unstructured mesh to load + if (object_exists(group, "filename")) { + read_dataset(group, "filename", filename_); + if (!file_exists(filename_)) { + fatal_error("Mesh file '" + filename_ + "' does not exist!"); + } + } else { + fatal_error(fmt::format( + "No filename supplied for unstructured mesh with ID: {}", id_)); + } + + if (attribute_exists(group, "options")) { + read_attribute(group, "options", options_); + } + + // check if mesh tally data should be written with + // statepoint files + if (attribute_exists(group, "output")) { + read_attribute(group, "output", output_); + } +} + void UnstructuredMesh::determine_bounds() { double xmin = INFTY; @@ -1086,6 +1179,72 @@ void StructuredMesh::surface_bins_crossed( // RegularMesh implementation //============================================================================== +int RegularMesh::set_grid() +{ + auto shape = xt::adapt(shape_, {n_dimension_}); + + // Check that dimensions are all greater than zero + if (xt::any(shape <= 0)) { + set_errmsg("All entries for a regular mesh dimensions " + "must be positive."); + return OPENMC_E_INVALID_ARGUMENT; + } + + // Make sure lower_left and dimension match + if (lower_left_.size() != n_dimension_) { + set_errmsg("Number of entries in lower_left must be the same " + "as the regular mesh dimensions."); + return OPENMC_E_INVALID_ARGUMENT; + } + if (width_.size() > 0) { + + // Check to ensure width has same dimensions + if (width_.size() != n_dimension_) { + set_errmsg("Number of entries on width must be the same as " + "the regular mesh dimensions."); + return OPENMC_E_INVALID_ARGUMENT; + } + + // Check for negative widths + if (xt::any(width_ < 0.0)) { + set_errmsg("Cannot have a negative width on a regular mesh."); + return OPENMC_E_INVALID_ARGUMENT; + } + + // Set width and upper right coordinate + upper_right_ = xt::eval(lower_left_ + shape * width_); + + } else if (upper_right_.size() > 0) { + + // Check to ensure upper_right_ has same dimensions + if (upper_right_.size() != n_dimension_) { + set_errmsg("Number of entries on upper_right must be the " + "same as the regular mesh dimensions."); + return OPENMC_E_INVALID_ARGUMENT; + } + + // Check that upper-right is above lower-left + if (xt::any(upper_right_ < lower_left_)) { + set_errmsg( + "The upper_right coordinates of a regular mesh must be greater than " + "the lower_left coordinates."); + return OPENMC_E_INVALID_ARGUMENT; + } + + // Set width + width_ = xt::eval((upper_right_ - lower_left_) / shape); + } + + // Set material volumes + volume_frac_ = 1.0 / xt::prod(shape)(); + + element_volume_ = 1.0; + for (int i = 0; i < n_dimension_; i++) { + element_volume_ *= width_[i]; + } + return 0; +} + RegularMesh::RegularMesh(pugi::xml_node node) : StructuredMesh {node} { // Determine number of dimensions for mesh @@ -1100,12 +1259,6 @@ RegularMesh::RegularMesh(pugi::xml_node node) : StructuredMesh {node} } std::copy(shape.begin(), shape.end(), shape_.begin()); - // Check that dimensions are all greater than zero - if (xt::any(shape <= 0)) { - fatal_error("All entries on the element for a tally " - "mesh must be positive."); - } - // Check for lower-left coordinates if (check_for_node(node, "lower_left")) { // Read mesh lower-left corner location @@ -1114,12 +1267,6 @@ RegularMesh::RegularMesh(pugi::xml_node node) : StructuredMesh {node} fatal_error("Must specify on a mesh."); } - // Make sure lower_left and dimension match - if (shape.size() != lower_left_.size()) { - fatal_error("Number of entries on must be the same " - "as the number of entries on ."); - } - if (check_for_node(node, "width")) { // Make sure one of upper-right or width were specified if (check_for_node(node, "upper_right")) { @@ -1128,49 +1275,52 @@ RegularMesh::RegularMesh(pugi::xml_node node) : StructuredMesh {node} width_ = get_node_xarray(node, "width"); - // Check to ensure width has same dimensions - auto n = width_.size(); - if (n != lower_left_.size()) { - fatal_error("Number of entries on must be the same as " - "the number of entries on ."); - } + } else if (check_for_node(node, "upper_right")) { - // Check for negative widths - if (xt::any(width_ < 0.0)) { - fatal_error("Cannot have a negative on a tally mesh."); - } + upper_right_ = get_node_xarray(node, "upper_right"); - // Set width and upper right coordinate - upper_right_ = xt::eval(lower_left_ + shape * width_); + } else { + fatal_error("Must specify either or on a mesh."); + } - } else if (check_for_node(node, "upper_right")) { - upper_right_ = get_node_xarray(node, "upper_right"); + if (int err = set_grid()) { + fatal_error(openmc_err_msg); + } +} - // Check to ensure width has same dimensions - auto n = upper_right_.size(); - if (n != lower_left_.size()) { - fatal_error("Number of entries on must be the " - "same as the number of entries on ."); - } +RegularMesh::RegularMesh(hid_t group) : StructuredMesh {group} +{ + // Determine number of dimensions for mesh + if (!object_exists(group, "dimension")) { + fatal_error("Must specify on a regular mesh."); + } - // Check that upper-right is above lower-left - if (xt::any(upper_right_ < lower_left_)) { - fatal_error("The coordinates must be greater than " - "the coordinates on a tally mesh."); - } + xt::xtensor shape; + read_dataset(group, "dimension", shape); + int n = n_dimension_ = shape.size(); + if (n != 1 && n != 2 && n != 3) { + fatal_error("Mesh must be one, two, or three dimensions."); + } + std::copy(shape.begin(), shape.end(), shape_.begin()); - // Set width - width_ = xt::eval((upper_right_ - lower_left_) / shape); + // Check for lower-left coordinates + if (object_exists(group, "lower_left")) { + // Read mesh lower-left corner location + read_dataset(group, "lower_left", lower_left_); } else { - fatal_error("Must specify either or on a mesh."); + fatal_error("Must specify lower_left dataset on a mesh."); } - // Set material volumes - volume_frac_ = 1.0 / xt::prod(shape)(); + if (object_exists(group, "upper_right")) { - element_volume_ = 1.0; - for (int i = 0; i < n_dimension_; i++) { - element_volume_ *= width_[i]; + read_dataset(group, "upper_right", upper_right_); + + } else { + fatal_error("Must specify either upper_right dataset on a mesh."); + } + + if (int err = set_grid()) { + fatal_error(openmc_err_msg); } } @@ -1343,6 +1493,19 @@ RectilinearMesh::RectilinearMesh(pugi::xml_node node) : StructuredMesh {node} } } +RectilinearMesh::RectilinearMesh(hid_t group) : StructuredMesh {group} +{ + n_dimension_ = 3; + + read_dataset(group, "x_grid", grid_[0]); + read_dataset(group, "y_grid", grid_[1]); + read_dataset(group, "z_grid", grid_[2]); + + if (int err = set_grid()) { + fatal_error(openmc_err_msg); + } +} + const std::string RectilinearMesh::mesh_type = "rectilinear"; std::string RectilinearMesh::get_mesh_type() const @@ -1478,6 +1641,19 @@ CylindricalMesh::CylindricalMesh(pugi::xml_node node) } } +CylindricalMesh::CylindricalMesh(hid_t group) : PeriodicStructuredMesh {group} +{ + n_dimension_ = 3; + read_dataset(group, "r_grid", grid_[0]); + read_dataset(group, "phi_grid", grid_[1]); + read_dataset(group, "z_grid", grid_[2]); + read_dataset(group, "origin", origin_); + + if (int err = set_grid()) { + fatal_error(openmc_err_msg); + } +} + const std::string CylindricalMesh::mesh_type = "cylindrical"; std::string CylindricalMesh::get_mesh_type() const @@ -1756,6 +1932,20 @@ SphericalMesh::SphericalMesh(pugi::xml_node node) } } +SphericalMesh::SphericalMesh(hid_t group) : PeriodicStructuredMesh {group} +{ + n_dimension_ = 3; + + read_dataset(group, "r_grid", grid_[0]); + read_dataset(group, "theta_grid", grid_[1]); + read_dataset(group, "phi_grid", grid_[2]); + read_dataset(group, "origin", origin_); + + if (int err = set_grid()) { + fatal_error(openmc_err_msg); + } +} + const std::string SphericalMesh::mesh_type = "spherical"; std::string SphericalMesh::get_mesh_type() const @@ -2520,6 +2710,11 @@ MOABMesh::MOABMesh(pugi::xml_node node) : UnstructuredMesh(node) initialize(); } +MOABMesh::MOABMesh(hid_t group) : UnstructuredMesh(group) +{ + initialize(); +} + MOABMesh::MOABMesh(const std::string& filename, double length_multiplier) : UnstructuredMesh() { @@ -3228,6 +3423,15 @@ LibMesh::LibMesh(pugi::xml_node node) : UnstructuredMesh(node) initialize(); } +LibMesh::LibMesh(hid_t group) : UnstructuredMesh(group) +{ + // filename_ and length_multiplier_ will already be set by the + // UnstructuredMesh constructor + set_mesh_pointer_from_filename(filename_); + set_length_multiplier(length_multiplier_); + initialize(); +} + // create the mesh from a pointer to a libMesh Mesh LibMesh::LibMesh(libMesh::MeshBase& input_mesh, double length_multiplier) { @@ -3618,34 +3822,51 @@ void read_meshes(pugi::xml_node root) mesh_lib = get_node_value(node, "library", true, true); } - // Read mesh and add to vector - if (mesh_type == RegularMesh::mesh_type) { - model::meshes.push_back(make_unique(node)); - } else if (mesh_type == RectilinearMesh::mesh_type) { - model::meshes.push_back(make_unique(node)); - } else if (mesh_type == CylindricalMesh::mesh_type) { - model::meshes.push_back(make_unique(node)); - } else if (mesh_type == SphericalMesh::mesh_type) { - model::meshes.push_back(make_unique(node)); -#ifdef OPENMC_DAGMC_ENABLED - } else if (mesh_type == UnstructuredMesh::mesh_type && - mesh_lib == MOABMesh::mesh_lib_type) { - model::meshes.push_back(make_unique(node)); -#endif -#ifdef OPENMC_LIBMESH_ENABLED - } else if (mesh_type == UnstructuredMesh::mesh_type && - mesh_lib == LibMesh::mesh_lib_type) { - model::meshes.push_back(make_unique(node)); -#endif - } else if (mesh_type == UnstructuredMesh::mesh_type) { - fatal_error("Unstructured mesh support is not enabled or the mesh " - "library is invalid."); + Mesh::create(node, mesh_type, mesh_lib); + } +} + +void read_meshes(hid_t group) +{ + std::unordered_set mesh_ids; + + std::vector ids; + read_attribute(group, "ids", ids); + + for (auto id : ids) { + + // Check to make sure multiple meshes in the same file don't share IDs + if (contains(mesh_ids, id)) { + fatal_error(fmt::format("Two or more meshes use the same unique ID " + "'{}' in the same HDF5 input file", + id)); + } + mesh_ids.insert(id); + + // If we've already read a mesh with the same ID in a *different* file, + // assume it is the same here + if (model::mesh_map.find(id) != model::mesh_map.end()) { + warning(fmt::format("Mesh with ID={} appears in multiple files.", id)); + continue; + } + + std::string name = fmt::format("mesh {}", id); + hid_t mesh_group = open_group(group, name.c_str()); + + std::string mesh_type; + if (object_exists(mesh_group, "type")) { + read_dataset(mesh_group, "type", mesh_type); } else { - fatal_error("Invalid mesh type: " + mesh_type); + mesh_type = "regular"; + } + + // determine the mesh library to use + std::string mesh_lib; + if (object_exists(mesh_group, "library")) { + read_dataset(mesh_group, "library", mesh_lib); } - // Map ID to position in vector - model::mesh_map[model::meshes.back()->id_] = model::meshes.size() - 1; + Mesh::create(mesh_group, mesh_type, mesh_lib); } } diff --git a/src/weight_windows.cpp b/src/weight_windows.cpp index 26762ad18a6..0d648d3335e 100644 --- a/src/weight_windows.cpp +++ b/src/weight_windows.cpp @@ -1328,6 +1328,10 @@ extern "C" int openmc_weight_windows_import(const char* filename) hid_t weight_windows_group = open_group(ww_file, "weight_windows"); + hid_t mesh_group = open_group(ww_file, "meshes"); + + read_meshes(mesh_group); + std::vector names = group_names(weight_windows_group); for (const auto& name : names) { diff --git a/tests/cpp_unit_tests/CMakeLists.txt b/tests/cpp_unit_tests/CMakeLists.txt index 8fedc2daa57..5f87db9eac2 100644 --- a/tests/cpp_unit_tests/CMakeLists.txt +++ b/tests/cpp_unit_tests/CMakeLists.txt @@ -5,6 +5,7 @@ set(TEST_NAMES test_interpolate test_math test_mcpl_stat_sum + test_mesh # Add additional unit test files here ) diff --git a/tests/cpp_unit_tests/test_mesh.cpp b/tests/cpp_unit_tests/test_mesh.cpp new file mode 100644 index 00000000000..24c4f77373f --- /dev/null +++ b/tests/cpp_unit_tests/test_mesh.cpp @@ -0,0 +1,257 @@ +#include +#include +#include + +#include +#include + +#include "openmc/hdf5_interface.h" +#include "openmc/mesh.h" + +using namespace openmc; + +TEST_CASE("Test mesh hdf5 roundtrip - regular") +{ + // The XML data as a string + std::string xml_string = R"( + + 3 4 5 + -2 -3 -5 + 2 3 5 + + )"; + + // Create a pugixml document object + pugi::xml_document doc; + + // Load the XML from the string + pugi::xml_parse_result result = doc.load_string(xml_string.c_str()); + + pugi::xml_node root = doc.child("mesh"); + + auto mesh = RegularMesh(root); + + hid_t file_id = file_open("mesh.h5", 'w'); + + mesh.to_hdf5(file_id); + + file_close(file_id); + + hid_t file_id2 = file_open("mesh.h5", 'r'); + + hid_t group = open_group(file_id2, "mesh 1"); + + auto mesh2 = RegularMesh(group); + + file_close(file_id2); + + remove("mesh.h5"); + + REQUIRE(mesh2.shape_ == mesh.shape_); + + REQUIRE(mesh2.lower_left() == mesh.lower_left()); + + REQUIRE(mesh2.upper_right() == mesh.upper_right()); +} + +TEST_CASE("Test mesh hdf5 roundtrip - rectilinear") +{ + // The XML data as a string + std::string xml_string = R"( + + 0.0 1.0 5.0 10.0 + -10.0 -5.0 0.0 + -100.0 0.0 100.0 + + )"; + + // Create a pugixml document object + pugi::xml_document doc; + + // Load the XML from the string + pugi::xml_parse_result result = doc.load_string(xml_string.c_str()); + + pugi::xml_node root = doc.child("mesh"); + + auto mesh = RectilinearMesh(root); + + hid_t file_id = file_open("mesh.h5", 'w'); + + mesh.to_hdf5(file_id); + + file_close(file_id); + + hid_t file_id2 = file_open("mesh.h5", 'r'); + + hid_t group = open_group(file_id2, "mesh 1"); + + auto mesh2 = RectilinearMesh(group); + + file_close(file_id2); + + remove("mesh.h5"); + + REQUIRE(mesh2.shape_ == mesh.shape_); + + REQUIRE(mesh2.grid_ == mesh.grid_); +} + +TEST_CASE("Test mesh hdf5 roundtrip - cylindrical") +{ + // The XML data as a string + std::string xml_string = R"( + + 0.1 0.2 0.5 1.0 + 0.0 6.283185307179586 + 0.1 0.2 0.4 0.6 1.0 + 0 0 0 + + )"; + + // Create a pugixml document object + pugi::xml_document doc; + + // Load the XML from the string + pugi::xml_parse_result result = doc.load_string(xml_string.c_str()); + + pugi::xml_node root = doc.child("mesh"); + + auto mesh = CylindricalMesh(root); + + hid_t file_id = file_open("mesh.h5", 'w'); + + mesh.to_hdf5(file_id); + + file_close(file_id); + + hid_t file_id2 = file_open("mesh.h5", 'r'); + + hid_t group = open_group(file_id2, "mesh 1"); + + auto mesh2 = CylindricalMesh(group); + + file_close(file_id2); + + remove("mesh.h5"); + + REQUIRE(mesh2.shape_ == mesh.shape_); + + REQUIRE(mesh2.grid_ == mesh.grid_); +} + +TEST_CASE("Test mesh hdf5 roundtrip - spherical") +{ + // The XML data as a string + std::string xml_string = R"( + + 0.1 0.2 0.5 1.0 + 0.0 3.141592653589793 + 0.0 6.283185307179586 + 0.0 0.0 0.0 + ' + )"; + + // Create a pugixml document object + pugi::xml_document doc; + + // Load the XML from the string + pugi::xml_parse_result result = doc.load_string(xml_string.c_str()); + + pugi::xml_node root = doc.child("mesh"); + + auto mesh = SphericalMesh(root); + + hid_t file_id = file_open("mesh.h5", 'w'); + + mesh.to_hdf5(file_id); + + file_close(file_id); + + hid_t file_id2 = file_open("mesh.h5", 'r'); + + hid_t group = open_group(file_id2, "mesh 1"); + + auto mesh2 = SphericalMesh(group); + + file_close(file_id2); + + remove("mesh.h5"); + + REQUIRE(mesh2.shape_ == mesh.shape_); + + REQUIRE(mesh2.grid_ == mesh.grid_); +} + +TEST_CASE("Test multiple meshes HDF5 roundtrip - spherical") +{ + // The XML data as a string + std::string xml_string = R"( + + + 0.1 0.2 0.5 1.0 + 0.0 3.141592653589793 + 0.0 6.283185307179586 + 0.0 0.0 0.0 + + + 3 4 5 + -2 -3 -5 + 2 3 5 + + + )"; + + // Create a pugixml document object + pugi::xml_document doc; + + // Load the XML from the string + pugi::xml_parse_result result = doc.load_string(xml_string.c_str()); + + pugi::xml_node root = doc.child("meshes"); + + read_meshes(root); + + const auto spherical_mesh_xml = + dynamic_cast(model::meshes[0].get()); + const auto regular_mesh_xml = + dynamic_cast(model::meshes[1].get()); + + hid_t file_id = file_open("meshes.h5", 'w'); + + hid_t root_group = create_group(file_id, "root"); + + open_group(file_id, "root"); + + meshes_to_hdf5(root_group); + + close_group(root_group); + + file_close(file_id); + + hid_t file_id2 = file_open("meshes.h5", 'r'); + + hid_t root_group_read = open_group(file_id2, "root"); + + hid_t mesh_group_read = open_group(root_group_read, "meshes"); + + read_meshes(mesh_group_read); + + // increment mesh IDs to avoid collision during read + for (auto& mesh : model::meshes) { + mesh->set_id(mesh->id() + 10); + } + + const auto spherical_mesh_hdf5 = dynamic_cast( + model::meshes[model::mesh_map[spherical_mesh_xml->id_]].get()); + const auto regular_mesh_hdf5 = dynamic_cast( + model::meshes[model::mesh_map[regular_mesh_xml->id_]].get()); + + remove("meshes.h5"); + + REQUIRE(spherical_mesh_hdf5->shape_ == spherical_mesh_xml->shape_); + REQUIRE(spherical_mesh_hdf5->grid_ == spherical_mesh_xml->grid_); + + REQUIRE(regular_mesh_hdf5->shape_ == regular_mesh_xml->shape_); + REQUIRE(regular_mesh_hdf5->lower_left() == regular_mesh_xml->lower_left()); + REQUIRE(regular_mesh_hdf5->upper_right() == regular_mesh_xml->upper_right()); +} From 70b52546624cb3a654189856d8c9e012624c7672 Mon Sep 17 00:00:00 2001 From: John Tramm Date: Mon, 27 Oct 2025 03:30:54 -0500 Subject: [PATCH 134/159] Random Ray Geometry Debug Mode Fix (#3615) --- src/random_ray/random_ray.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/random_ray/random_ray.cpp b/src/random_ray/random_ray.cpp index 89a91449df7..c19d136a4a2 100644 --- a/src/random_ray/random_ray.cpp +++ b/src/random_ray/random_ray.cpp @@ -278,6 +278,10 @@ uint64_t RandomRay::transport_history_based_single_ray() // Transports ray across a single source region void RandomRay::event_advance_ray() { + // If geometry debug mode is on, check for cell overlaps + if (settings::check_overlaps) + check_cell_overlap(*this); + // Find the distance to the nearest boundary boundary() = distance_to_boundary(*this); double distance = boundary().distance(); From 230db28d39ae1e3011e3ad85cb8b5090693fe862 Mon Sep 17 00:00:00 2001 From: John Tramm Date: Tue, 28 Oct 2025 03:22:37 -0500 Subject: [PATCH 135/159] FW-CADIS Disregard Max Realizations Setting (#3616) --- docs/source/usersguide/variance_reduction.rst | 3 +-- src/simulation.cpp | 7 ++----- src/weight_windows.cpp | 15 +++++++++++---- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/docs/source/usersguide/variance_reduction.rst b/docs/source/usersguide/variance_reduction.rst index 369e33e2d80..5c248515859 100644 --- a/docs/source/usersguide/variance_reduction.rst +++ b/docs/source/usersguide/variance_reduction.rst @@ -133,8 +133,7 @@ random ray mode can be found in the :ref:`Random Ray User Guide `. # we used for source region decomposition wwg = openmc.WeightWindowGenerator( method='fw_cadis', - mesh=mesh, - max_realizations=settings.batches + mesh=mesh ) # Add generator to openmc.settings object diff --git a/src/simulation.cpp b/src/simulation.cpp index f55546c4ce8..05d55452602 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -400,11 +400,8 @@ void finalize_batch() simulation::time_tallies.stop(); // update weight windows if needed - if (settings::solver_type != SolverType::RANDOM_RAY || - simulation::current_batch == settings::n_batches) { - for (const auto& wwg : variance_reduction::weight_windows_generators) { - wwg->update(); - } + for (const auto& wwg : variance_reduction::weight_windows_generators) { + wwg->update(); } // Reset global tally results diff --git a/src/weight_windows.cpp b/src/weight_windows.cpp index 0d648d3335e..674fae49c3b 100644 --- a/src/weight_windows.cpp +++ b/src/weight_windows.cpp @@ -26,6 +26,7 @@ #include "openmc/random_ray/flat_source_domain.h" #include "openmc/search.h" #include "openmc/settings.h" +#include "openmc/simulation.h" #include "openmc/tallies/filter_energy.h" #include "openmc/tallies/filter_mesh.h" #include "openmc/tallies/filter_particle.h" @@ -966,11 +967,17 @@ void WeightWindowsGenerator::update() const Tally* tally = model::tallies[tally_idx_].get(); - // if we're beyond the number of max realizations or not at the corrrect - // update interval, skip the update - if (max_realizations_ < tally->n_realizations_ || - tally->n_realizations_ % update_interval_ != 0) + // If in random ray mode, only update on the last batch + if (settings::solver_type == SolverType::RANDOM_RAY) { + if (simulation::current_batch != settings::n_batches) { + return; + } + // If in Monte Carlo mode and beyond the number of max realizations or + // not at the correct update interval, skip the update + } else if (max_realizations_ < tally->n_realizations_ || + tally->n_realizations_ % update_interval_ != 0) { return; + } wws->update_weights(tally, tally_value_, threshold_, ratio_, method_); From f10d7d9f67375acb088d01e82bab957617f18e39 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 28 Oct 2025 03:23:16 -0500 Subject: [PATCH 136/159] Speed up `apply_time_correction` by reducing file I/O and deepcopies (#3617) --- openmc/deplete/d1s.py | 27 ++++++++++++++++----------- tests/unit_tests/test_d1s.py | 14 ++++++++++++++ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/openmc/deplete/d1s.py b/openmc/deplete/d1s.py index f51dea41606..bc99fc42dba 100644 --- a/openmc/deplete/d1s.py +++ b/openmc/deplete/d1s.py @@ -5,7 +5,7 @@ """ -from copy import deepcopy +from copy import copy from typing import Sequence from math import log, prod @@ -164,8 +164,12 @@ def apply_time_correction( radionuclides = [str(x) for x in tally.filters[i_filter].bins] tcf = np.array([time_correction_factors[x][index] for x in radionuclides]) - # Create copy of tally - new_tally = deepcopy(tally) + # Force tally results to be read and std_dev to be computed + tally.std_dev + + # Create shallow copy of tally + new_tally = copy(tally) + new_tally._filters = copy(tally._filters) # Determine number of bins in other filters n_bins_before = prod([f.num_bins for f in tally.filters[:i_filter]]) @@ -177,32 +181,33 @@ def apply_time_correction( shape = (n_bins_before, n_radionuclides, n_bins_after, n_nuclides, n_scores) tally_sum = new_tally.sum.reshape(shape) tally_sum_sq = new_tally.sum_sq.reshape(shape) + tally_mean = new_tally.mean.reshape(shape) + tally_std_dev = new_tally.std_dev.reshape(shape) # Apply TCF, broadcasting to the correct dimensions tcf.shape = (1, -1, 1, 1, 1) new_tally._sum = tally_sum * tcf new_tally._sum_sq = tally_sum_sq * (tcf*tcf) - new_tally._mean = None - new_tally._std_dev = None + new_tally._mean = tally_mean * tcf + new_tally._std_dev = tally_std_dev * tcf shape = (-1, n_nuclides, n_scores) if sum_nuclides: - # Query the mean and standard deviation - mean = new_tally.mean - std_dev = new_tally.std_dev - # Sum over parent nuclides (note that when combining different bins for # parent nuclide, we can't work directly on sum_sq) - new_tally._mean = mean.sum(axis=1).reshape(shape) - new_tally._std_dev = np.linalg.norm(std_dev, axis=1).reshape(shape) + new_tally._mean = new_tally.mean.sum(axis=1).reshape(shape) + new_tally._std_dev = np.linalg.norm(new_tally.std_dev, axis=1).reshape(shape) new_tally._derived = True # Remove ParentNuclideFilter new_tally.filters.pop(i_filter) else: + # Change shape back to (filter combinations, nuclides, scores) new_tally._sum.shape = shape new_tally._sum_sq.shape = shape + new_tally._mean.shape = shape + new_tally._std_dev.shape = shape return new_tally diff --git a/tests/unit_tests/test_d1s.py b/tests/unit_tests/test_d1s.py index 9410f2da2e0..8f3b62f4000 100644 --- a/tests/unit_tests/test_d1s.py +++ b/tests/unit_tests/test_d1s.py @@ -120,6 +120,13 @@ def test_apply_time_correction(run_in_tmpdir): tally = sp.tallies[tally.id] flux = tally.mean.flatten() + # Copy attributes from original tally + tally_filters = list(tally.filters) + tally_sum = tally.sum.copy() + tally_sum_sq = tally.sum_sq.copy() + tally_mean = tally.mean.copy() + tally_std_dev = tally.std_dev.copy() + # Apply TCF and make sure results are consistent result = d1s.apply_time_correction(tally, factors, sum_nuclides=False) tcf = np.array([factors[nuc][-1] for nuc in nuclides]) @@ -129,6 +136,13 @@ def test_apply_time_correction(run_in_tmpdir): result_summed = d1s.apply_time_correction(tally, factors) assert result_summed.mean.flatten()[0] == pytest.approx(result.mean.sum()) + # Make sure original tally is unchanged + assert tally.filters == tally_filters + assert np.all(tally.sum == tally_sum) + assert np.all(tally.sum_sq == tally_sum_sq) + assert np.all(tally.mean == tally_mean) + assert np.all(tally.std_dev == tally_std_dev) + # Make sure various tally methods work result.get_values() result_summed.get_values() From a74c1424a8a883066b2c0afe4b23f20c84cbb815 Mon Sep 17 00:00:00 2001 From: Makarand More <40858007+Jarvis2001@users.noreply.github.com> Date: Tue, 28 Oct 2025 20:20:11 +0530 Subject: [PATCH 137/159] Update `check_type` calls to accept both `str` and `os.PathLike` objects. (#3618) Co-authored-by: Paul Romano --- openmc/checkvalue.py | 2 +- openmc/mgxs/library.py | 7 ++-- openmc/mgxs/mdgxs.py | 3 +- openmc/mgxs/mgxs.py | 3 +- openmc/mgxs_library.py | 5 ++- openmc/plots.py | 2 +- tests/unit_tests/test_pathlike_simple.py | 46 ++++++++++++++++++++++++ 7 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 tests/unit_tests/test_pathlike_simple.py diff --git a/openmc/checkvalue.py b/openmc/checkvalue.py index 4fa205b14f7..5ff2cf9ac5a 100644 --- a/openmc/checkvalue.py +++ b/openmc/checkvalue.py @@ -37,7 +37,7 @@ def check_type(name, value, expected_type, expected_iter_type=None, *, none_ok=F [t.__name__ for t in expected_type])) else: msg = (f'Unable to set "{name}" to "{value}" which is not of type "' - f'{expected_type.__name__}"') + f'{expected_type}"') raise TypeError(msg) if expected_iter_type: diff --git a/openmc/mgxs/library.py b/openmc/mgxs/library.py index 12a4630bdc4..f782cbe9e52 100644 --- a/openmc/mgxs/library.py +++ b/openmc/mgxs/library.py @@ -10,6 +10,7 @@ import openmc import openmc.mgxs import openmc.checkvalue as cv +from openmc.checkvalue import PathLike from ..tallies import ESTIMATOR_TYPES @@ -851,7 +852,7 @@ def build_hdf5_store(self, filename='mgxs.h5', directory='mgxs', 'since a statepoint has not yet been loaded' raise ValueError(msg) - cv.check_type('filename', filename, str) + cv.check_type('filename', filename, (str, PathLike)) cv.check_type('directory', directory, str) import h5py @@ -894,7 +895,7 @@ def dump_to_file(self, filename='mgxs', directory='mgxs'): """ - cv.check_type('filename', filename, str) + cv.check_type('filename', filename, (str, PathLike)) cv.check_type('directory', directory, str) # Make directory if it does not exist @@ -930,7 +931,7 @@ def load_from_file(filename='mgxs', directory='mgxs'): """ - cv.check_type('filename', filename, str) + cv.check_type('filename', filename, (str, PathLike)) cv.check_type('directory', directory, str) # Make directory if it does not exist diff --git a/openmc/mgxs/mdgxs.py b/openmc/mgxs/mdgxs.py index b95a4fbc0eb..c12c1a9abe6 100644 --- a/openmc/mgxs/mdgxs.py +++ b/openmc/mgxs/mdgxs.py @@ -7,6 +7,7 @@ import openmc import openmc.checkvalue as cv +from openmc.checkvalue import PathLike from openmc.mgxs import MGXS from .mgxs import _DOMAIN_TO_FILTER @@ -722,7 +723,7 @@ def export_xs_data(self, filename='mgxs', directory='mgxs', """ - cv.check_type('filename', filename, str) + cv.check_type('filename', filename, (str, PathLike)) cv.check_type('directory', directory, str) cv.check_value('format', format, ['csv', 'excel', 'pickle', 'latex']) cv.check_value('xs_type', xs_type, ['macro', 'micro']) diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index b8f2b8d3fb6..533ab0ad325 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -10,6 +10,7 @@ import openmc from openmc.data import REACTION_MT, REACTION_NAME, FISSION_MTS import openmc.checkvalue as cv +from openmc.checkvalue import PathLike from ..tallies import ESTIMATOR_TYPES from . import EnergyGroups @@ -1982,7 +1983,7 @@ def export_xs_data(self, filename='mgxs', directory='mgxs', """ - cv.check_type('filename', filename, str) + cv.check_type('filename', filename, (str, PathLike)) cv.check_type('directory', directory, str) cv.check_value('format', format, ['csv', 'excel', 'pickle', 'latex']) cv.check_value('xs_type', xs_type, ['macro', 'micro']) diff --git a/openmc/mgxs_library.py b/openmc/mgxs_library.py index ce86f97dfb2..4bc2d4a5a76 100644 --- a/openmc/mgxs_library.py +++ b/openmc/mgxs_library.py @@ -2514,8 +2514,7 @@ def export_to_hdf5(self, filename='mgxs.h5', libver='earliest'): """ - check_type('filename', filename, PathLike) - + check_type('filename', filename, (str, PathLike)) # Create and write to the HDF5 file file = h5py.File(filename, "w", libver=libver) file.attrs['filetype'] = np.bytes_(_FILETYPE_MGXS_LIBRARY) @@ -2554,7 +2553,7 @@ def from_hdf5(cls, filename=None): raise ValueError("Either path or openmc.config['mg_cross_sections']" "must be set") - check_type('filename', filename, str) + check_type('filename', filename, (str, PathLike)) file = h5py.File(filename, 'r') # Check filetype and version diff --git a/openmc/plots.py b/openmc/plots.py index e9130f0bea5..a0bde3f007a 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -439,7 +439,7 @@ def filename(self): @filename.setter def filename(self, filename): - cv.check_type('filename', filename, str) + cv.check_type('filename', filename, (str, PathLike)) self._filename = filename @property diff --git a/tests/unit_tests/test_pathlike_simple.py b/tests/unit_tests/test_pathlike_simple.py new file mode 100644 index 00000000000..e0116bf01c3 --- /dev/null +++ b/tests/unit_tests/test_pathlike_simple.py @@ -0,0 +1,46 @@ +"""Simple test for PathLike filename support""" + +from pathlib import Path + +import pytest +import openmc +from openmc.checkvalue import check_type, PathLike + + +def test_pathlike_type_checking(): + """Test that PathLike type checking works correctly""" + + # Test with string (should work) + check_type('filename', 'test.txt', PathLike) + + # Test with Path object (should work) + path_obj = Path('test.txt') + check_type('filename', path_obj, PathLike) + + # Test with Path object containing subdirectories (should work) + path_with_subdir = Path('subdir') / 'test.txt' + check_type('filename', path_with_subdir, PathLike) + + # Test with invalid type (should raise TypeError) + with pytest.raises(TypeError): + check_type('filename', 123, PathLike) + + +def test_plot_filename_pathlike(): + """Test that plot filename accepts Path objects""" + + plot = openmc.Plot() + + # Test with string (should still work) + plot.filename = "test_plot" + assert plot.filename == "test_plot" + + # Test with Path object + path_obj = Path("test_plot_path") + plot.filename = path_obj + assert plot.filename == path_obj + + # Test with Path object containing subdirectories + path_with_subdir = Path("subdir") / "test_plot" + plot.filename = path_with_subdir + assert plot.filename == path_with_subdir From 4c4176661124fe92f5304d20d1b6e7675c44d455 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 28 Oct 2025 11:36:03 -0500 Subject: [PATCH 138/159] Criticality search method on the Model class (#3569) --- openmc/model/model.py | 270 ++++++++++++++++++++++++++++++++- src/settings.cpp | 5 - tests/unit_tests/test_lib.py | 3 - tests/unit_tests/test_model.py | 57 +++++++ 4 files changed, 326 insertions(+), 9 deletions(-) diff --git a/openmc/model/model.py b/openmc/model/model.py index c1ffafafd3f..7963751d567 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -1,6 +1,7 @@ from __future__ import annotations -from collections.abc import Iterable, Sequence +from collections.abc import Callable, Iterable, Sequence import copy +from dataclasses import dataclass, field from functools import cache from pathlib import Path import math @@ -8,11 +9,13 @@ import random import re from tempfile import NamedTemporaryFile, TemporaryDirectory +from typing import Any, Protocol import warnings import h5py import lxml.etree as ET import numpy as np +from scipy.optimize import curve_fit import openmc import openmc._xml as xml @@ -24,6 +27,12 @@ from openmc.utility_funcs import change_directory +# Protocol for a function that is passed to search_keff +class ModelModifier(Protocol): + def __call__(self, val: float, **kwargs: Any) -> None: + ... + + class Model: """Model container. @@ -2196,3 +2205,262 @@ def _replace_infinity(value): # Take a wild guess as to how many rays are needed self.settings.particles = 2 * int(max_length) + + def keff_search( + self, + func: ModelModifier, + x0: float, + x1: float, + target: float = 1.0, + k_tol: float = 1e-4, + sigma_final: float = 3e-4, + p: float = 0.5, + q: float = 0.95, + memory: int = 4, + x_min: float | None = None, + x_max: float | None = None, + b0: int | None = None, + b_min: int = 20, + b_max: int | None = None, + maxiter: int = 50, + output: bool = False, + func_kwargs: dict[str, Any] | None = None, + run_kwargs: dict[str, Any] | None = None, + ) -> SearchResult: + r"""Perform a keff search on a model parametrized by a single variable. + + This method uses the GRsecant method described in a paper by `Price and + Roskoff `_. The GRsecant + method is a modification of the secant method that accounts for + uncertainties in the function evaluations. The method uses a weighted + linear fit of the most recent function evaluations to predict the next + point to evaluate. It also adaptively changes the number of batches to + meet the target uncertainty value at each iteration. + + The target uncertainty for iteration :math:`n+1` is determined by the + following equation (following Eq. (8) in the paper): + + .. math:: + \sigma_{i+1} = q \sigma_\text{final} \left ( \frac{ \min \left \{ + \left\lvert k_i - k_\text{target} \right\rvert : k=0,1,\dots,n + \right \} }{k_\text{tol}} \right )^p + + where :math:`q` is a multiplicative factor less than 1, given as the + ``sigma_factor`` parameter below. + + Parameters + ---------- + func : ModelModifier + Function that takes the parameter to be searched and makes a + modification to the model. + x0 : float + First guess for the parameter passed to `func` + x1 : float + Second guess for the parameter passed to `func` + target : float, optional + keff value to search for + k_tol : float, optional + Stopping criterion on the function value; the absolute value must be + within ``k_tol`` of zero to be accepted. + sigma_final : float, optional + Maximum accepted k-effective uncertainty for the stopping criterion. + p : float, optional + Exponent used in the stopping criterion. + q : float, optional + Multiplicative factor used in the stopping criterion. + memory : int, optional + Number of most-recent points used in the weighted linear fit of + ``f(x) = a + b x`` to predict the next point. + x_min : float, optional + Minimum allowed value for the parameter ``x``. + x_max : float, optional + Maximum allowed value for the parameter ``x``. + b0 : int, optional + Number of active batches to use for the initial function + evaluations. If None, uses the model's current setting. + b_min : int, optional + Minimum number of active batches to use in a function evaluation. + b_max : int, optional + Maximum number of active batches to use in a function evaluation. + maxiter : int, optional + Maximum number of iterations to perform. + output : bool, optional + Whether or not to display output showing iteration progress. + func_kwargs : dict, optional + Keyword-based arguments to pass to the `func` function. + run_kwargs : dict, optional + Keyword arguments to pass to :meth:`openmc.Model.run` or + :meth:`openmc.lib.run`. + + Returns + ------- + SearchResult + Result object containing the estimated root (parameter value) and + evaluation history (parameters, means, standard deviations, and + batches), plus convergence status and termination reason. + + """ + import openmc.lib + + check_type('model modifier', func, Callable) + check_type('target', target, Real) + if memory < 2: + raise ValueError("memory must be ≥ 2") + func_kwargs = {} if func_kwargs is None else dict(func_kwargs) + run_kwargs = {} if run_kwargs is None else dict(run_kwargs) + run_kwargs.setdefault('output', False) + + # Create lists to store the history of evaluations + xs: list[float] = [] + fs: list[float] = [] + ss: list[float] = [] + gs: list[int] = [] + count = 0 + + # Helper function to evaluate f and store results + def eval_at(x: float, batches: int) -> tuple[float, float]: + # Modify the model with the current guess + func(x, **func_kwargs) + + # Change the number of batches and run the model + batches += self.settings.inactive + if openmc.lib.is_initialized: + openmc.lib.settings.set_batches(batches) + openmc.lib.reset() + openmc.lib.run(**run_kwargs) + sp_filepath = f'statepoint.{batches}.h5' + else: + self.settings.batches = batches + sp_filepath = self.run(**run_kwargs) + + # Extract keff and its uncertainty + with openmc.StatePoint(sp_filepath) as sp: + keff = sp.keff + + if output: + nonlocal count + count += 1 + print(f'Iteration {count}: {batches=}, {x=:.6g}, {keff=:.5f}') + + xs.append(float(x)) + fs.append(float(keff.n - target)) + ss.append(float(keff.s)) + gs.append(int(batches)) + return fs[-1], ss[-1] + + # Default b0 to current model settings if not explicitly provided + if b0 is None: + b0 = self.settings.batches - self.settings.inactive + + # Perform the search (inlined GRsecant) in a temporary directory + with TemporaryDirectory() as tmpdir: + if not openmc.lib.is_initialized: + run_kwargs.setdefault('cwd', tmpdir) + + # ---- Seed with two evaluations + f0, s0 = eval_at(x0, b0) + if abs(f0) <= k_tol and s0 <= sigma_final: + return SearchResult(x0, xs, fs, ss, gs, True, "converged") + f1, s1 = eval_at(x1, b0) + if abs(f1) <= k_tol and s1 <= sigma_final: + return SearchResult(x1, xs, fs, ss, gs, True, "converged") + + for _ in range(maxiter - 2): + # ------ Step 1: propose next x via GRsecant + m = min(memory, len(xs)) + + # Perform a curve fit on f(x) = a + bx accounting for + # uncertainties. This is equivalent to minimizing the function + # in Equation (A.14) + (a, b), _ = curve_fit( + lambda x, a, b: a + b*x, + xs[-m:], fs[-m:], sigma=ss[-m:], absolute_sigma=True + ) + x_new = float(-a / b) + + # Clamp x_new to the bounds if provided + if x_min is not None: + x_new = max(x_new, x_min) + if x_max is not None: + x_new = min(x_new, x_max) + + # ------ Step 2: choose target σ for next run (Eq. 8 + clamp) + + min_abs_f = float(np.min(np.abs(fs))) + base = q * sigma_final + ratio = min_abs_f / k_tol if k_tol > 0 else 1.0 + sig = base * (ratio ** p) + sig_target = max(sig, base) + + # ------ Step 3: choose generations to hit σ_target (Appendix C) + + # Use at least two past points for regression + if len(gs) >= 2 and np.var(np.log(gs)) > 0.0: + # Perform a curve fit based on Eq. (C.3) to solve for ln(k). + # Note that unlike in the paper, we do not leave r as an + # undetermined parameter and choose r=0.5. + (ln_k,), _ = curve_fit( + lambda ln_b, ln_k: ln_k - 0.5*ln_b, + np.log(gs[-4:]), np.log(ss[-4:]), + ) + k = float(np.exp(ln_k)) + else: + k = float(ss[-1] * math.sqrt(gs[-1])) + + b_new = (k / sig_target) ** 2 + + # Clamp and round up to integer + b_new = max(b_min, math.ceil(b_new)) + if b_max is not None: + b_new = min(b_new, b_max) + + # Evaluate at proposed x with batches determined above + f_new, s_new = eval_at(x_new, b_new) + + # Termination based on both criteria (|f| and σ) + if abs(f_new) <= k_tol and s_new <= sigma_final: + return SearchResult(x_new, xs, fs, ss, gs, True, "converged") + + return SearchResult(xs[-1], xs, fs, ss, gs, False, "maxiter") + + +@dataclass +class SearchResult: + """Result of a GRsecant keff search. + + Attributes + ---------- + root : float + Estimated parameter value where f(x) = 0 at termination. + parameters : list[float] + Parameter values (x) evaluated during the search, in order. + keffs : list[float] + Estimated keff values for each evaluation. + stdevs : list[float] + One-sigma uncertainties of keff for each evaluation. + batches : list[int] + Number of active batches used for each evaluation. + converged : bool + Whether both |f| <= k_tol and sigma <= sigma_final were met. + flag : str + Reason for termination (e.g., "converged", "maxiter"). + """ + root: float + parameters: list[float] = field(repr=False) + means: list[float] = field(repr=False) + stdevs: list[float] = field(repr=False) + batches: list[int] = field(repr=False) + converged: bool + flag: str + + @property + def function_calls(self) -> int: + """Number of function evaluations performed.""" + return len(self.parameters) + + @property + def total_batches(self) -> int: + """Total number of active batches used across all evaluations.""" + return sum(self.batches) + + diff --git a/src/settings.cpp b/src/settings.cpp index 325256cdc50..13b91b0e4f8 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1220,11 +1220,6 @@ extern "C" int openmc_set_n_batches( return OPENMC_E_INVALID_ARGUMENT; } - if (simulation::current_batch >= n_batches) { - set_errmsg("Number of batches must be greater than current batch."); - return OPENMC_E_INVALID_ARGUMENT; - } - if (!settings::trigger_on) { // Set n_batches and n_max_batches to same value settings::n_batches = n_batches; diff --git a/tests/unit_tests/test_lib.py b/tests/unit_tests/test_lib.py index 43bc5a8f6f0..eb4dc3dce66 100644 --- a/tests/unit_tests/test_lib.py +++ b/tests/unit_tests/test_lib.py @@ -496,9 +496,6 @@ def test_set_n_batches(lib_run): for i in range(7): openmc.lib.next_batch() - # Setting n_batches less than current_batch should raise error - with pytest.raises(exc.InvalidArgumentError): - settings.set_batches(6) # n_batches should stay the same assert settings.get_batches() == 10 diff --git a/tests/unit_tests/test_model.py b/tests/unit_tests/test_model.py index 60f8b1a25a3..f4f94a47cac 100644 --- a/tests/unit_tests/test_model.py +++ b/tests/unit_tests/test_model.py @@ -901,6 +901,7 @@ def test_id_map_aligned_model(): assert tr_instance == 3, f"Expected cell instance 3 at top-right corner, got {tr_instance}" assert tr_material == 5, f"Expected material ID 5 at top-right corner, got {tr_material}" + def test_setter_from_list(): mat = openmc.Material() model = openmc.Model(materials=[mat]) @@ -913,3 +914,59 @@ def test_setter_from_list(): plot = openmc.Plot() model = openmc.Model(plots=[plot]) assert isinstance(model.plots, openmc.Plots) + + +def test_keff_search(run_in_tmpdir): + """Test the Model.keff_search method""" + + # Create model of a sphere of U235 + mat = openmc.Material() + mat.set_density('g/cm3', 18.9) + mat.add_nuclide('U235', 1.0) + sphere = openmc.Sphere(r=10.0, boundary_type='vacuum') + cell = openmc.Cell(fill=mat, region=-sphere) + geometry = openmc.Geometry([cell]) + settings = openmc.Settings(particles=1000, inactive=10, batches=30) + model = openmc.Model(geometry=geometry, settings=settings) + + # Define function to modify sphere radius + def modify_radius(radius): + sphere.r = radius + + # Perform keff search + k_tol = 4e-3 + sigma_final = 2e-3 + result = model.keff_search( + func=modify_radius, + x0=6.0, + x1=9.0, + k_tol=k_tol, + sigma_final=sigma_final, + output=True, + ) + + final_keff = result.means[-1] + 1.0 # Add back target since means are (keff - target) + final_sigma = result.stdevs[-1] + + # Check for convergence and that tolerances are met + assert result.converged, "keff_search did not converge" + assert abs(final_keff - 1.0) <= k_tol, \ + f"Final keff {final_keff:.5f} not within k_tol {k_tol}" + assert final_sigma <= sigma_final, \ + f"Final uncertainty {final_sigma:.5f} exceeds sigma_final {sigma_final}" + + # Check type of result + assert isinstance(result, openmc.model.SearchResult) + + # Check that we have function evaluation history + assert len(result.parameters) >= 2 + assert len(result.means) == len(result.parameters) + assert len(result.stdevs) == len(result.parameters) + assert len(result.batches) == len(result.parameters) + + # Check that function_calls property works + assert result.function_calls == len(result.parameters) + + # Check that total_batches property works + assert result.total_batches == sum(result.batches) + assert result.total_batches > 0 From 5fc289b99d6ace24d6bcf736a77cfef4c59f6a82 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 31 Oct 2025 19:15:25 -0500 Subject: [PATCH 139/159] Automate workflow for mesh- or cell-based R2S calculations (#3508) Co-authored-by: Ethan Peterson Co-authored-by: Jonathan Shimwell --- docs/source/pythonapi/deplete.rst | 10 + docs/source/usersguide/decay_sources.rst | 211 +++++-- openmc/deplete/__init__.py | 1 + openmc/deplete/microxs.py | 113 +++- openmc/deplete/r2s.py | 680 +++++++++++++++++++++++ openmc/mesh.py | 18 +- openmc/model/model.py | 23 + openmc/utility_funcs.py | 19 + tests/unit_tests/test_r2s.py | 152 +++++ 9 files changed, 1176 insertions(+), 51 deletions(-) create mode 100644 openmc/deplete/r2s.py create mode 100644 tests/unit_tests/test_r2s.py diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index f112cf8ccff..25fcd898f49 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -287,6 +287,16 @@ the following abstract base classes: abc.SIIntegrator abc.DepSystemSolver +R2S Automation +-------------- + +.. autosummary:: + :toctree: generated + :nosignatures: + :template: myclass.rst + + R2SManager + D1S Functions ------------- diff --git a/docs/source/usersguide/decay_sources.rst b/docs/source/usersguide/decay_sources.rst index d5a078135ba..398680e7464 100644 --- a/docs/source/usersguide/decay_sources.rst +++ b/docs/source/usersguide/decay_sources.rst @@ -6,42 +6,189 @@ Decay Sources Through the :ref:`depletion ` capabilities in OpenMC, it is possible to simulate radiation emitted from the decay of activated materials. -For fusion energy systems, this is commonly done using what is known as the -`rigorous 2-step `_ (R2S) method. -In this method, a neutron transport calculation is used to determine the neutron -flux and reaction rates over a cell- or mesh-based spatial discretization of the -model. Then, the neutron flux in each discrete region is used to predict the -activated material composition using a depletion solver. Finally, a photon -transport calculation with a source based on the activity and energy spectrum of -the activated materials is used to determine a desired physical response (e.g., -a dose rate) at one or more locations of interest. - -Once a depletion simulation has been completed in OpenMC, the intrinsic decay -source can be determined as follows. First the activated material composition -can be determined using the :class:`openmc.deplete.Results` object. Indexing an -instance of this class with the timestep index returns a -:class:`~openmc.deplete.StepResult` object, which itself has a -:meth:`~openmc.deplete.StepResult.get_material` method. Once the activated -:class:`~openmc.Material` has been obtained, the -:meth:`~openmc.Material.get_decay_photon_energy` method will give the energy -spectrum of the decay photon source. The integral of the spectrum also indicates -the intensity of the source in units of [Bq]. Altogether, the workflow looks as -follows:: - +For fusion energy systems, this is commonly done using either the `rigorous +2-step `_ (R2S) method or the +`direct 1-step `_ (D1S) method. +In the R2S method, a neutron transport calculation is used to determine the +neutron flux and reaction rates over a cell- or mesh-based spatial +discretization of the model. Then, the neutron flux in each discrete region is +used to predict the activated material composition using a depletion solver. +Finally, a photon transport calculation with a source based on the activity and +energy spectrum of the activated materials is used to determine a desired +physical response (e.g., a dose rate) at one or more locations of interest. +OpenMC includes automation for both the R2S and D1S methods as described in the +following sections. + +Rigorous 2-Step (R2S) Calculations +================================== + +OpenMC includes an :class:`openmc.deplete.R2SManager` class that fully automates +cell- and mesh-based R2S calculations. Before we describe this class, it is +useful to understand the basic mechanics of how an R2S calculation works. +Generally, it involves the following steps: + +1. The :meth:`openmc.deplete.get_microxs_and_flux` function is called to run a + neutron transport calculation that determines fluxes and microscopic cross + sections in each activation region. +2. The :class:`openmc.deplete.IndependentOperator` and + :class:`openmc.deplete.PredictorIntegrator` classes are used to carry out a + depletion (activation) calculation in order to determine predicted material + compositions based on a set of timesteps and source rates. +3. The activated material composition is determined using the + :class:`openmc.deplete.Results` class. Indexing an instance of this class + with the timestep index returns a :class:`~openmc.deplete.StepResult` object, + which itself has a :meth:`~openmc.deplete.StepResult.get_material` method + returning an activated material. +4. The :meth:`openmc.Material.get_decay_photon_energy` method is used to obtain + the energy spectrum of the decay photon source. The integral of the spectrum + also indicates the intensity of the source in units of [Bq]. +5. A new photon source is defined using one of OpenMC's source classes with the + energy distribution set equal to the object returned by the + :meth:`openmc.Material.get_decay_photon_energy` method. The source is then + assigned to a photon :class:`~openmc.Model`. +6. A photon transport calculation is run with ``model.run()``. + +Altogether, the workflow looks as follows:: + + # Run neutron transport calculation + fluxes, micros = openmc.deplete.get_microxs_and_flux(model, domains) + + # Run activation calculation + op = openmc.deplete.IndependentOperator(mats, fluxes, micros) + timesteps = ... + source_rates = ... + integrator = openmc.deplete.Integrator(op, timesteps, source_rates) + integrator.integrate() + + # Get decay photon source at last timestep results = openmc.deplete.Results("depletion_results.h5") - - # Get results at last timestep step = results[-1] - - # Get activated material composition for ID=1 activated_mat = step.get_material('1') - - # Determine photon source photon_energy = activated_mat.get_decay_photon_energy() - -By default, the :meth:`~openmc.Material.get_decay_photon_energy` method will -eliminate spectral lines with very low intensity, but this behavior can be -configured with the ``clip_tolerance`` argument. + photon_source = openmc.IndependentSource( + space=..., + energy=photon_energy, + particle='photon', + strength=photon_energy.integral() + ) + + # Run photon transport calculation + model.settings.source = photon_source + model.run() + +Note that by default, the :meth:`~openmc.Material.get_decay_photon_energy` +method will eliminate spectral lines with very low intensity, but this behavior +can be configured with the ``clip_tolerance`` argument. + +Cell-based R2S +-------------- + +In practice, users do not need to manually go through each of the steps in an R2S +calculation described above. The :class:`~openmc.deplete.R2SManager` fully +automates the execution of neutron transport, depletion, decay source +generation, and photon transport. For a cell-based R2S calculation, once you +have a :class:`~openmc.Model` that has been defined, simply create an instance +of :class:`~openmc.deplete.R2SManager` by passing the model and a list of cells +to activate:: + + r2s = openmc.deplete.R2SManager(model, [cell1, cell2, cell3]) + +Note that the ``volume`` attribute must be set for any cell that is to be +activated. The :class:`~openmc.deplete.R2SManager` class allows you to +optionally specify a separate photon model; if not given as an argument, it will +create a shallow copy of the original neutron model (available as the +``neutron_model`` attribute) and store it in the ``photon_model`` attribute. We +can use this to define tallies specific to the photon model:: + + dose_tally = openmc.Tally() + ... + r2s.photon_model.tallies = [dose_tally] + +Next, define the timesteps and source rates for the activation calculation:: + + timesteps = [(3.0, 'd'), (5.0, 'h')] + source_rates = [1e12, 0.0] + +In this case, the model is irradiated for 3 days with a source rate of +:math:`10^{12}` neutron/sec and then the source is turned off and the activated +materials are allowed to decay for 5 hours. These parameters should be passed to +the :meth:`~openmc.deplete.R2SManager.run` method to execute the full R2S +calculation. Before we can do that though, for a cell-based calculation, the one +other piece of information that is needed is bounding boxes of the activated +cells:: + + bounding_boxes = { + cell1.id: cell1.bounding_box, + cell2.id: cell2.bounding_box, + cell3.id: cell3.bounding_box + } + +Note that calling the ``bounding_box`` attribute may not work for all +constructive solid geometry regions (for example, a cell that uses a +non-axis-aligned plane). In these cases, the bounding box will need to be +specified manually. Once you have a set of bounding boxes, the R2S calculation +can be run:: + + r2s.run(timesteps, source_rates, bounding_boxes=bounding_boxes) + +If not specified otherwise, a photon transport calculation is run at each time +in the depletion schedule. That means in the case above, we would see three +photon transport calculations. To specify specific times at which photon +transport calculations should be run, pass the ``photon_time_indices`` argument. +For example, if we wanted to run a photon transport calculation only on the last +time (after the 5 hour decay), we would run:: + + r2s.run(timesteps, source_rates, bounding_boxes=bounding_boxes, + photon_time_indices=[2]) + +After an R2S calculation has been run, the :class:`~openmc.deplete.R2SManager` +instance will have a ``results`` dictionary that allows you to directly access +results from each of the steps. It will also write out all the output files into +a directory that is named "r2s_/". The ``output_dir`` argument to the +:meth:`~openmc.deplete.R2SManager.run` method enables you to override the +default output directory name if desired. + +The :meth:`~openmc.deplete.R2SManager.run` method actually runs three +lower-level methods under the hood:: + + r2s.step1_neutron_transport(...) + r2s.step2_activation(...) + r2s.step3_photon_transport(...) + +For users looking for more control over the calculation, these lower-level +methods can be used in lieu of the :meth:`openmc.deplete.R2SManager.run` method. + +Mesh-based R2S +-------------- + +Executing a mesh-based R2S calculation looks nearly identical to the cell-based +R2S workflow described above. The only difference is that instead of passing a +list of cells to the ``domains`` argument of +:class:`~openmc.deplete.R2SManager`, you need to define a mesh object and pass +that instead. This might look like the following:: + + # Define a regular Cartesian mesh + mesh = openmc.RegularMesh() + mesh.lower_left = (-50., -50., 0.) + mesh.upper_right = (50., 50., 75.) + mesh.dimension = (10, 10, 5) + + r2s = openmc.deplete.R2SManager(model, mesh) + +Executing the R2S calculation is then performed by adding photon tallies and +calling the :meth:`~openmc.deplete.R2SManager.run` method with the appropriate +timesteps and source rates. Note that in this case we do not need to define cell +volumes or bounding boxes as is required for a cell-based R2S calculation. +Instead, during the neutron transport step, OpenMC will run a raytracing +calculation to determine material volume fractions within each mesh element +using the :meth:`openmc.MeshBase.material_volumes` method. Arguments to this +method can be customized via the ``mat_vol_kwargs`` argument to the +:meth:`~openmc.deplete.R2SManager.run` method. Most often, this would involve +customizing the number of rays traced to obtain better estimates of volumes. As +an example, if we wanted to run the raytracing calculation with 10 million rays, +we would run:: + + r2s.run(timesteps, source_rates, mat_vol_kwargs={'n_samples': 10_000_000}) Direct 1-Step (D1S) Calculations ================================ diff --git a/openmc/deplete/__init__.py b/openmc/deplete/__init__.py index 8a9509e9009..052e2245963 100644 --- a/openmc/deplete/__init__.py +++ b/openmc/deplete/__init__.py @@ -17,6 +17,7 @@ from .results import * from .integrators import * from .transfer_rates import * +from .r2s import * from . import abc from . import cram from . import helpers diff --git a/openmc/deplete/microxs.py b/openmc/deplete/microxs.py index 4ce199f0cc1..e351c923df0 100644 --- a/openmc/deplete/microxs.py +++ b/openmc/deplete/microxs.py @@ -8,8 +8,9 @@ from collections.abc import Sequence import shutil from tempfile import TemporaryDirectory -from typing import Union, TypeAlias +from typing import Union, TypeAlias, Self +import h5py import pandas as pd import numpy as np @@ -20,6 +21,7 @@ import openmc from .chain import Chain, REACTIONS, _get_chain from .coupled_operator import _find_cross_sections, _get_nuclides_with_data +from ..utility_funcs import h5py_file_or_group import openmc.lib from openmc.mpi import comm @@ -47,6 +49,7 @@ def get_microxs_and_flux( reaction_rate_mode: str = 'direct', chain_file: PathLike | Chain | None = None, path_statepoint: PathLike | None = None, + path_input: PathLike | None = None, run_kwargs=None ) -> tuple[list[np.ndarray], list[MicroXS]]: """Generate microscopic cross sections and fluxes for multiple domains. @@ -59,7 +62,7 @@ def get_microxs_and_flux( .. versionadded:: 0.14.0 .. versionchanged:: 0.15.3 - Added `reaction_rate_mode` and `path_statepoint` arguments. + Added `reaction_rate_mode`, `path_statepoint`, `path_input` arguments. Parameters ---------- @@ -90,6 +93,10 @@ def get_microxs_and_flux( Path to write the statepoint file from the neutron transport solve to. By default, The statepoint file is written to a temporary directory and is not kept. + path_input : path-like, optional + Path to write the model XML file from the neutron transport solve to. + By default, the model XML file is written to a temporary directory and + not kept. run_kwargs : dict, optional Keyword arguments passed to :meth:`openmc.Model.run` @@ -108,7 +115,7 @@ def get_microxs_and_flux( check_value('reaction_rate_mode', reaction_rate_mode, {'direct', 'flux'}) # Save any original tallies on the model - original_tallies = model.tallies + original_tallies = list(model.tallies) # Determine what reactions and nuclides are available in chain chain = _get_chain(chain_file) @@ -178,6 +185,10 @@ def get_microxs_and_flux( shutil.move(statepoint_path, path_statepoint) statepoint_path = path_statepoint + # Export the model to path_input if provided + if path_input is not None: + model.export_to_model_xml(path_input) + with StatePoint(statepoint_path) as sp: if reaction_rate_mode == 'direct': rr_tally = sp.tallies[rr_tally.id] @@ -383,8 +394,7 @@ def from_csv(cls, csv_file, **kwargs): MicroXS """ - if 'float_precision' not in kwargs: - kwargs['float_precision'] = 'round_trip' + kwargs.setdefault('float_precision', 'round_trip') df = pd.read_csv(csv_file, **kwargs) df.set_index(['nuclides', 'reactions', 'groups'], inplace=True) @@ -419,3 +429,96 @@ def to_csv(self, *args, **kwargs): ) df = pd.DataFrame({'xs': self.data.flatten()}, index=multi_index) df.to_csv(*args, **kwargs) + + def to_hdf5(self, group_or_filename: h5py.Group | PathLike, **kwargs): + """Export microscopic cross section data to HDF5 format + + Parameters + ---------- + group_or_filename : h5py.Group or path-like + HDF5 group or filename to write to + kwargs : dict, optional + Keyword arguments to pass to :meth:`h5py.Group.create_dataset`. + Defaults to {'compression': 'lzf'}. + + """ + kwargs.setdefault('compression', 'lzf') + + with h5py_file_or_group(group_or_filename, 'w') as group: + # Store cross section data as 3D dataset + group.create_dataset('data', data=self.data, **kwargs) + + # Store metadata as datasets using string encoding + group.create_dataset('nuclides', data=np.array(self.nuclides, dtype='S')) + group.create_dataset('reactions', data=np.array(self.reactions, dtype='S')) + + @classmethod + def from_hdf5(cls, group_or_filename: h5py.Group | PathLike) -> Self: + """Load data from an HDF5 file + + Parameters + ---------- + group_or_filename : h5py.Group or str or PathLike + HDF5 group or path to HDF5 file. If given as an h5py.Group, the + data is read from that group. If given as a string, it is assumed + to be the filename for the HDF5 file. + + Returns + ------- + MicroXS + """ + + with h5py_file_or_group(group_or_filename, 'r') as group: + # Read data from HDF5 group + data = group['data'][:] + nuclides = [nuc.decode('utf-8') for nuc in group['nuclides'][:]] + reactions = [rxn.decode('utf-8') for rxn in group['reactions'][:]] + + return cls(data, nuclides, reactions) + + +def write_microxs_hdf5( + micros: Sequence[MicroXS], + filename: PathLike, + names: Sequence[str] | None = None, + **kwargs +): + """Write multiple MicroXS objects to an HDF5 file + + Parameters + ---------- + micros : list of MicroXS + List of MicroXS objects + filename : PathLike + Output HDF5 filename + names : list of str, optional + Names for each MicroXS object. If None, uses 'domain_0', 'domain_1', + etc. + **kwargs + Additional keyword arguments passed to :meth:`h5py.Group.create_dataset` + """ + if names is None: + names = [f'domain_{i}' for i in range(len(micros))] + + # Open file once and write all domains using group interface + with h5py.File(filename, 'w') as f: + for microxs, name in zip(micros, names): + group = f.create_group(name) + microxs.to_hdf5(group, **kwargs) + + +def read_microxs_hdf5(filename: PathLike) -> dict[str, MicroXS]: + """Read multiple MicroXS objects from an HDF5 file + + Parameters + ---------- + filename : path-like + HDF5 filename + + Returns + ------- + dict + Dictionary mapping domain names to MicroXS objects + """ + with h5py.File(filename, 'r') as f: + return {name: MicroXS.from_hdf5(group) for name, group in f.items()} diff --git a/openmc/deplete/r2s.py b/openmc/deplete/r2s.py new file mode 100644 index 00000000000..7b5deddc23c --- /dev/null +++ b/openmc/deplete/r2s.py @@ -0,0 +1,680 @@ +from __future__ import annotations +from collections.abc import Sequence +import copy +from datetime import datetime +import json +from pathlib import Path + +import numpy as np +import openmc +from . import IndependentOperator, PredictorIntegrator +from .microxs import get_microxs_and_flux, write_microxs_hdf5, read_microxs_hdf5 +from .results import Results +from ..checkvalue import PathLike + + +def get_activation_materials( + model: openmc.Model, mmv: openmc.MeshMaterialVolumes +) -> openmc.Materials: + """Get a list of activation materials for each mesh element/material. + + When performing a mesh-based R2S calculation, a unique material is needed + for each activation region, which is a combination of a mesh element and a + material within that mesh element. This function generates a list of such + materials, each with a unique name and volume corresponding to the mesh + element and material. + + Parameters + ---------- + model : openmc.Model + The full model containing the geometry and materials. + mmv : openmc.MeshMaterialVolumes + The mesh material volumes object containing the materials and their + volumes for each mesh element. + + Returns + ------- + openmc.Materials + A list of materials, each corresponding to a unique mesh element and + material combination. + + """ + # Get the material ID, volume, and element index for each element-material + # combination + mat_ids = mmv._materials[mmv._materials > -1] + volumes = mmv._volumes[mmv._materials > -1] + elems, _ = np.where(mmv._materials > -1) + + # Get all materials in the model + material_dict = model._get_all_materials() + + # Create a new activation material for each element-material combination + materials = openmc.Materials() + for elem, mat_id, vol in zip(elems, mat_ids, volumes): + mat = material_dict[mat_id] + new_mat = mat.clone() + new_mat.depletable = True + new_mat.name = f'Element {elem}, Material {mat_id}' + new_mat.volume = vol + materials.append(new_mat) + + return materials + + +class R2SManager: + """Manager for Rigorous 2-Step (R2S) method calculations. + + This class is responsible for managing the materials and sources needed for + mesh-based or cell-based R2S calculations. It provides methods to get + activation materials and decay photon sources based on the mesh/cells and + materials in the OpenMC model. + + This class supports the use of a different models for the neutron and photon + transport calculation. However, for cell-based calculations, it assumes that + the only changes in the model are material assignments. For mesh-based + calculations, it checks material assignments in the photon model and any + element--material combinations that don't appear in the photon model are + skipped. + + Parameters + ---------- + neutron_model : openmc.Model + The OpenMC model to use for neutron transport. + domains : openmc.MeshBase or Sequence[openmc.Cell] + The mesh or a sequence of cells that represent the spatial units over + which the R2S calculation will be performed. + photon_model : openmc.Model, optional + The OpenMC model to use for photon transport calculations. If None, a + shallow copy of the neutron_model will be created and used. + + Attributes + ---------- + domains : openmc.MeshBase or Sequence[openmc.Cell] + The mesh or a sequence of cells that represent the spatial units over + which the R2S calculation will be performed. + neutron_model : openmc.Model + The OpenMC model used for neutron transport. + photon_model : openmc.Model + The OpenMC model used for photon transport calculations. + method : {'mesh-based', 'cell-based'} + Indicates whether the R2S calculation uses mesh elements ('mesh-based') + as the spatial discetization or a list of a cells ('cell-based'). + results : dict + A dictionary that stores results from the R2S calculation. + + """ + def __init__( + self, + neutron_model: openmc.Model, + domains: openmc.MeshBase | Sequence[openmc.Cell], + photon_model: openmc.Model | None = None, + ): + self.neutron_model = neutron_model + if photon_model is None: + # Create a shallow copy of the neutron model for photon transport + self.photon_model = openmc.Model( + geometry=copy.copy(neutron_model.geometry), + materials=copy.copy(neutron_model.materials), + settings=copy.copy(neutron_model.settings), + tallies=copy.copy(neutron_model.tallies), + plots=copy.copy(neutron_model.plots), + ) + else: + self.photon_model = photon_model + if isinstance(domains, openmc.MeshBase): + self.method = 'mesh-based' + else: + self.method = 'cell-based' + self.domains = domains + self.results = {} + + def run( + self, + timesteps: Sequence[float] | Sequence[tuple[float, str]], + source_rates: float | Sequence[float], + timestep_units: str = 's', + photon_time_indices: Sequence[int] | None = None, + output_dir: PathLike | None = None, + bounding_boxes: dict[int, openmc.BoundingBox] | None = None, + chain_file: PathLike | None = None, + micro_kwargs: dict | None = None, + mat_vol_kwargs: dict | None = None, + run_kwargs: dict | None = None, + operator_kwargs: dict | None = None, + ): + """Run the R2S calculation. + + Parameters + ---------- + timesteps : Sequence[float] or Sequence[tuple[float, str]] + Sequence of timesteps. Note that values are not cumulative. The + units are specified by the `timestep_units` argument when + `timesteps` is an iterable of float. Alternatively, units can be + specified for each step by passing an iterable of (value, unit) + tuples. + source_rates : float or Sequence[float] + Source rate in [neutron/sec] for each interval in `timesteps`. + timestep_units : {'s', 'min', 'h', 'd', 'a'}, optional + Units for values specified in the `timesteps` argument when passing + float values. 's' means seconds, 'min' means minutes, 'h' means + hours, 'd' means days, and 'a' means years (Julian). + photon_time_indices : Sequence[int], optional + Sequence of time indices at which photon transport should be run; + represented as indices into the array of times formed by the + timesteps. For example, if two timesteps are specified, the array of + times would contain three entries, and [2] would indicate computing + photon results at the last time. A value of None indicates to run + photon transport for each time. + output_dir : PathLike, optional + Path to directory where R2S calculation outputs will be saved. If + not provided, a timestamped directory 'r2s_YYYY-MM-DDTHH-MM-SS' is + created. Subdirectories will be created for the neutron transport, + activation, and photon transport steps. + bounding_boxes : dict[int, openmc.BoundingBox], optional + Dictionary mapping cell IDs to bounding boxes used for spatial + source sampling in cell-based R2S calculations. Required if method + is 'cell-based'. + chain_file : PathLike, optional + Path to the depletion chain XML file to use during activation. If + not provided, the default configured chain file will be used. + micro_kwargs : dict, optional + Additional keyword arguments passed to + :func:`openmc.deplete.get_microxs_and_flux` during the neutron + transport step. + mat_vol_kwargs : dict, optional + Additional keyword arguments passed to + :meth:`openmc.MeshBase.material_volumes`. + run_kwargs : dict, optional + Additional keyword arguments passed to :meth:`openmc.Model.run` + during the neutron and photon transport step. By default, output is + disabled. + operator_kwargs : dict, optional + Additional keyword arguments passed to + :class:`openmc.deplete.IndependentOperator`. + + Returns + ------- + Path + Path to the output directory containing all calculation results + """ + + if output_dir is None: + stamp = datetime.now().strftime('%Y-%m-%dT%H-%M-%S') + output_dir = Path(f'r2s_{stamp}') + + # Set run_kwargs for the neutron transport step + if micro_kwargs is None: + micro_kwargs = {} + if run_kwargs is None: + run_kwargs = {} + if operator_kwargs is None: + operator_kwargs = {} + run_kwargs.setdefault('output', False) + micro_kwargs.setdefault('run_kwargs', run_kwargs) + # If a chain file is provided, prefer it for steps 1 and 2 + if chain_file is not None: + micro_kwargs.setdefault('chain_file', chain_file) + operator_kwargs.setdefault('chain_file', chain_file) + + self.step1_neutron_transport( + output_dir / 'neutron_transport', mat_vol_kwargs, micro_kwargs + ) + self.step2_activation( + timesteps, source_rates, timestep_units, output_dir / 'activation', + operator_kwargs=operator_kwargs + ) + self.step3_photon_transport( + photon_time_indices, bounding_boxes, output_dir / 'photon_transport', + mat_vol_kwargs=mat_vol_kwargs, run_kwargs=run_kwargs + ) + + return output_dir + + def step1_neutron_transport( + self, + output_dir: PathLike = "neutron_transport", + mat_vol_kwargs: dict | None = None, + micro_kwargs: dict | None = None + ): + """Run the neutron transport step. + + This step computes the material volume fractions on the mesh, creates a + mesh-material filter, and retrieves the fluxes and microscopic cross + sections for each mesh/material combination. This step will populate the + 'fluxes' and 'micros' keys in the results dictionary. For a mesh-based + calculation, it will also populate the 'mesh_material_volumes' key. + + Parameters + ---------- + output_dir : PathLike, optional + The directory where the results will be saved. + mat_vol_kwargs : dict, optional + Additional keyword arguments based to + :meth:`openmc.MeshBase.material_volumes`. + micro_kwargs : dict, optional + Additional keyword arguments passed to + :func:`openmc.deplete.get_microxs_and_flux`. + + """ + + output_dir = Path(output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + + if self.method == 'mesh-based': + # Compute material volume fractions on the mesh + if mat_vol_kwargs is None: + mat_vol_kwargs = {} + self.results['mesh_material_volumes'] = mmv = \ + self.domains.material_volumes(self.neutron_model, **mat_vol_kwargs) + + # Save results to file + mmv.save(output_dir / 'mesh_material_volumes.npz') + + # Create mesh-material filter based on what combos were found + domains = openmc.MeshMaterialFilter.from_volumes(self.domains, mmv) + else: + domains: Sequence[openmc.Cell] = self.domains + + # Check to make sure that each cell is filled with a material and + # that the volume has been set + + # TODO: If volumes are not set, run volume calculation for cells + for cell in domains: + if cell.fill is None: + raise ValueError( + f"Cell {cell.id} is not filled with a materials. " + "Please set the fill material for each cell before " + "running the R2S calculation." + ) + if cell.volume is None: + raise ValueError( + f"Cell {cell.id} does not have a volume set. " + "Please set the volume for each cell before running " + "the R2S calculation." + ) + + # Set default keyword arguments for microxs and flux calculation + if micro_kwargs is None: + micro_kwargs = {} + micro_kwargs.setdefault('path_statepoint', output_dir / 'statepoint.h5') + micro_kwargs.setdefault('path_input', output_dir / 'model.xml') + + # Run neutron transport and get fluxes and micros + self.results['fluxes'], self.results['micros'] = get_microxs_and_flux( + self.neutron_model, domains, **micro_kwargs) + + # Save flux and micros to file + np.save(output_dir / 'fluxes.npy', self.results['fluxes']) + write_microxs_hdf5(self.results['micros'], output_dir / 'micros.h5') + + def step2_activation( + self, + timesteps: Sequence[float] | Sequence[tuple[float, str]], + source_rates: float | Sequence[float], + timestep_units: str = 's', + output_dir: PathLike = 'activation', + operator_kwargs: dict | None = None, + ): + """Run the activation step. + + This step creates a unique copy of each activation material based on the + mesh elements or cells, then solves the depletion equations for each + material using the fluxes and microscopic cross sections obtained in the + neutron transport step. This step will populate the 'depletion_results' + and 'activation_materials' keys in the results dictionary. + + Parameters + ---------- + timesteps : Sequence[float] or Sequence[tuple[float, str]] + Sequence of timesteps. Note that values are not cumulative. The + units are specified by the `timestep_units` argument when + `timesteps` is an iterable of float. Alternatively, units can be + specified for each step by passing an iterable of (value, unit) + tuples. + source_rates : float | Sequence[float] + Source rate in [neutron/sec] for each interval in `timesteps`. + timestep_units : {'s', 'min', 'h', 'd', 'a'}, optional + Units for values specified in the `timesteps` argument when passing + float values. 's' means seconds, 'min' means minutes, 'h' means + hours, 'd' means days, and 'a' means years (Julian). + output_dir : PathLike, optional + Path to directory where activation calculation outputs will be + saved. + operator_kwargs : dict, optional + Additional keyword arguments passed to + :class:`openmc.deplete.IndependentOperator`. + """ + + if self.method == 'mesh-based': + # Get unique material for each (mesh, material) combination + mmv = self.results['mesh_material_volumes'] + self.results['activation_materials'] = get_activation_materials(self.neutron_model, mmv) + else: + # Create unique material for each cell + activation_mats = openmc.Materials() + for cell in self.domains: + mat = cell.fill.clone() + mat.name = f'Cell {cell.id}' + mat.depletable = True + mat.volume = cell.volume + activation_mats.append(mat) + self.results['activation_materials'] = activation_mats + + # Save activation materials to file + output_dir = Path(output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + self.results['activation_materials'].export_to_xml( + output_dir / 'materials.xml') + + # Create depletion operator for the activation materials + if operator_kwargs is None: + operator_kwargs = {} + operator_kwargs.setdefault('normalization_mode', 'source-rate') + op = IndependentOperator( + self.results['activation_materials'], + self.results['fluxes'], + self.results['micros'], + **operator_kwargs + ) + + # Create time integrator and solve depletion equations + integrator = PredictorIntegrator( + op, timesteps, source_rates=source_rates, timestep_units=timestep_units + ) + output_path = output_dir / 'depletion_results.h5' + integrator.integrate(final_step=False, path=output_path) + + # Get depletion results + self.results['depletion_results'] = Results(output_path) + + def step3_photon_transport( + self, + time_indices: Sequence[int] | None = None, + bounding_boxes: dict[int, openmc.BoundingBox] | None = None, + output_dir: PathLike = 'photon_transport', + mat_vol_kwargs: dict | None = None, + run_kwargs: dict | None = None, + ): + """Run the photon transport step. + + This step performs photon transport calculations using decay photon + sources created from the activated materials. For each specified time, + it creates appropriate photon sources and runs a transport calculation. + In mesh-based mode, the sources are created using the mesh material + volumes, while in cell-based mode, they are created using bounding boxes + for each cell. This step will populate the 'photon_tallies' key in the + results dictionary. + + Parameters + ---------- + time_indices : Sequence[int], optional + Sequence of time indices at which photon transport should be run; + represented as indices into the array of times formed by the + timesteps. For example, if two timesteps are specified, the array of + times would contain three entries, and [2] would indicate computing + photon results at the last time. A value of None indicates to run + photon transport for each time. + bounding_boxes : dict[int, openmc.BoundingBox], optional + Dictionary mapping cell IDs to bounding boxes used for spatial + source sampling in cell-based R2S calculations. Required if method + is 'cell-based'. + output_dir : PathLike, optional + Path to directory where photon transport outputs will be saved. + mat_vol_kwargs : dict, optional + Additional keyword arguments passed to + :meth:`openmc.MeshBase.material_volumes`. + run_kwargs : dict, optional + Additional keyword arguments passed to :meth:`openmc.Model.run` + during the photon transport step. By default, output is disabled. + """ + + # TODO: Automatically determine bounding box for each cell + if bounding_boxes is None and self.method == 'cell-based': + raise ValueError("bounding_boxes must be provided for cell-based " + "R2S calculations.") + + # Set default run arguments if not provided + if run_kwargs is None: + run_kwargs = {} + run_kwargs.setdefault('output', False) + + # Write out JSON file with tally IDs that can be used for loading + # results + output_dir = Path(output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + + # Get default time indices if not provided + if time_indices is None: + n_steps = len(self.results['depletion_results']) + time_indices = list(range(n_steps)) + + # Check whether the photon model is different + neutron_univ = self.neutron_model.geometry.root_universe + photon_univ = self.photon_model.geometry.root_universe + different_photon_model = (neutron_univ != photon_univ) + + # For mesh-based calculations, compute material volume fractions for the + # photon model if it is different from the neutron model to account for + # potential material changes + if self.method == 'mesh-based' and different_photon_model: + self.results['mesh_material_volumes_photon'] = photon_mmv = \ + self.domains.material_volumes(self.photon_model, **mat_vol_kwargs) + + # Save photon MMV results to file + photon_mmv.save(output_dir / 'mesh_material_volumes.npz') + + tally_ids = [tally.id for tally in self.photon_model.tallies] + with open(output_dir / 'tally_ids.json', 'w') as f: + json.dump(tally_ids, f) + + self.results['photon_tallies'] = {} + + # Get dictionary of cells in the photon model + if different_photon_model: + photon_cells = self.photon_model.geometry.get_all_cells() + + for time_index in time_indices: + # Create decay photon source + if self.method == 'mesh-based': + self.photon_model.settings.source = \ + self.get_decay_photon_source_mesh(time_index) + else: + sources = [] + results = self.results['depletion_results'] + for cell, original_mat in zip(self.domains, self.results['activation_materials']): + # Skip if the cell is not in the photon model or the + # material has changed + if different_photon_model: + if cell.id not in photon_cells or \ + cell.fill.id != photon_cells[cell.id].fill.id: + continue + + # Get bounding box for the cell + bounding_box = bounding_boxes[cell.id] + + # Get activated material composition + activated_mat = results[time_index].get_material(str(original_mat.id)) + + # Create decay photon source source + space = openmc.stats.Box(*bounding_box) + energy = activated_mat.get_decay_photon_energy() + strength = energy.integral() if energy is not None else 0.0 + source = openmc.IndependentSource( + space=space, + energy=energy, + particle='photon', + strength=strength, + constraints={'domains': [cell]} + ) + sources.append(source) + self.photon_model.settings.source = sources + + # Convert time_index (which may be negative) to a normal index + if time_index < 0: + time_index = len(self.results['depletion_results']) + time_index + + # Run photon transport calculation + run_kwargs['cwd'] = Path(output_dir) / f'time_{time_index}' + statepoint_path = self.photon_model.run(**run_kwargs) + + # Store tally results + with openmc.StatePoint(statepoint_path) as sp: + self.results['photon_tallies'][time_index] = [ + sp.tallies[tally.id] for tally in self.photon_model.tallies + ] + + def get_decay_photon_source_mesh( + self, + time_index: int = -1 + ) -> list[openmc.MeshSource]: + """Create decay photon source for a mesh-based calculation. + + This function creates N :class:`MeshSource` objects where N is the + maximum number of unique materials that appears in a single mesh + element. For each mesh element-material combination, and + IndependentSource instance is created with a spatial constraint limited + the sampled decay photons to the correct region. + + When the photon transport model is different from the neutron model, the + photon MeshMaterialVolumes is used to determine whether an (element, + material) combination exists in the photon model. + + Parameters + ---------- + time_index : int, optional + Time index for the decay photon source. Default is -1 (last time). + + Returns + ------- + list of openmc.MeshSource + A list of MeshSource objects, each containing IndependentSource + instances for the decay photons in the corresponding mesh element. + + """ + mat_dict = self.neutron_model._get_all_materials() + + # Some MeshSource objects will have empty positions; create a "null source" + # that is used for this case + null_source = openmc.IndependentSource(particle='photon', strength=0.0) + + # List to hold sources for each MeshSource (length = N) + source_lists = [] + + # Index in the overall list of activated materials + index_mat = 0 + + # Get various results from previous steps + mat_vols = self.results['mesh_material_volumes'] + materials = self.results['activation_materials'] + results = self.results['depletion_results'] + photon_mat_vols = self.results.get('mesh_material_volumes_photon') + + # Total number of mesh elements + n_elements = mat_vols.num_elements + + for index_elem in range(n_elements): + # Determine which materials exist in the photon model for this element + if photon_mat_vols is not None: + photon_materials = { + mat_id + for mat_id, _ in photon_mat_vols.by_element(index_elem) + if mat_id is not None + } + + for j, (mat_id, _) in enumerate(mat_vols.by_element(index_elem)): + # Skip void volume + if mat_id is None: + continue + + # Skip if this material doesn't exist in photon model + if photon_mat_vols is not None and mat_id not in photon_materials: + index_mat += 1 + continue + + # Check whether a new MeshSource object is needed + if j >= len(source_lists): + source_lists.append([null_source]*n_elements) + + # Get activated material composition + original_mat = materials[index_mat] + activated_mat = results[time_index].get_material(str(original_mat.id)) + + # Create decay photon source source + energy = activated_mat.get_decay_photon_energy() + if energy is not None: + strength = energy.integral() + source_lists[j][index_elem] = openmc.IndependentSource( + energy=energy, + particle='photon', + strength=strength, + constraints={'domains': [mat_dict[mat_id]]} + ) + + # Increment index of activated material + index_mat += 1 + + # Return list of mesh sources + return [openmc.MeshSource(self.domains, sources) for sources in source_lists] + + def load_results(self, path: PathLike): + """Load results from a previous R2S calculation. + + Parameters + ---------- + path : PathLike + Path to the directory containing the R2S calculation results. + + """ + path = Path(path) + + # Load neutron transport results + neutron_dir = path / 'neutron_transport' + if self.method == 'mesh-based': + mmv_file = neutron_dir / 'mesh_material_volumes.npz' + if mmv_file.exists(): + self.results['mesh_material_volumes'] = \ + openmc.MeshMaterialVolumes.from_npz(mmv_file) + fluxes_file = neutron_dir / 'fluxes.npy' + if fluxes_file.exists(): + self.results['fluxes'] = list(np.load(fluxes_file, allow_pickle=True)) + micros_dict = read_microxs_hdf5(neutron_dir / 'micros.h5') + self.results['micros'] = [ + micros_dict[f'domain_{i}'] for i in range(len(micros_dict)) + ] + + # Load activation results + activation_dir = path / 'activation' + activation_results = activation_dir / 'depletion_results.h5' + if activation_results.exists(): + self.results['depletion_results'] = Results(activation_results) + activation_mats_file = activation_dir / 'materials.xml' + if activation_mats_file.exists(): + self.results['activation_materials'] = \ + openmc.Materials.from_xml(activation_mats_file) + + # Load photon transport results + photon_dir = path / 'photon_transport' + + # Load photon mesh material volumes if they exist (for mesh-based calculations) + if self.method == 'mesh-based': + photon_mmv_file = photon_dir / 'mesh_material_volumes.npz' + if photon_mmv_file.exists(): + self.results['mesh_material_volumes_photon'] = \ + openmc.MeshMaterialVolumes.from_npz(photon_mmv_file) + + # Load tally IDs from JSON file + tally_ids_path = photon_dir / 'tally_ids.json' + if tally_ids_path.exists(): + with tally_ids_path.open('r') as f: + tally_ids = json.load(f) + self.results['photon_tallies'] = {} + + # For each photon transport calc, load the statepoint and get the + # tally results based on tally_ids + for time_dir in photon_dir.glob('time_*'): + time_index = int(time_dir.name.split('_')[1]) + for sp_path in time_dir.glob('statepoint.*.h5'): + with openmc.StatePoint(sp_path) as sp: + self.results['photon_tallies'][time_index] = [ + sp.tallies[tally_id] for tally_id in tally_ids + ] diff --git a/openmc/mesh.py b/openmc/mesh.py index 9601207e912..3d93d87a1b1 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -325,20 +325,10 @@ def get_homogenized_materials( vols = material_volumes mat_volume_by_element = [vols.by_element(i) for i in range(vols.num_elements)] - # Create homogenized material for each element - materials = model.geometry.get_all_materials() - - # Account for materials in DAGMC universes - # TODO: This should really get incorporated in lower-level calls to - # get_all_materials, but right now it requires information from the - # Model object - for cell in model.geometry.get_all_cells().values(): - if isinstance(cell.fill, openmc.DAGMCUniverse): - names = cell.fill.material_names - materials.update({ - mat.id: mat for mat in model.materials if mat.name in names - }) + # Get dictionary of all materials + materials = model._get_all_materials() + # Create homogenized material for each element homogenized_materials = [] for mat_volume_list in mat_volume_by_element: material_ids, volumes = [list(x) for x in zip(*mat_volume_list)] @@ -410,7 +400,7 @@ def material_volumes( # In order to get mesh into model, we temporarily replace the # tallies with a single mesh tally using the current mesh - original_tallies = model.tallies + original_tallies = list(model.tallies) new_tally = openmc.Tally() new_tally.filters = [openmc.MeshFilter(self)] new_tally.scores = ['flux'] diff --git a/openmc/model/model.py b/openmc/model/model.py index 7963751d567..64294c23cbb 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -212,6 +212,29 @@ def _materials_by_name(self) -> dict[int, openmc.Material]: result[mat.name].add(mat) return result + # TODO: This should really get incorporated in lower-level calls to + # get_all_materials, but right now it requires information from the Model object + def _get_all_materials(self) -> dict[int, openmc.Material]: + """Get all materials including those in DAGMC universes + + Returns + ------- + dict + Dictionary mapping material ID to material instances + """ + # Get all materials from the Geometry object + materials = self.geometry.get_all_materials() + + # Account for materials in DAGMC universes + for cell in self.geometry.get_all_cells().values(): + if isinstance(cell.fill, openmc.DAGMCUniverse): + names = cell.fill.material_names + materials.update({ + mat.id: mat for mat in self.materials if mat.name in names + }) + + return materials + def add_kinetics_parameters_tallies(self, num_groups: int | None = None): """Add tallies for calculating kinetics parameters using the IFP method. diff --git a/openmc/utility_funcs.py b/openmc/utility_funcs.py index da9f73b1651..935a589853d 100644 --- a/openmc/utility_funcs.py +++ b/openmc/utility_funcs.py @@ -3,6 +3,8 @@ from pathlib import Path from tempfile import TemporaryDirectory +import h5py + import openmc from .checkvalue import PathLike @@ -57,3 +59,20 @@ def input_path(filename: PathLike) -> Path: return Path(filename).resolve() else: return Path(filename) + + +@contextmanager +def h5py_file_or_group(group_or_filename: PathLike | h5py.Group, *args, **kwargs): + """Context manager for opening an HDF5 file or using an existing group + + Parameters + ---------- + group_or_filename : path-like or h5py.Group + Path to HDF5 file, or group from an existing HDF5 file + + """ + if isinstance(group_or_filename, h5py.Group): + yield group_or_filename + else: + with h5py.File(group_or_filename, *args, **kwargs) as f: + yield f diff --git a/tests/unit_tests/test_r2s.py b/tests/unit_tests/test_r2s.py new file mode 100644 index 00000000000..a94f85c8c08 --- /dev/null +++ b/tests/unit_tests/test_r2s.py @@ -0,0 +1,152 @@ +from pathlib import Path + +import pytest +import openmc +from openmc.deplete import Chain, R2SManager + + +@pytest.fixture +def simple_model_and_mesh(tmp_path): + # Define two materials: water and Ni + h2o = openmc.Material() + h2o.add_nuclide("H1", 2.0) + h2o.add_nuclide("O16", 1.0) + h2o.set_density("g/cm3", 1.0) + nickel = openmc.Material() + nickel.add_element("Ni", 1.0) + nickel.set_density("g/cm3", 4.0) + + # Geometry: two half-spaces split by x=0 plane + left = openmc.XPlane(0.0) + x_min = openmc.XPlane(-10.0, boundary_type='vacuum') + x_max = openmc.XPlane(10.0, boundary_type='vacuum') + y_min = openmc.YPlane(-10.0, boundary_type='vacuum') + y_max = openmc.YPlane(10.0, boundary_type='vacuum') + z_min = openmc.ZPlane(-10.0, boundary_type='vacuum') + z_max = openmc.ZPlane(10.0, boundary_type='vacuum') + + c1 = openmc.Cell(fill=h2o, region=+x_min & -left & +y_min & -y_max & +z_min & -z_max) + c2 = openmc.Cell(fill=nickel, region=+left & -x_max & +y_min & -y_max & +z_min & -z_max) + c1.volume = 4000.0 + c2.volume = 4000.0 + geometry = openmc.Geometry([c1, c2]) + + # Simple settings with a point source + settings = openmc.Settings() + settings.batches = 10 + settings.particles = 1000 + settings.run_mode = 'fixed source' + settings.source = openmc.IndependentSource() + model = openmc.Model(geometry, settings=settings) + + mesh = openmc.RegularMesh() + mesh.lower_left = (-10.0, -10.0, -10.0) + mesh.upper_right = (10.0, 10.0, 10.0) + mesh.dimension = (1, 1, 1) + return model, (c1, c2), mesh + + +def test_r2s_mesh_expected_output(simple_model_and_mesh, tmp_path): + model, (c1, c2), mesh = simple_model_and_mesh + + # Use mesh-based domains + r2s = R2SManager(model, mesh) + + # Use custom reduced chain file for Ni + chain = Chain.from_xml(Path(__file__).parents[1] / "chain_ni.xml") + + # Run R2S calculation + outdir = r2s.run( + timesteps=[(1.0, 'd')], + source_rates=[1.0], + photon_time_indices=[1], + output_dir=tmp_path, + chain_file=chain, + ) + + # Check directories and files exist + nt = Path(outdir) / 'neutron_transport' + assert (nt / 'fluxes.npy').exists() + assert (nt / 'micros.h5').exists() + assert (nt / 'mesh_material_volumes.npz').exists() + act = Path(outdir) / 'activation' + assert (act / 'depletion_results.h5').exists() + pt = Path(outdir) / 'photon_transport' + assert (pt / 'tally_ids.json').exists() + assert (pt / 'time_1' / 'statepoint.10.h5').exists() + + # Basic results structure checks + assert len(r2s.results['fluxes']) == 2 + assert len(r2s.results['micros']) == 2 + assert len(r2s.results['mesh_material_volumes']) == 2 + assert len(r2s.results['activation_materials']) == 2 + assert len(r2s.results['depletion_results']) == 2 + + # Check activation materials + amats = r2s.results['activation_materials'] + assert all(m.depletable for m in amats) + # Volumes preserved + assert {m.volume for m in amats} == {c1.volume, c2.volume} + + # Check loading results + r2s_loaded = R2SManager(model, mesh) + r2s_loaded.load_results(outdir) + assert len(r2s_loaded.results['fluxes']) == 2 + assert len(r2s_loaded.results['micros']) == 2 + assert len(r2s_loaded.results['mesh_material_volumes']) == 2 + assert len(r2s_loaded.results['activation_materials']) == 2 + assert len(r2s_loaded.results['depletion_results']) == 2 + + +def test_r2s_cell_expected_output(simple_model_and_mesh, tmp_path): + model, (c1, c2), _ = simple_model_and_mesh + + # Use cell-based domains + r2s = R2SManager(model, [c1, c2]) + + # Use custom reduced chain file for Ni + chain = Chain.from_xml(Path(__file__).parents[1] / "chain_ni.xml") + + # Run R2S calculation + bounding_boxes = {c1.id: c1.bounding_box, c2.id: c2.bounding_box} + outdir = r2s.run( + timesteps=[(1.0, 'd')], + source_rates=[1.0], + photon_time_indices=[1], + output_dir=tmp_path, + bounding_boxes=bounding_boxes, + chain_file=chain + ) + + # Check directories and files exist + nt = Path(outdir) / 'neutron_transport' + assert (nt / 'fluxes.npy').exists() + assert (nt / 'micros.h5').exists() + act = Path(outdir) / 'activation' + assert (act / 'depletion_results.h5').exists() + pt = Path(outdir) / 'photon_transport' + assert (pt / 'tally_ids.json').exists() + assert (pt / 'time_1' / 'statepoint.10.h5').exists() + + # Basic results structure checks + assert len(r2s.results['fluxes']) == 2 + assert len(r2s.results['micros']) == 2 + assert len(r2s.results['activation_materials']) == 2 + assert len(r2s.results['depletion_results']) == 2 + + # Check activation materials + amats = r2s.results['activation_materials'] + assert all(m.depletable for m in amats) + # Names include cell IDs + assert any(f"Cell {c1.id}" in m.name for m in amats) + assert any(f"Cell {c2.id}" in m.name for m in amats) + # Volumes preserved + assert {m.volume for m in amats} == {c1.volume, c2.volume} + + # Check loading results + r2s_loaded = R2SManager(model, [c1, c2]) + r2s_loaded.load_results(outdir) + assert len(r2s_loaded.results['fluxes']) == 2 + assert len(r2s_loaded.results['micros']) == 2 + assert len(r2s_loaded.results['activation_materials']) == 2 + assert len(r2s_loaded.results['depletion_results']) == 2 From fd964bc9b04dcf2090b212b8307551b32fea810b Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Mon, 3 Nov 2025 09:42:59 +0200 Subject: [PATCH 140/159] Enable specifying reference direction for azimuthal angle in PolarAzimuthal distribution (#3582) Co-authored-by: shimwell --- include/openmc/distribution_multi.h | 2 ++ openmc/stats/multivariate.py | 25 +++++++++++++++- src/distribution_multi.cpp | 15 +++++++++- tests/regression_tests/source/inputs_true.dat | 2 +- .../regression_tests/source/results_true.dat | 2 +- tests/unit_tests/test_stats.py | 30 +++++++++++++++++++ 6 files changed, 72 insertions(+), 4 deletions(-) diff --git a/include/openmc/distribution_multi.h b/include/openmc/distribution_multi.h index 9e84d03d57f..75126593f7d 100644 --- a/include/openmc/distribution_multi.h +++ b/include/openmc/distribution_multi.h @@ -51,6 +51,8 @@ class PolarAzimuthal : public UnitSphereDistribution { Distribution* phi() const { return phi_.get(); } private: + Direction v_ref_ {1.0, 0.0, 0.0}; //!< reference direction + Direction w_ref_; UPtrDist mu_; //!< Distribution of polar angle UPtrDist phi_; //!< Distribution of azimuthal angle }; diff --git a/openmc/stats/multivariate.py b/openmc/stats/multivariate.py index 222d2d18a56..1ce998758ad 100644 --- a/openmc/stats/multivariate.py +++ b/openmc/stats/multivariate.py @@ -79,6 +79,9 @@ class PolarAzimuthal(UnitSphere): reference_uvw : Iterable of float Direction from which polar angle is measured. Defaults to the positive z-direction. + reference_vwu : Iterable of float + Direction from which azimuthal angle is measured. Defaults to the positive + x-direction. Attributes ---------- @@ -89,8 +92,9 @@ class PolarAzimuthal(UnitSphere): """ - def __init__(self, mu=None, phi=None, reference_uvw=(0., 0., 1.)): + def __init__(self, mu=None, phi=None, reference_uvw=(0., 0., 1.), reference_vwu=(1., 0., 0.)): super().__init__(reference_uvw) + self.reference_vwu = reference_vwu if mu is not None: self.mu = mu else: @@ -100,6 +104,20 @@ def __init__(self, mu=None, phi=None, reference_uvw=(0., 0., 1.)): self.phi = phi else: self.phi = Uniform(0., 2*pi) + + @property + def reference_vwu(self): + return self._reference_vwu + + @reference_vwu.setter + def reference_vwu(self, vwu): + cv.check_type('reference v direction', vwu, Iterable, Real) + vwu = np.asarray(vwu) + uvw = self.reference_uvw + cv.check_greater_than('reference v direction must not be parallel to reference u direction', np.linalg.norm(np.cross(vwu,uvw)), 1e-6*np.linalg.norm(vwu)) + vwu -= vwu.dot(uvw)*uvw + cv.check_less_than('reference v direction must be orthogonal to reference u direction', np.abs(vwu.dot(uvw)), 1e-6) + self._reference_vwu = vwu/np.linalg.norm(vwu) @property def mu(self): @@ -132,6 +150,8 @@ def to_xml_element(self): element.set("type", "mu-phi") if self.reference_uvw is not None: element.set("reference_uvw", ' '.join(map(str, self.reference_uvw))) + if self.reference_vwu is not None: + element.set("reference_vwu", ' '.join(map(str, self.reference_vwu))) element.append(self.mu.to_xml_element('mu')) element.append(self.phi.to_xml_element('phi')) return element @@ -155,6 +175,9 @@ def from_xml_element(cls, elem): uvw = get_elem_list(elem, "reference_uvw", float) if uvw is not None: mu_phi.reference_uvw = uvw + vwu = get_elem_list(elem, "reference_vwu", float) + if vwu is not None: + mu_phi.reference_vwu = vwu mu_phi.mu = Univariate.from_xml_element(elem.find('mu')) mu_phi.phi = Univariate.from_xml_element(elem.find('phi')) return mu_phi diff --git a/src/distribution_multi.cpp b/src/distribution_multi.cpp index b7b3efe5268..cdb33adc2ad 100644 --- a/src/distribution_multi.cpp +++ b/src/distribution_multi.cpp @@ -58,6 +58,15 @@ PolarAzimuthal::PolarAzimuthal(Direction u, UPtrDist mu, UPtrDist phi) PolarAzimuthal::PolarAzimuthal(pugi::xml_node node) : UnitSphereDistribution {node} { + // Read reference directional unit vector + if (check_for_node(node, "reference_vwu")) { + auto v_ref = get_node_array(node, "reference_vwu"); + if (v_ref.size() != 3) + fatal_error("Angular distribution reference v direction must have " + "three parameters specified."); + v_ref_ = Direction(v_ref.data()); + } + w_ref_ = u_ref_.cross(v_ref_); if (check_for_node(node, "mu")) { pugi::xml_node node_dist = node.child("mu"); mu_ = distribution_from_xml(node_dist); @@ -79,11 +88,15 @@ Direction PolarAzimuthal::sample(uint64_t* seed) const double mu = mu_->sample(seed); if (mu == 1.0) return u_ref_; + if (mu == -1.0) + return -u_ref_; // Sample azimuthal angle double phi = phi_->sample(seed); - return rotate_angle(u_ref_, mu, &phi, seed); + double f = std::sqrt(1 - mu * mu); + + return mu * u_ref_ + f * std::cos(phi) * v_ref_ + f * std::sin(phi) * w_ref_; } //============================================================================== diff --git a/tests/regression_tests/source/inputs_true.dat b/tests/regression_tests/source/inputs_true.dat index 9f10b79d6b1..0c3764ba6f9 100644 --- a/tests/regression_tests/source/inputs_true.dat +++ b/tests/regression_tests/source/inputs_true.dat @@ -25,7 +25,7 @@ -2.0 0.0 2.0 0.2 0.3 0.2 - + -1.0 0.0 1.0 0.5 0.25 0.25 diff --git a/tests/regression_tests/source/results_true.dat b/tests/regression_tests/source/results_true.dat index 951075bbb90..7d03c696d3e 100644 --- a/tests/regression_tests/source/results_true.dat +++ b/tests/regression_tests/source/results_true.dat @@ -1,2 +1,2 @@ k-combined: -3.034717E-01 2.799386E-03 +3.080655E-01 4.837707E-03 diff --git a/tests/unit_tests/test_stats.py b/tests/unit_tests/test_stats.py index abf143f12a2..998d4b984ce 100644 --- a/tests/unit_tests/test_stats.py +++ b/tests/unit_tests/test_stats.py @@ -516,3 +516,33 @@ def test_combine_distributions(): # uncertainty of the expected value samples = combined.sample(10_000) assert_sample_mean(samples, 0.25) + +def test_reference_vwu_projection(): + """When a non-orthogonal vector is provided, the setter should project out + any component along reference_uvw so the stored vector is orthogonal. + """ + pa = openmc.stats.PolarAzimuthal() # default reference_uvw == (0, 0, 1) + + # Provide a vector that is not orthogonal to (0,0,1) + pa.reference_vwu = (2.0, 0.5, 0.3) + + reference_v = np.asarray(pa.reference_vwu) + reference_u = np.asarray(pa.reference_uvw) + + # reference_v should be orthogonal to reference_u + assert abs(np.dot(reference_v, reference_u)) < 1e-6 + + +def test_reference_vwu_normalization(): + """When a non-normalized vector is provided, the setter should normalize + the projected vector to unit length. + """ + pa = openmc.stats.PolarAzimuthal() # default reference_uvw == (0, 0, 1) + + # Provide a vector that is neither orthogonal to (0,0,1) nor unit-length + pa.reference_vwu = (2.0, 0.5, 0.3) + + reference_v = np.asarray(pa.reference_vwu) + + # reference_v should be unit length + assert np.isclose(np.linalg.norm(reference_v), 1.0, atol=1e-12) From 2d8e006c3de9c8fbc0b010357ebf5a870909c8bc Mon Sep 17 00:00:00 2001 From: Perry <100789850+yrrepy@users.noreply.github.com> Date: Mon, 3 Nov 2025 09:10:30 -0800 Subject: [PATCH 141/159] Enable nuclide filters with get_decay_photon_energy (#3614) Co-authored-by: Paul Romano --- openmc/material.py | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/openmc/material.py b/openmc/material.py index c7b954b6666..1609da05a36 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -290,11 +290,13 @@ def decay_photon_energy(self) -> Univariate | None: return self.get_decay_photon_energy(0.0) def get_decay_photon_energy( - self, - clip_tolerance: float = 1e-6, - units: str = 'Bq', - volume: float | None = None - ) -> Univariate | None: + self, + clip_tolerance: float = 1e-6, + units: str = 'Bq', + volume: float | None = None, + exclude_nuclides: list[str] | None = None, + include_nuclides: list[str] | None = None + ) -> Univariate | None: r"""Return energy distribution of decay photons from unstable nuclides. .. versionadded:: 0.14.0 @@ -302,22 +304,31 @@ def get_decay_photon_energy( Parameters ---------- clip_tolerance : float - Maximum fraction of :math:`\sum_i x_i p_i` for discrete - distributions that will be discarded. + Maximum fraction of :math:`\sum_i x_i p_i` for discrete distributions + that will be discarded. units : {'Bq', 'Bq/g', 'Bq/kg', 'Bq/cm3'} Specifies the units on the integral of the distribution. volume : float, optional Volume of the material. If not passed, defaults to using the :attr:`Material.volume` attribute. + exclude_nuclides : list of str, optional + Nuclides to exclude from the photon source calculation. + include_nuclides : list of str, optional + Nuclides to include in the photon source calculation. If specified, + only these nuclides are used. Returns ------- Univariate or None - Decay photon energy distribution. The integral of this distribution - is the total intensity of the photon source in the requested units. + Decay photon energy distribution. The integral of this distribution is + the total intensity of the photon source in the requested units. """ cv.check_value('units', units, {'Bq', 'Bq/g', 'Bq/kg', 'Bq/cm3'}) + + if exclude_nuclides is not None and include_nuclides is not None: + raise ValueError("Cannot specify both exclude_nuclides and include_nuclides") + if units == 'Bq': multiplier = volume if volume is not None else self.volume if multiplier is None: @@ -332,6 +343,11 @@ def get_decay_photon_energy( dists = [] probs = [] for nuc, atoms_per_bcm in self.get_nuclide_atom_densities().items(): + if exclude_nuclides is not None and nuc in exclude_nuclides: + continue + if include_nuclides is not None and nuc not in include_nuclides: + continue + source_per_atom = openmc.data.decay_photon_energy(nuc) if source_per_atom is not None and atoms_per_bcm > 0.0: dists.append(source_per_atom) From bd76fc056651e33d138d18dbee061c0a7bc83823 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 3 Nov 2025 11:11:57 -0600 Subject: [PATCH 142/159] Fix bug in normalization of tally results with no_reduce (#3619) --- src/tallies/tally.cpp | 7 +++++- tests/unit_tests/test_no_reduce.py | 40 ++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 tests/unit_tests/test_no_reduce.py diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index b9c615ecb5b..12ee3427ebd 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -825,9 +825,14 @@ void Tally::accumulate() total_source = 1.0; } + // Determine number of particles contributing to tally + double contributing_particles = settings::reduce_tallies + ? settings::n_particles + : simulation::work_per_rank; + // Account for number of source particles in normalization double norm = - total_source / (settings::n_particles * settings::gen_per_batch); + total_source / (contributing_particles * settings::gen_per_batch); if (settings::solver_type == SolverType::RANDOM_RAY) { norm = 1.0; diff --git a/tests/unit_tests/test_no_reduce.py b/tests/unit_tests/test_no_reduce.py new file mode 100644 index 00000000000..00ddb5a9590 --- /dev/null +++ b/tests/unit_tests/test_no_reduce.py @@ -0,0 +1,40 @@ +"""Test the settings.no_reduce feature to ensure tallies are correctly +reduced across MPI processes.""" + +import openmc +import pytest + +from tests.testing_harness import config + + +@pytest.mark.parametrize('no_reduce', [True, False]) +def test_no_reduce(no_reduce, run_in_tmpdir): + """Test that tally results are correct with and without no_reduce.""" + + # Create simple sphere model with vacuum + model = openmc.Model() + sphere = openmc.Sphere(r=1.0, boundary_type='vacuum') + cell = openmc.Cell(region=-sphere) + model.geometry = openmc.Geometry([cell]) + model.settings.run_mode = 'fixed source' + model.settings.batches = 10 + model.settings.particles = 100 + model.settings.source = openmc.IndependentSource(space=openmc.stats.Point()) + model.settings.no_reduce = no_reduce + + # Tally: surface current on vacuum boundary + surf_filter = openmc.SurfaceFilter(sphere) + tally = openmc.Tally() + tally.filters = [surf_filter] + tally.scores = ['current'] + model.tallies = [tally] + + # Run OpenMC with proper MPI arguments if needed + kwargs = {'apply_tally_results': True, 'openmc_exec': config['exe']} + if config['mpi']: + kwargs['mpi_args'] = [config['mpiexec'], '-n', config['mpi_np']] + model.run(**kwargs) + + # The tally should be ~1.0 (every particle crosses the surface once) + tally_mean = tally.mean.flatten()[0] + assert tally_mean == pytest.approx(1.0) From e5c7d0ca88983cdd37bb9870cd1fccc8e4301736 Mon Sep 17 00:00:00 2001 From: Jon Shimwell Date: Wed, 5 Nov 2025 17:03:20 +0100 Subject: [PATCH 143/159] Adding vtkhdf option to write vtk data (#3252) Co-authored-by: shimwell Co-authored-by: Jonathan Shimwell Co-authored-by: rherrero-pf <156206440+rherrero-pf@users.noreply.github.com> Co-authored-by: Patrick Shriwise Co-authored-by: Paul Romano --- openmc/mesh.py | 166 ++++++++++++++++-- .../test_mesh_dagmc_tets.vtk | 159 +++++++++++++++++ tests/unit_tests/test_mesh.py | 69 ++++++++ tests/unit_tests/test_mesh_dagmc_tets.vtk | 1 + 4 files changed, 377 insertions(+), 18 deletions(-) create mode 100644 tests/regression_tests/unstructured_mesh/test_mesh_dagmc_tets.vtk create mode 120000 tests/unit_tests/test_mesh_dagmc_tets.vtk diff --git a/openmc/mesh.py b/openmc/mesh.py index 3d93d87a1b1..ce5218b5e97 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -5,6 +5,7 @@ from functools import wraps from math import pi, sqrt, atan2 from numbers import Integral, Real +from pathlib import Path from typing import Protocol import h5py @@ -2443,6 +2444,7 @@ class UnstructuredMesh(MeshBase): _UNSUPPORTED_ELEM = -1 _LINEAR_TET = 0 _LINEAR_HEX = 1 + _VTK_TETRA = 10 def __init__(self, filename: PathLike, library: str, mesh_id: int | None = None, name: str = '', length_multiplier: float = 1.0, @@ -2652,7 +2654,8 @@ def write_vtk_mesh(self, **kwargs): warnings.warn( "The 'UnstructuredMesh.write_vtk_mesh' method has been renamed " "to 'write_data_to_vtk' and will be removed in a future version " - " of OpenMC.", FutureWarning + " of OpenMC.", + FutureWarning, ) self.write_data_to_vtk(**kwargs) @@ -2670,9 +2673,10 @@ def write_data_to_vtk( Parameters ---------- filename : str or pathlib.Path - Name of the VTK file to write. If the filename ends in '.vtu' then a - binary VTU format file will be written, if the filename ends in - '.vtk' then a legacy VTK file will be written. + Name of the VTK file to write. If the filename ends in '.vtkhdf' + then a VTKHDF format file will be written. If the filename ends in + '.vtu' then a binary VTU format file will be written. If the + filename ends in '.vtk' then a legacy VTK file will be written. datasets : dict Dictionary whose keys are the data labels and values are numpy appropriately sized arrays of the data @@ -2680,6 +2684,35 @@ def write_data_to_vtk( Whether or not to normalize the data by the volume of the mesh elements """ + + if Path(filename).suffix == ".vtkhdf": + + self._write_data_to_vtk_hdf5_format( + filename=filename, + datasets=datasets, + volume_normalization=volume_normalization, + ) + + elif Path(filename).suffix == ".vtk" or Path(filename).suffix == ".vtu": + + self._write_data_to_vtk_ascii_format( + filename=filename, + datasets=datasets, + volume_normalization=volume_normalization, + ) + + else: + raise ValueError( + "Unsupported file extension, The filename must end with " + "'.vtkhdf', '.vtu' or '.vtk'" + ) + + def _write_data_to_vtk_ascii_format( + self, + filename: PathLike | None = None, + datasets: dict | None = None, + volume_normalization: bool = True, + ): from vtkmodules.util import numpy_support from vtkmodules import vtkCommonCore from vtkmodules import vtkCommonDataModel @@ -2687,9 +2720,7 @@ def write_data_to_vtk( from vtkmodules import vtkIOXML if self.connectivity is None or self.vertices is None: - raise RuntimeError( - "This mesh has not been loaded from a statepoint file." - ) + raise RuntimeError("This mesh has not been loaded from a statepoint file.") if filename is None: filename = f"mesh_{self.id}.vtk" @@ -2771,29 +2802,128 @@ def write_data_to_vtk( writer.Write() + def _write_data_to_vtk_hdf5_format( + self, + filename: PathLike | None = None, + datasets: dict | None = None, + volume_normalization: bool = True, + ): + def append_dataset(dset, array): + """Convenience function to append data to an HDF5 dataset""" + origLen = dset.shape[0] + dset.resize(origLen + array.shape[0], axis=0) + dset[origLen:] = array + + if self.library != "moab": + raise NotImplementedError("VTKHDF output is only supported for MOAB meshes") + + # the self.connectivity contains arrays of length 8 to support hex + # elements as well, in the case of tetrahedra mesh elements, the + # last 4 values are -1 and are removed + trimmed_connectivity = [] + for cell in self.connectivity: + # Find the index of the first -1 value, if any + first_negative_index = np.where(cell == -1)[0] + if first_negative_index.size > 0: + # Slice the array up to the first -1 value + trimmed_connectivity.append(cell[: first_negative_index[0]]) + else: + # No -1 values, append the whole cell + trimmed_connectivity.append(cell) + trimmed_connectivity = np.array(trimmed_connectivity, dtype="int32").flatten() + + # MOAB meshes supports tet elements only so we know it has 4 points per cell + points_per_cell = 4 + + # offsets are the indices of the first point of each cell in the array of points + offsets = np.arange(0, self.n_elements * points_per_cell + 1, points_per_cell) + + for name, data in datasets.items(): + if data.shape != self.dimension: + raise ValueError( + f'Cannot apply dataset "{name}" with ' + f"shape {data.shape} to mesh {self.id} " + f"with dimensions {self.dimension}" + ) + + with h5py.File(filename, "w") as f: + + root = f.create_group("VTKHDF") + vtk_file_format_version = (2, 1) + root.attrs["Version"] = vtk_file_format_version + ascii_type = "UnstructuredGrid".encode("ascii") + root.attrs.create( + "Type", + ascii_type, + dtype=h5py.string_dtype("ascii", len(ascii_type)), + ) + + # create hdf5 file structure + root.create_dataset("NumberOfPoints", (0,), maxshape=(None,), dtype="i8") + root.create_dataset("Types", (0,), maxshape=(None,), dtype="uint8") + root.create_dataset("Points", (0, 3), maxshape=(None, 3), dtype="f") + root.create_dataset( + "NumberOfConnectivityIds", (0,), maxshape=(None,), dtype="i8" + ) + root.create_dataset("NumberOfCells", (0,), maxshape=(None,), dtype="i8") + root.create_dataset("Offsets", (0,), maxshape=(None,), dtype="i8") + root.create_dataset("Connectivity", (0,), maxshape=(None,), dtype="i8") + + append_dataset(root["NumberOfPoints"], np.array([len(self.vertices)])) + append_dataset(root["Points"], self.vertices) + append_dataset( + root["NumberOfConnectivityIds"], + np.array([len(trimmed_connectivity)]), + ) + append_dataset(root["Connectivity"], trimmed_connectivity) + append_dataset(root["NumberOfCells"], np.array([self.n_elements])) + append_dataset(root["Offsets"], offsets) + + append_dataset( + root["Types"], np.full(self.n_elements, self._VTK_TETRA, dtype="uint8") + ) + + cell_data_group = root.create_group("CellData") + + for name, data in datasets.items(): + + cell_data_group.create_dataset( + name, (0,), maxshape=(None,), dtype="float64", chunks=True + ) + + if volume_normalization: + data /= self.volumes + append_dataset(cell_data_group[name], data) + @classmethod def from_hdf5(cls, group: h5py.Group, mesh_id: int, name: str): - filename = group['filename'][()].decode() - library = group['library'][()].decode() - if 'options' in group.attrs: + filename = group["filename"][()].decode() + library = group["library"][()].decode() + if "options" in group.attrs: options = group.attrs['options'].decode() else: options = None - mesh = cls(filename=filename, library=library, mesh_id=mesh_id, name=name, options=options) + mesh = cls( + filename=filename, + library=library, + mesh_id=mesh_id, + name=name, + options=options, + ) mesh._has_statepoint_data = True - vol_data = group['volumes'][()] + vol_data = group["volumes"][()] mesh.volumes = np.reshape(vol_data, (vol_data.shape[0],)) mesh.n_elements = mesh.volumes.size - vertices = group['vertices'][()] + vertices = group["vertices"][()] mesh._vertices = vertices.reshape((-1, 3)) - connectivity = group['connectivity'][()] + connectivity = group["connectivity"][()] mesh._connectivity = connectivity.reshape((-1, 8)) - mesh._element_types = group['element_types'][()] + mesh._element_types = group["element_types"][()] - if 'length_multiplier' in group: - mesh.length_multiplier = group['length_multiplier'][()] + if "length_multiplier" in group: + mesh.length_multiplier = group["length_multiplier"][()] return mesh @@ -2812,7 +2942,7 @@ def to_xml_element(self): element.set("library", self._library) if self.options is not None: - element.set('options', self.options) + element.set("options", self.options) subelement = ET.SubElement(element, "filename") subelement.text = str(self.filename) diff --git a/tests/regression_tests/unstructured_mesh/test_mesh_dagmc_tets.vtk b/tests/regression_tests/unstructured_mesh/test_mesh_dagmc_tets.vtk new file mode 100644 index 00000000000..2ddd228cf13 --- /dev/null +++ b/tests/regression_tests/unstructured_mesh/test_mesh_dagmc_tets.vtk @@ -0,0 +1,159 @@ +# vtk DataFile Version 2.0 +made_with_cad_to_dagmc_package, Created by Gmsh 4.12.1 +ASCII +DATASET UNSTRUCTURED_GRID +POINTS 14 double +-0.5 -0.5 0.5 +-0.5 -0.5 -0.5 +-0.5 0.5 0.5 +-0.5 0.5 -0.5 +0.5 -0.5 0.5 +0.5 -0.5 -0.5 +0.5 0.5 0.5 +0.5 0.5 -0.5 +-0.5 0 0 +0.5 0 0 +0 -0.5 0 +0 0.5 0 +0 0 -0.5 +0 0 0.5 + +CELLS 68 268 +1 0 +1 1 +1 2 +1 3 +1 4 +1 5 +1 6 +1 7 +2 1 0 +2 0 2 +2 3 2 +2 1 3 +2 5 4 +2 4 6 +2 7 6 +2 5 7 +2 1 5 +2 0 4 +2 3 7 +2 2 6 +3 1 0 8 +3 0 2 8 +3 3 1 8 +3 2 3 8 +3 5 9 4 +3 4 9 6 +3 7 9 5 +3 6 9 7 +3 0 1 10 +3 4 0 10 +3 1 5 10 +3 5 4 10 +3 2 11 3 +3 6 11 2 +3 3 11 7 +3 7 11 6 +3 1 3 12 +3 5 1 12 +3 3 7 12 +3 7 5 12 +3 0 13 2 +3 4 13 0 +3 2 13 6 +3 6 13 4 +4 13 8 12 10 +4 11 8 12 13 +4 12 11 13 9 +4 10 12 13 9 +4 12 3 11 7 +4 13 2 8 0 +4 8 12 1 3 +4 11 3 8 2 +4 10 8 1 0 +4 0 10 13 4 +4 1 12 10 5 +4 11 2 13 6 +4 4 9 13 6 +4 6 9 11 7 +4 10 9 4 5 +4 7 9 12 5 +4 3 8 12 11 +4 1 12 8 10 +4 8 11 2 13 +4 10 13 8 0 +4 13 10 9 4 +4 11 13 9 6 +4 12 11 9 7 +4 9 10 12 5 + +CELL_TYPES 68 +1 +1 +1 +1 +1 +1 +1 +1 +3 +3 +3 +3 +3 +3 +3 +3 +3 +3 +3 +3 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 diff --git a/tests/unit_tests/test_mesh.py b/tests/unit_tests/test_mesh.py index f0f289408ab..9aca8b59656 100644 --- a/tests/unit_tests/test_mesh.py +++ b/tests/unit_tests/test_mesh.py @@ -2,6 +2,7 @@ from tempfile import TemporaryDirectory from pathlib import Path +import h5py import numpy as np from scipy.stats import chi2 import pytest @@ -486,6 +487,74 @@ def test_umesh(run_in_tmpdir, simple_umesh, export_type): with pytest.raises(ValueError, match='Cannot apply dataset "mean"'): simple_umesh.write_data_to_vtk(datasets={'mean': ref_data[:-2]}, filename=filename) + +@pytest.mark.skipif(not openmc.lib._dagmc_enabled(), reason="DAGMC not enabled.") +def test_write_vtkhdf(request, run_in_tmpdir): + """Performs a minimal UnstructuredMesh simulation, reads in the resulting + statepoint file and writes the mesh data to vtk and vtkhdf files. It is + necessary to read in the unstructured mesh from a statepoint file to ensure + it has all the required attributes + """ + model = openmc.Model() + + surf1 = openmc.Sphere(r=1000.0, boundary_type="vacuum") + cell1 = openmc.Cell(region=-surf1) + model.geometry = openmc.Geometry([cell1]) + + umesh = openmc.UnstructuredMesh( + request.path.parent / "test_mesh_dagmc_tets.vtk", + "moab", + mesh_id = 1 + ) + mesh_filter = openmc.MeshFilter(umesh) + + # Create flux mesh tally to score alpha production + mesh_tally = openmc.Tally(name="test_tally") + mesh_tally.filters = [mesh_filter] + mesh_tally.scores = ["flux"] + + model.tallies = [mesh_tally] + + model.settings.run_mode = "fixed source" + model.settings.batches = 2 + model.settings.particles = 10 + + statepoint_file = model.run() + + with openmc.StatePoint(statepoint_file) as statepoint: + my_tally = statepoint.get_tally(name="test_tally") + + umesh_from_sp = statepoint.meshes[umesh.id] + + datasets={ + "mean": my_tally.mean.flatten(), + "std_dev": my_tally.std_dev.flatten() + } + + umesh_from_sp.write_data_to_vtk(datasets=datasets, filename="test_mesh.vtkhdf") + umesh_from_sp.write_data_to_vtk(datasets=datasets, filename="test_mesh.vtk") + + with pytest.raises(ValueError, match="Unsupported file extension"): + # Supported file extensions are vtk or vtkhdf, not hdf5, so this should raise an error + umesh_from_sp.write_data_to_vtk( + datasets=datasets, + filename="test_mesh.hdf5", + ) + with pytest.raises(ValueError, match="Cannot apply dataset"): + # The shape of the data should match the shape of the mesh, so this should raise an error + umesh_from_sp.write_data_to_vtk( + datasets={'incorrectly_shaped_data': np.array(([1,2,3]))}, + filename="test_mesh_incorrect_shape.vtkhdf", + ) + + assert Path("test_mesh.vtk").exists() + assert Path("test_mesh.vtkhdf").exists() + + # just ensure we can open the file without error + with h5py.File("test_mesh.vtkhdf", "r"): + ... + + def test_mesh_get_homogenized_materials(): """Test the get_homogenized_materials method""" # Simple model with 1 cm of Fe56 next to 1 cm of H1 diff --git a/tests/unit_tests/test_mesh_dagmc_tets.vtk b/tests/unit_tests/test_mesh_dagmc_tets.vtk new file mode 120000 index 00000000000..9f7000175d7 --- /dev/null +++ b/tests/unit_tests/test_mesh_dagmc_tets.vtk @@ -0,0 +1 @@ +../regression_tests/unstructured_mesh/test_mesh_dagmc_tets.vtk \ No newline at end of file From c0f302db6845e2d303625317430b6ee26ff2cec7 Mon Sep 17 00:00:00 2001 From: Perry <100789850+yrrepy@users.noreply.github.com> Date: Fri, 7 Nov 2025 08:58:40 -0800 Subject: [PATCH 144/159] Add energy group structure: SCALE-999 (#3564) --- openmc/mgxs/__init__.py | 208 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 205 insertions(+), 3 deletions(-) diff --git a/openmc/mgxs/__init__.py b/openmc/mgxs/__init__.py index 5de85afb0e7..682b5d55072 100644 --- a/openmc/mgxs/__init__.py +++ b/openmc/mgxs/__init__.py @@ -13,8 +13,8 @@ - "XMAS-172_" designed for LWR analysis ([SAR1990]_, [SAN2004]_) - "SHEM-361_" designed for LWR analysis to eliminate self-shielding calculations of thermal resonances ([HFA2005]_, [SAN2007]_, [HEB2008]_) -- "SCALE-X" (where X is 44 which is designed for criticality analysis - and 252 is designed for thermal reactors) for the SCALE code suite +- "SCALE-X" (where X is 44 which is designed for criticality analysis, 252 is designed + for thermal reactors and 999 for multipurpose activation) for the SCALE code suite ([ZAL1999]_ and [REARDEN2013]_) - "MPACT-X" (where X is 51 (PWR), 60 (BWR), 69 (Magnox)) from the MPACT_ reactor physics code ([KIM2019]_ and [KIM2020]_) @@ -29,6 +29,7 @@ .. _SCALE44: https://www-nds.iaea.org/publications/indc/indc-czr-0001.pdf .. _ECCO-33: https://serpent.vtt.fi/mediawiki/index.php/ECCO_33-group_structure .. _SCALE252: https://oecd-nea.org/science/wpncs/amct/workingarea/meeting2013/EGAMCT2013_08.pdf +.. _SCALE999: https://info.ornl.gov/sites/publications/Files/Pub67728.pdf, https://www.nrc.gov/docs/ML1218/ML12184A002.pdf .. _MPACT: https://vera.ornl.gov/mpact/ .. _XMAS-172: https://www-nds.iaea.org/wimsd/energy.htm .. _SHEM-361: http://merlin.polymtl.ca/downloads/FP214.pdf @@ -593,7 +594,208 @@ 2.4000e8, 2.8000e8, 3.2000e8, 3.6000e8, 4.0000e8, 4.4000e8, 4.8000e8, 5.2000e8, 5.6000e8, 6.0000e8, 6.4000e8, 6.8000e8, 7.2000e8, 7.6000e8, 8.0000e8, - 8.4000e8, 8.8000e8, 9.2000e8, 9.6000e8, 1.0000e9,]) + 8.4000e8, 8.8000e8, 9.2000e8, 9.6000e8, 1.0000e9]) +GROUP_STRUCTURES['SCALE-999'] = np.array([ + 1.000e-5, 1.000e-4, 5.000e-4, 7.500e-4, 1.000e-3, + 1.200e-3, 1.500e-3, 2.000e-3, 2.500e-3, 3.000e-3, + 4.000e-3, 5.000e-3, 7.500e-3, 1.000e-2, 1.450e-2, + 1.850e-2, 2.100e-2, 2.530e-2, 3.000e-2, 4.000e-2, + 5.000e-2, 6.000e-2, 7.000e-2, 8.000e-2, 9.000e-2, + 1.000e-1, 1.250e-1, 1.500e-1, 1.750e-1, 1.840e-1, + 2.000e-1, 2.250e-1, 2.500e-1, 2.750e-1, 3.000e-1, + 3.250e-1, 3.500e-1, 3.668e-1, 3.750e-1, 4.000e-1, + 4.140e-1, 4.500e-1, 5.000e-1, 5.316e-1, 5.500e-1, + 6.000e-1, 6.250e-1, 6.500e-1, 6.826e-1, 7.000e-1, + 7.500e-1, 8.000e-1, 8.500e-1, 8.764e-1, 9.000e-1, + 9.250e-1, 9.500e-1, 9.750e-1, 1.000e+0, 1.010e+0, + 1.020e+0, 1.030e+0, 1.040e+0, 1.050e+0, 1.060e+0, + 1.070e+0, 1.080e+0, 1.090e+0, 1.100e+0, 1.110e+0, + 1.120e+0, 1.130e+0, 1.140e+0, 1.150e+0, 1.175e+0, + 1.200e+0, 1.225e+0, 1.250e+0, 1.300e+0, 1.350e+0, + 1.400e+0, 1.450e+0, 1.500e+0, 1.545e+0, 1.590e+0, + 1.635e+0, 1.680e+0, 1.725e+0, 1.770e+0, 1.815e+0, + 1.860e+0, 1.900e+0, 1.940e+0, 1.970e+0, 2.000e+0, + 2.060e+0, 2.120e+0, 2.165e+0, 2.210e+0, 2.255e+0, + 2.300e+0, 2.340e+0, 2.380e+0, 2.425e+0, 2.470e+0, + 2.520e+0, 2.570e+0, 2.620e+0, 2.670e+0, 2.720e+0, + 2.770e+0, 2.820e+0, 2.870e+0, 2.920e+0, 2.970e+0, + 3.000e+0, 3.100e+0, 3.200e+0, 3.300e+0, 3.500e+0, + 3.620e+0, 3.730e+0, 3.830e+0, 3.928e+0, 4.000e+0, + 4.100e+0, 4.300e+0, 4.500e+0, 4.750e+0, 4.875e+0, + 5.000e+0, 5.044e+0, 5.250e+0, 5.400e+0, 5.550e+0, + 5.700e+0, 5.850e+0, 6.000e+0, 6.250e+0, 6.375e+0, + 6.500e+0, 6.625e+0, 6.750e+0, 6.875e+0, 7.000e+0, + 7.075e+0, 7.150e+0, 7.625e+0, 8.100e+0, 8.208e+0, + 8.315e+0, 8.708e+0, 9.100e+0, 9.550e+0, 1.000e+1, + 1.034e+1, 1.068e+1, 1.109e+1, 1.150e+1, 1.170e+1, + 1.190e+1, 1.240e+1, 1.290e+1, 1.333e+1, 1.375e+1, + 1.408e+1, 1.440e+1, 1.475e+1, 1.510e+1, 1.555e+1, + 1.600e+1, 1.650e+1, 1.700e+1, 1.730e+1, 1.760e+1, + 1.805e+1, 1.850e+1, 1.875e+1, 1.900e+1, 1.940e+1, + 2.000e+1, 2.050e+1, 2.100e+1, 2.175e+1, 2.250e+1, + 2.375e+1, 2.500e+1, 2.625e+1, 2.750e+1, 2.826e+1, + 2.902e+1, 2.951e+1, 3.000e+1, 3.063e+1, 3.125e+1, + 3.150e+1, 3.175e+1, 3.250e+1, 3.325e+1, 3.350e+1, + 3.375e+1, 3.418e+1, 3.460e+1, 3.500e+1, 3.550e+1, + 3.600e+1, 3.700e+1, 3.713e+1, 3.727e+1, 3.763e+1, + 3.800e+1, 3.855e+1, 3.910e+1, 3.935e+1, 3.960e+1, + 4.030e+1, 4.100e+1, 4.170e+1, 4.240e+1, 4.320e+1, + 4.400e+1, 4.460e+1, 4.520e+1, 4.610e+1, 4.700e+1, + 4.743e+1, 4.785e+1, 4.808e+1, 4.830e+1, 4.875e+1, + 4.920e+1, 4.990e+1, 5.060e+1, 5.130e+1, 5.200e+1, + 5.270e+1, 5.340e+1, 5.620e+1, 5.800e+1, 6.000e+1, + 6.100e+1, 6.122e+1, 6.144e+1, 6.300e+1, 6.500e+1, + 6.625e+1, 6.750e+1, 6.975e+1, 7.200e+1, 7.400e+1, + 7.600e+1, 7.745e+1, 7.889e+1, 7.945e+1, 8.000e+1, + 8.170e+1, 8.200e+1, 8.400e+1, 8.600e+1, 8.800e+1, + 9.000e+1, 9.250e+1, 9.500e+1, 9.700e+1, 1.000e+2, + 1.012e+2, 1.038e+2, 1.050e+2, 1.080e+2, 1.105e+2, + 1.130e+2, 1.160e+2, 1.175e+2, 1.190e+2, 1.205e+2, + 1.220e+2, 1.240e+2, 1.260e+2, 1.280e+2, 1.301e+2, + 1.325e+2, 1.350e+2, 1.375e+2, 1.400e+2, 1.430e+2, + 1.450e+2, 1.475e+2, 1.500e+2, 1.525e+2, 1.550e+2, + 1.575e+2, 1.600e+2, 1.625e+2, 1.650e+2, 1.670e+2, + 1.700e+2, 1.716e+2, 1.739e+2, 1.763e+2, 1.786e+2, + 1.800e+2, 1.877e+2, 1.885e+2, 1.915e+2, 1.930e+2, + 1.962e+2, 1.999e+2, 2.020e+2, 2.074e+2, 2.088e+2, + 2.095e+2, 2.122e+2, 2.145e+2, 2.175e+2, 2.200e+2, + 2.237e+2, 2.269e+2, 2.301e+2, 2.333e+2, 2.367e+2, + 2.400e+2, 2.442e+2, 2.484e+2, 2.527e+2, 2.571e+2, + 2.615e+2, 2.661e+2, 2.707e+2, 2.754e+2, 2.801e+2, + 2.850e+2, 2.899e+2, 2.948e+2, 2.999e+2, 3.050e+2, + 3.107e+2, 3.165e+2, 3.224e+2, 3.284e+2, 3.345e+2, + 3.408e+2, 3.471e+2, 3.536e+2, 3.591e+2, 3.648e+2, + 3.705e+2, 3.764e+2, 3.823e+2, 3.883e+2, 3.944e+2, + 4.007e+2, 4.070e+2, 4.134e+2, 4.199e+2, 4.265e+2, + 4.332e+2, 4.400e+2, 4.470e+2, 4.540e+2, 4.595e+2, + 4.650e+2, 4.706e+2, 4.763e+2, 4.821e+2, 4.879e+2, + 4.937e+2, 4.997e+2, 5.057e+2, 5.118e+2, 5.180e+2, + 5.243e+2, 5.306e+2, 5.370e+2, 5.435e+2, 5.500e+2, + 5.581e+2, 5.662e+2, 5.745e+2, 5.830e+2, 5.932e+2, + 6.036e+2, 6.142e+2, 6.250e+2, 6.359e+2, 6.471e+2, + 6.585e+2, 6.700e+2, 6.765e+2, 6.830e+2, 6.909e+2, + 6.988e+2, 7.069e+2, 7.150e+2, 7.232e+2, 7.316e+2, + 7.400e+2, 7.485e+2, 7.598e+2, 7.712e+2, 7.827e+2, + 7.945e+2, 8.064e+2, 8.185e+2, 8.308e+2, 8.433e+2, + 8.559e+2, 8.688e+2, 8.818e+2, 8.950e+2, 9.085e+2, + 9.221e+2, 9.360e+2, 9.500e+2, 9.555e+2, 9.611e+2, + 9.720e+2, 9.829e+2, 9.940e+2, 1.005e+3, 1.017e+3, + 1.028e+3, 1.040e+3, 1.051e+3, 1.063e+3, 1.075e+3, + 1.087e+3, 1.100e+3, 1.112e+3, 1.125e+3, 1.137e+3, + 1.150e+3, 1.171e+3, 1.191e+3, 1.213e+3, 1.234e+3, + 1.249e+3, 1.265e+3, 1.280e+3, 1.296e+3, 1.312e+3, + 1.328e+3, 1.344e+3, 1.361e+3, 1.377e+3, 1.394e+3, + 1.411e+3, 1.429e+3, 1.446e+3, 1.464e+3, 1.482e+3, + 1.500e+3, 1.525e+3, 1.550e+3, 1.585e+3, 1.610e+3, + 1.636e+3, 1.662e+3, 1.689e+3, 1.716e+3, 1.744e+3, + 1.772e+3, 1.800e+3, 1.828e+3, 1.856e+3, 1.885e+3, + 1.914e+3, 1.943e+3, 1.973e+3, 2.004e+3, 2.035e+3, + 2.075e+3, 2.116e+3, 2.158e+3, 2.200e+3, 2.250e+3, + 2.290e+3, 2.337e+3, 2.386e+3, 2.435e+3, 2.500e+3, + 2.532e+3, 2.580e+3, 2.613e+3, 2.679e+3, 2.747e+3, + 2.808e+3, 2.871e+3, 2.935e+3, 3.000e+3, 3.035e+3, + 3.112e+3, 3.191e+3, 3.272e+3, 3.355e+3, 3.440e+3, + 3.527e+3, 3.616e+3, 3.707e+3, 3.740e+3, 3.819e+3, + 3.900e+3, 3.998e+3, 4.099e+3, 4.202e+3, 4.307e+3, + 4.400e+3, 4.500e+3, 4.620e+3, 4.740e+3, 4.850e+3, + 4.960e+3, 5.100e+3, 5.250e+3, 5.400e+3, 5.531e+3, + 5.645e+3, 5.700e+3, 5.879e+3, 6.000e+3, 6.128e+3, + 6.258e+3, 6.392e+3, 6.528e+3, 6.667e+3, 6.809e+3, + 6.954e+3, 7.102e+3, 7.212e+3, 7.323e+3, 7.437e+3, + 7.552e+3, 7.669e+3, 7.787e+3, 7.908e+3, 8.030e+3, + 8.159e+3, 8.289e+3, 8.422e+3, 8.557e+3, 8.694e+3, + 8.834e+3, 8.975e+3, 9.119e+3, 9.307e+3, 9.500e+3, + 9.763e+3, 1.003e+4, 1.031e+4, 1.060e+4, 1.086e+4, + 1.114e+4, 1.142e+4, 1.171e+4, 1.202e+4, 1.234e+4, + 1.266e+4, 1.300e+4, 1.324e+4, 1.348e+4, 1.373e+4, + 1.398e+4, 1.424e+4, 1.450e+4, 1.476e+4, 1.503e+4, + 1.527e+4, 1.550e+4, 1.574e+4, 1.599e+4, 1.623e+4, + 1.649e+4, 1.674e+4, 1.700e+4, 1.727e+4, 1.755e+4, + 1.783e+4, 1.812e+4, 1.841e+4, 1.870e+4, 1.900e+4, + 1.931e+4, 1.965e+4, 2.000e+4, 2.045e+4, 2.092e+4, + 2.139e+4, 2.188e+4, 2.229e+4, 2.271e+4, 2.314e+4, + 2.358e+4, 2.388e+4, 2.418e+4, 2.450e+4, 2.479e+4, + 2.500e+4, 2.520e+4, 2.552e+4, 2.580e+4, 2.606e+4, + 2.653e+4, 2.700e+4, 2.737e+4, 2.774e+4, 2.812e+4, + 2.850e+4, 2.887e+4, 2.924e+4, 2.962e+4, 3.000e+4, + 3.045e+4, 3.090e+4, 3.136e+4, 3.183e+4, 3.243e+4, + 3.304e+4, 3.367e+4, 3.431e+4, 3.468e+4, 3.507e+4, + 3.545e+4, 3.584e+4, 3.624e+4, 3.663e+4, 3.704e+4, + 3.744e+4, 3.786e+4, 3.827e+4, 3.869e+4, 3.912e+4, + 3.955e+4, 3.998e+4, 4.042e+4, 4.087e+4, 4.136e+4, + 4.186e+4, 4.237e+4, 4.288e+4, 4.340e+4, 4.393e+4, + 4.446e+4, 4.500e+4, 4.565e+4, 4.631e+4, 4.721e+4, + 4.812e+4, 4.905e+4, 5.000e+4, 5.099e+4, 5.200e+4, + 5.248e+4, 5.347e+4, 5.448e+4, 5.551e+4, 5.656e+4, + 5.740e+4, 5.826e+4, 5.912e+4, 6.000e+4, 6.088e+4, + 6.177e+4, 6.267e+4, 6.358e+4, 6.451e+4, 6.545e+4, + 6.641e+4, 6.738e+4, 6.851e+4, 6.965e+4, 7.081e+4, + 7.200e+4, 7.300e+4, 7.399e+4, 7.500e+4, 7.610e+4, + 7.722e+4, 7.835e+4, 7.950e+4, 8.074e+4, 8.200e+4, + 8.250e+4, 8.374e+4, 8.500e+4, 8.652e+4, 8.788e+4, + 8.926e+4, 9.067e+4, 9.210e+4, 9.355e+4, 9.502e+4, + 9.652e+4, 9.804e+4, 1.000e+5, 1.013e+5, 1.027e+5, + 1.040e+5, 1.054e+5, 1.068e+5, 1.082e+5, 1.096e+5, + 1.111e+5, 1.125e+5, 1.139e+5, 1.153e+5, 1.168e+5, + 1.183e+5, 1.197e+5, 1.213e+5, 1.228e+5, 1.241e+5, + 1.255e+5, 1.269e+5, 1.283e+5, 1.291e+5, 1.307e+5, + 1.323e+5, 1.340e+5, 1.357e+5, 1.374e+5, 1.391e+5, + 1.409e+5, 1.426e+5, 1.445e+5, 1.463e+5, 1.481e+5, + 1.490e+5, 1.519e+5, 1.538e+5, 1.557e+5, 1.576e+5, + 1.596e+5, 1.616e+5, 1.637e+5, 1.657e+5, 1.678e+5, + 1.699e+5, 1.721e+5, 1.742e+5, 1.764e+5, 1.786e+5, + 1.809e+5, 1.832e+5, 1.855e+5, 1.878e+5, 1.902e+5, + 1.926e+5, 1.962e+5, 2.000e+5, 2.024e+5, 2.050e+5, + 2.076e+5, 2.102e+5, 2.128e+5, 2.155e+5, 2.182e+5, + 2.209e+5, 2.237e+5, 2.265e+5, 2.294e+5, 2.323e+5, + 2.352e+5, 2.381e+5, 2.411e+5, 2.442e+5, 2.472e+5, + 2.500e+5, 2.527e+5, 2.555e+5, 2.584e+5, 2.612e+5, + 2.641e+5, 2.670e+5, 2.700e+5, 2.732e+5, 2.767e+5, + 2.802e+5, 2.837e+5, 2.873e+5, 2.909e+5, 2.945e+5, + 2.972e+5, 2.985e+5, 3.020e+5, 3.053e+5, 3.088e+5, + 3.122e+5, 3.157e+5, 3.192e+5, 3.228e+5, 3.264e+5, + 3.300e+5, 3.337e+5, 3.379e+5, 3.422e+5, 3.465e+5, + 3.508e+5, 3.553e+5, 3.597e+5, 3.643e+5, 3.688e+5, + 3.735e+5, 3.782e+5, 3.829e+5, 3.877e+5, 3.938e+5, + 4.000e+5, 4.076e+5, 4.138e+5, 4.200e+5, 4.249e+5, + 4.299e+5, 4.349e+5, 4.400e+5, 4.452e+5, 4.505e+5, + 4.553e+5, 4.601e+5, 4.650e+5, 4.700e+5, 4.772e+5, + 4.845e+5, 4.920e+5, 4.995e+5, 5.054e+5, 5.113e+5, + 5.173e+5, 5.234e+5, 5.299e+5, 5.365e+5, 5.432e+5, + 5.500e+5, 5.557e+5, 5.614e+5, 5.672e+5, 5.730e+5, + 5.784e+5, 5.891e+5, 6.000e+5, 6.081e+5, 6.158e+5, + 6.235e+5, 6.313e+5, 6.393e+5, 6.468e+5, 6.545e+5, + 6.622e+5, 6.700e+5, 6.790e+5, 6.926e+5, 7.065e+5, + 7.154e+5, 7.244e+5, 7.335e+5, 7.427e+5, 7.500e+5, + 7.576e+5, 7.653e+5, 7.730e+5, 7.808e+5, 7.904e+5, + 8.002e+5, 8.100e+5, 8.200e+5, 8.301e+5, 8.403e+5, + 8.506e+5, 8.611e+5, 8.750e+5, 8.874e+5, 9.000e+5, + 9.072e+5, 9.200e+5, 9.400e+5, 9.616e+5, 9.800e+5, + 1.003e+6, 1.010e+6, 1.040e+6, 1.070e+6, 1.100e+6, + 1.108e+6, 1.136e+6, 1.165e+6, 1.200e+6, 1.225e+6, + 1.250e+6, 1.287e+6, 1.317e+6, 1.356e+6, 1.400e+6, + 1.423e+6, 1.461e+6, 1.500e+6, 1.536e+6, 1.572e+6, + 1.612e+6, 1.653e+6, 1.695e+6, 1.738e+6, 1.782e+6, + 1.827e+6, 1.850e+6, 1.921e+6, 1.969e+6, 2.019e+6, + 2.070e+6, 2.123e+6, 2.176e+6, 2.231e+6, 2.307e+6, + 2.354e+6, 2.365e+6, 2.385e+6, 2.466e+6, 2.479e+6, + 2.535e+6, 2.592e+6, 2.658e+6, 2.725e+6, 2.794e+6, + 2.865e+6, 2.932e+6, 3.000e+6, 3.080e+6, 3.166e+6, + 3.247e+6, 3.329e+6, 3.413e+6, 3.499e+6, 3.588e+6, + 3.679e+6, 3.772e+6, 3.867e+6, 3.965e+6, 4.066e+6, + 4.183e+6, 4.304e+6, 4.398e+6, 4.493e+6, 4.607e+6, + 4.724e+6, 4.800e+6, 4.882e+6, 4.966e+6, 5.092e+6, + 5.221e+6, 5.353e+6, 5.488e+6, 5.627e+6, 5.770e+6, + 5.916e+6, 6.065e+6, 6.219e+6, 6.376e+6, 6.434e+6, + 6.592e+6, 6.703e+6, 6.873e+6, 7.047e+6, 7.225e+6, + 7.408e+6, 7.596e+6, 7.788e+6, 7.985e+6, 8.187e+6, + 8.395e+6, 8.607e+6, 8.825e+6, 9.048e+6, 9.278e+6, + 9.512e+6, 9.753e+6, 1.000e+7, 1.025e+7, 1.051e+7, + 1.078e+7, 1.105e+7, 1.133e+7, 1.162e+7, 1.191e+7, + 1.221e+7, 1.252e+7, 1.284e+7, 1.317e+7, 1.350e+7, + 1.384e+7, 1.419e+7, 1.455e+7, 1.492e+7, 1.530e+7, + 1.568e+7, 1.608e+7, 1.649e+7, 1.691e+7, 1.733e+7, + 1.790e+7, 1.845e+7, 1.900e+7, 1.964e+7, 2.000e+7]) GROUP_STRUCTURES['UKAEA-1102'] = np.array([ 1.0000e-5, 1.0471e-5, 1.0965e-5, 1.1482e-5, 1.2023e-5, 1.2589e-5, 1.3183e-5, 1.3804e-5, 1.4454e-5, 1.5136e-5, From 8cd3911cbe0fa277b889effa1c5c1566b0683f55 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 12 Nov 2025 00:01:18 -0600 Subject: [PATCH 145/159] Reset DAGMC history when reviving from source. (#3601) Co-authored-by: Paul Romano --- src/particle.cpp | 3 + .../weightwindows/dagmc/__init__.py | 0 .../dagmc/nested_shell_geometry.h5m | Bin 0 -> 58680 bytes tests/unit_tests/weightwindows/dagmc/test.py | 98 ++++++++++++++++++ 4 files changed, 101 insertions(+) create mode 100644 tests/unit_tests/weightwindows/dagmc/__init__.py create mode 100644 tests/unit_tests/weightwindows/dagmc/nested_shell_geometry.h5m create mode 100644 tests/unit_tests/weightwindows/dagmc/test.py diff --git a/src/particle.cpp b/src/particle.cpp index 6b4c332a8b2..6ba8ebf1286 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -121,6 +121,9 @@ void Particle::from_source(const SourceSite* src) fission() = false; zero_flux_derivs(); lifetime() = 0.0; +#ifdef OPENMC_DAGMC_ENABLED + history().reset(); +#endif // Copy attributes from source bank site type() = src->particle; diff --git a/tests/unit_tests/weightwindows/dagmc/__init__.py b/tests/unit_tests/weightwindows/dagmc/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/unit_tests/weightwindows/dagmc/nested_shell_geometry.h5m b/tests/unit_tests/weightwindows/dagmc/nested_shell_geometry.h5m new file mode 100644 index 0000000000000000000000000000000000000000..af1d5563d9255b518d42b551b7527cdf45eeaaf3 GIT binary patch literal 58680 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 ZKJ@mkzRs4Wo;}NVH#PV5?Swso{}1hu%5MMw literal 0 HcmV?d00001 diff --git a/tests/unit_tests/weightwindows/dagmc/test.py b/tests/unit_tests/weightwindows/dagmc/test.py new file mode 100644 index 00000000000..ed01a93ed3d --- /dev/null +++ b/tests/unit_tests/weightwindows/dagmc/test.py @@ -0,0 +1,98 @@ +import pytest + +import openmc +import openmc.lib + +pytestmark = pytest.mark.skipif( + not openmc.lib._dagmc_enabled(), + reason="DAGMC CAD geometry is not enabled.", +) + + +def test_dagmc_weight_windows_near_boundary(run_in_tmpdir, request): + """Ensure splitting near a boundary doesn't lose particles due to + a stale DAGMC history on the particle object.""" + + # DAGMC model overview: + # * Three nested cubes; innermost cube contains a fusion neutron source. + # * Two outer cubes filled with tungsten. Weight windows defined on a mesh + # cause particles to split moving outward. + # Outer cubes are similar in size (outer slightly larger) so particles + # frequently cross the problem boundary immediately after splitting. No lost + # particles are allowed to the correct DAGMC history after splitting is used. + model = openmc.Model() + + dagmc_file = request.path.parent / 'nested_shell_geometry.h5m' + dagmc_univ = openmc.DAGMCUniverse(dagmc_file) + model.geometry = openmc.Geometry(dagmc_univ) + + tungsten = openmc.Material(name='shell') + tungsten.add_element('W', 1.0) + tungsten.set_density('g/cm3', 7.8) + materials = openmc.Materials([tungsten]) + model.materials = materials + + settings = openmc.Settings() + settings.output = {'tallies': False, 'summary': False} + + source = openmc.IndependentSource() + source.space = openmc.stats.Point((0.0, 0.0, 0.0)) + source.angle = openmc.stats.Isotropic() + source.energy = openmc.stats.Discrete([14.1e6], [1.0]) + settings.source = source + + settings.batches = 2 + settings.particles = 500 + settings.run_mode = 'fixed source' + settings.survival_biasing = False + settings.max_lost_particles = 1 + settings.max_history_splits = 10_000_000 + + settings.weight_window_checkpoints = { + 'surface': True, + 'collision': True, + } + + mesh = openmc.RegularMesh() + mesh.lower_left = (-60.0, -60.0, -60.0) + mesh.upper_right = (60.0, 60.0, 60.0) + mesh.dimension = (24, 1, 1) + + weight_windows_lower = [ + 0.030750733294361156, + 0.056110505674355333, + 0.08187875047968339, + 0.1101743496347699, + 0.13982370013053508, + 0.17443799246829372, + 0.21576286623367483, + 0.26416659508033646, + 0.318574932646899, + 0.3804031702117963, + 0.42899359749256355, + 0.4954283294279403, + 0.49999999999999994, + 0.43432341070872266, + 0.38302303850488206, + 0.32148375935490886, + 0.2637416945702018, + 0.21498369367288853, + 0.17163611765361744, + 0.13832102142074995, + 0.10717772257151495, + 0.07986176041282561, + 0.05499644859408233, + 0.03058023506703803, + ] + + weight_windows = openmc.WeightWindows( + mesh, + lower_ww_bounds=weight_windows_lower, + upper_bound_ratio=5.0, + ) + weight_windows.max_lower_bound_ratio = 1.0 + settings.weight_windows = weight_windows + settings.weight_windows_on = True + model.settings = settings + + model.run() \ No newline at end of file From 5c2bfe771eb94c759923c63be5ba3eb01a6b3ec9 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 12 Nov 2025 00:29:19 -0600 Subject: [PATCH 146/159] Write particle states as separate lines in track VTK files. (#3628) --- openmc/tracks.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/openmc/tracks.py b/openmc/tracks.py index 61e5a72442e..81646e7d2e7 100644 --- a/openmc/tracks.py +++ b/openmc/tracks.py @@ -296,16 +296,16 @@ def write_to_vtk(self, filename=Path('tracks.vtp')): for state in pt.states: points.InsertNextPoint(state['r']) - # Create VTK line and assign points to line. - n = pt.states.size - line = vtk.vtkPolyLine() - line.GetPointIds().SetNumberOfIds(n) - for i in range(n): - line.GetPointIds().SetId(i, point_offset + i) - point_offset += n - - # Add line to cell array - cells.InsertNextCell(line) + # Create VTK line and assign points to line. + n = pt.states.size + line = vtk.vtkPolyLine() + line.GetPointIds().SetNumberOfIds(n) + for i in range(n): + line.GetPointIds().SetId(i, point_offset + i) + point_offset += n + + # Add line to cell array + cells.InsertNextCell(line) data = vtk.vtkPolyData() data.SetPoints(points) From e5348d3f62a821f84513a026977eef0f8bef01ad Mon Sep 17 00:00:00 2001 From: Marco De Pietri Date: Wed, 12 Nov 2025 05:35:07 -0500 Subject: [PATCH 147/159] Avoid divide-by-zero in `from_multigroup_flux` when flux is zero (#3624) Co-authored-by: Paul Romano --- openmc/deplete/microxs.py | 12 ++++++++---- tests/unit_tests/test_deplete_microxs.py | 13 +++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/openmc/deplete/microxs.py b/openmc/deplete/microxs.py index e351c923df0..d7624955e8b 100644 --- a/openmc/deplete/microxs.py +++ b/openmc/deplete/microxs.py @@ -356,13 +356,17 @@ def from_multigroup_flux( reactions = chain.reactions mts = [REACTION_MT[name] for name in reactions] - # Normalize multigroup flux - multigroup_flux = np.array(multigroup_flux) - multigroup_flux /= multigroup_flux.sum() - # Create 3D array for microscopic cross sections microxs_arr = np.zeros((len(nuclides), len(mts), 1)) + # If flux is zero, safely return zero cross sections + multigroup_flux = np.array(multigroup_flux) + if (flux_sum := multigroup_flux.sum()) == 0.0: + return cls(microxs_arr, nuclides, reactions) + + # Normalize multigroup flux + multigroup_flux /= flux_sum + # Compute microscopic cross sections within a temporary session with openmc.lib.TemporarySession(**init_kwargs): # For each nuclide and reaction, compute the flux-averaged xs diff --git a/tests/unit_tests/test_deplete_microxs.py b/tests/unit_tests/test_deplete_microxs.py index 073b3f162d1..5762a8511b6 100644 --- a/tests/unit_tests/test_deplete_microxs.py +++ b/tests/unit_tests/test_deplete_microxs.py @@ -111,3 +111,16 @@ def test_multigroup_flux_same(): energies=energies, multigroup_flux=flux, chain_file=chain_file) assert microxs_4g.data == pytest.approx(microxs_2g.data) + + +def test_microxs_zero_flux(): + chain_file = Path(__file__).parents[1] / 'chain_simple.xml' + + # Generate micro XS based on zero flux + energies = [0., 6.25e-1, 5.53e3, 8.21e5, 2.e7] + flux = [0.0, 0.0, 0.0, 0.0] + microxs = MicroXS.from_multigroup_flux( + energies=energies, multigroup_flux=flux, chain_file=chain_file) + + # All microscopic cross sections should be zero + assert np.all(microxs.data == 0.0) From 2d77544b0c045f7e693146dc466e5cc290fbdc49 Mon Sep 17 00:00:00 2001 From: Gregoire Biot Date: Wed, 12 Nov 2025 11:41:37 -0500 Subject: [PATCH 148/159] Adding variance of variance and normality tests for tally statistics (#3454) Co-authored-by: Ethan Peterson Co-authored-by: Paul Romano --- docs/source/io_formats/statepoint.rst | 2 + docs/source/methods/tallies.rst | 107 +++++- include/openmc/constants.h | 2 +- include/openmc/hdf5_interface.h | 10 +- include/openmc/tallies/tally.h | 5 + openmc/statepoint.py | 4 + openmc/tallies.py | 524 +++++++++++++++++++++++++- src/hdf5_interface.cpp | 16 +- src/state_point.cpp | 17 +- src/tallies/tally.cpp | 42 ++- src/weight_windows.cpp | 8 +- tests/unit_tests/test_tallies.py | 213 +++++++++++ 12 files changed, 915 insertions(+), 35 deletions(-) diff --git a/docs/source/io_formats/statepoint.rst b/docs/source/io_formats/statepoint.rst index 3b103176965..2309643dc89 100644 --- a/docs/source/io_formats/statepoint.rst +++ b/docs/source/io_formats/statepoint.rst @@ -149,6 +149,8 @@ The current version of the statepoint file format is 18.1. tallies will have a value of 0 unless otherwise instructed. - **multiply_density** (*int*) -- Flag indicating whether reaction rates should be multiplied by atom density (1) or not (0). + - **higher_moments** (*int*) -- Flag indicating whether + higher-order tally moments are enabled (1) or not (0). :Datasets: - **n_realizations** (*int*) -- Number of realizations. - **n_filters** (*int*) -- Number of filters used. diff --git a/docs/source/methods/tallies.rst b/docs/source/methods/tallies.rst index 79a63fbdd83..27a3f873ab6 100644 --- a/docs/source/methods/tallies.rst +++ b/docs/source/methods/tallies.rst @@ -387,6 +387,101 @@ of this is that the longer you run a simulation, the better you know your results. Therefore, by running a simulation long enough, it is possible to reduce the stochastic uncertainty to arbitrarily low levels. +Skewness +++++++++ + +The `skewness`_ of a population quantifies the asymmetry of the probability +distribution around its mean. Positive and negative skewness indicate a +longer/heavier right and left tail respectively. Let :math:`x_1,\ldots,x_n` be +the per-realization values for a bin, with sample mean :math:`\bar{x}` and +sample central moments: + +.. math:: + + m_k \;=\; \frac{1}{n}\sum_{i=1}^{n}\bigl(x_i-\bar{x}\bigr)^k. + +OpenMC reports the *adjusted Fisher-Pearson skewness* (defined for :math:`n \ge +3`), which is commonly used in many statistical packages: + +.. math:: + + G_1 \;=\; \frac{\sqrt{n \cdot (n-1)}}{\,n-2\,}\cdot\frac{m_3}{m_2^{3/2}}. + +where :math:`m_2` and :math:`m_3` correspond to the biased sample second and +third central moment respectively. + +Kurtosis +++++++++ + +The `kurtosis`_ of a population quantifies tail weight (also called tailedness) +of the probability distribution relative to a normal distribution. Positive +excess kurtosis indicates *heavier tails* whereas negative excess kurtosis +indicates *lighter tails*. Kurtosis is especially useful for identifying bins +where occasional extreme scores dominate uncertainty. OpenMC reports the +*adjusted excess kurtosis* (defined for :math:`n \ge 4`): + +.. math:: + + G_2 \;=\; \frac{(n-1)}{(n-2)(n-3)} + \left[(n+1)\,\frac{m_4}{m_2^{2}} \;-\; 3(n-1)\right]. + +where :math:`m_2` and :math:`m_4` correspond to the biased sample second and +fourth central moment respectively. For a perfectly normal distribution, the +excess kurtosis is :math:`0`. + +Variance of Variance +++++++++++++++++++++ + +The variance of the variance (also known as the coefficient of variation +squared) measures *stability of the sample variance* :math:`s^2` and, by +extension, the reliability of reported relative errors. High VOV means that +error bars themselves are noisy—often due to heavy tails, skewness, or too few +realizations. + +.. math:: + + VOV = \frac{s^2(s_{\bar{X}}^2)}{s_{\bar{X}}^4 } = \frac{m_4}{m_2^2} - \frac{1}{n} + +where :math:`s_{\bar{X}}^2` is the estimated variance of the mean and +:math:`s^2(s_{\bar{X}}^2)` is the estimated variance in :math:`s_{\bar{X}}^2`. +The MCNP manual suggests a hard threshold such that :math:`VOV < 0.1` to improve +the probability of forming a reliable confidence interval. However, OpenMC does +not enforce an universal cut-off because the suitability of any single threshold +depends strongly on problem specifics (estimator choice, variance-reduction +settings, tally binning, or even effective sample size). + + +Normality Tests (D'Agostino-Pearson) +++++++++++++++++++++++++++++++++++++ + +These normality test verify the hypothesis that fluctuations are *approximately +normal*, a working assumption behind many Monte Carlo diagnostics and +`confidence-interval heuristics`_. Tests are provided for: (i) skewness-only, +(ii) kurtosis-only, and (iii) the *omnibus* combination. OpenMC uses the +finite-sample-adjusted skewness :math:`G_1` and excess kurtosis :math:`G_2` +above to construct standardized normal scores :math:`Z_1` (from :math:`G_1`) and +:math:`Z_2` (from :math:`G_2`) via the D'Agostino-Pearson transformations. The +omnibus statistic is + +.. math:: + + K^2 \;=\; Z_1^{\,2} \;+\; Z_2^{\,2} + \;\sim\; \chi^2_{(2)} \quad \text{under } H_0:\ \text{normality}. + +OpenMC reports :math:`Z_1`, :math:`Z_2`, :math:`K^2`, and their p-values when +prerequisites are met (skewness for :math:`n\ge 3`, kurtosis and omnibus for +:math:`n\ge 4`). Given a user-chosen significance level :math:`\alpha` (default +is :math:`0.05`), reject :math:`H_0` if :math:`\text{p-value}<\alpha`; otherwise +fail to reject. OpenMC leaves the interpretation to the user, who should +consider VOV together with skewness, kurtosis, and normality tests results when +judging whether reported confidence intervals are credible for their application +[#norm-tests]_. + +.. [#norm-tests] + Higher-moments accumulation must be enabled with ``higher_moments = True`` + for running these diagnostics including the skewness, kurtosis, and normality + tests. + Figure of Merit +++++++++++++++ @@ -405,14 +500,16 @@ defined as .. math:: :label: relative_error - r = \frac{s_\bar{X}}{\bar{x}}. + r = \frac{s_{\bar{X}}}{\bar{x}}. Based on this definition, one can see that a higher FOM is desirable. The FOM is useful as a comparative tool. For example, if a variance reduction technique is being applied to a simulation, the FOM with variance reduction can be compared to the FOM without variance reduction to ascertain whether the reduction in variance outweighs the potential increase in execution time (e.g., due to -particle splitting). +particle splitting). It is important to note that MCNP reports the FOM using CPU +time (wall-clock time multiplied by the number of threads/cores), whereas OpenMC +reports the FOM using only the wall-clock time :math:`t`. Confidence Intervals ++++++++++++++++++++ @@ -521,6 +618,8 @@ improve the estimate of the percentile. .. rubric:: References +.. _confidence-interval heuristics: https://doi.org/10.1080/00031305.1990.10475751 + .. _following approximation: https://doi.org/10.1080/03610918708812641 .. _Bessel's correction: https://en.wikipedia.org/wiki/Bessel's_correction @@ -541,6 +640,10 @@ improve the estimate of the percentile. .. _converges in distribution: https://en.wikipedia.org/wiki/Convergence_of_random_variables#Convergence_in_distribution +.. _skewness: https://en.wikipedia.org/wiki/Skewness + +.. _kurtosis: https://en.wikipedia.org/wiki/Kurtosis + .. _confidence intervals: https://en.wikipedia.org/wiki/Confidence_interval .. _Student's t-distribution: https://en.wikipedia.org/wiki/Student%27s_t-distribution diff --git a/include/openmc/constants.h b/include/openmc/constants.h index a0d1646131c..b661934812a 100644 --- a/include/openmc/constants.h +++ b/include/openmc/constants.h @@ -291,7 +291,7 @@ enum class MgxsType { // ============================================================================ // TALLY-RELATED CONSTANTS -enum class TallyResult { VALUE, SUM, SUM_SQ, SIZE }; +enum class TallyResult { VALUE, SUM, SUM_SQ, SUM_THIRD, SUM_FOURTH }; enum class TallyType { VOLUME, MESH_SURFACE, SURFACE, PULSE_HEIGHT }; diff --git a/include/openmc/hdf5_interface.h b/include/openmc/hdf5_interface.h index 0092c08f8df..28b0d2b113d 100644 --- a/include/openmc/hdf5_interface.h +++ b/include/openmc/hdf5_interface.h @@ -100,8 +100,8 @@ void read_llong(hid_t obj_id, const char* name, long long* buffer, bool indep); void read_string( hid_t obj_id, const char* name, size_t slen, char* buffer, bool indep); -void read_tally_results( - hid_t group_id, hsize_t n_filter, hsize_t n_score, double* results); +void read_tally_results(hid_t group_id, hsize_t n_filter, hsize_t n_score, + hsize_t n_results, double* results); void write_attr_double(hid_t obj_id, int ndim, const hsize_t* dims, const char* name, const double* buffer); void write_attr_int(hid_t obj_id, int ndim, const hsize_t* dims, @@ -114,9 +114,9 @@ void write_int(hid_t group_id, int ndim, const hsize_t* dims, const char* name, void write_llong(hid_t group_id, int ndim, const hsize_t* dims, const char* name, const long long* buffer, bool indep); void write_string(hid_t group_id, int ndim, const hsize_t* dims, size_t slen, - const char* name, char const* buffer, bool indep); -void write_tally_results( - hid_t group_id, hsize_t n_filter, hsize_t n_score, const double* results); + const char* name, const char* buffer, bool indep); +void write_tally_results(hid_t group_id, hsize_t n_filter, hsize_t n_score, + hsize_t n_results, const double* results); } // extern "C" //============================================================================== diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index 3beeb9d5ac5..374daff92a0 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -106,6 +106,8 @@ class Tally { bool writable() const { return writable_; } + bool higher_moments() const { return higher_moments_; } + //---------------------------------------------------------------------------- // Other methods. @@ -190,6 +192,9 @@ class Tally { //! Whether to multiply by atom density for reaction rates bool multiply_density_ {true}; + //! Whether to accumulate higher moments (third and fourth) + bool higher_moments_ {false}; + int64_t index_; }; diff --git a/openmc/statepoint.py b/openmc/statepoint.py index a763db3971b..11986841f0e 100644 --- a/openmc/statepoint.py +++ b/openmc/statepoint.py @@ -434,6 +434,10 @@ def tallies(self): if "multiply_density" in group.attrs: tally.multiply_density = group.attrs["multiply_density"].item() > 0 + # Check if tally has higher_moments attribute + if 'higher_moments' in group.attrs: + tally.higher_moments = bool(group.attrs['higher_moments'][()]) + # Read the number of realizations n_realizations = group['n_realizations'][()] diff --git a/openmc/tallies.py b/openmc/tallies.py index 075b1e99117..25ec29a5850 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -3,6 +3,7 @@ import copy from functools import partial, reduce, wraps from itertools import product +from math import sqrt, log from numbers import Integral, Real import operator from pathlib import Path @@ -12,6 +13,7 @@ import numpy as np import pandas as pd import scipy.sparse as sps +from scipy.stats import chi2, norm import openmc import openmc.checkvalue as cv @@ -91,10 +93,20 @@ class Tally(IDManagerMixin): sum_sq : numpy.ndarray An array containing the sum of each independent realization squared for each bin + sum_third : numpy.ndarray + An array containing the sum of each independent realization to the third power for + each bin + sum_fourth : numpy.ndarray + An array containing the sum of each independent realization to the fourth power for + each bin mean : numpy.ndarray An array containing the sample mean for each bin std_dev : numpy.ndarray An array containing the sample standard deviation for each bin + vov : numpy.ndarray + An array containing the variance of the variance for each tally bin + higher_moments : bool + Whether or not the tally accumulates the sums third and fourth to compute higher-order moments figure_of_merit : numpy.ndarray An array containing the figure of merit for each bin @@ -129,8 +141,12 @@ def __init__(self, tally_id=None, name=''): self._sum = None self._sum_sq = None + self._sum_third = None + self._sum_fourth = None self._mean = None self._std_dev = None + self._vov = None + self._higher_moments = False self._simulation_time = None self._with_batch_statistics = False self._derived = False @@ -221,6 +237,15 @@ def multiply_density(self, value): cv.check_type('multiply density', value, bool) self._multiply_density = value + @property + def higher_moments(self) -> bool: + return self._higher_moments + + @higher_moments.setter + def higher_moments(self, value): + cv.check_type("higher_moments", value, bool) + self._higher_moments = value + @property def filters(self): return self._filters @@ -371,6 +396,11 @@ def _read_results(self): # Update nuclides nuclide_names = group['nuclides'][()] self._nuclides = [name.decode().strip() for name in nuclide_names] + # Check for higher_moments attribute + if "higher_moments" in group.attrs: + self._higher_moments = bool(group.attrs["higher_moments"][()]) + else: + self._higher_moments = False # Extract Tally data from the file data = group['results'] @@ -385,10 +415,25 @@ def _read_results(self): self._sum = sum_ self._sum_sq = sum_sq + if self._higher_moments: + # Extract additional Tally data when higher moments enabled + sum_third = data[:, :, 2] + sum_fourth = data[:, :, 3] + + # Reshape the results arrays + sum_third = np.reshape(sum_third, self.shape) + sum_fourth = np.reshape(sum_fourth, self.shape) + + # Set the additional data for this Tally + self._sum_third = sum_third + self._sum_fourth = sum_fourth + # Convert NumPy arrays to SciPy sparse LIL matrices if self.sparse: self._sum = sps.lil_matrix(self._sum.flatten(), self._sum.shape) self._sum_sq = sps.lil_matrix(self._sum_sq.flatten(), self._sum_sq.shape) + self._sum_third = sps.lil_matrix(self._sum_third.flatten(), self._sum_third.shape) + self._sum_fourth = sps.lil_matrix(self.sum_fourth.flatten(), self._sum_fourth.shape) # Read simulation time (needed for figure of merit) self._simulation_time = f["runtime"]["simulation"][()] @@ -428,6 +473,52 @@ def sum_sq(self, sum_sq): cv.check_type('sum_sq', sum_sq, Iterable) self._sum_sq = sum_sq + @property + @ensure_results + def sum_third(self): + if not self._higher_moments: + raise ValueError( + "Higher moments have not been enabled for this tally. To make " + "higher moments available, set the higher_moments attribute to " + "True before running a simulation." + ) + + if not self._sp_filename or self.derived: + return None + + if self.sparse: + return np.reshape(self._sum_third.toarray(), self.shape) + else: + return self._sum_third + + @sum_third.setter + def sum_third(self, sum_third): + cv.check_type("sum_third", sum_third, Iterable) + self._sum_third = sum_third + + @property + @ensure_results + def sum_fourth(self): + if not self._higher_moments: + raise ValueError( + "Higher moments have not been enabled for this tally. To make " + "higher moments available, set the higher_moments attribute to " + "True before running a simulation." + ) + + if not self._sp_filename or self.derived: + return None + + if self.sparse: + return np.reshape(self._sum_fourth.toarray(), self.shape) + else: + return self._sum_fourth + + @sum_fourth.setter + def sum_fourth(self, sum_fourth): + cv.check_type("sum_fourth", sum_fourth, Iterable) + self._sum_fourth = sum_fourth + @property def mean(self): if self._mean is None: @@ -470,14 +561,370 @@ def std_dev(self): else: return self._std_dev + @property + def vov(self): + if self._vov is None: + n = self.num_realizations + sum1 = self.sum + sum2 = self.sum_sq + sum3 = self.sum_third + sum4 = self.sum_fourth + self._vov = np.zeros_like(sum1, dtype=float) + + # Calculate the variance of the variance (Eq. 2.232 in + # https://doi.org/10.2172/2372634) + numerator = (sum4 - (4.0*sum3*sum1)/n + + (6.0*sum2*(sum1**2))/(n**2) + - (3.0*(sum1)**4)/(n**3)) + denominator = (sum2 - (1.0/n)*(sum1**2))**2 + + mask = denominator > 0.0 + + self._vov[mask] = numerator[mask]/denominator[mask] - 1.0/n + + if self.sparse: + self._vov = sps.lil_matrix(self._vov.flatten(), self._vov.shape) + + if self.sparse: + return np.reshape(self._vov.toarray(), self.shape) + else: + return self._vov + + @property + def m2(self): + n = self.num_realizations + return self.sum_sq/n - self.mean**2 + + @property + def m3(self): + n = self.num_realizations + mean = self.mean + sum2 = self.sum_sq/n + sum3 = self.sum_third/n + + return sum3 - 3.0*mean*sum2 + 2.0*mean**3 + + @property + def m4(self): + n = self.num_realizations + mean = self.mean + sum2 = self.sum_sq/n + sum3 = self.sum_third/n + sum4 = self.sum_fourth/n + + return sum4 - 4.0*mean*sum3 + 6.0*(mean**2)*sum2 - 3.0*mean**4 + + def skew(self, bias=False) -> np.ndarray: + """Return the sample skewness of each tally bin. + + This method computes and returns the unadjusted or adjusted + Fisher-Pearson coefficient of skewness. + + Parameters + ---------- + bias : bool + If False, calculations are corrected for bias and the adjusted + Fisher-Pearson skewness (:math:`G_1`) is returned. If True, + calculations are not corrected for bias and the unadjusted skewness + (:math:`g_1`) is returned. + + Returns + ------- + float + The skewness of each tally bin + """ + n = self.num_realizations + m2 = self.m2 + m3 = self.m3 + + with np.errstate(divide="ignore", invalid="ignore"): + g1 = np.where(m2 > 0.0, m3/(m2**1.5), 0.0) + + if bias: + return g1 + else: + if n <= 2: + raise ValueError("Insufficient number of independent realizations" + f"for bias-corrected skewness: need n >= 3, got {n=}.") + else: + return sqrt(n*(n - 1))/(n - 2)*g1 + + def kurtosis(self, fisher=True, bias=False) -> np.ndarray: + r"""Return the sample kurtosis of each tally bin. + + This method computes and returns the sample kurtosis using either + Pearson's or Fisher's definition, with or without finite-sample bias + correction. The value returned depends on the `bias` and `fisher` + arguments as follows: + + - **bias=True, fisher=False**: Returns :math:`b_2` (Pearson's kurtosis) + This is the raw fourth standardized moment: :math:`m_4/m_2^2`. For a + normal distribution, :math:`b_2\approx 3`. + + - **bias=True, fisher=True**: Returns :math:`g_2` (excess kurtosis) This + is :math:`b_2 - 3`, centered at 0 for normal distributions. Positive + values indicate heavier tails, negative values lighter tails. + + - **bias=False, fisher=True** (default): Returns :math:`G_2` (adjusted + excess kurtosis). This applies finite-sample bias correction to + :math:`g_2`. This is the recommended estimator for statistical + inference. + + - **bias=False, fisher=False**: Returns bias-corrected Pearson's + kurtosis. This is :math:`G_2 + 3`. + + Parameters + ---------- + fisher : bool, optional + If True (default), Fisher's definition is used (excess kurtosis). If + False, Pearson's definition is used. + bias : bool, optional + If False (default), calculations are corrected for statistical bias + using finite-sample adjustments. If True, calculations use the + biased estimator (population formulas). + + Returns + ------- + numpy.ndarray + The kurtosis of each tally bin + + """ + n = self.num_realizations + m2 = self.m2 + m4 = self.m4 + + with np.errstate(divide="ignore", invalid="ignore"): + b2 = np.where(m2 > 0.0, m4/(m2**2), 0.0) + g2 = b2 - 3.0 + + if bias: + # Biased estimator (g2 or b2) + return g2 if fisher else b2 + else: + # Unbiased estimator with finite-sample correction + if n <= 3: + raise ValueError("Insufficient number of independent realizations" + f"for bias-corrected kurtosis: need n >= 4, got {n=}.") + else: + G2 = ((n - 1)/((n - 2)*(n - 3)))*((n + 1)*g2 + 6.0) + return G2 if fisher else G2 + 3.0 + + def skewtest(self, alternative: str = "two-sided"): + """Perform D'Agostino and Pearson's test for skewness. + + This method tests the null hypothesis that the skewness of the + population that the sample was drawn from is the same as that of a + corresponding normal distribution. + + Parameters + ---------- + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. The following options are + available: + + * 'two-sided': the skewness of the distribution is different from + that of the normal distribution (i.e., non-zero) + * 'less': the skewness of the distribution is less than that of the + normal distribution + * 'greater': the skewness of the distribution is greater than that + of the normal distribution + + Returns + ------- + statistic : np.ndarray + The computed z-score for the skewness test for each tally bin + pvalue : np.ndarray + The p-value for the hypothesis test for each tally bin + + Notes + ----- + This test is based on D'Agostino and Pearson's test [1]_. The test + requires at least 8 realizations to produce valid results. + + References + ---------- + .. [1] D'Agostino, R. B. (1971), "An omnibus test of normality for + moderate and large sample size", Biometrika, 58, 341-348 + + """ + n = self.num_realizations + if n < 8: + raise ValueError("Skewness test is not well-defined for n < 8.") + + g1 = self.skew(bias=True) + + # --- Z1 (skewness) --- + y = g1 * sqrt(((n + 1.0)*(n + 3.0))/(6.0*(n - 2.0))) + beta2 = (3.0*(n**2 + 27.0*n - 70.0)*(n + 1.0)*(n + 3.0) + )/((n - 2.0)*(n + 5.0)*(n + 7.0)*(n + 9.0)) + W2 = -1.0 + sqrt(2.0*(beta2 - 1.0)) + delta = 1.0 / sqrt(log(sqrt(W2))) + alpha = sqrt(2.0 / (W2 - 1.0)) + Zb1 = np.where( + y >= 0.0, + delta*np.log((y/alpha) + np.sqrt((y/alpha)**2 + 1.0)), + -delta*np.log((-y/alpha) + np.sqrt((y/alpha)**2 + 1.0)) + ) + + # p-value + if alternative == "two-sided": + p = 2.0 * (1.0 - norm.cdf(np.abs(Zb1))) + elif alternative == "greater": + p = 1.0 - norm.cdf(Zb1) + elif alternative == "less": + p = norm.cdf(Zb1) + else: + raise ValueError("alternative must be 'two-sided', 'greater', or 'less'") + + return Zb1, p + + def kurtosistest(self, alternative: str = "two-sided"): + """Perform D'Agostino and Pearson's test for kurtosis. + + This method tests the null hypothesis that the kurtosis of the + population that the sample was drawn from is the same as that of a + corresponding normal distribution. + + Parameters + ---------- + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis. Default is 'two-sided'. + The following options are available: + + * 'two-sided': the kurtosis of the distribution is different from + that of the normal distribution + * 'less': the kurtosis of the distribution is less than that of the + normal distribution + * 'greater': the kurtosis of the distribution is greater than that + of the normal distribution + + Returns + ------- + statistic : np.ndarray + The computed z-score for the kurtosis test for each tally bin + pvalue : np.ndarray + The p-value for the hypothesis test for each tally bin + + Raises + ------ + ValueError + If the number of realizations is less than 20, or if an invalid + alternative hypothesis is specified. + + Notes + ----- + This test is based on D'Agostino and Pearson's test [1]_. The test + is typically recommended for at least 20 realizations to produce + valid results. + + References + ---------- + .. [1] D'Agostino, R. B. (1971), "An omnibus test of normality for + moderate and large sample size", Biometrika, 58, 341-348 + + """ + n = self.num_realizations + if n < 20: + raise ValueError("Kurtosis test is typically recommended for n >= 20.") + + b2 = self.kurtosis(bias=True, fisher=False) + + # --- Z2 (kurtosis) --- + mean_b2 = 3.0 * (n - 1.0) / (n + 1.0) + var_b2 = (24.0*n*(n - 2.0)*(n - 3.0)/( + (n + 1.0)**2*(n + 3.0)*(n + 5.0))) + x = (b2 - mean_b2)/np.sqrt(var_b2) + moment = ((6.0*(n**2 - 5.0*n + 2.0))/((n + 7.0)*(n + 9.0)) + )*sqrt((6.0*(n + 3.0)*(n + 5.0))/(n*(n - 2.0)*(n - 3.0))) + A = 6.0 + (8.0/moment)*((2.0/moment) + sqrt(1.0 + 4.0/(moment**2))) + Zb2 = (1.0- 2.0/(9.0*A) - ((1.0 - 2.0/A) / (1.0 + (x + )*sqrt(2.0/(A - 4.0))))**(1.0/3.0)) / sqrt(2.0/(9.0*A)) + + # p-value + if alternative == "two-sided": + p = 2.0 * (1.0 - norm.cdf(np.abs(Zb2))) + elif alternative == "greater": + p = 1.0 - norm.cdf(Zb2) + elif alternative == "less": + p = norm.cdf(Zb2) + else: + raise ValueError("alternative must be 'two-sided', 'greater', or 'less'") + + return Zb2, p + + def normaltest(self, alternative: str = "two-sided"): + """Perform D'Agostino and Pearson's omnibus test for normality. + + This method tests the null hypothesis that a sample comes from a + normal distribution. It combines skewness and kurtosis to produce an + omnibus test of normality. + + Parameters + ---------- + alternative : {'two-sided', 'less', 'greater'}, optional + Defines the alternative hypothesis used for the component skewness + and kurtosis tests. Default is 'two-sided'. The following options + are available: + + * 'two-sided': the distribution is different from normal + * 'less': used for the component tests + * 'greater': used for the component tests + + Returns + ------- + statistic : np.ndarray + The computed z-score for the normality test for each tally bin + pvalue : np.ndarray + The p-value for the hypothesis test for each tally bin + + Raises + ------ + ValueError + If the number of realizations is less than 20, or if an invalid + alternative hypothesis is specified. + + Notes + ----- + This test combines a test for skewness and a test for kurtosis to + produce an omnibus test [1]_. The test statistic is: + + .. math:: + + K^2 = Z_1^2 + Z_2^2 + + where :math:`Z_1` is the z-score from the skewness test and + :math:`Z_2` is the z-score from the kurtosis test. This statistic + follows a chi-square distribution with 2 degrees of freedom. + + The test requires at least 20 realizations to produce valid results. + + References + ---------- + .. [1] D'Agostino, R. B. and Pearson, E. S. (1973), "Tests for + departure from normality", Biometrika, 60, 613-622 + + """ + n = self.num_realizations + if n < 20: + raise ValueError("normaltest requires n >= 20 (per D'Agostino-Pearson).") + + # Use the component tests + Z1, _ = self.skewtest(alternative) + Z2, _ = self.kurtosistest(alternative) + + # Combine as chi-square with df=2 since we have skewness and kurtosis + K2 = Z1*Z1 + Z2*Z2 + p = chi2.sf(K2, df=2) + return K2, p + @property def figure_of_merit(self): mean = self.mean std_dev = self.std_dev fom = np.zeros_like(mean) nonzero = np.abs(mean) > 0 - fom[nonzero] = 1.0 / ( - (std_dev[nonzero] / mean[nonzero])**2 * self._simulation_time) + rel_err = std_dev[nonzero] / mean[nonzero] + fom[nonzero] = 1.0 / (rel_err**2 * self._simulation_time) return fom @property @@ -528,6 +975,12 @@ def sparse(self, sparse): if self._sum_sq is not None: self._sum_sq = sps.lil_matrix(self._sum_sq.flatten(), self._sum_sq.shape) + if self._sum_third is not None: + self._sum_third = sps.lil_matrix(self._sum_third.flatten(), + self._sum_third.shape) + if self._sum_fourth is not None: + self._sum_fourth = sps.lil_matrix(self._sum_fourth.flatten(), + self._sum_fourth.shape) if self._mean is not None: self._mean = sps.lil_matrix(self._mean.flatten(), self._mean.shape) @@ -543,6 +996,10 @@ def sparse(self, sparse): self._sum = np.reshape(self._sum.toarray(), self.shape) if self._sum_sq is not None: self._sum_sq = np.reshape(self._sum_sq.toarray(), self.shape) + if self._sum_third is not None: + self._sum_third = np.reshape(self._sum_third.toarray(), self.shape) + if self._sum_fourth is not None: + self._sum_fourth = np.reshape(self._sum_fourth.toarray(), self.shape) if self._mean is not None: self._mean = np.reshape(self._mean.toarray(), self.shape) if self._std_dev is not None: @@ -869,6 +1326,34 @@ def merge(self, other): merged_tally._sum_sq = np.reshape(merged_sum_sq, merged_tally.shape) + # Concatenate sum_third arrays if present in both tallies + if self._sum_third is not None and other._sum_third is not None: + self_sum_third = self.get_reshaped_data(value="sum_third") + other_sum_third = other_copy.get_reshaped_data(value="sum_third") + + if join_right: + merged_sum_third = np.concatenate((self_sum_third, other_sum_third), + axis=merge_axis) + else: + merged_sum_third = np.concatenate((other_sum_third, self_sum_third), + axis=merge_axis) + + merged_tally._sum_third = np.reshape(merged_sum_third, merged_tally.shape) + + # Concatenate sum_fourth arrays if present in both tallies + if self._sum_fourth is not None and other._sum_fourth is not None: + self_sum_fourth = self.get_reshaped_data(value="sum_fourth") + other_sum_fourth = other_copy.get_reshaped_data(value="sum_fourth") + + if join_right: + merged_sum_fourth = np.concatenate((self_sum_fourth, other_sum_fourth), + axis=merge_axis) + else: + merged_sum_fourth = np.concatenate((other_sum_fourth, self_sum_fourth), + axis=merge_axis) + + merged_tally._sum_fourth = np.reshape(merged_sum_fourth, merged_tally.shape) + # Concatenate mean arrays if present in both tallies if self.mean is not None and other.mean is not None: self_mean = self.get_reshaped_data(value='mean') @@ -958,6 +1443,11 @@ def to_xml_element(self): subelement = ET.SubElement(element, "derivative") subelement.text = str(self.derivative.id) + # Optional higher moments accumulation + if self.higher_moments: + subelement = ET.SubElement(element, "higher_moments") + subelement.text = str(self.higher_moments).lower() + return element def add_results(self, statepoint: cv.PathLike | openmc.StatePoint): @@ -984,8 +1474,12 @@ def add_results(self, statepoint: cv.PathLike | openmc.StatePoint): # point are based on the current statepoint file self._sum = None self._sum_sq = None + self._sum_third = None + self._sum_fourth = None self._mean = None self._std_dev = None + self._vov = None + self._higher_moments = False self._num_realizations = 0 self._results_read = False @@ -1355,7 +1849,9 @@ def get_values(self, scores=[], filters=[], filter_bins=[], (value == 'std_dev' and self.std_dev is None) or \ (value == 'rel_err' and self.mean is None) or \ (value == 'sum' and self.sum is None) or \ - (value == 'sum_sq' and self.sum_sq is None): + (value == 'sum_sq' and self.sum_sq is None) or \ + (value == "sum_third" and self.sum_third is None) or \ + (value == "sum_fourth" and self.sum_fourth is None): msg = f'The Tally ID="{self.id}" has no data to return' raise ValueError(msg) @@ -1378,10 +1874,14 @@ def get_values(self, scores=[], filters=[], filter_bins=[], data = self.sum[indices] elif value == 'sum_sq': data = self.sum_sq[indices] + elif value == "sum_third": + data = self.sum_third[indices] + elif value == "sum_fourth": + data = self.sum_fourth[indices] else: msg = f'Unable to return results from Tally ID="{value}" since ' \ f'the requested value "{self.id}" is not \'mean\', ' \ - '\'std_dev\', \'rel_err\', \'sum\', or \'sum_sq\'' + '\'std_dev\', \'rel_err\', \'sum\', \'sum_sq\', \'sum_third\' or \'sum_fourth\'' raise LookupError(msg) return data @@ -2711,6 +3211,16 @@ def get_slice(self, scores=[], filters=[], filter_bins=[], nuclides=[], new_sum_sq = self.get_values(scores, filters, filter_bins, nuclides, 'sum_sq') new_tally.sum_sq = new_sum_sq + if not self.derived and self._sum_third is not None: + new_sum_third = self.get_values( + scores, filters, filter_bins, nuclides, "sum_third" + ) + new_tally._sum_third = new_sum_third + if not self.derived and self._sum_fourth is not None: + new_sum_fourth = self.get_values( + scores, filters, filter_bins, nuclides, "sum_fourth" + ) + new_tally._sum_fourth = new_sum_fourth if self.mean is not None: new_mean = self.get_values(scores, filters, filter_bins, nuclides, 'mean') @@ -3151,6 +3661,12 @@ def diagonalize_filter(self, new_filter, filter_position=-1): if not self.derived and self.sum_sq is not None: new_tally._sum_sq = np.zeros(new_tally.shape, dtype=np.float64) new_tally._sum_sq[diag_indices, :, :] = self.sum_sq + if not self.derived and self._sum_third is not None: + new_tally._sum_third = np.zeros(new_tally.shape, dtype=np.float64) + new_tally._sum_third[diag_indices, :, :] = self.sum_third + if not self.derived and self._sum_fourth is not None: + new_tally._sum_fourth = np.zeros(new_tally.shape, dtype=np.float64) + new_tally._sum_fourth[diag_indices, :, :] = self.sum_fourth if self.mean is not None: new_tally._mean = np.zeros(new_tally.shape, dtype=np.float64) new_tally._mean[diag_indices, :, :] = self.mean diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index bf1f79549e0..c56d485e281 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -536,14 +536,14 @@ void read_complex( H5Tclose(complex_id); } -void read_tally_results( - hid_t group_id, hsize_t n_filter, hsize_t n_score, double* results) +void read_tally_results(hid_t group_id, hsize_t n_filter, hsize_t n_score, + hsize_t n_results, double* results) { // Create dataspace for hyperslab in memory constexpr int ndim = 3; - hsize_t dims[ndim] {n_filter, n_score, 3}; + hsize_t dims[ndim] {n_filter, n_score, n_results}; hsize_t start[ndim] {0, 0, 1}; - hsize_t count[ndim] {n_filter, n_score, 2}; + hsize_t count[ndim] {n_filter, n_score, n_results - 1}; hid_t memspace = H5Screate_simple(ndim, dims, nullptr); H5Sselect_hyperslab(memspace, H5S_SELECT_SET, start, nullptr, count, nullptr); @@ -686,15 +686,15 @@ void write_string( group_id, 0, nullptr, buffer.length(), name, buffer.c_str(), indep); } -void write_tally_results( - hid_t group_id, hsize_t n_filter, hsize_t n_score, const double* results) +void write_tally_results(hid_t group_id, hsize_t n_filter, hsize_t n_score, + hsize_t n_results, const double* results) { // Set dimensions of sum/sum_sq hyperslab to store constexpr int ndim = 3; - hsize_t count[ndim] {n_filter, n_score, 2}; + hsize_t count[ndim] {n_filter, n_score, n_results - 1}; // Set dimensions of results array - hsize_t dims[ndim] {n_filter, n_score, 3}; + hsize_t dims[ndim] {n_filter, n_score, n_results}; hsize_t start[ndim] {0, 0, 1}; hid_t memspace = H5Screate_simple(ndim, dims, nullptr); H5Sselect_hyperslab(memspace, H5S_SELECT_SET, start, nullptr, count, nullptr); diff --git a/src/state_point.cpp b/src/state_point.cpp index 8195c486507..0b0fed13248 100644 --- a/src/state_point.cpp +++ b/src/state_point.cpp @@ -201,6 +201,12 @@ extern "C" int openmc_statepoint_write(const char* filename, bool* write_source) write_attribute(tally_group, "multiply_density", 0); } + if (tally->higher_moments()) { + write_attribute(tally_group, "higher_moments", 1); + } else { + write_attribute(tally_group, "higher_moments", 0); + } + if (tally->estimator_ == TallyEstimator::ANALOG) { write_dataset(tally_group, "estimator", "analog"); } else if (tally->estimator_ == TallyEstimator::TRACKLENGTH) { @@ -264,12 +270,13 @@ extern "C" int openmc_statepoint_write(const char* filename, bool* write_source) for (const auto& tally : model::tallies) { if (!tally->writable_) continue; - // Write sum and sum_sq for each bin + + // Write results for each bin std::string name = "tally " + std::to_string(tally->id_); hid_t tally_group = open_group(tallies_group, name.c_str()); auto& results = tally->results_; write_tally_results(tally_group, results.shape()[0], - results.shape()[1], results.data()); + results.shape()[1], results.shape()[2], results.data()); close_group(tally_group); } } else { @@ -509,7 +516,8 @@ extern "C" int openmc_statepoint_load(const char* filename) } else { auto& results = tally->results_; read_tally_results(tally_group, results.shape()[0], - results.shape()[1], results.data()); + results.shape()[1], results.shape()[2], results.data()); + read_dataset(tally_group, "n_realizations", tally->n_realizations_); close_group(tally_group); } @@ -1001,7 +1009,8 @@ void write_tally_results_nr(hid_t file_id) // Write reduced tally results to file auto shape = results_copy.shape(); - write_tally_results(tally_group, shape[0], shape[1], results_copy.data()); + write_tally_results( + tally_group, shape[0], shape[1], shape[2], results_copy.data()); close_group(tally_group); } else { diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 12ee3427ebd..9daeb1d69f9 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -107,6 +107,9 @@ Tally::Tally(pugi::xml_node node) multiply_density_ = get_node_value_bool(node, "multiply_density"); } + if (check_for_node(node, "higher_moments")) { + higher_moments_ = get_node_value_bool(node, "higher_moments"); + } // ======================================================================= // READ DATA FOR FILTERS @@ -800,7 +803,11 @@ void Tally::init_triggers(pugi::xml_node node) void Tally::init_results() { int n_scores = scores_.size() * nuclides_.size(); - results_ = xt::empty({n_filter_bins_, n_scores, 3}); + if (higher_moments_) { + results_ = xt::empty({n_filter_bins_, n_scores, 5}); + } else { + results_ = xt::empty({n_filter_bins_, n_scores, 3}); + } } void Tally::reset() @@ -838,14 +845,33 @@ void Tally::accumulate() norm = 1.0; } -// Accumulate each result + // Accumulate each result + if (higher_moments_) { +#pragma omp parallel for + // filter bins (specific cell, energy bins) + for (int i = 0; i < results_.shape()[0]; ++i) { + // score bins (flux, total reaction rate, fission reaction rate, etc.) + for (int j = 0; j < results_.shape()[1]; ++j) { + double val = results_(i, j, TallyResult::VALUE) * norm; + double val2 = val * val; + results_(i, j, TallyResult::VALUE) = 0.0; + results_(i, j, TallyResult::SUM) += val; + results_(i, j, TallyResult::SUM_SQ) += val2; + results_(i, j, TallyResult::SUM_THIRD) += val2 * val; + results_(i, j, TallyResult::SUM_FOURTH) += val2 * val2; + } + } + } else { #pragma omp parallel for - for (int i = 0; i < results_.shape()[0]; ++i) { - for (int j = 0; j < results_.shape()[1]; ++j) { - double val = results_(i, j, TallyResult::VALUE) * norm; - results_(i, j, TallyResult::VALUE) = 0.0; - results_(i, j, TallyResult::SUM) += val; - results_(i, j, TallyResult::SUM_SQ) += val * val; + // filter bins (specific cell, energy bins) + for (int i = 0; i < results_.shape()[0]; ++i) { + // score bins (flux, total reaction rate, fission reaction rate, etc.) + for (int j = 0; j < results_.shape()[1]; ++j) { + double val = results_(i, j, TallyResult::VALUE) * norm; + results_(i, j, TallyResult::VALUE) = 0.0; + results_(i, j, TallyResult::SUM) += val; + results_(i, j, TallyResult::SUM_SQ) += val * val; + } } } } diff --git a/src/weight_windows.cpp b/src/weight_windows.cpp index 674fae49c3b..9333800bc36 100644 --- a/src/weight_windows.cpp +++ b/src/weight_windows.cpp @@ -547,8 +547,10 @@ void WeightWindows::update_weights(const Tally* tally, const std::string& value, // build a shape for a view of the tally results, this will always be // dimension 5 (3 filter dimensions, 1 score dimension, 1 results dimension) - std::array shape = { - 1, 1, 1, tally->n_scores(), static_cast(TallyResult::SIZE)}; + // Look for the size of the last dimension of the results array + const auto& results_arr = tally->results(); + const int results_dim = static_cast(results_arr.shape()[2]); + std::array shape = {1, 1, 1, tally->n_scores(), results_dim}; // set the shape for the filters applied on the tally for (int i = 0; i < tally->filters().size(); i++) { @@ -586,7 +588,7 @@ void WeightWindows::update_weights(const Tally* tally, const std::string& value, // get a fully reshaped view of the tally according to tally ordering of // filters - auto tally_values = xt::reshape_view(tally->results(), shape); + auto tally_values = xt::reshape_view(results_arr, shape); // get a that is (particle, energy, mesh, scores, values) auto transposed_view = xt::transpose(tally_values, transpose); diff --git a/tests/unit_tests/test_tallies.py b/tests/unit_tests/test_tallies.py index c38f067d58c..7b1bf0a2fee 100644 --- a/tests/unit_tests/test_tallies.py +++ b/tests/unit_tests/test_tallies.py @@ -1,6 +1,8 @@ +from math import sqrt import numpy as np import pytest import openmc +import scipy.stats as sps def test_xml_roundtrip(run_in_tmpdir): @@ -163,3 +165,214 @@ def test_tally_application(sphere_model, run_in_tmpdir): assert (sp_tally.std_dev == tally.std_dev).all() assert (sp_tally.mean == tally.mean).all() assert sp_tally.nuclides == tally.nuclides + +def _tally_from_data(x, *, higher_moments=True, normality=True): + t = openmc.Tally() + t.scores = ["flux"] # 1 score + t.nuclides = [openmc.Nuclide("H1")] # 1 nuclide + t._sp_filename = "dummy.h5" # mark "results available" + t._results_read = True # don't try to read from disk + t._num_realizations = int(len(x)) # n + t.higher_moments = bool(higher_moments) + + x = np.asarray(x, dtype=float) + # (num_filter_bins=1, num_nuclides=1, num_scores=1) -> (1,1,1) arrays + t._sum = np.array([[[np.sum(x)]]], dtype=float) + t._sum_sq = np.array([[[np.sum(x**2)]]], dtype=float) + if higher_moments: + t._sum_third = np.array([[[np.sum(x**3)]]], dtype=float) + t._sum_fourth = np.array([[[np.sum(x**4)]]], dtype=float) + return t + +@pytest.mark.parametrize( + "x, skew_true, kurt_true", + [ # Rademacher distribution + (np.array([1.0, -1.0] * 200), 0.0, 1.0), + # Two-point {0,3} with p(0)=3/4, p(3)=1/4 + (np.concatenate([np.zeros(600), np.full(200, 3.0)]), 2.0 / sqrt(3.0), 7.0 / 3.0), + # Bernoulli distribution + (np.concatenate([np.ones(300), np.zeros(700)]), (1 - 2 * 0.3) / sqrt(0.3 * 0.7), (1 - 3 * 0.3 + 3 * 0.3**2) / (0.3 * 0.7)), + ], +) +def test_b1_b2_analytical_against_tally(x, skew_true, kurt_true): + t = _tally_from_data(x, higher_moments=True, normality=False) + + g1 = t.skew(bias=True)[0, 0, 0] + b2 = t.kurtosis(bias=True, fisher=False)[0, 0, 0] + + assert np.isclose(g1, skew_true, rtol=0, atol=1e-12) + assert np.isclose(b2, kurt_true, rtol=0, atol=1e-12) + +@pytest.mark.parametrize( + "draw, skew_true, kurt_true", + [(lambda rng, n: rng.normal(0, 1, n), 0.0, 3.0), # Normal + (lambda rng, n: rng.random(n), 0.0, 1.8), # Uniform(0,1) + (lambda rng, n: rng.exponential(1.0, n), 2.0, 9.0), # Exp(1) + (lambda rng, n: (rng.random(n) < 0.3).astype(float), + (1 - 2 * 0.3) / sqrt(0.3 * 0.7), + (1 - 3 * 0.3 + 3 * 0.3**2) / (0.3 * 0.7),),],) + +def test_b1_b2_scipy_and_theory(draw, skew_true, kurt_true): + rng = np.random.default_rng(12345) + N = 200_000 + x = draw(rng, N) + + # Tally outputs + t = _tally_from_data(x, higher_moments=True, normality=False) + g1_t = t.skew(bias=True)[0, 0, 0] + b2_t = t.kurtosis(bias=True, fisher=False)[0, 0, 0] + + # SciPy (population, bias=True to match population-moment style) + skew_sp = sps.skew(x, bias=True) + kurt_sp = sps.kurtosis(x, fisher=False, bias=True) + + # Compare to SciPy numerically + assert np.isclose(g1_t, skew_sp, rtol=0, atol=5e-3) + assert np.isclose(b2_t, kurt_sp, rtol=0, atol=5e-3) + + # Compare to analytical targets with size-dependent tolerances + tol_skew = 0.02 if abs(skew_true) < 0.5 else 0.05 + tol_kurt = 0.03 if kurt_true < 4 else 0.1 + assert abs(g1_t - skew_true) < tol_skew + assert abs(b2_t - kurt_true) < tol_kurt + + +def test_kurtosis_bias_fisher_combinations(): + """Test that all combinations of bias and fisher match scipy.stats.kurtosis""" + rng = np.random.default_rng(42) + x = rng.normal(0, 1, 10000) + + t = _tally_from_data(x, higher_moments=True, normality=False) # Test all four combinations + # 1. bias=True, fisher=False (Pearson's kurtosis, b2) + b2_tally = t.kurtosis(bias=True, fisher=False)[0, 0, 0] + b2_scipy = sps.kurtosis(x, fisher=False, bias=True) + assert np.isclose(b2_tally, b2_scipy, rtol=0, atol=1e-10) + assert np.isclose(b2_tally, 3.0, rtol=0.05, atol=0.1) # Should be ~3 for normal + + # 2. bias=True, fisher=True (excess kurtosis, g2) + g2_tally = t.kurtosis(bias=True, fisher=True)[0, 0, 0] + g2_scipy = sps.kurtosis(x, fisher=True, bias=True) + assert np.isclose(g2_tally, g2_scipy, rtol=0, atol=1e-10) + assert np.isclose(g2_tally, 0.0, rtol=0, atol=0.1) # Should be ~0 for normal + assert np.isclose(g2_tally, b2_tally - 3.0, rtol=0, atol=1e-10) # g2 = b2 - 3 + + # 3. bias=False, fisher=True (adjusted excess kurtosis, G2) + G2_tally = t.kurtosis(bias=False, fisher=True)[0, 0, 0] + G2_tally_default = t.kurtosis()[0, 0, 0] # Should be same as default + G2_scipy = sps.kurtosis(x, fisher=True, bias=False) + assert np.isclose(G2_tally, G2_tally_default, rtol=0, atol=1e-10) + assert np.isclose(G2_tally, G2_scipy, rtol=0, atol=1e-10) + assert np.isclose(G2_tally, 0.0, rtol=0, atol=0.1) # Should be ~0 for normal + + # 4. bias=False, fisher=False (adjusted Pearson's kurtosis) + adj_b2_tally = t.kurtosis(bias=False, fisher=False)[0, 0, 0] + adj_b2_scipy = sps.kurtosis(x, fisher=False, bias=False) + assert np.isclose(adj_b2_tally, adj_b2_scipy, rtol=0, atol=1e-10) + assert np.isclose(adj_b2_tally, 3.0, rtol=0.05, atol=0.1) # Should be ~3 for normal + assert np.isclose(adj_b2_tally, G2_tally + 3.0, rtol=0, atol=1e-10) # adj_b2 = G2 + 3 + + +def test_ztests_scipy_comparison(): + rng = np.random.default_rng(987) + x_norm = rng.normal(size=50_000) + x_exp = rng.exponential(size=50_000) + + # -------- Normal dataset (should not reject) -------- + t0 = _tally_from_data(x_norm, higher_moments=True, normality=True) + Zb1_0, p_skew_0 = t0.skewtest(alternative="two-sided") + Zb2_0, p_kurt_0 = t0.kurtosistest(alternative="two-sided") + K2_0, p_omni_0 = t0.normaltest(alternative="two-sided") + + Zb1_0 = Zb1_0.ravel()[0] + p_skew_0 = p_skew_0.ravel()[0] + Zb2_0 = Zb2_0.ravel()[0] + p_kurt_0 = p_kurt_0.ravel()[0] + K2_0 = K2_0.ravel()[0] + p_omni_0 = p_omni_0.ravel()[0] + + z_skew_sp0, p_skew_sp0 = sps.skewtest(x_norm) + z_kurt_sp0, p_kurt_sp0 = sps.kurtosistest(x_norm) + k2_sp0, p_omni_sp0 = sps.normaltest(x_norm) + + assert np.isclose(Zb1_0, z_skew_sp0, atol=0.15) + assert np.isclose(Zb2_0, z_kurt_sp0, atol=0.15) + assert np.isclose(K2_0, k2_sp0, atol=0.30) + assert np.isclose(p_skew_0, p_skew_sp0, atol=5e-3) + assert np.isclose(p_kurt_0, p_kurt_sp0, atol=5e-3) + assert np.isclose(p_omni_0, p_omni_sp0, atol=5e-3) + + # -------- Exponential dataset (should strongly reject) -------- + t1 = _tally_from_data(x_exp, higher_moments=True, normality=True) + + Zb1_1, p_skew_1 = t1.skewtest(alternative="two-sided") + Zb2_1, p_kurt_1 = t1.kurtosistest(alternative="two-sided") + K2_1, p_omni_1 = t1.normaltest(alternative="two-sided") + + Zb1_1 = Zb1_1.ravel()[0] + p_skew_1 = p_skew_1.ravel()[0] + Zb2_1 = Zb2_1.ravel()[0] + p_kurt_1 = p_kurt_1.ravel()[0] + K2_1 = K2_1.ravel()[0] + p_omni_1 = p_omni_1.ravel()[0] + + z_skew_sp1, p_skew_sp1 = sps.skewtest(x_exp) + z_kurt_sp1, p_kurt_sp1 = sps.kurtosistest(x_exp) + k2_sp1, p_omni_sp1 = sps.normaltest(x_exp) + + # Both pipelines should reject very strongly + assert p_skew_1 < 1e-6 and p_skew_sp1 < 1e-6 + assert p_kurt_1 < 1e-6 and p_kurt_sp1 < 1e-6 + assert p_omni_1 < 1e-6 and p_omni_sp1 < 1e-6 + + # Right-skewed and heavy-tailed → large positive Z-statistics + assert Zb1_1 > 30 and z_skew_sp1 > 30 + assert Zb2_1 > 30 and z_kurt_sp1 > 30 + assert K2_1 > 2000 and k2_sp1 > 2000 + +def test_vov_stochastic(sphere_model, run_in_tmpdir): + tally = openmc.Tally(name="test tally") + ef = openmc.EnergyFilter([0.0, 0.1, 1.0, 10.0e6]) + mesh = openmc.RegularMesh.from_domain(sphere_model.geometry, (2, 2, 2)) + mf = openmc.MeshFilter(mesh) + tally.filters = [ef, mf] + tally.scores = ["flux", "absorption", "fission", "scatter"] + tally.higher_moments = True + sphere_model.tallies = [tally] + + sp_file = sphere_model.run(apply_tally_results=True) + + assert tally._mean is None + assert tally._std_dev is None + assert tally._sum is None + assert tally._sum_sq is None + assert tally._sum_third is None + assert tally._sum_fourth is None + assert tally._num_realizations == 0 + assert tally._sp_filename == sp_file + + with openmc.StatePoint(sp_file) as sp: + assert tally in sp.tallies.values() + sp_tally = sp.tallies[tally.id] + + assert np.all(sp_tally.std_dev == tally.std_dev) + assert np.all(sp_tally.mean == tally.mean) + assert np.all(sp_tally.vov == tally.vov) + assert sp_tally.nuclides == tally.nuclides + + n = sp_tally.num_realizations + mean = sp_tally.mean + sum_ = sp_tally._sum + sum_sq = sp_tally._sum_sq + sum_third = sp_tally._sum_third + sum_fourth = sp_tally._sum_fourth + + expected_vov = np.zeros_like(mean) + nonzero = np.abs(mean) > 0 + + num = (sum_fourth - (4.0*sum_third*sum_)/n + (6.0*sum_sq*sum_**2)/(n**2) + - (3.0*sum_**4)/(n**3)) + den = (sum_sq - (1.0/n)*sum_**2)**2 + + expected_vov[nonzero] = num[nonzero]/den[nonzero] - 1.0/n + + assert np.allclose(expected_vov, sp_tally.vov, rtol=1e-7, atol=0.0) From 4e24c2d933dcf92b4ddb51dfca362a20ef82f2f6 Mon Sep 17 00:00:00 2001 From: April Novak Date: Wed, 12 Nov 2025 15:59:45 -0600 Subject: [PATCH 149/159] Update documentation for particle tracks (#3627) Co-authored-by: Paul Romano --- docs/source/usersguide/processing.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/source/usersguide/processing.rst b/docs/source/usersguide/processing.rst index 8b5ae53fac6..fe6ab0826ff 100644 --- a/docs/source/usersguide/processing.rst +++ b/docs/source/usersguide/processing.rst @@ -68,17 +68,17 @@ generation, and particle number of the desired particle. For example, to create a track file for particle 4 of batch 1 and generation 2:: settings = openmc.Settings() - settings.track = (1, 2, 4) + settings.track = [(1, 2, 4)] -To specify multiple particles, the length of the iterable should be a multiple -of three, e.g., if we wanted particles 3 and 4 from batch 1 and generation 2:: +To specify multiple particles, specify a list of tuples, e.g., if we wanted +particles 3 and 4 from batch 1 and generation 2:: - settings.track = (1, 2, 3, 1, 2, 4) + settings.track = [(1, 2, 3), (1, 2, 4)] -After running OpenMC, the working directory will contain a file of the form -"track_(batch #)_(generation #)_(particle #).h5" for each particle tracked. -These track files can be converted into VTK poly data files with the -:class:`openmc.Tracks` class. +After running OpenMC (now, without the ``-t`` argument), the working directory +will contain a file named `tracks.h5`, which contains a collection of particle +tracks. These track files can be converted into VTK poly data files or +matplotlib plots with the :class:`openmc.Tracks` class. ---------------------- Source Site Processing From 50bb0df191053e6fc58b9d779022402ac159115e Mon Sep 17 00:00:00 2001 From: Lorenzo Chierici Date: Wed, 12 Nov 2025 23:45:48 +0100 Subject: [PATCH 150/159] depletion-thermochemistry: Redox control transfer rates (#2783) Co-authored-by: Gavin Ridley Co-authored-by: Paul Romano --- openmc/deplete/abc.py | 30 ++++++++++ openmc/deplete/chain.py | 54 ++++++++++++++++++ openmc/deplete/pool.py | 13 +++++ openmc/deplete/transfer_rates.py | 43 +++++++++++++- .../ref_depletion_with_ext_source.h5 | Bin 37328 -> 37328 bytes .../ref_depletion_with_feed.h5 | Bin 37328 -> 37328 bytes .../ref_depletion_with_redox.h5 | Bin 0 -> 37328 bytes .../ref_depletion_with_removal.h5 | Bin 37328 -> 37328 bytes .../ref_depletion_with_removal_and_redox.h5 | Bin 0 -> 37328 bytes .../ref_depletion_with_transfer.h5 | Bin 37328 -> 37328 bytes .../ref_depletion_with_transfer_and_redox.h5 | Bin 0 -> 37328 bytes .../ref_no_depletion_only_feed.h5 | Bin 37328 -> 37328 bytes .../ref_no_depletion_only_removal.h5 | Bin 37328 -> 37328 bytes .../ref_no_depletion_with_ext_source.h5 | Bin 37328 -> 37328 bytes .../ref_no_depletion_with_transfer.h5 | Bin 37328 -> 37328 bytes .../deplete_with_transfer_rates/test.py | 22 ++++--- .../unit_tests/test_deplete_transfer_rates.py | 32 +++++++++++ 17 files changed, 185 insertions(+), 9 deletions(-) create mode 100644 tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_redox.h5 create mode 100644 tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_removal_and_redox.h5 create mode 100644 tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_transfer_and_redox.h5 diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index c5a6219c7b8..fb18d86afc4 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -1013,6 +1013,36 @@ def add_external_source_rate( material, composition, rate, rate_units, timesteps) + def add_redox(self, material, buffer, oxidation_states, timesteps=None): + """Add redox control to depletable material. + + Parameters + ---------- + material : openmc.Material or str or int + Depletable material + buffer : dict + Dictionary of buffer nuclides used to maintain redox balance. Keys + are nuclide names (strings) and values are their respective + fractions (float) that collectively sum to 1. + oxidation_states : dict + User-defined oxidation states for elements. Keys are element symbols + (e.g., 'H', 'He'), and values are their corresponding oxidation + states as integers (e.g., +1, 0). + timesteps : list of int, optional + List of timestep indices where to set external source rates. + Defaults to None, which means the external source rate is set for + all timesteps. + """ + if self.transfer_rates is None: + if hasattr(self.operator, 'model'): + materials = self.operator.model.materials + elif hasattr(self.operator, 'materials'): + materials = self.operator.materials + self.transfer_rates = TransferRates( + self.operator, materials, len(self.timesteps)) + + self.transfer_rates.set_redox(material, buffer, oxidation_states, timesteps) + @add_params class SIIntegrator(Integrator): r"""Abstract class for the Stochastic Implicit Euler integrators diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index ac5c02aa501..f34416d5618 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -7,6 +7,7 @@ from io import StringIO from itertools import chain import math +import numpy as np import re from collections import defaultdict, namedtuple from collections.abc import Mapping, Iterable @@ -714,6 +715,59 @@ def setval(i, j, val): # Return CSC representation instead of DOK return sp.csc_matrix((vals, (rows, cols)), shape=(n, n)) + def add_redox_term(self, matrix, buffer, oxidation_states): + """Adds a redox term to the depletion matrix from data contained in + the matrix itself and a few user-inputs. + + The redox term to add to the buffer nuclide :math:`N_j` can be written + as: :math:`\frac{dN_j(t)}{dt} = + \cdots - \frac{1}{OS_j}\sum_i N_i a_{ij} \cdot OS_i ` + + where :math:`OS` is the oxidation states vector and `a_{ij}` the + corresponding term in the Bateman matrix. + + Parameters + ---------- + matrix : scipy.sparse.csc_matrix + Sparse matrix representing depletion + buffer : dict + Dictionary of buffer nuclides used to maintain anoins net balance. + Keys are nuclide names (strings) and values are their respective + fractions (float) that collectively sum to 1. + oxidation_states : dict + User-defined oxidation states for elements. Keys are element symbols + (e.g., 'H', 'He'), and values are their corresponding oxidation + states as integers (e.g., +1, 0). + Returns + ------- + matrix : scipy.sparse.csc_matrix + Sparse matrix with redox term added + """ + # Elements list with the same size as self.nuclides + elements = [re.split(r'\d+', nuc.name)[0] for nuc in self.nuclides] + + # Match oxidation states with all elements and add 0 if not data + os = np.array([oxidation_states[elm] if elm in oxidation_states else 0 + for elm in elements]) + + # Buffer idx with nuclide index as value + buffer_idx = {nuc: self.nuclide_dict[nuc] for nuc in buffer} + array = matrix.toarray() + redox_change = np.array([]) + + # calculate the redox array + for i in range(len(self)): + # Net redox impact of reaction: multiply the i-th column of the + # depletion matrix by the oxidation states + redox_change = np.append(redox_change, sum(array[:, i]*os)) + + # Subtract redox vector to the buffer nuclides in the matrix scaling by + # their respective oxidation states + for nuc, idx in buffer_idx.items(): + array[idx] -= redox_change * buffer[nuc] / os[idx] + + return sp.csc_matrix(array) + def form_rr_term(self, tr_rates, current_timestep, mats): """Function to form the transfer rate term matrices. diff --git a/openmc/deplete/pool.py b/openmc/deplete/pool.py index 03b050af38f..aa348c02aa7 100644 --- a/openmc/deplete/pool.py +++ b/openmc/deplete/pool.py @@ -109,6 +109,13 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, matrices = [matrix - transfer for (matrix, transfer) in zip(matrices, transfers)] + if transfer_rates.redox: + for mat_idx, mat_id in enumerate(transfer_rates.local_mats): + if mat_id in transfer_rates.redox: + matrices[mat_idx] = chain.add_redox_term(matrices[mat_idx], + transfer_rates.redox[mat_id][0], + transfer_rates.redox[mat_id][1]) + if current_timestep in transfer_rates.index_transfer: # Gather all on comm.rank 0 matrices = comm.gather(matrices) @@ -125,6 +132,12 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, transfer_matrix = chain.form_rr_term(transfer_rates, current_timestep, mat_pair) + + # check if destination material has a redox control + if mat_pair[0] in transfer_rates.redox: + transfer_matrix = chain.add_redox_term(transfer_matrix, + transfer_rates.redox[mat_pair[0]][0], + transfer_rates.redox[mat_pair[0]][1]) transfer_pair[mat_pair] = transfer_matrix # Combine all matrices together in a single matrix of matrices diff --git a/openmc/deplete/transfer_rates.py b/openmc/deplete/transfer_rates.py index 4f2b9aba5f5..ea9fc9185ec 100644 --- a/openmc/deplete/transfer_rates.py +++ b/openmc/deplete/transfer_rates.py @@ -49,9 +49,10 @@ def __init__(self, operator, materials, number_of_timesteps): self.local_mats = operator.local_mats self.number_of_timesteps = number_of_timesteps - # initialize transfer rates container dict + #initialize transfer rates container dict self.external_rates = {mat: defaultdict(list) for mat in self.burnable_mats} self.external_timesteps = [] + self.redox = {} def _get_material_id(self, val): """Helper method for getting material id from Material obj or name. @@ -300,6 +301,46 @@ def set_transfer_rate(self, material, components, transfer_rate, self.external_timesteps = np.unique(np.concatenate( [self.external_timesteps, timesteps])) + def set_redox(self, material, buffer, oxidation_states, timesteps=None): + """Add redox control to depletable material. + + Parameters + ---------- + material : openmc.Material or str or int + Depletable material + buffer : dict + Dictionary of buffer nuclides used to maintain redox balance. + Keys are nuclide names (strings) and values are their respective + fractions (float) that collectively sum to 1. + oxidation_states : dict + User-defined oxidation states for elements. + Keys are element symbols (e.g., 'H', 'He'), and values are their + corresponding oxidation states as integers (e.g., +1, 0). + timesteps : list of int, optional + List of timestep indices where to set external source rates. + Defaults to None, which means the external source rate is set for + all timesteps. + + """ + material_id = self._get_material_id(material) + if timesteps is not None: + for timestep in timesteps: + check_value('timestep', timestep, range(self.number_of_timesteps)) + timesteps = np.array(timesteps) + else: + timesteps = np.arange(self.number_of_timesteps) + #Check nuclides in buffer exist + for nuc in buffer: + if nuc not in self.chain_nuclides: + raise ValueError(f'{nuc} is not a valid nuclide.') + # Checks element in oxidation states exist + for elm in oxidation_states: + if elm not in ELEMENT_SYMBOL.values(): + raise ValueError(f'{elm} is not a valid element.') + + self.redox[material_id] = (buffer, oxidation_states) + self.external_timesteps = np.unique(np.concatenate( + [self.external_timesteps, timesteps])) class ExternalSourceRates(ExternalRates): """Class for defining external source rates. diff --git a/tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_ext_source.h5 b/tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_ext_source.h5 index 3ed38f3d6ce3973fd63e7967b1246c8012ebf063..2f9951a42aaa0613c0b4a20351ae2c0df985b0bc 100644 GIT binary patch delta 569 zcmcbxnCZe|rVS2#j53oQ`{L`jC9l$uo6Bd%00mo5bLh;i`vB*6-8Fg^a%@Z|^%tzWFI=h?g8OIAU8Q+5L;d0Wiy{t}+CheB5fg=BG-o^RH!sw#G-cKV@?+ lvt52=hS{y(9<~z?PT;!2+W+$S!`t>#eokIBQHcfQX8^fN!^Qvr delta 569 zcmcbxnCZe|rVS2#jIxs*`{L_&C$G|wo6Bd%00p~FbLh;i`vB+9zH9U>A<9GwvP;sMMu~xzzyKY&yn6*$7~OC!0S~X{%IR5 zfIH~(R?pA-SLnmlFa6CcVRLUEoFD6wD}C|0DV%?T@qMWGIZr5Ghk>DNxAxX|6D;8h z!uRO(8|Rq7`3BtGg7OzW*zbHj`BcA>Pr~V`_9Ai?hL(RfIj?-IXu)%AiL?06R;z-| zJDfKbB~3rKWrnlezFmg858|97_9uO*KcDM-K)2w9eprA@^!-^6e!oB9@-EZjXwU$|5)1ozM82TJp1hWf+#f#MFA+A<9Gwl@uqMMu~xzztx@&yn6*$7~OC0RQR_|FjJj zz#SyJ&GYmA75Z@X7k=|f*xcI(=U2MqN?*Kg3g_=&d>`t4&J)VlVPL4(t-bZ#1WULA z?L9jE#yKW%zAtyTp!|go_8VVMKGm;eYMy)4p7roO@nbax&i1Xxp0O+IIp?XaT-X2G z-&si`evf9Gw)3wSZ=X!ra>~idzshk<{vD^2lNBe#*FWgkJSnRZ5!7DGvL?Kcc@MWp zd+k@Q#?^?R{&hwBn8;rdxct>wS&HXgE{F3E1^K6nY*B*qt=xn)Z+)<~W1IU>{{7^C zw(F$#N!OoJx4W+YZrzO+z@S!>jLbeY@4xMJnS~F(zr1SurDjo);Q>Xs0cWOPU+YrE z4iD;j&zaoGrw~D1U8cVxayP=ly?f^ssVzJN*YBUTQRZ1TBB)g*n)PzcgQ4=kptjt2 mSHW(N1zbbsbFtQv79%*n?DLVpyHy|US4U4?HBpHL6x0C2(a??n delta 688 zcmcbxnCZe|rVS2#jIxs*`{L{8C9l$uo6Bd%00nDKbLh;i`vB+fxNGz*A<9Gw&x6vMMu~xzzyKc&yn6*$7~OCz|~bB{%IR5 zfIH~sR?pA-SLnmlXaDAvu(`Jn&Np<)mA-i06wW`*_&(J8oF|m8!@$tGTYKxf36^jL zA$xTCjdM)kd~fb5d`FEVoPga}|Uw^G*^Q5dwL{Jwl%bM^)<~`gZ z$F*O%8doEN`oR_LVY`dXJN zc6d-1dCufcK7|PCiZcBbk-HHV?%q4ENNwRExPHH^jWW-&5kW03(X5wi9t@QS2DQt^ ny9#!DEZ`c-pNqAYv>3tp)t`?9-mUs*?+`Y5)kGy0P*4K^K)BOB diff --git a/tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_redox.h5 b/tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_redox.h5 new file mode 100644 index 0000000000000000000000000000000000000000..c7f002ba14a29133887bb7b7d06cc0a6378a71ec GIT binary patch literal 37328 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 literal 0 HcmV?d00001 diff --git a/tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_removal.h5 b/tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_removal.h5 index 3d48105d7b444cc16f5b6438f97f897d2c1657fd..c3121a9535097cbd3bc2457b664ff774ca654e2c 100644 GIT binary patch delta 670 zcmcbxnCZe|rVS2#j53oQ`{L_&B(Ktto6Bd%00o;)bLh;i`vB)Jx@+_-A<9Gws#GVMMu~xzzyKa&yn6*$7~OC0Qc$-|FjJj zz#a5utLNwaEA-*&zy9Wxu(`Jn&i8Q1mA-i06wcqn_&(J8oF|m8!@y9!TYKxf36^jL zv3qp-jdM)k{BZ7WLHP?G>`y$Ke5zl`luz@NJT(XoTCHz?k32oIA3}(w0l2|Vr!&ni1-^&# z&9=`ztNUUC+-Db=>$Yl5mx9ZGh{~C^=j3uYKVa88i=i7=GEVh&%zk>SYbyTR{^~xxdBVvnwgz4i2di9_;0BcayLV^9 zOg4B>Uv^^fW6N3ycTl?2aigtQRpIItk4CJtQ920cPht(=UOL|j&gV63vJXBP0_6)c fFhrcY#JKz_BBA<9GwjT_RMMu~xzztx@&yn6*$7~OCz^7Fo{%IR5 zfIH~;R?pA-SLnmlxBupqu(`Jn&JTCVmA-i06wZIk_&(J8oF|m8!@#g;xAxX|6D;8h zTK4Gl8|Rq7`L^8Mg7OzW*!Mr0e5zl`bi>_A_H2jmi65&maE=b-yPF`V;(YJLj7yfU zteis+&*nYhrsDkO#oH%Sww!V@@UL=QlYhtQ{A9%m@%8btXQeu%1mFhioX#}66!;#_ zSKmJStnP~maG%{{uG^|LT?#ILEh=Z)o|DVre3M=8ELL)B!}+yOo-wv;x3-(OB2r_L z*I(N|ExJ2Bk80S}&0fJ`EaOyf$LFWFx~Ag4?eFf>nOD>jer#C_;SOq-I&QS}sw!N)=+TIkHcAKK`~ubh?xpjs;Cwa1Ci~!%AyB?B f1B1r7ON`5}B7!=0^2CYaO#D)lS4~u60R=1or!~mt diff --git a/tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_removal_and_redox.h5 b/tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_removal_and_redox.h5 new file mode 100644 index 0000000000000000000000000000000000000000..80ce771fceae774cd606fef7888c325128b7462f GIT binary patch literal 37328 zcmeHPeQ;D)6~Avcp$h~EErk?n`cj#8O;Xu>&?bc4Y}gPJ93>E_tL?O#k1eE=FOw_= zMa)R6adafK&NSlSq|TtBEp7VcfOPCu`jOfW6Dn3lfr(>Bqr=oy+YzgP@4b7@d)b?P zyX+%NxAg2Ed*8YD+;h+Uz4JTo-nZ}VcWSF^=FM3;hp8WjgJrO6?GV2%!e@LBuR$5x z18^WgIRNE|jUPygMYSyl>a)SVZ08lDP`_#8+O>??7~01Yn%UqiepCh(^@_Io325K+ zb+scBz##z9d+LwQ$EI zi8BM}phP(TAYZV|VxZ4vEBzhpzSg#2@Aj@BsI%)7#f6H1`YpjOML`)+?hf{Z+Io9H zQBpKi9poGOe-8MT13cn%WLs7ISG6lh`a}{o-J)9W6mM=T2L{2-DAE zJo8EH8n#I3x2>}+)UK>j7Rr?{gqM}hWu+H|JlnjZsqXlZKUSLMB^TsZ%1Z*^B*sL* zEH9lDc#ipErt%WQZ+j%K5d({3D@Apis;_YY`hEa55-ya%S%r!

%x-v&8GFh?j9+>{@M|+SuFKE_a05 zn}Xdku36O?nXOx48LIU4Y7>`~Rjaz*?G83J^V?^Qy}>D#64^1!R}naGl&>_H=_>&O zX8CFe;t0EAewe9zg*+4UjlmCVJj(=!4LjC&hIY(&=KlmAhqJ;(FKMhc%v|*M=Qy zJVQHXJaaDR8pJb1ipqEhnDK1H^I_@FFw#<-iQ`}*jUucxSF zr6PQ$rD&!4le}8qp_FyJnr$vF(SEU4N>xpjwFkQVL9U>dYCrTZpem){gU|Uo!S>cX zet_eO`%D+;jl;Sa^~7~De>#n^GBAvK;&t8&_J$!Zp`MthYC+Gjf*+us3*>s?yn^}_ zEAaOm(H|o(TFbBao`PY=T7E@43Ewv;=aAv=-d7Aj8YnFg5c8%CUX8$WMd+lnqi{=O zdwb)jpyIyP9__swYN}Mt0k*iA~&T)v>AgsHX8UryM4`qT)uy?{?)e%kI|3HhfRHBh=j1+7d)je;yMn z_#>W7ua~OT`1@4e>zvGVmDrnt_LEE=}>^=TH3K1H{4l{B08t8uOpR zgMq{+?Fwq(`v6`Tv%WI^;7(|E3hR6Ey_&6{=kmq37l3AYO?=$UCkWNuRz7PV003kpK5CVh%A&?jWvvp&l=fWmn-H3X4-&heb1G)c;IZ~qTvS_-#D17w>S4!4&8O?r@HJpeDoK8Snbs1)%S1z=Zi0__H-XE zeDlIn7u-dU^t@LSae96jnJqp1T9v1%;nDJQFI{wZeygG>{LtI(f4E+~%U>z${cq@O zTlV};yZ$dviyn_$C~yty{#^I-x_`YmvO?E??Z5MKR-gH$F7K&sUv~U=D|Pv-^!Lr5 zJyxg7Uq4dt{JVqYx?KK3;qH>QQeAGJ``v{%jYlerCZCz8IPzoN4-K!J_q|#f4r=AH zidQP_N6##Ib%)F6-!T7lAa}X%<$qh{+jR6t^TukH``#}I-!t&1M}6+~U#)tg z^VFKxk4bgAl=JI&`ah=*|Jz+X`oX&EADO}PyZO%T%Ql{!r_X2a{&i=5zxJ#yKRHo- zXz7_j9nZ0X-Z=~M7whf23O0OjVApuxSSzs7TCaKk(6pSkGX zdwVFa>T#!MZ`p(Y``M8y&zExZpKUnCJO|(TitpvWyzTx?z9*~ni#O~2Z`t0I_vzbj#Uk?pXmO0pZ!AM+Z&DX)-3JnoL}@Sz5h+`<(2Gn7VC29yDck1HJ{VvhVrb6 r6H6*|x&K7To8Nn5l`e-o3%>A<9Gwl55hMMu~xzzq<{&yn6*$7~OCfZFO0|FjJj zz#ZhZ&GYmA75Z@X8-Mdk*xcI(=TCFVmA-i06wd$6_&(J8oF|m8&A?E&TYKxf36^jL z!FzQ2jdM)kd_V4PLHP?G?4M6Q*{^JxAW&`3eE6RDu^I#C@Ib!1334jVH(t!RWckX< zIrQ*s-V<&r&VOFKeKKXsDJLucD#tbXcbra6R-7O&m>}J^Zs&pTPPN${3vX`Man_w2 zI6=SOJ>FEg%MYsX17rIU?4rhUff8Ci)C;mT2gum`8`#JKgT5$F2L_>AAg&&0T zw`t!p_?Br5=if<`KCk~K5X#qKU@%&myZ1l^BK)o8?kKz}H-u|=wB~g4EP;>qYmQD{ KHBpHL6afHS{MF3> delta 709 zcmcbxnCZe|rVS2#jIxs*`{L`DC9l$uo6Bd%00p~FbLh;i`vB)}x@+_-cf;rNC>vkUa?o^xYvGC@09cPot zffMxWo&RKSGXBL6x9x6RlTzGVMEEP$?Rw6*aRS`C^9xHBepVNStKam}H}24d6>z?w zn@N+`a&JF5Ax5ReE~DLlJJkhVxw~7KgLI!@u~v!mHixfRxP;thoYgn+rkgR z`75+<8GOq$hV%C&N}tz%6A0z&Ffc?c&E0#T0ulbMa(5J7l^en}JY93Td6vLOdxH~` LS4~u60Yv}+O6u9M diff --git a/tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_transfer_and_redox.h5 b/tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_transfer_and_redox.h5 new file mode 100644 index 0000000000000000000000000000000000000000..6847fd7dfe6960da89a0e4fdf0be9f35eb56d7f4 GIT binary patch literal 37328 zcmeHPeQ;FO6~AvcfyFEkD9BbUJ_?mZLRm8)#x@Bn7nTz}OARLSi=`%~yj} zWTv!@b_OCXL#Apc>GUJoVpFTqG%0LliV+ zn|)+4#j}6xedpeD&pr2d&+okZ-oCewNjs(w~jFzL;F}lGi#o}kFuenP|;RDLG7Eq zZgd3l*X0(fbHsSTI1BodJM(D}uQ(pYwUgy*153=ooJw>zglcNbt14N#L!pt@!X1+& z&J3V~65;%Ve8JMmK%Y%g`a9V6@^GkbXLShF*{2l6X^MdQrJ-s?K^al53Dwqz>uNz! zQZ!T@NkLIb&yB6eImuWr@BsZ3d4cncOy%}aZvbS+bY7ng!})!& z$Njik&Ca|1Y22e`aE#{xW|{{+F8^uV@qvJt2ard8I6t75$P?rv)>|!k9Kr6D!v4%@#7(|0nS z`6M=x%~1L^RE6s+lwHa+IR}RDv#d0hbydi-B@b>ZIN9}=9J9RSf&5B&NdTP05fL!U zOML>*F<*>TUP3;Jc_mRkp0sJ3iyxp~ z5y*|7=JhVnYq=#_F9LG^t-M|aJvJv=&kb_Eu7HZ;v&8eNh?j9+Y`V5jEv~DokSpse zwuNeB+_S1PGTXPpHdN{B*CsA0yH<6-TN5fS;kVC<>q0{;#j|6UuLfb=C|_wX(^nh> z%<@$W#1VGK{4iGe3V9~x8-pL#c$Nwd8+NSm4DFcltmrd*AmSND2$iuAFyk46IKsZk z;+c59HuzzUXDQ&YVaFQJ(2g0;A~GL{c!m)|Wh?~DcvdDy_evJe#Qm1 zMSO&T#k@55VeNc|ej0YH@eJ*l@hpSnc6S*N~x-;vi3k%QHU$3rP>eu3#v*<_~3KCZm?Z8 zmmlD`;&rA6^m<`mjC$fenLnMzST-0&J@Gv62YaoMmrzg4Q~99hSi%ob&jWIyu&$tf z-3t6YNA$$-qdDF9+*P}m0AUAq>{U_}k&yONri9WB3xS2iQI0*RS`cqy(#Hbtz0kgc)BJdP> zjBz!RtwidO4`N~a`mUaK3>6Z-7n{N>aQ}z;C{F^X@jY*|cDwQ>tJ-Gveo3nG zET7tLv)eQi4kl}s?Y7jG_1clvtf?rXIxM8{u|kv&g5SgODAJmwRL#9%@BfN;pd1eu z)Jq&6eqm&{P3YguomL$iijQg4}%^ABb#lvS36~9B)C1^OE#G~POa0DezH$9#)9uWtW6Q92e z=5Me5I-eKDCd|DtiL;GwdbD_8ydfSaUjrWP-!$-0&5ct$`1*_CMV;1Jb9^4JhZef2fzE@KQdY(XRdqHTH*T&Wx@2%?oSsLO0V44$j zJbf?kfc=n%@yrbFi`Kw?SYm#|eaw)6jP6JhAwUQa0)zk|KnM^5ga9Ex2oM5^f4$3%LZnXoc%Z?4PKa-FP7i<6?g-#p_Cx>H0Fkg(7uAfDj-A2mwNX5Fi8y0YZQf zAOr{jLf{$@FuR|SK1!Pr_+1(G@w(rvd+TG-EvSzr-5-d?ha^IP5Fi8y0YZQfAOr{j zLSQ^0VD=zTlsxU8^W+pC+UtJBk@4iN zL0vv_e%afbU*4zxYvPejKS+7_&>X$JYdbic%CD|?GY{5&uzt|(dwg(`^!=A|ePx^4R$lzWRqyuitlk#cf5!Wc=lGVQ99i$b zq$)i7`CWGXU!HFJ#o(1io>twT87~yPf3$sxuK&h=C%e{MIHJo9c@?uyzP? zo-e*upvzmk7Crxu=9Rjpykxczpo11d+hPaJ?rNOK3WvHuj#LCf$R+r zei+}f94lM242tgqwOAD!#`(3{tB>8_avhpg|0*?XVqjC{Cu&)4$X-Z-2Q`0UK^ z37hx*wc5C&_&RaaSvc~m+!B*)R6zg8sFK*x6a;uius)TPcP`YYoX8n!k;T#-MPN0KR@|I z%A3p=E=@bM=dCl|3tRiVoBHSK{g2*v<&I}-Q*>Pa^K|#04sL1F*LU9OS(n}mF4pxA zyu0}Ji3?xU Date: Thu, 13 Nov 2025 21:35:33 +0100 Subject: [PATCH 151/159] Addition of a collision tracking feature (#3417) Co-authored-by: Paul Romano --- CMakeLists.txt | 1 + docs/source/io_formats/collision_track.rst | 46 ++++ docs/source/io_formats/index.rst | 1 + docs/source/io_formats/settings.rst | 79 ++++++ docs/source/pythonapi/base.rst | 3 + docs/source/usersguide/settings.rst | 56 ++++ include/openmc/bank.h | 2 + include/openmc/bank_io.h | 103 +++++++ include/openmc/collision_track.h | 23 ++ include/openmc/constants.h | 1 + include/openmc/mcpl_interface.h | 15 ++ include/openmc/message_passing.h | 1 + include/openmc/particle_data.h | 19 ++ include/openmc/settings.h | 20 ++ include/openmc/simulation.h | 1 + include/openmc/urr.h | 8 +- openmc/settings.py | 246 ++++++++++++++--- openmc/source.py | 175 ++++++++++-- src/bank.cpp | 3 + src/collision_track.cpp | 238 ++++++++++++++++ src/finalize.cpp | 5 + src/initialize.cpp | 31 +++ src/mcpl_interface.cpp | 155 +++++++++++ src/message_passing.cpp | 1 + src/particle.cpp | 6 + src/physics.cpp | 5 +- src/settings.cpp | 76 +++++- src/simulation.cpp | 13 +- src/state_point.cpp | 90 +------ .../collision_track/__init__.py | 0 .../case_1_Reactions/inputs_true.dat | 58 ++++ .../case_1_Reactions/results_true.dat | 2 + .../case_2_Cell_ID/inputs_true.dat | 58 ++++ .../case_2_Cell_ID/results_true.dat | 2 + .../case_3_Material_ID/inputs_true.dat | 58 ++++ .../case_3_Material_ID/results_true.dat | 2 + .../case_4_Nuclide_ID/inputs_true.dat | 58 ++++ .../case_4_Nuclide_ID/results_true.dat | 2 + .../case_5_Universe_ID/inputs_true.dat | 59 ++++ .../case_5_Universe_ID/results_true.dat | 2 + .../inputs_true.dat | 58 ++++ .../results_true.dat | 2 + .../inputs_true.dat | 63 +++++ .../results_true.dat | 2 + .../case_8_2threads/inputs_true.dat | 57 ++++ .../case_8_2threads/results_true.dat | 2 + .../regression_tests/collision_track/test.py | 255 ++++++++++++++++++ tests/testing_harness.py | 126 ++++++++- tests/unit_tests/test_collision_track.py | 127 +++++++++ 49 files changed, 2268 insertions(+), 148 deletions(-) create mode 100644 docs/source/io_formats/collision_track.rst create mode 100644 include/openmc/bank_io.h create mode 100644 include/openmc/collision_track.h create mode 100644 src/collision_track.cpp create mode 100644 tests/regression_tests/collision_track/__init__.py create mode 100644 tests/regression_tests/collision_track/case_1_Reactions/inputs_true.dat create mode 100644 tests/regression_tests/collision_track/case_1_Reactions/results_true.dat create mode 100644 tests/regression_tests/collision_track/case_2_Cell_ID/inputs_true.dat create mode 100644 tests/regression_tests/collision_track/case_2_Cell_ID/results_true.dat create mode 100644 tests/regression_tests/collision_track/case_3_Material_ID/inputs_true.dat create mode 100644 tests/regression_tests/collision_track/case_3_Material_ID/results_true.dat create mode 100644 tests/regression_tests/collision_track/case_4_Nuclide_ID/inputs_true.dat create mode 100644 tests/regression_tests/collision_track/case_4_Nuclide_ID/results_true.dat create mode 100644 tests/regression_tests/collision_track/case_5_Universe_ID/inputs_true.dat create mode 100644 tests/regression_tests/collision_track/case_5_Universe_ID/results_true.dat create mode 100644 tests/regression_tests/collision_track/case_6_deposited_energy_threshold/inputs_true.dat create mode 100644 tests/regression_tests/collision_track/case_6_deposited_energy_threshold/results_true.dat create mode 100644 tests/regression_tests/collision_track/case_7_all_parameters_used_together/inputs_true.dat create mode 100644 tests/regression_tests/collision_track/case_7_all_parameters_used_together/results_true.dat create mode 100644 tests/regression_tests/collision_track/case_8_2threads/inputs_true.dat create mode 100644 tests/regression_tests/collision_track/case_8_2threads/results_true.dat create mode 100644 tests/regression_tests/collision_track/test.py create mode 100644 tests/unit_tests/test_collision_track.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 474451c8aeb..87b8789d101 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -338,6 +338,7 @@ list(APPEND libopenmc_SOURCES src/cell.cpp src/chain.cpp src/cmfd_solver.cpp + src/collision_track.cpp src/cross_sections.cpp src/dagmc.cpp src/distribution.cpp diff --git a/docs/source/io_formats/collision_track.rst b/docs/source/io_formats/collision_track.rst new file mode 100644 index 00000000000..a3645975444 --- /dev/null +++ b/docs/source/io_formats/collision_track.rst @@ -0,0 +1,46 @@ +.. _io_collision_track: + +=========================== +Collision Track File Format +=========================== + +When collision tracking is enabled with ``mcpl=false`` (the default), OpenMC +writes binary data to an HDF5 file named ``collision_track.h5``. The same data +may also be written after each batch when multiple files are requested +(``collision_track.N.h5``) or when the run is performed in parallel. The file +contains the information needed to reconstruct each recorded collision. + +The current revision of the collision track file format is 1.0. + +**/** + +:Attributes: + - **filetype** (*char[]*) -- String indicating the type of file. + For collision-track files the value is ``"collision_track"``. + +:Datasets: + + - **collision_track_bank** (Compound type) -- Collision information + for each stored event. Each entry in the dataset corresponds to one + collision and contains the following fields: + + - ``r`` (*double[3]*) -- Position of the collision in [cm]. + - ``u`` (*double[3]*) -- Direction unit vector immediately after the collision. + - ``E`` (*double*) -- Incident particle energy before the collision in [eV]. + - ``dE`` (*double*) -- Energy loss over the collision (:math:`E_\text{before} - E_\text{after}`) in [eV]. + - ``time`` (*double*) -- Time of the collision in [s]. + - ``wgt`` (*double*) -- Particle weight at the collision. + - ``event_mt`` (*int*) -- ENDF MT number identifying the reaction. + - ``delayed_group`` (*int*) -- Delayed neutron group index (non-zero for delayed events). + - ``cell_id`` (*int*) -- ID of the cell in which the collision occurred. + - ``nuclide_id`` (*int*) -- ZA identifier of the nuclide (ZZZAAAM format). + - ``material_id`` (*int*) -- ID of the material containing the collision site. + - ``universe_id`` (*int*) -- ID of the universe containing the collision site. + - ``n_collision`` (*int*) -- Collision counter for the particle history. + - ``particle`` (*int*) -- Particle type (0=neutron, 1=photon, 2=electron, 3=positron). + - ``parent_id`` (*int64*) -- Unique ID of the parent particle. + - ``progeny_id`` (*int64*) -- Progeny ID of the particle. + +In an MPI run, OpenMC writes the combined dataset by gathering collision-track +entries from all ranks before flushing them to disk, so the final file appears +as though it were produced serially. diff --git a/docs/source/io_formats/index.rst b/docs/source/io_formats/index.rst index 4bbaa961a68..5b4efea669e 100644 --- a/docs/source/io_formats/index.rst +++ b/docs/source/io_formats/index.rst @@ -44,6 +44,7 @@ Output Files statepoint source + collision_track summary properties depletion_results diff --git a/docs/source/io_formats/settings.rst b/docs/source/io_formats/settings.rst index 720846c8519..b7874fcf2f8 100644 --- a/docs/source/io_formats/settings.rst +++ b/docs/source/io_formats/settings.rst @@ -20,6 +20,85 @@ source neutrons. *Default*: None +----------------------------- +```` Element +----------------------------- + +The ```` element indicates to track information about particle +collisions based on a set of criteria and store these events in a file named +``collision_track.h5``. This file records details such as the position of the +interaction, direction of the incoming particle, incident energy and deposited +energy, weight, time of the interaction, and the delayed neutron group (0 for +prompt neutrons). Additional information such as the cell ID, material ID, +universe ID, nuclide ZAID, particle type, and event MT number are also stored. +Users can specify one or more criterion to filter collisions. If no criteria are +specified, it defaults to tracking all collisions across the model. + +.. warning:: + Storing all collisions can be very memory intensive. For more targeted + tracking, users can employ a variety of parameters such as ``cell_ids``, + ``reactions``, ``universe_ids``, ``material_ids``, ``nuclides``, and + ``deposited_E_threshold`` to refine the selection of particle interactions + to be banked. + +This element can contain one or more of the following attributes or +sub-elements: + + :max_collisions: + An integer indicating the maximum number of collisions to be banked per file. + + *Default*: 1000 + + :max_collision_track_files: + An integer indicating the number of collision_track files to be used. + + *Default*: 1 + + :mcpl: + An optional boolean to enable MCPL_-format instead of the native HDF5-based + format. If activated, the output file name and type is changed to + ``collision_track.mcpl``. + + *Default*: false + + .. _MCPL: https://mctools.github.io/mcpl/mcpl.pdf + + :cell_ids: + A list of integers representing cell IDs to define specific cells in which + collisions are to be banked. + + *Default*: None + + :universe_ids: + A list of integers representing the universe IDs to define specific + universes in which collisions are to be banked. + + *Default*: None + + :material_ids: + A list of integers representing the material IDs to define specific + materials in which collisions are to be banked. + + *Default*: None + + :nuclides: + A list of strings representing the nuclide, to define specific + define specific target nuclide collisions to be banked. + + *Default*: None + + :reactions: + A list of integers representing the ENDF-6 format MT numbers or strings + (e.g. (n,fission)) to define specific reaction types to be banked. + + *Default*: None + + :deposited_E_threshold: + A float defining the minimum deposited energy per collision (in eV) to + trigger banking. + + *Default*: 0.0 + ---------------------------------- ```` Element ---------------------------------- diff --git a/docs/source/pythonapi/base.rst b/docs/source/pythonapi/base.rst index 2a9d0876cd2..ce2f6f0f857 100644 --- a/docs/source/pythonapi/base.rst +++ b/docs/source/pythonapi/base.rst @@ -216,6 +216,9 @@ Post-processing :nosignatures: :template: myfunction.rst + openmc.read_collision_track_file + openmc.read_collision_track_hdf5 + openmc.read_collision_track_mcpl openmc.voxel_to_vtk The following classes and functions are used for functional expansion reconstruction. diff --git a/docs/source/usersguide/settings.rst b/docs/source/usersguide/settings.rst index 1b2d4bc1a5e..f973f146552 100644 --- a/docs/source/usersguide/settings.rst +++ b/docs/source/usersguide/settings.rst @@ -756,6 +756,62 @@ instance, whereas the :meth:`openmc.Track.filter` method returns a new track_files = [f"tracks_p{rank}.h5" for rank in range(32)] openmc.Tracks.combine(track_files, "tracks.h5") +Collision Track File +--------------------- + +OpenMC can generate a collision track file that contains detailed collision +information (position, direction, energy, deposited energy, time, weight, cell +ID, material ID, universe ID, nuclide ZAID, particle type, particle delayed +group and particle ID) for each particle collision depending on user-defined +parameters. To invoke this feature, set the +:attr:`~openmc.Settings.collision_track` attribute as shown in this example:: + + settings.collision_track = { + "max_collisions": 300, + "reactions": ["(n,fission)", "(n,2n)"], + "material_ids": [1,2], + "nuclides": ["U238", "O16"], + "cell_ids": [5, 12] + } + +In this example, collision track information is written to the +collision_track.h5 file at the end of the simulation. The file contains +300 recorded collisions that occurred in materials with IDs 1 or 2, involving +fission or (n,2n) reactions on the nuclides U-238 or O-16, within cells +with IDs 5 and 12. +The file can be read using :func:`openmc.read_collision_track_file`. +The example below shows how to extract the data from the collision_track +feature and displays the fields stored in the file: + +>>> data = openmc.read_collision_track_file('collision_track.h5') +>>> data.dtype + dtype([('r', [('x', ' source_bank; extern SharedArray surf_source_bank; +extern SharedArray collision_track_bank; + extern SharedArray fission_bank; extern vector> ifp_source_delayed_group_bank; diff --git a/include/openmc/bank_io.h b/include/openmc/bank_io.h new file mode 100644 index 00000000000..90ffd820fad --- /dev/null +++ b/include/openmc/bank_io.h @@ -0,0 +1,103 @@ +#ifndef OPENMC_BANK_IO_H +#define OPENMC_BANK_IO_H + +#include "hdf5.h" + +#include "openmc/message_passing.h" +#include "openmc/span.h" +#include "openmc/vector.h" + +#include + +#ifdef OPENMC_MPI +#include +#endif + +namespace openmc { + +template +void write_bank_dataset(const char* dataset_name, hid_t group_id, + span bank, const vector& bank_index, hid_t banktype +#ifdef OPENMC_MPI + , + MPI_Datatype mpi_dtype +#endif +) +{ + int64_t dims_size = bank_index.back(); + int64_t count_size = bank_index[mpi::rank + 1] - bank_index[mpi::rank]; + +#ifdef PHDF5 + hsize_t dims[] {static_cast(dims_size)}; + hid_t dspace = H5Screate_simple(1, dims, nullptr); + hid_t dset = H5Dcreate(group_id, dataset_name, banktype, dspace, H5P_DEFAULT, + H5P_DEFAULT, H5P_DEFAULT); + + hsize_t count[] {static_cast(count_size)}; + hid_t memspace = H5Screate_simple(1, count, nullptr); + + hsize_t start[] {static_cast(bank_index[mpi::rank])}; + H5Sselect_hyperslab(dspace, H5S_SELECT_SET, start, nullptr, count, nullptr); + + hid_t plist = H5Pcreate(H5P_DATASET_XFER); + H5Pset_dxpl_mpio(plist, H5FD_MPIO_COLLECTIVE); + + H5Dwrite(dset, banktype, memspace, dspace, plist, bank.data()); + + H5Sclose(dspace); + H5Sclose(memspace); + H5Dclose(dset); + H5Pclose(plist); +#else + if (mpi::master) { + hsize_t dims[] {static_cast(dims_size)}; + hid_t dspace = H5Screate_simple(1, dims, nullptr); + hid_t dset = H5Dcreate(group_id, dataset_name, banktype, dspace, + H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); + +#ifdef OPENMC_MPI + vector temp_bank {bank.begin(), bank.end()}; +#endif + + for (int i = 0; i < mpi::n_procs; ++i) { + hsize_t count[] {static_cast(bank_index[i + 1] - bank_index[i])}; + hid_t memspace = H5Screate_simple(1, count, nullptr); + +#ifdef OPENMC_MPI + if (i > 0) { + MPI_Recv(bank.data(), count[0], mpi_dtype, i, i, mpi::intracomm, + MPI_STATUS_IGNORE); + } +#endif + + hid_t dspace_rank = H5Dget_space(dset); + hsize_t start[] {static_cast(bank_index[i])}; + H5Sselect_hyperslab( + dspace_rank, H5S_SELECT_SET, start, nullptr, count, nullptr); + + H5Dwrite(dset, banktype, memspace, dspace_rank, H5P_DEFAULT, bank.data()); + + H5Sclose(memspace); + H5Sclose(dspace_rank); + } + + H5Dclose(dset); + +#ifdef OPENMC_MPI + std::copy(temp_bank.begin(), temp_bank.end(), bank.begin()); +#endif + } +#ifdef OPENMC_MPI + else { + if (!bank.empty()) { + MPI_Send( + bank.data(), bank.size(), mpi_dtype, 0, mpi::rank, mpi::intracomm); + } + } +#endif +#endif +} + +} // namespace openmc + +#endif // OPENMC_BANK_IO_H diff --git a/include/openmc/collision_track.h b/include/openmc/collision_track.h new file mode 100644 index 00000000000..208b8f6e1f7 --- /dev/null +++ b/include/openmc/collision_track.h @@ -0,0 +1,23 @@ +#ifndef OPENMC_COLLISION_TRACK_H +#define OPENMC_COLLISION_TRACK_H + +#include + +namespace openmc { + +class Particle; + +//! Reserve space in the collision track bank according to user settings. +void collision_track_reserve_bank(); + +//! Write collision track data to disk when the bank is full or the batch ends. +void collision_track_flush_bank(); + +//! Record the current particle as a collision-track entry when applicable. +//! +//! \param particle Particle whose collision should be recorded if eligible +void collision_track_record(Particle& particle); + +} // namespace openmc + +#endif // OPENMC_COLLISION_TRACK_H diff --git a/include/openmc/constants.h b/include/openmc/constants.h index b661934812a..bba260c3a4b 100644 --- a/include/openmc/constants.h +++ b/include/openmc/constants.h @@ -34,6 +34,7 @@ constexpr array VERSION_VOXEL {2, 0}; constexpr array VERSION_MGXS_LIBRARY {1, 0}; constexpr array VERSION_PROPERTIES {1, 1}; constexpr array VERSION_WEIGHT_WINDOWS {1, 0}; +constexpr array VERSION_COLLISION_TRACK {1, 0}; // ============================================================================ // ADJUSTABLE PARAMETERS diff --git a/include/openmc/mcpl_interface.h b/include/openmc/mcpl_interface.h index a76d72e6494..a9cce3e69af 100644 --- a/include/openmc/mcpl_interface.h +++ b/include/openmc/mcpl_interface.h @@ -38,6 +38,21 @@ vector mcpl_source_sites(std::string path); void write_mcpl_source_point(const char* filename, span source_bank, const vector& bank_index); +//! Write an MCPL collision track file +//! +//! This function writes collision track data to an MCPL file. Additional +//! collision-specific metadata (such as energy deposition, material info, etc.) +//! is stored in the file header as blob data. +//! +//! \param[in] filename Path to MCPL file +//! \param[in] collision_track_bank Vector of CollisionTrackSites to write to +//! file for this MPI rank. +//! \param[in] bank_index Pointer to vector of site index ranges over all +//! MPI ranks. +void write_mcpl_collision_track(const char* filename, + span collision_track_bank, + const vector& bank_index); + //! Check if MCPL functionality is available bool is_mcpl_interface_available(); diff --git a/include/openmc/message_passing.h b/include/openmc/message_passing.h index a1641a9069e..ce993776b7b 100644 --- a/include/openmc/message_passing.h +++ b/include/openmc/message_passing.h @@ -18,6 +18,7 @@ extern bool master; #ifdef OPENMC_MPI extern MPI_Datatype source_site; +extern MPI_Datatype collision_track_site; extern MPI_Comm intracomm; #endif diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index 5383487d4fa..fdacfa765b3 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -56,6 +56,25 @@ struct SourceSite { int64_t progeny_id; }; +struct CollisionTrackSite { + Position r; + Direction u; + double E; + double dE; + double time {0.0}; + double wgt {1.0}; + int event_mt {0}; + int delayed_group {0}; + int cell_id {0}; + int nuclide_id; + int material_id {0}; + int universe_id {0}; + int n_collision {0}; + ParticleType particle; + int64_t parent_id; + int64_t progeny_id; +}; + //! State of a particle used for particle track files struct TrackState { Position r; //!< Position in [cm] diff --git a/include/openmc/settings.h b/include/openmc/settings.h index 78bfa088e6f..b369c99fef8 100644 --- a/include/openmc/settings.h +++ b/include/openmc/settings.h @@ -32,6 +32,24 @@ enum class IFPParameter { GenerationTime, }; +struct CollisionTrackConfig { + bool mcpl_write {false}; //!< Write collision tracks using MCPL? + std::unordered_set + cell_ids; //!< Cell ids where collisions will be written + std::unordered_set + mt_numbers; //!< MT Numbers where collisions will be written + std::unordered_set + universe_ids; //!< Universe IDs where collisions will be written + std::unordered_set + material_ids; //!< Material IDs where collisions will be written + std::unordered_set + nuclides; //!< Nuclides where collisions will be written + double deposited_energy_threshold {0.0}; //!< Minimum deposited energy [eV] + int64_t max_collisions { + 1000}; //!< Maximum events recorded per collision track file + int64_t max_files {1}; //!< Maximum number of collision track files +}; + //============================================================================== // Global variable declarations //============================================================================== @@ -41,6 +59,7 @@ namespace settings { // Boolean flags extern bool assume_separate; //!< assume tallies are spatially separate? extern bool check_overlaps; //!< check overlaps in geometry? +extern bool collision_track; //!< flag to use collision track feature? extern bool confidence_intervals; //!< use confidence intervals for results? extern bool create_fission_neutrons; //!< create fission neutrons (fixed source)? @@ -145,6 +164,7 @@ extern std::unordered_set statepoint_batch; //!< Batches when state should be written extern std::unordered_set source_write_surf_id; //!< Surface ids where sources will be written +extern CollisionTrackConfig collision_track_config; extern double source_rejection_fraction; //!< Minimum fraction of source sites //!< that must be accepted extern double free_gas_threshold; //!< Threshold multiplier for free gas diff --git a/include/openmc/simulation.h b/include/openmc/simulation.h index 3e4e24e1d06..9a6cf1b2131 100644 --- a/include/openmc/simulation.h +++ b/include/openmc/simulation.h @@ -22,6 +22,7 @@ constexpr int STATUS_EXIT_ON_TRIGGER {2}; namespace simulation { +extern int ct_current_file; //!< current collision track file index extern "C" int current_batch; //!< current batch extern "C" int current_gen; //!< current fission generation extern "C" bool initialized; //!< has simulation been initialized? diff --git a/include/openmc/urr.h b/include/openmc/urr.h index 3978c7b86a2..1e603715847 100644 --- a/include/openmc/urr.h +++ b/include/openmc/urr.h @@ -27,10 +27,10 @@ class UrrData { double heating; }; - Interpolation interp_; //!< interpolation type - int inelastic_flag_; //!< inelastic competition flag - int absorption_flag_; //!< other absorption flag - bool multiply_smooth_; //!< multiply by smooth cross section? + Interpolation interp_; //!< interpolation type + int inelastic_flag_; //!< inelastic competition flag + int absorption_flag_; //!< other absorption flag + bool multiply_smooth_; //!< multiply by smooth cross section? vector energy_; //!< incident energies auto n_energy() const { return energy_.size(); } diff --git a/openmc/settings.py b/openmc/settings.py index 2f8a2b12485..43c1fe06982 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -6,7 +6,7 @@ from pathlib import Path import lxml.etree as ET - +import warnings import openmc import openmc.checkvalue as cv from openmc.checkvalue import PathLike @@ -47,6 +47,21 @@ class Settings: half-width of the 95% two-sided confidence interval. If False, uncertainties on tally results will be reported as the sample standard deviation. + collision_track : dict + Options for writing collision information. Acceptable keys are: + + :max_collisions: Maximum number of collisions to be banked per file. (int) + :max_collision_track_files: Maximum number of collision_track files. (int) + :mcpl: Output in the form of an MCPL-file. (bool) + :cell_ids: List of cell IDs to define cells in which collisions should be banked. (list of int) + :universe_ids: List of universe IDs to define universes in which collisions should be banked. (list of int) + :material_ids: List of material IDs to define materials in which collisions should be banked. (list of int) + :nuclides: List of nuclides to define nuclides in which collisions should be banked. + (ex: ["I135m", "U233"] ). (list of str) + :reactions: List of reaction to define specific reactions that should be banked + (ex: ["(n,fission)", 2, "(n,2n)"] ). (list of str or int) + :deposited_E_threshold: Number to define the minimum deposited energy during + per collision to trigger banking. (float) create_fission_neutrons : bool Indicate whether fission neutrons should be created or not. cutoff : dict @@ -395,6 +410,9 @@ def __init__(self, **kwargs): # Iterated Fission Probability self._ifp_n_generation = None + # Collision track feature + self._collision_track = {} + # Output options self._statepoint = {} self._sourcepoint = {} @@ -434,7 +452,8 @@ def __init__(self, **kwargs): self._max_particle_events = None self._write_initial_source = None self._weight_windows = WeightWindowsList() - self._weight_window_generators = cv.CheckedList(WeightWindowGenerator, 'weight window generators') + self._weight_window_generators = cv.CheckedList( + WeightWindowGenerator, 'weight window generators') self._weight_windows_on = None self._weight_windows_file = None self._weight_window_checkpoints = {} @@ -475,8 +494,9 @@ def generations_per_batch(self) -> int: @generations_per_batch.setter def generations_per_batch(self, generations_per_batch: int): - cv.check_type('generations per patch', generations_per_batch, Integral) - cv.check_greater_than('generations per batch', generations_per_batch, 0) + cv.check_type('generations per batch', generations_per_batch, Integral) + cv.check_greater_than('generations per batch', + generations_per_batch, 0) self._generations_per_batch = generations_per_batch @property @@ -506,7 +526,8 @@ def rel_max_lost_particles(self) -> float: @rel_max_lost_particles.setter def rel_max_lost_particles(self, rel_max_lost_particles: float): cv.check_type('rel_max_lost_particles', rel_max_lost_particles, Real) - cv.check_greater_than('rel_max_lost_particles', rel_max_lost_particles, 0) + cv.check_greater_than('rel_max_lost_particles', + rel_max_lost_particles, 0) cv.check_less_than('rel_max_lost_particles', rel_max_lost_particles, 1) self._rel_max_lost_particles = rel_max_lost_particles @@ -516,8 +537,10 @@ def max_write_lost_particles(self) -> int: @max_write_lost_particles.setter def max_write_lost_particles(self, max_write_lost_particles: int): - cv.check_type('max_write_lost_particles', max_write_lost_particles, Integral) - cv.check_greater_than('max_write_lost_particles', max_write_lost_particles, 0) + cv.check_type('max_write_lost_particles', + max_write_lost_particles, Integral) + cv.check_greater_than('max_write_lost_particles', + max_write_lost_particles, 0) self._max_write_lost_particles = max_write_lost_particles @property @@ -538,12 +561,12 @@ def keff_trigger(self) -> dict: def keff_trigger(self, keff_trigger: dict): if not isinstance(keff_trigger, dict): msg = f'Unable to set a trigger on keff from "{keff_trigger}" ' \ - 'which is not a Python dictionary' + 'which is not a Python dictionary' raise ValueError(msg) elif 'type' not in keff_trigger: msg = f'Unable to set a trigger on keff from "{keff_trigger}" ' \ - 'which does not have a "type" key' + 'which does not have a "type" key' raise ValueError(msg) elif keff_trigger['type'] not in ['variance', 'std_dev', 'rel_err']: @@ -553,7 +576,7 @@ def keff_trigger(self, keff_trigger: dict): elif 'threshold' not in keff_trigger: msg = f'Unable to set a trigger on keff from "{keff_trigger}" ' \ - 'which does not have a "threshold" key' + 'which does not have a "threshold" key' raise ValueError(msg) elif not isinstance(keff_trigger['threshold'], Real): @@ -570,7 +593,7 @@ def energy_mode(self) -> str: @energy_mode.setter def energy_mode(self, energy_mode: str): cv.check_value('energy mode', energy_mode, - ['continuous-energy', 'multi-group']) + ['continuous-energy', 'multi-group']) self._energy_mode = energy_mode @property @@ -593,7 +616,8 @@ def source(self) -> list[SourceBase]: def source(self, source: SourceBase | Iterable[SourceBase]): if not isinstance(source, MutableSequence): source = [source] - self._source = cv.CheckedList(SourceBase, 'source distributions', source) + self._source = cv.CheckedList( + SourceBase, 'source distributions', source) @property def confidence_intervals(self) -> bool: @@ -610,7 +634,8 @@ def electron_treatment(self) -> str: @electron_treatment.setter def electron_treatment(self, electron_treatment: str): - cv.check_value('electron treatment', electron_treatment, ['led', 'ttb']) + cv.check_value('electron treatment', + electron_treatment, ['led', 'ttb']) self._electron_treatment = electron_treatment @property @@ -704,7 +729,8 @@ def trigger_max_batches(self) -> int: @trigger_max_batches.setter def trigger_max_batches(self, trigger_max_batches: int): cv.check_type('trigger maximum batches', trigger_max_batches, Integral) - cv.check_greater_than('trigger maximum batches', trigger_max_batches, 0) + cv.check_greater_than('trigger maximum batches', + trigger_max_batches, 0) self._trigger_max_batches = trigger_max_batches @property @@ -713,8 +739,10 @@ def trigger_batch_interval(self) -> int: @trigger_batch_interval.setter def trigger_batch_interval(self, trigger_batch_interval: int): - cv.check_type('trigger batch interval', trigger_batch_interval, Integral) - cv.check_greater_than('trigger batch interval', trigger_batch_interval, 0) + cv.check_type('trigger batch interval', + trigger_batch_interval, Integral) + cv.check_greater_than('trigger batch interval', + trigger_batch_interval, 0) self._trigger_batch_interval = trigger_batch_interval @property @@ -798,19 +826,22 @@ def surf_source_write(self) -> dict: @surf_source_write.setter def surf_source_write(self, surf_source_write: dict): - cv.check_type("surface source writing options", surf_source_write, Mapping) + cv.check_type("surface source writing options", + surf_source_write, Mapping) for key, value in surf_source_write.items(): cv.check_value( "surface source writing key", key, - ("surface_ids", "max_particles", "max_source_files", "mcpl", "cell", "cellfrom", "cellto"), + ("surface_ids", "max_particles", "max_source_files", + "mcpl", "cell", "cellfrom", "cellto"), ) if key == "surface_ids": cv.check_type( "surface ids for source banking", value, Iterable, Integral ) for surf_id in value: - cv.check_greater_than("surface id for source banking", surf_id, 0) + cv.check_greater_than( + "surface id for source banking", surf_id, 0) elif key == "mcpl": cv.check_type("write to an MCPL-format file", value, bool) @@ -827,6 +858,79 @@ def surf_source_write(self, surf_source_write: dict): self._surf_source_write = surf_source_write + @property + def collision_track(self) -> dict: + return self._collision_track + + @collision_track.setter + def collision_track(self, collision_track: dict): + cv.check_type('Collision tracking options', collision_track, Mapping) + for key, value in collision_track.items(): + cv.check_value('collision_track key', key, + ('cell_ids', 'reactions', 'universe_ids', 'material_ids', 'nuclides', + 'deposited_E_threshold', 'max_collisions', 'max_collision_track_files', 'mcpl')) + if key == 'cell_ids': + cv.check_type('cell ids for collision tracking data banking', value, + Iterable, Integral) + for cell_id in value: + cv.check_greater_than('cell id for collision tracking data banking', + cell_id, 0) + elif key == 'reactions': + cv.check_type('MT numbers for collision tracking data banking', value, + Iterable) + for reaction in value: + if isinstance(reaction, int): + cv.check_greater_than( + 'MT number for collision tracking data banking', reaction, 0 + ) + elif isinstance(reaction, str): + # check against allowed strings? so far let C++ code handle it + pass + else: + raise TypeError( + f"MT number for collision tracking data banking must be a positive int or string, " + f"got {type(reaction).__name__}") + elif key == 'universe_ids': + cv.check_type('universe ids for collision tracking data banking', value, + Iterable, Integral) + for universe_id in value: + cv.check_greater_than('universe id for collision tracking data banking', + universe_id, 0) + elif key == 'material_ids': + cv.check_type('material ids for collision tracking data banking', value, + Iterable, Integral) + for material_id in value: + cv.check_greater_than('material id for collision tracking data banking', + material_id, 0) + elif key == 'nuclides': + cv.check_type('nuclides for collision tracking data banking', value, + Iterable, str) + for nuclide in value: + # If nuclide name doesn't look valid, give a warning + try: + openmc.data.zam(nuclide) + except ValueError: + warnings.warn(f"Nuclide {nuclide} is not valid") + elif key == 'deposited_E_threshold': + cv.check_type('Deposited Energy Threshold for collision tracking data banking', + value, Real) + cv.check_greater_than('Deposited Energy Threshold for collision tracking data banking', + value, 0) + elif key == 'max_collisions': + cv.check_type('maximum collisions banks per file', + value, Integral) + cv.check_greater_than('maximum collisions banks in collision tracking', + value, 0) + elif key == 'max_collision_track_files': + cv.check_type('maximum collisions banks', + value, Integral) + cv.check_greater_than('maximum number of collision_track files ', + value, 0) + elif key == 'mcpl': + cv.check_type('write to an MCPL-format file', value, bool) + + self._collision_track = collision_track + @property def no_reduce(self) -> bool: return self._no_reduce @@ -943,7 +1047,7 @@ def cutoff(self) -> dict: def cutoff(self, cutoff: dict): if not isinstance(cutoff, Mapping): msg = f'Unable to set cutoff from "{cutoff}" which is not a '\ - 'Python dictionary' + 'Python dictionary' raise ValueError(msg) for key in cutoff: if key == 'weight': @@ -961,7 +1065,7 @@ def cutoff(self, cutoff: dict): cv.check_greater_than('energy cutoff', cutoff[key], 0.0) else: msg = f'Unable to set cutoff to "{key}" which is unsupported ' \ - 'by OpenMC' + 'by OpenMC' self._cutoff = cutoff @@ -1130,12 +1234,14 @@ def weight_window_checkpoints(self) -> dict: @weight_window_checkpoints.setter def weight_window_checkpoints(self, weight_window_checkpoints: dict): for key in weight_window_checkpoints.keys(): - cv.check_value('weight_window_checkpoints', key, ('collision', 'surface')) + cv.check_value('weight_window_checkpoints', + key, ('collision', 'surface')) self._weight_window_checkpoints = weight_window_checkpoints @property def max_splits(self): - raise AttributeError('max_splits has been deprecated. Please use max_history_splits instead') + raise AttributeError( + 'max_splits has been deprecated. Please use max_history_splits instead') @property def max_history_splits(self) -> int: @@ -1184,7 +1290,8 @@ def weight_window_generators(self) -> list[WeightWindowGenerator]: def weight_window_generators(self, wwgs): if not isinstance(wwgs, MutableSequence): wwgs = [wwgs] - self._weight_window_generators = cv.CheckedList(WeightWindowGenerator, 'weight window generators', wwgs) + self._weight_window_generators = cv.CheckedList( + WeightWindowGenerator, 'weight window generators', wwgs) @property def random_ray(self) -> dict: @@ -1221,7 +1328,8 @@ def random_ray(self, random_ray: dict): for mesh, domains in value: cv.check_type('mesh', mesh, MeshBase) cv.check_type('domains', domains, Iterable) - valid_types = (openmc.Material, openmc.Cell, openmc.Universe) + valid_types = (openmc.Material, + openmc.Cell, openmc.Universe) for domain in domains: if not isinstance(domain, valid_types): raise ValueError( @@ -1255,9 +1363,12 @@ def source_rejection_fraction(self) -> float: @source_rejection_fraction.setter def source_rejection_fraction(self, source_rejection_fraction: float): - cv.check_type('source_rejection_fraction', source_rejection_fraction, Real) - cv.check_greater_than('source_rejection_fraction', source_rejection_fraction, 0) - cv.check_less_than('source_rejection_fraction', source_rejection_fraction, 1) + cv.check_type('source_rejection_fraction', + source_rejection_fraction, Real) + cv.check_greater_than('source_rejection_fraction', + source_rejection_fraction, 0) + cv.check_less_than('source_rejection_fraction', + source_rejection_fraction, 1) self._source_rejection_fraction = source_rejection_fraction @property @@ -1422,6 +1533,45 @@ def _create_surf_source_write_subelement(self, root): subelement = ET.SubElement(element, key) subelement.text = str(self._surf_source_write[key]) + def _create_collision_track_subelement(self, root): + if self._collision_track: + element = ET.SubElement(root, "collision_track") + if 'cell_ids' in self._collision_track: + subelement = ET.SubElement(element, "cell_ids") + subelement.text = ' '.join( + str(x) for x in self._collision_track['cell_ids']) + if 'reactions' in self._collision_track: + subelement = ET.SubElement(element, "reactions") + subelement.text = ' '.join( + str(x) for x in self._collision_track['reactions']) + if 'universe_ids' in self._collision_track: + subelement = ET.SubElement(element, "universe_ids") + subelement.text = ' '.join( + str(x) for x in self._collision_track['universe_ids']) + if 'material_ids' in self._collision_track: + subelement = ET.SubElement(element, "material_ids") + subelement.text = ' '.join( + str(x) for x in self._collision_track['material_ids']) + if 'nuclides' in self._collision_track: + subelement = ET.SubElement(element, "nuclides") + subelement.text = ' '.join( + str(x) for x in self._collision_track['nuclides']) + if 'deposited_E_threshold' in self._collision_track: + subelement = ET.SubElement(element, "deposited_E_threshold") + subelement.text = str( + self._collision_track['deposited_E_threshold']) + if 'max_collisions' in self._collision_track: + subelement = ET.SubElement(element, "max_collisions") + subelement.text = str(self._collision_track['max_collisions']) + if 'max_collision_track_files' in self._collision_track: + subelement = ET.SubElement( + element, "max_collision_track_files") + subelement.text = str( + self._collision_track['max_collision_track_files']) + if 'mcpl' in self._collision_track: + subelement = ET.SubElement(element, "mcpl") + subelement.text = str(self._collision_track['mcpl']).lower() + def _create_confidence_intervals(self, root): if self._confidence_intervals is not None: element = ET.SubElement(root, "confidence_intervals") @@ -1477,8 +1627,8 @@ def _create_entropy_mesh_subelement(self, root, mesh_memo=None): # use default heuristic for entropy mesh if not set by user if self.entropy_mesh.dimension is None: if self.particles is None: - raise RuntimeError("Number of particles must be set in order to " \ - "use entropy mesh dimension heuristic") + raise RuntimeError("Number of particles must be set in order to " + "use entropy mesh dimension heuristic") else: n = ceil((self.particles / 20.0)**(1.0 / 3.0)) d = len(self.entropy_mesh.lower_left) @@ -1568,7 +1718,8 @@ def _create_ufs_mesh_subelement(self, root, mesh_memo=None): path = f"./mesh[@id='{self.ufs_mesh.id}']" if root.find(path) is None: root.append(self.ufs_mesh.to_xml_element()) - if mesh_memo is not None: mesh_memo.add(self.ufs_mesh.id) + if mesh_memo is not None: + mesh_memo.add(self.ufs_mesh.id) def _create_use_decay_photons_subelement(self, root): if self._use_decay_photons is not None: @@ -1694,11 +1845,13 @@ def _create_weight_window_checkpoints_subelement(self, root): if 'collision' in self._weight_window_checkpoints: subelement = ET.SubElement(element, "collision") - subelement.text = str(self._weight_window_checkpoints['collision']).lower() + subelement.text = str( + self._weight_window_checkpoints['collision']).lower() if 'surface' in self._weight_window_checkpoints: subelement = ET.SubElement(element, "surface") - subelement.text = str(self._weight_window_checkpoints['surface']).lower() + subelement.text = str( + self._weight_window_checkpoints['surface']).lower() def _create_max_history_splits_subelement(self, root): if self._max_history_splits is not None: @@ -1730,6 +1883,9 @@ def _create_random_ray_subelement(self, root, mesh_memo=None): for domain in domains: domain_elem = ET.SubElement(mesh_elem, 'domain') domain_elem.set('id', str(domain.id)) + domain_elem.set( + 'type', domain.__class__.__name__.lower()) + if mesh_memo is not None and mesh.id not in mesh_memo: domain_elem.set('type', domain.__class__.__name__.lower()) # See if a element already exists -- if not, add it path = f"./mesh[@id='{mesh.id}']" @@ -1881,6 +2037,25 @@ def _surf_source_write_from_xml_element(self, root): value = int(value) self.surf_source_write[key] = value + def _collision_track_from_xml_element(self, root): + elem = root.find('collision_track') + if elem is not None: + for key in ('cell_ids', 'reactions', 'universe_ids', 'material_ids', 'nuclides', + 'deposited_E_threshold', 'max_collisions', "max_collision_track_files", 'mcpl'): + value = get_text(elem, key) + if value is not None: + if key in ('cell_ids', 'universe_ids', 'material_ids'): + value = [int(x) for x in value.split()] + elif key in ('reactions', 'nuclides'): + value = value.split() + elif key in ('max_collisions', 'max_collision_track_files'): + value = int(value) + elif key == 'deposited_E_threshold': + value = float(value) + elif key == 'mcpl': + value = value in ('true', '1') + self.collision_track[key] = value + def _confidence_intervals_from_xml_element(self, root): text = get_text(root, 'confidence_intervals') if text is not None: @@ -2180,7 +2355,8 @@ def _random_ray_from_xml_element(self, root, meshes=None): elif domain_type == 'universe': domain = openmc.Universe(domain_id) domains.append(domain) - self.random_ray['source_region_meshes'].append((mesh, domains)) + self.random_ray['source_region_meshes'].append( + (mesh, domains)) def _use_decay_photons_from_xml_element(self, root): text = get_text(root, 'use_decay_photons') @@ -2223,6 +2399,7 @@ def to_xml_element(self, mesh_memo=None): self._create_sourcepoint_subelement(element) self._create_surf_source_read_subelement(element) self._create_surf_source_write_subelement(element) + self._create_collision_track_subelement(element) self._create_confidence_intervals(element) self._create_electron_treatment_subelement(element) self._create_energy_mode_subelement(element) @@ -2336,6 +2513,7 @@ def from_xml_element(cls, elem, meshes=None): settings._sourcepoint_from_xml_element(elem) settings._surf_source_read_from_xml_element(elem) settings._surf_source_write_from_xml_element(elem) + settings._collision_track_from_xml_element(elem) settings._confidence_intervals_from_xml_element(elem) settings._electron_treatment_from_xml_element(elem) settings._energy_mode_from_xml_element(elem) diff --git a/openmc/source.py b/openmc/source.py index 9b730cf1de2..84d8a9619dc 100644 --- a/openmc/source.py +++ b/openmc/source.py @@ -6,7 +6,6 @@ from pathlib import Path import warnings from typing import Any -from pathlib import Path import lxml.etree as ET import numpy as np @@ -107,10 +106,12 @@ def constraints(self, constraints: dict[str, Any] | None): cv.check_type('fissionable', value, bool) self._constraints['fissionable'] = value elif key == 'rejection_strategy': - cv.check_value('rejection strategy', value, ('resample', 'kill')) + cv.check_value('rejection strategy', + value, ('resample', 'kill')) self._constraints['rejection_strategy'] = value else: - raise ValueError(f'Unknown key in constraints dictionary: {key}') + raise ValueError( + f'Unknown key in constraints dictionary: {key}') @abstractmethod def populate_xml_element(self, element): @@ -144,13 +145,16 @@ def to_xml_element(self) -> ET.Element: dt_elem = ET.SubElement(constraints_elem, "domain_type") dt_elem.text = constraints["domain_type"] id_elem = ET.SubElement(constraints_elem, "domain_ids") - id_elem.text = ' '.join(str(uid) for uid in constraints["domain_ids"]) + id_elem.text = ' '.join(str(uid) + for uid in constraints["domain_ids"]) if "time_bounds" in constraints: dt_elem = ET.SubElement(constraints_elem, "time_bounds") - dt_elem.text = ' '.join(str(t) for t in constraints["time_bounds"]) + dt_elem.text = ' '.join(str(t) + for t in constraints["time_bounds"]) if "energy_bounds" in constraints: dt_elem = ET.SubElement(constraints_elem, "energy_bounds") - dt_elem.text = ' '.join(str(E) for E in constraints["energy_bounds"]) + dt_elem.text = ' '.join(str(E) + for E in constraints["energy_bounds"]) if "fissionable" in constraints: dt_elem = ET.SubElement(constraints_elem, "fissionable") dt_elem.text = str(constraints["fissionable"]).lower() @@ -199,7 +203,8 @@ def from_xml_element(cls, elem: ET.Element, meshes=None) -> SourceBase: elif source_type == 'mesh': return MeshSource.from_xml_element(elem, meshes) else: - raise ValueError(f'Source type {source_type} is not recognized') + raise ValueError( + f'Source type {source_type} is not recognized') @staticmethod def _get_constraints(elem: ET.Element) -> dict[str, Any]: @@ -316,7 +321,8 @@ def __init__( time: openmc.stats.Univariate | None = None, strength: float = 1.0, particle: str = 'neutron', - domains: Sequence[openmc.Cell | openmc.Material | openmc.Universe] | None = None, + domains: Sequence[openmc.Cell | openmc.Material | + openmc.Universe] | None = None, constraints: dict[str, Any] | None = None ): if domains is not None: @@ -527,11 +533,12 @@ class MeshSource(SourceBase): 'fissionable', and 'rejection_strategy'. """ + def __init__( self, mesh: MeshBase, sources: Sequence[SourceBase], - constraints: dict[str, Any] | None = None, + constraints: dict[str, Any] | None = None, ): super().__init__(strength=None, constraints=constraints) self.mesh = mesh @@ -577,7 +584,8 @@ def sources(self, s): elif isinstance(self.mesh, UnstructuredMesh): if s.ndim > 1: - raise ValueError('Sources must be a 1-D array for unstructured mesh') + raise ValueError( + 'Sources must be a 1-D array for unstructured mesh') self._sources = s for src in self._sources: @@ -646,7 +654,8 @@ def from_xml_element(cls, elem: ET.Element, meshes) -> openmc.MeshSource: mesh_id = int(get_text(elem, 'mesh')) mesh = meshes[mesh_id] - sources = [SourceBase.from_xml_element(e) for e in elem.iterchildren('source')] + sources = [SourceBase.from_xml_element( + e) for e in elem.iterchildren('source')] constraints = cls._get_constraints(elem) return cls(mesh, sources, constraints=constraints) @@ -656,7 +665,8 @@ def Source(*args, **kwargs): A function for backward compatibility of sources. Will be removed in the future. Please update to IndependentSource. """ - warnings.warn("This class is deprecated in favor of 'IndependentSource'", FutureWarning) + warnings.warn( + "This class is deprecated in favor of 'IndependentSource'", FutureWarning) return openmc.IndependentSource(*args, **kwargs) @@ -703,6 +713,7 @@ class CompiledSource(SourceBase): 'fissionable', and 'rejection_strategy'. """ + def __init__( self, library: PathLike, @@ -914,7 +925,8 @@ def from_string(cls, value: str): try: return cls[value.upper()] except KeyError: - raise ValueError(f"Invalid string for creation of {cls.__name__}: {value}") + raise ValueError( + f"Invalid string for creation of {cls.__name__}: {value}") @classmethod def from_pdg_number(cls, pdg_number: int) -> ParticleType: @@ -984,6 +996,7 @@ class SourceParticle: Type of the particle """ + def __init__( self, r: Iterable[float] = (0., 0., 0.), @@ -1042,7 +1055,8 @@ def write_source_file( openmc.SourceParticle """ - cv.check_iterable_type("source particles", source_particles, SourceParticle) + cv.check_iterable_type( + "source particles", source_particles, SourceParticle) pl = ParticleList(source_particles) pl.export_to_hdf5(filename, **kwargs) @@ -1104,7 +1118,8 @@ def from_mcpl(cls, filename: PathLike) -> ParticleList: for particle in f.particles: # Determine particle type based on the PDG number try: - particle_type = ParticleType.from_pdg_number(particle.pdgcode) + particle_type = ParticleType.from_pdg_number( + particle.pdgcode) except ValueError: particle_type = "UNKNOWN" @@ -1241,3 +1256,133 @@ def read_source_file(filename: PathLike) -> ParticleList: return ParticleList.from_hdf5(filename) else: return ParticleList.from_mcpl(filename) + + +def read_collision_track_hdf5(filename): + """Read a collision track file in HDF5 format. + + Parameters + ---------- + filename : str or path-like + Path to the HDF5 collision track file. + + Returns + ------- + numpy.ndarray + Structured array containing collision track data. + + See Also + -------- + read_collision_track_mcpl + read_collision_track_file + """ + + with h5py.File(filename, 'r') as file: + data = file['collision_track_bank'][:] + + return data + + +def read_collision_track_mcpl(file_path): + """Read a collision track file in MCPL format. + + Parameters + ---------- + file_path : str or path-like + Path to the MCPL collision track file. + + Returns + ------- + numpy.ndarray + Structured array of particle collision track information, including + position, direction, energy, weight, reaction data, and identifiers. + + See Also + -------- + read_collision_track_hdf5 + read_collision_track_file + """ + import mcpl + myfile = mcpl.MCPLFile(file_path) + data = { + 'r': [], # for position (x, y, z) + 'u': [], # for direction (ux, uy, uz) + 'E': [], 'dE': [], 'time': [], + 'wgt': [], 'event_mt': [], 'delayed_group': [], + 'cell_id': [], 'nuclide_id': [], 'material_id': [], + 'universe_id': [], 'n_collision': [], 'particle': [], + 'parent_id': [], 'progeny_id': [] + } + + # Read and collect data from the MCPL file + for i, p in enumerate(myfile.particles): + if f'blob_{i}' in myfile.blobs: + blob_data = myfile.blobs[f'blob_{i}'] + decoded_str = blob_data.decode('utf-8') + pairs = decoded_str.split(';') + values_dict = {k.strip(): v.strip() + for k, v in (pair.split(':') for pair in pairs if pair.strip())} + + data['r'].append((p.x, p.y, p.z)) # Append as tuple + data['u'].append((p.ux, p.uy, p.uz)) # Append as tuple + data['E'].append(p.ekin * 1e6) + data['dE'].append(float(values_dict.get('dE', 0))) + data['time'].append(p.time * 1e-3) + data['wgt'].append(p.weight) + data['event_mt'].append(int(values_dict.get('event_mt', 0))) + data['delayed_group'].append( + int(values_dict.get('delayed_group', 0))) + data['cell_id'].append(int(values_dict.get('cell_id', 0))) + data['nuclide_id'].append(int(values_dict.get('nuclide_id', 0))) + data['material_id'].append(int(values_dict.get('material_id', 0))) + data['universe_id'].append(int(values_dict.get('universe_id', 0))) + data['n_collision'].append(int(values_dict.get('n_collision', 0))) + data['particle'].append(ParticleType.from_pdg_number(p.pdgcode)) + data['parent_id'].append(int(values_dict.get('parent_id', 0))) + data['progeny_id'].append(int(values_dict.get('progeny_id', 0))) + + dtypes = [ + ('r', [('x', 'f8'), ('y', 'f8'), ('z', 'f8')]), + ('u', [('x', 'f8'), ('y', 'f8'), ('z', 'f8')]), + ('E', 'f8'), ('dE', 'f8'), ('time', 'f8'), ('wgt', 'f8'), + ('event_mt', 'f8'), ('delayed_group', 'i4'), ('cell_id', 'i4'), + ('nuclide_id', 'i4'), ('material_id', 'i4'), ('universe_id', 'i4'), + ('n_collision', 'i4'), ('particle', 'i4'), + ('parent_id', 'i8'), ('progeny_id', 'i8') + ] + + structured_array = np.zeros(len(data['r']), dtype=dtypes) + for key in data: + structured_array[key] = data[key] # Assign data + + return structured_array + + +def read_collision_track_file(filename): + """Read a collision track file (HDF5 or MCPL) and return its data. + + Parameters + ---------- + filename : str or path-like + Path to the collision track file to read. Must end with + ``.h5`` or ``.mcpl``. + + Returns + ------- + numpy.ndarray + Structured array containing collision track data. + + See Also + -------- + read_collision_track_hdf5 + read_collision_track_mcpl + """ + + filename = Path(filename) + if filename.suffix not in ('.h5', '.mcpl'): + raise ValueError('Collision track file must have a .h5 or .mcpl extension.') + + if filename.suffix == '.h5': + return read_collision_track_hdf5(filename) + else: + return read_collision_track_mcpl(filename) diff --git a/src/bank.cpp b/src/bank.cpp index 3e806b3c0b7..33790379b85 100644 --- a/src/bank.cpp +++ b/src/bank.cpp @@ -20,6 +20,8 @@ vector source_bank; SharedArray surf_source_bank; +SharedArray collision_track_bank; + // The fission bank is allocated as a SharedArray, rather than a vector, as it // will be shared by all threads in the simulation. It will be allocated to a // fixed maximum capacity in the init_fission_bank() function. Then, Elements @@ -50,6 +52,7 @@ void free_memory_bank() { simulation::source_bank.clear(); simulation::surf_source_bank.clear(); + simulation::collision_track_bank.clear(); simulation::fission_bank.clear(); simulation::progeny_per_particle.clear(); simulation::ifp_source_delayed_group_bank.clear(); diff --git a/src/collision_track.cpp b/src/collision_track.cpp new file mode 100644 index 00000000000..75e56574a68 --- /dev/null +++ b/src/collision_track.cpp @@ -0,0 +1,238 @@ +#include "openmc/collision_track.h" + +#include +#include + +#include + +#include "openmc/bank.h" +#include "openmc/bank_io.h" +#include "openmc/cell.h" +#include "openmc/constants.h" +#include "openmc/error.h" +#include "openmc/file_utils.h" +#include "openmc/hdf5_interface.h" +#include "openmc/material.h" +#include "openmc/mcpl_interface.h" +#include "openmc/message_passing.h" +#include "openmc/nuclide.h" +#include "openmc/output.h" +#include "openmc/particle.h" +#include "openmc/settings.h" +#include "openmc/simulation.h" +#include "openmc/universe.h" + +#ifdef OPENMC_MPI +#include +#endif + +namespace openmc { + +namespace { + +hid_t h5_collision_track_banktype() +{ + hid_t postype = H5Tcreate(H5T_COMPOUND, sizeof(Position)); + H5Tinsert(postype, "x", HOFFSET(Position, x), H5T_NATIVE_DOUBLE); + H5Tinsert(postype, "y", HOFFSET(Position, y), H5T_NATIVE_DOUBLE); + H5Tinsert(postype, "z", HOFFSET(Position, z), H5T_NATIVE_DOUBLE); + + hid_t banktype = H5Tcreate(H5T_COMPOUND, sizeof(CollisionTrackSite)); + + H5Tinsert(banktype, "r", HOFFSET(CollisionTrackSite, r), postype); + H5Tinsert(banktype, "u", HOFFSET(CollisionTrackSite, u), postype); + H5Tinsert(banktype, "E", HOFFSET(CollisionTrackSite, E), H5T_NATIVE_DOUBLE); + H5Tinsert(banktype, "dE", HOFFSET(CollisionTrackSite, dE), H5T_NATIVE_DOUBLE); + H5Tinsert( + banktype, "time", HOFFSET(CollisionTrackSite, time), H5T_NATIVE_DOUBLE); + H5Tinsert( + banktype, "wgt", HOFFSET(CollisionTrackSite, wgt), H5T_NATIVE_DOUBLE); + H5Tinsert(banktype, "event_mt", HOFFSET(CollisionTrackSite, event_mt), + H5T_NATIVE_INT); + H5Tinsert(banktype, "delayed_group", + HOFFSET(CollisionTrackSite, delayed_group), H5T_NATIVE_INT); + H5Tinsert( + banktype, "cell_id", HOFFSET(CollisionTrackSite, cell_id), H5T_NATIVE_INT); + H5Tinsert(banktype, "nuclide_id", HOFFSET(CollisionTrackSite, nuclide_id), + H5T_NATIVE_INT); + H5Tinsert(banktype, "material_id", HOFFSET(CollisionTrackSite, material_id), + H5T_NATIVE_INT); + H5Tinsert(banktype, "universe_id", HOFFSET(CollisionTrackSite, universe_id), + H5T_NATIVE_INT); + H5Tinsert(banktype, "n_collision", HOFFSET(CollisionTrackSite, n_collision), + H5T_NATIVE_INT); + H5Tinsert(banktype, "particle", HOFFSET(CollisionTrackSite, particle), + H5T_NATIVE_INT); + H5Tinsert(banktype, "parent_id", HOFFSET(CollisionTrackSite, parent_id), + H5T_NATIVE_INT64); + H5Tinsert(banktype, "progeny_id", HOFFSET(CollisionTrackSite, progeny_id), + H5T_NATIVE_INT64); + H5Tclose(postype); + return banktype; +} + +void write_collision_track_bank(hid_t group_id, + openmc::span collision_track_bank, + const openmc::vector& bank_index) +{ + hid_t banktype = h5_collision_track_banktype(); +#ifdef OPENMC_MPI + write_bank_dataset("collision_track_bank", group_id, collision_track_bank, + bank_index, banktype, mpi::collision_track_site); +#else + write_bank_dataset("collision_track_bank", group_id, collision_track_bank, + bank_index, banktype); +#endif + + H5Tclose(banktype); +} + +void write_h5_collision_track(const char* filename, + openmc::span collision_track_bank, + const openmc::vector& bank_index) +{ +#ifdef PHDF5 + bool parallel = true; +#else + bool parallel = false; +#endif + + if (!filename) + fatal_error("write_h5_collision_track filename needs a nonempty name."); + + std::string filename_(filename); + const auto extension = get_file_extension(filename_); + if (extension.empty()) { + filename_.append(".h5"); + } else if (extension != "h5") { + warning("write_h5_collision_track was passed a file extension differing " + "from .h5, but an hdf5 file will be written."); + } + + hid_t file_id; + if (mpi::master || parallel) { + file_id = file_open(filename_.c_str(), 'w', true); + + // Write filetype and version info + write_attribute(file_id, "filetype", "collision_track"); + write_attribute(file_id, "version", VERSION_COLLISION_TRACK); + } + + write_collision_track_bank(file_id, collision_track_bank, bank_index); + + if (mpi::master || parallel) + file_close(file_id); +} + +} // namespace + +bool should_record_event(int id_cell, int mt_event, const std::string& nuclide, + int id_universe, int id_material, double energy_loss) +{ + auto matches_filter = [](const auto& filter_set, const auto& value) { + return filter_set.empty() || filter_set.count(value) > 0; + }; + + const auto& cfg = settings::collision_track_config; + return simulation::current_batch > settings::n_inactive && + !simulation::collision_track_bank.full() && + matches_filter(cfg.cell_ids, id_cell) && + matches_filter(cfg.mt_numbers, mt_event) && + matches_filter(cfg.universe_ids, id_universe) && + matches_filter(cfg.material_ids, id_material) && + matches_filter(cfg.nuclides, nuclide) && + (cfg.deposited_energy_threshold == 0 || + cfg.deposited_energy_threshold < energy_loss); +} + +void collision_track_reserve_bank() +{ + simulation::collision_track_bank.reserve( + settings::collision_track_config.max_collisions); +} + +void collision_track_flush_bank() +{ + const auto& cfg = settings::collision_track_config; + if (simulation::ct_current_file > cfg.max_files) + return; + + bool last_batch = (simulation::current_batch == settings::n_batches); + if (!simulation::collision_track_bank.full() && !last_batch) + return; + + auto size = simulation::collision_track_bank.size(); + if (size == 0 && !last_batch) + return; + + auto collision_track_work_index = mpi::calculate_parallel_index_vector(size); + openmc::span collisiontrackbankspan( + simulation::collision_track_bank.begin(), size); + + std::string ext = cfg.mcpl_write ? "mcpl" : "h5"; + auto filename = fmt::format("{}collision_track.{}.{}", settings::path_output, + simulation::ct_current_file, ext); + + if (cfg.max_files == 1 || (simulation::ct_current_file == 1 && last_batch)) { + filename = settings::path_output + "collision_track." + ext; + } + write_message("Creating {}...", filename, 4); + + if (cfg.mcpl_write) { + write_mcpl_collision_track( + filename.c_str(), collisiontrackbankspan, collision_track_work_index); + } else { + write_h5_collision_track( + filename.c_str(), collisiontrackbankspan, collision_track_work_index); + } + + simulation::collision_track_bank.clear(); + if (!last_batch && cfg.max_files >= 1) { + collision_track_reserve_bank(); + } + ++simulation::ct_current_file; +} + +void collision_track_record(Particle& particle) +{ + int cell_index = particle.lowest_coord().cell(); + if (cell_index == C_NONE) + return; + + int cell_id = model::cells[cell_index]->id_; + const auto* nuclide_ptr = data::nuclides[particle.event_nuclide()].get(); + std::string nuclide = nuclide_ptr->name_; + int universe_id = model::universes[particle.lowest_coord().universe()]->id_; + double delta_E = particle.E_last() - particle.E(); + int material_index = particle.material(); + if (material_index == C_NONE) + return; + + int material_id = model::materials[material_index]->id_; + + if (!should_record_event(cell_id, particle.event_mt(), nuclide, universe_id, + material_id, delta_E)) + return; + + CollisionTrackSite site; + site.r = particle.r(); + site.u = particle.u(); + site.E = particle.E_last(); + site.dE = delta_E; + site.time = particle.time(); + site.wgt = particle.wgt(); + site.event_mt = particle.event_mt(); + site.delayed_group = particle.delayed_group(); + site.cell_id = cell_id; + site.nuclide_id = + 10000 * nuclide_ptr->Z_ + 10 * nuclide_ptr->A_ + nuclide_ptr->metastable_; + site.material_id = material_id; + site.universe_id = universe_id; + site.n_collision = particle.n_collision(); + site.particle = particle.type(); + site.parent_id = particle.id(); + site.progeny_id = particle.n_progeny(); + simulation::collision_track_bank.thread_safe_append(site); +} + +} // namespace openmc diff --git a/src/finalize.cpp b/src/finalize.cpp index 659f390b39a..9ee94340997 100644 --- a/src/finalize.cpp +++ b/src/finalize.cpp @@ -3,6 +3,7 @@ #include "openmc/bank.h" #include "openmc/capi.h" #include "openmc/cmfd_solver.h" +#include "openmc/collision_track.h" #include "openmc/constants.h" #include "openmc/cross_sections.h" #include "openmc/dagmc.h" @@ -76,6 +77,7 @@ int openmc_finalize() // Reset global variables settings::assume_separate = false; settings::check_overlaps = false; + settings::collision_track_config = CollisionTrackConfig {}; settings::confidence_intervals = false; settings::create_fission_neutrons = true; settings::create_delayed_neutrons = true; @@ -177,6 +179,9 @@ int openmc_finalize() if (mpi::source_site != MPI_DATATYPE_NULL) { MPI_Type_free(&mpi::source_site); } + if (mpi::collision_track_site != MPI_DATATYPE_NULL) { + MPI_Type_free(&mpi::collision_track_site); + } #endif openmc_reset_random_ray(); diff --git a/src/initialize.cpp b/src/initialize.cpp index 36f32611690..e2a5b974338 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -178,6 +178,37 @@ void initialize_mpi(MPI_Comm intracomm) MPI_DOUBLE, MPI_INT, MPI_INT, MPI_INT, MPI_INT, MPI_LONG, MPI_LONG}; MPI_Type_create_struct(11, blocks, disp, types, &mpi::source_site); MPI_Type_commit(&mpi::source_site); + + CollisionTrackSite bc; + MPI_Aint dispc[16]; + MPI_Get_address(&bc.r, &dispc[0]); // double + MPI_Get_address(&bc.u, &dispc[1]); // double + MPI_Get_address(&bc.E, &dispc[2]); // double + MPI_Get_address(&bc.dE, &dispc[3]); // double + MPI_Get_address(&bc.time, &dispc[4]); // double + MPI_Get_address(&bc.wgt, &dispc[5]); // double + MPI_Get_address(&bc.event_mt, &dispc[6]); // int + MPI_Get_address(&bc.delayed_group, &dispc[7]); // int + MPI_Get_address(&bc.cell_id, &dispc[8]); // int + MPI_Get_address(&bc.nuclide_id, &dispc[9]); // int + MPI_Get_address(&bc.material_id, &dispc[10]); // int + MPI_Get_address(&bc.universe_id, &dispc[11]); // int + MPI_Get_address(&bc.n_collision, &dispc[12]); // int + MPI_Get_address(&bc.particle, &dispc[13]); // int + MPI_Get_address(&bc.parent_id, &dispc[14]); // int64_t + MPI_Get_address(&bc.progeny_id, &dispc[15]); // int64_t + for (int i = 15; i >= 0; --i) { + dispc[i] -= dispc[0]; + } + + int blocksc[] = {3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + MPI_Datatype typesc[] = {MPI_DOUBLE, MPI_DOUBLE, MPI_DOUBLE, MPI_DOUBLE, + MPI_DOUBLE, MPI_DOUBLE, MPI_INT, MPI_INT, MPI_INT, MPI_INT, MPI_INT, + MPI_INT, MPI_INT, MPI_INT, MPI_INT64_T, MPI_INT64_T}; + + MPI_Type_create_struct( + 16, blocksc, dispc, typesc, &mpi::collision_track_site); + MPI_Type_commit(&mpi::collision_track_site); } #endif // OPENMC_MPI diff --git a/src/mcpl_interface.cpp b/src/mcpl_interface.cpp index b8e78071003..1294073019e 100644 --- a/src/mcpl_interface.cpp +++ b/src/mcpl_interface.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -61,6 +62,8 @@ using mcpl_hdr_nparticles_fpt = uint64_t (*)(mcpl_file_t* file_handle); using mcpl_read_fpt = const mcpl_particle_repr_t* (*)(mcpl_file_t* file_handle); using mcpl_close_file_fpt = void (*)(mcpl_file_t* file_handle); +using mcpl_hdr_add_data_fpt = void (*)(mcpl_outfile_t* file_handle, + const char* key, int32_t ldata, const char* data); using mcpl_create_outfile_fpt = mcpl_outfile_t* (*)(const char* filename); using mcpl_hdr_set_srcname_fpt = void (*)( mcpl_outfile_t* outfile_handle, const char* srcname); @@ -110,6 +113,7 @@ struct McplApi { mcpl_close_file_fpt close_file; mcpl_create_outfile_fpt create_outfile; mcpl_hdr_set_srcname_fpt hdr_set_srcname; + mcpl_hdr_add_data_fpt hdr_add_data; mcpl_add_particle_fpt add_particle; mcpl_close_outfile_fpt close_outfile; mcpl_hdr_add_stat_sum_fpt hdr_add_stat_sum; @@ -146,6 +150,8 @@ struct McplApi { load_symbol_platform("mcpl_create_outfile")); hdr_set_srcname = reinterpret_cast( load_symbol_platform("mcpl_hdr_set_srcname")); + hdr_add_data = reinterpret_cast( + load_symbol_platform("mcpl_hdr_add_data")); add_particle = reinterpret_cast( load_symbol_platform("mcpl_add_particle")); close_outfile = reinterpret_cast( @@ -545,4 +551,153 @@ void write_mcpl_source_point(const char* filename, span source_bank, } } +// Collision track feature with MCPL +void write_mcpl_collision_track_internal(mcpl_outfile_t* file_id, + span collision_track_bank, + const vector& bank_index_all_ranks) +{ + if (mpi::master) { + if (!file_id) { + fatal_error("MCPL: Internal error - master rank called " + "write_mcpl_source_bank_internal with null file_id."); + } + vector receive_buffer; + vector all_sites; + all_sites.reserve(static_cast(bank_index_all_ranks.back())); + vector all_blobs; + all_blobs.reserve(static_cast(bank_index_all_ranks.back())); + + for (int rank_idx = 0; rank_idx < mpi::n_procs; ++rank_idx) { + size_t num_sites_on_rank = static_cast( + bank_index_all_ranks[rank_idx + 1] - bank_index_all_ranks[rank_idx]); + if (num_sites_on_rank == 0) + continue; + + span sites_to_process; +#ifdef OPENMC_MPI + if (rank_idx == mpi::rank) { + sites_to_process = openmc::span( + collision_track_bank.data(), num_sites_on_rank); + } else { + receive_buffer.resize(num_sites_on_rank); + MPI_Recv(receive_buffer.data(), num_sites_on_rank, + mpi::collision_track_site, rank_idx, rank_idx, mpi::intracomm, + MPI_STATUS_IGNORE); + sites_to_process = openmc::span( + receive_buffer.data(), num_sites_on_rank); + } +#else + sites_to_process = openmc::span( + collision_track_bank.data(), num_sites_on_rank); +#endif + + for (const auto& site : sites_to_process) { + std::ostringstream custom_data_stream; + custom_data_stream << " dE : " << site.dE + << " ; event_mt : " << site.event_mt + << " ; delayed_group : " << site.delayed_group + << " ; cell_id : " << site.cell_id + << " ; nuclide_id : " << site.nuclide_id + << " ; material_id : " << site.material_id + << " ; universe_id : " << site.universe_id + << " ; n_collision : " << site.n_collision + << " ; parent_id : " << site.parent_id + << " ; progeny_id : " << site.progeny_id; + + all_blobs.push_back(custom_data_stream.str()); + all_sites.push_back(site); + } + } + + for (size_t idx = 0; idx < all_blobs.size(); ++idx) { + const auto& blob = all_blobs[idx]; + std::string key = "blob_" + std::to_string(idx); + g_mcpl_api->hdr_add_data(file_id, key.c_str(), blob.size(), blob.c_str()); + } + + for (const auto& site : all_sites) { + mcpl_particle_repr_t p_repr {}; + p_repr.position[0] = site.r.x; + p_repr.position[1] = site.r.y; + p_repr.position[2] = site.r.z; + p_repr.direction[0] = site.u.x; + p_repr.direction[1] = site.u.y; + p_repr.direction[2] = site.u.z; + p_repr.ekin = site.E * 1e-6; + p_repr.time = site.time * 1e3; + p_repr.weight = site.wgt; + switch (site.particle) { + case ParticleType::neutron: + p_repr.pdgcode = 2112; + break; + case ParticleType::photon: + p_repr.pdgcode = 22; + break; + case ParticleType::electron: + p_repr.pdgcode = 11; + break; + case ParticleType::positron: + p_repr.pdgcode = -11; + break; + default: + continue; + } + g_mcpl_api->add_particle(file_id, &p_repr); + } + } else { +#ifdef OPENMC_MPI + if (!collision_track_bank.empty()) { + MPI_Send(collision_track_bank.data(), collision_track_bank.size(), + mpi::collision_track_site, 0, mpi::rank, mpi::intracomm); + } +#endif + } +} + +void write_mcpl_collision_track(const char* filename, + span collision_track_bank, + const vector& bank_index) +{ + ensure_mcpl_ready_or_fatal(); + + std::string filename_(filename); + const auto extension = get_file_extension(filename_); + if (extension.empty()) { + filename_.append(".mcpl"); + } else if (extension != "mcpl") { + warning(fmt::format("Specified filename '{}' has an extension '.{}', but " + "an MCPL file (.mcpl) will be written using this name.", + filename, extension)); + } + + mcpl_outfile_t* file_id = nullptr; + + if (mpi::master) { + file_id = g_mcpl_api->create_outfile(filename_.c_str()); + if (!file_id) { + fatal_error(fmt::format( + "MCPL: Failed to create output file '{}'. Check permissions and path.", + filename_)); + } + std::string src_line; + if (VERSION_DEV) { + src_line = fmt::format("OpenMC {}.{}.{}-dev{}", VERSION_MAJOR, + VERSION_MINOR, VERSION_RELEASE, VERSION_COMMIT_COUNT); + } else { + src_line = fmt::format( + "OpenMC {}.{}.{}", VERSION_MAJOR, VERSION_MINOR, VERSION_RELEASE); + } + + g_mcpl_api->hdr_set_srcname(file_id, src_line.c_str()); + } + write_mcpl_collision_track_internal( + file_id, collision_track_bank, bank_index); + + if (mpi::master) { + if (file_id) { + g_mcpl_api->close_outfile(file_id); + } + } +} + } // namespace openmc diff --git a/src/message_passing.cpp b/src/message_passing.cpp index 374c1aa7257..a160f6d73c0 100644 --- a/src/message_passing.cpp +++ b/src/message_passing.cpp @@ -10,6 +10,7 @@ bool master {true}; #ifdef OPENMC_MPI MPI_Comm intracomm {MPI_COMM_NULL}; MPI_Datatype source_site {MPI_DATATYPE_NULL}; +MPI_Datatype collision_track_site {MPI_DATATYPE_NULL}; #endif extern "C" bool openmc_master() diff --git a/src/particle.cpp b/src/particle.cpp index 6ba8ebf1286..2d70d715e22 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -8,6 +8,7 @@ #include "openmc/bank.h" #include "openmc/capi.h" #include "openmc/cell.h" +#include "openmc/collision_track.h" #include "openmc/constants.h" #include "openmc/dagmc.h" #include "openmc/error.h" @@ -351,6 +352,11 @@ void Particle::event_collide() collision_mg(*this); } + // Collision track feature to recording particle interaction + if (settings::collision_track) { + collision_track_record(*this); + } + // Score collision estimator tallies -- this is done after a collision // has occurred rather than before because we need information on the // outgoing energy for any tallies with an outgoing energy filter diff --git a/src/physics.cpp b/src/physics.cpp index 3a17077b368..41509af97be 100644 --- a/src/physics.cpp +++ b/src/physics.cpp @@ -122,6 +122,7 @@ void sample_neutron_reaction(Particle& p) "with k-effective close to or greater than one."); } } + p.event_mt() = rx.mt_; } // Create secondary photons @@ -663,7 +664,9 @@ void absorption(Particle& p, int i_nuclide) p.wgt() = 0.0; p.event() = TallyEvent::ABSORB; - p.event_mt() = N_DISAPPEAR; + if (!p.fission()) { + p.event_mt() = N_DISAPPEAR; + } } } } diff --git a/src/settings.cpp b/src/settings.cpp index 13b91b0e4f8..9dcf7c8dbc8 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -11,6 +11,7 @@ #endif #include "openmc/capi.h" +#include "openmc/collision_track.h" #include "openmc/constants.h" #include "openmc/container_util.h" #include "openmc/distribution.h" @@ -26,6 +27,7 @@ #include "openmc/plot.h" #include "openmc/random_lcg.h" #include "openmc/random_ray/random_ray.h" +#include "openmc/reaction.h" #include "openmc/simulation.h" #include "openmc/source.h" #include "openmc/string_utils.h" @@ -45,6 +47,7 @@ namespace settings { // Default values for boolean flags bool assume_separate {false}; bool check_overlaps {false}; +bool collision_track {false}; bool cmfd_run {false}; bool confidence_intervals {false}; bool create_delayed_neutrons {true}; @@ -128,6 +131,7 @@ std::unordered_set statepoint_batch; double source_rejection_fraction {0.05}; double free_gas_threshold {400.0}; std::unordered_set source_write_surf_id; +CollisionTrackConfig collision_track_config {}; int64_t ssw_max_particles; int64_t ssw_max_files; int64_t ssw_cell_id {C_NONE}; @@ -925,8 +929,72 @@ void read_settings_xml(pugi::xml_node root) } } - // If source is not separate and is to be written out in the statepoint file, - // make sure that the sourcepoint batch numbers are contained in the + // Check if the user has specified to write specific collisions + if (check_for_node(root, "collision_track")) { + settings::collision_track = true; + // Get collision track node + xml_node node_ct = root.child("collision_track"); + collision_track_config = CollisionTrackConfig {}; + + // Determine cell ids at which crossing particles are to be banked + if (check_for_node(node_ct, "cell_ids")) { + auto temp = get_node_array(node_ct, "cell_ids"); + for (const auto& b : temp) { + collision_track_config.cell_ids.insert(b); + } + } + if (check_for_node(node_ct, "reactions")) { + auto temp = get_node_array(node_ct, "reactions"); + for (const auto& b : temp) { + int reaction_int = reaction_type(b); + if (reaction_int > 0) { + collision_track_config.mt_numbers.insert(reaction_int); + } + } + } + if (check_for_node(node_ct, "universe_ids")) { + auto temp = get_node_array(node_ct, "universe_ids"); + for (const auto& b : temp) { + collision_track_config.universe_ids.insert(b); + } + } + if (check_for_node(node_ct, "material_ids")) { + auto temp = get_node_array(node_ct, "material_ids"); + for (const auto& b : temp) { + collision_track_config.material_ids.insert(b); + } + } + if (check_for_node(node_ct, "nuclides")) { + auto temp = get_node_array(node_ct, "nuclides"); + for (const auto& b : temp) { + collision_track_config.nuclides.insert(b); + } + } + if (check_for_node(node_ct, "deposited_E_threshold")) { + collision_track_config.deposited_energy_threshold = + std::stod(get_node_value(node_ct, "deposited_E_threshold")); + } + // Get maximum number of particles to be banked per collision + if (check_for_node(node_ct, "max_collisions")) { + collision_track_config.max_collisions = + std::stoll(get_node_value(node_ct, "max_collisions")); + } else { + warning("A maximum number of collisions needs to be specified. " + "By default the code sets 'max_collisions' parameter equals to " + "1000."); + } + // Get maximum number of collision_track files to be created + if (check_for_node(node_ct, "max_collision_track_files")) { + collision_track_config.max_files = + std::stoll(get_node_value(node_ct, "max_collision_track_files")); + } + if (check_for_node(node_ct, "mcpl")) { + collision_track_config.mcpl_write = get_node_value_bool(node_ct, "mcpl"); + } + } + + // If source is not separate and is to be written out in the statepoint + // file, make sure that the sourcepoint batch numbers are contained in the // statepoint list if (!source_separate) { for (const auto& b : sourcepoint_batch) { @@ -1171,8 +1239,8 @@ void read_settings_xml(pugi::xml_node root) variance_reduction::weight_windows_generators.emplace_back( std::make_unique(node_wwg)); } - // if any of the weight windows are intended to be generated otf, make sure - // they're applied + // if any of the weight windows are intended to be generated otf, make + // sure they're applied for (const auto& wwg : variance_reduction::weight_windows_generators) { if (wwg->on_the_fly_) { settings::weight_windows_on = true; diff --git a/src/simulation.cpp b/src/simulation.cpp index 05d55452602..b536ae5881f 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -2,6 +2,7 @@ #include "openmc/bank.h" #include "openmc/capi.h" +#include "openmc/collision_track.h" #include "openmc/container_util.h" #include "openmc/eigenvalue.h" #include "openmc/error.h" @@ -9,7 +10,6 @@ #include "openmc/geometry_aux.h" #include "openmc/ifp.h" #include "openmc/material.h" -#include "openmc/mcpl_interface.h" #include "openmc/message_passing.h" #include "openmc/nuclide.h" #include "openmc/output.h" @@ -118,6 +118,7 @@ int openmc_simulation_init() // Reset global variables -- this is done before loading state point (as that // will potentially populate k_generation and entropy) simulation::current_batch = 0; + simulation::ct_current_file = 1; simulation::ssw_current_file = 1; simulation::k_generation.clear(); simulation::entropy.clear(); @@ -297,6 +298,7 @@ namespace openmc { namespace simulation { +int ct_current_file; int current_batch; int current_gen; bool initialized {false}; @@ -347,6 +349,11 @@ void allocate_banks() // Allocate surface source bank simulation::surf_source_bank.reserve(settings::ssw_max_particles); } + + if (settings::collision_track) { + // Allocate collision track bank + collision_track_reserve_bank(); + } } void initialize_batch() @@ -490,6 +497,10 @@ void finalize_batch() ++simulation::ssw_current_file; } } + // Write collision track file if requested + if (settings::collision_track) { + collision_track_flush_bank(); + } } void initialize_generation() diff --git a/src/state_point.cpp b/src/state_point.cpp index 0b0fed13248..47296da3a51 100644 --- a/src/state_point.cpp +++ b/src/state_point.cpp @@ -9,6 +9,7 @@ #include #include "openmc/bank.h" +#include "openmc/bank_io.h" #include "openmc/capi.h" #include "openmc/constants.h" #include "openmc/eigenvalue.h" @@ -642,91 +643,12 @@ void write_source_bank(hid_t group_id, span source_bank, { hid_t banktype = h5banktype(); - // Set total and individual process dataspace sizes for source bank - int64_t dims_size = bank_index.back(); - int64_t count_size = bank_index[mpi::rank + 1] - bank_index[mpi::rank]; - -#ifdef PHDF5 - // Set size of total dataspace for all procs and rank - hsize_t dims[] {static_cast(dims_size)}; - hid_t dspace = H5Screate_simple(1, dims, nullptr); - hid_t dset = H5Dcreate(group_id, "source_bank", banktype, dspace, H5P_DEFAULT, - H5P_DEFAULT, H5P_DEFAULT); - - // Create another data space but for each proc individually - hsize_t count[] {static_cast(count_size)}; - hid_t memspace = H5Screate_simple(1, count, nullptr); - - // Select hyperslab for this dataspace - hsize_t start[] {static_cast(bank_index[mpi::rank])}; - H5Sselect_hyperslab(dspace, H5S_SELECT_SET, start, nullptr, count, nullptr); - - // Set up the property list for parallel writing - hid_t plist = H5Pcreate(H5P_DATASET_XFER); - H5Pset_dxpl_mpio(plist, H5FD_MPIO_COLLECTIVE); - - // Write data to file in parallel - H5Dwrite(dset, banktype, memspace, dspace, plist, source_bank.data()); - - // Free resources - H5Sclose(dspace); - H5Sclose(memspace); - H5Dclose(dset); - H5Pclose(plist); - -#else - - if (mpi::master) { - // Create dataset big enough to hold all source sites - hsize_t dims[] {static_cast(dims_size)}; - hid_t dspace = H5Screate_simple(1, dims, nullptr); - hid_t dset = H5Dcreate(group_id, "source_bank", banktype, dspace, - H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); - - // Save source bank sites since the array is overwritten below #ifdef OPENMC_MPI - vector temp_source {source_bank.begin(), source_bank.end()}; -#endif - - for (int i = 0; i < mpi::n_procs; ++i) { - // Create memory space - hsize_t count[] {static_cast(bank_index[i + 1] - bank_index[i])}; - hid_t memspace = H5Screate_simple(1, count, nullptr); - -#ifdef OPENMC_MPI - // Receive source sites from other processes - if (i > 0) - MPI_Recv(source_bank.data(), count[0], mpi::source_site, i, i, - mpi::intracomm, MPI_STATUS_IGNORE); -#endif - - // Select hyperslab for this dataspace - dspace = H5Dget_space(dset); - hsize_t start[] {static_cast(bank_index[i])}; - H5Sselect_hyperslab( - dspace, H5S_SELECT_SET, start, nullptr, count, nullptr); - - // Write data to hyperslab - H5Dwrite( - dset, banktype, memspace, dspace, H5P_DEFAULT, source_bank.data()); - - H5Sclose(memspace); - H5Sclose(dspace); - } - - // Close all ids - H5Dclose(dset); - -#ifdef OPENMC_MPI - // Restore state of source bank - std::copy(temp_source.begin(), temp_source.end(), source_bank.begin()); -#endif - } else { -#ifdef OPENMC_MPI - MPI_Send(source_bank.data(), count_size, mpi::source_site, 0, mpi::rank, - mpi::intracomm); -#endif - } + write_bank_dataset("source_bank", group_id, source_bank, bank_index, banktype, + mpi::source_site); +#else + write_bank_dataset( + "source_bank", group_id, source_bank, bank_index, banktype); #endif H5Tclose(banktype); diff --git a/tests/regression_tests/collision_track/__init__.py b/tests/regression_tests/collision_track/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/collision_track/case_1_Reactions/inputs_true.dat b/tests/regression_tests/collision_track/case_1_Reactions/inputs_true.dat new file mode 100644 index 00000000000..7533616c059 --- /dev/null +++ b/tests/regression_tests/collision_track/case_1_Reactions/inputs_true.dat @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + (n,fission) 101 + 300 + + 1 + + diff --git a/tests/regression_tests/collision_track/case_1_Reactions/results_true.dat b/tests/regression_tests/collision_track/case_1_Reactions/results_true.dat new file mode 100644 index 00000000000..d4d1d1e5ad4 --- /dev/null +++ b/tests/regression_tests/collision_track/case_1_Reactions/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +5.642735E-02 1.494035E-02 diff --git a/tests/regression_tests/collision_track/case_2_Cell_ID/inputs_true.dat b/tests/regression_tests/collision_track/case_2_Cell_ID/inputs_true.dat new file mode 100644 index 00000000000..55fb835de03 --- /dev/null +++ b/tests/regression_tests/collision_track/case_2_Cell_ID/inputs_true.dat @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 22 + 300 + + 1 + + diff --git a/tests/regression_tests/collision_track/case_2_Cell_ID/results_true.dat b/tests/regression_tests/collision_track/case_2_Cell_ID/results_true.dat new file mode 100644 index 00000000000..d4d1d1e5ad4 --- /dev/null +++ b/tests/regression_tests/collision_track/case_2_Cell_ID/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +5.642735E-02 1.494035E-02 diff --git a/tests/regression_tests/collision_track/case_3_Material_ID/inputs_true.dat b/tests/regression_tests/collision_track/case_3_Material_ID/inputs_true.dat new file mode 100644 index 00000000000..61890414bab --- /dev/null +++ b/tests/regression_tests/collision_track/case_3_Material_ID/inputs_true.dat @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 1 + 300 + + 1 + + diff --git a/tests/regression_tests/collision_track/case_3_Material_ID/results_true.dat b/tests/regression_tests/collision_track/case_3_Material_ID/results_true.dat new file mode 100644 index 00000000000..d4d1d1e5ad4 --- /dev/null +++ b/tests/regression_tests/collision_track/case_3_Material_ID/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +5.642735E-02 1.494035E-02 diff --git a/tests/regression_tests/collision_track/case_4_Nuclide_ID/inputs_true.dat b/tests/regression_tests/collision_track/case_4_Nuclide_ID/inputs_true.dat new file mode 100644 index 00000000000..8960dde5cbd --- /dev/null +++ b/tests/regression_tests/collision_track/case_4_Nuclide_ID/inputs_true.dat @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + O16 U235 + 300 + + 1 + + diff --git a/tests/regression_tests/collision_track/case_4_Nuclide_ID/results_true.dat b/tests/regression_tests/collision_track/case_4_Nuclide_ID/results_true.dat new file mode 100644 index 00000000000..d4d1d1e5ad4 --- /dev/null +++ b/tests/regression_tests/collision_track/case_4_Nuclide_ID/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +5.642735E-02 1.494035E-02 diff --git a/tests/regression_tests/collision_track/case_5_Universe_ID/inputs_true.dat b/tests/regression_tests/collision_track/case_5_Universe_ID/inputs_true.dat new file mode 100644 index 00000000000..8c0d7aa8ee6 --- /dev/null +++ b/tests/regression_tests/collision_track/case_5_Universe_ID/inputs_true.dat @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 22 + 77 + 300 + + 1 + + diff --git a/tests/regression_tests/collision_track/case_5_Universe_ID/results_true.dat b/tests/regression_tests/collision_track/case_5_Universe_ID/results_true.dat new file mode 100644 index 00000000000..d4d1d1e5ad4 --- /dev/null +++ b/tests/regression_tests/collision_track/case_5_Universe_ID/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +5.642735E-02 1.494035E-02 diff --git a/tests/regression_tests/collision_track/case_6_deposited_energy_threshold/inputs_true.dat b/tests/regression_tests/collision_track/case_6_deposited_energy_threshold/inputs_true.dat new file mode 100644 index 00000000000..5173dc35cf1 --- /dev/null +++ b/tests/regression_tests/collision_track/case_6_deposited_energy_threshold/inputs_true.dat @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 550000.0 + 300 + + 1 + + diff --git a/tests/regression_tests/collision_track/case_6_deposited_energy_threshold/results_true.dat b/tests/regression_tests/collision_track/case_6_deposited_energy_threshold/results_true.dat new file mode 100644 index 00000000000..d4d1d1e5ad4 --- /dev/null +++ b/tests/regression_tests/collision_track/case_6_deposited_energy_threshold/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +5.642735E-02 1.494035E-02 diff --git a/tests/regression_tests/collision_track/case_7_all_parameters_used_together/inputs_true.dat b/tests/regression_tests/collision_track/case_7_all_parameters_used_together/inputs_true.dat new file mode 100644 index 00000000000..005d9feb271 --- /dev/null +++ b/tests/regression_tests/collision_track/case_7_all_parameters_used_together/inputs_true.dat @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 22 33 + elastic 18 (n,disappear) + 77 + 1 11 + U238 U235 H1 U234 + 100000.0 + 300 + + 1 + + diff --git a/tests/regression_tests/collision_track/case_7_all_parameters_used_together/results_true.dat b/tests/regression_tests/collision_track/case_7_all_parameters_used_together/results_true.dat new file mode 100644 index 00000000000..d4d1d1e5ad4 --- /dev/null +++ b/tests/regression_tests/collision_track/case_7_all_parameters_used_together/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +5.642735E-02 1.494035E-02 diff --git a/tests/regression_tests/collision_track/case_8_2threads/inputs_true.dat b/tests/regression_tests/collision_track/case_8_2threads/inputs_true.dat new file mode 100644 index 00000000000..514932c1a69 --- /dev/null +++ b/tests/regression_tests/collision_track/case_8_2threads/inputs_true.dat @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 200 + + 1 + + diff --git a/tests/regression_tests/collision_track/case_8_2threads/results_true.dat b/tests/regression_tests/collision_track/case_8_2threads/results_true.dat new file mode 100644 index 00000000000..d4d1d1e5ad4 --- /dev/null +++ b/tests/regression_tests/collision_track/case_8_2threads/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +5.642735E-02 1.494035E-02 diff --git a/tests/regression_tests/collision_track/test.py b/tests/regression_tests/collision_track/test.py new file mode 100644 index 00000000000..00e1e3de410 --- /dev/null +++ b/tests/regression_tests/collision_track/test.py @@ -0,0 +1,255 @@ +"""Test the 'collision_track' setting. + +Results +------- + +All results are generated using only 1 MPI process. + +All results are generated using 1 thread except for "test_consistency_low_realization_number". +This specific test verifies that when the number of realization (i.e., point being candidate +to be stored) is lower than the capacity, results are reproducible even with multiple +threads (i.e., there is no potential thread competition that would produce different +results in that case). + +All results are generated using the history-based mode except for cases e01 to e03. + +All results are visually verified using the '_visualize.py' script in the regression test folder. + +OpenMC models +------------- + +Four OpenMC models with CSG-only geometries are used to cover the transmission, vacuum, +reflective and periodic Boundary Conditions (BC): + +- model_1: cylindrical core in 2 boxes (vacuum and transmission BC), + +# Test cases for simulation parameters using CSG-only geometries +# ============================================================ +# Each test case is defined by a combination of folder name, model name, and specific parameters. +# Below is a summary of the parameters used in the test cases: +# +# - max_collisions: Maximum number of particles to track in the simulation. +# - reactions: List of MT numbers (reaction types- 2 for scattering, 18 for fission, 101 for absorbtion). +# - cell_ids: IDs of specific cells in the model. +# - mat_ids: Material IDs for filtering particles. +# - nuclides: Nuclides for filtering particles. +# - univ_ids: Universe IDs for filtering particles. +# - E_threshold: Energy threshold for filtering particles (optional). +# +# The test cases are designed to validate the behavior of the simulation under various configurations. + +*: BC stands for Boundary Conditions, T for Transmission, R for Reflective, and V for Vacuum. + +An additional case, called 'case-a01', is used to check that the results are comparable when +the number of threads is set to 2 if the number of realization is lower than the capacity. + + +*: BC stands for Boundary Conditions, T for Transmission, and V for Vacuum. + +Notes: + +- The test cases list is non-exhaustive compared to the number of possible combinations. + Test cases have been selected based on use and internal code logic. + + + +TODO: + +- Test with a lattice. + +""" + +import os + +import openmc +import openmc.lib +import pytest + +from tests.testing_harness import CollisionTrackTestHarness +from tests.regression_tests import config + + +@pytest.fixture(scope="function") +def two_threads(monkeypatch): + """Set the number of OMP threads to 2 for the test.""" + monkeypatch.setenv("OMP_NUM_THREADS", "2") + + +@pytest.fixture(scope="function") +def single_process(monkeypatch): + """Set the number of MPI process to 1 for the test.""" + monkeypatch.setitem(config, "mpi_np", "1") + + +@pytest.fixture(scope="module") +def model_1(): + """Cylindrical core contained in a first box which is contained in a larger box. + A lower universe is used to describe the interior of the first box which + contains the core and its surrounding space. + + """ + openmc.reset_auto_ids() + model = openmc.Model() + + # ============================================================================= + # Materials + # ============================================================================= + + fuel = openmc.Material(material_id=1) + fuel.add_nuclide("U234", 0.0004524) + fuel.add_nuclide("U235", 0.0506068) + fuel.add_nuclide("U238", 0.9487090) + fuel.add_nuclide("U236", 0.0002318) + fuel.add_nuclide("O16", 2.0) + fuel.set_density("g/cm3", 11.0) + + water = openmc.Material(material_id=11) + water.add_nuclide("H1", 2.0) + water.add_nuclide("O16", 1.0) + water.set_density("g/cm3", 1.0) + + # ============================================================================= + # Geometry + # ============================================================================= + + # ----------------------------------------------------------------------------- + # Cylindrical core + # ----------------------------------------------------------------------------- + + # Parameters + core_radius = 2.0 + core_height = 4.0 + + # Surfaces + core_cylinder = openmc.ZCylinder(r=core_radius) + core_lower_plane = openmc.ZPlane(-core_height / 2.0) + core_upper_plane = openmc.ZPlane(core_height / 2.0) + + # Region + core_region = -core_cylinder & +core_lower_plane & -core_upper_plane + + # Cells + core = openmc.Cell(fill=fuel, region=core_region, cell_id=22) + outside_core_region = +core_cylinder | -core_lower_plane | +core_upper_plane + outside_core = openmc.Cell( + fill=water, region=outside_core_region, cell_id=33) + + # Universe + inside_box1_universe = openmc.Universe( + cells=[core, outside_core], universe_id=77) + + # ----------------------------------------------------------------------------- + # Box 1 + # ----------------------------------------------------------------------------- + + # Parameters + box1_size = 6.0 + + # Surfaces + box1_rpp = openmc.model.RectangularParallelepiped( + -box1_size / 2.0, box1_size / 2.0, + -box1_size / 2.0, box1_size / 2.0, + -box1_size / 2.0, box1_size / 2.0, + ) + + # Cell + box1 = openmc.Cell(fill=inside_box1_universe, region=-box1_rpp, cell_id=5) + + # ----------------------------------------------------------------------------- + # Box 2 + # ----------------------------------------------------------------------------- + + # Parameters + box2_size = 8 + + # Surfaces + box2_rpp = openmc.model.RectangularParallelepiped( + -box2_size / 2.0, box2_size / 2.0, + -box2_size / 2.0, box2_size / 2.0, + -box2_size / 2.0, box2_size / 2.0, + boundary_type="vacuum" + ) + + # Cell + box2 = openmc.Cell(fill=water, region=-box2_rpp & +box1_rpp, cell_id=8) + + # Register geometry + model.geometry = openmc.Geometry([box1, box2]) + + # ============================================================================= + # Settings + # ============================================================================= + + model.settings = openmc.Settings() + model.settings.particles = 100 + model.settings.batches = 5 + model.settings.inactive = 1 + model.settings.seed = 1 + + bounds = [ + -core_radius, + -core_radius, + -core_height / 2.0, + core_radius, + core_radius, + core_height / 2.0, + ] + distribution = openmc.stats.Box(bounds[:3], bounds[3:]) + model.settings.source = openmc.IndependentSource( + space=distribution, constraints={'fissionable': True}) + + return model + + +@pytest.mark.parametrize( + "folder, model_name, parameter", + [("case_1_Reactions", "model_1", {"max_collisions": 300, "reactions": ["(n,fission)", 101]}), + ("case_2_Cell_ID", "model_1", { + "max_collisions": 300, "cell_ids": [22]}), + ("case_3_Material_ID", "model_1", { + "max_collisions": 300, "material_ids": [1]}), + ("case_4_Nuclide_ID", "model_1", { + "max_collisions": 300, "nuclides": ["O16", "U235"]}), + ("case_5_Universe_ID", "model_1", { + "max_collisions": 300, "cell_ids": [22], "universe_ids": [77]}), + ("case_6_deposited_energy_threshold", "model_1", { + "max_collisions": 300, "deposited_E_threshold": 5.5e5}), + ("case_7_all_parameters_used_together", "model_1", { + "max_collisions": 300, + "reactions": ["elastic", 18, "(n,disappear)"], + "material_ids": [1, 11], + "universe_ids": [77], + "nuclides": ["U238", "U235", "H1", "U234"], + "cell_ids": [22, 33], + "deposited_E_threshold": 1e5}) + ], +) +def test_collision_track_several_cases( + folder, model_name, parameter, request +): + # Since for these tests the actual number of collisions recorded is < max_collisions, + # we can run them with 1 or 2 threads, and in history or event mode. + model = request.getfixturevalue(model_name) + model.settings.collision_track = parameter + harness = CollisionTrackTestHarness( + "statepoint.5.h5", model=model, workdir=folder + ) + harness.main() + + +@pytest.mark.skipif(config["event"], reason="Results from history-based mode.") +def test_collision_track_2threads(model_1, two_threads, single_process): + # This test checks that the `max_collisions` setting is honored: + # no collisions beyond the specified limit should be recorded. + # + # For the result to be reproducible, the number of threads and + # the transport mode (history vs. event) must remain fixed. + assert os.environ["OMP_NUM_THREADS"] == "2" + assert config["mpi_np"] == "1" + model_1.settings.collision_track = { + "max_collisions": 200 + } + harness = CollisionTrackTestHarness( + "statepoint.5.h5", model=model_1, workdir="case_8_2threads" + ) + harness.main() diff --git a/tests/testing_harness.py b/tests/testing_harness.py index 11ced5b38a7..1ad91b7a89f 100644 --- a/tests/testing_harness.py +++ b/tests/testing_harness.py @@ -68,7 +68,7 @@ def _run_openmc(self): if config['mpi']: mpi_args = [config['mpiexec'], '-n', config['mpi_np']] openmc.run(openmc_exec=config['exe'], mpi_args=mpi_args, - event_based=config['event']) + event_based=config['event']) else: openmc.run(openmc_exec=config['exe'], event_based=config['event']) @@ -305,9 +305,12 @@ def main(self): else: self.execute_test() - def execute_test(self): + def execute_test(self, change_dir=False): """Build input XMLs, run OpenMC, and verify correct results.""" + base_dir = os.getcwd() if change_dir else None try: + if change_dir: + os.chdir(self.workdir) self._build_inputs() inputs = self._get_inputs() self._write_inputs(inputs) @@ -319,10 +322,15 @@ def execute_test(self): self._compare_results() finally: self._cleanup() + if base_dir: + os.chdir(base_dir) - def update_results(self): + def update_results(self, change_dir=False): """Update results_true.dat and inputs_true.dat""" + base_dir = os.getcwd() if change_dir else None try: + if change_dir: + os.chdir(self.workdir) self._build_inputs() inputs = self._get_inputs() self._write_inputs(inputs) @@ -334,6 +342,8 @@ def update_results(self): self._overwrite_results() finally: self._cleanup() + if base_dir: + os.chdir(base_dir) def _build_inputs(self): """Write input XML files.""" @@ -371,7 +381,8 @@ def _cleanup(self): """Delete XMLs, statepoints, tally, and test files.""" super()._cleanup() output = ['materials.xml', 'geometry.xml', 'settings.xml', - 'tallies.xml', 'plots.xml', 'inputs_test.dat', 'model.xml'] + 'tallies.xml', 'plots.xml', 'inputs_test.dat', 'model.xml', + 'collision_track.h5', 'collision_track.mcpl'] for f in output: if os.path.exists(f): os.remove(f) @@ -389,6 +400,7 @@ class TolerantPyAPITestHarness(PyAPITestHarness): due to single precision usage (e.g., as in the random ray solver). """ + def _are_files_equal(self, actual_path, expected_path, tolerance): def isfloat(value): try: @@ -428,7 +440,8 @@ def compare_tokens(token1, token2): def _compare_results(self): """Make sure the current results agree with the reference.""" - compare = self._are_files_equal('results_test.dat', 'results_true.dat', 1e-6) + compare = self._are_files_equal( + 'results_test.dat', 'results_true.dat', 1e-6) if not compare: expected = open('results_true.dat').readlines() actual = open('results_test.dat').readlines() @@ -476,6 +489,7 @@ def _cleanup(self): class PlotTestHarness(TestHarness): """Specialized TestHarness for running OpenMC plotting tests.""" + def __init__(self, plot_names, voxel_convert_checks=[]): super().__init__(None) self._plot_names = plot_names @@ -523,3 +537,105 @@ def _get_results(self): outstr = sha512.hexdigest() return outstr + + +class CollisionTrackTestHarness(PyAPITestHarness): + def __init__(self, statepoint_name, model=None, inputs_true=None, workdir=None): + super().__init__(statepoint_name, model, inputs_true) + self.workdir = workdir + + def _test_output_created(self): + """Make sure collision_track.h5 has also been created.""" + super()._test_output_created() + if self._model.settings.collision_track: + assert os.path.exists( + "collision_track.h5" + ), "collision_track file has not been created." + + def _compare_output(self): + """Compare collision_track.h5 files.""" + if self._model.settings.collision_track: + collision_track_true = self._return_collision_track_data( + "collision_track_true.h5") + collision_track_test = self._return_collision_track_data( + "collision_track.h5") + np.testing.assert_allclose( + collision_track_true, collision_track_test, rtol=1e-07) + + def main(self): + """Accept commandline arguments and either run or update tests.""" + if config["build_inputs"]: + self.build_inputs() + elif config["update"]: + self.update_results(change_dir=True) + else: + self.execute_test(change_dir=True) + + def build_inputs(self): + """Build inputs.""" + base_dir = os.getcwd() + try: + os.chdir(self.workdir) + self._build_inputs() + finally: + os.chdir(base_dir) + + def _overwrite_results(self): + """Also add the 'collision_track.h5' file during overwriting.""" + super()._overwrite_results() + if os.path.exists("collision_track.h5"): + shutil.copyfile("collision_track.h5", "collision_track_true.h5") + + @staticmethod + def _return_collision_track_data(filepath): + """ + Read a collision_track file and return a sorted array composed + of flatten arrays of collision information. + + Parameters + ---------- + filepath : str + Path to the collision_track file + + Returns + ------- + data : np.array + Sorted array composed of flatten arrays of collision_track data for + each collision information + """ + data = [] + keys = [] + + # Read source file + source = openmc.read_collision_track_file(filepath) + for src in source: + r = src['r'] + u = src['u'] + e = src['E'] + de = src['dE'] + time = src['time'] + wgt = src['wgt'] + delayed_group = src['delayed_group'] + cell_id = src['cell_id'] + nuclide_id = src['nuclide_id'] + material_id = src['material_id'] + universe_id = src['universe_id'] + n_collision = src['n_collision'] + event_mt = src['event_mt'] + key = ( + f"{r[0]:.10e} {r[1]:.10e} {r[2]:.10e} {u[0]:.10e} {u[1]:.10e} {u[2]:.10e}" + f"{e:.10e} {de:.10e} {time:.10e} {wgt:.10e} {event_mt} {delayed_group} {cell_id}" + f"{nuclide_id} {material_id} {universe_id} {n_collision} " + ) + keys.append(key) + values = [*r, *u, e, de, time, wgt, event_mt, + delayed_group, cell_id, nuclide_id, material_id, + universe_id, n_collision] + assert len(values) == 17 + data.append(values) + + data = np.array(data) + keys = np.array(keys) + sorted_idx = np.argsort(keys, kind='stable') + + return data[sorted_idx] diff --git a/tests/unit_tests/test_collision_track.py b/tests/unit_tests/test_collision_track.py new file mode 100644 index 00000000000..9bc6a8c15f5 --- /dev/null +++ b/tests/unit_tests/test_collision_track.py @@ -0,0 +1,127 @@ +"""Test the 'collision_track' setting used to store particle information +during specified collision conditions in a file for a given simulation.""" + +import openmc +import pytest +import h5py +import numpy as np + +from tests.testing_harness import CollisionTrackTestHarness as ctt + + +@pytest.fixture(scope="module") +def geometry(): + """Simple hydrogen sphere geometry""" + openmc.reset_auto_ids() + material = openmc.Material(name="H1") + material.add_element("H", 1.0) + sphere = openmc.Sphere(r=1.0, boundary_type="vacuum") + cell = openmc.Cell(region=-sphere, fill=material) + return openmc.Geometry([cell]) + + +@pytest.mark.parametrize( + "parameter", + [ + {"max_collisions": 200}, + {"max_collisions": 200, "reactions": ["(n,disappear)"]}, + {"max_collisions": 200, "cell_ids": [1]}, + {"max_collisions": 200, "material_ids": [1]}, + {"max_collisions": 200, "universe_ids": [1]}, + {"max_collisions": 200, "nuclides": ["H1"]}, + {"max_collisions": 200, "deposited_E_threshold": 200000.0}, + {"max_collisions": 200, "mcpl": True} + + ], +) +def test_xml_serialization(parameter, run_in_tmpdir): + """Check that the different use cases can be written and read in XML.""" + settings = openmc.Settings() + settings.collision_track = parameter + settings.export_to_xml() + + read_settings = openmc.Settings.from_xml() + assert read_settings.collision_track == parameter + + +@pytest.fixture(scope="module") +def model(): + """Simple hydrogen sphere divided in two hemispheres + by a z-plane to form 2 cells.""" + openmc.reset_auto_ids() + model = openmc.Model() + + # Material + material = openmc.Material(name="H1") + material.add_element("H", 1.0) + + # Geometry + radius = 1.0 + sphere = openmc.Sphere(r=radius, boundary_type="reflective") + plane = openmc.ZPlane(0.0) + cell_1 = openmc.Cell(region=-sphere & -plane, fill=material, cell_id=1) + cell_2 = openmc.Cell(region=-sphere & +plane, fill=material, cell_id=2) + root = openmc.Universe(cells=[cell_1, cell_2]) + model.geometry = openmc.Geometry(root) + + # Settings + model.settings = openmc.Settings() + model.settings.run_mode = "fixed source" + model.settings.particles = 1 + model.settings.batches = 1 + model.settings.seed = 2 + + bounds = [-radius, -radius, -radius, radius, radius, radius] + distribution = openmc.stats.Box(bounds[:3], bounds[3:]) + model.settings.source = openmc.IndependentSource(space=distribution) + + return model + + +def test_particle_location(run_in_tmpdir, model): + """Test the location of particles with respected to the "cell_ids" + and the location x, y, z of the particle itself. the upper sphere will + have positive z component and the bottom sphere a negative z compnent. + + """ + model.settings.collision_track = { + "max_collisions": 200, + "reactions": ["elastic"], + "cell_ids": [1, 2] + } + model.run() + + with h5py.File("collision_track.h5", "r") as f: + source = f["collision_track_bank"] + + assert len(source) == 60 + + # We want to verify that the collisions happenening are in the right cells + # and the position of the particle is either positive or negative relative + # to the z plane. In this case, we track the position of the particle + # relative to the cell_id already set. + for point in source: + if point['cell_id'] == 1: + assert point['r'][2] < 0.0 # z component negative + elif point['cell_id'] == 2: + assert point["r"][2] > 0.0 # z component positive + else: + assert False + + +def test_format_similarity(run_in_tmpdir, model): + model.settings.collision_track = {"max_collisions": 200, "reactions": ['elastic'], + "cell_ids": [1, 2], "mcpl": False} + model.run() + data_h5 = ctt._return_collision_track_data('collision_track.h5') + + model.settings.collision_track["mcpl"] = True + model.run() + data_mcpl = ctt._return_collision_track_data('collision_track.mcpl') + + assert len(data_h5) == 60 + assert len(data_mcpl) == 60 + + np.testing.assert_allclose(data_h5, data_mcpl, rtol=1e-05) + # tolerance not that low due to the strings that is saved in MCPL, + # not enough precision! From 8d618716b55d92e892314d966ac75ffe5b19b7d7 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 13 Nov 2025 17:14:39 -0600 Subject: [PATCH 152/159] Avoid multiprocessing Pool when running depletion tests with MPI (#3633) --- tests/conftest.py | 21 +++++++++++++++++-- .../deplete_no_transport/test.py | 4 ++++ .../deplete_with_transport/test.py | 2 ++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index fa6718502d8..71dd5ebf5d4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -29,12 +29,29 @@ def run_in_tmpdir(tmpdir): yield finally: orig.chdir() - + @pytest.fixture(scope="module") def endf_data(): - return os.environ['OPENMC_ENDF_DATA'] + return os.environ['OPENMC_ENDF_DATA'] @pytest.fixture(scope='session', autouse=True) def resolve_paths(): with openmc.config.patch('resolve_paths', False): yield + + +@pytest.fixture(scope='session', autouse=True) +def disable_depletion_multiprocessing_under_mpi(): + """Fork-based depletion multiprocessing may deadlock if MPI is active.""" + if not regression_config['mpi']: + yield + return + + from openmc.deplete import pool + + original_setting = pool.USE_MULTIPROCESSING + pool.USE_MULTIPROCESSING = False + try: + yield + finally: + pool.USE_MULTIPROCESSING = original_setting diff --git a/tests/regression_tests/deplete_no_transport/test.py b/tests/regression_tests/deplete_no_transport/test.py index 63ae584e116..5550ad04814 100644 --- a/tests/regression_tests/deplete_no_transport/test.py +++ b/tests/regression_tests/deplete_no_transport/test.py @@ -76,6 +76,8 @@ def test_against_self(run_in_tmpdir, dt = [360] # single step # Perform simulation using the predictor algorithm + if config['mpi'] and multiproc: + pytest.skip("Multiprocessing depletion is disabled when MPI is enabled.") openmc.deplete.pool.USE_MULTIPROCESSING = multiproc openmc.deplete.PredictorIntegrator(op, dt, @@ -135,6 +137,8 @@ def test_against_coupled(run_in_tmpdir, dt = [dt] # single step # Perform simulation using the predictor algorithm + if config['mpi'] and multiproc: + pytest.skip("Multiprocessing depletion is disabled when MPI is enabled.") openmc.deplete.pool.USE_MULTIPROCESSING = multiproc openmc.deplete.PredictorIntegrator( op, dt, power=174, timestep_units=time_units).integrate() diff --git a/tests/regression_tests/deplete_with_transport/test.py b/tests/regression_tests/deplete_with_transport/test.py index 0f7ebf00f31..a2be22c5658 100644 --- a/tests/regression_tests/deplete_with_transport/test.py +++ b/tests/regression_tests/deplete_with_transport/test.py @@ -65,6 +65,8 @@ def test_full(run_in_tmpdir, problem, multiproc): power = 2.337e15*4*JOULE_PER_EV*1e6 # MeV/second cm from CASMO # Perform simulation using the predictor algorithm + if config['mpi'] and multiproc: + pytest.skip("Multiprocessing depletion is disabled when MPI is enabled.") openmc.deplete.pool.USE_MULTIPROCESSING = multiproc openmc.deplete.PredictorIntegrator(op, dt, power).integrate() From 7815d3a680de21362e7fec64c871ecbab1150421 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 13 Nov 2025 23:27:17 -0600 Subject: [PATCH 153/159] Fix typo in DAGMC lost particle test (#3634) --- tests/unit_tests/dagmc/test_lost_particles.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests/dagmc/test_lost_particles.py b/tests/unit_tests/dagmc/test_lost_particles.py index 502bd795e85..3a4166009df 100644 --- a/tests/unit_tests/dagmc/test_lost_particles.py +++ b/tests/unit_tests/dagmc/test_lost_particles.py @@ -70,12 +70,12 @@ def test_lost_particles(run_in_tmpdir, broken_dagmc_model): openmc.run() # run this again, but with the dagmc universe as the root unvierse + # to ensure that lost particles are still caught in this case for univ in broken_dagmc_model.geometry.get_all_universes().values(): if isinstance(univ, openmc.DAGMCUniverse): - broken_dagmc_model.geometry.root_unvierse = univ + broken_dagmc_model.geometry.root_universe = univ break broken_dagmc_model.export_to_xml() with pytest.raises(RuntimeError, match='Maximum number of lost particles has been reached.'): openmc.run() - From 5c63e0df21a25e2c9a25f8be6cfd23a09d6007b3 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 18 Nov 2025 17:57:12 -0600 Subject: [PATCH 154/159] Fix a few warnings, rename add_to_tallies_file (#3639) --- docs/source/usersguide/random_ray.rst | 2 +- openmc/deplete/chain.py | 13 +++--- openmc/mgxs/library.py | 18 +++++--- openmc/mgxs/mgxs.py | 4 +- openmc/model/model.py | 6 +-- openmc/tallies.py | 45 +++++++------------ .../mgxs_library_ce_to_mg/test.py | 2 +- .../mgxs_library_ce_to_mg_nuclides/test.py | 2 +- .../mgxs_library_condense/test.py | 2 +- .../mgxs_library_correction/test.py | 2 +- .../mgxs_library_distribcell/test.py | 2 +- .../mgxs_library_hdf5/test.py | 2 +- .../mgxs_library_histogram/test.py | 2 +- .../mgxs_library_mesh/test.py | 2 +- .../mgxs_library_no_nuclides/test.py | 2 +- .../mgxs_library_nuclides/test.py | 2 +- .../mgxs_library_specific_nuclides/test.py | 2 +- 17 files changed, 54 insertions(+), 56 deletions(-) diff --git a/docs/source/usersguide/random_ray.rst b/docs/source/usersguide/random_ray.rst index 138ae910c9a..d5d752a834d 100644 --- a/docs/source/usersguide/random_ray.rst +++ b/docs/source/usersguide/random_ray.rst @@ -765,7 +765,7 @@ energy decomposition:: # Create a "tallies.xml" file for the MGXS Library tallies = openmc.Tallies() - mgxs_lib.add_to_tallies_file(tallies, merge=True) + mgxs_lib.add_to_tallies(tallies, merge=True) # Export tallies.export_to_xml() diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index f34416d5618..873d7ca8921 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -630,7 +630,7 @@ def form_matrix(self, rates, fission_yields=None): n = len(self) - # we accumulate indices and value entries for everything and create the matrix + # we accumulate indices and value entries for everything and create the matrix # in one step at the end to avoid expensive index checks scipy otherwise does. rows, cols, vals = [], [], [] def setval(i, j, val): @@ -716,14 +716,17 @@ def setval(i, j, val): return sp.csc_matrix((vals, (rows, cols)), shape=(n, n)) def add_redox_term(self, matrix, buffer, oxidation_states): - """Adds a redox term to the depletion matrix from data contained in + r"""Adds a redox term to the depletion matrix from data contained in the matrix itself and a few user-inputs. The redox term to add to the buffer nuclide :math:`N_j` can be written - as: :math:`\frac{dN_j(t)}{dt} = - \cdots - \frac{1}{OS_j}\sum_i N_i a_{ij} \cdot OS_i ` + as: - where :math:`OS` is the oxidation states vector and `a_{ij}` the + .. math:: + \frac{dN_j(t)}{dt} = \cdots - \frac{1}{OS_j}\sum_i N_i a_{ij} + \cdot OS_i + + where :math:`OS` is the oxidation states vector and :math:`a_{ij}` the corresponding term in the Bateman matrix. Parameters diff --git a/openmc/mgxs/library.py b/openmc/mgxs/library.py index f782cbe9e52..b476de90202 100644 --- a/openmc/mgxs/library.py +++ b/openmc/mgxs/library.py @@ -556,14 +556,14 @@ def build_library(self): self.all_mgxs[domain.id][mgxs_type] = mgxs - def add_to_tallies_file(self, tallies_file, merge=True): - """Add all tallies from all MGXS objects to a tallies file. + def add_to_tallies(self, tallies, merge=True): + """Add tallies from all MGXS objects to a tallies object. NOTE: This assumes that :meth:`Library.build_library` has been called Parameters ---------- - tallies_file : openmc.Tallies + tallies : openmc.Tallies A Tallies collection to add each MGXS' tallies to generate a 'tallies.xml' input file for OpenMC merge : bool @@ -572,7 +572,7 @@ def add_to_tallies_file(self, tallies_file, merge=True): """ - cv.check_type('tallies_file', tallies_file, openmc.Tallies) + cv.check_type('tallies', tallies, openmc.Tallies) # Add tallies from each MGXS for each domain and mgxs type for domain in self.domains: @@ -587,7 +587,15 @@ def add_to_tallies_file(self, tallies_file, merge=True): = list(range(1, self.num_delayed_groups + 1)) for tally in mgxs.tallies.values(): - tallies_file.append(tally, merge=merge) + tallies.append(tally, merge=merge) + + def add_to_tallies_file(self, tallies_file, merge=True): + warn( + "The Library.add_to_tallies_file(...) method has been renamed to" + "add_to_tallies(...) and will be removed in a future version of " + "OpenMC.", FutureWarning + ) + self.add_to_tallies(tallies_file, merge=merge) def load_from_statepoint(self, statepoint): """Extracts tallies in an OpenMC StatePoint with the data needed to diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index 533ab0ad325..f621db092fc 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -2127,8 +2127,8 @@ def get_pandas_dataframe(self, groups='all', nuclides='all', df['std. dev.'] /= np.tile(densities, tile_factor) # Replace NaNs by zeros (happens if nuclide density is zero) - df['mean'].replace(np.nan, 0.0, inplace=True) - df['std. dev.'].replace(np.nan, 0.0, inplace=True) + df['mean'] = df['mean'].replace(np.nan, 0.0) + df['std. dev.'] = df['std. dev.'].replace(np.nan, 0.0) # Sort the dataframe by domain type id (e.g., distribcell id) and # energy groups such that data is from fast to thermal diff --git a/openmc/model/model.py b/openmc/model/model.py index 64294c23cbb..10e5cbfc745 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -1791,7 +1791,7 @@ def _generate_infinite_medium_mgxs( mgxs_lib.build_library() # Create a "tallies.xml" file for the MGXS Library - mgxs_lib.add_to_tallies_file(model.tallies, merge=True) + mgxs_lib.add_to_tallies(model.tallies, merge=True) # Run statepoint_filename = model.run(cwd=directory) @@ -1980,7 +1980,7 @@ def _generate_stochastic_slab_mgxs( mgxs_lib.build_library() # Create a "tallies.xml" file for the MGXS Library - mgxs_lib.add_to_tallies_file(model.tallies, merge=True) + mgxs_lib.add_to_tallies(model.tallies, merge=True) # Run statepoint_filename = model.run(cwd=directory) @@ -2075,7 +2075,7 @@ def _generate_material_wise_mgxs( mgxs_lib.build_library() # Create a "tallies.xml" file for the MGXS Library - mgxs_lib.add_to_tallies_file(model.tallies, merge=True) + mgxs_lib.add_to_tallies(model.tallies, merge=True) # Run statepoint_filename = model.run(cwd=directory) diff --git a/openmc/tallies.py b/openmc/tallies.py index 25ec29a5850..add35657982 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -738,13 +738,9 @@ def skewtest(self, alternative: str = "two-sided"): Notes ----- - This test is based on D'Agostino and Pearson's test [1]_. The test - requires at least 8 realizations to produce valid results. - - References - ---------- - .. [1] D'Agostino, R. B. (1971), "An omnibus test of normality for - moderate and large sample size", Biometrika, 58, 341-348 + This test is based on `D'Agostino and Pearson's test + `_. The test requires at least + 8 realizations to produce valid results. """ n = self.num_realizations @@ -788,8 +784,8 @@ def kurtosistest(self, alternative: str = "two-sided"): Parameters ---------- alternative : {'two-sided', 'less', 'greater'}, optional - Defines the alternative hypothesis. Default is 'two-sided'. - The following options are available: + Defines the alternative hypothesis. Default is 'two-sided'. The + following options are available: * 'two-sided': the kurtosis of the distribution is different from that of the normal distribution @@ -813,14 +809,9 @@ def kurtosistest(self, alternative: str = "two-sided"): Notes ----- - This test is based on D'Agostino and Pearson's test [1]_. The test - is typically recommended for at least 20 realizations to produce - valid results. - - References - ---------- - .. [1] D'Agostino, R. B. (1971), "An omnibus test of normality for - moderate and large sample size", Biometrika, 58, 341-348 + This test is based on `D'Agostino and Pearson's test + `_. The test is typically + recommended for at least 20 realizations to produce valid results. """ n = self.num_realizations @@ -855,9 +846,9 @@ def kurtosistest(self, alternative: str = "two-sided"): def normaltest(self, alternative: str = "two-sided"): """Perform D'Agostino and Pearson's omnibus test for normality. - This method tests the null hypothesis that a sample comes from a - normal distribution. It combines skewness and kurtosis to produce an - omnibus test of normality. + This method tests the null hypothesis that a sample comes from a normal + distribution. It combines skewness and kurtosis to produce an omnibus + test of normality. Parameters ---------- @@ -886,23 +877,19 @@ def normaltest(self, alternative: str = "two-sided"): Notes ----- This test combines a test for skewness and a test for kurtosis to - produce an omnibus test [1]_. The test statistic is: + produce an `omnibus test `_. + The test statistic is: .. math:: K^2 = Z_1^2 + Z_2^2 - where :math:`Z_1` is the z-score from the skewness test and - :math:`Z_2` is the z-score from the kurtosis test. This statistic - follows a chi-square distribution with 2 degrees of freedom. + where :math:`Z_1` is the z-score from the skewness test and :math:`Z_2` + is the z-score from the kurtosis test. This statistic follows a + chi-square distribution with 2 degrees of freedom. The test requires at least 20 realizations to produce valid results. - References - ---------- - .. [1] D'Agostino, R. B. and Pearson, E. S. (1973), "Tests for - departure from normality", Biometrika, 60, 613-622 - """ n = self.num_realizations if n < 20: diff --git a/tests/regression_tests/mgxs_library_ce_to_mg/test.py b/tests/regression_tests/mgxs_library_ce_to_mg/test.py index 48a715997ae..075167f5884 100644 --- a/tests/regression_tests/mgxs_library_ce_to_mg/test.py +++ b/tests/regression_tests/mgxs_library_ce_to_mg/test.py @@ -28,7 +28,7 @@ def __init__(self, *args, **kwargs): self.mgxs_lib.build_library() # Initialize a tallies file - self.mgxs_lib.add_to_tallies_file(self._model.tallies, merge=False) + self.mgxs_lib.add_to_tallies(self._model.tallies, merge=False) def _run_openmc(self): # Initial run diff --git a/tests/regression_tests/mgxs_library_ce_to_mg_nuclides/test.py b/tests/regression_tests/mgxs_library_ce_to_mg_nuclides/test.py index a77ad242965..489105f8f54 100644 --- a/tests/regression_tests/mgxs_library_ce_to_mg_nuclides/test.py +++ b/tests/regression_tests/mgxs_library_ce_to_mg_nuclides/test.py @@ -28,7 +28,7 @@ def __init__(self, *args, **kwargs): self.mgxs_lib.build_library() # Initialize a tallies file - self.mgxs_lib.add_to_tallies_file(self._model.tallies, merge=False) + self.mgxs_lib.add_to_tallies(self._model.tallies, merge=False) def _run_openmc(self): # Initial run diff --git a/tests/regression_tests/mgxs_library_condense/test.py b/tests/regression_tests/mgxs_library_condense/test.py index a7e60617f08..bbc4c11bfa9 100644 --- a/tests/regression_tests/mgxs_library_condense/test.py +++ b/tests/regression_tests/mgxs_library_condense/test.py @@ -36,7 +36,7 @@ def __init__(self, *args, **kwargs): self.mgxs_lib.build_library() # Add tallies - self.mgxs_lib.add_to_tallies_file(self._model.tallies, merge=False) + self.mgxs_lib.add_to_tallies(self._model.tallies, merge=False) def _get_results(self, hash_output=False): """Digest info in the statepoint and return as a string.""" diff --git a/tests/regression_tests/mgxs_library_correction/test.py b/tests/regression_tests/mgxs_library_correction/test.py index 05eedfef824..64e638e442f 100644 --- a/tests/regression_tests/mgxs_library_correction/test.py +++ b/tests/regression_tests/mgxs_library_correction/test.py @@ -29,7 +29,7 @@ def __init__(self, *args, **kwargs): self.mgxs_lib.build_library() # Add tallies - self.mgxs_lib.add_to_tallies_file(self._model.tallies, merge=False) + self.mgxs_lib.add_to_tallies(self._model.tallies, merge=False) def _get_results(self, hash_output=False): """Digest info in the statepoint and return as a string.""" diff --git a/tests/regression_tests/mgxs_library_distribcell/test.py b/tests/regression_tests/mgxs_library_distribcell/test.py index fd6c8e93866..464b309c002 100644 --- a/tests/regression_tests/mgxs_library_distribcell/test.py +++ b/tests/regression_tests/mgxs_library_distribcell/test.py @@ -36,7 +36,7 @@ def __init__(self, *args, **kwargs): self.mgxs_lib.build_library() # Add tallies - self.mgxs_lib.add_to_tallies_file(self._model.tallies, merge=False) + self.mgxs_lib.add_to_tallies(self._model.tallies, merge=False) self._model.tallies.export_to_xml() def _get_results(self, hash_output=False): diff --git a/tests/regression_tests/mgxs_library_hdf5/test.py b/tests/regression_tests/mgxs_library_hdf5/test.py index 06625c25f9e..4fb4bf09369 100644 --- a/tests/regression_tests/mgxs_library_hdf5/test.py +++ b/tests/regression_tests/mgxs_library_hdf5/test.py @@ -40,7 +40,7 @@ def __init__(self, *args, **kwargs): self.mgxs_lib.build_library() # Add tallies - self.mgxs_lib.add_to_tallies_file(self._model.tallies, merge=False) + self.mgxs_lib.add_to_tallies(self._model.tallies, merge=False) def _get_results(self, hash_output=False): """Digest info in the statepoint and return as a string.""" diff --git a/tests/regression_tests/mgxs_library_histogram/test.py b/tests/regression_tests/mgxs_library_histogram/test.py index b9905910abc..42fc1957a6b 100644 --- a/tests/regression_tests/mgxs_library_histogram/test.py +++ b/tests/regression_tests/mgxs_library_histogram/test.py @@ -30,7 +30,7 @@ def __init__(self, *args, **kwargs): self.mgxs_lib.build_library() # Add tallies - self.mgxs_lib.add_to_tallies_file(self._model.tallies, merge=False) + self.mgxs_lib.add_to_tallies(self._model.tallies, merge=False) def _get_results(self, hash_output=False): """Digest info in the statepoint and return as a string.""" diff --git a/tests/regression_tests/mgxs_library_mesh/test.py b/tests/regression_tests/mgxs_library_mesh/test.py index 89c68a75a33..c1a5980b5d1 100644 --- a/tests/regression_tests/mgxs_library_mesh/test.py +++ b/tests/regression_tests/mgxs_library_mesh/test.py @@ -57,7 +57,7 @@ def model(): model.mgxs_lib.build_library() # Add tallies - model.mgxs_lib.add_to_tallies_file(model.tallies, merge=False) + model.mgxs_lib.add_to_tallies(model.tallies, merge=False) return model diff --git a/tests/regression_tests/mgxs_library_no_nuclides/test.py b/tests/regression_tests/mgxs_library_no_nuclides/test.py index af14a5dc8f2..a02086af3ec 100644 --- a/tests/regression_tests/mgxs_library_no_nuclides/test.py +++ b/tests/regression_tests/mgxs_library_no_nuclides/test.py @@ -39,7 +39,7 @@ def __init__(self, *args, **kwargs): self.mgxs_lib.build_library() # Add tallies - self.mgxs_lib.add_to_tallies_file(self._model.tallies, merge=False) + self.mgxs_lib.add_to_tallies(self._model.tallies, merge=False) def _get_results(self, hash_output=False): """Digest info in the statepoint and return as a string.""" diff --git a/tests/regression_tests/mgxs_library_nuclides/test.py b/tests/regression_tests/mgxs_library_nuclides/test.py index 8a7673565e5..a10070358ad 100644 --- a/tests/regression_tests/mgxs_library_nuclides/test.py +++ b/tests/regression_tests/mgxs_library_nuclides/test.py @@ -36,7 +36,7 @@ def __init__(self, *args, **kwargs): self.mgxs_lib.build_library() # Add tallies - self.mgxs_lib.add_to_tallies_file(self._model.tallies, merge=False) + self.mgxs_lib.add_to_tallies(self._model.tallies, merge=False) def _get_results(self, hash_output=True): """Digest info in the statepoint and return as a string.""" diff --git a/tests/regression_tests/mgxs_library_specific_nuclides/test.py b/tests/regression_tests/mgxs_library_specific_nuclides/test.py index 61910e539e8..0ccbb83bdbe 100644 --- a/tests/regression_tests/mgxs_library_specific_nuclides/test.py +++ b/tests/regression_tests/mgxs_library_specific_nuclides/test.py @@ -37,7 +37,7 @@ def __init__(self, *args, **kwargs): self.mgxs_lib.build_library() # Add tallies - self.mgxs_lib.add_to_tallies_file(self._model.tallies, merge=True) + self.mgxs_lib.add_to_tallies(self._model.tallies, merge=True) def _get_results(self, hash_output=True): """Digest info in the statepoint and return as a string.""" From 028f4404485407b01a31c44f30249852601a29c8 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 19 Nov 2025 11:19:27 -0600 Subject: [PATCH 155/159] Support MPI parallelism in R2SManager (#3632) --- openmc/deplete/microxs.py | 31 +++++++++++++------------ openmc/deplete/r2s.py | 48 ++++++++++++++++++++++++--------------- openmc/lib/core.py | 45 ++++++++++++++++++++++++++++-------- 3 files changed, 82 insertions(+), 42 deletions(-) diff --git a/openmc/deplete/microxs.py b/openmc/deplete/microxs.py index d7624955e8b..f4bdc679535 100644 --- a/openmc/deplete/microxs.py +++ b/openmc/deplete/microxs.py @@ -170,13 +170,14 @@ def get_microxs_and_flux( # Reinitialize with tallies openmc.lib.init(intracomm=comm) - # create temporary run with TemporaryDirectory() as temp_dir: - if run_kwargs is None: - run_kwargs = {} - else: - run_kwargs = dict(run_kwargs) - run_kwargs.setdefault('cwd', temp_dir) + # Indicate to run in temporary directory unless being executed through + # openmc.lib, in which case we don't need to specify the cwd + run_kwargs = dict(run_kwargs) if run_kwargs else {} + if not openmc.lib.is_initialized: + run_kwargs.setdefault('cwd', temp_dir) + + # Run transport simulation statepoint_path = model.run(**run_kwargs) if comm.rank == 0: @@ -189,15 +190,18 @@ def get_microxs_and_flux( if path_input is not None: model.export_to_model_xml(path_input) - with StatePoint(statepoint_path) as sp: - if reaction_rate_mode == 'direct': - rr_tally = sp.tallies[rr_tally.id] - rr_tally._read_results() - flux_tally = sp.tallies[flux_tally.id] - flux_tally._read_results() + # Broadcast updated statepoint path to all ranks + statepoint_path = comm.bcast(statepoint_path) + + # Read in tally results (on all ranks) + with StatePoint(statepoint_path) as sp: + if reaction_rate_mode == 'direct': + rr_tally = sp.tallies[rr_tally.id] + rr_tally._read_results() + flux_tally = sp.tallies[flux_tally.id] + flux_tally._read_results() # Get flux values and make energy groups last dimension - flux_tally = comm.bcast(flux_tally) flux = flux_tally.get_reshaped_data() # (domains, groups, 1, 1) flux = np.moveaxis(flux, 1, -1) # (domains, 1, 1, groups) @@ -206,7 +210,6 @@ def get_microxs_and_flux( if reaction_rate_mode == 'direct': # Get reaction rates - rr_tally = comm.bcast(rr_tally) reaction_rates = rr_tally.get_reshaped_data() # (domains, groups, nuclides, reactions) # Make energy groups last dimension diff --git a/openmc/deplete/r2s.py b/openmc/deplete/r2s.py index 7b5deddc23c..7f3e94edd96 100644 --- a/openmc/deplete/r2s.py +++ b/openmc/deplete/r2s.py @@ -11,6 +11,9 @@ from .microxs import get_microxs_and_flux, write_microxs_hdf5, read_microxs_hdf5 from .results import Results from ..checkvalue import PathLike +from ..mpi import comm +from openmc.lib import TemporarySession +from openmc.utility_funcs import change_directory def get_activation_materials( @@ -199,8 +202,10 @@ def run( """ if output_dir is None: + # Create timestamped output directory and broadcast to all ranks for + # consistency (different ranks may have slightly different times) stamp = datetime.now().strftime('%Y-%m-%dT%H-%M-%S') - output_dir = Path(f'r2s_{stamp}') + output_dir = Path(comm.bcast(f'r2s_{stamp}')) # Set run_kwargs for the neutron transport step if micro_kwargs is None: @@ -257,18 +262,19 @@ def step1_neutron_transport( """ - output_dir = Path(output_dir) + output_dir = Path(output_dir).resolve() output_dir.mkdir(parents=True, exist_ok=True) if self.method == 'mesh-based': # Compute material volume fractions on the mesh if mat_vol_kwargs is None: mat_vol_kwargs = {} - self.results['mesh_material_volumes'] = mmv = \ - self.domains.material_volumes(self.neutron_model, **mat_vol_kwargs) + self.results['mesh_material_volumes'] = mmv = comm.bcast( + self.domains.material_volumes(self.neutron_model, **mat_vol_kwargs)) # Save results to file - mmv.save(output_dir / 'mesh_material_volumes.npz') + if comm.rank == 0: + mmv.save(output_dir / 'mesh_material_volumes.npz') # Create mesh-material filter based on what combos were found domains = openmc.MeshMaterialFilter.from_volumes(self.domains, mmv) @@ -299,13 +305,16 @@ def step1_neutron_transport( micro_kwargs.setdefault('path_statepoint', output_dir / 'statepoint.h5') micro_kwargs.setdefault('path_input', output_dir / 'model.xml') - # Run neutron transport and get fluxes and micros - self.results['fluxes'], self.results['micros'] = get_microxs_and_flux( - self.neutron_model, domains, **micro_kwargs) + # Run neutron transport and get fluxes and micros. Run via openmc.lib to + # maintain a consistent parallelism strategy with the activation step. + with TemporarySession(): + self.results['fluxes'], self.results['micros'] = get_microxs_and_flux( + self.neutron_model, domains, **micro_kwargs) # Save flux and micros to file - np.save(output_dir / 'fluxes.npy', self.results['fluxes']) - write_microxs_hdf5(self.results['micros'], output_dir / 'micros.h5') + if comm.rank == 0: + np.save(output_dir / 'fluxes.npy', self.results['fluxes']) + write_microxs_hdf5(self.results['micros'], output_dir / 'micros.h5') def step2_activation( self, @@ -457,15 +466,17 @@ def step3_photon_transport( # photon model if it is different from the neutron model to account for # potential material changes if self.method == 'mesh-based' and different_photon_model: - self.results['mesh_material_volumes_photon'] = photon_mmv = \ - self.domains.material_volumes(self.photon_model, **mat_vol_kwargs) + self.results['mesh_material_volumes_photon'] = photon_mmv = comm.bcast( + self.domains.material_volumes(self.photon_model, **mat_vol_kwargs)) # Save photon MMV results to file - photon_mmv.save(output_dir / 'mesh_material_volumes.npz') + if comm.rank == 0: + photon_mmv.save(output_dir / 'mesh_material_volumes.npz') - tally_ids = [tally.id for tally in self.photon_model.tallies] - with open(output_dir / 'tally_ids.json', 'w') as f: - json.dump(tally_ids, f) + if comm.rank == 0: + tally_ids = [tally.id for tally in self.photon_model.tallies] + with open(output_dir / 'tally_ids.json', 'w') as f: + json.dump(tally_ids, f) self.results['photon_tallies'] = {} @@ -514,8 +525,9 @@ def step3_photon_transport( time_index = len(self.results['depletion_results']) + time_index # Run photon transport calculation - run_kwargs['cwd'] = Path(output_dir) / f'time_{time_index}' - statepoint_path = self.photon_model.run(**run_kwargs) + photon_dir = Path(output_dir) / f'time_{time_index}' + with TemporarySession(self.photon_model, cwd=photon_dir): + statepoint_path = self.photon_model.run(**run_kwargs) # Store tally results with openmc.StatePoint(statepoint_path) as sp: diff --git a/openmc/lib/core.py b/openmc/lib/core.py index 9f8db69d577..cfccecef2a9 100644 --- a/openmc/lib/core.py +++ b/openmc/lib/core.py @@ -13,6 +13,7 @@ from . import _dll from .error import _error_handler +from ..mpi import comm from openmc.checkvalue import PathLike import openmc.lib import openmc @@ -632,6 +633,9 @@ class TemporarySession: model : openmc.Model, optional OpenMC model to use for the session. If None, a minimal working model is created. + cwd : PathLike, optional + Working directory in which to run OpenMC. If None, a temporary directory + is created and deleted automatically. **init_kwargs Keyword arguments to pass to :func:`openmc.lib.init`. @@ -639,10 +643,13 @@ class TemporarySession: ---------- model : openmc.Model The OpenMC model used for the session. + comm : mpi4py.MPI.Intracomm + The MPI intracommunicator used for the session. """ - def __init__(self, model=None, **init_kwargs): - self.init_kwargs = init_kwargs + def __init__(self, model=None, cwd=None, **init_kwargs): + self.init_kwargs = dict(init_kwargs) + self.cwd = cwd if model is None: surf = openmc.Sphere(boundary_type="vacuum") cell = openmc.Cell(region=-surf) @@ -652,6 +659,10 @@ def __init__(self, model=None, **init_kwargs): particles=1, batches=1, output={'summary': False}) self.model = model + # Determine MPI intercommunicator + self.init_kwargs.setdefault('intracomm', comm) + self.comm = self.init_kwargs['intracomm'] + def __enter__(self): """Initialize the OpenMC library in a temporary directory.""" # If already initialized, the context manager is a no-op @@ -662,14 +673,24 @@ def __enter__(self): # Store original working directory self.orig_dir = Path.cwd() - # Set up temporary directory - self.tmp_dir = TemporaryDirectory() - working_dir = Path(self.tmp_dir.name) - working_dir.mkdir(parents=True, exist_ok=True) - os.chdir(working_dir) + if self.cwd is None: + # Set up temporary directory on rank 0 + if self.comm.rank == 0: + self._tmp_dir = TemporaryDirectory() + self.cwd = self._tmp_dir.name + + # Broadcast the path so that all ranks use the same directory + self.cwd = self.comm.bcast(self.cwd) - # Export model and initialize OpenMC - self.model.export_to_model_xml() + # Create and change to specified directory + self.cwd = Path(self.cwd) + self.cwd.mkdir(parents=True, exist_ok=True) + os.chdir(self.cwd) + + # Export model on first rank and initialize OpenMC + if self.comm.rank == 0: + self.model.export_to_model_xml() + self.comm.barrier() openmc.lib.init(**self.init_kwargs) return self @@ -683,7 +704,11 @@ def __exit__(self, exc_type, exc_value, traceback): finalize() finally: os.chdir(self.orig_dir) - self.tmp_dir.cleanup() + + # Make sure all ranks have finalized before deleting temporary dir + self.comm.barrier() + if hasattr(self, '_tmp_dir'): + self._tmp_dir.cleanup() class _DLLGlobal: From f544d02e499248281050ef2272d042c4a7933858 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 19 Nov 2025 11:26:57 -0600 Subject: [PATCH 156/159] Don't write reaction rates in depletion results by default, remove per-stage data for multistage integrators (#3609) --- docs/source/io_formats/depletion_results.rst | 26 +-- openmc/deplete/abc.py | 157 ++++++++----- openmc/deplete/integrators.py | 85 +++---- openmc/deplete/results.py | 8 +- openmc/deplete/stepresult.py | 222 +++++++++++-------- tests/unit_tests/test_deplete_continue.py | 8 +- tests/unit_tests/test_deplete_integrator.py | 93 +++++--- tests/unit_tests/test_deplete_restart.py | 14 +- 8 files changed, 338 insertions(+), 275 deletions(-) diff --git a/docs/source/io_formats/depletion_results.rst b/docs/source/io_formats/depletion_results.rst index 7035fc9c9ee..b2a726dad04 100644 --- a/docs/source/io_formats/depletion_results.rst +++ b/docs/source/io_formats/depletion_results.rst @@ -4,7 +4,7 @@ Depletion Results File Format ============================= -The current version of the depletion results file format is 1.1. +The current version of the depletion results file format is 1.2. **/** @@ -12,22 +12,20 @@ The current version of the depletion results file format is 1.1. - **version** (*int[2]*) -- Major and minor version of the statepoint file format. -:Datasets: - **eigenvalues** (*double[][][2]*) -- k-eigenvalues at each - time/stage. This array has shape (number of timesteps, number of - stages, value). The last axis contains the eigenvalue and the - associated uncertainty - - **number** (*double[][][][]*) -- Total number of atoms. This array - has shape (number of timesteps, number of stages, number of +:Datasets: - **eigenvalues** (*double[][2]*) -- k-eigenvalues at each timestep. + This array has shape (number of timesteps, 2). The second axis + contains the eigenvalue and its associated uncertainty. + - **number** (*double[][][]*) -- Total number of atoms at each + timestep. This array has shape (number of timesteps, number of materials, number of nuclides). - - **reaction rates** (*double[][][][][]*) -- Reaction rates used to - build depletion matrices. This array has shape (number of - timesteps, number of stages, number of materials, number of - nuclides, number of reactions). + - **reaction rates** (*double[][][][]*) -- Reaction rates at each + timestep. This array has shape (number of timesteps, number of + materials, number of nuclides, number of reactions). Only stored if + write_rates=True. - **time** (*double[][2]*) -- Time in [s] at beginning/end of each step. - - **source_rate** (*double[][]*) -- Power in [W] or source rate in - [neutron/sec]. This array has shape (number of timesteps, number - of stages). + - **source_rate** (*double[]*) -- Power in [W] or source rate in + [neutron/sec] for each timestep. - **depletion time** (*double[]*) -- Average process time in [s] spent depleting a material across all burnable materials and, if applicable, MPI processes. diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index fb18d86afc4..32f468306db 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -631,17 +631,7 @@ def __init__( solver: str = "cram48", continue_timesteps: bool = False, ): - # Check number of stages previously used - if operator.prev_res is not None: - res = operator.prev_res[-1] - if res.data.shape[0] != self._num_stages: - raise ValueError( - "{} incompatible with previous restart calculation. " - "Previous scheme used {} intermediate solutions, while " - "this uses {}".format( - self.__class__.__name__, res.data.shape[0], - self._num_stages)) - elif continue_timesteps: + if continue_timesteps and operator.prev_res is None: raise ValueError("Continuation run requires passing prev_results.") self.operator = operator self.chain = operator.chain @@ -775,12 +765,8 @@ def __call__( ------- proc_time : float Time spent in CRAM routines for all materials in [s] - n_list : list of list of numpy.ndarray - Concentrations at each of the intermediate points with - the final concentration as the last element - op_results : list of openmc.deplete.OperatorResult - Eigenvalue and reaction rates from intermediate transport - simulations + n_end : list of numpy.ndarray + Concentrations at end of timestep """ @property @@ -811,9 +797,9 @@ def _get_bos_data_from_restart(self, source_rate, bos_conc): """Get beginning of step concentrations, reaction rates from restart""" res = self.operator.prev_res[-1] # Depletion methods expect list of arrays - bos_conc = list(res.data[0]) - rates = res.rates[0] - k = ufloat(res.k[0, 0], res.k[0, 1]) + bos_conc = list(res.data) + rates = res.rates + k = ufloat(res.k[0], res.k[1]) if res.source_rate != 0.0: # Scale reaction rates by ratio of source rates @@ -855,7 +841,8 @@ def integrate( self, final_step: bool = True, output: bool = True, - path: PathLike = 'depletion_results.h5' + path: PathLike = 'depletion_results.h5', + write_rates: bool = False ): """Perform the entire depletion process across all steps @@ -874,6 +861,11 @@ def integrate( Path to file to write. Defaults to 'depletion_results.h5'. .. versionadded:: 0.15.0 + write_rates : bool, optional + Whether reaction rates should be written to the results file for + each step. Defaults to ``False`` to reduce file size. + + .. versionadded:: 0.15.3 """ with change_directory(self.operator.output_dir): n = self.operator.initial_condition() @@ -890,18 +882,22 @@ def integrate( n, res = self._get_bos_data_from_restart(source_rate, n) # Solve Bateman equations over time interval - proc_time, n_list, res_list = self(n, res.rates, dt, source_rate, i) - - # Insert BOS concentration, transport results - n_list.insert(0, n) - res_list.insert(0, res) - - # Remove actual EOS concentration for next step - n = n_list.pop() - - StepResult.save(self.operator, n_list, res_list, [t, t + dt], - source_rate, self._i_res + i, proc_time, path) + proc_time, n_end = self(n, res.rates, dt, source_rate, i) + + StepResult.save( + self.operator, + n, + res, + [t, t + dt], + source_rate, + self._i_res + i, + proc_time, + write_rates=write_rates, + path=path + ) + # Update for next step + n = n_end t += dt # Final simulation -- in the case that final_step is False, a zero @@ -910,9 +906,18 @@ def integrate( # solve) if output and final_step and comm.rank == 0: print(f"[openmc.deplete] t={t} (final operator evaluation)") - res_list = [self.operator(n, source_rate if final_step else 0.0)] - StepResult.save(self.operator, [n], res_list, [t, t], - source_rate, self._i_res + len(self), proc_time, path) + res_final = self.operator(n, source_rate if final_step else 0.0) + StepResult.save( + self.operator, + n, + res_final, + [t, t], + source_rate, + self._i_res + len(self), + proc_time, + write_rates=write_rates, + path=path + ) self.operator.write_bos_data(len(self) + self._i_res) self.operator.finalize() @@ -1171,10 +1176,40 @@ def _get_bos_data_from_operator(self, step_index, step_power, n_bos): self.operator.settings.particles //= self.n_steps return inherited + @abstractmethod + def __call__(self, n, rates, dt, source_rate, i): + """Perform the integration across one time step + + Parameters + ---------- + n : list of numpy.ndarray + List of atom number arrays for each material. Each array has + shape ``(n_nucs,)`` where ``n_nucs`` is the number of nuclides + rates : openmc.deplete.ReactionRates + Reaction rates (from transport operator) + dt : float + Time step in [s] + source_rate : float + Power in [W] or source rate in [neutron/sec] + i : int + Current time step index + + Returns + ------- + proc_time : float + Time spent in transport simulation + n_end : list of numpy.ndarray + Updated atom number densities for each material + op_result : OperatorResult + Eigenvalue and reaction rates resulting from transport simulation + + """ + def integrate( self, output: bool = True, - path: PathLike = "depletion_results.h5" + path: PathLike = "depletion_results.h5", + write_rates: bool = False ): """Perform the entire depletion process across all steps @@ -1186,11 +1221,17 @@ def integrate( Path to file to write. Defaults to 'depletion_results.h5'. .. versionadded:: 0.15.0 + write_rates : bool, optional + Whether reaction rates should be written to the results file for + each step. Defaults to ``False`` to reduce file size. + + .. versionadded:: 0.15.3 """ with change_directory(self.operator.output_dir): n = self.operator.initial_condition() t, self._i_res = self._get_start_data() + res_end = None # Will be set in first iteration for i, (dt, p) in enumerate(self): if output: print(f"[openmc.deplete] t={t} s, dt={dt} s, source={p}") @@ -1200,28 +1241,38 @@ def integrate( n, res = self._get_bos_data_from_operator(i, p, n) else: n, res = self._get_bos_data_from_restart(p, n) - else: - # Pull rates, k from previous iteration w/o - # re-running transport - res = res_list[-1] # defined in previous i iteration - - proc_time, n_list, res_list = self(n, res.rates, dt, p, i) - # Insert BOS concentration, transport results - n_list.insert(0, n) - res_list.insert(0, res) - - # Remove actual EOS concentration for next step - n = n_list.pop() - - StepResult.save(self.operator, n_list, res_list, [t, t + dt], - p, self._i_res + i, proc_time, path) + proc_time, n_end, res_end = self(n, res.rates, dt, p, i) + + StepResult.save( + self.operator, + n, + res, + [t, t + dt], + p, + self._i_res + i, + proc_time, + write_rates=write_rates, + path=path + ) + # Update for next step + n = n_end + res = res_end t += dt # No final simulation for SIE, use last iteration results - StepResult.save(self.operator, [n], [res_list[-1]], [t, t], - p, self._i_res + len(self), proc_time, path) + StepResult.save( + self.operator, + n, + res_end, + [t, t], + p, + self._i_res + len(self), + proc_time, + write_rates=write_rates, + path=path + ) self.operator.write_bos_data(self._i_res + len(self)) self.operator.finalize() diff --git a/openmc/deplete/integrators.py b/openmc/deplete/integrators.py index 7c543a6cb86..25e64cb2ef8 100644 --- a/openmc/deplete/integrators.py +++ b/openmc/deplete/integrators.py @@ -46,15 +46,12 @@ def __call__(self, n, rates, dt, source_rate, _i=None): ------- proc_time : float Time spent in CRAM routines for all materials in [s] - n_list : list of list of numpy.ndarray + n_end : list of numpy.ndarray Concentrations at end of interval - op_results : empty list - Kept for consistency with API. No intermediate calls to operator - with predictor """ proc_time, n_end = self._timed_deplete(n, rates, dt, _i) - return proc_time, [n_end], [] + return proc_time, n_end @add_params @@ -98,11 +95,8 @@ def __call__(self, n, rates, dt, source_rate, _i=None): ------- proc_time : float Time spent in CRAM routines for all materials in [s] - n_list : list of list of numpy.ndarray - Concentrations at each of the intermediate points with - the final concentration as the last element - op_results : list of openmc.deplete.OperatorResult - Eigenvalue and reaction rates from transport simulations + n_end : list of numpy.ndarray + Concentrations at end of interval """ # deplete across first half of interval time0, n_middle = self._timed_deplete(n, rates, dt / 2, _i) @@ -112,7 +106,7 @@ def __call__(self, n, rates, dt, source_rate, _i=None): # MOS reaction rates time1, n_end = self._timed_deplete(n, res_middle.rates, dt, _i) - return time0 + time1, [n_middle, n_end], [res_middle] + return time0 + time1, n_end @add_params @@ -162,12 +156,8 @@ def __call__(self, n_bos, bos_rates, dt, source_rate, _i=None): ------- proc_time : float Time spent in CRAM routines for all materials in [s] - n_list : list of numpy.ndarray - Concentrations at each of the intermediate points with - the final concentration as the last element - op_results : list of openmc.deplete.OperatorResult - Eigenvalue and reaction rates from intermediate transport - simulations + n_end : list of numpy.ndarray + Concentrations at end of interval """ # Step 1: deplete with matrix 1/2*A(y0) time1, n_eos1 = self._timed_deplete( @@ -192,9 +182,7 @@ def __call__(self, n_bos, bos_rates, dt, source_rate, _i=None): time5, n_eos5 = self._timed_deplete( n_inter, list_rates, dt, _i, matrix_func=cf4_f4) - return (time1 + time2 + time3 + time4 + time5, - [n_eos1, n_eos2, n_eos3, n_eos5], - [res1, res2, res3]) + return time1 + time2 + time3 + time4 + time5, n_eos5 @add_params @@ -240,12 +228,8 @@ def __call__(self, n_bos, rates, dt, source_rate, _i=None): ------- proc_time : float Time spent in CRAM routines for all materials in [s] - n_list : list of list of numpy.ndarray - Concentrations at each of the intermediate points with - the final concentration as the last element - op_results : list of openmc.deplete.OperatorResult - Eigenvalue and reaction rates from intermediate transport - simulation + n_end : list of numpy.ndarray + Concentrations at end of interval """ # deplete to end using BOS rates proc_time, n_ce = self._timed_deplete(n_bos, rates, dt, _i) @@ -260,7 +244,7 @@ def __call__(self, n_bos, rates, dt, source_rate, _i=None): time_le2, n_end = self._timed_deplete( n_inter, list_rates, dt, _i, matrix_func=celi_f2) - return proc_time + time_le1 + time_le1, [n_ce, n_end], [res_ce] + return proc_time + time_le1 + time_le2, n_end @add_params @@ -306,12 +290,8 @@ def __call__(self, n, rates, dt, source_rate, _i=None): ------- proc_time : float Time spent in CRAM routines for all materials in [s] - n_list : list of list of numpy.ndarray - Concentrations at each of the intermediate points with - the final concentration as the last element - op_results : list of openmc.deplete.OperatorResult - Eigenvalue and reaction rates from intermediate transport - simulations + n_end : list of numpy.ndarray + Concentrations at end of interval """ # Step 1: deplete with matrix A(y0) / 2 @@ -330,7 +310,7 @@ def __call__(self, n, rates, dt, source_rate, _i=None): list_rates = list(zip(rates, res1.rates, res2.rates, res3.rates)) time4, n4 = self._timed_deplete(n, list_rates, dt, _i, matrix_func=rk4_f4) - return (time1 + time2 + time3 + time4, [n1, n2, n3, n4], [res1, res2, res3]) + return time1 + time2 + time3 + time4, n4 @add_params @@ -388,12 +368,8 @@ def __call__(self, n_bos, bos_rates, dt, source_rate, i): ------- proc_time : float Time spent in CRAM routines for all materials in [s] - n_list : list of list of numpy.ndarray - Concentrations at each of the intermediate points with - the final concentration as the last element - op_results : list of openmc.deplete.OperatorResult - Eigenvalue and reaction rates from intermediate transport - simulation + n_end : list of numpy.ndarray + Concentrations at end of interval """ if i == 0: if self._i_res < 1: # need at least previous transport solution @@ -402,7 +378,7 @@ def __call__(self, n_bos, bos_rates, dt, source_rate, i): self, n_bos, bos_rates, dt, source_rate, i) prev_res = self.operator.prev_res[-2] prev_dt = self.timesteps[i] - prev_res.time[0] - self._prev_rates = prev_res.rates[0] + self._prev_rates = prev_res.rates else: prev_dt = self.timesteps[i - 1] @@ -431,9 +407,7 @@ def __call__(self, n_bos, bos_rates, dt, source_rate, i): # store updated rates self._prev_rates = copy.deepcopy(bos_res.rates) - return ( - time1 + time2 + time3 + time4, [n_eos0, n_eos1], - [bos_res, res_inter]) + return time1 + time2 + time3 + time4, n_eos1 @add_params @@ -470,10 +444,9 @@ def __call__(self, n_bos, bos_rates, dt, source_rate, _i=None): ------- proc_time : float Time spent in CRAM routines for all materials in [s] - n_bos_list : list of list of numpy.ndarray - Concentrations at each of the intermediate points with - the final concentration as the last element - op_results : list of openmc.deplete.OperatorResult + n_end : list of numpy.ndarray + Concentrations at end of interval + op_result : openmc.deplete.OperatorResult Eigenvalue and reaction rates from intermediate transport simulations """ @@ -499,7 +472,7 @@ def __call__(self, n_bos, bos_rates, dt, source_rate, _i=None): proc_time += time1 + time2 # end iteration - return proc_time, [n_eos, n_inter], [res_bar] + return proc_time, n_inter, res_bar @add_params @@ -536,10 +509,9 @@ def __call__(self, n_bos, bos_rates, dt, source_rate, i): ------- proc_time : float Time spent in CRAM routines for all materials in [s] - n_list : list of list of numpy.ndarray - Concentrations at each of the intermediate points with - the final concentration as the last element - op_results : list of openmc.deplete.OperatorResult + n_end : list of numpy.ndarray + Concentrations at end of interval + op_result : openmc.deplete.OperatorResult Eigenvalue and reaction rates from intermediate transport simulation """ @@ -551,7 +523,7 @@ def __call__(self, n_bos, bos_rates, dt, source_rate, i): self, n_bos, bos_rates, dt, source_rate, i) prev_res = self.operator.prev_res[-2] prev_dt = self.timesteps[i] - prev_res.time[0] - self._prev_rates = prev_res.rates[0] + self._prev_rates = prev_res.rates else: prev_dt = self.timesteps[i - 1] @@ -584,7 +556,10 @@ def __call__(self, n_bos, bos_rates, dt, source_rate, i): n_inter, inputs, dt, i, matrix_func=leqi_f4) proc_time += time1 + time2 - return proc_time, [n_eos, n_inter], [res_bar] + # Store updated rates for next step + self._prev_rates = copy.deepcopy(bos_rates) + + return proc_time, n_inter, res_bar integrator_by_name = { diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index 7427abd7353..e1fcb26b6d6 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -203,7 +203,7 @@ def get_atoms( # Evaluate value in each region for i, result in enumerate(self): times[i] = result.time[0] - concentrations[i] = result[0, mat_id, nuc] + concentrations[i] = result[mat_id, nuc] # Unit conversions times = _get_time_as(times, time_units) @@ -363,7 +363,7 @@ def get_reaction_rate( # Evaluate value in each region for i, result in enumerate(self): times[i] = result.time[0] - rates[i] = result.rates[0].get(mat_id, nuc, rx) * result[0, mat, nuc] + rates[i] = result.rates.get(mat_id, nuc, rx) * result[mat, nuc] return times, rates @@ -397,7 +397,7 @@ def get_keff(self, time_units: str = 's') -> tuple[np.ndarray, np.ndarray]: # Get time/eigenvalue at each point for i, result in enumerate(self): times[i] = result.time[0] - eigenvalues[i] = result.k[0] + eigenvalues[i] = result.k # Convert time units if necessary times = _get_time_as(times, time_units) @@ -630,7 +630,7 @@ def export_to_materials( for nuc in result.index_nuc: if nuc not in available_cross_sections: continue - atoms = result[0, mat_id, nuc] + atoms = result[mat_id, nuc] if atoms > 0.0: atoms_per_barn_cm = 1e-24 * atoms / mat.volume mat.remove_nuclide(nuc) # Replace if it's there diff --git a/openmc/deplete/stepresult.py b/openmc/deplete/stepresult.py index 1a26cbe3466..ff39e9acb6d 100644 --- a/openmc/deplete/stepresult.py +++ b/openmc/deplete/stepresult.py @@ -16,7 +16,7 @@ from openmc.checkvalue import PathLike from .reaction_rates import ReactionRates -VERSION_RESULTS = (1, 1) +VERSION_RESULTS = (1, 2) __all__ = ["StepResult"] @@ -30,8 +30,8 @@ class StepResult: Attributes ---------- - k : list of (float, float) - Eigenvalue and uncertainty for each substep. + k : tuple of (float, float) + Eigenvalue and uncertainty at end of step. time : list of float Time at beginning, end of step, in seconds. source_rate : float @@ -40,8 +40,8 @@ class StepResult: Number of mats. n_nuc : int Number of nuclides. - rates : list of ReactionRates - The reaction rates for each substep. + rates : ReactionRates + The reaction rates at end of step. volume : dict of str to float Dictionary mapping mat id to volume. index_mat : dict of str to int @@ -52,10 +52,8 @@ class StepResult: A dictionary mapping mat ID as string to global index. n_hdf5_mats : int Number of materials in entire geometry. - n_stages : int - Number of stages in simulation. data : numpy.ndarray - Atom quantity, stored by stage, mat, then by nuclide. + Atom quantity, stored by mat, then by nuclide. proc_time : int Average time spent depleting a material across all materials and processes @@ -86,17 +84,17 @@ def __getitem__(self, pos): Parameters ---------- pos : tuple - A three-length tuple containing a stage index, mat index and a nuc - index. All can be integers or slices. The second two can be + A two-length tuple containing a mat index and a nuc + index. Both can be integers or slices, or can be strings corresponding to their respective dictionary. Returns ------- float - The atoms for stage, mat, nuc + The atoms for mat, nuc """ - stage, mat, nuc = pos + mat, nuc = pos if isinstance(mat, openmc.Material): mat = str(mat.id) if isinstance(mat, str): @@ -104,7 +102,7 @@ def __getitem__(self, pos): if isinstance(nuc, str): nuc = self.index_nuc[nuc] - return self.data[stage, mat, nuc] + return self.data[mat, nuc] def __setitem__(self, pos, val): """Sets an item from results. @@ -112,21 +110,21 @@ def __setitem__(self, pos, val): Parameters ---------- pos : tuple - A three-length tuple containing a stage index, mat index and a nuc - index. All can be integers or slices. The second two can be + A two-length tuple containing a mat index and a nuc + index. Both can be integers or slices, or can be strings corresponding to their respective dictionary. val : float The value to set data to. """ - stage, mat, nuc = pos + mat, nuc = pos if isinstance(mat, str): mat = self.index_mat[mat] if isinstance(nuc, str): nuc = self.index_nuc[nuc] - self.data[stage, mat, nuc] = val + self.data[mat, nuc] = val @property def n_mat(self): @@ -140,11 +138,7 @@ def n_nuc(self): def n_hdf5_mats(self): return len(self.mat_to_hdf5_ind) - @property - def n_stages(self): - return self.data.shape[0] - - def allocate(self, volume, nuc_list, burn_list, full_burn_list, stages): + def allocate(self, volume, nuc_list, burn_list, full_burn_list): """Allocate memory for depletion step data Parameters @@ -157,8 +151,6 @@ def allocate(self, volume, nuc_list, burn_list, full_burn_list, stages): A list of all mat IDs to be burned. Used for sorting the simulation. full_burn_list : list of str List of all burnable material IDs - stages : int - Number of stages in simulation. """ self.volume = copy.deepcopy(volume) @@ -167,7 +159,7 @@ def allocate(self, volume, nuc_list, burn_list, full_burn_list, stages): self.mat_to_hdf5_ind = {mat: i for i, mat in enumerate(full_burn_list)} # Create storage array - self.data = np.zeros((stages, self.n_mat, self.n_nuc)) + self.data = np.zeros((self.n_mat, self.n_nuc)) def distribute(self, local_materials, ranges): """Create a new object containing data for distributed materials @@ -196,8 +188,8 @@ def distribute(self, local_materials, ranges): for attr in direct_attrs: setattr(new, attr, getattr(self, attr)) # Get applicable slice of data - new.data = self.data[:, ranges] - new.rates = [r[ranges] for r in self.rates] + new.data = self.data[ranges] + new.rates = self.rates[ranges] return new def get_material(self, mat_id): @@ -232,7 +224,7 @@ def get_material(self, mat_id): f'values are {list(self.volume.keys())}' ) from e for nuc, _ in sorted(self.index_nuc.items(), key=lambda x: x[1]): - atoms = self[0, mat_id, nuc] + atoms = self[mat_id, nuc] if atoms <= 0.0: continue atom_per_bcm = atoms / vol * 1e-24 @@ -240,7 +232,7 @@ def get_material(self, mat_id): material.volume = vol return material - def export_to_hdf5(self, filename, step): + def export_to_hdf5(self, filename, step, write_rates: bool = False): """Export results to an HDF5 file Parameters @@ -249,6 +241,8 @@ def export_to_hdf5(self, filename, step): The filename to write to step : int What step is this? + write_rates : bool, optional + Whether to include reaction rate datasets in the results file. """ # Write new file if first time step, else add to existing file @@ -259,7 +253,8 @@ def export_to_hdf5(self, filename, step): kwargs['driver'] = 'mpio' kwargs['comm'] = comm with h5py.File(filename, **kwargs) as handle: - self._to_hdf5(handle, step, parallel=True) + self._to_hdf5(handle, step, parallel=True, + write_rates=write_rates) else: # Gather results at root process all_results = comm.gather(self) @@ -268,15 +263,18 @@ def export_to_hdf5(self, filename, step): if comm.rank == 0: with h5py.File(filename, **kwargs) as handle: for res in all_results: - res._to_hdf5(handle, step, parallel=False) + res._to_hdf5(handle, step, parallel=False, + write_rates=write_rates) - def _write_hdf5_metadata(self, handle): + def _write_hdf5_metadata(self, handle, write_rates): """Writes result metadata in HDF5 file Parameters ---------- handle : h5py.File or h5py.Group An hdf5 file or group type to store this in. + write_rates : bool + Whether reaction rate datasets are being written. """ # Create and save the 5 dictionaries: @@ -284,8 +282,8 @@ def _write_hdf5_metadata(self, handle): # self.index_mat -> self.volume (TODO: support for changing volumes) # self.index_nuc # reactions - # self.rates[0].index_nuc (can be different from above, above is superset) - # self.rates[0].index_rx + # self.rates.index_nuc (can be different from above, above is superset) + # self.rates.index_rx # these are shared by every step of the simulation, and should be deduplicated. # Store concentration mat and nuclide dictionaries (along with volumes) @@ -295,13 +293,19 @@ def _write_hdf5_metadata(self, handle): mat_list = sorted(self.mat_to_hdf5_ind, key=int) nuc_list = sorted(self.index_nuc) - rxn_list = sorted(self.rates[0].index_rx) + + include_rates = ( + write_rates + and self.rates is not None + and bool(self.rates.index_nuc) + and bool(self.rates.index_rx) + ) + rxn_list = sorted(self.rates.index_rx) if include_rates else [] n_mats = self.n_hdf5_mats n_nuc_number = len(nuc_list) - n_nuc_rxn = len(self.rates[0].index_nuc) + n_nuc_rxn = len(self.rates.index_nuc) if include_rates else 0 n_rxn = len(rxn_list) - n_stages = self.n_stages mat_group = handle.create_group("materials") @@ -315,41 +319,44 @@ def _write_hdf5_metadata(self, handle): for nuc in nuc_list: nuc_single_group = nuc_group.create_group(nuc) nuc_single_group.attrs["atom number index"] = self.index_nuc[nuc] - if nuc in self.rates[0].index_nuc: - nuc_single_group.attrs["reaction rate index"] = self.rates[0].index_nuc[nuc] + if include_rates and nuc in self.rates.index_nuc: + nuc_single_group.attrs["reaction rate index"] = ( + self.rates.index_nuc[nuc]) - rxn_group = handle.create_group("reactions") + if include_rates: + rxn_group = handle.create_group("reactions") - for rxn in rxn_list: - rxn_single_group = rxn_group.create_group(rxn) - rxn_single_group.attrs["index"] = self.rates[0].index_rx[rxn] + for rxn in rxn_list: + rxn_single_group = rxn_group.create_group(rxn) + rxn_single_group.attrs["index"] = ( + self.rates.index_rx[rxn]) # Construct array storage - handle.create_dataset("number", (1, n_stages, n_mats, n_nuc_number), - maxshape=(None, n_stages, n_mats, n_nuc_number), + handle.create_dataset("number", (1, n_mats, n_nuc_number), + maxshape=(None, n_mats, n_nuc_number), chunks=True, dtype='float64') - if n_nuc_rxn > 0 and n_rxn > 0: - handle.create_dataset("reaction rates", (1, n_stages, n_mats, n_nuc_rxn, n_rxn), - maxshape=(None, n_stages, n_mats, n_nuc_rxn, n_rxn), - chunks=True, - dtype='float64') + if include_rates and n_nuc_rxn > 0 and n_rxn > 0: + handle.create_dataset( + "reaction rates", (1, n_mats, n_nuc_rxn, n_rxn), + maxshape=(None, n_mats, n_nuc_rxn, n_rxn), + chunks=True, dtype='float64') - handle.create_dataset("eigenvalues", (1, n_stages, 2), - maxshape=(None, n_stages, 2), dtype='float64') + handle.create_dataset("eigenvalues", (1, 2), + maxshape=(None, 2), dtype='float64') handle.create_dataset("time", (1, 2), maxshape=(None, 2), dtype='float64') - handle.create_dataset("source_rate", (1, n_stages), maxshape=(None, n_stages), + handle.create_dataset("source_rate", (1,), maxshape=(None,), dtype='float64') handle.create_dataset( "depletion time", (1,), maxshape=(None,), dtype="float64") - def _to_hdf5(self, handle, index, parallel=False): + def _to_hdf5(self, handle, index, parallel=False, write_rates: bool = False): """Converts results object into an hdf5 object. Parameters @@ -360,12 +367,14 @@ def _to_hdf5(self, handle, index, parallel=False): What step is this? parallel : bool Being called with parallel HDF5? + write_rates : bool, optional + Whether reaction rate datasets are being written. """ if "/number" not in handle: if parallel: comm.barrier() - self._write_hdf5_metadata(handle) + self._write_hdf5_metadata(handle, write_rates) if parallel: comm.barrier() @@ -417,18 +426,14 @@ def _to_hdf5(self, handle, index, parallel=False): return # Add data - # Note, for the last step, self.n_stages = 1, even if n_stages != 1. - n_stages = self.n_stages inds = [self.mat_to_hdf5_ind[mat] for mat in self.index_mat] low = min(inds) high = max(inds) - for i in range(n_stages): - number_dset[index, i, low:high+1] = self.data[i] - if has_reactions: - rxn_dset[index, i, low:high+1] = self.rates[i] - if comm.rank == 0: - eigenvalues_dset[index, i] = self.k[i] + number_dset[index, low:high+1] = self.data + if has_reactions: + rxn_dset[index, low:high+1] = self.rates if comm.rank == 0: + eigenvalues_dset[index] = self.k time_dset[index] = self.time source_rate_dset[index] = self.source_rate if self.proc_time is not None: @@ -459,10 +464,24 @@ def from_hdf5(cls, handle, step): # Older versions used "power" instead of "source_rate" source_rate_dset = handle["/power"] - results.data = number_dset[step, :, :, :] - results.k = eigenvalues_dset[step, :] + # Check if this is an old format file (with stages dimension) or new format + # Old format: number has shape (n_steps, n_stages, n_mats, n_nucs) + # New format: number has shape (n_steps, n_mats, n_nucs) + has_stages = len(number_dset.shape) == 4 + + if has_stages: + # Old format - extract data from first stage (index 0) + results.data = number_dset[step, 0, :, :] + results.k = eigenvalues_dset[step, 0, :] + # source_rate had shape (n_steps, n_stages) in old format + results.source_rate = source_rate_dset[step, 0] + else: + # New format - no stages dimension + results.data = number_dset[step, :, :] + results.k = eigenvalues_dset[step, :] + results.source_rate = source_rate_dset[step] + results.time = time_dset[step, :] - results.source_rate = source_rate_dset[step, 0] if "depletion time" in handle: proc_time_dset = handle["/depletion time"] @@ -493,33 +512,45 @@ def from_hdf5(cls, handle, step): if "reaction rate index" in nuc_handle.attrs: rxn_nuc_to_ind[nuc] = nuc_handle.attrs["reaction rate index"] - for rxn, rxn_handle in handle["/reactions"].items(): - rxn_to_ind[rxn] = rxn_handle.attrs["index"] - - results.rates = [] - # Reconstruct reactions - for i in range(results.n_stages): - rate = ReactionRates(results.index_mat, rxn_nuc_to_ind, rxn_to_ind, True) + if "reactions" in handle: + for rxn, rxn_handle in handle["/reactions"].items(): + rxn_to_ind[rxn] = rxn_handle.attrs["index"] - if "reaction rates" in handle: - rate[:] = handle["/reaction rates"][step, i, :, :, :] - results.rates.append(rate) + # Reconstruct reaction rates + rate = ReactionRates(results.index_mat, rxn_nuc_to_ind, rxn_to_ind, True) + if "reaction rates" in handle: + if has_stages: + # Old format: (n_steps, n_stages, n_mats, n_nucs, n_rxns) + rate[:] = handle["/reaction rates"][step, 0, :, :, :] + else: + # New format: (n_steps, n_mats, n_nucs, n_rxns) + rate[:] = handle["/reaction rates"][step, :, :, :] + results.rates = rate return results @staticmethod - def save(op, x, op_results, t, source_rate, step_ind, proc_time=None, - path: PathLike = "depletion_results.h5"): + def save( + op, + x, + op_results, + t, + source_rate, + step_ind, + proc_time=None, + write_rates: bool = False, + path: PathLike = "depletion_results.h5" + ): """Creates and writes depletion results to disk Parameters ---------- op : openmc.deplete.abc.TransportOperator The operator used to generate these results. - x : list of list of numpy.array - The prior x vectors. Indexed [i][cell] using the above equation. - op_results : list of openmc.deplete.OperatorResult - Results of applying transport operator + x : numpy.array + End-of-step concentrations for each material + op_results : openmc.deplete.OperatorResult + Result of applying transport operator at end of step t : list of float Time indices. source_rate : float @@ -530,7 +561,8 @@ def save(op, x, op_results, t, source_rate, step_ind, proc_time=None, Total process time spent depleting materials. This may be process-dependent and will be reduced across MPI processes. - + write_rates : bool, optional + Whether reaction rates should be written to the results file. path : PathLike Path to file to write. Defaults to 'depletion_results.h5'. @@ -539,26 +571,20 @@ def save(op, x, op_results, t, source_rate, step_ind, proc_time=None, # Get indexing terms vol_dict, nuc_list, burn_list, full_burn_list = op.get_results_info() - stages = len(x) - # Create results results = StepResult() - results.allocate(vol_dict, nuc_list, burn_list, full_burn_list, stages) + results.allocate(vol_dict, nuc_list, burn_list, full_burn_list) n_mat = len(burn_list) - for i in range(stages): - for mat_i in range(n_mat): - results[i, mat_i, :] = x[i][mat_i] + for mat_i in range(n_mat): + results[mat_i, :] = x[mat_i] - ks = [] - for r in op_results: - if isinstance(r.k, type(None)): - ks += [(None, None)] - else: - ks += [(r.k.nominal_value, r.k.std_dev)] - results.k = ks - results.rates = [r.rates for r in op_results] + if isinstance(op_results.k, type(None)): + results.k = (None, None) + else: + results.k = (op_results.k.nominal_value, op_results.k.std_dev) + results.rates = op_results.rates results.time = t results.source_rate = source_rate results.proc_time = proc_time @@ -567,7 +593,7 @@ def save(op, x, op_results, t, source_rate, step_ind, proc_time=None, if not Path(path).is_file(): Path(path).parent.mkdir(parents=True, exist_ok=True) - results.export_to_hdf5(path, step_ind) + results.export_to_hdf5(path, step_ind, write_rates) def transfer_volumes(self, model): """Transfers volumes from depletion results to geometry diff --git a/tests/unit_tests/test_deplete_continue.py b/tests/unit_tests/test_deplete_continue.py index 637c9d5e440..1b6eac2384f 100644 --- a/tests/unit_tests/test_deplete_continue.py +++ b/tests/unit_tests/test_deplete_continue.py @@ -17,7 +17,7 @@ def test_continue(run_in_tmpdir): operator = dummy_operator.DummyOperator() # initial depletion - bundle.solver(operator, [1.0, 2.0], [1.0, 2.0]).integrate() + bundle.solver(operator, [1.0, 2.0], [1.0, 2.0]).integrate(write_rates=True) # set up continue run prev_res = openmc.deplete.Results(operator.output_dir / "depletion_results.h5") @@ -25,7 +25,7 @@ def test_continue(run_in_tmpdir): # if continue run happens, test passes bundle.solver(operator, [1.0, 2.0, 3.0, 4.0], [1.0, 2.0, 3.0, 4.0], - continue_timesteps=True).integrate() + continue_timesteps=True).integrate(write_rates=True) final_res = openmc.deplete.Results(operator.output_dir / "depletion_results.h5") @@ -42,7 +42,7 @@ def test_continue_continue(run_in_tmpdir): operator = dummy_operator.DummyOperator() # initial depletion - bundle.solver(operator, [1.0, 2.0], [1.0, 2.0]).integrate() + bundle.solver(operator, [1.0, 2.0], [1.0, 2.0]).integrate(write_rates=True) # set up continue run prev_res = openmc.deplete.Results(operator.output_dir / "depletion_results.h5") @@ -50,7 +50,7 @@ def test_continue_continue(run_in_tmpdir): # first continue run bundle.solver(operator, [1.0, 2.0, 3.0, 4.0], [1.0, 2.0, 3.0, 4.0], - continue_timesteps=True).integrate() + continue_timesteps=True).integrate(write_rates=True) prev_res = openmc.deplete.Results(operator.output_dir / "depletion_results.h5") # second continue run diff --git a/tests/unit_tests/test_deplete_integrator.py b/tests/unit_tests/test_deplete_integrator.py index b1d2cb950eb..6463eaa2084 100644 --- a/tests/unit_tests/test_deplete_integrator.py +++ b/tests/unit_tests/test_deplete_integrator.py @@ -10,6 +10,7 @@ from random import uniform from unittest.mock import MagicMock +import h5py import numpy as np from uncertainties import ufloat import pytest @@ -38,8 +39,6 @@ def test_results_save(run_in_tmpdir): """Test data save module""" - stages = 3 - rng = np.random.RandomState(comm.rank) # Mock geometry @@ -63,31 +62,22 @@ def test_results_save(run_in_tmpdir): op.get_results_info.return_value = ( vol_dict, nuc_list, burn_list, full_burn_list) - # Construct x - x1 = [] - x2 = [] - - for i in range(stages): - x1.append([rng.random(2), rng.random(2)]) - x2.append([rng.random(2), rng.random(2)]) + # Construct end-of-step concentrations + x1 = [rng.random(2), rng.random(2)] + x2 = [rng.random(2), rng.random(2)] - # Construct r + # Construct reaction rates r1 = ReactionRates(burn_list, ["na", "nb"], ["ra", "rb"]) r1[:] = rng.random((2, 2, 2)) + rate1 = copy.deepcopy(r1) - rate1 = [] - rate2 = [] - - for i in range(stages): - rate1.append(copy.deepcopy(r1)) - r1[:] = rng.random((2, 2, 2)) - rate2.append(copy.deepcopy(r1)) - r1[:] = rng.random((2, 2, 2)) + r2 = ReactionRates(burn_list, ["na", "nb"], ["ra", "rb"]) + r2[:] = rng.random((2, 2, 2)) + rate2 = copy.deepcopy(r2) - # Create global terms - # Col 0: eig, Col 1: uncertainty - eigvl1 = rng.random((stages, 2)) - eigvl2 = rng.random((stages, 2)) + # Create global terms (eigenvalue and uncertainty) + eigvl1 = rng.random(2) + eigvl2 = rng.random(2) eigvl1 = comm.bcast(eigvl1, root=0) eigvl2 = comm.bcast(eigvl2, root=0) @@ -95,29 +85,35 @@ def test_results_save(run_in_tmpdir): t1 = [0.0, 1.0] t2 = [1.0, 2.0] - op_result1 = [OperatorResult(ufloat(*k), rates) - for k, rates in zip(eigvl1, rate1)] - op_result2 = [OperatorResult(ufloat(*k), rates) - for k, rates in zip(eigvl2, rate2)] + op_result1 = OperatorResult(ufloat(*eigvl1), rate1) + op_result2 = OperatorResult(ufloat(*eigvl2), rate2) # saves within a subdirectory - StepResult.save(op, x1, op_result1, t1, 0, 0, path='out/put/depletion.h5') + StepResult.save( + op, + x1, + op_result1, + t1, + 0, + 0, + write_rates=True, + path='out/put/depletion.h5' + ) res = Results('out/put/depletion.h5') # saves with default filename - StepResult.save(op, x1, op_result1, t1, 0, 0) - StepResult.save(op, x2, op_result2, t2, 0, 1) + StepResult.save(op, x1, op_result1, t1, 0, 0, write_rates=True) + StepResult.save(op, x2, op_result2, t2, 0, 1, write_rates=True) # Load the files res = Results("depletion_results.h5") - for i in range(stages): - for mat_i, mat in enumerate(burn_list): - for nuc_i, nuc in enumerate(nuc_list): - assert res[0][i, mat, nuc] == x1[i][mat_i][nuc_i] - assert res[1][i, mat, nuc] == x2[i][mat_i][nuc_i] - np.testing.assert_array_equal(res[0].rates[i], rate1[i]) - np.testing.assert_array_equal(res[1].rates[i], rate2[i]) + for mat_i, mat in enumerate(burn_list): + for nuc_i, nuc in enumerate(nuc_list): + assert res[0][mat, nuc] == x1[mat_i][nuc_i] + assert res[1][mat, nuc] == x2[mat_i][nuc_i] + np.testing.assert_array_equal(res[0].rates, rate1) + np.testing.assert_array_equal(res[1].rates, rate2) np.testing.assert_array_equal(res[0].k, eigvl1) np.testing.assert_array_equal(res[0].time, t1) @@ -126,6 +122,31 @@ def test_results_save(run_in_tmpdir): np.testing.assert_array_equal(res[1].time, t2) +def test_results_save_without_rates(run_in_tmpdir): + """StepResult.save skips reaction-rate datasets by default""" + + op = MagicMock() + op.prev_res = None + vol_dict = {"0": 1.0} + nuc_list = ["na"] + burn_list = ["0"] + op.get_results_info.return_value = (vol_dict, nuc_list, burn_list, burn_list) + + x = [np.array([1.0])] + rates = ReactionRates(burn_list, nuc_list, ["ra"]) + rates[:] = np.array([[[2.0]]]) + op_result = OperatorResult(ufloat(1.0, 0.1), rates) + + StepResult.save(op, x, op_result, [0.0, 1.0], 0.0, 0) + + with h5py.File('depletion_results.h5', 'r') as handle: + assert 'reaction rates' not in handle + assert 'reactions' not in handle + + res = Results('depletion_results.h5') + assert res[0].rates.size == 0 + + def test_bad_integrator_inputs(): """Test failure modes for Integrator inputs""" diff --git a/tests/unit_tests/test_deplete_restart.py b/tests/unit_tests/test_deplete_restart.py index e8bfc062a08..1cbff30a5fb 100644 --- a/tests/unit_tests/test_deplete_restart.py +++ b/tests/unit_tests/test_deplete_restart.py @@ -21,7 +21,7 @@ def test_restart_predictor_cecm(run_in_tmpdir): # Perform simulation using the predictor algorithm dt = [0.75] power = 1.0 - openmc.deplete.PredictorIntegrator(op, dt, power).integrate() + openmc.deplete.PredictorIntegrator(op, dt, power).integrate(write_rates=True) # Load the files prev_res = openmc.deplete.Results(op.output_dir / "depletion_results.h5") @@ -30,10 +30,6 @@ def test_restart_predictor_cecm(run_in_tmpdir): op = dummy_operator.DummyOperator(prev_res) op.output_dir = output_dir - # check ValueError is raised, indicating previous and current stages - with pytest.raises(ValueError, match="incompatible.* 1.*2"): - openmc.deplete.CECMIntegrator(op, dt, power) - def test_restart_cecm_predictor(run_in_tmpdir): """Integral regression test of integrator algorithm using CE/CM for the @@ -47,7 +43,7 @@ def test_restart_cecm_predictor(run_in_tmpdir): dt = [0.75] power = 1.0 cecm = openmc.deplete.CECMIntegrator(op, dt, power) - cecm.integrate() + cecm.integrate(write_rates=True) # Load the files prev_res = openmc.deplete.Results(op.output_dir / "depletion_results.h5") @@ -56,10 +52,6 @@ def test_restart_cecm_predictor(run_in_tmpdir): op = dummy_operator.DummyOperator(prev_res) op.output_dir = output_dir - # check ValueError is raised, indicating previous and current stages - with pytest.raises(ValueError, match="incompatible.* 2.*1"): - openmc.deplete.PredictorIntegrator(op, dt, power) - @pytest.mark.parametrize("scheme", dummy_operator.SCHEMES) def test_restart(run_in_tmpdir, scheme): @@ -70,7 +62,7 @@ def test_restart(run_in_tmpdir, scheme): operator = dummy_operator.DummyOperator() # take first step - bundle.solver(operator, [0.75], 1.0).integrate() + bundle.solver(operator, [0.75], 1.0).integrate(write_rates=True) # restart prev_res = openmc.deplete.Results( From d217efa007760dbe927af25063ab6d68990779cc Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 20 Nov 2025 12:10:04 -0600 Subject: [PATCH 157/159] Add two MPI barriers in R2S workflow (#3646) --- openmc/deplete/microxs.py | 3 ++- openmc/deplete/r2s.py | 1 + openmc/lib/core.py | 10 ++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/openmc/deplete/microxs.py b/openmc/deplete/microxs.py index f4bdc679535..879a2d4ee99 100644 --- a/openmc/deplete/microxs.py +++ b/openmc/deplete/microxs.py @@ -177,8 +177,9 @@ def get_microxs_and_flux( if not openmc.lib.is_initialized: run_kwargs.setdefault('cwd', temp_dir) - # Run transport simulation + # Run transport simulation and synchronize statepoint_path = model.run(**run_kwargs) + comm.barrier() if comm.rank == 0: # Move the statepoint file if it is being saved to a specific path diff --git a/openmc/deplete/r2s.py b/openmc/deplete/r2s.py index 7f3e94edd96..97277cbda84 100644 --- a/openmc/deplete/r2s.py +++ b/openmc/deplete/r2s.py @@ -392,6 +392,7 @@ def step2_activation( ) output_path = output_dir / 'depletion_results.h5' integrator.integrate(final_step=False, path=output_path) + comm.barrier() # Get depletion results self.results['depletion_results'] = Results(output_path) diff --git a/openmc/lib/core.py b/openmc/lib/core.py index cfccecef2a9..02c7784d1cc 100644 --- a/openmc/lib/core.py +++ b/openmc/lib/core.py @@ -7,6 +7,7 @@ from pathlib import Path from random import getrandbits from tempfile import TemporaryDirectory +import traceback as tb import numpy as np from numpy.ctypeslib import as_array @@ -700,6 +701,15 @@ def __exit__(self, exc_type, exc_value, traceback): if self.already_initialized: return + # If an exception occurred, abort all ranks immediately + if exc_type is not None: + # Print exception info on the rank that failed + tb.print_exception(exc_type, exc_value, traceback) + sys.stdout.flush() + + # Abort all MPI processes + self.comm.Abort(1) + try: finalize() finally: From 27e38e894697bb32a1dac7848d2618818b6b8daf Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sat, 22 Nov 2025 23:06:17 +0100 Subject: [PATCH 158/159] Add release notes for 0.15.3 (#3644) --- docs/source/releasenotes/0.15.3.rst | 226 ++++++++++++++++++ docs/source/releasenotes/index.rst | 1 + docs/source/usersguide/variance_reduction.rst | 4 +- openmc/model/model.py | 6 +- openmc/settings.py | 9 +- 5 files changed, 240 insertions(+), 6 deletions(-) create mode 100644 docs/source/releasenotes/0.15.3.rst diff --git a/docs/source/releasenotes/0.15.3.rst b/docs/source/releasenotes/0.15.3.rst new file mode 100644 index 00000000000..c5095810474 --- /dev/null +++ b/docs/source/releasenotes/0.15.3.rst @@ -0,0 +1,226 @@ +==================== +What's New in 0.15.3 +==================== + +.. currentmodule:: openmc + +------- +Summary +------- + +This release of OpenMC includes many bug fixes, performance improvements, and +several notable new features. The major highlights of this release include a new +:class:`~openmc.deplete.R2SManager` class that automates the workflow for +rigorous 2-step (R2S) shutdown dose rate calculations, the ability to collect +higher moments for tally results that can be used to test normality, a new +uncertainty-aware criticality search method, a new collision tracking feature +that enables detailed tracking of particle interactions, support for distributed +cell densities, and several new tally filters. The random ray solver also +continues to receive significant updates, including automatic setup +capabilities, improved geometry handling, and better weight window support. +Depletion capabilities have been expanded with thermochemical redox control, +external transfer rates, and improved performance. + +------------------------------------ +Compatibility Notes and Deprecations +------------------------------------ + +MCPL has been changed from a build-time dependency to a runtime optional +dependency, which means OpenMC will attempt to load the MCPL library at +runtime when needed rather than requiring it at build time. + +The ``openmc.mgxs.Library.add_to_tallies_file`` method has been renamed to +:meth:`openmc.mgxs.Library.add_to_tallies`. + +------------ +New Features +------------ + +- A new collision tracking feature enables detailed tracking of particle + interactions (`#3417 `_) +- Added :meth:`~openmc.model.Model.keff_search` method for automated criticality + searches (`#3569 `_) +- Introduced automated workflow for mesh- or cell-based R2S calculations + (`#3508 `_) +- Ability to source electron/positrons directly for charged particle + simulations (`#3404 `_) +- Multi-group capability for kinetics parameter calculations with Iterated + Fission Probability (`#3425 + `_) +- Introduced a new :class:`openmc.MeshMaterialFilter` class (`#3406 + `_) +- Added support for distributed cell densities (`#3546 + `_) +- Implemented a :class:`openmc.WeightWindowsList` class that enables export to + HDF5 (`#3456 `_) +- Added :meth:`openmc.Material.mean_free_path` method (`#3469 + `_) +- Introduced :func:`openmc.lib.TemporarySession` context manager (`#3475 + `_) +- Added material depletion function for tracking individual material depletion + (`#3420 `_) +- Added methods on :class:`~openmc.Material` class for waste disposal rating / + classification (`#3366 `_, + `#3376 `_) +- Support for thermochemical redox control transfer rates in depletion + (`#2783 `_) +- Support for external transfer rates source term in depletion (`#3088 + `_) +- Added combing capability for fission site sampling and delayed neutron + emission time (`#2992 `_) +- Ability to specify reference direction for azimuthal angle in + :class:`~openmc.stats.PolarAzimuthal` distribution (`#3582 + `_) +- Allow spatial constraints on element sources within + :class:`~openmc.MeshSource` (`#3431 + `_) +- Added VTK HDF (.vtkhdf) format support for writing VTK data (`#3252 + `_) +- Implemented filter weight capability (`#3345 + `_) +- Optionally collect higher moments for tallies (`#3363 + `_) +- Several random ray solver enhancements: + + - Random Ray AutoMagic Setup for automatic configuration (`#3351 `_) + - Point source locator for random ray mode (`#3360 `_) + - Support for DAGMC geometries (`#3374 `_) + - Optimized mapping of source regions to tallies (`#3465 `_) + - Base source region refactor (`#3576 `_) + +--------------------------- +Bug Fixes and Small Changes +--------------------------- + +- Add two MPI barriers in R2S workflow (`#3646 `_) +- Fix a few warnings, rename add_to_tallies_file (`#3639 `_) +- Fix typo in DAGMC lost particle test (`#3634 `_) +- Avoid multiprocessing Pool when running depletion tests with MPI (`#3633 `_) +- Support MPI parallelism in R2SManager (`#3632 `_) +- Update documentation for particle tracks (`#3627 `_) +- Adding variance of variance and normality tests for tally statistics (`#3454 `_) +- Avoid divide-by-zero in ``from_multigroup_flux`` when flux is zero (`#3624 `_) +- Write particle states as separate lines in track VTK files (`#3628 `_) +- Reset DAGMC history when reviving from source (`#3601 `_) +- Add energy group structure: SCALE-999 (`#3564 `_) +- Fix bug in normalization of tally results with no_reduce (`#3619 `_) +- Enable nuclide filters with get_decay_photon_energy (`#3614 `_) +- Update ``check_type`` calls to accept both ``str`` and ``os.PathLike`` objects (`#3618 `_) +- Speed up ``apply_time_correction`` by reducing file I/O and deepcopies (`#3617 `_) +- FW-CADIS Disregard Max Realizations Setting (`#3616 `_) +- Random Ray Geometry Debug Mode Fix (`#3615 `_) +- Don't write reaction rates in depletion results by default (`#3609 `_) +- Allow Path objects in MGXSLibrary.export_to_hdf5 (`#3608 `_) +- Clip mixture distributions based on mean times integral (`#3603 `_) +- Allow V0 in atomic_mass function (for ENDF/B-VII.0 data) (`#3607 `_) +- Re-run flaky tests when needed (`#3604 `_) +- Ability to load mesh objects from weight_windows.h5 file (`#3598 `_) +- Switch to using coveralls github action for reporting (`#3594 `_) +- Add user setting for free gas threshold (`#3593 `_) +- Speed up time correction factors (`#3592 `_) +- Fix caching issue when using NCrystal materials (`#3538 `_) +- Fix random ray source region mesh export when using model.export_to_xml() (`#3579 `_) +- Ensure weight_windows_file information is read from XML (`#3587 `_) +- Add missing documentation on in depletion chain file format (`#3590 `_) +- Adding tally filter type option to statepoint get_tally (`#3584 `_) +- Optional separation of mesh-material-volume calc from get_homogenized_materials (`#3581 `_) +- Fix IFP implementation (`#3580 `_) +- Remove several TODOs related to C++17 support (`#3574 `_) +- Fix performance regression in libMesh unstructured mesh tallies (`#3577 `_) +- Update find_package calls in OpenMCConfig.cmake (`#3572 `_) +- Ensure ``n_dimension_`` attribute is set for unstructured meshes (`#3575 `_) +- Allow newer Sphinx version and fix docbuild warnings (`#3571 `_) +- Fixed a bug when combining TimeFilter, MeshFilter, and tracklength estimator (`#3525 `_) +- PowerLaw raises an error if sampling interval contains negative values (`#3542 `_) +- depletion: fix performance of chain matrix construction (`#3567 `_) +- Do not apply boundary conditions when initialized in volume calculation mode (`#3562 `_) +- Bump up tolerance for flaky activation test (`#3560 `_) +- Fixed a bug in plotting cross sections with S(a,b) data (`#3558 `_) +- Change test order to run unit tests first (`#3533 `_) +- adding ecco 33 (`#3556 `_) +- Refactor endf_data to be a fixture (`#3539 `_) +- Revert "fix broken CI" (`#3554 `_) +- fix broken CI (`#3551 `_) +- Leverage particle.move_distance in event advance (`#3544 `_) +- fix tests that accidentaly got broken (`#3543 `_) +- not printing nuclides with 0 percent to terminal (option 2 ) (`#3448 `_) +- Fix a bug in time cutoff behavior (`#3526 `_) +- Avoid duplicate materials written to XML (`#3536 `_) +- Use cached property for openmc.data.Decay.sources (`#3535 `_) +- more helpful error message for dose_coefficients (`#3534 `_) +- Adding 616 group structure (`#3531 `_) +- Remove unused special accessors for tallies (`#3527 `_) +- Consistent XML parsing using functions from _xml module (`#3517 `_) +- Add stat:sum field to MCPL files for proper weight normalization (`#3522 `_) +- Remove reorder_attributes from openmc._xml (`#3519 `_) +- fixed a bug in MeshMaterialFilter.from_volumes (`#3520 `_) +- Fixed a bug in distribcell offsets logic (`#3424 `_) +- Add test for FW-CADIS based WW generation on a DAGMC model (`#3504 `_) +- Fix for Weight Window Scaling Bug (`#3511 `_) +- Fix: ``materials``, ``plots``, and ``tallies`` cannot be passed as lists (`#3513 `_) +- Allow already-initialized openmc.lib in TemporarySession (`#3505 `_) +- Update DAGMC and libMesh precompiler definitions (`#3510 `_) +- Avoid adding ParentNuclideFilter twice when calling prepare_tallies (`#3506 `_) +- Enabling MCPL source files to be read when using surf_source_read (`#3472 `_) +- Boundary info accessors (`#3496 `_) +- automatically finding appropriate dimension when making regular mesh from domain (`#3468 `_) +- Add accessor methods for LocalCoord (`#3494 `_) +- Make MCPL a Runtime Optional Dependency (`#3429 `_) +- Use auto-chunking for StepResult HDF5 writing (`#3498 `_) +- Provide a way to get ID maps from plot parameters on the Model class (`#3481 `_) +- Update OSX install instructions to point to x64 platform (`#3501 `_) +- Update conda install instructions for macOS Apple silicon (`#3488 `_) +- Only show warning if in restart mode (`#3478 `_) +- Add flag to CMakeLists to use submodules instead of searching (`#3480 `_) +- Added citation metadata file (`#3409 `_) +- fix zam parsing (`#3484 `_) +- Support flux collapse method in ``get_microxs_and_flux`` (`#3466 `_) +- Stabilize Adjoint Source (`#3476 `_) +- Refactor and Harden Configuration Management (`#3461 `_) +- Updated Docs to Not Give Specific Python Version Requirement (`#3473 `_) +- Parallelization of Weight Window Update (`#3467 `_) +- Limit Random Ray Weight Window Generation to Final Batch (`#3464 `_) +- Fix Dockerfile DAGMC build (`#3463 `_) +- Fix Weight Window Infinite Loop Bug (`#3457 `_) +- Weight Window Birth Scaling (`#3459 `_) +- Adding checks to geometry.plot to avoid material name overlaps (`#3458 `_) +- Fixing crash when calling Geometry.plot when DAGMCUniverse in geometry (`#3455 `_) +- fixing expansion of elemental Ta bug (`#3443 `_) +- Prevent Adjoint Sources from Trending towards Infinity (`#3449 `_) +- adding plot function to DAGMCUnvierse (`#3451 `_) +- Allow specifying number of equiprobable angles for thermal scattering data generation (`#3346 `_) +- Change Dockerfile from debian:bookworm-slim to ubuntu:24.04 (`#3442 `_) +- Fix Resetting of Auto IDs When Generating MGXS (`#3437 `_) +- Allowing chain_file to be chain object to save reloading time (`#3436 `_) +- update units for flux (`#3441 `_) +- Fix raytrace infinite loop (`#3423 `_) +- Apply Max Number of Events Check to Random Rays (`#3438 `_) +- Add user setting for source rejection fraction (`#3433 `_) +- Adding fix and tests for spherical mesh as spatial distribution (`#3428 `_) +- Random Ray Missed Cell Policy Change for Adjoint Mode (`#3434 `_) +- Random Ray External Source Plotting Fix (`#3430 `_) +- Avoid negative heating values during pair production and bremsstrahlung (`#3426 `_) +- Fix no serialization of periodic_surface_id bug (`#3421 `_) +- Update _get_start_data to always grab the beginning of timestep time (`#3414 `_) +- Fixed a bug in charged particle energy deposition (`#3416 `_) +- Fix bug where the same mesh is written multiple times to settings.xml (`#3418 `_) +- small typo - spelling of Debian (`#3411 `_) +- added test for dagmc geometry plot (`#3375 `_) +- Random Ray Misc Memory Error Fixes (`#3405 `_) +- added type hints to model file (`#3399 `_) +- Apply resolve paths to path values in ``config`` (`#3400 `_) +- Fixing an incorrect computation of CDF of bremsstrahlung photons (`#3396 `_) +- Fix weight modification for uniform source sampling (`#3395 `_) +- Updates to VTK data checks (`#3371 `_) +- Map Compton subshell data to atomic relaxation data (`#3392 `_) +- Skip atomic relaxation if binding energy is larger than photon energy (`#3391 `_) +- Fix extremely large yields from Bremsstrahlung (`#3386 `_) +- corrected tally name in D1S example (`#3383 `_) +- Install MCPL using same build type as OpenMC in CI (`#3388 `_) +- using reduce chain level to remove need for reduce chain (`#3377 `_) +- Fix negative distances from bins_crossed for CylindricalMesh (`#3370 `_) +- Add check for equal value bins in an EnergyFilter (`#3372 `_) +- Fix for Issue Loading MGXS Data Files with LLVM 20 or Newer (`#3368 `_) +- Report plot ID instead of index for unsupported plot types in random ray mode (`#3361 `_) +- Handle Missing Tags in Versioning by Setting Default to 0 (`#3359 `_) +- added kg units to doc string in results class (`#3358 `_) diff --git a/docs/source/releasenotes/index.rst b/docs/source/releasenotes/index.rst index d24b83f9eb0..1292599ba91 100644 --- a/docs/source/releasenotes/index.rst +++ b/docs/source/releasenotes/index.rst @@ -7,6 +7,7 @@ Release Notes .. toctree:: :maxdepth: 1 + 0.15.3 0.15.2 0.15.1 0.15.0 diff --git a/docs/source/usersguide/variance_reduction.rst b/docs/source/usersguide/variance_reduction.rst index 5c248515859..93321e10723 100644 --- a/docs/source/usersguide/variance_reduction.rst +++ b/docs/source/usersguide/variance_reduction.rst @@ -51,7 +51,7 @@ With the :class:`~openmc.WeightWindowGenerator` instance added to the :attr:`~openmc.Settings`, the rest of the problem can be defined as normal. When running, note that the second iteration and beyond may be several orders of magnitude slower than the first. As the weight windows are applied in each -iteration, particles may be agressively split, resulting in a large number of +iteration, particles may be aggressively split, resulting in a large number of secondary (split) particles being generated per initial source particle. This is not necessarily a bad thing, as the split particles are much more efficient at exploring low flux regions of phase space as compared to initial particles. @@ -161,7 +161,7 @@ solver, the Python input just needs to load the h5 file:: settings.weight_window_checkpoints = {'collision': True, 'surface': True} settings.survival_biasing = False - settings.weight_windows = openmc.WeightWindowsList.from_hdf5('weight_windows.h5') + settings.weight_windows_file = "weight_windows.h5" settings.weight_windows_on = True The :class:`~openmc.WeightWindowGenerator` instance is not needed to load an diff --git a/openmc/model/model.py b/openmc/model/model.py index 10e5cbfc745..48ae9f0b911 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -2112,8 +2112,12 @@ def convert_to_multigroup( groups : openmc.mgxs.EnergyGroups or str, optional Energy group structure for the MGXS or the name of the group structure (based on keys from openmc.mgxs.GROUP_STRUCTURES). + nparticles : int, optional + Number of particles to simulate per batch when generating MGXS. + overwrite_mgxs_library : bool, optional + Whether to overwrite an existing MGXS library file. mgxs_path : str, optional - Filename of the mgxs.h5 library file. + Path to the mgxs.h5 library file. correction : str, optional Transport correction to apply to the MGXS. Options are None and "P0". diff --git a/openmc/settings.py b/openmc/settings.py index 43c1fe06982..289fb35b731 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -1278,9 +1278,12 @@ def weight_windows_file(self) -> PathLike | None: return self._weight_windows_file @weight_windows_file.setter - def weight_windows_file(self, value: PathLike): - cv.check_type('weight windows file', value, PathLike) - self._weight_windows_file = input_path(value) + def weight_windows_file(self, value: PathLike | None): + if value is None: + self._weight_windows_file = None + else: + cv.check_type('weight windows file', value, PathLike) + self._weight_windows_file = input_path(value) @property def weight_window_generators(self) -> list[WeightWindowGenerator]: From 4a46d1707598d5c5e023bf96bc0c6fbdfc2d1a69 Mon Sep 17 00:00:00 2001 From: zxkjack123 Date: Fri, 23 Jan 2026 15:57:12 +0800 Subject: [PATCH 159/159] chore: M1 coupling baseline compatibility --- .gitignore | 4 ++++ openmc/weight_windows.py | 5 ++++- pyproject.toml | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 3b2a24a4dbc..4c195e60df3 100644 --- a/.gitignore +++ b/.gitignore @@ -103,6 +103,10 @@ CMakeSettings.json # Visual Studio Code configuration files .vscode/ +.devcontainer/ + +# Local install prefix (CMake) +install/ # Python pickle files *.pkl diff --git a/openmc/weight_windows.py b/openmc/weight_windows.py index 5d52a579a11..908583279b3 100644 --- a/openmc/weight_windows.py +++ b/openmc/weight_windows.py @@ -2,7 +2,10 @@ from numbers import Real, Integral from collections.abc import Iterable, Sequence from pathlib import Path -from typing import Self +try: + from typing import Self # Python 3.11+ +except ImportError: + from typing_extensions import Self # Python <3.11 import warnings import lxml.etree as ET diff --git a/pyproject.toml b/pyproject.toml index 2d67e834012..44ca3bbce0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ authors = [ ] description = "OpenMC" dynamic = ["version"] -requires-python = ">=3.11" +requires-python = ">=3.10" license = {file = "LICENSE"} classifiers = [ "Development Status :: 4 - Beta", @@ -33,6 +33,7 @@ dependencies = [ "matplotlib", "pandas", "lxml", + "typing_extensions; python_version < '3.11'", "uncertainties", "setuptools", "endf",