diff --git a/CMakeLists.txt b/CMakeLists.txt index eb2224867b..9b9f5a3c95 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -309,13 +309,6 @@ add_library(libopenmc SHARED src/bank_header.F90 src/api.F90 src/dagmc_header.F90 - src/cmfd_data.F90 - src/cmfd_execute.F90 - src/cmfd_header.F90 - src/cmfd_input.F90 - src/cmfd_loss_operator.F90 - src/cmfd_prod_operator.F90 - src/cmfd_solver.F90 src/constants.F90 src/dict_header.F90 src/eigenvalue.F90 @@ -383,7 +376,7 @@ add_library(libopenmc SHARED src/bank.cpp src/dagmc.cpp src/cell.cpp - src/cmfd_execute.cpp + src/cmfd_solver.cpp src/cross_sections.cpp src/distribution.cpp src/distribution_angle.cpp diff --git a/docs/source/io_formats/cmfd.rst b/docs/source/io_formats/cmfd.rst deleted file mode 100644 index 80e8365c79..0000000000 --- a/docs/source/io_formats/cmfd.rst +++ /dev/null @@ -1,221 +0,0 @@ -.. _io_cmfd: - -============================== -CMFD Specification -- cmfd.xml -============================== - -Coarse mesh finite difference acceleration method has been implemented in -OpenMC. Currently, it allows users to accelerate fission source convergence -during inactive neutron batches. To run CMFD, the ```` element in -``settings.xml`` should be set to "true". - -------------------- -```` Element -------------------- - -The ```` element controls what batch CMFD calculations should begin. - - *Default*: 1 - ------------------------- -```` Element ------------------------- - -The ```` element controls whether :math:`\widehat{D}` nonlinear -CMFD parameters should be reset to zero before solving CMFD eigenproblem. -It can be turned on with "true" and off with "false". - - *Default*: false - ---------------------- -```` Element ---------------------- - -The ```` element sets one additional CMFD output column. Options are: - -* "balance" - prints the RMS [%] of the resdiual from the neutron balance - equation on CMFD tallies. -* "dominance" - prints the estimated dominance ratio from the CMFD iterations. - **This will only work for power iteration eigensolver**. -* "entropy" - prints the *entropy* of the CMFD predicted fission source. - **Can only be used if OpenMC entropy is active as well**. -* "source" - prints the RMS [%] between the OpenMC fission source and CMFD - fission source. - - *Default*: balance - -------------------------- -```` Element -------------------------- - -The ```` element controls whether an effective downscatter cross -section should be used when using 2-group CMFD. It can be turned on with "true" -and off with "false". - - *Default*: false - ----------------------- -```` Element ----------------------- - -The ```` element controls whether or not the CMFD diffusion result is -used to adjust the weight of fission source neutrons on the next OpenMC batch. -It can be turned on with "true" and off with "false". - - *Default*: false - ------------------------------------- -```` Element ------------------------------------- - -The ```` element specifies two parameters. The first is -the absolute inner tolerance for Gauss-Seidel iterations when performing CMFD -and the second is the relative inner tolerance for Gauss-Seidel iterations -for CMFD calculations. - - *Default*: 1.e-10 1.e-5 - --------------------- -```` Element --------------------- - -The ```` element specifies the tolerance on the eigenvalue when performing -CMFD power iteration. - - *Default*: 1.e-8 - ------------------- -```` Element ------------------- - -The CMFD mesh is a structured Cartesian mesh. This element has the following -attributes/sub-elements: - - :lower_left: - The lower-left corner of the structured mesh. If only two coordinates are - given, it is assumed that the mesh is an x-y mesh. - - :upper_right: - The upper-right corner of the structrued mesh. If only two coordinates are - given, it is assumed that the mesh is an x-y mesh. - - :dimension: - The number of mesh cells in each direction. - - :width: - The width of mesh cells in each direction. - - :energy: - Energy bins [in eV], listed in ascending order (e.g. 0.0 0.625 20.0e6) - for CMFD tallies and acceleration. If no energy bins are listed, OpenMC - automatically assumes a one energy group calculation over the entire - energy range. - - :albedo: - Surface ratio of incoming to outgoing partial currents on global boundary - conditions. They are listed in the following order: -x +x -y +y -z +z. - - *Default*: 1.0 1.0 1.0 1.0 1.0 1.0 - - :map: - An optional acceleration map can be specified to overlay on the coarse - mesh spatial grid. If this option is used, a ``1`` is used for a - non-accelerated region and a ``2`` is used for an accelerated region. - For a simple 4x4 coarse mesh with a 2x2 fuel lattice surrounded by - reflector, the map is: - - ``1 1 1 1`` - - ``1 2 2 1`` - - ``1 2 2 1`` - - ``1 1 1 1`` - - Therefore a 2x2 system of equations is solved rather than a 4x4. This - is extremely important to use in reflectors as neutrons will not - contribute to any tallies far away from fission source neutron regions. - A ``2`` must be used to identify any fission source region. - - .. note:: Only two of the following three sub-elements are needed: - ``lower_left``, ``upper_right`` and ``width``. Any combination - of two of these will yield the third. - ------------------- -```` Element ------------------- - -The ```` element is used to normalize the CMFD fission source distribution -to a particular value. For example, if a fission source is calculated for a -17 x 17 lattice of pins, the fission source may be normalized to the number of -fission source regions, in this case 289. This is useful when visualizing this -distribution as the average peaking factor will be unity. This parameter will -not impact the calculation. - - *Default*: 1.0 - ---------------------------- -```` Element ---------------------------- - -The ```` element is used to view the convergence of power -iteration. This option can be turned on with "true" and turned off with "false". - - *Default*: false - -------------------------- -```` Element -------------------------- - -The ```` element can be turned on with "true" to have an adjoint -calculation be performed on the last batch when CMFD is active. - - *Default*: false - --------------------- -```` Element --------------------- - -The ```` element specifies an optional Wielandt shift parameter for -accelerating power iterations. It is by default very large so the impact of the -shift is effectively zero. - - *Default*: 1e6 - ----------------------- -```` Element ----------------------- - -The ```` element specifies an optional spectral radius that can be set to -accelerate the convergence of Gauss-Seidel iterations during CMFD power iteration -solve. - - *Default*: 0.0 - ------------------- -```` Element ------------------- - -The ```` element specifies the tolerance on the fission source when performing -CMFD power iteration. - - *Default*: 1.e-8 - -------------------------- -```` Element -------------------------- - -The ```` element contains a list of batch numbers in which CMFD tallies -should be reset. - - *Default*: None - ----------------------------- -```` Element ----------------------------- - -The ```` element is used to write the sparse matrices created -when solving CMFD equations. This option can be turned on with "true" and off -with "false". - - *Default*: false diff --git a/docs/source/io_formats/index.rst b/docs/source/io_formats/index.rst index ef83bd23da..7ae22c0a33 100644 --- a/docs/source/io_formats/index.rst +++ b/docs/source/io_formats/index.rst @@ -19,7 +19,6 @@ Input Files settings tallies plots - cmfd ---------- Data Files diff --git a/docs/source/io_formats/settings.rst b/docs/source/io_formats/settings.rst index f19a9e902d..178c440801 100644 --- a/docs/source/io_formats/settings.rst +++ b/docs/source/io_formats/settings.rst @@ -361,16 +361,6 @@ or sub-elements: .. note:: This element is not used in the multi-group :ref:`energy_mode`. ----------------------- -```` Element ----------------------- - -The ```` element indicates whether or not CMFD acceleration should be -turned on or off. This element has no attributes or sub-elements and can be set -to either "false" or "true". - - *Default*: false - ---------------------- ```` Element ---------------------- diff --git a/docs/source/io_formats/statepoint.rst b/docs/source/io_formats/statepoint.rst index 38dae7dd7a..9c5b8f5260 100644 --- a/docs/source/io_formats/statepoint.rst +++ b/docs/source/io_formats/statepoint.rst @@ -17,8 +17,6 @@ The current version of the statepoint file format is 17.0. - **date_and_time** (*char[]*) -- Date and time the summary was written. - **path** (*char[]*) -- Path to directory containing input files. - - **cmfd_on** (*int*) -- Flag indicating whether CMFD is on (1) or - off (0). - **tallies_present** (*int*) -- Flag indicating whether tallies are present (1) or not (0). - **source_present** (*int*) -- Flag indicating whether the source @@ -59,18 +57,6 @@ The current version of the statepoint file format is 17.0. source particle, respectively. Only present when `run_mode` is 'eigenvalue'. -**/cmfd/** - -:Datasets: - **indices** (*int[4]*) -- Indices for cmfd mesh (i,j,k,g) - - **k_cmfd** (*double[]*) -- CMFD eigenvalues - - **cmfd_src** (*double[][][][]*) -- CMFD fission source - - **cmfd_entropy** (*double[]*) -- CMFD estimate of Shannon entropy - - **cmfd_balance** (*double[]*) -- RMS of the residual neutron - balance equation on CMFD mesh - - **cmfd_dominance** (*double[]*) -- CMFD estimate of dominance ratio - - **cmfd_srccmp** (*double[]*) -- RMS comparison of difference - between OpenMC and CMFD fission source - **/tallies/** :Attributes: - **n_tallies** (*int*) -- Number of user-defined tallies. @@ -166,9 +152,3 @@ All values are given in seconds and are measured on the master process. source sites between processes for load balancing. - **accumulating tallies** (*double*) -- Time spent communicating tally results and evaluating their statistics. - - **CMFD** (*double*) -- Time spent evaluating CMFD. - - **CMFD building matrices** (*double*) -- Time spent buliding CMFD - matrices. - - **CMFD solving matrices** (*double*) -- Time spent solving CMFD - matrices. - - **total** (*double*) -- Total time spent in the program. diff --git a/docs/source/pythonapi/base.rst b/docs/source/pythonapi/base.rst index 84bb3cc54d..f39421b286 100644 --- a/docs/source/pythonapi/base.rst +++ b/docs/source/pythonapi/base.rst @@ -130,17 +130,6 @@ Constructing Tallies openmc.Tally openmc.Tallies -Coarse Mesh Finite Difference Acceleration ------------------------------------------- - -.. autosummary:: - :toctree: generated - :nosignatures: - :template: myclass.rst - - openmc.CMFDMesh - openmc.CMFD - Geometry Plotting ----------------- @@ -183,7 +172,7 @@ The following classes and functions are used for functional expansion reconstruc .. autosummary:: :toctree: generated :nosignatures: - :template: myclass.rst + :template: myclass.rst openmc.ZernikeRadial @@ -208,3 +197,32 @@ Various classes may be created when performing tally slicing and/or arithmetic: openmc.arithmetic.AggregateScore openmc.arithmetic.AggregateNuclide openmc.arithmetic.AggregateFilter + +Coarse Mesh Finite Difference Acceleration +------------------------------------------ + +CMFD is implemented in OpenMC and allows users to accelerate fission source +convergence during inactive neutron batches. To run CMFD, the CMFDRun class +should be used, and :mod:`from openmc import cmfd` should be included at the +top of the Python input file. Additionally, this class has a dependence on the +C API. + +.. autosummary:: + :toctree: generated + :nosignatures: + :template: myclass.rst + + openmc.CMFDMesh + openmc.CMFDRun + +At the minimum, a CMFD mesh needs to be specified in order to run CMFD. Once +these properties are set, an OpenMC simulation can be run with CMFD turned on +with the function: + +.. autosummary:: + :toctree: generated + :nosignatures: + :template: myfunction.rst + + openmc.CMFDRun.run + diff --git a/docs/source/usersguide/basics.rst b/docs/source/usersguide/basics.rst index 74e65f771c..2dd91ee0fa 100644 --- a/docs/source/usersguide/basics.rst +++ b/docs/source/usersguide/basics.rst @@ -43,10 +43,6 @@ described below. This file gives specifications for producing slice or voxel plots of the geometry. - :ref:`io_cmfd` - This file specifies execution parameters for coarse mesh finite difference - (CMFD) acceleration. - eXtensible Markup Language (XML) -------------------------------- diff --git a/include/openmc/capi.h b/include/openmc/capi.h index 50ae72000f..aad72a1386 100644 --- a/include/openmc/capi.h +++ b/include/openmc/capi.h @@ -124,6 +124,31 @@ extern "C" { int openmc_zernike_filter_set_params(int32_t index, const double* x, const double* y, const double* r); + //! Sets the fixed variables that are used for CMFD linear solver + //! \param[in] CSR format index pointer array of loss matrix + //! \param[in] length of indptr + //! \param[in] CSR format index array of loss matrix + //! \param[in] number of non-zero elements in CMFD loss matrix + //! \param[in] dimension n of nxn CMFD loss matrix + //! \param[in] spectral radius of CMFD matrices and tolerances + //! \param[in] indices storing spatial and energy dimensions of CMFD problem + //! \param[in] coremap for problem, storing accelerated regions + extern "C" void openmc_initialize_linsolver(const int* indptr, int len_indptr, + const int* indices, int n_elements, + int dim, double spectral, + const int* cmfd_indices, + const int* map); + + //! Runs a Gauss Seidel linear solver to solve CMFD matrix equations + //! linear solver + //! \param[in] CSR format data array of coefficient matrix + //! \param[in] right hand side vector + //! \param[out] unknown vector + //! \param[in] tolerance on final error + //! \return number of inner iterations required to reach convergence + extern "C" int openmc_run_linsolver(const double* A_data, const double* b, + double* x, double tol); + // Error codes extern int OPENMC_E_UNASSIGNED; extern int OPENMC_E_ALLOCATE; diff --git a/include/openmc/constants.h b/include/openmc/constants.h index dbb5ed3147..7697843510 100644 --- a/include/openmc/constants.h +++ b/include/openmc/constants.h @@ -434,6 +434,12 @@ constexpr int RUN_MODE_PLOTTING {3}; constexpr int RUN_MODE_PARTICLE {4}; constexpr int RUN_MODE_VOLUME {5}; +// ============================================================================ +// CMFD CONSTANTS + +// For non-accelerated regions on coarse mesh overlay +constexpr int CMFD_NOACCEL {-1}; + } // namespace openmc #endif // OPENMC_CONSTANTS_H diff --git a/openmc/__init__.py b/openmc/__init__.py index 4627c340b2..715550302b 100644 --- a/openmc/__init__.py +++ b/openmc/__init__.py @@ -1,5 +1,6 @@ from openmc.arithmetic import * from openmc.cell import * +from openmc.checkvalue import * from openmc.mesh import * from openmc.element import * from openmc.geometry import * @@ -20,7 +21,6 @@ from openmc.trigger import * from openmc.tally_derivative import * from openmc.tallies import * from openmc.mgxs_library import * -from openmc.cmfd import * from openmc.executor import * from openmc.statepoint import * from openmc.summary import * diff --git a/openmc/capi/core.py b/openmc/capi/core.py index 88c2b45a38..540413e7bf 100644 --- a/openmc/capi/core.py +++ b/openmc/capi/core.py @@ -20,6 +20,13 @@ class _Bank(Structure): ('delayed_group', c_int)] +# Define input type for numpy arrays that will be passed into C++ functions +# Must be an int or double array, with single dimension that is contiguous +_array_1d_int = np.ctypeslib.ndpointer(dtype=np.int32, ndim=1, + flags='CONTIGUOUS') +_array_1d_dble = np.ctypeslib.ndpointer(dtype=np.double, ndim=1, + flags='CONTIGUOUS') + _dll.openmc_calculate_volumes.restype = c_int _dll.openmc_calculate_volumes.errcheck = _error_handler _dll.openmc_finalize.restype = c_int @@ -36,6 +43,10 @@ _dll.openmc_init.errcheck = _error_handler _dll.openmc_get_keff.argtypes = [POINTER(c_double*2)] _dll.openmc_get_keff.restype = c_int _dll.openmc_get_keff.errcheck = _error_handler +_init_linsolver_argtypes = [_array_1d_int, c_int, _array_1d_int, c_int, c_int, + c_double, _array_1d_int, _array_1d_int] +_dll.openmc_initialize_linsolver.argtypes = _init_linsolver_argtypes +_dll.openmc_initialize_linsolver.restype = None _dll.openmc_next_batch.argtypes = [POINTER(c_int)] _dll.openmc_next_batch.restype = c_int _dll.openmc_next_batch.errcheck = _error_handler @@ -45,6 +56,10 @@ _dll.openmc_run.restype = c_int _dll.openmc_run.errcheck = _error_handler _dll.openmc_reset.restype = c_int _dll.openmc_reset.errcheck = _error_handler +_run_linsolver_argtypes = [_array_1d_dble, _array_1d_dble, _array_1d_dble, + c_double] +_dll.openmc_run_linsolver.argtypes = _run_linsolver_argtypes +_dll.openmc_run_linsolver.restype = c_int _dll.openmc_source_bank.argtypes = [POINTER(POINTER(_Bank)), POINTER(c_int64)] _dll.openmc_source_bank.restype = c_int _dll.openmc_source_bank.errcheck = _error_handler @@ -126,6 +141,7 @@ def find_material(xyz): else: return mats[instance.value] + def hard_reset(): """Reset tallies, timers, and pseudo-random number generator state.""" _dll.openmc_hard_reset() @@ -315,7 +331,7 @@ def statepoint_write(filename=None, write_source=True): @contextmanager -def run_in_memory(intracomm=None): +def run_in_memory(**kwargs): """Provides context manager for calling OpenMC shared library functions. This function is intended to be used in a 'with' statement and ensures that @@ -331,11 +347,11 @@ def run_in_memory(intracomm=None): Parameters ---------- - intracomm : mpi4py.MPI.Intracomm or None - MPI intracommunicator + **kwargs + All keyword arguments are passed to :func:`init`. """ - init(intracomm=intracomm) + init(**kwargs) try: yield finally: diff --git a/openmc/cmfd.py b/openmc/cmfd.py index c156cd00a3..c13b6991d4 100644 --- a/openmc/cmfd.py +++ b/openmc/cmfd.py @@ -10,25 +10,56 @@ References """ -from collections.abc import Iterable +from collections.abc import Iterable, Mapping from numbers import Real, Integral -from xml.etree import ElementTree as ET import sys +import time +from ctypes import c_int, c_double -from openmc._xml import clean_indentation +import numpy as np +from scipy import sparse + +import openmc.capi from openmc.checkvalue import (check_type, check_length, check_value, check_greater_than, check_less_than) +from openmc.exceptions import OpenMCError + +# See if mpi4py module can be imported, define have_mpi global variable +try: + from mpi4py import MPI + have_mpi = True +except ImportError: + have_mpi = False + +# Maximum/minimum neutron energies +_ENERGY_MAX_NEUTRON = np.inf +_ENERGY_MIN_NEUTRON = 0. + +# Tolerance for detecting zero flux values +_TINY_BIT = 1.e-8 + +# For non-accelerated regions on coarse mesh overlay +_CMFD_NOACCEL = -1 + +# Constant to represent a zero flux "albedo" +_ZERO_FLUX = 999.0 + +# Map that returns index of current direction in numpy current matrix +_CURRENTS = { + 'out_left': 0, 'in_left': 1, 'out_right': 2, 'in_right': 3, + 'out_back': 4, 'in_back': 5, 'out_front': 6, 'in_front': 7, + 'out_bottom': 8, 'in_bottom': 9, 'out_top': 10, 'in_top': 11 +} class CMFDMesh(object): - """A structured Cartesian mesh used for Coarse Mesh Finite Difference (CMFD) - acceleration. + """"A structured Cartesian mesh used for CMFD acceleration. Attributes ---------- lower_left : Iterable of float - The lower-left corner of the structured mesh. If only two coordinates are - given, it is assumed that the mesh is an x-y mesh. + The lower-left corner of the structured mesh. If only two coordinates + are given, it is assumed that the mesh is an x-y mesh. upper_right : Iterable of float The upper-right corner of the structrued mesh. If only two coordinates are given, it is assumed that the mesh is an x-y mesh. @@ -38,31 +69,31 @@ class CMFDMesh(object): The width of mesh cells in each direction. energy : Iterable of float Energy bins in eV, listed in ascending order (e.g. [0.0, 0.625e-1, - 20.0e6]) for CMFD tallies and acceleration. If no energy bins are listed, - OpenMC automatically assumes a one energy group calculation over the - entire energy range. + 20.0e6]) for CMFD tallies and acceleration. If no energy bins are + listed, OpenMC automatically assumes a one energy group calculation + over the entire energy range. albedo : Iterable of float Surface ratio of incoming to outgoing partial currents on global - boundary conditions. They are listed in the following order: -x +x -y +y - -z +z. + boundary conditions. They are listed in the following order: -x +x -y + +y -z +z. map : Iterable of int An optional acceleration map can be specified to overlay on the coarse - mesh spatial grid. If this option is used, a ``1`` is used for a - non-accelerated region and a ``2`` is used for an accelerated region. + mesh spatial grid. If this option is used, a ``0`` is used for a + non-accelerated region and a ``1`` is used for an accelerated region. For a simple 4x4 coarse mesh with a 2x2 fuel lattice surrounded by reflector, the map is: :: - [1, 1, 1, 1, - 1, 2, 2, 1, - 1, 2, 2, 1, - 1, 1, 1, 1] + [0, 0, 0, 0, + 0, 1, 1, 0, + 0, 1, 1, 0, + 0, 0, 0, 0] - Therefore a 2x2 system of equations is solved rather than a 4x4. This is - extremely important to use in reflectors as neutrons will not contribute - to any tallies far away from fission source neutron regions. A ``2`` - must be used to identify any fission source region. + Therefore a 2x2 system of equations is solved rather than a 4x4. This + is extremely important to use in reflectors as neutrons will not + contribute to any tallies far away from fission source neutron regions. + A ``1`` must be used to identify any fission source region. """ @@ -119,12 +150,16 @@ class CMFDMesh(object): def dimension(self, dimension): check_type('CMFD mesh dimension', dimension, Iterable, Integral) check_length('CMFD mesh dimension', dimension, 2, 3) + for d in dimension: + check_greater_than('CMFD mesh dimension', d, 0) self._dimension = dimension @width.setter def width(self, width): check_type('CMFD mesh width', width, Iterable, Real) check_length('CMFD mesh width', width, 2, 3) + for w in width: + check_greater_than('CMFD mesh width', w, 0) self._width = width @energy.setter @@ -147,74 +182,40 @@ class CMFDMesh(object): def map(self, meshmap): check_type('CMFD mesh map', meshmap, Iterable, Integral) for m in meshmap: - check_value('CMFD mesh map', m, [1, 2]) + check_value('CMFD mesh map', m, [0, 1]) self._map = meshmap - def _get_xml_element(self): - element = ET.Element("mesh") - subelement = ET.SubElement(element, "lower_left") - subelement.text = ' '.join(map(str, self._lower_left)) - - if self.upper_right is not None: - subelement = ET.SubElement(element, "upper_right") - subelement.text = ' '.join(map(str, self.upper_right)) - - subelement = ET.SubElement(element, "dimension") - subelement.text = ' '.join(map(str, self.dimension)) - - if self.width is not None: - subelement = ET.SubElement(element, "width") - subelement.text = ' '.join(map(str, self.width)) - - if self.energy is not None: - subelement = ET.SubElement(element, "energy") - subelement.text = ' '.join(map(str, self.energy)) - - if self.albedo is not None: - subelement = ET.SubElement(element, "albedo") - subelement.text = ' '.join(map(str, self.albedo)) - - if self.map is not None: - subelement = ET.SubElement(element, "map") - subelement.text = ' '.join(map(str, self.map)) - - return element - - -class CMFD(object): - r"""Parameters that control the use of coarse-mesh finite difference acceleration - in OpenMC. This corresponds directly to the cmfd.xml input file. +class CMFDRun(object): + r"""Class for running CMFD acceleration through the C API. Attributes ---------- begin : int Batch number at which CMFD calculations should begin dhat_reset : bool - Indicate whether :math:`\widehat{D}` nonlinear CMFD parameters should be - reset to zero before solving CMFD eigenproblem. - display : {'balance', 'dominance', 'entropy', 'source'} - Set one additional CMFD output column. Options are: - - * "balance" - prints the RMS [%] of the resdiual from the neutron balance - equation on CMFD tallies. - * "dominance" - prints the estimated dominance ratio from the CMFD - iterations. - * "entropy" - prints the *entropy* of the CMFD predicted fission source. - * "source" - prints the RMS [%] between the OpenMC fission source and - CMFD fission source. + Indicate whether :math:`\widehat{D}` nonlinear CMFD parameters should + be reset to zero before solving CMFD eigenproblem. + display : dict + Dictionary indicating which CMFD results to output. Note that CMFD + k-effective will always be outputted. Acceptable keys are: + * "balance" - Whether to output RMS [%] of the resdiual from the + neutron balance equation on CMFD tallies (bool) + * "dominance" - Whether to output the estimated dominance ratio from + the CMFD iterations (bool) + * "entropy" - Whether to output the *entropy* of the CMFD predicted + fission source (bool) + * "source" - Whether to ouput the RMS [%] between the OpenMC fission + source and CMFD fission source (bool) downscatter : bool Indicate whether an effective downscatter cross section should be used when using 2-group CMFD. feedback : bool Indicate or not the CMFD diffusion result is used to adjust the weight of fission source neutrons on the next OpenMC batch. Defaults to False. - gauss_seidel_tolerance : Iterable of float - Two parameters specifying the absolute inner tolerance and the relative - inner tolerance for Gauss-Seidel iterations when performing CMFD. - ktol : float + cmfd_ktol : float Tolerance on the eigenvalue when performing CMFD power iteration - cmfd_mesh : openmc.CMFDMesh + mesh : openmc.CMFDMesh Structured mesh to be used for acceleration norm : float Normalization factor applied to the CMFD fission source distribution @@ -222,42 +223,157 @@ class CMFD(object): View convergence of power iteration during CMFD acceleration run_adjoint : bool Perform adjoint calculation on the last batch - shift : float + w_shift : float Optional Wielandt shift parameter for accelerating power iterations. By default, it is very large so there is effectively no impact. - spectral : float - Optional spectral radius that can be used to accelerate the convergence - of Gauss-Seidel iterations during CMFD power iteration. stol : float Tolerance on the fission source when performing CMFD power iteration - tally_reset : list of int + reset : list of int List of batch numbers at which CMFD tallies should be reset write_matrices : bool Write sparse matrices that are used during CMFD acceleration (loss, - production) to file + production) and resultant normalized flux vector phi to file + spectral : float + Optional spectral radius that can be used to accelerate the convergence + of Gauss-Seidel iterations during CMFD power iteration. + gauss_seidel_tolerance : Iterable of float + Two parameters specifying the absolute inner tolerance and the relative + inner tolerance for Gauss-Seidel iterations when performing CMFD. + adjoint_type : {'physical', 'math'} + Stores type of adjoint calculation that should be performed. + ``run_adjoint`` must be true for an adjoint calculation to be + perfomed. Options are: + + * "physical" - Create adjoint matrices from physical parameters of + CMFD problem + * "math" - Create adjoint matrices mathematically as the transpose of + loss and production CMFD matrices + indices : numpy.ndarray + Stores spatial and group dimensions as [nx, ny, nz, ng] + cmfd_src : numpy.ndarray + CMFD source distribution calculated from solving CMFD equations + entropy : list of floats + "Shannon entropy" from cmfd fission source, stored for each generation + that CMFD is invoked + balance : list of floats + RMS of neutron balance equations, stored for each generation that CMFD + is invoked + src_cmp : list of floats + RMS deviation of OpenMC and CMFD normalized source, stored for each + generation that CMFD is invoked + dom : list of floats + Dominance ratio from solving CMFD matrix equations, stored for each + generation that CMFD is invoked + k_cmfd : list of floats + List of CMFD k-effectives, stored for each generation that CMFD is + invoked + time_cmfd : float + Time for entire CMFD calculation, in seconds + time_cmfdbuild : float + Time for building CMFD matrices, in seconds + time_cmfdsolve : float + Time for solving CMFD matrix equations, in seconds + intracomm : mpi4py.MPI.Intracomm or None + MPI intercommunicator for running MPI commands """ def __init__(self): - self._begin = None - self._dhat_reset = None - self._display = None - self._downscatter = None - self._feedback = None - self._gauss_seidel_tolerance = None - self._ktol = None - self._cmfd_mesh = None - self._norm = None - self._power_monitor = None - self._run_adjoint = None - self._shift = None - self._spectral = None - self._stol = None - self._tally_reset = None - self._write_matrices = None + """Constructor for CMFDRun class. Default values for instance variables + set in this method. - self._cmfd_file = ET.Element("cmfd") - self._cmfd_mesh_element = None + """ + # Variables that users can modify + self._begin = 1 + self._dhat_reset = False + self._display = {'balance': False, 'dominance': False, + 'entropy': False, 'source': False} + self._downscatter = False + self._feedback = False + self._cmfd_ktol = 1.e-8 + self._mesh = None + self._norm = 1. + self._power_monitor = False + self._run_adjoint = False + self._w_shift = 1.e6 + self._stol = 1.e-8 + self._reset = [] + self._write_matrices = False + self._spectral = 0.0 + self._gauss_seidel_tolerance = [1.e-10, 1.e-5] + self._adjoint_type = 'physical' + self._intracomm = None + + # External variables used during runtime but users cannot control + self._indices = np.zeros(4, dtype=np.int32) + self._egrid = None + self._albedo = None + self._coremap = None + self._n_resets = 0 + self._mesh_id = None + self._tally_ids = None + self._energy_filters = None + self._cmfd_on = False + self._mat_dim = _CMFD_NOACCEL + self._keff_bal = None + self._keff = None + self._adj_keff = None + self._phi = None + self._adj_phi = None + self._flux = None + self._totalxs = None + self._p1scattxs = None + self._scattxs = None + self._nfissxs = None + self._diffcof = None + self._dtilde = None + self._dhat = None + self._hxyz = None + self._current = None + self._cmfd_src = None + self._openmc_src = None + self._sourcecounts = None + self._weightfactors = None + self._entropy = None + self._balance = None + self._src_cmp = None + self._dom = None + self._k_cmfd = None + self._resnb = None + self._time_cmfd = None + self._time_cmfdbuild = None + self._time_cmfdsolve = None + + # All index-related variables, for numpy vectorization + self._first_x_accel = None + self._last_x_accel = None + self._first_y_accel = None + self._last_y_accel = None + self._first_z_accel = None + self._last_z_accel = None + self._notfirst_x_accel = None + self._notlast_x_accel = None + self._notfirst_y_accel = None + self._notlast_y_accel = None + self._notfirst_z_accel = None + self._notlast_z_accel = None + self._is_adj_ref_left = None + self._is_adj_ref_right = None + self._is_adj_ref_back = None + self._is_adj_ref_front = None + self._is_adj_ref_bottom = None + self._is_adj_ref_top = None + self._accel_idxs = None + self._accel_neig_left_idxs = None + self._accel_neig_right_idxs = None + self._accel_neig_back_idxs = None + self._accel_neig_front_idxs = None + self._accel_neig_bot_idxs = None + self._accel_neig_top_idxs = None + self._loss_row = None + self._loss_col = None + self._prod_row = None + self._prod_col = None @property def begin(self): @@ -280,21 +396,21 @@ class CMFD(object): return self._feedback @property - def gauss_seidel_tolerance(self): - return self._gauss_seidel_tolerance + def cmfd_ktol(self): + return self._cmfd_ktol @property - def ktol(self): - return self._ktol - - @property - def cmfd_mesh(self): - return self._cmfd_mesh + def mesh(self): + return self._mesh @property def norm(self): return self._norm + @property + def adjoint_type(self): + return self._adjoint_type + @property def power_monitor(self): return self._power_monitor @@ -304,25 +420,57 @@ class CMFD(object): return self._run_adjoint @property - def shift(self): - return self._shift - - @property - def spectral(self): - return self._spectral + def w_shift(self): + return self._w_shift @property def stol(self): return self._stol @property - def tally_reset(self): - return self._tally_reset + def spectral(self): + return self._spectral + + @property + def reset(self): + return self._reset @property def write_matrices(self): return self._write_matrices + @property + def gauss_seidel_tolerance(self): + return self._gauss_seidel_tolerance + + @property + def indices(self): + return self._indices + + @property + def cmfd_src(self): + return self._cmfd_src + + @property + def dom(self): + return self._dom + + @property + def src_cmp(self): + return self._src_cmp + + @property + def balance(self): + return self._balance + + @property + def entropy(self): + return self._entropy + + @property + def k_cmfd(self): + return self._k_cmfd + @begin.setter def begin(self, begin): check_type('CMFD begin batch', begin, Integral) @@ -336,10 +484,12 @@ class CMFD(object): @display.setter def display(self, display): - check_type('CMFD display', display, str) - check_value('CMFD display', display, - ['balance', 'dominance', 'entropy', 'source']) - self._display = display + check_type('display', display, Mapping) + for key, value in display.items(): + check_value('display key', key, + ('balance', 'entropy', 'dominance', 'source')) + check_type("display['{}']".format(key), value, bool) + self._display[key] = value @downscatter.setter def downscatter(self, downscatter): @@ -351,28 +501,71 @@ class CMFD(object): check_type('CMFD feedback', feedback, bool) self._feedback = feedback - @gauss_seidel_tolerance.setter - def gauss_seidel_tolerance(self, gauss_seidel_tolerance): - check_type('CMFD Gauss-Seidel tolerance', gauss_seidel_tolerance, - Iterable, Real) - check_length('Gauss-Seidel tolerance', gauss_seidel_tolerance, 2) - self._gauss_seidel_tolerance = gauss_seidel_tolerance + @cmfd_ktol.setter + def cmfd_ktol(self, cmfd_ktol): + check_type('CMFD eigenvalue tolerance', cmfd_ktol, Real) + self._cmfd_ktol = cmfd_ktol - @ktol.setter - def ktol(self, ktol): - check_type('CMFD eigenvalue tolerance', ktol, Real) - self._ktol = ktol + @mesh.setter + def mesh(self, cmfd_mesh): + check_type('CMFD mesh', cmfd_mesh, CMFDMesh) - @cmfd_mesh.setter - def cmfd_mesh(self, mesh): - check_type('CMFD mesh', mesh, CMFDMesh) - self._cmfd_mesh = mesh + # Check dimension defined + if cmfd_mesh.dimension is None: + raise ValueError('CMFD mesh requires spatial ' + 'dimensions to be specified') + + # Check lower left defined + if cmfd_mesh.lower_left is None: + raise ValueError('CMFD mesh requires lower left coordinates ' + 'to be specified') + + # Check that both upper right and width both not defined + if cmfd_mesh.upper_right is not None and cmfd_mesh.width is not None: + raise ValueError('Both upper right coordinates and width ' + 'cannot be specified for CMFD mesh') + + # Check that at least one of width or upper right is defined + if cmfd_mesh.upper_right is None and cmfd_mesh.width is None: + raise ValueError('CMFD mesh requires either upper right ' + 'coordinates or width to be specified') + + # Check width and lower length are same dimension and define + # upper_right + if cmfd_mesh.width is not None: + check_length('CMFD mesh width', cmfd_mesh.width, + len(cmfd_mesh.lower_left)) + cmfd_mesh.upper_right = np.array(cmfd_mesh.lower_left) + \ + np.array(cmfd_mesh.width) * np.array(cmfd_mesh.dimension) + + # Check upper_right and lower length are same dimension and define + # width + elif cmfd_mesh.upper_right is not None: + check_length('CMFD mesh upper right', cmfd_mesh.upper_right, + len(cmfd_mesh.lower_left)) + # Check upper right coordinates are greater than lower left + if np.any(np.array(cmfd_mesh.upper_right) <= + np.array(cmfd_mesh.lower_left)): + raise ValueError('CMFD mesh requires upper right ' + 'coordinates to be greater than lower ' + 'left coordinates') + cmfd_mesh.width = np.true_divide((np.array(cmfd_mesh.upper_right) - + np.array(cmfd_mesh.lower_left)), + np.array(cmfd_mesh.dimension)) + self._mesh = cmfd_mesh @norm.setter def norm(self, norm): check_type('CMFD norm', norm, Real) self._norm = norm + @adjoint_type.setter + def adjoint_type(self, adjoint_type): + check_type('CMFD adjoint type', adjoint_type, str) + check_value('CMFD adjoint type', adjoint_type, + ['math', 'physical']) + self._adjoint_type = adjoint_type + @power_monitor.setter def power_monitor(self, power_monitor): check_type('CMFD power monitor', power_monitor, bool) @@ -383,138 +576,2126 @@ class CMFD(object): check_type('CMFD run adjoint', run_adjoint, bool) self._run_adjoint = run_adjoint - @shift.setter - def shift(self, shift): - check_type('CMFD Wielandt shift', shift, Real) - self._shift = shift - - @spectral.setter - def spectral(self, spectral): - check_type('CMFD spectral radius', spectral, Real) - self._spectral = spectral + @w_shift.setter + def w_shift(self, w_shift): + check_type('CMFD Wielandt shift', w_shift, Real) + self._w_shift = w_shift @stol.setter def stol(self, stol): check_type('CMFD fission source tolerance', stol, Real) self._stol = stol - @tally_reset.setter - def tally_reset(self, tally_reset): - check_type('tally reset batches', tally_reset, Iterable, Integral) - self._tally_reset = tally_reset + @spectral.setter + def spectral(self, spectral): + check_type('CMFD spectral radius', spectral, Real) + self._spectral = spectral + + @reset.setter + def reset(self, reset): + check_type('tally reset batches', reset, Iterable, Integral) + self._reset = reset @write_matrices.setter def write_matrices(self, write_matrices): check_type('CMFD write matrices', write_matrices, bool) self._write_matrices = write_matrices - def _create_begin_subelement(self): - if self._begin is not None: - element = ET.SubElement(self._cmfd_file, "begin") - element.text = str(self._begin) + @gauss_seidel_tolerance.setter + def gauss_seidel_tolerance(self, gauss_seidel_tolerance): + check_type('CMFD Gauss-Seidel tolerance', gauss_seidel_tolerance, + Iterable, Real) + check_length('Gauss-Seidel tolerance', gauss_seidel_tolerance, 2) + self._gauss_seidel_tolerance = gauss_seidel_tolerance - def _create_dhat_reset_subelement(self): - if self._dhat_reset is not None: - element = ET.SubElement(self._cmfd_file, "dhat_reset") - element.text = str(self._dhat_reset).lower() + def run(self, **kwargs): + """Run OpenMC with coarse mesh finite difference acceleration - def _create_display_subelement(self): - if self._display is not None: - element = ET.SubElement(self._cmfd_file, "display") - element.text = str(self._display) + This method is called by user to run CMFD once instance variables of + CMFDRun class are set - def _create_downscatter_subelement(self): - if self._downscatter is not None: - element = ET.SubElement(self._cmfd_file, "downscatter") - element.text = str(self._downscatter).lower() - - def _create_feedback_subelement(self): - if self._feedback is not None: - element = ET.SubElement(self._cmfd_file, "feeback") - element.text = str(self._feedback).lower() - - def _create_gauss_seidel_tolerance_subelement(self): - if self._gauss_seidel_tolerance is not None: - element = ET.SubElement(self._cmfd_file, "gauss_seidel_tolerance") - element.text = ' '.join(map(str, self._gauss_seidel_tolerance)) - - def _create_ktol_subelement(self): - if self._ktol is not None: - element = ET.SubElement(self._ktol, "ktol") - element.text = str(self._ktol) - - def _create_mesh_subelement(self): - if self._cmfd_mesh is not None: - xml_element = self._cmfd_mesh._get_xml_element() - self._cmfd_file.append(xml_element) - - def _create_norm_subelement(self): - if self._norm is not None: - element = ET.SubElement(self._cmfd_file, "norm") - element.text = str(self._norm) - - def _create_power_monitor_subelement(self): - if self._power_monitor is not None: - element = ET.SubElement(self._cmfd_file, "power_monitor") - element.text = str(self._power_monitor).lower() - - def _create_run_adjoint_subelement(self): - if self._run_adjoint is not None: - element = ET.SubElement(self._cmfd_file, "run_adjoint") - element.text = str(self._run_adjoint).lower() - - def _create_shift_subelement(self): - if self._shift is not None: - element = ET.SubElement(self._shift, "shift") - element.text = str(self._shift) - - def _create_spectral_subelement(self): - if self._spectral is not None: - element = ET.SubElement(self._spectral, "spectral") - element.text = str(self._spectral) - - def _create_stol_subelement(self): - if self._stol is not None: - element = ET.SubElement(self._stol, "stol") - element.text = str(self._stol) - - def _create_tally_reset_subelement(self): - if self._tally_reset is not None: - element = ET.SubElement(self._tally_reset, "tally_reset") - element.text = ' '.join(map(str, self._tally_reset)) - - def _create_write_matrices_subelement(self): - if self._write_matrices is not None: - element = ET.SubElement(self._cmfd_file, "write_matrices") - element.text = str(self._write_matrices).lower() - - def export_to_xml(self): - """Create a cmfd.xml file using the class data that can be used for an OpenMC - simulation. + Parameters + ---------- + **kwargs + All keyword arguments are passed to + :func:`openmc.capi.run_in_memory`. """ + # Store intracomm for part of CMFD routine where MPI reduce and + # broadcast calls are made + if 'intracomm' in kwargs and kwargs['intracomm'] is not None: + self._intracomm = kwargs['intracomm'] + elif have_mpi: + self._intracomm = MPI.COMM_WORLD - self._create_begin_subelement() - self._create_dhat_reset_subelement() - self._create_display_subelement() - self._create_downscatter_subelement() - self._create_feedback_subelement() - self._create_gauss_seidel_tolerance_subelement() - self._create_ktol_subelement() - self._create_mesh_subelement() - self._create_norm_subelement() - self._create_power_monitor_subelement() - self._create_run_adjoint_subelement() - self._create_shift_subelement() - self._create_spectral_subelement() - self._create_stol_subelement() - self._create_tally_reset_subelement() - self._create_write_matrices_subelement() + # Run and pass arguments to C API run_in_memory function + with openmc.capi.run_in_memory(**kwargs): + # Configure CMFD parameters and tallies + self._configure_cmfd() - # Clean the indentation in the file to be user-readable - clean_indentation(self._cmfd_file) + # Set up CMFD coremap + self._set_coremap() - # Write the XML Tree to the cmfd.xml file - tree = ET.ElementTree(self._cmfd_file) - tree.write("cmfd.xml", xml_declaration=True, - encoding='utf-8', method="xml") + # Compute and store array indices used to build cross section + # arrays + self._precompute_array_indices() + + # Compute and store row and column indices used to build CMFD + # matrices + self._precompute_matrix_indices() + + # Initialize all variables used for linear solver in C++ + self._initialize_linsolver() + + # Initialize simulation + openmc.capi.simulation_init() + + status = 0 + while status == 0: + # Initialize CMFD batch + self._cmfd_init_batch() + + # Run next batch + status = openmc.capi.next_batch() + + # Perform CMFD calculation if on + if self._cmfd_on: + self._execute_cmfd() + + # Write CMFD output if CMFD on for current batch + if openmc.capi.master(): + self._write_cmfd_output() + + # Finalize simuation + openmc.capi.simulation_finalize() + + # Print out CMFD timing statistics + self._write_cmfd_timing_stats() + + def _initialize_linsolver(self): + # Determine number of rows in CMFD matrix + ng = self._indices[3] + n = self._mat_dim*ng + + # Create temp loss matrix to pass row/col indices to C++ linear solver + loss_row = self._loss_row + loss_col = self._loss_col + temp_data = np.ones(len(loss_row)) + temp_loss = sparse.csr_matrix((temp_data, (loss_row, loss_col)), + shape=(n, n)) + + # Pass coremap as 1-d array of 32-bit integers + coremap = np.swapaxes(self._coremap, 0, 2).flatten().astype(np.int32) + + args = temp_loss.indptr, len(temp_loss.indptr), \ + temp_loss.indices, len(temp_loss.indices), n, \ + self._spectral, self._indices, coremap + return openmc.capi._dll.openmc_initialize_linsolver(*args) + + def _write_cmfd_output(self): + """Write CMFD output to buffer at the end of each batch""" + # Display CMFD k-effective + outstr = '{:>11s}CMFD k: {:0.5f}'.format('', self._k_cmfd[-1]) + # Display value of additional fields based on display dict + outstr += '\n' + if self._display['dominance']: + outstr += ('{:>11s}Dom Rat: {:0.5f}\n' + .format('', self._dom[-1])) + if self._display['entropy']: + outstr += ('{:>11s}CMFD Ent: {:0.5f}\n' + .format('', self._entropy[-1])) + if self._display['source']: + outstr += ('{:>11s}RMS Src: {:0.5f}\n' + .format('', self._src_cmp[-1])) + if self._display['balance']: + outstr += ('{:>11s}RMS Bal: {:0.5f}\n' + .format('', self._balance[-1])) + + print(outstr) + sys.stdout.flush() + + def _write_cmfd_timing_stats(self): + """Write CMFD timing stats to buffer after finalizing simulation""" + if openmc.capi.master(): + outstr = ("=====================> " + "CMFD TIMING STATISTICS <====================\n\n" + " Time in CMFD = {:.5E} seconds\n" + " Building matrices = {:.5E} seconds\n" + " Solving matrices = {:.5E} seconds\n") + print(outstr.format(self._time_cmfd, self._time_cmfdbuild, + self._time_cmfdsolve)) + sys.stdout.flush() + + def _configure_cmfd(self): + """Initialize CMFD parameters and set CMFD input variables""" + # Read in cmfd input defined in Python + self._read_cmfd_input() + + # Initialize timers + self._time_cmfd = 0.0 + self._time_cmfdbuild = 0.0 + self._time_cmfdsolve = 0.0 + + # Initialize all numpy arrays used for CMFD solver + self._allocate_cmfd() + + def _read_cmfd_input(self): + """Sets values of additional instance variables based on user input""" + # Print message to user and flush output to stdout + if openmc.capi.settings.verbosity >= 7 and openmc.capi.master(): + print(' Configuring CMFD parameters for simulation') + sys.stdout.flush() + + # Check if CMFD mesh is defined + if self._mesh is None: + raise ValueError('No CMFD mesh has been specified for ' + 'simulation') + + # Set spatial dimensions of CMFD object + for i, n in enumerate(self._mesh.dimension): + self._indices[i] = n + + # Check if in continuous energy mode + if not openmc.capi.settings.run_CE: + raise OpenMCError('CMFD must be run in continuous energy mode') + + # Set number of energy groups + if self._mesh.energy is not None: + ng = len(self._mesh.energy) + self._egrid = np.array(self._mesh.energy) + self._indices[3] = ng - 1 + self._energy_filters = True + else: + self._egrid = np.array([_ENERGY_MIN_NEUTRON, _ENERGY_MAX_NEUTRON]) + self._indices[3] = 1 + self._energy_filters = False + + # Set global albedo + if self._mesh.albedo is not None: + self._albedo = np.array(self._mesh.albedo) + else: + self._albedo = np.array([1., 1., 1., 1., 1., 1.]) + + # Get acceleration map, otherwise set all regions to be accelerated + if self._mesh.map is not None: + check_length('CMFD coremap', self._mesh.map, + np.product(self._indices[0:3])) + self._coremap = np.array(self._mesh.map) + else: + self._coremap = np.ones((np.product(self._indices[0:3])), + dtype=int) + + # Set number of batches where cmfd tallies should be reset + if self._reset is not None: + self._n_resets = len(self._reset) + + # Create tally objects + self._create_cmfd_tally() + + def _allocate_cmfd(self): + """Allocates all numpy arrays and lists used in CMFD algorithm""" + # Extract spatial and energy indices + nx = self._indices[0] + ny = self._indices[1] + nz = self._indices[2] + ng = self._indices[3] + + # Allocate flux, cross sections and diffusion coefficient + self._flux = np.zeros((nx, ny, nz, ng)) + self._totalxs = np.zeros((nx, ny, nz, ng)) + self._p1scattxs = np.zeros((nx, ny, nz, ng)) + self._scattxs = np.zeros((nx, ny, nz, ng, ng)) # Incoming, outgoing + self._nfissxs = np.zeros((nx, ny, nz, ng, ng)) # Incoming, outgoing + self._diffcof = np.zeros((nx, ny, nz, ng)) + + # Allocate dtilde and dhat + self._dtilde = np.zeros((nx, ny, nz, ng, 6)) + self._dhat = np.zeros((nx, ny, nz, ng, 6)) + + # Allocate dimensions for each mesh cell + self._hxyz = np.zeros((nx, ny, nz, 3)) + + # Allocate surface currents + self._current = np.zeros((nx, ny, nz, 12, ng)) + + # Allocate source distributions + self._cmfd_src = np.zeros((nx, ny, nz, ng)) + self._openmc_src = np.zeros((nx, ny, nz, ng)) + + # Allocate source weight modification variables + self._sourcecounts = np.zeros((nx*ny*nz, ng)) + self._weightfactors = np.ones((nx, ny, nz, ng)) + + # Allocate batchwise parameters + self._entropy = [] + self._balance = [] + self._src_cmp = [] + self._dom = [] + self._k_cmfd = [] + + def _cmfd_init_batch(self): + """Handles CMFD options at the beginning of each batch""" + # Get current batch through C API + # Add 1 as next_batch has not been called yet + current_batch = openmc.capi.current_batch() + 1 + + # Check to activate CMFD diffusion and possible feedback + if self._begin == current_batch: + self._cmfd_on = True + + # Check to reset tallies + if self._n_resets > 0 and current_batch in self._reset: + self._cmfd_tally_reset() + + def _execute_cmfd(self): + """Runs CMFD calculation on master node""" + # Run CMFD on single processor on master + if openmc.capi.master(): + # Start CMFD timer + time_start_cmfd = time.time() + + # Create CMFD data from OpenMC tallies + self._set_up_cmfd() + + # Call solver + self._cmfd_solver_execute() + + # Store k-effective + self._k_cmfd.append(self._keff) + + # Check to perform adjoint on last batch + if (openmc.capi.current_batch() == openmc.capi.settings.batches + and self._run_adjoint): + self._cmfd_solver_execute(adjoint=True) + + # Calculate fission source + self._calc_fission_source() + + # Calculate weight factors + self._cmfd_reweight(True) + + # Stop CMFD timer + if openmc.capi.master(): + time_stop_cmfd = time.time() + self._time_cmfd += time_stop_cmfd - time_start_cmfd + + def _cmfd_tally_reset(self): + """Resets all CMFD tallies in memory""" + # Print message + if openmc.capi.settings.verbosity >= 6 and openmc.capi.master(): + print(' CMFD tallies reset') + sys.stdout.flush() + + # Reset CMFD tallies + tallies = openmc.capi.tallies + for tally_id in self._tally_ids: + tallies[tally_id].reset() + + def _set_up_cmfd(self): + """Configures CMFD object for a CMFD eigenvalue calculation + + """ + # Calculate all cross sections based on reaction rates from last batch + self._compute_xs() + + # Compute effective downscatter cross section + if self._downscatter: + self._compute_effective_downscatter() + + # Check neutron balance + self._neutron_balance() + + # Calculate dtilde + self._compute_dtilde() + + # Calculate dhat + self._compute_dhat() + + def _cmfd_solver_execute(self, adjoint=False): + """Sets up and runs power iteration solver for CMFD + + Parameters + ---------- + adjoint : bool + Whether or not to run an adjoint calculation + + """ + # Check for physical adjoint + physical_adjoint = adjoint and self._adjoint_type == 'physical' + + # Start timer for build + time_start_buildcmfd = time.time() + + # Build the loss and production matrices + if not adjoint: + # Build matrices without adjoint calculation + loss = self._build_loss_matrix(False) + prod = self._build_prod_matrix(False) + else: + # Build adjoint matrices by running adjoint calculation + if self._adjoint_type == 'physical': + loss = self._build_loss_matrix(True) + prod = self._build_prod_matrix(True) + # Build adjoint matrices as transpose of non-adjoint matrices + else: + loss = self._build_loss_matrix(False).transpose() + prod = self._build_prod_matrix(False).transpose() + + # Write out the matrices. + if self._write_matrices: + if not adjoint: + self._write_matrix(loss, 'loss') + self._write_matrix(prod, 'prod') + else: + self._write_matrix(loss, 'adj_loss') + self._write_matrix(prod, 'adj_prod') + + # Stop timer for build + time_stop_buildcmfd = time.time() + self._time_cmfdbuild += time_stop_buildcmfd - time_start_buildcmfd + + # Begin power iteration + time_start_solvecmfd = time.time() + phi, keff, dom = self._execute_power_iter(loss, prod) + time_stop_solvecmfd = time.time() + self._time_cmfdsolve += time_stop_solvecmfd - time_start_solvecmfd + + # Save results, normalizing phi to sum to 1 + if adjoint: + self._adj_keff = keff + self._adj_phi = phi/np.sqrt(np.sum(phi*phi)) + else: + self._keff = keff + self._phi = phi/np.sqrt(np.sum(phi*phi)) + + self._dom.append(dom) + + # Write out flux vector + if self._write_matrices: + if adjoint: + self._write_vector(self._adj_phi, 'adj_fluxvec') + else: + self._write_vector(self._phi, 'fluxvec') + + def _write_vector(self, vector, base_filename): + """Write a 1-D numpy array to file and also save it in .npy format. + This particular format allows users to load the variable directly in a + Python session with np.load() + + Parameters + ---------- + vector : numpy.ndarray + Vector that will be saved + base_filename : str + Filename to save vector as, without any file extension at the end. + Vector will be saved to file [base_filename].dat and in numpy + format as [base_filename].npy + + """ + # Write each element in vector to file + with open(base_filename+'.dat', 'w') as fh: + for val in vector: + fh.write('{:0.8f}\n'.format(val)) + + # Save as numpy format + np.save(base_filename, vector) + + def _write_matrix(self, matrix, base_filename): + """Write a numpy matrix to file and also save it in .npz format. This + particular format allows users to load the variable directly in a + Python session with scipy.sparse.load_npz() + + Parameters + ---------- + matrix : scipy.sparse.spmatrix + Sparse matrix that will be saved + base_filename : str + Filename to save matrix entries, without any file extension at the + end. Matrix entries will be saved to file [base_filename].dat and + in scipy format as [base_filename].npz + + """ + # Write row, col, and data of each entry in sparse matrix. This ignores + # all zero-entries, and indices are written with zero-based indexing + with open(base_filename+'.dat', 'w') as fh: + for row in range(matrix.shape[0]): + # Get all cols for particular row in matrix + cols = matrix.indices[matrix.indptr[row]:matrix.indptr[row+1]] + # Get all data entries for particular row in matrix + data = matrix.data[matrix.indptr[row]:matrix.indptr[row+1]] + for i in range(len(cols)): + fh.write('{:3d}, {:3d}, {:0.8f}\n'.format( + row, cols[i], data[i])) + + # Save matrix in scipy format + sparse.save_npz(base_filename, matrix) + + def _calc_fission_source(self): + """Calculates CMFD fission source from CMFD flux. If a coremap is + defined, there will be a discrepancy between the spatial indices in the + variables ``phi`` and ``nfissxs``, so ``phi`` needs to be mapped to the + spatial indices of the cross sections. This can be done in a vectorized + numpy manner or with for loops + + """ + # Extract number of groups and number of accelerated regions + nx = self._indices[0] + ny = self._indices[1] + nz = self._indices[2] + ng = self._indices[3] + n = self._mat_dim + + # Reset CMFD source to 0 + self._cmfd_src.fill(0.) + + # Compute cmfd_src in a vecotorized manner by phi to the spatial + # indices of the actual problem so that cmfd_flux can be multiplied by + # nfissxs + + # Calculate volume + vol = np.product(self._hxyz, axis=3) + + # Reshape phi by number of groups + phi = self._phi.reshape((n, ng)) + + # Extract indices of coremap that are accelerated + idx = self._accel_idxs + + # Initialize CMFD flux map that maps phi to actual spatial and + # group indices of problem + cmfd_flux = np.zeros((nx, ny, nz, ng)) + + # Loop over all groups and set CMFD flux based on indices of + # coremap and values of phi + for g in range(ng): + phi_g = phi[:,g] + cmfd_flux[idx + (g,)] = phi_g[self._coremap[idx]] + + # Compute fission source + cmfd_src = (np.sum(self._nfissxs[:,:,:,:,:] * + cmfd_flux[:,:,:,:,np.newaxis], axis=3) * + vol[:,:,:,np.newaxis]) + + # Normalize source such that it sums to 1.0 + self._cmfd_src = cmfd_src / np.sum(cmfd_src) + + # Compute entropy + if openmc.capi.settings.entropy_on: + # Compute source times log_2(source) + source = self._cmfd_src[self._cmfd_src > 0] \ + * np.log(self._cmfd_src[self._cmfd_src > 0])/np.log(2) + + # Sum source and store + self._entropy.append(-1.0 * np.sum(source)) + + # Normalize source so average is 1.0 + self._cmfd_src = self._cmfd_src/np.sum(self._cmfd_src) * self._norm + + # Calculate differences between normalized sources + self._src_cmp.append(np.sqrt(1.0 / self._norm + * np.sum((self._cmfd_src - self._openmc_src)**2))) + + def _cmfd_reweight(self, new_weights): + """Performs weighting of particles in source bank + + Parameters + ---------- + new_weights : bool + Whether to reweight particles or not + + """ + # Compute new weight factors + if new_weights: + + # Get spatial dimensions and energy groups + nx = self._indices[0] + ny = self._indices[1] + nz = self._indices[2] + ng = self._indices[3] + + # Set weight factors to default 1.0 + self._weightfactors.fill(1.0) + + # Count bank site in mesh and reverse due to egrid structured + outside = self._count_bank_sites() + + # Check and raise error if source sites exist outside of CMFD mesh + if openmc.capi.master() and outside: + raise OpenMCError('Source sites outside of the CMFD mesh') + + # Have master compute weight factors, ignore any zeros in + # sourcecounts or cmfd_src + if openmc.capi.master(): + # Compute normalization factor + norm = np.sum(self._sourcecounts) / np.sum(self._cmfd_src) + + # Define target reshape dimensions for sourcecounts. This + # defines how self._sourcecounts is ordered by dimension + target_shape = [nz, ny, nx, ng] + + # Reshape sourcecounts to target shape. Swap x and z axes so + # that the shape is now [nx, ny, nz, ng] + sourcecounts = np.swapaxes( + self._sourcecounts.reshape(target_shape), 0, 2) + + # Flip index of energy dimension + sourcecounts = np.flip(sourcecounts, axis=3) + + # Compute weight factors + div_condition = np.logical_and(sourcecounts > 0, + self._cmfd_src > 0) + with np.errstate(divide='ignore', invalid='ignore'): + self._weightfactors = (np.divide(self._cmfd_src * norm, + sourcecounts, where=div_condition, + out=np.ones_like(self._cmfd_src))) + + if not self._feedback: + return + + # Broadcast weight factors to all procs + if have_mpi: + self._weightfactors = self._intracomm.bcast( + self._weightfactors) + + m = openmc.capi.meshes[self._mesh_id] + energy = self._egrid + ng = self._indices[3] + + # Get xyz locations and energies of all particles in source bank + source_xyz = openmc.capi.source_bank()['xyz'] + source_energies = openmc.capi.source_bank()['E'] + + # Convert xyz location to the CMFD mesh index + mesh_ijk = np.floor((source_xyz-m.lower_left)/m.width).astype(int) + + # Determine which energy bin each particle's energy belongs to + # Separate into cases bases on where source energies lies on egrid + energy_bins = np.zeros(len(source_energies), dtype=int) + idx = np.where(source_energies < energy[0]) + energy_bins[idx] = ng - 1 + idx = np.where(source_energies > energy[-1]) + energy_bins[idx] = 0 + idx = np.where((source_energies >= energy[0]) & + (source_energies <= energy[-1])) + energy_bins[idx] = ng - np.digitize(source_energies, energy) + + # Determine weight factor of each particle based on its mesh index + # and energy bin and updates its weight + openmc.capi.source_bank()['wgt'] *= self._weightfactors[ + mesh_ijk[:,0], mesh_ijk[:,1], mesh_ijk[:,2], energy_bins] + + if openmc.capi.master() and np.any(source_energies < energy[0]): + print(' WARNING: Source pt below energy grid') + sys.stdout.flush() + if openmc.capi.master() and np.any(source_energies > energy[-1]): + print(' WARNING: Source pt above energy grid') + sys.stdout.flush() + + def _count_bank_sites(self): + """Determines the number of fission bank sites in each cell of a given + mesh and energy group structure. + Returns + ------- + bool + Wheter any source sites outside of CMFD mesh were found + + """ + # Initialize variables + m = openmc.capi.meshes[self._mesh_id] + bank = openmc.capi.source_bank() + energy = self._egrid + sites_outside = np.zeros(1, dtype=bool) + ng = self._indices[3] + + outside = np.zeros(1, dtype=bool) + count = np.zeros(self._sourcecounts.shape) + + # Get xyz locations and energies of each particle in source bank + source_xyz = openmc.capi.source_bank()['xyz'] + source_energies = openmc.capi.source_bank()['E'] + + # Convert xyz location to mesh index and ravel index to scalar + mesh_locations = np.floor((source_xyz - m.lower_left) / m.width) + mesh_bins = mesh_locations[:,2] * m.dimension[1] * m.dimension[0] + \ + mesh_locations[:,1] * m.dimension[0] + mesh_locations[:,0] + + # Check if any source locations lie outside of defined CMFD mesh + if np.any(mesh_bins < 0) or np.any(mesh_bins >= np.prod(m.dimension)): + outside[0] = True + + # Determine which energy bin each particle's energy belongs to + # Separate into cases bases on where source energies lies on egrid + energy_bins = np.zeros(len(source_energies), dtype=int) + idx = np.where(source_energies < energy[0]) + energy_bins[idx] = 0 + idx = np.where(source_energies > energy[-1]) + energy_bins[idx] = ng - 1 + idx = np.where((source_energies >= energy[0]) & + (source_energies <= energy[-1])) + energy_bins[idx] = np.digitize(source_energies, energy) - 1 + + # Determine all unique combinations of mesh bin and energy bin, and + # count number of particles that belong to these combinations + idx, counts = np.unique(np.array([mesh_bins, energy_bins]), axis=1, + return_counts=True) + + # Store counts to appropriate mesh-energy combination + count[idx[0].astype(int), idx[1].astype(int)] = counts + + if have_mpi: + # Collect values of count from all processors + self._intracomm.Reduce(count, self._sourcecounts, MPI.SUM) + # Check if there were sites outside the mesh for any processor + self._intracomm.Reduce(outside, sites_outside, MPI.LOR) + # Deal with case if MPI not defined (only one proc) + else: + sites_outside = outside + self._sourcecounts = count + + return sites_outside[0] + + def _build_loss_matrix(self, adjoint): + # Extract spatial and energy indices and define matrix dimension + ng = self._indices[3] + n = self._mat_dim*ng + + # Define data entries used to build csr matrix + data = np.array([]) + + dtilde_left = self._dtilde[:,:,:,:,0] + dtilde_right = self._dtilde[:,:,:,:,1] + dtilde_back = self._dtilde[:,:,:,:,2] + dtilde_front = self._dtilde[:,:,:,:,3] + dtilde_bottom = self._dtilde[:,:,:,:,4] + dtilde_top = self._dtilde[:,:,:,:,5] + dhat_left = self._dhat[:,:,:,:,0] + dhat_right = self._dhat[:,:,:,:,1] + dhat_back = self._dhat[:,:,:,:,2] + dhat_front = self._dhat[:,:,:,:,3] + dhat_bottom = self._dhat[:,:,:,:,4] + dhat_top = self._dhat[:,:,:,:,5] + + dx = self._hxyz[:,:,:,np.newaxis,0] + dy = self._hxyz[:,:,:,np.newaxis,1] + dz = self._hxyz[:,:,:,np.newaxis,2] + + # Define net leakage coefficient for each surface in each matrix + # element + jnet = (((dtilde_right + dhat_right)-(-1.0 * dtilde_left + dhat_left)) + / dx + + ((dtilde_front + dhat_front)-(-1.0 * dtilde_back + dhat_back)) + / dy + + ((dtilde_top + dhat_top)-(-1.0 * dtilde_bottom + dhat_bottom)) + / dz) + + for g in range(ng): + # Define leakage terms that relate terms to their neighbors to the + # left + dtilde = self._dtilde[:,:,:,g,0][self._accel_neig_left_idxs] + dhat = self._dhat[:,:,:,g,0][self._accel_neig_left_idxs] + dx = self._hxyz[:,:,:,0][self._accel_neig_left_idxs] + vals = (-1.0 * dtilde - dhat) / dx + # Store data to add to CSR matrix + data = np.append(data, vals) + + # Define leakage terms that relate terms to their neighbors to the + # right + dtilde = self._dtilde[:,:,:,g,1][self._accel_neig_right_idxs] + dhat = self._dhat[:,:,:,g,1][self._accel_neig_right_idxs] + dx = self._hxyz[:,:,:,0][self._accel_neig_right_idxs] + vals = (-1.0 * dtilde + dhat) / dx + # Store data to add to CSR matrix + data = np.append(data, vals) + + # Define leakage terms that relate terms to their neighbors in the + # back + dtilde = self._dtilde[:,:,:,g,2][self._accel_neig_back_idxs] + dhat = self._dhat[:,:,:,g,2][self._accel_neig_back_idxs] + dy = self._hxyz[:,:,:,1][self._accel_neig_back_idxs] + vals = (-1.0 * dtilde - dhat) / dy + # Store data to add to CSR matrix + data = np.append(data, vals) + + # Define leakage terms that relate terms to their neighbors in the + # front + dtilde = self._dtilde[:,:,:,g,3][self._accel_neig_front_idxs] + dhat = self._dhat[:,:,:,g,3][self._accel_neig_front_idxs] + dy = self._hxyz[:,:,:,1][self._accel_neig_front_idxs] + vals = (-1.0 * dtilde + dhat) / dy + # Store data to add to CSR matrix + data = np.append(data, vals) + + # Define leakage terms that relate terms to their neighbors to the + # bottom + dtilde = self._dtilde[:,:,:,g,4][self._accel_neig_bot_idxs] + dhat = self._dhat[:,:,:,g,4][self._accel_neig_bot_idxs] + dz = self._hxyz[:,:,:,2][self._accel_neig_bot_idxs] + vals = (-1.0 * dtilde - dhat) / dz + # Store data to add to CSR matrix + data = np.append(data, vals) + + # Define leakage terms that relate terms to their neighbors to the + # top + dtilde = self._dtilde[:,:,:,g,5][self._accel_neig_top_idxs] + dhat = self._dhat[:,:,:,g,5][self._accel_neig_top_idxs] + dz = self._hxyz[:,:,:,2][self._accel_neig_top_idxs] + vals = (-1.0 * dtilde + dhat) / dz + # Store data to add to CSR matrix + data = np.append(data, vals) + + # Define terms that relate to loss of neutrons in a cell. These + # correspond to all the diagonal entries of the loss matrix + jnet_g = jnet[:,:,:,g][self._accel_idxs] + total_xs = self._totalxs[:,:,:,g][self._accel_idxs] + scatt_xs = self._scattxs[:,:,:,g,g][self._accel_idxs] + vals = jnet_g + total_xs - scatt_xs + # Store data to add to CSR matrix + data = np.append(data, vals) + + # Define terms that relate to in-scattering from group to group. + # These terms relate a mesh index to all mesh indices with the same + # spatial dimensions but belong to a different energy group + for h in range(ng): + if h != g: + # Get scattering macro xs, transposed + if adjoint: + scatt_xs = self._scattxs[:,:,:,g,h][self._accel_idxs] + # Get scattering macro xs + else: + scatt_xs = self._scattxs[:,:,:,h,g][self._accel_idxs] + vals = -1.0 * scatt_xs + # Store data to add to CSR matrix + data = np.append(data, vals) + + # Create csr matrix + loss_row = self._loss_row + loss_col = self._loss_col + loss = sparse.csr_matrix((data, (loss_row, loss_col)), shape=(n, n)) + return loss + + def _build_prod_matrix(self, adjoint): + # Extract spatial and energy indices and define matrix dimension + ng = self._indices[3] + n = self._mat_dim*ng + + # Define rows, columns, and data used to build csr matrix + data = np.array([]) + + # Define terms that relate to fission production from group to group. + for g in range(ng): + for h in range(ng): + # Get nu-fission macro xs, transposed + if adjoint: + vals = (self._nfissxs[:, :, :, g, h])[self._accel_idxs] + # Get nu-fission macro xs + else: + vals = (self._nfissxs[:, :, :, h, g])[self._accel_idxs] + # Store rows, cols, and data to add to CSR matrix + data = np.append(data, vals) + + # Create csr matrix + prod_row = self._prod_row + prod_col = self._prod_col + prod = sparse.csr_matrix((data, (prod_row, prod_col)), shape=(n, n)) + return prod + + def _execute_power_iter(self, loss, prod): + """Main power iteration routine for the CMFD calculation + + Parameters + ---------- + loss : scipy.sparse.spmatrix + Sparse matrix storing elements of CMFD loss matrix + prod : scipy.sparse.spmatrix + Sparse matrix storing elements of CMFD production matrix + + Returns + ------- + phi_n : numpy.ndarray + Flux vector of CMFD problem + k_n : float + Eigenvalue of CMFD problem + dom : float + Dominance ratio of CMFD problem + + """ + # Get problem size + n = loss.shape[0] + + # Set up tolerances for C++ solver + atoli = self._gauss_seidel_tolerance[0] + rtoli = self._gauss_seidel_tolerance[1] + toli = rtoli * 100 + + # Set up flux vectors, intital guess set to 1 + phi_n = np.ones((n,)) + phi_o = np.ones((n,)) + + # Set up source vectors + s_n = np.zeros((n,)) + s_o = np.zeros((n,)) + + # Set initial guess + k_n = openmc.capi.keff()[0] + k_o = k_n + dw = self._w_shift + k_s = k_o + dw + k_ln = 1.0/(1.0/k_n - 1.0/k_s) + k_lo = k_ln + + # Set norms to 0 + norm_n = 0.0 + norm_o = 0.0 + + # Maximum number of power iterations + maxits = 10000 + + # Perform Wielandt shift + loss -= 1.0/k_s*prod + + # Begin power iteration + for i in range(maxits): + # Check if reach max number of iterations + if i == maxits - 1: + raise OpenMCError('Reached maximum iterations in CMFD power ' + 'iteration solver.') + + # Compute source vector + s_o = prod.dot(phi_o) + + # Normalize source vector + s_o /= k_lo + + # Compute new flux with C++ solver + innerits = openmc.capi._dll.openmc_run_linsolver(loss.data, s_o, + phi_n, toli) + + # Compute new source vector + s_n = prod.dot(phi_n) + + # Compute new shifted eigenvalue + k_ln = np.sum(s_n) / np.sum(s_o) + + # Compute new eigenvalue + k_n = 1.0/(1.0/k_ln + 1.0/k_s) + + # Renormalize the old source + s_o *= k_lo + + # Check convergence + iconv, norm_n = self._check_convergence(s_n, s_o, k_n, k_o, i+1, + innerits) + + # If converged, calculate dominance ratio and break from loop + if iconv: + dom = norm_n / norm_o + return phi_n, k_n, dom + + # Record old values if not converged + phi_o = phi_n + k_o = k_n + k_lo = k_ln + norm_o = norm_n + + # Update tolerance for inner iterations + toli = max(atoli, rtoli*norm_n) + + def _check_convergence(self, s_n, s_o, k_n, k_o, iter, innerits): + """Checks the convergence of the CMFD problem + + Parameters + ---------- + s_n : numpy.ndarray + Source vector from current iteration + s_o : numpy.ndarray + Source vector from previous iteration + k_n : float + K-effective from current iteration + k_o : float + K-effective from previous iteration + iter: int + Iteration number + innerits: int + Number of iterations required for convergence in inner GS loop + + Returns + ------- + iconv : bool + Whether the power iteration has reached convergence + serr : float + Error in source from previous iteration to current iteration, used + for dominance ratio calculations + + """ + # Calculate error in keff + kerr = abs(k_o - k_n) / k_n + + # Calculate max error in source + with np.errstate(divide='ignore', invalid='ignore'): + serr = np.sqrt(np.sum(np.where(s_n > 0, ((s_n-s_o) / s_n)**2, 0)) + / len(s_n)) + + # Check for convergence + iconv = kerr < self._cmfd_ktol and serr < self._stol + + # Print out to user + if self._power_monitor and openmc.capi.master(): + str1 = ' {:d}:'.format(iter) + str2 = 'k-eff: {:0.8f}'.format(k_n) + str3 = 'k-error: {:.5E}'.format(kerr) + str4 = 'src-error: {:.5E}'.format(serr) + str5 = ' {:d}'.format(innerits) + print('{:8s}{:20s}{:25s}{:s}{:s}'.format(str1, str2, str3, str4, + str5)) + sys.stdout.flush() + + return iconv, serr + + def _set_coremap(self): + """Sets the core mapping information. All regions marked with zero + are set to CMFD_NOACCEL, while all regions marked with 1 are set to a + unique index that maps each fuel region to a row number when building + CMFD matrices + + """ + # Set number of accelerated regions in problem. This will be related to + # the dimension of CMFD matrices + self._mat_dim = np.sum(self._coremap) + + # Define coremap as cumulative sum over accelerated regions, + # otherwise set value to _CMFD_NOACCEL + self._coremap = np.where(self._coremap == 0, _CMFD_NOACCEL, + np.cumsum(self._coremap)-1) + + # Reshape coremap to three dimensional array + # Indices of coremap in user input switched in x and z axes + nx = self._indices[0] + ny = self._indices[1] + nz = self._indices[2] + self._coremap = self._coremap.reshape(nz, ny, nx) + self._coremap = np.swapaxes(self._coremap, 0, 2) + + def _compute_xs(self): + """Takes CMFD tallies from OpenMC and computes macroscopic cross + sections, flux, and diffusion coefficients for each mesh cell + + """ + # Extract spatial and energy indices + nx = self._indices[0] + ny = self._indices[1] + nz = self._indices[2] + ng = self._indices[3] + + # Set flux object and source distribution all to zeros + self._flux.fill(0.) + self._openmc_src.fill(0.) + + # Set mesh widths + self._hxyz[:,:,:,:] = openmc.capi.meshes[self._mesh_id].width + + # Reset keff_bal to zero + self._keff_bal = 0. + + # Get tallies in-memory + tallies = openmc.capi.tallies + + # Ravel coremap as 1d array similar to how tally data is arranged + coremap = np.ravel(self._coremap.swapaxes(0, 2)) + + # Set conditional numpy array as boolean vector based on coremap + # Repeat each value for number of groups in problem + is_cmfd_accel = np.repeat(coremap != _CMFD_NOACCEL, ng) + + # Get flux from CMFD tally 0 + tally_id = self._tally_ids[0] + tally_results = tallies[tally_id].results[:,0,1] + flux = np.where(is_cmfd_accel, tally_results, 0.) + + # Detect zero flux, abort if located + if np.any(flux[is_cmfd_accel] < _TINY_BIT): + # Get index of zero flux in flux array + idx = np.argmax(np.where(is_cmfd_accel, flux, 1) < _TINY_BIT) + + # Convert scalar idx to index in flux matrix + mat_idx = np.unravel_index(idx, self._flux.shape) + + # Throw error message (one-based indexing) + # Index of group is flipped + err_message = 'Detected zero flux without coremap overlay' + \ + ' at mesh: (' + \ + ', '.join(str(i+1) for i in mat_idx[:-1]) + \ + ') in group ' + str(ng-mat_idx[-1]) + raise OpenMCError(err_message) + + # Define target tally reshape dimensions. This defines how openmc + # tallies are ordered by dimension + target_tally_shape = [nz, ny, nx, ng] + + # Reshape flux array to target shape. Swap x and z axes so that + # flux shape is now [nx, ny, nz, ng] + reshape_flux = np.swapaxes(flux.reshape(target_tally_shape), 0, 2) + + # Flip energy axis as tally results are given in reverse order of + # energy group + self._flux = np.flip(reshape_flux, axis=3) + + # Get total rr and convert to total xs from CMFD tally 0 + tally_results = tallies[tally_id].results[:,1,1] + with np.errstate(divide='ignore', invalid='ignore'): + totalxs = np.divide(tally_results, flux, + where=flux > 0, + out=np.zeros_like(tally_results)) + + # Reshape totalxs array to target shape. Swap x and z axes so that + # shape is now [nx, ny, nz, ng] + reshape_totalxs = np.swapaxes(totalxs.reshape(target_tally_shape), + 0, 2) + + # Total xs is flipped in energy axis as tally results are given in + # reverse order of energy group + self._totalxs = np.flip(reshape_totalxs, axis=3) + + # Get scattering xs from CMFD tally 1 + # flux is repeated to account for extra dimensionality of scattering xs + tally_id = self._tally_ids[1] + tally_results = tallies[tally_id].results[:,0,1] + with np.errstate(divide='ignore', invalid='ignore'): + scattxs = np.divide(tally_results, np.repeat(flux, ng), + where=np.repeat(flux > 0, ng), + out=np.zeros_like(tally_results)) + + # Define target tally reshape dimensions for xs with incoming + # and outgoing energies + target_tally_shape = [nz, ny, nx, ng, ng] + + # Reshape scattxs array to target shape. Swap x and z axes so that + # shape is now [nx, ny, nz, ng, ng] + reshape_scattxs = np.swapaxes(scattxs.reshape(target_tally_shape), + 0, 2) + + # Scattering xs is flipped in both incoming and outgoing energy axes + # as tally results are given in reverse order of energy group + self._scattxs = np.flip(reshape_scattxs, axis=3) + self._scattxs = np.flip(self._scattxs, axis=4) + + # Get nu-fission xs from CMFD tally 1 + # flux is repeated to account for extra dimensionality of nu-fission xs + tally_results = tallies[tally_id].results[:,1,1] + num_realizations = tallies[tally_id].num_realizations + with np.errstate(divide='ignore', invalid='ignore'): + nfissxs = np.divide(tally_results, np.repeat(flux, ng), + where=np.repeat(flux > 0, ng), + out=np.zeros_like(tally_results)) + + # Reshape nfissxs array to target shape. Swap x and z axes so that + # shape is now [nx, ny, nz, ng, ng] + reshape_nfissxs = np.swapaxes(nfissxs.reshape(target_tally_shape), + 0, 2) + + # Nu-fission xs is flipped in both incoming and outgoing energy axes + # as tally results are given in reverse order of energy group + self._nfissxs = np.flip(reshape_nfissxs, axis=3) + self._nfissxs = np.flip(self._nfissxs, axis=4) + + # Openmc source distribution is sum of nu-fission rr in incoming + # energies + self._openmc_src = np.sum(self._nfissxs*self._flux[:,:,:,:,np.newaxis], + axis=3) + + # Compute k_eff from source distribution + self._keff_bal = np.sum(self._openmc_src) / num_realizations + + # Normalize openmc source distribution + self._openmc_src /= np.sum(self._openmc_src) * self._norm + + # Get surface currents from CMFD tally 2 + tally_id = self._tally_ids[2] + tally_results = tallies[tally_id].results[:,0,1] + + # Filter tally results to include only accelerated regions + current = np.where(np.repeat(flux > 0, 12), tally_results, 0.) + + # Define target tally reshape dimensions for current + target_tally_shape = [nz, ny, nx, 12, ng] + + # Reshape current array to target shape. Swap x and z axes so that + # shape is now [nx, ny, nz, ng, 12] + reshape_current = np.swapaxes(current.reshape(target_tally_shape), + 0, 2) + + # Current is flipped in energy axis as tally results are given in + # reverse order of energy group + self._current = np.flip(reshape_current, axis=4) + + # Get p1 scatter xs from CMFD tally 3 + tally_id = self._tally_ids[3] + tally_results = tallies[tally_id].results[:,0,1] + + # Define target tally reshape dimensions for p1 scatter tally + target_tally_shape = [nz, ny, nx, 2, ng] + + # Reshape and extract only p1 data from tally results as there is + # no need for p0 data + p1scattrr = np.swapaxes(tally_results.reshape(target_tally_shape), + 0, 2)[:,:,:,1,:] + + # Store p1 scatter xs + # p1 scatter xs is flipped in energy axis as tally results are given in + # reverse order of energy group + with np.errstate(divide='ignore', invalid='ignore'): + self._p1scattxs = np.divide(np.flip(p1scattrr, axis=3), self._flux, + where=self._flux > 0, + out=np.zeros_like(p1scattrr)) + + # Calculate and store diffusion coefficient + with np.errstate(divide='ignore', invalid='ignore'): + self._diffcof = np.where(self._flux > 0, 1.0 / (3.0 * + (self._totalxs - self._p1scattxs)), 0.) + + def _compute_effective_downscatter(self): + """Changes downscatter rate for zero upscatter""" + # Extract energy index + ng = self._indices[3] + + # Return if not two groups + if ng != 2: + return + + # Extract cross sections and flux for each group + flux1 = self._flux[:,:,:,0] + flux2 = self._flux[:,:,:,1] + sigt1 = self._totalxs[:,:,:,0] + sigt2 = self._totalxs[:,:,:,1] + + # First energy index is incoming energy, second is outgoing energy + sigs11 = self._scattxs[:,:,:,0,0] + sigs21 = self._scattxs[:,:,:,1,0] + sigs12 = self._scattxs[:,:,:,0,1] + sigs22 = self._scattxs[:,:,:,1,1] + + # Compute absorption xs + siga1 = sigt1 - sigs11 - sigs12 + siga2 = sigt2 - sigs22 - sigs21 + + # Compute effective downscatter XS + with np.errstate(divide='ignore', invalid='ignore'): + sigs12_eff = sigs12 - sigs21 * np.divide(flux2, flux1, + where=flux1 > 0, + out=np.zeros_like(flux2)) + + # Recompute total cross sections and record + self._totalxs[:,:,:,0] = siga1 + sigs11 + sigs12_eff + self._totalxs[:,:,:,1] = siga2 + sigs22 + + # Record effective dowmscatter xs + self._scattxs[:,:,:,0,1] = sigs12_eff + + # Zero out upscatter cross section + self._scattxs[:,:,:,1,0] = 0.0 + + def _neutron_balance(self): + """Computes the RMS neutron balance over the CMFD mesh""" + # Extract energy indices + ng = self._indices[3] + + # Get number of accelerated regions + num_accel = self._mat_dim + + # Get openmc k-effective + keff = openmc.capi.keff()[0] + + # Define leakage in each mesh cell and energy group + leakage = (((self._current[:,:,:,_CURRENTS['out_right'],:] - + self._current[:,:,:,_CURRENTS['in_right'],:]) - + (self._current[:,:,:,_CURRENTS['in_left'],:] - + self._current[:,:,:,_CURRENTS['out_left'],:])) + + ((self._current[:,:,:,_CURRENTS['out_front'],:] - + self._current[:,:,:,_CURRENTS['in_front'],:]) - + (self._current[:,:,:,_CURRENTS['in_back'],:] - + self._current[:,:,:,_CURRENTS['out_back'],:])) + + ((self._current[:,:,:,_CURRENTS['out_top'],:] - + self._current[:,:,:,_CURRENTS['in_top'],:]) - + (self._current[:,:,:,_CURRENTS['in_bottom'],:] - + self._current[:,:,:,_CURRENTS['out_bottom'],:]))) + + # Compute total rr + interactions = self._totalxs * self._flux + + # Compute scattering rr by broadcasting flux in outgoing energy and + # summing over incoming energy + scattering = np.sum(self._scattxs * self._flux[:,:,:,:,np.newaxis], + axis=3) + + # Compute fission rr by broadcasting flux in outgoing energy and + # summing over incoming energy + fission = np.sum(self._nfissxs * self._flux[:,:,:,:,np.newaxis], + axis=3) + + # Compute residual + res = leakage + interactions - scattering - (1.0 / keff) * fission + + # Normalize res by flux and bank res + self._resnb = np.divide(res, self._flux, where=self._flux > 0) + + # Calculate RMS and record for this batch + self._balance.append(np.sqrt( + np.sum(np.multiply(self._resnb, self._resnb)) / + (ng * num_accel))) + + def _precompute_array_indices(self): + """Computes the indices used to populate certain cross section arrays. + These indices are used in _compute_dtilde and _compute_dhat + + """ + # Extract spatial indices + nx = self._indices[0] + ny = self._indices[1] + nz = self._indices[2] + + # Logical for determining whether region of interest is accelerated + # region + is_accel = self._coremap != _CMFD_NOACCEL + # Logical for determining whether a zero flux "albedo" b.c. should be + # applied + is_zero_flux_alb = abs(self._albedo - _ZERO_FLUX) < _TINY_BIT + x_inds, y_inds, z_inds = np.indices((nx, ny, nz)) + + # Define slice equivalent to is_accel[0,:,:] + slice_x = x_inds[:1,:,:] + slice_y = y_inds[:1,:,:] + slice_z = z_inds[:1,:,:] + bndry_accel = is_accel[(slice_x, slice_y, slice_z)] + self._first_x_accel = (slice_x[bndry_accel], slice_y[bndry_accel], + slice_z[bndry_accel]) + + # Define slice equivalent to is_accel[-1,:,:] + slice_x = x_inds[-1:,:,:] + slice_y = y_inds[-1:,:,:] + slice_z = z_inds[-1:,:,:] + bndry_accel = is_accel[(slice_x, slice_y, slice_z)] + self._last_x_accel = (slice_x[bndry_accel], slice_y[bndry_accel], + slice_z[bndry_accel]) + + # Define slice equivalent to is_accel[:,0,:] + slice_x = x_inds[:,:1,:] + slice_y = y_inds[:,:1,:] + slice_z = z_inds[:,:1,:] + bndry_accel = is_accel[(slice_x, slice_y, slice_z)] + self._first_y_accel = (slice_x[bndry_accel], slice_y[bndry_accel], + slice_z[bndry_accel]) + + # Define slice equivalent to is_accel[:,-1,:] + slice_x = x_inds[:,-1:,:] + slice_y = y_inds[:,-1:,:] + slice_z = z_inds[:,-1:,:] + bndry_accel = is_accel[(slice_x, slice_y, slice_z)] + self._last_y_accel = (slice_x[bndry_accel], slice_y[bndry_accel], + slice_z[bndry_accel]) + + # Define slice equivalent to is_accel[:,:,0] + slice_x = x_inds[:,:,:1] + slice_y = y_inds[:,:,:1] + slice_z = z_inds[:,:,:1] + bndry_accel = is_accel[(slice_x, slice_y, slice_z)] + self._first_z_accel = (slice_x[bndry_accel], slice_y[bndry_accel], + slice_z[bndry_accel]) + + # Define slice equivalent to is_accel[:,:,-1] + slice_x = x_inds[:,:,-1:] + slice_y = y_inds[:,:,-1:] + slice_z = z_inds[:,:,-1:] + bndry_accel = is_accel[(slice_x, slice_y, slice_z)] + self._last_z_accel = (slice_x[bndry_accel], slice_y[bndry_accel], + slice_z[bndry_accel]) + + # Define slice equivalent to is_accel[1:,:,:] + slice_x = x_inds[1:,:,:] + slice_y = y_inds[1:,:,:] + slice_z = z_inds[1:,:,:] + bndry_accel = is_accel[(slice_x, slice_y, slice_z)] + self._notfirst_x_accel = (slice_x[bndry_accel], slice_y[bndry_accel], + slice_z[bndry_accel]) + + # Define slice equivalent to is_accel[:-1,:,:] + slice_x = x_inds[:-1,:,:] + slice_y = y_inds[:-1,:,:] + slice_z = z_inds[:-1,:,:] + bndry_accel = is_accel[(slice_x, slice_y, slice_z)] + self._notlast_x_accel = (slice_x[bndry_accel], slice_y[bndry_accel], + slice_z[bndry_accel]) + + # Define slice equivalent to is_accel[:,1:,:] + slice_x = x_inds[:,1:,:] + slice_y = y_inds[:,1:,:] + slice_z = z_inds[:,1:,:] + bndry_accel = is_accel[(slice_x, slice_y, slice_z)] + self._notfirst_y_accel = (slice_x[bndry_accel], slice_y[bndry_accel], + slice_z[bndry_accel]) + + # Define slice equivalent to is_accel[:,:-1,:] + slice_x = x_inds[:,:-1,:] + slice_y = y_inds[:,:-1,:] + slice_z = z_inds[:,:-1,:] + bndry_accel = is_accel[(slice_x, slice_y, slice_z)] + self._notlast_y_accel = (slice_x[bndry_accel], slice_y[bndry_accel], + slice_z[bndry_accel]) + + # Define slice equivalent to is_accel[:,:,1:] + slice_x = x_inds[:,:,1:] + slice_y = y_inds[:,:,1:] + slice_z = z_inds[:,:,1:] + bndry_accel = is_accel[(slice_x, slice_y, slice_z)] + self._notfirst_z_accel = (slice_x[bndry_accel], slice_y[bndry_accel], + slice_z[bndry_accel]) + + # Define slice equivalent to is_accel[:,:,:-1] + slice_x = x_inds[:,:,:-1] + slice_y = y_inds[:,:,:-1] + slice_z = z_inds[:,:,:-1] + bndry_accel = is_accel[(slice_x, slice_y, slice_z)] + self._notlast_z_accel = (slice_x[bndry_accel], slice_y[bndry_accel], + slice_z[bndry_accel]) + + # Store logical for whether neighboring cell is reflector region + # in all directions + adj_reflector_left = np.roll(self._coremap, 1, axis=0) == _CMFD_NOACCEL + self._is_adj_ref_left = adj_reflector_left[ + self._notfirst_x_accel + (np.newaxis,)] + + adj_reflector_right = np.roll(self._coremap, -1, axis=0) == \ + _CMFD_NOACCEL + self._is_adj_ref_right = adj_reflector_right[ + self._notlast_x_accel + (np.newaxis,)] + + adj_reflector_back = np.roll(self._coremap, 1, axis=1) == \ + _CMFD_NOACCEL + self._is_adj_ref_back = adj_reflector_back[ + self._notfirst_y_accel + (np.newaxis,)] + + adj_reflector_front = np.roll(self._coremap, -1, axis=1) == \ + _CMFD_NOACCEL + self._is_adj_ref_front = adj_reflector_front[ + self._notlast_y_accel + (np.newaxis,)] + + adj_reflector_bottom = np.roll(self._coremap, 1, axis=2) == \ + _CMFD_NOACCEL + self._is_adj_ref_bottom = adj_reflector_bottom[ + self._notfirst_z_accel + (np.newaxis,)] + + adj_reflector_top = np.roll(self._coremap, -1, axis=2) == \ + _CMFD_NOACCEL + self._is_adj_ref_top = adj_reflector_top[ + self._notlast_z_accel + (np.newaxis,)] + + def _precompute_matrix_indices(self): + """Computes the indices and row/column data used to populate CMFD CSR + matrices. These indices are used in _build_loss_matrix and + _build_prod_matrix + + """ + # Extract energy group indices + ng = self._indices[3] + + # Shift coremap in all directions to determine whether leakage term + # should be defined for particular cell in matrix + coremap_shift_left = np.pad(self._coremap, ((1,0),(0,0),(0,0)), + mode='constant', + constant_values=_CMFD_NOACCEL)[:-1,:,:] + + coremap_shift_right = np.pad(self._coremap, ((0,1),(0,0),(0,0)), + mode='constant', + constant_values=_CMFD_NOACCEL)[1:,:,:] + + coremap_shift_back = np.pad(self._coremap, ((0,0),(1,0),(0,0)), + mode='constant', + constant_values=_CMFD_NOACCEL)[:,:-1,:] + + coremap_shift_front = np.pad(self._coremap, ((0,0),(0,1),(0,0)), + mode='constant', + constant_values=_CMFD_NOACCEL)[:,1:,:] + + coremap_shift_bottom = np.pad(self._coremap, ((0,0),(0,0),(1,0)), + mode='constant', + constant_values=_CMFD_NOACCEL)[:,:,:-1] + + coremap_shift_top = np.pad(self._coremap, ((0,0),(0,0),(0,1)), + mode='constant', + constant_values=_CMFD_NOACCEL)[:,:,1:] + + # Create empty row and column vectors to store for loss matrix + row = np.array([]) + col = np.array([]) + + # Store all indices used to populate production and loss matrix + is_accel = self._coremap != _CMFD_NOACCEL + self._accel_idxs = np.where(is_accel) + self._accel_neig_left_idxs = (np.where(is_accel & + (coremap_shift_left != _CMFD_NOACCEL))) + self._accel_neig_right_idxs = (np.where(is_accel & + (coremap_shift_right != _CMFD_NOACCEL))) + self._accel_neig_back_idxs = (np.where(is_accel & + (coremap_shift_back != _CMFD_NOACCEL))) + self._accel_neig_front_idxs = (np.where(is_accel & + (coremap_shift_front != _CMFD_NOACCEL))) + self._accel_neig_bot_idxs = (np.where(is_accel & + (coremap_shift_bottom != _CMFD_NOACCEL))) + self._accel_neig_top_idxs = (np.where(is_accel & + (coremap_shift_top != _CMFD_NOACCEL))) + + for g in range(ng): + # Extract row and column data of regions where a cell and its + # neighbor to the left are both fuel regions + idx_x = ng * (self._coremap[self._accel_neig_left_idxs]) + g + idx_y = ng * (coremap_shift_left[self._accel_neig_left_idxs]) + g + row = np.append(row, idx_x) + col = np.append(col, idx_y) + + # Extract row and column data of regions where a cell and its + # neighbor to the right are both fuel regions + idx_x = ng * (self._coremap[self._accel_neig_right_idxs]) + g + idx_y = ng * (coremap_shift_right[self._accel_neig_right_idxs]) + g + row = np.append(row, idx_x) + col = np.append(col, idx_y) + + # Extract row and column data of regions where a cell and its + # neighbor to the back are both fuel regions + idx_x = ng * (self._coremap[self._accel_neig_back_idxs]) + g + idx_y = ng * (coremap_shift_back[self._accel_neig_back_idxs]) + g + row = np.append(row, idx_x) + col = np.append(col, idx_y) + + # Extract row and column data of regions where a cell and its + # neighbor to the front are both fuel regions + idx_x = ng * (self._coremap[self._accel_neig_front_idxs]) + g + idx_y = ng * (coremap_shift_front[self._accel_neig_front_idxs]) + g + row = np.append(row, idx_x) + col = np.append(col, idx_y) + + # Extract row and column data of regions where a cell and its + # neighbor to the bottom are both fuel regions + idx_x = ng * (self._coremap[self._accel_neig_bot_idxs]) + g + idx_y = ng * (coremap_shift_bottom[self._accel_neig_bot_idxs]) \ + + g + row = np.append(row, idx_x) + col = np.append(col, idx_y) + + # Extract row and column data of regions where a cell and its + # neighbor to the top are both fuel regions + idx_x = ng * (self._coremap[self._accel_neig_top_idxs]) + g + idx_y = ng * (coremap_shift_top[self._accel_neig_top_idxs]) + g + row = np.append(row, idx_x) + col = np.append(col, idx_y) + + # Extract all regions where a cell is a fuel region + idx_x = ng * (self._coremap[self._accel_idxs]) + g + idx_y = idx_x + row = np.append(row, idx_x) + col = np.append(col, idx_y) + + for h in range(ng): + if h != g: + # Extract all regions where a cell is a fuel region + idx_x = ng * (self._coremap[self._accel_idxs]) + g + idx_y = ng * (self._coremap[self._accel_idxs]) + h + row = np.append(row, idx_x) + col = np.append(col, idx_y) + + # Store row and col as rows and columns of production matrix + self._loss_row = row + self._loss_col = col + + # Create empty row and column vectors to store for production matrix + row = np.array([], dtype=int) + col = np.array([], dtype=int) + + for g in range(ng): + for h in range(ng): + # Extract all regions where a cell is a fuel region + idx_x = ng * (self._coremap[self._accel_idxs]) + g + idx_y = ng * (self._coremap[self._accel_idxs]) + h + # Store rows, cols, and data to add to CSR matrix + row = np.append(row, idx_x) + col = np.append(col, idx_y) + + # Store row and col as rows and columns of production matrix + self._prod_row = row + self._prod_col = col + + def _compute_dtilde(self): + """Computes the diffusion coupling coefficient using a vectorized numpy + approach. Aggregate values for the dtilde multidimensional array are + populated by first defining values on the problem boundary, and then + for all other regions. For indices not lying on a boundary, dtilde + values are distinguished between regions that neighbor a reflector + region and regions that don't neighbor a reflector + + """ + # Logical for determining whether a zero flux "albedo" b.c. should be + # applied + is_zero_flux_alb = abs(self._albedo - _ZERO_FLUX) < _TINY_BIT + + # Define dtilde at left surface for all mesh cells on left boundary + # Separate between zero flux b.c. and alebdo b.c. + boundary = self._first_x_accel + boundary_grps = boundary + (slice(None),) + D = self._diffcof[boundary_grps] + dx = self._hxyz[boundary + (np.newaxis, 0)] + if is_zero_flux_alb[0]: + self._dtilde[boundary_grps + (0,)] = 2.0 * D / dx + else: + alb = self._albedo[0] + self._dtilde[boundary_grps + (0,)] = ((2.0 * D * (1.0 - alb)) + / (4.0 * D * (1.0 + alb) + + (1.0 - alb) * dx)) + + # Define dtilde at right surface for all mesh cells on right boundary + # Separate between zero flux b.c. and alebdo b.c. + boundary = self._last_x_accel + boundary_grps = boundary + (slice(None),) + D = self._diffcof[boundary_grps] + dx = self._hxyz[boundary + (np.newaxis, 0)] + if is_zero_flux_alb[1]: + self._dtilde[boundary_grps + (1,)] = 2.0 * D / dx + else: + alb = self._albedo[1] + self._dtilde[boundary_grps + (1,)] = ((2.0 * D * (1.0 - alb)) + / (4.0 * D * (1.0 + alb) + + (1.0 - alb) * dx)) + + # Define dtilde at back surface for all mesh cells on back boundary + # Separate between zero flux b.c. and alebdo b.c. + boundary = self._first_y_accel + boundary_grps = boundary + (slice(None),) + D = self._diffcof[boundary_grps] + dy = self._hxyz[boundary + (np.newaxis, 1)] + if is_zero_flux_alb[2]: + self._dtilde[boundary_grps + (2,)] = 2.0 * D / dy + else: + alb = self._albedo[2] + self._dtilde[boundary_grps + (2,)] = ((2.0 * D * (1.0 - alb)) + / (4.0 * D * (1.0 + alb) + + (1.0 - alb) * dy)) + + # Define dtilde at front surface for all mesh cells on front boundary + # Separate between zero flux b.c. and alebdo b.c. + boundary = self._last_y_accel + boundary_grps = boundary + (slice(None),) + D = self._diffcof[boundary_grps] + dy = self._hxyz[boundary + (np.newaxis, 1)] + if is_zero_flux_alb[3]: + self._dtilde[boundary_grps + (3,)] = 2.0 * D / dy + else: + alb = self._albedo[3] + self._dtilde[boundary_grps + (3,)] = ((2.0 * D * (1.0 - alb)) + / (4.0 * D * (1.0 + alb) + + (1.0 - alb) * dy)) + + # Define dtilde at bottom surface for all mesh cells on bottom boundary + # Separate between zero flux b.c. and alebdo b.c. + boundary = self._first_z_accel + boundary_grps = boundary + (slice(None),) + D = self._diffcof[boundary_grps] + dz = self._hxyz[boundary + (np.newaxis, 2)] + if is_zero_flux_alb[4]: + self._dtilde[boundary_grps + (4,)] = 2.0 * D / dz + else: + alb = self._albedo[4] + self._dtilde[boundary_grps + (4,)] = ((2.0 * D * (1.0 - alb)) + / (4.0 * D * (1.0 + alb) + + (1.0 - alb) * dz)) + + # Define dtilde at top surface for all mesh cells on top boundary + # Separate between zero flux b.c. and alebdo b.c. + boundary = self._last_z_accel + boundary_grps = boundary + (slice(None),) + + D = self._diffcof[boundary_grps] + dz = self._hxyz[boundary + (np.newaxis, 2)] + if is_zero_flux_alb[5]: + self._dtilde[boundary_grps + (5,)] = 2.0 * D / dz + else: + alb = self._albedo[5] + self._dtilde[boundary_grps + (5,)] = ((2.0 * D * (1 - alb)) + / (4.0 * D * (1.0 + alb) + + (1.0 - alb) * dz)) + + # Define reflector albedo for all cells on the left surface, in case + # a cell borders a reflector region on the left + current_in_left = self._current[:,:,:,_CURRENTS['in_left'],:] + current_out_left = self._current[:,:,:,_CURRENTS['out_left'],:] + with np.errstate(divide='ignore', invalid='ignore'): + ref_albedo = np.divide(current_in_left, current_out_left, + where=current_out_left > 1.0e-10, + out=np.ones_like(current_out_left)) + + # Diffusion coefficient of neighbor to left + neig_dc = np.roll(self._diffcof, 1, axis=0) + # Cell dimensions of neighbor to left + neig_hxyz = np.roll(self._hxyz, 1, axis=0) + + # Define dtilde at left surface for all mesh cells not on left boundary + # Dtilde is defined differently for regions that do and don't neighbor + # reflector regions + boundary = self._notfirst_x_accel + boundary_grps = boundary + (slice(None),) + D = self._diffcof[boundary_grps] + dx = self._hxyz[boundary + (np.newaxis, 0)] + neig_D = neig_dc[boundary_grps] + neig_dx = neig_hxyz[boundary + (np.newaxis, 0)] + alb = ref_albedo[boundary_grps] + is_adj_ref = self._is_adj_ref_left + dtilde = np.where(is_adj_ref, (2.0 * D * (1.0 - alb)) / + (4.0 * D * (1.0 + alb) + (1.0 - alb) * dx), + (2.0 * D * neig_D) / (neig_dx * D + dx * neig_D)) + self._dtilde[boundary_grps + (0,)] = dtilde + + # Define reflector albedo for all cells on the right surface, in case + # a cell borders a reflector region on the right + current_in_right = self._current[:,:,:,_CURRENTS['in_right'],:] + current_out_right = self._current[:,:,:,_CURRENTS['out_right'],:] + with np.errstate(divide='ignore', invalid='ignore'): + ref_albedo = np.divide(current_in_right, current_out_right, + where=current_out_right > 1.0e-10, + out=np.ones_like(current_out_right)) + + # Diffusion coefficient of neighbor to right + neig_dc = np.roll(self._diffcof, -1, axis=0) + # Cell dimensions of neighbor to right + neig_hxyz = np.roll(self._hxyz, -1, axis=0) + + # Define dtilde at right surface for all mesh cells not on right + # boundary. Dtilde is defined differently for regions that do and don't + # neighbor reflector regions + boundary = self._notlast_x_accel + boundary_grps = boundary + (slice(None),) + D = self._diffcof[boundary_grps] + dx = self._hxyz[boundary + (np.newaxis, 0)] + neig_D = neig_dc[boundary_grps] + neig_dx = neig_hxyz[boundary + (np.newaxis, 0)] + alb = ref_albedo[boundary_grps] + is_adj_ref = self._is_adj_ref_right + dtilde = np.where(is_adj_ref, (2.0 * D * (1.0 - alb)) / + (4.0 * D * (1.0 + alb) + (1.0 - alb) * dx), + (2.0 * D * neig_D) / (neig_dx * D + dx * neig_D)) + self._dtilde[boundary_grps + (1,)] = dtilde + + # Define reflector albedo for all cells on the back surface, in case + # a cell borders a reflector region on the back + current_in_back = self._current[:,:,:,_CURRENTS['in_back'],:] + current_out_back = self._current[:,:,:,_CURRENTS['out_back'],:] + with np.errstate(divide='ignore', invalid='ignore'): + ref_albedo = np.divide(current_in_back, current_out_back, + where=current_out_back > 1.0e-10, + out=np.ones_like(current_out_back)) + + # Diffusion coefficient of neighbor to back + neig_dc = np.roll(self._diffcof, 1, axis=1) + # Cell dimensions of neighbor to back + neig_hxyz = np.roll(self._hxyz, 1, axis=1) + + # Define dtilde at back surface for all mesh cells not on back boundary + # Dtilde is defined differently for regions that do and don't neighbor + # reflector regions + boundary = self._notfirst_y_accel + boundary_grps = boundary + (slice(None),) + D = self._diffcof[boundary_grps] + dy = self._hxyz[boundary + (np.newaxis, 1)] + neig_D = neig_dc[boundary_grps] + neig_dy = neig_hxyz[boundary + (np.newaxis, 1)] + alb = ref_albedo[boundary_grps] + is_adj_ref = self._is_adj_ref_back + dtilde = np.where(is_adj_ref, (2.0 * D * (1.0 - alb)) / + (4.0 * D * (1.0 + alb) + (1.0 - alb) * dy), + (2.0 * D * neig_D) / (neig_dy * D + dy * neig_D)) + self._dtilde[boundary_grps + (2,)] = dtilde + + # Define reflector albedo for all cells on the front surface, in case + # a cell borders a reflector region in the front + current_in_front = self._current[:,:,:,_CURRENTS['in_front'],:] + current_out_front = self._current[:,:,:,_CURRENTS['out_front'],:] + with np.errstate(divide='ignore', invalid='ignore'): + ref_albedo = np.divide(current_in_front, current_out_front, + where=current_out_front > 1.0e-10, + out=np.ones_like(current_out_front)) + + # Diffusion coefficient of neighbor to front + neig_dc = np.roll(self._diffcof, -1, axis=1) + # Cell dimensions of neighbor to front + neig_hxyz = np.roll(self._hxyz, -1, axis=1) + + # Define dtilde at front surface for all mesh cells not on front + # boundary. Dtilde is defined differently for regions that do and don't + # neighbor reflector regions + boundary = self._notlast_y_accel + boundary_grps = boundary + (slice(None),) + D = self._diffcof[boundary_grps] + dy = self._hxyz[boundary + (np.newaxis, 1)] + neig_D = neig_dc[boundary_grps] + neig_dy = neig_hxyz[boundary + (np.newaxis, 1)] + alb = ref_albedo[boundary_grps] + is_adj_ref = self._is_adj_ref_front + dtilde = np.where(is_adj_ref, (2.0 * D * (1.0 - alb)) / + (4.0 * D * (1.0 + alb) + (1.0 - alb) * dy), + (2.0 * D * neig_D) / (neig_dy * D + dy * neig_D)) + self._dtilde[boundary_grps + (3,)] = dtilde + + # Define reflector albedo for all cells on the bottom surface, in case + # a cell borders a reflector region on the bottom + current_in_bottom = self._current[:,:,:,_CURRENTS['in_bottom'],:] + current_out_bottom = self._current[:,:,:,_CURRENTS['out_bottom'],:] + with np.errstate(divide='ignore', invalid='ignore'): + ref_albedo = np.divide(current_in_bottom, current_out_bottom, + where=current_out_bottom > 1.0e-10, + out=np.ones_like(current_out_bottom)) + + # Diffusion coefficient of neighbor to bottom + neig_dc = np.roll(self._diffcof, 1, axis=2) + # Cell dimensions of neighbor to bottom + neig_hxyz = np.roll(self._hxyz, 1, axis=2) + + # Define dtilde at bottom surface for all mesh cells not on bottom + # boundary. Dtilde is defined differently for regions that do and don't + # neighbor reflector regions + boundary = self._notfirst_z_accel + boundary_grps = boundary + (slice(None),) + D = self._diffcof[boundary_grps] + dz = self._hxyz[boundary + (np.newaxis, 2)] + neig_D = neig_dc[boundary_grps] + neig_dz = neig_hxyz[boundary + (np.newaxis, 2)] + alb = ref_albedo[boundary_grps] + is_adj_ref = self._is_adj_ref_bottom + dtilde = np.where(is_adj_ref, (2.0 * D * (1.0 - alb)) / + (4.0 * D * (1.0 + alb) + (1.0 - alb) * dz), + (2.0 * D * neig_D) / (neig_dz * D + dz * neig_D)) + self._dtilde[boundary_grps + (4,)] = dtilde + + # Define reflector albedo for all cells on the top surface, in case + # a cell borders a reflector region on the top + current_in_top = self._current[:,:,:,_CURRENTS['in_top'],:] + current_out_top = self._current[:,:,:,_CURRENTS['out_top'],:] + with np.errstate(divide='ignore', invalid='ignore'): + ref_albedo = np.divide(current_in_top, current_out_top, + where=current_out_top > 1.0e-10, + out=np.ones_like(current_out_top)) + + # Diffusion coefficient of neighbor to top + neig_dc = np.roll(self._diffcof, -1, axis=2) + # Cell dimensions of neighbor to top + neig_hxyz = np.roll(self._hxyz, -1, axis=2) + + # Define dtilde at top surface for all mesh cells not on top boundary + # Dtilde is defined differently for regions that do and don't neighbor + # reflector regions + boundary = self._notlast_z_accel + boundary_grps = boundary + (slice(None),) + D = self._diffcof[boundary_grps] + dz = self._hxyz[boundary + (np.newaxis, 2)] + neig_D = neig_dc[boundary_grps] + neig_dz = neig_hxyz[boundary + (np.newaxis, 2)] + alb = ref_albedo[boundary_grps] + is_adj_ref = self._is_adj_ref_top + dtilde = np.where(is_adj_ref, (2.0 * D * (1.0 - alb)) / + (4.0 * D * (1.0 + alb) + (1.0 - alb) * dz), + (2.0 * D * neig_D) / (neig_dz * D + dz * neig_D)) + self._dtilde[boundary_grps + (5,)] = dtilde + + def _compute_dhat(self): + """Computes the nonlinear coupling coefficient using a vectorized numpy + approach. Aggregate values for the dhat multidimensional array are + populated by first defining values on the problem boundary, and then + for all other regions. For indices not lying by a boundary, dhat values + are distinguished between regions that neighbor a reflector region and + regions that don't neighbor a reflector + + """ + # Define current in each direction + current_in_left = self._current[:,:,:,_CURRENTS['in_left'],:] + current_out_left = self._current[:,:,:,_CURRENTS['out_left'],:] + current_in_right = self._current[:,:,:,_CURRENTS['in_right'],:] + current_out_right = self._current[:,:,:,_CURRENTS['out_right'],:] + current_in_back = self._current[:,:,:,_CURRENTS['in_back'],:] + current_out_back = self._current[:,:,:,_CURRENTS['out_back'],:] + current_in_front = self._current[:,:,:,_CURRENTS['in_front'],:] + current_out_front = self._current[:,:,:,_CURRENTS['out_front'],:] + current_in_bottom = self._current[:,:,:,_CURRENTS['in_bottom'],:] + current_out_bottom = self._current[:,:,:,_CURRENTS['out_bottom'],:] + current_in_top = self._current[:,:,:,_CURRENTS['in_top'],:] + current_out_top = self._current[:,:,:,_CURRENTS['out_top'],:] + + dx = self._hxyz[:,:,:,np.newaxis,0] + dy = self._hxyz[:,:,:,np.newaxis,1] + dz = self._hxyz[:,:,:,np.newaxis,2] + dxdydz = np.prod(self._hxyz, axis=3)[:,:,:,np.newaxis] + + # Define net current on each face + net_current_left = (current_in_left - current_out_left) / dxdydz * dx + net_current_right = (current_out_right - current_in_right) / dxdydz * \ + dx + net_current_back = (current_in_back - current_out_back) / dxdydz * dy + net_current_front = (current_out_front - current_in_front) / dxdydz * \ + dy + net_current_bottom = (current_in_bottom - current_out_bottom) / \ + dxdydz * dz + net_current_top = (current_out_top - current_in_top) / dxdydz * dz + + # Define flux in each cell + cell_flux = self._flux / dxdydz + # Extract indices of coremap that are accelerated + is_accel = self._coremap != _CMFD_NOACCEL + + # Define dhat at left surface for all mesh cells on left boundary + boundary = self._first_x_accel + boundary_grps = boundary + (slice(None),) + net_current = net_current_left[boundary_grps] + dtilde = self._dtilde[boundary + (slice(None), 0)] + flux = cell_flux[boundary_grps] + self._dhat[boundary_grps + (0,)] = (net_current + dtilde * flux) / flux + + # Define dhat at right surface for all mesh cells on right boundary + boundary = self._last_x_accel + boundary_grps = boundary + (slice(None),) + net_current = net_current_right[boundary_grps] + dtilde = self._dtilde[boundary + (slice(None), 1)] + flux = cell_flux[boundary_grps] + self._dhat[boundary_grps + (1,)] = (net_current - dtilde * flux) / flux + + # Define dhat at back surface for all mesh cells on back boundary + boundary = self._first_y_accel + boundary_grps = boundary + (slice(None),) + net_current = net_current_back[boundary_grps] + dtilde = self._dtilde[boundary + (slice(None), 2)] + flux = cell_flux[boundary_grps] + self._dhat[boundary_grps + (2,)] = (net_current + dtilde * flux) / flux + + # Define dhat at front surface for all mesh cells on front boundary + boundary = self._last_y_accel + boundary_grps = boundary + (slice(None),) + net_current = net_current_front[boundary_grps] + dtilde = self._dtilde[boundary + (slice(None), 3)] + flux = cell_flux[boundary_grps] + self._dhat[boundary_grps + (3,)] = (net_current - dtilde * flux) / flux + + # Define dhat at bottom surface for all mesh cells on bottom boundary + boundary = self._first_z_accel + boundary_grps = boundary + (slice(None),) + net_current = net_current_bottom[boundary_grps] + dtilde = self._dtilde[boundary + (slice(None), 4)] + flux = cell_flux[boundary_grps] + self._dhat[boundary_grps + (4,)] = (net_current + dtilde * flux) / flux + + # Define dhat at top surface for all mesh cells on top boundary + boundary = self._last_z_accel + boundary_grps = boundary + (slice(None),) + net_current = net_current_top[boundary_grps] + dtilde = self._dtilde[boundary + (slice(None), 5)] + flux = cell_flux[boundary_grps] + self._dhat[boundary_grps + (5,)] = (net_current - dtilde * flux) / flux + + # Cell flux of neighbor to left + neig_flux = np.roll(self._flux, 1, axis=0) / dxdydz + + # Define dhat at left surface for all mesh cells not on left boundary + # Dhat is defined differently for regions that do and don't neighbor + # reflector regions + boundary = self._notfirst_x_accel + boundary_grps = boundary + (slice(None),) + net_current = net_current_left[boundary_grps] + dtilde = self._dtilde[boundary_grps + (0,)] + flux = cell_flux[boundary_grps] + flux_left = neig_flux[boundary_grps] + is_adj_ref = self._is_adj_ref_left + dhat = np.where(is_adj_ref, (net_current + dtilde * flux) / flux, + (net_current - dtilde * (flux_left - flux)) / + (flux_left + flux)) + self._dhat[boundary_grps + (0,)] = dhat + + # Cell flux of neighbor to right + neig_flux = np.roll(self._flux, -1, axis=0) / dxdydz + + # Define dhat at right surface for all mesh cells not on right boundary + # Dhat is defined differently for regions that do and don't neighbor + # reflector regions + boundary = self._notlast_x_accel + boundary_grps = boundary + (slice(None),) + net_current = net_current_right[boundary_grps] + dtilde = self._dtilde[boundary_grps + (1,)] + flux = cell_flux[boundary_grps] + flux_right = neig_flux[boundary_grps] + is_adj_ref = self._is_adj_ref_right + dhat = np.where(is_adj_ref, (net_current - dtilde * flux) / flux, + (net_current + dtilde * (flux_right - flux)) / + (flux_right + flux)) + self._dhat[boundary_grps + (1,)] = dhat + + # Cell flux of neighbor to back + neig_flux = np.roll(self._flux, 1, axis=1) / dxdydz + + # Define dhat at back surface for all mesh cells not on back boundary + # Dhat is defined differently for regions that do and don't neighbor + # reflector regions + boundary = self._notfirst_y_accel + boundary_grps = boundary + (slice(None),) + net_current = net_current_back[boundary_grps] + dtilde = self._dtilde[boundary_grps + (2,)] + flux = cell_flux[boundary_grps] + flux_back = neig_flux[boundary_grps] + is_adj_ref = self._is_adj_ref_back + dhat = np.where(is_adj_ref, (net_current + dtilde * flux) / flux, + (net_current - dtilde * (flux_back - flux)) / + (flux_back + flux)) + self._dhat[boundary_grps + (2,)] = dhat + + # Cell flux of neighbor to front + neig_flux = np.roll(self._flux, -1, axis=1) / dxdydz + + # Define dhat at front surface for all mesh cells not on front boundary + # Dhat is defined differently for regions that do and don't neighbor + # reflector regions + boundary = self._notlast_y_accel + boundary_grps = boundary + (slice(None),) + net_current = net_current_front[boundary_grps] + dtilde = self._dtilde[boundary_grps + (3,)] + flux = cell_flux[boundary_grps] + flux_front = neig_flux[boundary_grps] + is_adj_ref = self._is_adj_ref_front + dhat = np.where(is_adj_ref, (net_current - dtilde * flux) / flux, + (net_current + dtilde * (flux_front - flux)) / + (flux_front + flux)) + self._dhat[boundary_grps + (3,)] = dhat + + # Cell flux of neighbor to bottom + neig_flux = np.roll(self._flux, 1, axis=2) / dxdydz + + # Define dhat at bottom surface for all mesh cells not on bottom + # boundary. Dhat is defined differently for regions that do and don't + # neighbor reflector regions + boundary = self._notfirst_z_accel + boundary_grps = boundary + (slice(None),) + net_current = net_current_bottom[boundary_grps] + dtilde = self._dtilde[boundary_grps + (4,)] + flux = cell_flux[boundary_grps] + flux_bottom = neig_flux[boundary_grps] + is_adj_ref = self._is_adj_ref_bottom + dhat = np.where(is_adj_ref, (net_current + dtilde * flux) / flux, + (net_current - dtilde * (flux_bottom - flux)) / + (flux_bottom + flux)) + self._dhat[boundary_grps + (4,)] = dhat + + # Cell flux of neighbor to top + neig_flux = np.roll(self._flux, -1, axis=2) / dxdydz + + # Define dhat at top surface for all mesh cells not on top boundary + # Dhat is defined differently for regions that do and don't neighbor + # reflector regions + boundary = self._notlast_z_accel + boundary_grps = boundary + (slice(None),) + net_current = net_current_top[boundary_grps] + dtilde = self._dtilde[boundary_grps + (5,)] + flux = cell_flux[boundary_grps] + flux_top = neig_flux[boundary_grps] + is_adj_ref = self._is_adj_ref_top + dhat = np.where(is_adj_ref, (net_current - dtilde * flux) / flux, + (net_current + dtilde * (flux_top - flux)) / + (flux_top + flux)) + self._dhat[boundary_grps + (5,)] = dhat + + def _create_cmfd_tally(self): + """Creates all tallies in-memory that are used to solve CMFD problem""" + # Create Mesh object based on CMFDMesh, stored internally + cmfd_mesh = openmc.capi.Mesh() + # Store id of Mesh object + self._mesh_id = cmfd_mesh.id + # Set dimension and parameters of Mesh object + cmfd_mesh.dimension = self._mesh.dimension + cmfd_mesh.set_parameters(lower_left=self._mesh.lower_left, + upper_right=self._mesh.upper_right, + width=self._mesh.width) + + # Create Mesh Filter object, stored internally + mesh_filter = openmc.capi.MeshFilter() + # Set mesh for Mesh Filter + mesh_filter.mesh = cmfd_mesh + + # Set up energy filters, if applicable + if self._energy_filters: + # Create Energy Filter object, stored internally + energy_filter = openmc.capi.EnergyFilter() + # Set bins for Energy Filter + energy_filter.bins = self._egrid + + # Create Energy Out Filter object, stored internally + energyout_filter = openmc.capi.EnergyoutFilter() + # Set bins for Energy Filter + energyout_filter.bins = self._egrid + + # Create Mesh Surface Filter object, stored internally + meshsurface_filter = openmc.capi.MeshSurfaceFilter() + # Set mesh for Mesh Surface Filter + meshsurface_filter.mesh = cmfd_mesh + + # Create Legendre Filter object, stored internally + legendre_filter = openmc.capi.LegendreFilter() + # Set order for Legendre Filter + legendre_filter.order = 1 + + # Create CMFD tallies, stored internally + n_tallies = 4 + self._tally_ids = [] + for i in range(n_tallies): + cmfd_tally = openmc.capi.Tally() + # Set nuclide bins + cmfd_tally.nuclides = ['total'] + self._tally_ids.append(cmfd_tally.id) + + # Set attributes of CMFD flux, total tally + if i == 0: + # Set filters for tally + if self._energy_filters: + cmfd_tally.filters = [mesh_filter, energy_filter] + else: + cmfd_tally.filters = [mesh_filter] + # Set scores, type, and estimator for tally + cmfd_tally.scores = ['flux', 'total'] + cmfd_tally.type = 'volume' + cmfd_tally.estimator = 'analog' + + # Set attributes of CMFD neutron production tally + elif i == 1: + # Set filters for tally + if self._energy_filters: + cmfd_tally.filters = [mesh_filter, energy_filter, + energyout_filter] + else: + cmfd_tally.filters = [mesh_filter] + # Set scores, type, and estimator for tally + cmfd_tally.scores = ['nu-scatter', 'nu-fission'] + cmfd_tally.type = 'volume' + cmfd_tally.estimator = 'analog' + + # Set attributes of CMFD surface current tally + elif i == 2: + # Set filters for tally + if self._energy_filters: + cmfd_tally.filters = [meshsurface_filter, energy_filter] + else: + cmfd_tally.filters = [meshsurface_filter] + # Set scores, type, and estimator for tally + cmfd_tally.scores = ['current'] + cmfd_tally.type = 'mesh-surface' + cmfd_tally.estimator = 'analog' + + # Set attributes of CMFD P1 scatter tally + elif i == 3: + # Set filters for tally + if self._energy_filters: + cmfd_tally.filters = [mesh_filter, legendre_filter, + energy_filter] + else: + cmfd_tally.filters = [mesh_filter, legendre_filter] + # Set scores for tally + cmfd_tally.scores = ['scatter'] + cmfd_tally.type = 'volume' + cmfd_tally.estimator = 'analog' + + # Set all tallies to be active from beginning + cmfd_tally.active = True diff --git a/openmc/model/model.py b/openmc/model/model.py index 8583da27a7..28d19713c9 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -25,8 +25,6 @@ class Model(object): Settings information tallies : openmc.Tallies, optional Tallies information - cmfd : openmc.CMFD, optional - CMFD information plots : openmc.Plots, optional Plot information @@ -40,19 +38,16 @@ class Model(object): Settings information tallies : openmc.Tallies Tallies information - cmfd : openmc.CMFD - CMFD information plots : openmc.Plots Plot information """ def __init__(self, geometry=None, materials=None, settings=None, - tallies=None, cmfd=None, plots=None): + tallies=None, plots=None): self.geometry = openmc.Geometry() self.materials = openmc.Materials() self.settings = openmc.Settings() - self.cmfd = cmfd self.tallies = openmc.Tallies() self.plots = openmc.Plots() @@ -83,10 +78,6 @@ class Model(object): def tallies(self): return self._tallies - @property - def cmfd(self): - return self._cmfd - @property def plots(self): return self._plots @@ -121,11 +112,6 @@ class Model(object): for tally in tallies: self._tallies.append(tally) - @cmfd.setter - def cmfd(self, cmfd): - check_type('cmfd', cmfd, (openmc.CMFD, type(None))) - self._cmfd = cmfd - @plots.setter def plots(self, plots): check_type('plots', plots, Iterable, openmc.Plot) @@ -196,8 +182,6 @@ class Model(object): if self.tallies: self.tallies.export_to_xml() - if self.cmfd is not None: - self.cmfd.export_to_xml() if self.plots: self.plots.export_to_xml() diff --git a/openmc/settings.py b/openmc/settings.py index 2bbe0864e5..1d97250ede 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -90,8 +90,6 @@ class Settings(object): indicates what nuclides the method should be applied to. In its absence, the method will be applied to all nuclides with 0 K elastic scattering data present. - run_cmfd : bool - Indicate if coarse mesh finite difference acceleration is to be used run_mode : {'eigenvalue', 'fixed source', 'plot', 'volume', 'particle restart'} The type of calculation to perform (default is 'eigenvalue') seed : int @@ -182,7 +180,6 @@ class Settings(object): self._electron_treatment = None self._photon_transport = None self._ptables = None - self._run_cmfd = None self._seed = None self._survival_biasing = None @@ -279,10 +276,6 @@ class Settings(object): def photon_transport(self): return self._photon_transport - @property - def run_cmfd(self): - return self._run_cmfd - @property def seed(self): return self._seed @@ -528,11 +521,6 @@ class Settings(object): cv.check_type('probability tables', ptables, bool) self._ptables = ptables - @run_cmfd.setter - def run_cmfd(self, run_cmfd): - cv.check_type('run_cmfd', run_cmfd, bool) - self._run_cmfd = run_cmfd - @seed.setter def seed(self, seed): cv.check_type('random number generator seed', seed, Integral) @@ -830,11 +818,6 @@ class Settings(object): element = ET.SubElement(root, "ptables") element.text = str(self._ptables).lower() - def _create_run_cmfd_subelement(self, root): - if self._run_cmfd is not None: - element = ET.SubElement(root, "run_cmfd") - element.text = str(self._run_cmfd).lower() - def _create_seed_subelement(self, root): if self._seed is not None: element = ET.SubElement(root, "seed") @@ -991,7 +974,6 @@ class Settings(object): self._create_max_order_subelement(root_element) self._create_photon_transport_subelement(root_element) self._create_ptables_subelement(root_element) - self._create_run_cmfd_subelement(root_element) self._create_seed_subelement(root_element) self._create_survival_biasing_subelement(root_element) self._create_cutoff_subelement(root_element) diff --git a/schemas.xml b/schemas.xml index 465b9b3980..3e586ec6ac 100644 --- a/schemas.xml +++ b/schemas.xml @@ -5,6 +5,5 @@ - diff --git a/src/api.F90 b/src/api.F90 index d7fedcbe30..640ad910a5 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -59,7 +59,6 @@ contains subroutine free_memory() bind(C) use bank_header - use cmfd_header use geometry_header use material_header use photon_header @@ -85,6 +84,9 @@ contains subroutine free_memory_bank() bind(C) end subroutine free_memory_bank + + subroutine free_memory_cmfd() bind(C) + end subroutine free_memory_cmfd end interface call free_memory_geometry() @@ -102,13 +104,11 @@ contains call free_memory_tally_filter() call free_memory_tally_derivative() call free_memory_bank() + call free_memory_cmfd() #ifdef DAGMC call free_memory_dagmc() #endif - ! Deallocate CMFD - call deallocate_cmfd(cmfd) - end subroutine free_memory end module openmc_api diff --git a/src/cmfd_data.F90 b/src/cmfd_data.F90 deleted file mode 100644 index f2fb4dc26b..0000000000 --- a/src/cmfd_data.F90 +++ /dev/null @@ -1,943 +0,0 @@ -module cmfd_data - -!============================================================================== -! CMFD_DATA -- This module processes the cmfd tally object to generate -! parameters for CMFD calculation. -!============================================================================== - - use cmfd_header, only: allocate_cmfd, cmfd, cmfd_coremap, & - cmfd_downscatter, cmfd_tallies, dhat_reset - use constants - use tally_filter_mesh, only: MeshFilter - - implicit none - private - public :: set_up_cmfd, neutron_balance - -contains - -!============================================================================== -! SET_UP_CMFD configures cmfd object for a CMFD eigenvalue calculation -!============================================================================== - - subroutine set_up_cmfd() - - use constants, only: CMFD_NOACCEL - - ! Check for core map and set it up - if ((cmfd_coremap) .and. (cmfd%mat_dim == CMFD_NOACCEL)) call set_coremap() - - ! Calculate all cross sections based on reaction rates from last batch - call compute_xs() - - ! Compute effective downscatter cross section - if (cmfd_downscatter) call compute_effective_downscatter() - - ! Check neutron balance - call neutron_balance() - - ! Calculate dtilde - call compute_dtilde() - - ! Calculate dhat - call compute_dhat() - - end subroutine set_up_cmfd - -!=============================================================================== -! COMPUTE_XS takes tallies and computes macroscopic cross sections -!=============================================================================== - - subroutine compute_xs() - - use constants, only: FILTER_MESH, FILTER_ENERGYIN, FILTER_ENERGYOUT, & - FILTER_SURFACE, OUT_LEFT, OUT_RIGHT, OUT_BACK, & - OUT_FRONT, OUT_BOTTOM, OUT_TOP, IN_LEFT, IN_RIGHT, & - IN_BACK, IN_FRONT, IN_BOTTOM, IN_TOP, CMFD_NOACCEL, & - ZERO, ONE, TINY_BIT - use error, only: fatal_error - use mesh_header, only: RegularMesh, meshes - use string, only: to_str - use tally_filter_header, only: filters, filter_matches - - integer :: nx ! number of mesh cells in x direction - integer :: ny ! number of mesh cells in y direction - integer :: nz ! number of mesh cells in z direction - integer :: ng ! number of energy groups - integer :: i ! iteration counter for x - integer :: j ! iteration counter for y - integer :: k ! iteration counter for z - integer :: g ! iteration counter for g - integer :: h ! iteration counter for outgoing groups - integer :: l ! iteration counter for tally filters - integer :: ital ! tally object index - integer :: ijk(3) ! indices for mesh cell - integer :: score_index ! index to pull from tally object - integer :: i_filter_mesh ! index for mesh filter - integer :: i_filter_ein ! index for incoming energy filter - integer :: i_filter_eout ! index for outgoing energy filter - integer :: i_filter_legendre ! index for Legendre filter - integer :: i_mesh ! flattend index for mesh - logical :: energy_filters! energy filters present - real(8) :: flux ! temp variable for flux - type(RegularMesh) :: m ! pointer for mesh object - - ! Extract spatial and energy indices from object - nx = cmfd % indices(1) - ny = cmfd % indices(2) - nz = cmfd % indices(3) - ng = cmfd % indices(4) - - ! Set flux object and source distribution to all zeros - cmfd % flux = ZERO - cmfd % openmc_src = ZERO - - ! Associate tallies and mesh - associate (t => cmfd_tallies(1) % obj) - i_filter_mesh = t % filter(t % find_filter(FILTER_MESH)) - end associate - - select type(filt => filters(i_filter_mesh) % obj) - type is (MeshFilter) - m = meshes(filt % mesh()) - end select - - ! Set mesh widths - cmfd % hxyz(1,:,:,:) = m % width(1) ! set x width - cmfd % hxyz(2,:,:,:) = m % width(2) ! set y width - cmfd % hxyz(3,:,:,:) = m % width(3) ! set z width - - cmfd % keff_bal = ZERO - - ! Begin loop around tallies - TAL: do ital = 1, size(cmfd_tallies) - - ! Associate tallies and mesh - associate (t => cmfd_tallies(ital) % obj) - - if (ital < 3) then - i_filter_mesh = t % filter(t % find_filter(FILTER_MESH)) - else if (ital == 3) then - i_filter_mesh = t % filter(t % find_filter(FILTER_MESHSURFACE)) - else if (ital == 4) then - i_filter_mesh = t % filter(t % find_filter(FILTER_MESH)) - i_filter_legendre = t % filter(t % find_filter(FILTER_LEGENDRE)) - end if - - ! Check for energy filters - energy_filters = (t % find_filter(FILTER_ENERGYIN) > 0) - - if (energy_filters) then - i_filter_ein = t % filter(t % find_filter(FILTER_ENERGYIN)) - i_filter_eout = t % filter(t % find_filter(FILTER_ENERGYOUT)) - end if - - ! Begin loop around space - ZLOOP: do k = 1,nz - - YLOOP: do j = 1,ny - - XLOOP: do i = 1,nx - - ! Check for active mesh cell - if (allocated(cmfd%coremap)) then - if (cmfd%coremap(i,j,k) == CMFD_NOACCEL) then - cycle - end if - end if - - ! Loop around energy groups - OUTGROUP: do h = 1,ng - - ! Start tally 1 - TALLY: if (ital == 1) then - - ! Reset all bins to 1 - do l = 1, size(t % filter) - call filter_matches(t % filter(l)) % bins_clear() - call filter_matches(t % filter(l)) % bins_push_back(1) - end do - - ! Set ijk as mesh indices - ijk = (/ i, j, k /) - - ! Get bin number for mesh indices - call filter_matches(i_filter_mesh) & - % bins_set_data(1, m % get_bin_from_indices(ijk)) - - ! Apply energy in filter - if (energy_filters) then - call filter_matches(i_filter_ein) % bins_set_data(1, ng - h + 1) - end if - - ! Calculate score index from bins - score_index = 1 - do l = 1, size(t % filter) - score_index = score_index + (filter_matches(t % filter(l)) & - % bins_data(1) - 1) * t % stride(l) - end do - - ! Get flux - flux = t % results(RESULT_SUM,1,score_index) - cmfd % flux(h,i,j,k) = flux - - ! Detect zero flux, abort if located - if ((flux - ZERO) < TINY_BIT) then - call fatal_error('Detected zero flux without coremap overlay & - &at: (' // to_str(i) // ',' // to_str(j) // ',' // & - &to_str(k) // ') in group ' // to_str(h)) - end if - - ! Get total rr and convert to total xs - cmfd % totalxs(h,i,j,k) = t % results(RESULT_SUM,2,score_index) / flux - - else if (ital == 2) then - - ! Begin loop to get energy out tallies - INGROUP: do g = 1, ng - - ! Reset all bins to 1 - do l = 1, size(t % filter) - call filter_matches(t % filter(l)) % bins_clear() - call filter_matches(t % filter(l)) % bins_push_back(1) - end do - - ! Set ijk as mesh indices - ijk = (/ i, j, k /) - - ! Get bin number for mesh indices - call filter_matches(i_filter_mesh) & - % bins_set_data(1, m % get_bin_from_indices(ijk)) - - if (energy_filters) then - ! Apply energy in filter - call filter_matches(i_filter_ein) % bins_set_data(1, ng - h + 1) - - ! Set energy out bin - call filter_matches(i_filter_eout) % bins_set_data(1, ng - g + 1) - end if - - ! Calculate score index from bins - score_index = 1 - do l = 1, size(t % filter) - score_index = score_index + (filter_matches(t % filter(l)) & - % bins_data(1) - 1) * t % stride(l) - end do - - ! Get scattering - cmfd % scattxs(h,g,i,j,k) = t % results(RESULT_SUM,1,score_index) /& - cmfd % flux(h,i,j,k) - - ! Get nu-fission - cmfd % nfissxs(h,g,i,j,k) = t % results(RESULT_SUM,2,score_index) /& - cmfd % flux(h,i,j,k) - - ! Bank source - cmfd % openmc_src(g,i,j,k) = cmfd % openmc_src(g,i,j,k) + & - t % results(RESULT_SUM,2,score_index) - cmfd % keff_bal = cmfd % keff_bal + & - t % results(RESULT_SUM,2,score_index) / t % n_realizations - - end do INGROUP - - else if (ital == 3) then - - ! Initialize and filter for energy - do l = 1, size(t % filter) - call filter_matches(t % filter(l)) % bins_clear() - call filter_matches(t % filter(l)) % bins_push_back(1) - end do - - ! Set the bin for this mesh cell - i_mesh = m % get_bin_from_indices([ i, j, k ]) - call filter_matches(i_filter_mesh) % bins_set_data(1, 12*(i_mesh - 1) + 1) - - ! Set the energy bin if needed - if (energy_filters) then - call filter_matches(i_filter_ein) % bins_set_data(1, ng - h + 1) - end if - - score_index = 0 - do l = 1, size(t % filter) - score_index = score_index + (filter_matches(t % filter(l)) & - % bins_data(1) - 1) * t % stride(l) - end do - - ! Left surface - cmfd % current(1,h,i,j,k) = t % results(RESULT_SUM, 1, & - score_index + 1 + ng*(OUT_LEFT - 1)) - cmfd % current(2,h,i,j,k) = t % results(RESULT_SUM, 1, & - score_index + 1 + ng*(IN_LEFT - 1)) - - ! Right surface - cmfd % current(3,h,i,j,k) = t % results(RESULT_SUM, 1, & - score_index + 1 + ng*(IN_RIGHT - 1)) - cmfd % current(4,h,i,j,k) = t % results(RESULT_SUM, 1, & - score_index + 1 + ng*(OUT_RIGHT - 1)) - - ! Back surface - cmfd % current(5,h,i,j,k) = t % results(RESULT_SUM, 1, & - score_index + 1 + ng*(OUT_BACK - 1)) - cmfd % current(6,h,i,j,k) = t % results(RESULT_SUM, 1, & - score_index + 1 + ng*(IN_BACK - 1)) - - ! Front surface - cmfd % current(7,h,i,j,k) = t % results(RESULT_SUM, 1, & - score_index + 1 + ng*(IN_FRONT - 1)) - cmfd % current(8,h,i,j,k) = t % results(RESULT_SUM, 1, & - score_index + 1 + ng*(OUT_FRONT - 1)) - - ! Left surface - cmfd % current(9,h,i,j,k) = t % results(RESULT_SUM, 1, & - score_index + 1 + ng*(OUT_BOTTOM - 1)) - cmfd % current(10,h,i,j,k) = t % results(RESULT_SUM, 1, & - score_index + 1 + ng*(IN_BOTTOM - 1)) - - ! Right surface - cmfd % current(11,h,i,j,k) = t % results(RESULT_SUM, 1, & - score_index + 1 + ng*(IN_TOP - 1)) - cmfd % current(12,h,i,j,k) = t % results(RESULT_SUM, 1, & - score_index + 1 + ng*(OUT_TOP - 1)) - - else if (ital == 4) then - - ! Reset all bins to 1 - do l = 1, size(t % filter) - call filter_matches(t % filter(l)) % bins_clear() - call filter_matches(t % filter(l)) % bins_push_back(1) - end do - - ! Set ijk as mesh indices - ijk = (/ i, j, k /) - - ! Get bin number for mesh indices - call filter_matches(i_filter_mesh) & - % bins_set_data(1, m % get_bin_from_indices(ijk)) - - ! Apply energy in filter - if (energy_filters) then - call filter_matches(i_filter_ein) % bins_set_data(1, ng - h + 1) - end if - - ! Apply Legendre filter - call filter_matches(i_filter_legendre) % bins_set_data(1, 2) - - ! Calculate score index from bins - score_index = 1 - do l = 1, size(t % filter) - score_index = score_index + (filter_matches(t % filter(l)) & - % bins_data(1) - 1) * t % stride(l) - end do - - ! Get p1 scatter rr and convert to p1 scatter xs - cmfd % p1scattxs(h,i,j,k) = & - t % results(RESULT_SUM,1,score_index) / & - cmfd % flux(h,i,j,k) - - ! Calculate diffusion coefficient - cmfd % diffcof(h,i,j,k) = & - ONE/(3.0_8*(cmfd % totalxs(h,i,j,k) - & - cmfd % p1scattxs(h,i,j,k))) - end if TALLY - - end do OUTGROUP - - end do XLOOP - - end do YLOOP - - end do ZLOOP - - end associate - end do TAL - - ! Normalize openmc source distribution - cmfd % openmc_src = cmfd % openmc_src/sum(cmfd % openmc_src)*cmfd%norm - - end subroutine compute_xs - -!=============================================================================== -! SET_COREMAP is a routine that sets the core mapping information -!=============================================================================== - - subroutine set_coremap() - - use constants, only: CMFD_NOACCEL - - integer :: counter=1 ! counter for unique fuel assemblies - integer :: nx ! number of mesh cells in x direction - integer :: ny ! number of mesh cells in y direction - integer :: nz ! number of mesh cells in z direction - integer :: i ! iteration counter for x - integer :: j ! iteration counter for y - integer :: k ! iteration counter for z - - ! Extract spatial indices from object - nx = cmfd % indices(1) - ny = cmfd % indices(2) - nz = cmfd % indices(3) - - ! Count how many fuel assemblies exist - cmfd % mat_dim = sum(cmfd % coremap - 1) - - ! Allocate indexmap - if (.not. allocated(cmfd % indexmap)) & - allocate(cmfd % indexmap(cmfd % mat_dim,3)) - - ! Begin loops over spatial indices - ZLOOP: do k = 1, nz - - YLOOP: do j = 1, ny - - XLOOP: do i = 1, nx - - ! Check for reflector - if (cmfd % coremap(i,j,k) == 1) then - - ! reset value to CMFD no acceleration constant - cmfd % coremap(i,j,k) = CMFD_NOACCEL - - else - - ! Must be a fuel --> give unique id number - cmfd % coremap(i,j,k) = counter - cmfd % indexmap(counter,1) = i - cmfd % indexmap(counter,2) = j - cmfd % indexmap(counter,3) = k - counter = counter + 1 - - end if - - end do XLOOP - - end do YLOOP - - end do ZLOOP - - end subroutine set_coremap - -!=============================================================================== -! NEUTRON_BALANCE computes the RMS neutron balance over the CMFD mesh -!=============================================================================== - - subroutine neutron_balance() - - use constants, only: ONE, ZERO, CMFD_NOACCEL, CMFD_NORES - use simulation_header, only: keff, current_batch - - integer :: nx ! number of mesh cells in x direction - integer :: ny ! number of mesh cells in y direction - integer :: nz ! number of mesh cells in z direction - integer :: ng ! number of energy groups - integer :: i ! iteration counter for x - integer :: j ! iteration counter for y - integer :: k ! iteration counter for z - integer :: g ! iteration counter for g - integer :: h ! iteration counter for outgoing groups - integer :: l ! iteration counter for leakage - integer :: cnt ! number of locations to count neutron balance - real(8) :: leakage ! leakage term in neutron balance - real(8) :: interactions ! total number of interactions in balance - real(8) :: scattering ! scattering term in neutron balance - real(8) :: fission ! fission term in neutron balance - real(8) :: res ! residual of neutron balance - real(8) :: rms ! RMS of the residual - - ! Extract spatial and energy indices from object - nx = cmfd % indices(1) - ny = cmfd % indices(2) - nz = cmfd % indices(3) - ng = cmfd % indices(4) - - ! Allocate res dataspace - if (.not. allocated(cmfd%resnb)) allocate(cmfd%resnb(ng,nx,ny,nz)) - - ! Reset rms and cnt - rms = ZERO - cnt = 0 - - ! Begin loop around space and energy groups - ZLOOP: do k = 1, nz - - YLOOP: do j = 1, ny - - XLOOP: do i = 1, nx - - GROUPG: do g = 1, ng - - ! Check for active mesh - if (allocated(cmfd%coremap)) then - if (cmfd%coremap(i,j,k) == CMFD_NOACCEL) then - cmfd%resnb(g,i,j,k) = CMFD_NORES - cycle - end if - end if - - ! Get leakage - leakage = ZERO - LEAK: do l = 1, 3 - leakage = leakage + ((cmfd % current(4*l,g,i,j,k) - & - cmfd % current(4*l-1,g,i,j,k))) - & - ((cmfd % current(4*l-2,g,i,j,k) - & - cmfd % current(4*l-3,g,i,j,k))) - - end do LEAK - - ! Interactions - interactions = cmfd % totalxs(g,i,j,k) * cmfd % flux(g,i,j,k) - - ! Get scattering and fission - scattering = ZERO - fission = ZERO - GROUPH: do h = 1, ng - - scattering = scattering + cmfd % scattxs(h,g,i,j,k) * & - cmfd % flux(h,i,j,k) - - fission = fission + cmfd % nfissxs(h,g,i,j,k) * & - cmfd % flux(h,i,j,k) - - end do GROUPH - - ! Compute residual - res = leakage + interactions - scattering - (ONE/keff)*fission - - ! Normalize by flux - res = res/cmfd%flux(g,i,j,k) - - ! Bank res in cmfd object - cmfd%resnb(g,i,j,k) = res - - ! Take square for RMS calculation - rms = rms + res**2 - cnt = cnt + 1 - - end do GROUPG - - end do XLOOP - - end do YLOOP - - end do ZLOOP - - ! Calculate RMS and record in vector for this batch - cmfd % balance(current_batch) = sqrt(ONE/dble(cnt)*rms) - - end subroutine neutron_balance - -!=============================================================================== -! COMPUTE_DTILDE precomputes the diffusion coupling coefficient -!=============================================================================== - - subroutine compute_dtilde() - - use constants, only: CMFD_NOACCEL, ZERO_FLUX, TINY_BIT - - integer :: nx ! maximum number of cells in x direction - integer :: ny ! maximum number of cells in y direction - integer :: nz ! maximum number of cells in z direction - integer :: ng ! maximum number of energy groups - integer :: nxyz(3,2) ! single vector containing boundary locations - integer :: i ! iteration counter for x - integer :: j ! iteration counter for y - integer :: k ! iteration counter for z - integer :: g ! iteration counter for groups - integer :: l ! iteration counter for leakages - integer :: xyz_idx ! index for determining if x,y or z leakage - integer :: dir_idx ! index for determining - or + face of cell - integer :: shift_idx ! parameter to shift index by +1 or -1 - integer :: neig_idx(3) ! spatial indices of neighbour - integer :: bound(6) ! vector containing indices for boudary check - real(8) :: albedo(6) ! albedo vector with global boundaries - real(8) :: cell_dc ! diffusion coef of current cell - real(8) :: cell_hxyz(3) ! cell dimensions of current ijk cell - real(8) :: neig_dc ! diffusion coefficient of neighbor cell - real(8) :: neig_hxyz(3) ! cell dimensions of neighbor cell - real(8) :: dtilde ! finite difference coupling parameter - real(8) :: ref_albedo ! albedo to reflector - - ! Get maximum of spatial and group indices - nx = cmfd%indices(1) - ny = cmfd%indices(2) - nz = cmfd%indices(3) - ng = cmfd%indices(4) - - ! Create single vector of these indices for boundary calculation - nxyz(1,:) = (/1,nx/) - nxyz(2,:) = (/1,ny/) - nxyz(3,:) = (/1,nz/) - - ! Get boundary condition information - albedo = cmfd%albedo - - ! Loop over group and spatial indices - ZLOOP: do k = 1, nz - - YLOOP: do j = 1, ny - - XLOOP: do i = 1, nx - - GROUP: do g = 1, ng - - ! Check for active mesh cell - if (allocated(cmfd%coremap)) then - if (cmfd%coremap(i,j,k) == CMFD_NOACCEL) cycle - end if - - ! Get cell data - cell_dc = cmfd%diffcof(g,i,j,k) - cell_hxyz = cmfd%hxyz(:,i,j,k) - - ! Setup of vector to identify boundary conditions - bound = (/i,i,j,j,k,k/) - - ! Begin loop around sides of cell for leakage - LEAK: do l = 1, 6 - - ! Define xyz and +/- indices - xyz_idx = int(ceiling(real(l)/real(2))) ! x=1, y=2, z=3 - dir_idx = 2 - mod(l,2) ! -=1, +=2 - shift_idx = -2*mod(l,2) + 1 ! shift neig by -1 or +1 - - ! Check if at a boundary - if (bound(l) == nxyz(xyz_idx,dir_idx)) then - - ! Compute dtilde with albedo boundary condition - dtilde = (2*cell_dc*(1-albedo(l)))/(4*cell_dc*(1+albedo(l)) + & - (1-albedo(l))*cell_hxyz(xyz_idx)) - - ! Check for zero flux - if (abs(albedo(l) - ZERO_FLUX) < TINY_BIT) dtilde = 2*cell_dc / & - cell_hxyz(xyz_idx) - - else ! not a boundary - - ! Compute neighboring cell indices - neig_idx = (/i,j,k/) ! begin with i,j,k - neig_idx(xyz_idx) = shift_idx + neig_idx(xyz_idx) - - ! Get neigbor cell data - neig_dc = cmfd%diffcof(g,neig_idx(1),neig_idx(2),neig_idx(3)) - neig_hxyz = cmfd%hxyz(:,neig_idx(1),neig_idx(2),neig_idx(3)) - - ! Check for fuel-reflector interface - if (cmfd_coremap) then - - if (cmfd % coremap(neig_idx(1),neig_idx(2),neig_idx(3)) == & - CMFD_NOACCEL .and. cmfd % coremap(i,j,k) /= CMFD_NOACCEL) then - - ! Get albedo - ref_albedo = get_reflector_albedo(l,g,i,j,k) - - ! Compute dtilde - dtilde = (2*cell_dc*(1-ref_albedo))/(4*cell_dc*(1+ & - ref_albedo)+(1-ref_albedo)*cell_hxyz(xyz_idx)) - - else ! Not next to a reflector or no core map - - ! Compute dtilde to neighbor cell - dtilde = (2*cell_dc*neig_dc)/(neig_hxyz(xyz_idx)*cell_dc + & - cell_hxyz(xyz_idx)*neig_dc) - - end if - - else ! no core map - - ! Compute dtilde to neighbor cell - dtilde = (2*cell_dc*neig_dc)/(neig_hxyz(xyz_idx)*cell_dc + & - cell_hxyz(xyz_idx)*neig_dc) - - end if - - end if - - ! Record dtilde in cmfd object - cmfd%dtilde(l,g,i,j,k) = dtilde - - end do LEAK - - end do GROUP - - end do XLOOP - - end do YLOOP - - end do ZLOOP - - end subroutine compute_dtilde - -!=============================================================================== -! COMPUTE_DHAT computes the nonlinear coupling coefficient -!=============================================================================== - - subroutine compute_dhat() - - use constants, only: CMFD_NOACCEL, ZERO - use error, only: write_message - use string, only: to_str - - integer :: nx ! maximum number of cells in x direction - integer :: ny ! maximum number of cells in y direction - integer :: nz ! maximum number of cells in z direction - integer :: ng ! maximum number of energy groups - integer :: nxyz(3,2) ! single vector containing boundary locations - integer :: i ! iteration counter for x - integer :: j ! iteration counter for y - integer :: k ! iteration counter for z - integer :: g ! iteration counter for groups - integer :: l ! iteration counter for leakages - integer :: xyz_idx ! index for determining if x,y or z leakage - integer :: dir_idx ! index for determining - or + face of cell - integer :: shift_idx ! parameter to shift index by +1 or -1 - integer :: neig_idx(3) ! spatial indices of neighbour - integer :: bound(6) ! vector containing indices for boudary check - real(8) :: cell_dtilde(6) ! cell dtilde for each face - real(8) :: cell_flux ! flux in current cell - real(8) :: current(12) ! area integrated cell current at each face - real(8) :: net_current ! net current on a face - real(8) :: neig_flux ! flux in neighbor cell - real(8) :: dhat ! dhat equivalence parameter - - ! Get maximum of spatial and group indices - nx = cmfd%indices(1) - ny = cmfd%indices(2) - nz = cmfd%indices(3) - ng = cmfd%indices(4) - - ! Create single vector of these indices for boundary calculation - nxyz(1,:) = (/1,nx/) - nxyz(2,:) = (/1,ny/) - nxyz(3,:) = (/1,nz/) - - ! Geting loop over group and spatial indices - ZLOOP: do k = 1,nz - - YLOOP: do j = 1,ny - - XLOOP: do i = 1,nx - - GROUP: do g = 1,ng - - ! Check for active mesh cell - if (allocated(cmfd%coremap)) then - if (cmfd%coremap(i,j,k) == CMFD_NOACCEL) then - cycle - end if - end if - - ! Get cell data - cell_dtilde = cmfd%dtilde(:,g,i,j,k) - cell_flux = cmfd%flux(g,i,j,k)/product(cmfd%hxyz(:,i,j,k)) - current = cmfd%current(:,g,i,j,k) - - ! Setup of vector to identify boundary conditions - bound = (/i,i,j,j,k,k/) - - ! Begin loop around sides of cell for leakage - LEAK: do l = 1,6 - - ! Define xyz and +/- indices - xyz_idx = int(ceiling(real(l)/real(2))) ! x=1, y=2, z=3 - dir_idx = 2 - mod(l,2) ! -=1, +=2 - shift_idx = -2*mod(l,2) +1 ! shift neig by -1 or +1 - - ! Calculate net current on l face (divided by surf area) - net_current = (current(2*l) - current(2*l-1)) / & - product(cmfd%hxyz(:,i,j,k)) * cmfd%hxyz(xyz_idx,i,j,k) - - ! Check if at a boundary - if (bound(l) == nxyz(xyz_idx,dir_idx)) then - - ! Compute dhat - dhat = (net_current - shift_idx*cell_dtilde(l)*cell_flux) / & - cell_flux - - else ! not a boundary - - ! Compute neighboring cell indices - neig_idx = (/i,j,k/) ! begin with i,j,k - neig_idx(xyz_idx) = shift_idx + neig_idx(xyz_idx) - - ! Get neigbor flux - neig_flux = cmfd%flux(g,neig_idx(1),neig_idx(2),neig_idx(3)) / & - product(cmfd%hxyz(:,neig_idx(1),neig_idx(2),neig_idx(3))) - - ! Check for fuel-reflector interface - if (cmfd_coremap) then - - if (cmfd % coremap(neig_idx(1),neig_idx(2),neig_idx(3)) == & - CMFD_NOACCEL .and. cmfd % coremap(i,j,k) /= CMFD_NOACCEL) then - - ! compute dhat - dhat = (net_current - shift_idx*cell_dtilde(l)*cell_flux) /& - cell_flux - - else ! not a fuel-reflector interface - - ! Compute dhat - dhat = (net_current + shift_idx*cell_dtilde(l)* & - (neig_flux - cell_flux))/(neig_flux + cell_flux) - - end if - - else ! not for fuel-reflector case - - ! Compute dhat - dhat = (net_current + shift_idx*cell_dtilde(l)* & - (neig_flux - cell_flux))/(neig_flux + cell_flux) - - end if - - end if - - ! record dhat in cmfd object - cmfd%dhat(l,g,i,j,k) = dhat - - ! check for dhat reset - if (dhat_reset) then - cmfd%dhat(l,g,i,j,k) = ZERO - end if - - end do LEAK - - end do GROUP - - end do XLOOP - - end do YLOOP - - end do ZLOOP - - ! write that dhats are zero - if (dhat_reset) then - call write_message('Dhats reset to zero.', 8) - end if - - end subroutine compute_dhat - -!=============================================================================== -! GET_REFLECTOR_ALBEDO is a function that calculates the albedo to the reflector -!=============================================================================== - - function get_reflector_albedo(l, g, i, j, k) - - use constants, only: ONE - - real(8) :: get_reflector_albedo ! reflector albedo - integer, intent(in) :: i ! iteration counter for x - integer, intent(in) :: j ! iteration counter for y - integer, intent(in) :: k ! iteration counter for z - integer, intent(in) :: g ! iteration counter for groups - integer, intent(in) :: l ! iteration counter for leakages - - integer :: shift_idx ! parameter to shift index by +1 or -1 - real(8) :: current(12) ! partial currents for all faces of mesh cell - real(8) :: albedo ! the albedo - - ! Get partial currents from object - current = cmfd%current(:,g,i,j,k) - - ! Define xyz and +/- indices - shift_idx = -2*mod(l,2) + 1 ! shift neig by -1 or +1 - - ! Calculate albedo - if ((shift_idx == 1 .and. current(2*l ) < 1.0e-10_8) .or. & - (shift_idx == -1 .and. current(2*l-1) < 1.0e-10_8)) then - albedo = ONE - else - albedo = (current(2*l-1)/current(2*l))**(shift_idx) - end if - - ! Assign to function variable - get_reflector_albedo = albedo - - end function get_reflector_albedo - -!=============================================================================== -! COMPUTE_EFFECTIVE_DOWNSCATTER changes downscatter rate for zero upscatter -!=============================================================================== - - subroutine compute_effective_downscatter() - - use constants, only: ZERO, CMFD_NOACCEL - - integer :: nx ! number of mesh cells in x direction - integer :: ny ! number of mesh cells in y direction - integer :: nz ! number of mesh cells in z direction - integer :: ng ! number of energy groups - integer :: i ! iteration counter for x - integer :: j ! iteration counter for y - integer :: k ! iteration counter for z - real(8) :: flux1 ! group 1 volume int flux - real(8) :: flux2 ! group 2 volume int flux - real(8) :: sigt1 ! group 1 total xs - real(8) :: sigt2 ! group 2 total xs - real(8) :: sigs11 ! scattering transfer 1 --> 1 - real(8) :: sigs21 ! scattering transfer 2 --> 1 - real(8) :: sigs12 ! scattering transfer 1 --> 2 - real(8) :: sigs22 ! scattering transfer 2 --> 2 - real(8) :: siga1 ! group 1 abs xs - real(8) :: siga2 ! group 2 abs xs - real(8) :: sigs12_eff ! effective downscatter xs - - ! Extract spatial and energy indices from object - nx = cmfd % indices(1) - ny = cmfd % indices(2) - nz = cmfd % indices(3) - ng = cmfd % indices(4) - - ! Return if not two groups - if (ng /= 2) return - - ! Begin loop around space and energy groups - ZLOOP: do k = 1, nz - - YLOOP: do j = 1, ny - - XLOOP: do i = 1, nx - - ! Check for active mesh - if (allocated(cmfd%coremap)) then - if (cmfd%coremap(i,j,k) == CMFD_NOACCEL) cycle - end if - - ! Extract cross sections and flux from object - flux1 = cmfd % flux(1,i,j,k) - flux2 = cmfd % flux(2,i,j,k) - sigt1 = cmfd % totalxs(1,i,j,k) - sigt2 = cmfd % totalxs(2,i,j,k) - sigs11 = cmfd % scattxs(1,1,i,j,k) - sigs21 = cmfd % scattxs(2,1,i,j,k) - sigs12 = cmfd % scattxs(1,2,i,j,k) - sigs22 = cmfd % scattxs(2,2,i,j,k) - - ! Compute absorption xs - siga1 = sigt1 - sigs11 - sigs12 - siga2 = sigt2 - sigs22 - sigs21 - - ! Compute effective downscatter xs - sigs12_eff = sigs12 - sigs21*flux2/flux1 - - ! Recompute total cross sections (use effective and no upscattering) - sigt1 = siga1 + sigs11 + sigs12_eff - sigt2 = siga2 + sigs22 - - ! Record total xs - cmfd % totalxs(1,i,j,k) = sigt1 - cmfd % totalxs(2,i,j,k) = sigt2 - - ! Record effective downscatter xs - cmfd % scattxs(1,2,i,j,k) = sigs12_eff - - ! Zero out upscatter cross section - cmfd % scattxs(2,1,i,j,k) = ZERO - - end do XLOOP - - end do YLOOP - - end do ZLOOP - - end subroutine compute_effective_downscatter - -end module cmfd_data diff --git a/src/cmfd_execute.F90 b/src/cmfd_execute.F90 deleted file mode 100644 index 4b97b5e327..0000000000 --- a/src/cmfd_execute.F90 +++ /dev/null @@ -1,410 +0,0 @@ -module cmfd_execute - -!============================================================================== -! CMFD_EXECUTE -- This module is the highest level cmfd module that controls the -! cross section generation, diffusion calculation, and source re-weighting -!============================================================================== - - use cmfd_header - use settings - use simulation_header - - implicit none - private - public :: execute_cmfd, cmfd_init_batch, cmfd_tally_init - -#ifdef OPENMC_MPI - interface - subroutine cmfd_broadcast(n, buffer) bind(C) - import C_DOUBLE, C_INT - integer(C_INT), value :: n - real(C_DOUBLE), intent(out) :: buffer - end subroutine - end interface -#endif - -contains - -!============================================================================== -! EXECUTE_CMFD runs the CMFD calculation -!============================================================================== - - subroutine execute_cmfd() bind(C) - - use cmfd_data, only: set_up_cmfd - use cmfd_solver, only: cmfd_solver_execute - use error, only: warning, fatal_error - use message_passing, only: master - - ! CMFD single processor on master - if (master) then - - ! Start cmfd timer - call time_cmfd % start() - - ! Create cmfd data from OpenMC tallies - call set_up_cmfd() - - ! Call solver - call cmfd_solver_execute() - - ! Save k-effective - cmfd % k_cmfd(current_batch) = cmfd % keff - - ! check to perform adjoint on last batch - if (current_batch == n_batches .and. cmfd_run_adjoint) then - call cmfd_solver_execute(adjoint=.true.) - end if - - end if - - ! calculate fission source - call calc_fission_source() - - ! calculate weight factors - call cmfd_reweight(.true.) - - ! stop cmfd timer - if (master) call time_cmfd % stop() - - end subroutine execute_cmfd - -!============================================================================== -! CMFD_INIT_BATCH handles cmfd options at the start of every batch -!============================================================================== - - subroutine cmfd_init_batch() bind(C) - - ! Check to activate CMFD diffusion and possible feedback - ! this guarantees that when cmfd begins at least one batch of tallies are - ! accumulated - if (cmfd_run .and. cmfd_begin == current_batch) then - cmfd_on = .true. - end if - - ! If this is a restart run and we are just replaying batches leave - if (restart_run .and. current_batch <= restart_batch) return - - ! Check to reset tallies - if (cmfd_run .and. cmfd_reset % contains(current_batch)) then - call cmfd_tally_reset() - end if - - end subroutine cmfd_init_batch - -!=============================================================================== -! CALC_FISSION_SOURCE calculates the cmfd fission source -!=============================================================================== - - subroutine calc_fission_source() - - use constants, only: CMFD_NOACCEL, ZERO, TWO - use message_passing - use string, only: to_str - - integer :: nx ! maximum number of cells in x direction - integer :: ny ! maximum number of cells in y direction - integer :: nz ! maximum number of cells in z direction - integer :: ng ! maximum number of energy groups - integer :: n ! total size - integer :: i ! iteration counter for x - integer :: j ! iteration counter for y - integer :: k ! iteration counter for z - integer :: g ! iteration counter for groups - integer :: idx ! index in vector - real(8) :: hxyz(3) ! cell dimensions of current ijk cell - real(8) :: vol ! volume of cell - real(8),allocatable :: source(:,:,:,:) ! tmp source array for entropy - - ! Get maximum of spatial and group indices - nx = cmfd % indices(1) - ny = cmfd % indices(2) - nz = cmfd % indices(3) - ng = cmfd % indices(4) - n = ng*nx*ny*nz - - ! Allocate cmfd source if not already allocated and allocate buffer - if (.not. allocated(cmfd % cmfd_src)) & - allocate(cmfd % cmfd_src(ng,nx,ny,nz)) - - ! Reset cmfd source to 0 - cmfd % cmfd_src = ZERO - - ! Only perform for master - if (master) then - - ! Loop around indices to map to cmfd object - ZLOOP: do k = 1, nz - - YLOOP: do j = 1, ny - - XLOOP: do i = 1, nx - - GROUP: do g = 1, ng - - ! Check for core map - if (cmfd_coremap) then - if (cmfd % coremap(i,j,k) == CMFD_NOACCEL) then - cycle - end if - end if - - ! Get dimensions of cell - hxyz = cmfd % hxyz(:,i,j,k) - - ! Calculate volume - vol = hxyz(1)*hxyz(2)*hxyz(3) - - ! Get first index - idx = get_matrix_idx(1,i,j,k,ng,nx,ny) - - ! Compute fission source - cmfd % cmfd_src(g,i,j,k) = sum(cmfd % nfissxs(:,g,i,j,k) * & - cmfd % phi(idx:idx + (ng - 1)))*vol - - end do GROUP - - end do XLOOP - - end do YLOOP - - end do ZLOOP - - ! Normalize source such that it sums to 1.0 - cmfd % cmfd_src = cmfd % cmfd_src/sum(cmfd % cmfd_src) - - ! Compute entropy - if (entropy_on) then - - ! Allocate tmp array - if (.not.allocated(source)) allocate(source(ng,nx,ny,nz)) - - ! Initialize the source - source = ZERO - - ! Compute log - where (cmfd % cmfd_src > ZERO) - source = cmfd % cmfd_src*log(cmfd % cmfd_src)/log(TWO) - end where - - ! Sum that source - cmfd % entropy(current_batch) = -sum(source) - - ! Deallocate tmp array - if (allocated(source)) deallocate(source) - - end if - - ! Normalize source so average is 1.0 - cmfd % cmfd_src = cmfd % cmfd_src/sum(cmfd % cmfd_src)*cmfd % norm - - ! Calculate differences between normalized sources - cmfd % src_cmp(current_batch) = sqrt(ONE/cmfd % norm * & - sum((cmfd % cmfd_src - cmfd % openmc_src)**2)) - - end if - -#ifdef OPENMC_MPI - ! Broadcast full source to all procs - call cmfd_broadcast(n, cmfd % cmfd_src(1,1,1,1)) -#endif - - end subroutine calc_fission_source - -!=============================================================================== -! CMFD_REWEIGHT performs weighting of particles in the source bank -!=============================================================================== - - subroutine cmfd_reweight(new_weights) - - use algorithm, only: binary_search - use bank_header - use constants, only: ZERO, ONE - use error, only: warning, fatal_error - use message_passing - use string, only: to_str - - logical, intent(in) :: new_weights ! calcualte new weights - - integer :: nx ! maximum number of cells in x direction - integer :: ny ! maximum number of cells in y direction - integer :: nz ! maximum number of cells in z direction - integer(C_INT) :: ng ! maximum number of energy groups - integer(8) :: i ! iteration counter - integer :: g ! index for group - integer :: ijk(3) ! spatial bin location - integer :: e_bin ! energy bin of source particle - integer :: mesh_bin ! mesh bin of soruce particle - integer :: n_groups ! number of energy groups - real(8) :: norm ! normalization factor - real(C_DOUBLE) :: xyz(3) - logical(C_BOOL) :: outside ! any source sites outside mesh - logical :: in_mesh ! source site is inside mesh - - interface - subroutine cmfd_populate_sourcecounts(ng, energies, source_counts, outside) bind(C) - import C_INT, C_DOUBLE, C_BOOL - integer(C_INT), value :: ng - real(C_DOUBLE), intent(in) :: energies - real(C_DOUBLE), intent(out) :: source_counts - logical(C_BOOL), intent(out) :: outside - end subroutine - end interface - - ! Get maximum of spatial and group indices - nx = cmfd % indices(1) - ny = cmfd % indices(2) - nz = cmfd % indices(3) - ng = cmfd % indices(4) - - ! allocate arrays in cmfd object (can take out later extend to multigroup) - if (.not.allocated(cmfd%sourcecounts)) then - allocate(cmfd%sourcecounts(ng, nx*ny*nz)) - cmfd % sourcecounts = 0 - end if - if (.not.allocated(cmfd % weightfactors)) then - allocate(cmfd % weightfactors(ng,nx,ny,nz)) - cmfd % weightfactors = ONE - end if - - ! Compute new weight factors - if (new_weights) then - - ! Set weight factors to a default 1.0 - cmfd%weightfactors = ONE - - ! Count bank sites in mesh and reverse due to egrid structure - call cmfd_populate_sourcecounts(ng + 1, cmfd % egrid(1), & - cmfd % sourcecounts(1,1), outside) - - ! Check for sites outside of the mesh - if (master .and. outside) then - call fatal_error("Source sites outside of the CMFD mesh!") - end if - - ! Have master compute weight factors (watch for 0s) - if (master) then - ! Calculate normalization factor - norm = sum(cmfd % sourcecounts) / sum(cmfd % cmfd_src) - - do mesh_bin = 1, nx*ny*nz - call cmfd_mesh % get_indices_from_bin(mesh_bin, ijk) - do g = 1, ng - if (cmfd % sourcecounts(ng - g + 1, mesh_bin) > ZERO) then - if (cmfd % cmfd_src(g,ijk(1),ijk(2),ijk(3)) > ZERO) then - cmfd % weightfactors(g,ijk(1),ijk(2),ijk(3)) = & - cmfd % cmfd_src(g,ijk(1),ijk(2),ijk(3)) * norm & - / cmfd % sourcecounts(ng - g + 1, mesh_bin) - end if - end if - end do - end do - end if - - if (.not. cmfd_feedback) return - - ! Broadcast weight factors to all procs -#ifdef OPENMC_MPI - call cmfd_broadcast(ng*nx*ny*nz, cmfd % weightfactors(1,1,1,1)) -#endif - end if - - ! begin loop over source bank - do i = 1, work - - ! Determine spatial bin - call source_bank_xyz(i, xyz) - call cmfd_mesh % get_indices(xyz, ijk, in_mesh) - - ! Determine energy bin - n_groups = size(cmfd % egrid) - 1 - if (source_bank_E(i) < cmfd % egrid(1)) then - e_bin = 1 - if (master) call warning('Source pt below energy grid') - elseif (source_bank_E(i) > cmfd % egrid(n_groups + 1)) then - e_bin = n_groups - if (master) call warning('Source pt above energy grid') - else - e_bin = binary_search(cmfd % egrid, n_groups + 1, source_bank_E(i)) - end if - - ! Reverese energy bin (lowest grp is highest energy bin) - e_bin = n_groups - e_bin + 1 - - ! Check for outside of mesh - if (.not. in_mesh) then - call fatal_error('Source site found outside of CMFD mesh') - end if - - ! Reweight particle - call source_bank_set_wgt(i, source_bank_wgt(i) * & - cmfd % weightfactors(e_bin, ijk(1), ijk(2), ijk(3))) - end do - - end subroutine cmfd_reweight - -!=============================================================================== -! GET_MATRIX_IDX takes (x,y,z,g) indices and computes location in matrix -!=============================================================================== - - function get_matrix_idx(g, i, j, k, ng, nx, ny) result (matidx) - - integer :: matidx ! the index location in matrix - integer, intent(in) :: i ! current x index - integer, intent(in) :: j ! current y index - integer, intent(in) :: k ! current z index - integer, intent(in) :: g ! current group index - integer, intent(in) :: nx ! maximum number of cells in x direction - integer, intent(in) :: ny ! maximum number of cells in y direction - integer, intent(in) :: ng ! maximum number of energy groups - - ! Check if coremap is used - if (cmfd_coremap) then - - ! Get idx from core map - matidx = ng*(cmfd % coremap(i,j,k)) - (ng - g) - - else - - ! Compute index - matidx = g + ng*(i - 1) + ng*nx*(j - 1) + ng*nx*ny*(k - 1) - - end if - - end function get_matrix_idx - -!=============================================================================== -! CMFD_TALLY_INIT -!=============================================================================== - - subroutine cmfd_tally_init() bind(C) - integer :: i - if (cmfd_run) then - do i = 1, size(cmfd_tallies) - cmfd_tallies(i) % obj % active = .true. - end do - end if - end subroutine cmfd_tally_init - -!=============================================================================== -! CMFD_TALLY_RESET resets all cmfd tallies -!=============================================================================== - - subroutine cmfd_tally_reset() - - use error, only: write_message - - integer :: i ! loop counter - - ! Print message - call write_message("CMFD tallies reset", 6) - - ! Reset CMFD tallies - do i = 1, size(cmfd_tallies) - cmfd_tallies(i) % obj % n_realizations = 0 - cmfd_tallies(i) % obj % results(:,:,:) = ZERO - end do - - end subroutine cmfd_tally_reset - -end module cmfd_execute diff --git a/src/cmfd_execute.cpp b/src/cmfd_execute.cpp deleted file mode 100644 index 28561b00a7..0000000000 --- a/src/cmfd_execute.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include // for copy -#include -#include - -#include "xtensor/xarray.hpp" -#include "xtensor/xio.hpp" - -#include "openmc/bank.h" -#include "openmc/capi.h" -#include "openmc/mesh.h" -#include "openmc/message_passing.h" -#include "openmc/simulation.h" -#include "openmc/settings.h" - -namespace openmc { - - -extern "C" void -cmfd_populate_sourcecounts(int n_energy, const double* energies, - double* source_counts, bool* outside) -{ - // Get source counts in each mesh bin / energy bin - auto& m = model::meshes.at(settings::index_cmfd_mesh); - xt::xarray counts = m->count_sites(simulation::work, - simulation::source_bank.data(), n_energy, energies, outside); - - // Copy data from the xarray into the source counts array - std::copy(counts.begin(), counts.end(), source_counts); -} - -#ifdef OPENMC_MPI -extern "C" void -cmfd_broadcast(int n, double* buffer) -{ - MPI_Bcast(buffer, n, MPI_DOUBLE, 0, mpi::intracomm); -} -#endif - - -} // namespace openmc diff --git a/src/cmfd_header.F90 b/src/cmfd_header.F90 deleted file mode 100644 index f4b14b6f2b..0000000000 --- a/src/cmfd_header.F90 +++ /dev/null @@ -1,272 +0,0 @@ -module cmfd_header - - use, intrinsic :: ISO_C_BINDING - - use constants, only: CMFD_NOACCEL, ZERO, ONE - use mesh_header, only: RegularMesh - use set_header, only: SetInt - use tally_header, only: TallyContainer - use timer_header, only: Timer - - implicit none - private - public :: allocate_cmfd, deallocate_cmfd - - type, public :: cmfd_type - - ! Indices for problem - integer :: indices(4) - - ! Albedo boundary condition - real(8) :: albedo(6) - - ! Core overlay map - integer, allocatable :: coremap(:,:,:) - integer, allocatable :: indexmap(:,:) - integer :: mat_dim = CMFD_NOACCEL - - ! Energy grid - real(C_DOUBLE), allocatable :: egrid(:) - - ! Cross sections - real(8), allocatable :: totalxs(:,:,:,:) - real(8), allocatable :: p1scattxs(:,:,:,:) - real(8), allocatable :: scattxs(:,:,:,:,:) - real(8), allocatable :: nfissxs(:,:,:,:,:) - - ! Diffusion coefficient - real(8), allocatable :: diffcof(:,:,:,:) - - ! Current - real(8), allocatable :: current(:,:,:,:,:) - - ! Flux - real(8), allocatable :: flux(:,:,:,:) - - ! Coupling coefficients and equivalence parameters - real(8), allocatable :: dtilde(:,:,:,:,:) - real(8), allocatable :: dhat(:,:,:,:,:) - - ! Dimensions of mesh cells ([hu,hv,hw],xloc,yloc,zloc) - real(8), allocatable :: hxyz(:,:,:,:) - - ! Source distributions - real(C_DOUBLE), allocatable :: cmfd_src(:,:,:,:) - real(C_DOUBLE), allocatable :: openmc_src(:,:,:,:) - - ! Source sites in each mesh box - real(C_DOUBLE), allocatable :: sourcecounts(:,:) - - ! Weight adjustment factors - real(8), allocatable :: weightfactors(:,:,:,:) - - ! Eigenvector/eigenvalue from cmfd run - real(8), allocatable :: phi(:) - real(8) :: keff = ZERO - - ! Eigenvector/eigenvalue from adjoint run - real(8), allocatable :: adj_phi(:) - real(8) :: adj_keff = ZERO - - ! Residual for neutron balance - real(8), allocatable :: resnb(:,:,:,:) - - ! Openmc source normalization factor - real(8) :: norm = ONE - - ! "Shannon entropy" from cmfd fission source - real(8), allocatable :: entropy(:) - - ! RMS of neutron balance equations - real(8), allocatable :: balance(:) - - ! RMS deviation of OpenMC and CMFD normalized source - real(8), allocatable :: src_cmp(:) - - ! Dominance ratio - real(8), allocatable :: dom(:) - - ! List of CMFD k - real(8), allocatable :: k_cmfd(:) - - ! Balance keff - real(8) :: keff_bal - - end type cmfd_type - - ! Main object - type(cmfd_type), public :: cmfd - - integer(C_INT), public, bind(C) :: index_cmfd_mesh - type(RegularMesh), public :: cmfd_mesh - - ! Pointers for different tallies - type(TallyContainer), public, pointer :: cmfd_tallies(:) => null() - - ! Timing objects - type(Timer), public :: time_cmfd ! timer for whole cmfd calculation - type(Timer), public :: time_cmfdbuild ! timer for matrix build - type(Timer), public :: time_cmfdsolve ! timer for solver - - ! Flag for active core map - logical, public :: cmfd_coremap = .false. - - ! Flag to reset dhats to zero - logical, public :: dhat_reset = .false. - - ! Flag to activate neutronic feedback via source weights - logical, public :: cmfd_feedback = .false. - - ! Adjoint method type - character(len=10), public :: cmfd_adjoint_type = 'physical' - - ! Number of incomplete ilu factorization levels - integer, public :: cmfd_ilu_levels = 1 - - ! Batch to begin cmfd - integer, public :: cmfd_begin = 1 - - ! Tally reset list - integer, public :: n_cmfd_resets - type(SetInt), public :: cmfd_reset - - ! Compute effective downscatter cross section - logical, public :: cmfd_downscatter = .false. - - ! Convergence monitoring - logical, public :: cmfd_power_monitor = .false. - - ! Cmfd output - logical, public :: cmfd_write_matrices = .false. - - ! Run an adjoint calculation (last batch only) - logical, public :: cmfd_run_adjoint = .false. - - ! CMFD run logicals - logical(C_BOOL), public, bind(C) :: cmfd_on = .false. - - ! CMFD display info - character(len=25), public :: cmfd_display = 'balance' - - ! Estimate of spectral radius of CMFD matrices and tolerances - real(8), public :: cmfd_spectral = ZERO - real(8), public :: cmfd_shift = 1.e6 - real(8), public :: cmfd_ktol = 1.e-8_8 - real(8), public :: cmfd_stol = 1.e-8_8 - real(8), public :: cmfd_atoli = 1.e-10_8 - real(8), public :: cmfd_rtoli = 1.e-5_8 - -contains - -!============================================================================== -! ALLOCATE_CMFD allocates all data in of cmfd type -!============================================================================== - - subroutine allocate_cmfd(this, n_batches) - - integer, intent(in) :: n_batches ! number of batches in calc - type(cmfd_type), intent(inout) :: this ! cmfd instance - - integer :: nx ! number of mesh cells in x direction - integer :: ny ! number of mesh cells in y direction - integer :: nz ! number of mesh cells in z direction - integer :: ng ! number of energy groups - - ! Extract spatial and energy indices from object - nx = this % indices(1) - ny = this % indices(2) - nz = this % indices(3) - ng = this % indices(4) - - ! Allocate flux, cross sections and diffusion coefficient - if (.not. allocated(this % flux)) allocate(this % flux(ng,nx,ny,nz)) - if (.not. allocated(this % totalxs)) allocate(this % totalxs(ng,nx,ny,nz)) - if (.not. allocated(this % p1scattxs)) allocate(this % p1scattxs(ng,nx,ny,nz)) - if (.not. allocated(this % scattxs)) allocate(this % scattxs(ng,ng,nx,ny,nz)) - if (.not. allocated(this % nfissxs)) allocate(this % nfissxs(ng,ng,nx,ny,nz)) - if (.not. allocated(this % diffcof)) allocate(this % diffcof(ng,nx,ny,nz)) - - ! Allocate dtilde and dhat - if (.not. allocated(this % dtilde)) allocate(this % dtilde(6,ng,nx,ny,nz)) - if (.not. allocated(this % dhat)) allocate(this % dhat(6,ng,nx,ny,nz)) - - ! Allocate dimensions for each box (here for general case) - if (.not. allocated(this % hxyz)) allocate(this % hxyz(3,nx,ny,nz)) - - ! Allocate surface currents - if (.not. allocated(this % current)) allocate(this % current(12,ng,nx,ny,nz)) - - ! Allocate source distributions - if (.not. allocated(this % cmfd_src)) allocate(this % cmfd_src(ng,nx,ny,nz)) - if (.not. allocated(this % openmc_src)) allocate(this % openmc_src(ng,nx,ny,nz)) - - ! Allocate source weight modification vars - if (.not. allocated(this % sourcecounts)) allocate(this % sourcecounts(ng,nx*ny*nz)) - if (.not. allocated(this % weightfactors)) allocate(this % weightfactors(ng,nx,ny,nz)) - - ! Allocate batchwise parameters - if (.not. allocated(this % entropy)) allocate(this % entropy(n_batches)) - if (.not. allocated(this % balance)) allocate(this % balance(n_batches)) - if (.not. allocated(this % src_cmp)) allocate(this % src_cmp(n_batches)) - if (.not. allocated(this % dom)) allocate(this % dom(n_batches)) - if (.not. allocated(this % k_cmfd)) allocate(this % k_cmfd(n_batches)) - - ! Set everthing to 0 except weight multiply factors if feedback isnt on - this % flux = ZERO - this % totalxs = ZERO - this % p1scattxs = ZERO - this % scattxs = ZERO - this % nfissxs = ZERO - this % diffcof = ZERO - this % dtilde = ZERO - this % dhat = ZERO - this % hxyz = ZERO - this % current = ZERO - this % cmfd_src = ZERO - this % openmc_src = ZERO - this % sourcecounts = ZERO - this % weightfactors = ONE - this % balance = ZERO - this % src_cmp = ZERO - this % dom = ZERO - this % k_cmfd = ZERO - this % entropy = ZERO - - end subroutine allocate_cmfd - -!=============================================================================== -! DEALLOCATE_CMFD frees all memory of cmfd type -!=============================================================================== - - subroutine deallocate_cmfd(this) - - type(cmfd_type), intent(inout) :: this ! cmfd instance - - if (allocated(this % egrid)) deallocate(this % egrid) - if (allocated(this % totalxs)) deallocate(this % totalxs) - if (allocated(this % p1scattxs)) deallocate(this % p1scattxs) - if (allocated(this % scattxs)) deallocate(this % scattxs) - if (allocated(this % nfissxs)) deallocate(this % nfissxs) - if (allocated(this % diffcof)) deallocate(this % diffcof) - if (allocated(this % current)) deallocate(this % current) - if (allocated(this % flux)) deallocate(this % flux) - if (allocated(this % dtilde)) deallocate(this % dtilde) - if (allocated(this % dhat)) deallocate(this % dhat) - if (allocated(this % hxyz)) deallocate(this % hxyz) - if (allocated(this % coremap)) deallocate(this % coremap) - if (allocated(this % indexmap)) deallocate(this % indexmap) - if (allocated(this % phi)) deallocate(this % phi) - if (allocated(this % sourcecounts)) deallocate(this % sourcecounts) - if (allocated(this % weightfactors)) deallocate(this % weightfactors) - if (allocated(this % cmfd_src)) deallocate(this % cmfd_src) - if (allocated(this % openmc_src)) deallocate(this % openmc_src) - if (allocated(this % balance)) deallocate(this % balance) - if (allocated(this % src_cmp)) deallocate(this % src_cmp) - if (allocated(this % dom)) deallocate(this % dom) - if (allocated(this % k_cmfd)) deallocate(this % k_cmfd) - if (allocated(this % entropy)) deallocate(this % entropy) - if (allocated(this % resnb)) deallocate(this % resnb) - - end subroutine deallocate_cmfd - -end module cmfd_header diff --git a/src/cmfd_input.F90 b/src/cmfd_input.F90 deleted file mode 100644 index d1e0f6a9a7..0000000000 --- a/src/cmfd_input.F90 +++ /dev/null @@ -1,495 +0,0 @@ -module cmfd_input - - use, intrinsic :: ISO_C_BINDING - - use cmfd_header - use mesh_header - use mgxs_interface, only: energy_bins, num_energy_groups - use tally - use tally_header - use timer_header - - implicit none - private - public :: configure_cmfd - -contains - -!=============================================================================== -! CONFIGURE_CMFD initializes CMFD parameters -!=============================================================================== - - subroutine configure_cmfd() - - ! Read in cmfd input file - call read_cmfd_xml() - - ! Initialize timers - call time_cmfd % reset() - call time_cmfdbuild % reset() - call time_cmfdsolve % reset() - - ! Allocate cmfd object - call allocate_cmfd(cmfd, n_batches) - - end subroutine configure_cmfd - -!=============================================================================== -! READ_INPUT reads the CMFD input file and organizes it into a data structure -!=============================================================================== - - subroutine read_cmfd_xml() - - use constants, only: ZERO, ONE - use error, only: fatal_error, warning, write_message - use string, only: to_lower - use xml_interface - use, intrinsic :: ISO_FORTRAN_ENV - - integer :: i, g - integer :: ng - integer :: n_params - integer, allocatable :: iarray(:) - integer, allocatable :: int_array(:) - logical :: file_exists ! does cmfd.xml exist? - logical :: found - character(MAX_LINE_LEN) :: filename - real(8) :: gs_tol(2) - type(XMLDocument) :: doc - type(XMLNode) :: root - type(XMLNode) :: node_mesh - - ! Read cmfd input file - filename = trim(path_input) // "cmfd.xml" - inquire(FILE=filename, EXIST=file_exists) - if (.not. file_exists) then - ! CMFD is optional unless it is in on from settings - if (cmfd_run) then - call fatal_error("No CMFD XML file, '" // trim(filename) // "' does not& - & exist!") - end if - return - else - - ! Tell user - call write_message("Reading CMFD XML file...", 5) - - end if - - ! Parse cmfd.xml file - call doc % load_file(filename) - root = doc % document_element() - - ! Get pointer to mesh XML node - node_mesh = root % child("mesh") - - ! Check if mesh is there - if (.not. node_mesh % associated()) then - call fatal_error("No CMFD mesh specified in CMFD XML file.") - end if - - ! Set spatial dimensions in cmfd object - call get_node_array(node_mesh, "dimension", cmfd % indices(1:3)) - - ! Get number of energy groups - if (check_for_node(node_mesh, "energy")) then - ng = node_word_count(node_mesh, "energy") - if(.not. allocated(cmfd%egrid)) allocate(cmfd%egrid(ng)) - call get_node_array(node_mesh, "energy", cmfd%egrid) - cmfd % indices(4) = ng - 1 ! sets energy group dimension - ! If using MG mode, check to see if these egrid points at least match - ! the MG Data breakpoints - if (.not. run_CE) then - do i = 1, ng - found = .false. - do g = 1, num_energy_groups + 1 - if (cmfd % egrid(i) == energy_bins(g)) then - found = .true. - exit - end if - end do - if (.not. found) then - call fatal_error("CMFD energy mesh boundaries must align with& - & boundaries of multi-group data!") - end if - end do - end if - else - if(.not.allocated(cmfd % egrid)) allocate(cmfd % egrid(2)) - cmfd % egrid = [ ZERO, energy_max(NEUTRON) ] - cmfd % indices(4) = 1 ! one energy group - end if - - ! Set global albedo - if (check_for_node(node_mesh, "albedo")) then - call get_node_array(node_mesh, "albedo", cmfd % albedo) - else - cmfd % albedo = [ ONE, ONE, ONE, ONE, ONE, ONE ] - end if - - ! Get acceleration map - if (check_for_node(node_mesh, "map")) then - allocate(cmfd % coremap(cmfd % indices(1), cmfd % indices(2), & - cmfd % indices(3))) - if (node_word_count(node_mesh, "map") /= & - product(cmfd % indices(1:3))) then - call fatal_error('CMFD coremap not to correct dimensions') - end if - allocate(iarray(node_word_count(node_mesh, "map"))) - call get_node_array(node_mesh, "map", iarray) - cmfd % coremap = reshape(iarray,(cmfd % indices(1:3))) - cmfd_coremap = .true. - deallocate(iarray) - end if - - ! Check for normalization constant - if (check_for_node(root, "norm")) then - call get_node_value(root, "norm", cmfd % norm) - end if - - ! Set feedback logical - if (check_for_node(root, "feedback")) then - call get_node_value(root, "feedback", cmfd_feedback) - end if - - ! Set downscatter logical - if (check_for_node(root, "downscatter")) then - call get_node_value(root, "downscatter", cmfd_downscatter) - end if - - ! Reset dhat parameters - if (check_for_node(root, "dhat_reset")) then - call get_node_value(root, "dhat_reset", dhat_reset) - end if - - ! Set monitoring - if (check_for_node(root, "power_monitor")) then - call get_node_value(root, "power_monitor", cmfd_power_monitor) - end if - - ! Output logicals - if (check_for_node(root, "write_matrices")) then - call get_node_value(root, "write_matrices", cmfd_write_matrices) - end if - - ! Run an adjoint calc - if (check_for_node(root, "run_adjoint")) then - call get_node_value(root, "run_adjoint", cmfd_run_adjoint) - end if - - ! Batch to begin cmfd - if (check_for_node(root, "begin")) & - call get_node_value(root, "begin", cmfd_begin) - - ! Check for cmfd tally resets - if (check_for_node(root, "tally_reset")) then - n_cmfd_resets = node_word_count(root, "tally_reset") - else - n_cmfd_resets = 0 - end if - if (n_cmfd_resets > 0) then - allocate(int_array(n_cmfd_resets)) - call get_node_array(root, "tally_reset", int_array) - do i = 1, n_cmfd_resets - call cmfd_reset % add(int_array(i)) - end do - deallocate(int_array) - end if - - ! Get display - if (check_for_node(root, "display")) & - call get_node_value(root, "display", cmfd_display) - - ! Read in spectral radius estimate and tolerances - if (check_for_node(root, "spectral")) & - call get_node_value(root, "spectral", cmfd_spectral) - if (check_for_node(root, "shift")) & - call get_node_value(root, "shift", cmfd_shift) - if (check_for_node(root, "ktol")) & - call get_node_value(root, "ktol", cmfd_ktol) - if (check_for_node(root, "stol")) & - call get_node_value(root, "stol", cmfd_stol) - if (check_for_node(root, "gauss_seidel_tolerance")) then - n_params = node_word_count(root, "gauss_seidel_tolerance") - if (n_params /= 2) then - call fatal_error('Gauss Seidel tolerance is not 2 parameters & - &(absolute, relative).') - end if - call get_node_array(root, "gauss_seidel_tolerance", gs_tol) - cmfd_atoli = gs_tol(1) - cmfd_rtoli = gs_tol(2) - end if - - ! Create tally objects - call create_cmfd_tally(root) - - ! Close CMFD XML file - call doc % clear() - - end subroutine read_cmfd_xml - -!=============================================================================== -! CREATE_CMFD_TALLY creates the tally object for OpenMC to process for CMFD -! accleration. -! There are 3 tally types: -! 1: Only an energy in filter-> flux,total,p1 scatter -! 2: Energy in and energy out filter-> nu-scatter,nu-fission -! 3: Mesh current -!=============================================================================== - - subroutine create_cmfd_tally(root) - - use constants, only: MAX_LINE_LEN - use error, only: fatal_error, warning - use mesh_header - use string - use tally, only: openmc_tally_allocate - use tally_header, only: openmc_extend_tallies - use tally_filter_header - use tally_filter - use xml_interface - - type(XMLNode), intent(in) :: root ! XML root element - - logical :: energy_filters - integer :: i ! loop counter - integer :: n ! size of arrays in mesh specification - integer(C_INT32_T) :: ng ! number of energy groups (default 1) - integer :: n_filter ! number of filters - integer :: i_start, i_end - integer :: i_filt_start, i_filt_end - integer(C_INT32_T), allocatable :: filter_indices(:) - integer(C_INT) :: err - integer :: i_filt ! index in filters array - integer :: filt_id - integer :: tally_id - real(C_DOUBLE), allocatable :: energies(:) - type(XMLNode) :: node_mesh - - ! Read CMFD mesh - call read_meshes(root % ptr) - - ! Get index of cmfd mesh and set ID - i_start = n_meshes() - 1 - err = openmc_mesh_set_id(i_start, i_start) - - ! Save reference to CMFD mesh - index_cmfd_mesh = i_start - cmfd_mesh = meshes(i_start) - - ! Get pointer to mesh XML node - node_mesh = root % child("mesh") - - ! Determine number of filters - energy_filters = check_for_node(node_mesh, "energy") - n = merge(5, 3, energy_filters) - - ! Extend filters array so we can add CMFD filters - err = openmc_extend_filters(n, i_filt_start, i_filt_end) - - ! Set up mesh filter - i_filt = i_filt_start - err = openmc_filter_set_type(i_filt, C_CHAR_'mesh' // C_NULL_CHAR) - call openmc_get_filter_next_id(filt_id) - err = openmc_filter_set_id(i_filt, filt_id) - err = openmc_mesh_filter_set_mesh(i_filt, i_start) - - if (energy_filters) then - ! Read and set incoming energy mesh filter - i_filt = i_filt + 1 - err = openmc_filter_set_type(i_filt, C_CHAR_'energy' // C_NULL_CHAR) - call openmc_get_filter_next_id(filt_id) - err = openmc_filter_set_id(i_filt, filt_id) - - ! Get energies and set bins - ng = node_word_count(node_mesh, "energy") - allocate(energies(ng)) - call get_node_array(node_mesh, "energy", energies) - err = openmc_energy_filter_set_bins(i_filt, ng, energies) - - ! Read and set outgoing energy mesh filter - i_filt = i_filt + 1 - err = openmc_filter_set_type(i_filt, C_CHAR_'energyout' // C_NULL_CHAR) - call openmc_get_filter_next_id(filt_id) - err = openmc_filter_set_id(i_filt, filt_id) - err = openmc_energy_filter_set_bins(i_filt, ng, energies) - end if - - ! Duplicate the mesh filter for the mesh current tally since other - ! tallies use this filter and we need to change the dimension - i_filt = i_filt + 1 - err = openmc_filter_set_type(i_filt, C_CHAR_'meshsurface' // C_NULL_CHAR) - call openmc_get_filter_next_id(filt_id) - err = openmc_filter_set_id(i_filt, filt_id) - err = openmc_meshsurface_filter_set_mesh(i_filt, i_start) - - ! Add in legendre filter for the P1 tally - i_filt = i_filt + 1 - err = openmc_filter_set_type(i_filt, C_CHAR_'legendre' // C_NULL_CHAR) - call openmc_get_filter_next_id(filt_id) - err = openmc_filter_set_id(i_filt, filt_id) - err = openmc_legendre_filter_set_order(i_filt, 1) - - ! Initialize filters - do i = i_filt_start, i_filt_end - call filters(i) % obj % initialize() - end do - - ! Allocate tallies - err = openmc_extend_tallies(4, i_start, i_end) - cmfd_tallies => tallies(i_start:i_end) - - ! Begin loop around tallies - do i = 1, size(cmfd_tallies) - ! Allocate tally - err = openmc_tally_allocate(i_start + i - 1, C_CHAR_'generic' // C_NULL_CHAR) - call openmc_get_tally_next_id(tally_id) - err = openmc_tally_set_id(i_start + i - 1, tally_id) - - ! Point t to tally variable - associate (t => cmfd_tallies(i) % obj) - - ! Set the incoming energy mesh filter index in the tally find_filter - ! array - n_filter = 1 - if (energy_filters) then - n_filter = n_filter + 1 - end if - - ! Set number of nucilde bins - allocate(t % nuclide_bins(1)) - t % nuclide_bins(1) = -1 - t % n_nuclide_bins = 1 - - ! Record tally id which is equivalent to loop number - t % id = i_start + i - 1 - - if (i == 1) then - - ! Set name - t % name = "CMFD flux, total" - - ! Set tally estimator to analog - err = openmc_tally_set_estimator(i_start + i - 1, C_CHAR_'analog' // C_NULL_CHAR) - - ! Set tally type to volume - err = openmc_tally_set_type(i_start + i - 1, C_CHAR_'volume' // C_NULL_CHAR) - - ! Allocate and set filters - allocate(filter_indices(n_filter)) - filter_indices(1) = i_filt_start - if (energy_filters) then - filter_indices(2) = i_filt_start + 1 - end if - err = openmc_tally_set_filters(i_start + i - 1, n_filter, filter_indices) - deallocate(filter_indices) - - ! Allocate scoring bins - allocate(t % score_bins(2)) - t % n_score_bins = 2 - - ! Set macro_bins - t % score_bins(1) = SCORE_FLUX - t % score_bins(2) = SCORE_TOTAL - - else if (i == 2) then - - ! Set name - t % name = "CMFD neutron production" - - ! Set tally estimator to analog - err = openmc_tally_set_estimator(i_start + i - 1, C_CHAR_'analog' // C_NULL_CHAR) - - ! Set tally type to volume - err = openmc_tally_set_type(i_start + i - 1, C_CHAR_'volume' // C_NULL_CHAR) - - ! Set the incoming energy mesh filter index in the tally find_filter - ! array - if (energy_filters) then - n_filter = n_filter + 1 - end if - - ! Allocate and set indices in filters array - allocate(filter_indices(n_filter)) - filter_indices(1) = i_filt_start - if (energy_filters) then - filter_indices(2) = i_filt_start + 1 - filter_indices(3) = i_filt_start + 2 - end if - err = openmc_tally_set_filters(i_start + i - 1, n_filter, filter_indices) - deallocate(filter_indices) - - ! Allocate macro reactions - allocate(t % score_bins(2)) - t % n_score_bins = 2 - - ! Set macro_bins - t % score_bins(1) = SCORE_NU_SCATTER - t % score_bins(2) = SCORE_NU_FISSION - - else if (i == 3) then - - ! Set name - t % name = "CMFD surface currents" - - ! Set tally estimator to analog - err = openmc_tally_set_estimator(i_start + i - 1, C_CHAR_'analog' // C_NULL_CHAR) - - ! Allocate and set filters - allocate(filter_indices(n_filter)) - filter_indices(1) = i_filt_end - 1 - if (energy_filters) then - filter_indices(2) = i_filt_start + 1 - end if - err = openmc_tally_set_filters(i_start + i - 1, n_filter, filter_indices) - deallocate(filter_indices) - - ! Allocate macro reactions - allocate(t % score_bins(1)) - t % n_score_bins = 1 - - ! Set macro bins - t % score_bins(1) = SCORE_CURRENT - err = openmc_tally_set_type(i_start + i - 1, C_CHAR_'mesh-surface' // C_NULL_CHAR) - - else if (i == 4) then - ! Set name - t % name = "CMFD P1 scatter" - - ! Set tally estimator to analog - err = openmc_tally_set_estimator(i_start + i - 1, C_CHAR_'analog' // C_NULL_CHAR) - - ! Set tally type to volume - err = openmc_tally_set_type(i_start + i - 1, C_CHAR_'volume' // C_NULL_CHAR) - - ! Allocate and set filters - n_filter = 2 - if (energy_filters) then - n_filter = n_filter + 1 - end if - allocate(filter_indices(n_filter)) - filter_indices(1) = i_filt_start - filter_indices(2) = i_filt_end - if (energy_filters) then - filter_indices(3) = i_filt_start + 1 - end if - err = openmc_tally_set_filters(i_start + i - 1, n_filter, filter_indices) - deallocate(filter_indices) - - ! Allocate scoring bins - allocate(t % score_bins(1)) - t % n_score_bins = 1 - - ! Set macro_bins - t % score_bins(1) = SCORE_SCATTER - end if - - ! Make CMFD tallies active from the start - t % active = .true. - - end associate - end do - - end subroutine create_cmfd_tally - -end module cmfd_input diff --git a/src/cmfd_loss_operator.F90 b/src/cmfd_loss_operator.F90 deleted file mode 100644 index 50a75e52d5..0000000000 --- a/src/cmfd_loss_operator.F90 +++ /dev/null @@ -1,435 +0,0 @@ -module cmfd_loss_operator - - use constants, only: CMFD_NOACCEL, ZERO - use cmfd_header, only: cmfd, cmfd_coremap - use matrix_header, only: Matrix - - implicit none - private - public :: init_loss_matrix, build_loss_matrix - -contains - -!=============================================================================== -! INIT_LOSS_MATRIX preallocates loss matrix and initializes it -!=============================================================================== - - subroutine init_loss_matrix(loss_matrix) - - type(Matrix), intent(inout) :: loss_matrix ! cmfd loss matrix - - integer :: nx ! maximum number of x cells - integer :: ny ! maximum number of y cells - integer :: nz ! maximum number of z cells - integer :: ng ! maximum number of groups - integer :: n ! total length of matrix - integer :: nnz ! number of nonzeros in matrix - integer :: n_i ! number of interior cells - integer :: n_c ! number of corner cells - integer :: n_s ! number of side cells - integer :: n_e ! number of edge cells - integer :: nz_c ! number of non-zero corner cells - integer :: nz_e ! number of non-zero edge cells - integer :: nz_s ! number of non-zero side cells - integer :: nz_i ! number of non-zero interior cells - - ! Get maximum number of cells in each direction - nx = cmfd%indices(1) - ny = cmfd%indices(2) - nz = cmfd%indices(3) - ng = cmfd%indices(4) - - ! Calculate dimensions of matrix - if (cmfd_coremap) then - n = cmfd % mat_dim * ng - else - n = nx*ny*nz*ng - end if - - ! Calculate number of nonzeros, if core map -> need to determine manually - if (cmfd_coremap) then - nnz = preallocate_loss_matrix(nx, ny, nz, ng, n) - else ! structured Cartesian grid - n_c = 8 ! define # of corners - n_e = 4*(nx - 2) + 4*(ny - 2) + 4*(nz - 2) ! define # of edges - n_s = 2*(nx - 2)*(ny - 2) + 2*(nx - 2)*(nz - 2) & - + 2*(ny - 2)*(nz - 2) ! define # of sides - n_i = nx*ny*nz - (n_c + n_e + n_s) ! define # of interiors - nz_c = ng*n_c*(4 + ng - 1) ! define # nonzero corners - nz_e = ng*n_e*(5 + ng - 1) ! define # nonzero edges - nz_s = ng*n_s*(6 + ng - 1) ! define # nonzero sides - nz_i = ng*n_i*(7 + ng - 1) ! define # nonzero interiors - nnz = nz_c + nz_e + nz_s + nz_i - end if - - ! Configure loss matrix - call loss_matrix % create(n, nnz) - - end subroutine init_loss_matrix - -!=============================================================================== -! PREALLOCATE_LOSS_MATRIX manually preallocates the loss matrix -!=============================================================================== - - function preallocate_loss_matrix(nx, ny, nz, ng, n) result(nnz) - - integer, intent(in) :: nx ! maximum number of x cells - integer, intent(in) :: ny ! maximum number of y cells - integer, intent(in) :: nz ! maximum number of z cells - integer, intent(in) :: ng ! maximum number of groups - integer, intent(in) :: n ! total length of matrix - integer :: nnz ! number of nonzeros - - integer :: i ! iteration counter for x - integer :: j ! iteration counter for y - integer :: k ! iteration counter for z - integer :: g ! iteration counter for groups - integer :: l ! iteration counter for leakages - integer :: h ! energy group when doing scattering - integer :: irow ! row counter - integer :: bound(6) ! vector for comparing when looking for bound - integer :: xyz_idx ! index for determining if x,y or z leakage - integer :: dir_idx ! index for determining - or + face of cell - integer :: neig_idx(3) ! spatial indices of neighbour - integer :: nxyz(3,2) ! single vector containing bound. locations - integer :: shift_idx ! parameter to shift index by +1 or -1 - integer :: neig_mat_idx ! matrix index of neighbor cell - integer :: scatt_mat_idx ! matrix index for h-->g scattering terms - - ! Reset number of nonzeros to 0 - nnz = 0 - - ! Create single vector of these indices for boundary calculation - nxyz(1,:) = (/1,nx/) - nxyz(2,:) = (/1,ny/) - nxyz(3,:) = (/1,nz/) - - ! Begin loop around local rows - ROWS: do irow = 1, n - - ! Set a nonzero for diagonal - nnz = nnz + 1 - - ! Get location indices - call matrix_to_indices(irow, g, i, j, k, ng, nx, ny, nz) - - ! Create boundary vector - bound = (/i,i,j,j,k,k/) - - ! Begin loop over leakages - LEAK: do l = 1,6 - - ! Define (x,y,z) and (-,+) indices - xyz_idx = int(ceiling(real(l)/real(2))) ! x=1, y=2, z=3 - dir_idx = 2 - mod(l,2) ! -=1, +=2 - - ! Calculate spatial indices of neighbor - neig_idx = (/i,j,k/) ! begin with i,j,k - shift_idx = -2*mod(l,2) +1 ! shift neig by -1 or +1 - neig_idx(xyz_idx) = shift_idx + neig_idx(xyz_idx) - - ! Check for global boundary - if (bound(l) /= nxyz(xyz_idx,dir_idx)) then - - ! Check for coremap - if (cmfd_coremap) then - - ! Check for neighbor that is non-acceleartred - if (cmfd % coremap(neig_idx(1),neig_idx(2),neig_idx(3)) /= & - CMFD_NOACCEL) then - - ! Get neighbor matrix index - call indices_to_matrix(g,neig_idx(1), neig_idx(2), & - neig_idx(3), neig_mat_idx, ng, nx, ny) - - ! Record nonzero - nnz = nnz + 1 - - end if - - else - - ! Get neighbor matrix index - call indices_to_matrix(g, neig_idx(1), neig_idx(2), neig_idx(3), & - neig_mat_idx, ng, nx, ny) - - ! Record nonzero - nnz = nnz + 1 - - end if - - end if - - end do LEAK - - ! Begin loop over off diagonal in-scattering - SCATTR: do h = 1, ng - - ! Cycle though if h=g, it was already banked in removal xs - if (h == g) cycle - - ! Get neighbor matrix index - call indices_to_matrix(h, i, j, k, scatt_mat_idx, ng, nx, ny) - - ! Record nonzero - nnz = nnz + 1 - - end do SCATTR - - end do ROWS - - end function preallocate_loss_matrix - -!=============================================================================== -! BUILD_LOSS_MATRIX creates the matrix representing loss of neutrons -!=============================================================================== - - subroutine build_loss_matrix(loss_matrix, adjoint) - - type(Matrix), intent(inout) :: loss_matrix ! cmfd loss matrix - logical, intent(in), optional :: adjoint ! set up the adjoint - - integer :: nxyz(3,2) ! single vector containing bound. locations - integer :: i ! iteration counter for x - integer :: j ! iteration counter for y - integer :: k ! iteration counter for z - integer :: g ! iteration counter for groups - integer :: l ! iteration counter for leakages - integer :: h ! energy group when doing scattering - integer :: nx ! maximum number of x cells - integer :: ny ! maximum number of y cells - integer :: nz ! maximum number of z cells - integer :: ng ! maximum number of groups - integer :: neig_mat_idx ! matrix index of neighbor cell - integer :: scatt_mat_idx ! matrix index for h-->g scattering terms - integer :: bound(6) ! vector for comparing when looking for bound - integer :: xyz_idx ! index for determining if x,y or z leakage - integer :: dir_idx ! index for determining - or + face of cell - integer :: neig_idx(3) ! spatial indices of neighbour - integer :: shift_idx ! parameter to shift index by +1 or -1 - integer :: irow ! iteration counter over row - logical :: adjoint_calc ! is this a physical adjoint calculation? - real(8) :: totxs ! total macro cross section - real(8) :: scattxsgg ! scattering macro cross section g-->g - real(8) :: scattxshg ! scattering macro cross section h-->g - real(8) :: dtilde(6) ! finite difference coupling parameter - real(8) :: dhat(6) ! nonlinear coupling parameter - real(8) :: hxyz(3) ! cell lengths in each direction - real(8) :: jn ! direction dependent leakage coeff to neig - real(8) :: jo(6) ! leakage coeff in front of cell flux - real(8) :: jnet ! net leakage from jo - real(8) :: val ! temporary variable before saving to - - ! Check for adjoint - adjoint_calc = .false. - if (present(adjoint)) adjoint_calc = adjoint - - ! Get maximum number of cells in each direction - nx = cmfd%indices(1) - ny = cmfd%indices(2) - nz = cmfd%indices(3) - ng = cmfd%indices(4) - - ! Create single vector of these indices for boundary calculation - nxyz(1,:) = (/1,nx/) - nxyz(2,:) = (/1,ny/) - nxyz(3,:) = (/1,nz/) - - ! Begin iteration loops - ROWS: do irow = 1, loss_matrix % n - - ! Set up a new row in matrix - call loss_matrix % new_row() - - ! Get indices for that row - call matrix_to_indices(irow, g, i, j, k, ng, nx, ny, nz) - - ! Retrieve cell data - totxs = cmfd%totalxs(g,i,j,k) - scattxsgg = cmfd%scattxs(g,g,i,j,k) - dtilde = cmfd%dtilde(:,g,i,j,k) - hxyz = cmfd%hxyz(:,i,j,k) - - ! Check and get dhat - if (allocated(cmfd%dhat)) then - dhat = cmfd%dhat(:,g,i,j,k) - else - dhat = ZERO - end if - - ! Create boundary vector - bound = (/i,i,j,j,k,k/) - - ! Begin loop over leakages - ! 1=-x, 2=+x, 3=-y, 4=+y, 5=-z, 6=+z - LEAK: do l = 1,6 - - ! Define (x,y,z) and (-,+) indices - xyz_idx = int(ceiling(real(l)/real(2))) ! x=1, y=2, z=3 - dir_idx = 2 - mod(l,2) ! -=1, +=2 - - ! Calculate spatial indices of neighbor - neig_idx = (/i,j,k/) ! begin with i,j,k - shift_idx = -2*mod(l,2) +1 ! shift neig by -1 or +1 - neig_idx(xyz_idx) = shift_idx + neig_idx(xyz_idx) - - ! Check for global boundary - if (bound(l) /= nxyz(xyz_idx,dir_idx)) then - - ! Check for core map - if (cmfd_coremap) then - - ! Check that neighbor is not reflector - if (cmfd % coremap(neig_idx(1),neig_idx(2),neig_idx(3)) /= & - CMFD_NOACCEL) then - - ! Compute leakage coefficient for neighbor - jn = -dtilde(l) + shift_idx*dhat(l) - - ! Get neighbor matrix index - call indices_to_matrix(g, neig_idx(1), neig_idx(2), neig_idx(3), & - neig_mat_idx, ng, nx, ny) - - ! Compute value and record to bank - val = jn/hxyz(xyz_idx) - - ! Record value in matrix - call loss_matrix % add_value(neig_mat_idx, val) - - end if - - else - - ! Compute leakage coefficient for neighbor - jn = -dtilde(l) + shift_idx*dhat(l) - - ! Get neighbor matrix index - call indices_to_matrix(g, neig_idx(1), neig_idx(2), neig_idx(3), & - neig_mat_idx, ng, nx, ny) - - ! Compute value and record to bank - val = jn/hxyz(xyz_idx) - - ! Record value in matrix - call loss_matrix % add_value(neig_mat_idx, val) - - end if - - end if - - ! Compute leakage coefficient for target - jo(l) = shift_idx*dtilde(l) + dhat(l) - - end do LEAK - - ! Calculate net leakage coefficient for target - jnet = (jo(2) - jo(1))/hxyz(1) + (jo(4) - jo(3))/hxyz(2) + & - (jo(6) - jo(5))/hxyz(3) - - ! Calculate loss of neutrons - val = jnet + totxs - scattxsgg - - ! Record diagonal term - call loss_matrix % add_value(irow, val) - - ! Begin loop over off diagonal in-scattering - SCATTR: do h = 1, ng - - ! Cycle though if h=g, value already banked in removal xs - if (h == g) cycle - - ! Get neighbor matrix index - call indices_to_matrix(h, i, j, k, scatt_mat_idx, ng, nx, ny) - - ! Check for adjoint - if (adjoint_calc) then - ! Get scattering macro xs, transposed! - scattxshg = cmfd%scattxs(g, h, i, j, k) - else - ! Get scattering macro xs - scattxshg = cmfd%scattxs(h, g, i, j, k) - end if - - ! Negate the scattering xs - val = -scattxshg - - ! Record value in matrix - call loss_matrix % add_value(scatt_mat_idx, val) - - end do SCATTR - - end do ROWS - - ! CSR requires n+1 row - call loss_matrix % new_row() - - end subroutine build_loss_matrix - -!=============================================================================== -! INDICES_TO_MATRIX takes (x,y,z,g) indices and computes location in matrix -!=============================================================================== - - subroutine indices_to_matrix(g, i, j, k, matidx, ng, nx, ny) - - integer, intent(out) :: matidx ! the index location in matrix - integer, intent(in) :: i ! current x index - integer, intent(in) :: j ! current y index - integer, intent(in) :: k ! current z index - integer, intent(in) :: g ! current group index - integer, intent(in) :: nx ! maximum number of x cells - integer, intent(in) :: ny ! maximum number of y cells - integer, intent(in) :: ng ! maximum number of groups - - ! Check if coremap is used - if (cmfd_coremap) then - - ! Get idx from core map - matidx = ng*(cmfd % coremap(i,j,k)) - (ng - g) - - else - - ! Compute index - matidx = g + ng*(i - 1) + ng*nx*(j - 1) + ng*nx*ny*(k - 1) - - end if - - end subroutine indices_to_matrix - -!=============================================================================== -! MATRIX_TO_INDICES converts a matrix index to spatial and group indices -!=============================================================================== - - subroutine matrix_to_indices(irow, g, i, j, k, ng, nx, ny, nz) - - integer, intent(out) :: i ! iteration counter for x - integer, intent(out) :: j ! iteration counter for y - integer, intent(out) :: k ! iteration counter for z - integer, intent(out) :: g ! iteration counter for groups - integer, intent(in) :: irow ! iteration counter over row (0 reference) - integer, intent(in) :: nx ! maximum number of x cells - integer, intent(in) :: ny ! maximum number of y cells - integer, intent(in) :: nz ! maximum number of z cells - integer, intent(in) :: ng ! maximum number of groups - - ! Check for core map - if (cmfd_coremap) then - - ! Get indices from indexmap - g = mod(irow-1, ng) + 1 - i = cmfd % indexmap((irow-1)/ng+1,1) - j = cmfd % indexmap((irow-1)/ng+1,2) - k = cmfd % indexmap((irow-1)/ng+1,3) - - else - - ! Compute indices - g = mod(irow-1, ng) + 1 - i = mod(irow-1, ng*nx)/ng + 1 - j = mod(irow-1, ng*nx*ny)/(ng*nx)+ 1 - k = mod(irow-1, ng*nx*ny*nz)/(ng*nx*ny) + 1 - - end if - - end subroutine matrix_to_indices - -end module cmfd_loss_operator diff --git a/src/cmfd_prod_operator.F90 b/src/cmfd_prod_operator.F90 deleted file mode 100644 index 91ce43b359..0000000000 --- a/src/cmfd_prod_operator.F90 +++ /dev/null @@ -1,199 +0,0 @@ -module cmfd_prod_operator - - use constants, only: CMFD_NOACCEL - use cmfd_header, only: cmfd, cmfd_coremap - use matrix_header, only: Matrix - - implicit none - private - public :: init_prod_matrix, build_prod_matrix - -contains - -!============================================================================== -! INIT_PROD_MATRIX preallocates prod matrix and initializes it -!============================================================================== - - subroutine init_prod_matrix(prod_matrix) - - type(Matrix), intent(inout) :: prod_matrix ! production matrix - - integer :: nx ! maximum number of x cells - integer :: ny ! maximum number of y cells - integer :: nz ! maximum number of z cells - integer :: ng ! maximum number of groups - integer :: n ! total length of matrix - integer :: nnz ! number of nonzeros in matrix - - ! Get maximum number of cells in each direction - nx = cmfd%indices(1) - ny = cmfd%indices(2) - nz = cmfd%indices(3) - ng = cmfd%indices(4) - - ! Calculate dimensions and number of nonzeros in matrix - if (cmfd_coremap) then - n = cmfd % mat_dim * ng - else - n = nx*ny*nz*ng - end if - nnz = n * ng - - ! Configure prod matrix - call prod_matrix % create(n, nnz) - - end subroutine init_prod_matrix - -!=============================================================================== -! BUILD_PROD_MATRIX creates the matrix representing production of neutrons -!=============================================================================== - - subroutine build_prod_matrix(prod_matrix, adjoint) - - type(Matrix), intent(inout) :: prod_matrix ! production matrix - logical, intent(in), optional :: adjoint ! adjoint calculation logical - - integer :: i ! iteration counter for x - integer :: j ! iteration counter for y - integer :: k ! iteration counter for z - integer :: g ! iteration counter for groups - integer :: h ! energy group when doing scattering - integer :: nx ! maximum number of x cells - integer :: ny ! maximum number of y cells - integer :: nz ! maximum number of z cells - integer :: ng ! maximum number of groups - integer :: hmat_idx ! index in matrix for energy group h - integer :: irow ! iteration counter over row - logical :: adjoint_calc ! is this a physical adjoint? - real(8) :: nfissxs ! nufission cross section h-->g - real(8) :: val ! temporary variable for nfissxs - - ! get maximum number of cells in each direction - nx = cmfd%indices(1) - ny = cmfd%indices(2) - nz = cmfd%indices(3) - ng = cmfd%indices(4) - - ! check for adjoint - adjoint_calc = .false. - if (present(adjoint)) adjoint_calc = adjoint - - ! begin iteration loops - ROWS: do irow = 1, prod_matrix % n - - ! add a new row to matrix - call prod_matrix % new_row() - - ! get indices for that row - call matrix_to_indices(irow, g, i, j, k, ng, nx, ny, nz) - - ! check if not including reflector - if (cmfd_coremap) then - - ! check if at a reflector - if (cmfd % coremap(i,j,k) == CMFD_NOACCEL) then - cycle - end if - - end if - - ! loop around all other groups - - NFISS: do h = 1, ng - - ! get matrix column location - call indices_to_matrix(h, i, j, k, hmat_idx, ng, nx, ny) - - ! check for adjoint and bank val - if (adjoint_calc) then - ! get nu-fission cross section from cell - nfissxs = cmfd%nfissxs(g,h,i,j,k) - else - ! get nu-fission cross section from cell - nfissxs = cmfd%nfissxs(h,g,i,j,k) - end if - - ! set as value to be recorded - val = nfissxs - - ! record value in matrix - - call prod_matrix % add_value(hmat_idx, val) - - end do NFISS - - end do ROWS - - ! CSR requires n+1 row - call prod_matrix % new_row() - - end subroutine build_prod_matrix - -!=============================================================================== -! INDICES_TO_MATRIX takes (x,y,z,g) indices and computes location in matrix -!=============================================================================== - - subroutine indices_to_matrix(g, i, j, k, matidx, ng, nx, ny) - - integer, intent(out) :: matidx ! the index location in matrix - integer, intent(in) :: i ! current x index - integer, intent(in) :: j ! current y index - integer, intent(in) :: k ! current z index - integer, intent(in) :: g ! current group index - integer, intent(in) :: nx ! maximum number of x cells - integer, intent(in) :: ny ! maximum number of y cells - integer, intent(in) :: ng ! maximum number of groups - - ! check if coremap is used - if (cmfd_coremap) then - - ! get idx from core map - matidx = ng*(cmfd % coremap(i,j,k)) - (ng - g) - - else - - ! compute index - matidx = g + ng*(i - 1) + ng*nx*(j - 1) + ng*nx*ny*(k - 1) - - end if - - end subroutine indices_to_matrix - -!=============================================================================== -! MATRIX_TO_INDICES converts matrix index to spatial and group indices -!=============================================================================== - - subroutine matrix_to_indices(irow, g, i, j, k, ng, nx, ny, nz) - - integer, intent(out) :: i ! iteration counter for x - integer, intent(out) :: j ! iteration counter for y - integer, intent(out) :: k ! iteration counter for z - integer, intent(out) :: g ! iteration counter for groups - integer, intent(in) :: irow ! iteration counter over row (0 reference) - integer, intent(in) :: nx ! maximum number of x cells - integer, intent(in) :: ny ! maximum number of y cells - integer, intent(in) :: nz ! maximum number of z cells - integer, intent(in) :: ng ! maximum number of groups - - ! check for core map - if (cmfd_coremap) then - - ! get indices from indexmap - g = mod(irow-1, ng) + 1 - i = cmfd % indexmap((irow-1)/ng+1,1) - j = cmfd % indexmap((irow-1)/ng+1,2) - k = cmfd % indexmap((irow-1)/ng+1,3) - - else - - ! compute indices - g = mod(irow-1, ng) + 1 - i = mod(irow-1, ng*nx)/ng + 1 - j = mod(irow-1, ng*nx*ny)/(ng*nx)+ 1 - k = mod(irow-1, ng*nx*ny*nz)/(ng*nx*ny) + 1 - - end if - - end subroutine matrix_to_indices - -end module cmfd_prod_operator diff --git a/src/cmfd_solver.F90 b/src/cmfd_solver.F90 deleted file mode 100644 index 01404d9b0e..0000000000 --- a/src/cmfd_solver.F90 +++ /dev/null @@ -1,809 +0,0 @@ -module cmfd_solver - -! This module contains routines to execute the power iteration solver - - use constants, only: MAX_LINE_LEN - use cmfd_loss_operator, only: init_loss_matrix, build_loss_matrix - use cmfd_prod_operator, only: init_prod_matrix, build_prod_matrix - use matrix_header, only: Matrix - use vector_header, only: Vector - - implicit none - private - public :: cmfd_solver_execute - - real(8) :: k_n ! New k-eigenvalue - real(8) :: k_o ! Old k-eigenvalue - real(8) :: k_s ! Shift of eigenvalue - real(8) :: k_ln ! New shifted eigenvalue - real(8) :: k_lo ! Old shifted eigenvalue - real(8) :: norm_n ! Current norm of source vector - real(8) :: norm_o ! Old norm of source vector - real(8) :: kerr ! Error in keff - real(8) :: serr ! Error in source - real(8) :: ktol ! Tolerance on keff - real(8) :: stol ! Tolerance on source - logical :: adjoint_calc ! Run an adjoint calculation - type(Matrix) :: loss ! Cmfd loss matrix - type(Matrix) :: prod ! Cmfd prod matrix - type(Vector) :: phi_n ! New flux vector - type(Vector) :: phi_o ! Old flux vector - type(Vector) :: s_n ! New source vector - type(Vector) :: s_o ! Old flux vector - type(Vector) :: serr_v ! Error in source - - ! CMFD linear solver interface - abstract interface - subroutine linsolve(A, b, x, tol, i) - import :: Matrix - import :: Vector - type(Matrix), intent(inout) :: A - type(Vector), intent(inout) :: b - type(Vector), intent(inout) :: x - real(8), intent(in) :: tol - integer, intent(out) :: i - end subroutine linsolve - end interface - -contains - -!=============================================================================== -! CMFD_SOLVER_EXECUTE sets up and runs power iteration solver for CMFD -!=============================================================================== - - subroutine cmfd_solver_execute(adjoint) - - use cmfd_header, only: cmfd_adjoint_type, time_cmfdbuild, time_cmfdsolve - - logical, optional, intent(in) :: adjoint ! adjoint calc - - logical :: physical_adjoint = .false. - - ! Check for adjoint execution - adjoint_calc = .false. - if (present(adjoint)) adjoint_calc = adjoint - - ! Check for physical adjoint - if (adjoint_calc .and. trim(cmfd_adjoint_type) == 'physical') & - physical_adjoint = .true. - - ! Start timer for build - call time_cmfdbuild % start() - - ! Initialize matrices and vectors - call init_data(physical_adjoint) - - ! Check for mathematical adjoint calculation - if (adjoint_calc .and. trim(cmfd_adjoint_type) == 'math') & - call compute_adjoint() - - ! Stop timer for build - call time_cmfdbuild % stop() - - ! Begin power iteration - call time_cmfdsolve % start() - call execute_power_iter() - call time_cmfdsolve % stop() - - ! Extract results - call extract_results() - - ! Deallocate data - call finalize() - - end subroutine cmfd_solver_execute - -!=============================================================================== -! INIT_DATA allocates matrices and vectors for CMFD solution -!=============================================================================== - - subroutine init_data(adjoint) - - use constants, only: ONE, ZERO - use cmfd_header, only: cmfd_shift, cmfd_ktol, cmfd_stol, cmfd_write_matrices - use simulation_header, only: keff - - logical, intent(in) :: adjoint - - integer :: n ! problem size - real(8) :: guess ! initial guess - real(8) :: dw ! eigenvalue shift - - ! Set up matrices - call init_loss_matrix(loss) - call init_prod_matrix(prod) - - ! Get problem size - n = loss % n - - ! Set up flux vectors - call phi_n % create(n) - call phi_o % create(n) - - ! Set up source vectors - call s_n % create(n) - call s_o % create(n) - call serr_v % create(n) - - ! Set initial guess - guess = ONE - phi_n % val = guess - phi_o % val = guess - k_n = keff - k_o = k_n - dw = cmfd_shift - k_s = k_o + dw - k_ln = ONE/(ONE/k_n - ONE/k_s) - k_lo = k_ln - - ! Fill in loss matrix - call build_loss_matrix(loss, adjoint=adjoint) - - ! Fill in production matrix - call build_prod_matrix(prod, adjoint=adjoint) - - ! Finalize setup of CSR matrices - call loss % assemble() - call prod % assemble() - if (cmfd_write_matrices) then - if (adjoint) then - call loss % write('adj_loss.dat') - call prod % write('adj_prod.dat') - else - call loss % write('loss.dat') - call prod % write('prod.dat') - end if - end if - - ! Set norms to 0 - norm_n = ZERO - norm_o = ZERO - - ! Set tolerances - ktol = cmfd_ktol - stol = cmfd_stol - - end subroutine init_data - -!=============================================================================== -! COMPUTE_ADJOINT computes a mathematical adjoint of CMFD problem -!=============================================================================== - - subroutine compute_adjoint() - - use error, only: fatal_error - use cmfd_header, only: cmfd_write_matrices - - ! Transpose matrices - loss = loss % transpose() - prod = prod % transpose() - - ! Write out matrix in binary file (debugging) - if (cmfd_write_matrices) then - call loss % write('adj_loss.dat') - call prod % write('adj_prod.dat') - end if - - end subroutine compute_adjoint - -!=============================================================================== -! EXECUTE_POWER_ITER is the main power iteration routine -! for the cmfd calculation -!=============================================================================== - - subroutine execute_power_iter() - - use constants, only: ONE - use error, only: fatal_error - use cmfd_header, only: cmfd, cmfd_atoli, cmfd_rtoli - - integer :: i ! iteration counter - integer :: innerits ! # of inner iterations - integer :: totalits ! total number of inners - logical :: iconv ! did the problem converged - real(8) :: atoli ! absolute minimum tolerance - real(8) :: rtoli ! relative tolerance based on source conv - real(8) :: toli ! the current tolerance of inners - - ! Reset convergence flag - iconv = .false. - - ! Set up tolerances - atoli = cmfd_atoli - rtoli = cmfd_rtoli - toli = rtoli*100._8 - - ! Perform shift - call wielandt_shift() - totalits = 0 - - ! Begin power iteration - do i = 1, 10000 - - ! Check if reached iteration 10000 - if (i == 10000) then - call fatal_error('Reached maximum iterations in CMFD power iteration & - &solver.') - end if - - ! Compute source vector - call prod % vector_multiply(phi_o, s_o) - - ! Normalize source vector - s_o % val = s_o % val / k_lo - - ! Compute new flux vector - select case(cmfd % indices(4)) - case(1) - call cmfd_linsolver_1g(loss, s_o, phi_n, toli, innerits) - case(2) - call cmfd_linsolver_2g(loss, s_o, phi_n, toli, innerits) - case default - call cmfd_linsolver_ng(loss, s_o, phi_n, toli, innerits) - end select - - ! Compute new source vector - call prod % vector_multiply(phi_n, s_n) - - ! Compute new shifted eigenvalue - k_ln = sum(s_n % val) / sum(s_o % val) - - ! Compute new eigenvalue - k_n = ONE/(ONE/k_ln + ONE/k_s) - - ! Renormalize the old source - s_o % val = s_o % val * k_lo - - ! Check convergence - call convergence(i, innerits, iconv) - totalits = totalits + innerits - - ! Break loop if converged - if (iconv) exit - - ! Record old values - phi_o % val = phi_n % val - k_o = k_n - k_lo = k_ln - norm_o = norm_n - - ! Get new tolerance for inners - toli = max(atoli, rtoli*serr) - - end do - - end subroutine execute_power_iter - -!=============================================================================== -! WIELANDT SHIFT -!=============================================================================== - - subroutine wielandt_shift() - - use constants, only: ONE - - integer :: irow ! row counter - integer :: icol ! col counter - integer :: jcol ! current col index in prod matrix - - ! perform subtraction - jcol = 1 - ROWS: do irow = 1, loss % n - COLS: do icol = loss % get_row(irow), loss % get_row(irow + 1) - 1 - if (loss % get_col(icol) == prod % get_col(jcol) .and. & - jcol < prod % get_row(irow + 1)) then - loss % val(icol) = loss % val(icol) - ONE/k_s*prod % val(jcol) - jcol = jcol + 1 - end if - end do COLS - end do ROWS - - end subroutine wielandt_shift - -!=============================================================================== -! CONVERGENCE checks the convergence of the CMFD problem -!=============================================================================== - - subroutine convergence(iter, innerits, iconv) - - use, intrinsic :: ISO_FORTRAN_ENV - - use constants, only: ONE, ZERO - use cmfd_header, only: cmfd_power_monitor - use message_passing, only: master - - integer, intent(in) :: iter ! outer iteration number - integer, intent(in) :: innerits ! inner iteration nubmer - logical, intent(out) :: iconv ! convergence logical - - ! Reset convergence flag - iconv = .false. - - ! Calculate error in keff - kerr = abs(k_o - k_n)/k_n - - ! Calculate max error in source - where (s_n % val > ZERO) - serr_v % val = ((s_n % val - s_o % val)/s_n % val)**2 - end where - serr = sqrt(ONE/dble(s_n % n) * sum(serr_v % val)) - - ! Check for convergence - if(kerr < ktol .and. serr < stol) iconv = .true. - - ! Save the L2 norm of the source - norm_n = serr - - ! Print out to user - if (cmfd_power_monitor .and. master) then - write(OUTPUT_UNIT,FMT='(I0,":",T10,"k-eff: ",F0.8,T30,"k-error: ", & - &1PE12.5,T55, "src-error: ",1PE12.5,T80,I0)') iter, k_n, kerr, & - serr, innerits - end if - - end subroutine convergence - -!=============================================================================== -! CMFD_LINSOLVER_1g solves the CMFD linear system -!=============================================================================== - - subroutine cmfd_linsolver_1g(A, b, x, tol, its) - - use constants, only: ONE, ZERO - use error, only: fatal_error - use cmfd_header, only: cmfd, cmfd_spectral - - type(Matrix), intent(inout) :: A ! coefficient matrix - type(Vector), intent(inout) :: b ! right hand side vector - type(Vector), intent(inout) :: x ! unknown vector - real(8), intent(in) :: tol ! tolerance on final error - integer, intent(out) :: its ! number of inner iterations - - integer :: g ! group index - integer :: i ! loop counter for x - integer :: j ! loop counter for y - integer :: k ! loop counter for z - integer :: n ! total size of vector - integer :: nx ! maximum dimension in x direction - integer :: ny ! maximum dimension in y direction - integer :: nz ! maximum dimension in z direction - integer :: ng ! number of energy groups - integer :: igs ! Gauss-Seidel iteration counter - integer :: irb ! Red/Black iteration switch - integer :: irow ! row iteration - integer :: icol ! iteration counter over columns - integer :: didx ! index for diagonal component - logical :: found ! did we find col - real(8) :: tmp1 ! temporary sum g1 - real(8) :: x1 ! new g1 value of x - real(8) :: err ! error in convergence of solution - real(8) :: w ! overrelaxation parameter - type(Vector) :: tmpx ! temporary solution vector - - ! Set overrelaxation parameter - w = ONE - - ! Dimensions - ng = 1 - nx = cmfd % indices(1) - ny = cmfd % indices(2) - nz = cmfd % indices(3) - n = A % n - - ! Perform Gauss Seidel iterations - GS: do igs = 1, 10000 - - ! Check for max iterations met - if (igs == 10000) then - call fatal_error('Maximum Gauss-Seidel iterations encountered.') - endif - - ! Copy over x vector - call tmpx % copy(x) - - ! Perform red/black gs iterations - REDBLACK: do irb = 0,1 - - ! Begin loop around matrix rows - ROWS: do irow = 1, n - - ! Get spatial location - call matrix_to_indices(irow, g, i, j, k, ng, nx, ny, nz) - - ! Filter out black cells (even) - if (mod(i+j+k,2) == irb) cycle - - ! Get the index of the diagonals for both rows - call A % search_indices(irow, irow, didx, found) - - ! Perform temporary sums, first do left of diag block, then right of diag block - tmp1 = ZERO - do icol = A % get_row(irow), didx - 1 - tmp1 = tmp1 + A % val(icol)*x % val(A % get_col(icol)) - end do - do icol = didx + 1, A % get_row(irow + 1) - 1 - tmp1 = tmp1 + A % val(icol)*x % val(A % get_col(icol)) - end do - - ! Solve for new x - x1 = (b % val(irow) - tmp1)/A % val(didx) - - ! Perform overrelaxation - x % val(irow) = (ONE - w)*x % val(irow) + w*x1 - - end do ROWS - - end do REDBLACK - - ! Check convergence - err = sqrt(sum(((tmpx % val - x % val)/tmpx % val)**2)/n) - its = igs - if (err < tol) exit - - ! Calculation new overrelaxation parameter - w = ONE/(ONE - 0.25_8*cmfd_spectral*w) - - end do GS - - call tmpx % destroy() - - end subroutine cmfd_linsolver_1g - -!=============================================================================== -! CMFD_LINSOLVER_2G solves the CMFD linear system -!=============================================================================== - - subroutine cmfd_linsolver_2g(A, b, x, tol, its) - - use constants, only: ONE, ZERO - use error, only: fatal_error - use cmfd_header, only: cmfd, cmfd_spectral - - type(Matrix), intent(inout) :: A ! coefficient matrix - type(Vector), intent(inout) :: b ! right hand side vector - type(Vector), intent(inout) :: x ! unknown vector - real(8), intent(in) :: tol ! tolerance on final error - integer, intent(out) :: its ! number of inner iterations - - integer :: g ! group index - integer :: i ! loop counter for x - integer :: j ! loop counter for y - integer :: k ! loop counter for z - integer :: n ! total size of vector - integer :: nx ! maximum dimension in x direction - integer :: ny ! maximum dimension in y direction - integer :: nz ! maximum dimension in z direction - integer :: ng ! number of energy groups - integer :: d1idx ! index of row "1" diagonal - integer :: d2idx ! index of row "2" diagonal - integer :: igs ! Gauss-Seidel iteration counter - integer :: irb ! Red/Black iteration switch - integer :: irow ! row iteration - integer :: icol ! iteration counter over columns - logical :: found ! did we find col - real(8) :: m11 ! block diagonal component 1,1 - real(8) :: m12 ! block diagonal component 1,2 - real(8) :: m21 ! block diagonal component 2,1 - real(8) :: m22 ! block diagonal component 2,2 - real(8) :: dm ! determinant of block diagonal - real(8) :: d11 ! inverse component 1,1 - real(8) :: d12 ! inverse component 1,2 - real(8) :: d21 ! inverse component 2,1 - real(8) :: d22 ! inverse component 2,2 - real(8) :: tmp1 ! temporary sum g1 - real(8) :: tmp2 ! temporary sum g2 - real(8) :: x1 ! new g1 value of x - real(8) :: x2 ! new g2 value of x - real(8) :: err ! error in convergence of solution - real(8) :: w ! overrelaxation parameter - type(Vector) :: tmpx ! temporary solution vector - - ! Set tolerance and overrelaxation parameter - w = ONE - - ! Dimensions - ng = 2 - nx = cmfd % indices(1) - ny = cmfd % indices(2) - nz = cmfd % indices(3) - n = A % n - - ! Perform Gauss Seidel iterations - GS: do igs = 1, 10000 - - ! Check for max iterations met - if (igs == 10000) then - call fatal_error('Maximum Gauss-Seidel iterations encountered.') - endif - - ! Copy over x vector - call tmpx % copy(x) - - ! Perform red/black gs iterations - REDBLACK: do irb = 0,1 - - ! Begin loop around matrix rows - ROWS: do irow = 1, n, 2 - - ! Get spatial location - call matrix_to_indices(irow, g, i, j, k, ng, nx, ny, nz) - - ! Filter out black cells (even) - if (mod(i+j+k,2) == irb) cycle - - ! Get the index of the diagonals for both rows - call A % search_indices(irow, irow, d1idx, found) - call A % search_indices(irow + 1, irow + 1, d2idx, found) - - ! Get block diagonal - m11 = A % val(d1idx) ! group 1 diagonal - m12 = A % val(d1idx + 1) ! group 1 right of diagonal (sorted by col) - m21 = A % val(d2idx - 1) ! group 2 left of diagonal (sorted by col) - m22 = A % val(d2idx) ! group 2 diagonal - - ! Analytically invert the diagonal - dm = m11*m22 - m12*m21 - d11 = m22/dm - d12 = -m12/dm - d21 = -m21/dm - d22 = m11/dm - - ! Perform temporary sums, first do left of diag block, then right of diag block - tmp1 = ZERO - tmp2 = ZERO - do icol = A % get_row(irow), d1idx - 1 - tmp1 = tmp1 + A % val(icol)*x % val(A % get_col(icol)) - end do - do icol = A % get_row(irow + 1), d2idx - 2 - tmp2 = tmp2 + A % val(icol)*x % val(A % get_col(icol)) - end do - do icol = d1idx + 2, A % get_row(irow + 1) - 1 - tmp1 = tmp1 + A % val(icol)*x % val(A % get_col(icol)) - end do - do icol = d2idx + 1, A % get_row(irow + 2) - 1 - tmp2 = tmp2 + A % val(icol)*x % val(A % get_col(icol)) - end do - - ! Adjust with RHS vector - tmp1 = b % val(irow) - tmp1 - tmp2 = b % val(irow + 1) - tmp2 - - ! Solve for new x - x1 = d11*tmp1 + d12*tmp2 - x2 = d21*tmp1 + d22*tmp2 - - ! Perform overrelaxation - x % val(irow) = (ONE - w)*x % val(irow) + w*x1 - x % val(irow + 1) = (ONE - w)*x % val(irow + 1) + w*x2 - - end do ROWS - - end do REDBLACK - - ! Check convergence - err = sqrt(sum(((tmpx % val - x % val)/tmpx % val)**2)/n) - its = igs - if (err < tol) exit - - ! Calculation new overrelaxation parameter - w = ONE/(ONE - 0.25_8*cmfd_spectral*w) - - end do GS - - call tmpx % destroy() - - end subroutine cmfd_linsolver_2g - -!=============================================================================== -! CMFD_LINSOLVER_ng solves the CMFD linear system -!=============================================================================== - - subroutine cmfd_linsolver_ng(A, b, x, tol, its) - - use constants, only: ONE, ZERO - use error, only: fatal_error - use cmfd_header, only: cmfd, cmfd_spectral - - type(Matrix), intent(inout) :: A ! coefficient matrix - type(Vector), intent(inout) :: b ! right hand side vector - type(Vector), intent(inout) :: x ! unknown vector - real(8), intent(in) :: tol ! tolerance on final error - integer, intent(out) :: its ! number of inner iterations - - integer :: g ! group index - integer :: i ! loop counter for x - integer :: j ! loop counter for y - integer :: k ! loop counter for z - integer :: n ! total size of vector - integer :: nx ! maximum dimension in x direction - integer :: ny ! maximum dimension in y direction - integer :: nz ! maximum dimension in z direction - integer :: ng ! number of energy groups - integer :: igs ! Gauss-Seidel iteration counter - integer :: irow ! row iteration - integer :: icol ! iteration counter over columns - integer :: didx ! index for diagonal component - logical :: found ! did we find col - real(8) :: tmp1 ! temporary sum g1 - real(8) :: x1 ! new g1 value of x - real(8) :: err ! error in convergence of solution - real(8) :: w ! overrelaxation parameter - type(Vector) :: tmpx ! temporary solution vector - - ! Set overrelaxation parameter - w = ONE - - ! Dimensions - ng = 1 - nx = cmfd % indices(1) - ny = cmfd % indices(2) - nz = cmfd % indices(3) - n = A % n - - ! Perform Gauss Seidel iterations - GS: do igs = 1, 10000 - - ! Check for max iterations met - if (igs == 10000) then - call fatal_error('Maximum Gauss-Seidel iterations encountered.') - endif - - ! Copy over x vector - call tmpx % copy(x) - - ! Begin loop around matrix rows - ROWS: do irow = 1, n - - ! Get spatial location - call matrix_to_indices(irow, g, i, j, k, ng, nx, ny, nz) - - ! Get the index of the diagonals for both rows - call A % search_indices(irow, irow, didx, found) - - ! Perform temporary sums, first do left of diag block, then right of diag block - tmp1 = ZERO - do icol = A % get_row(irow), didx - 1 - tmp1 = tmp1 + A % val(icol)*x % val(A % get_col(icol)) - end do - do icol = didx + 1, A % get_row(irow + 1) - 1 - tmp1 = tmp1 + A % val(icol)*x % val(A % get_col(icol)) - end do - - ! Solve for new x - x1 = (b % val(irow) - tmp1)/A % val(didx) - - ! Perform overrelaxation - x % val(irow) = (ONE - w)*x % val(irow) + w*x1 - - end do ROWS - - ! Check convergence - err = sqrt(sum(((tmpx % val - x % val)/tmpx % val)**2)/n) - its = igs - - if (err < tol) exit - - ! Calculation new overrelaxation parameter - w = ONE/(ONE - 0.25_8*cmfd_spectral*w) - - end do GS - - call tmpx % destroy() - - end subroutine cmfd_linsolver_ng - -!=============================================================================== -! EXTRACT_RESULTS takes results and puts them in CMFD global data object -!=============================================================================== - - subroutine extract_results() - - use cmfd_header, only: cmfd, cmfd_write_matrices - use simulation_header, only: current_batch - - character(len=25) :: filename ! name of file to write data - integer :: n ! problem size - - ! Get problem size - n = loss % n - - ! Allocate in cmfd object if not already allocated - if (adjoint_calc) then - if (.not. allocated(cmfd%adj_phi)) allocate(cmfd%adj_phi(n)) - else - if (.not. allocated(cmfd%phi)) allocate(cmfd%phi(n)) - end if - - ! Save values - if (adjoint_calc) then - cmfd % adj_phi = phi_n % val - else - cmfd % phi = phi_n % val - end if - - ! Save eigenvalue - if(adjoint_calc) then - cmfd%adj_keff = k_n - else - cmfd%keff = k_n - end if - - ! Normalize phi to 1 - if (adjoint_calc) then - cmfd%adj_phi = cmfd%adj_phi/sqrt(sum(cmfd%adj_phi*cmfd%adj_phi)) - else - cmfd%phi = cmfd%phi/sqrt(sum(cmfd%phi*cmfd%phi)) - end if - - ! Save dominance ratio - cmfd % dom(current_batch) = norm_n/norm_o - - ! Write out results - if (cmfd_write_matrices) then - if (adjoint_calc) then - filename = 'adj_fluxvec.dat' - else - filename = 'fluxvec.dat' - end if - call phi_n % write(filename) - end if - - end subroutine extract_results - -!=============================================================================== -! MATRIX_TO_INDICES converts a matrix index to spatial and group indicies -!=============================================================================== - - subroutine matrix_to_indices(irow, g, i, j, k, ng, nx, ny, nz) - - use cmfd_header, only: cmfd, cmfd_coremap - - integer, intent(out) :: i ! iteration counter for x - integer, intent(out) :: j ! iteration counter for y - integer, intent(out) :: k ! iteration counter for z - integer, intent(out) :: g ! iteration counter for groups - integer, intent(in) :: irow ! iteration counter over row (0 reference) - integer, intent(in) :: nx ! maximum number of x cells - integer, intent(in) :: ny ! maximum number of y cells - integer, intent(in) :: nz ! maximum number of z cells - integer, intent(in) :: ng ! maximum number of groups - - ! Check for core map - if (cmfd_coremap) then - - ! Get indices from indexmap - g = mod(irow-1, ng) + 1 - i = cmfd % indexmap((irow-1)/ng+1,1) - j = cmfd % indexmap((irow-1)/ng+1,2) - k = cmfd % indexmap((irow-1)/ng+1,3) - - else - - ! Compute indices - g = mod(irow-1, ng) + 1 - i = mod(irow-1, ng*nx)/ng + 1 - j = mod(irow-1, ng*nx*ny)/(ng*nx)+ 1 - k = mod(irow-1, ng*nx*ny*nz)/(ng*nx*ny) + 1 - - end if - - end subroutine matrix_to_indices - -!=============================================================================== -! FINALIZE frees all memory associated with power iteration -!=============================================================================== - - subroutine finalize() - - ! Destroy all objects - call loss % destroy() - call prod % destroy() - call phi_n % destroy() - call phi_o % destroy() - call s_n % destroy() - call s_o % destroy() - call serr_v % destroy - - end subroutine finalize - -end module cmfd_solver diff --git a/src/cmfd_solver.cpp b/src/cmfd_solver.cpp new file mode 100644 index 0000000000..1fb95d1b76 --- /dev/null +++ b/src/cmfd_solver.cpp @@ -0,0 +1,364 @@ +#include +#include + +#include "xtensor/xtensor.hpp" + +#include "openmc/error.h" +#include "openmc/constants.h" +#include "openmc/capi.h" + +namespace openmc { + +namespace cmfd { + +//============================================================================== +// Global variables +//============================================================================== + +std::vector indptr; + +std::vector indices; + +int dim; + +double spectral; + +int nx, ny, nz, ng; + +xt::xtensor indexmap; + +} // namespace cmfd + +//============================================================================== +// MATRIX_TO_INDICES converts a matrix index to spatial and group +// indices +//============================================================================== + +void matrix_to_indices(int irow, int& g, int& i, int& j, int& k) +{ + g = irow % cmfd::ng; + i = cmfd::indexmap(irow/cmfd::ng, 0); + j = cmfd::indexmap(irow/cmfd::ng, 1); + k = cmfd::indexmap(irow/cmfd::ng, 2); +} + +//============================================================================== +// GET_DIAGONAL_INDEX returns the index in CSR index array corresponding to +// the diagonal element of a specified row +//============================================================================== + +int get_diagonal_index(int row) +{ + for (int j = cmfd::indptr[row]; j < cmfd::indptr[row+1]; j++) { + if (cmfd::indices[j] == row) + return j; + } + + // Return -1 if not found + return -1; +} + +//============================================================================== +// SET_INDEXMAP sets the elements of indexmap based on input coremap +//============================================================================== + +void set_indexmap(const int* coremap) +{ + for (int z = 0; z < cmfd::nz; z++) { + for (int y = 0; y < cmfd::ny; y++) { + for (int x = 0; x < cmfd::nx; x++) { + if (coremap[(z*cmfd::ny*cmfd::nx) + (y*cmfd::nx) + x] != CMFD_NOACCEL) { + int counter = coremap[(z*cmfd::ny*cmfd::nx) + (y*cmfd::nx) + x]; + cmfd::indexmap(counter, 0) = x; + cmfd::indexmap(counter, 1) = y; + cmfd::indexmap(counter, 2) = z; + } + } + } + } +} + +//============================================================================== +// CMFD_LINSOLVER_1G solves a one group CMFD linear system +//============================================================================== + +int cmfd_linsolver_1g(const double* A_data, const double* b, double* x, + double tol) +{ + // Set overrelaxation parameter + double w = 1.0; + + // Perform Gauss-Seidel iterations + for (int igs = 1; igs <= 10000; igs++) { + double err = 0.0; + + // Copy over x vector + std::vector tmpx {x, x+cmfd::dim}; + + // Perform red/black Gauss-Seidel iterations + for (int irb = 0; irb < 2; irb++) { + + // Loop around matrix rows + for (int irow = 0; irow < cmfd::dim; irow++) { + int g, i, j, k; + matrix_to_indices(irow, g, i, j, k); + + // Filter out black cells + if ((i+j+k) % 2 != irb) continue; + + // Get index of diagonal for current row + int didx = get_diagonal_index(irow); + + // Perform temporary sums, first do left of diag, then right of diag + double tmp1 = 0.0; + for (int icol = cmfd::indptr[irow]; icol < didx; icol++) + tmp1 += A_data[icol] * x[cmfd::indices[icol]]; + for (int icol = didx + 1; icol < cmfd::indptr[irow + 1]; icol++) + tmp1 += A_data[icol] * x[cmfd::indices[icol]]; + + // Solve for new x + double x1 = (b[irow] - tmp1) / A_data[didx]; + + // Perform overrelaxation + x[irow] = (1.0 - w) * x[irow] + w * x1; + + // Compute residual and update error + double res = (tmpx[irow] - x[irow]) / tmpx[irow]; + err += res * res; + } + } + + // Check convergence + err = std::sqrt(err / cmfd::dim); + if (err < tol) + return igs; + + // Calculate new overrelaxation parameter + w = 1.0/(1.0 - 0.25 * cmfd::spectral * w); + } + + // Throw error, as max iterations met + fatal_error("Maximum Gauss-Seidel iterations encountered."); + + // Return -1 by default, although error thrown before reaching this point + return -1; +} + +//============================================================================== +// CMFD_LINSOLVER_2G solves a two group CMFD linear system +//============================================================================== + +int cmfd_linsolver_2g(const double* A_data, const double* b, double* x, + double tol) +{ + // Set overrelaxation parameter + double w = 1.0; + + // Perform Gauss-Seidel iterations + for (int igs = 1; igs <= 10000; igs++) { + double err = 0.0; + + // Copy over x vector + std::vector tmpx {x, x+cmfd::dim}; + + // Perform red/black Gauss-Seidel iterations + for (int irb = 0; irb < 2; irb++) { + + // Loop around matrix rows + for (int irow = 0; irow < cmfd::dim; irow+=2) { + int g, i, j, k; + matrix_to_indices(irow, g, i, j, k); + + // Filter out black cells + if ((i+j+k) % 2 != irb) continue; + + // Get index of diagonals for current row and next row + int d1idx = get_diagonal_index(irow); + int d2idx = get_diagonal_index(irow+1); + + // Get block diagonal + double m11 = A_data[d1idx]; // group 1 diagonal + double m12 = A_data[d1idx + 1]; // group 1 right of diagonal (sorted by col) + double m21 = A_data[d2idx - 1]; // group 2 left of diagonal (sorted by col) + double m22 = A_data[d2idx]; // group 2 diagonal + + // Analytically invert the diagonal + double dm = m11*m22 - m12*m21; + double d11 = m22/dm; + double d12 = -m12/dm; + double d21 = -m21/dm; + double d22 = m11/dm; + + // Perform temporary sums, first do left of diag, then right of diag + double tmp1 = 0.0; + double tmp2 = 0.0; + for (int icol = cmfd::indptr[irow]; icol < d1idx; icol++) + tmp1 += A_data[icol] * x[cmfd::indices[icol]]; + for (int icol = cmfd::indptr[irow+1]; icol < d2idx-1; icol++) + tmp2 += A_data[icol] * x[cmfd::indices[icol]]; + for (int icol = d1idx + 2; icol < cmfd::indptr[irow + 1]; icol++) + tmp1 += A_data[icol] * x[cmfd::indices[icol]]; + for (int icol = d2idx + 1; icol < cmfd::indptr[irow + 2]; icol++) + tmp2 += A_data[icol] * x[cmfd::indices[icol]]; + + // Adjust with RHS vector + tmp1 = b[irow] - tmp1; + tmp2 = b[irow + 1] - tmp2; + + // Solve for new x + double x1 = d11*tmp1 + d12*tmp2; + double x2 = d21*tmp1 + d22*tmp2; + + // Perform overrelaxation + x[irow] = (1.0 - w) * x[irow] + w * x1; + x[irow + 1] = (1.0 - w) * x[irow + 1] + w * x2; + + // Compute residual and update error + double res = (tmpx[irow] - x[irow]) / tmpx[irow]; + err += res * res; + } + } + + // Check convergence + err = std::sqrt(err / cmfd::dim); + if (err < tol) + return igs; + + // Calculate new overrelaxation parameter + w = 1.0/(1.0 - 0.25 * cmfd::spectral * w); + } + + // Throw error, as max iterations met + fatal_error("Maximum Gauss-Seidel iterations encountered."); + + // Return -1 by default, although error thrown before reaching this point + return -1; +} + +//============================================================================== +// CMFD_LINSOLVER_NG solves a general CMFD linear system +//============================================================================== + +int cmfd_linsolver_ng(const double* A_data, const double* b, double* x, + double tol) +{ + // Set overrelaxation parameter + double w = 1.0; + + // Perform Gauss-Seidel iterations + for (int igs = 1; igs <= 10000; igs++) { + double err = 0.0; + + // Copy over x vector + std::vector tmpx {x, x+cmfd::dim}; + + // Loop around matrix rows + for (int irow = 0; irow < cmfd::dim; irow++) { + // Get index of diagonal for current row + int didx = get_diagonal_index(irow); + + // Perform temporary sums, first do left of diag, then right of diag + double tmp1 = 0.0; + for (int icol = cmfd::indptr[irow]; icol < didx; icol++) + tmp1 += A_data[icol] * x[cmfd::indices[icol]]; + for (int icol = didx + 1; icol < cmfd::indptr[irow + 1]; icol++) + tmp1 += A_data[icol] * x[cmfd::indices[icol]]; + + // Solve for new x + double x1 = (b[irow] - tmp1) / A_data[didx]; + + // Perform overrelaxation + x[irow] = (1.0 - w) * x[irow] + w * x1; + + // Compute residual and update error + double res = (tmpx[irow] - x[irow]) / tmpx[irow]; + err += res * res; + } + + // Check convergence + err = std::sqrt(err / cmfd::dim); + if (err < tol) + return igs; + + // Calculate new overrelaxation parameter + w = 1.0/(1.0 - 0.25 * cmfd::spectral * w); + } + + // Throw error, as max iterations met + fatal_error("Maximum Gauss-Seidel iterations encountered."); + + // Return -1 by default, although error thrown before reaching this point + return -1; +} + +//============================================================================== +// OPENMC_INITIALIZE_LINSOLVER sets the fixed variables that are used for the +// linear solver +//============================================================================== + +extern "C" +void openmc_initialize_linsolver(const int* indptr, int len_indptr, + const int* indices, int n_elements, int dim, + double spectral, const int* cmfd_indices, + const int* map) +{ + // Store elements of indptr + for (int i = 0; i < len_indptr; i++) + cmfd::indptr.push_back(indptr[i]); + + // Store elements of indices + for (int i = 0; i < n_elements; i++) + cmfd::indices.push_back(indices[i]); + + // Set dimenion of CMFD problem and specral radius + cmfd::dim = dim; + cmfd::spectral = spectral; + + // Set number of groups + cmfd::ng = cmfd_indices[3]; + + // Set problem dimensions and indexmap if 1 or 2 group problem + if (cmfd::ng == 1 || cmfd::ng == 2) { + cmfd::nx = cmfd_indices[0]; + cmfd::ny = cmfd_indices[1]; + cmfd::nz = cmfd_indices[2]; + + // Resize indexmap and set its elements + cmfd::indexmap.resize({static_cast(dim), 3}); + set_indexmap(map); + } +} + +//============================================================================== +// OPENMC_RUN_LINSOLVER runs a Gauss Seidel linear solver to solve CMFD matrix +// equations +//============================================================================== + +extern "C" +int openmc_run_linsolver(const double* A_data, const double* b, double* x, + double tol) +{ + switch (cmfd::ng) { + case 1: + return cmfd_linsolver_1g(A_data, b, x, tol); + case 2: + return cmfd_linsolver_2g(A_data, b, x, tol); + default: + return cmfd_linsolver_ng(A_data, b, x, tol); + } +} + +//============================================================================== +// Fortran compatibility +//============================================================================== + +extern "C" void free_memory_cmfd() +{ + cmfd::indptr.clear(); + cmfd::indices.clear(); + // Resize indexmap to be an empty array + cmfd::indexmap.resize({0}); +} + + +} // namespace openmc \ No newline at end of file diff --git a/src/constants.F90 b/src/constants.F90 index a9812e4688..0e667c32a3 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -439,18 +439,6 @@ module constants ELECTRON_LED = 1, & ! Local Energy Deposition ELECTRON_TTB = 2 ! Thick Target Bremsstrahlung - !============================================================================= - ! CMFD CONSTANTS - - ! for non-accelerated regions on coarse mesh overlay - integer, parameter :: CMFD_NOACCEL = 99999 - - ! constant to represent a zero flux "albedo" - real(8), parameter :: ZERO_FLUX = 999.0_8 - - ! constant for writing out no residual - real(8), parameter :: CMFD_NORES = 99999.0_8 - !============================================================================= ! DELAYED NEUTRON PRECURSOR CONSTANTS diff --git a/src/input_xml.F90 b/src/input_xml.F90 index dab02f52bc..67c418509e 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -3,8 +3,6 @@ module input_xml use, intrinsic :: ISO_C_BINDING use algorithm, only: find - use cmfd_input, only: configure_cmfd - use cmfd_header, only: index_cmfd_mesh use constants use dict_header, only: DictIntInt, DictCharInt, DictEntryCI use endf, only: reaction_name @@ -153,8 +151,6 @@ contains ! Initialize distribcell_filters call prepare_distribcell() - if (cmfd_run) call configure_cmfd() - if (run_mode == MODE_PLOTTING) then ! Read plots.xml if it exists call read_plots_xml() diff --git a/src/output.F90 b/src/output.F90 index 485d957a6e..d2130a87a9 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -3,7 +3,6 @@ module output use, intrinsic :: ISO_C_BINDING use, intrinsic :: ISO_FORTRAN_ENV - use cmfd_header use constants use eigenvalue, only: openmc_get_keff use endf, only: reaction_name @@ -290,30 +289,12 @@ contains write(UNIT=ou, FMT='(A8,3X)', ADVANCE='NO') " k " if (entropy_on) write(UNIT=ou, FMT='(A8,3X)', ADVANCE='NO') "Entropy " write(UNIT=ou, FMT='(A20,3X)', ADVANCE='NO') " Average k " - if (cmfd_run) then - write(UNIT=ou, FMT='(A8,3X)', ADVANCE='NO') " CMFD k " - select case(trim(cmfd_display)) - case('entropy') - write(UNIT=ou, FMT='(A8,3X)', ADVANCE='NO') "CMFD Ent" - case('balance') - write(UNIT=ou, FMT='(A8,3X)', ADVANCE='NO') "RMS Bal " - case('source') - write(UNIT=ou, FMT='(A8,3X)', ADVANCE='NO') "RMS Src " - case('dominance') - write(UNIT=ou, FMT='(A8,3X)', ADVANCE='NO') "Dom Rat " - end select - end if write(UNIT=ou, FMT=*) write(UNIT=ou, FMT='(2X,A9,3X)', ADVANCE='NO') "=========" write(UNIT=ou, FMT='(A8,3X)', ADVANCE='NO') "========" if (entropy_on) write(UNIT=ou, FMT='(A8,3X)', ADVANCE='NO') "========" write(UNIT=ou, FMT='(A20,3X)', ADVANCE='NO') "====================" - if (cmfd_run) then - write(UNIT=ou, FMT='(A8,3X)', ADVANCE='NO') "========" - if (cmfd_display /= '') & - write(UNIT=ou, FMT='(A8,3X)', ADVANCE='NO') "========" - end if write(UNIT=ou, FMT=*) end subroutine print_columns @@ -386,26 +367,6 @@ contains write(UNIT=OUTPUT_UNIT, FMT='(23X)', ADVANCE='NO') end if - ! write out cmfd keff if it is active and other display info - if (cmfd_on) then - write(UNIT=OUTPUT_UNIT, FMT='(3X, F8.5)', ADVANCE='NO') & - cmfd % k_cmfd(current_batch) - select case(trim(cmfd_display)) - case('entropy') - write(UNIT=OUTPUT_UNIT, FMT='(3X, F8.5)', ADVANCE='NO') & - cmfd % entropy(current_batch) - case('balance') - write(UNIT=OUTPUT_UNIT, FMT='(3X, F8.5)', ADVANCE='NO') & - cmfd % balance(current_batch) - case('source') - write(UNIT=OUTPUT_UNIT, FMT='(3X, F8.5)', ADVANCE='NO') & - cmfd % src_cmp(current_batch) - case('dominance') - write(UNIT=OUTPUT_UNIT, FMT='(3X, F8.5)', ADVANCE='NO') & - cmfd % dom(current_batch) - end select - end if - ! next line write(UNIT=OUTPUT_UNIT, FMT=*) @@ -442,11 +403,6 @@ contains write(ou,100) " SEND/RECV source sites", time_bank_sendrecv_elapsed() end if write(ou,100) " Time accumulating tallies", time_tallies_elapsed() - if (cmfd_run) write(ou,100) " Time in CMFD", time_cmfd % elapsed - if (cmfd_run) write(ou,100) " Building matrices", & - time_cmfdbuild % elapsed - if (cmfd_run) write(ou,100) " Solving matrices", & - time_cmfdsolve % elapsed write(ou,100) "Total time for finalization", time_finalize_elapsed() write(ou,100) "Total time elapsed", time_total_elapsed() diff --git a/src/relaxng/cmfd.rnc b/src/relaxng/cmfd.rnc deleted file mode 100644 index dde97f971e..0000000000 --- a/src/relaxng/cmfd.rnc +++ /dev/null @@ -1,53 +0,0 @@ -element cmfd { - element mesh { - (element dimension { list { xsd:int+ } } | - attribute dimension { list { xsd:int+ } }) & - (element lower_left { list { xsd:double+ } } | - attribute lower_left { list { xsd:double+ } }) & - ( - (element upper_right { list { xsd:double+ } } | - attribute upper_right { list { xsd:double+ } }) | - (element width { list { xsd:double+ } } | - attribute width { list { xsd:double+ } }) - ) & - (element albedo { list { xsd:double+ } } | - attribute albedo { list { xsd:double+ } }) & - (element map { list { xsd:int+ } } | - attribute map { list { xsd:int+ } })? & - (element energy { list { xsd:double+ } } | - attribute energy { list { xsd:double+ } })? - } & - - element norm { xsd:double }? & - - element feedback { xsd:boolean }? & - - element downscatter { xsd:boolean }? & - - element dhat_reset { xsd:boolean }? & - - element power_monitor { xsd:boolean }? & - - element write_matrices { xsd:boolean }? & - - element run_adjoint { xsd:boolean }? & - - element write_hdf5 { xsd:boolean }? & - - element begin { xsd:int }? & - - element tally_reset { list { xsd:int+ } }? & - - element display { xsd:string }? & - - element spectral { xsd:double }? & - - element shift { xsd:double }? & - - element ktol { xsd:double }? & - - element stol { xsd:double }? & - - element gauss_seidel_tolerance { list { xsd:double+ } }? - -} diff --git a/src/relaxng/cmfd.rng b/src/relaxng/cmfd.rng deleted file mode 100644 index dc096ab87e..0000000000 --- a/src/relaxng/cmfd.rng +++ /dev/null @@ -1,215 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/relaxng/settings.rnc b/src/relaxng/settings.rnc index 83d9bed0c4..731aa70a93 100644 --- a/src/relaxng/settings.rnc +++ b/src/relaxng/settings.rnc @@ -59,8 +59,6 @@ element settings { element dagmc { xsd:boolean }? & - element run_cmfd { xsd:boolean }? & - element run_mode { xsd:string }? & element seed { xsd:positiveInteger }? & diff --git a/src/relaxng/settings.rng b/src/relaxng/settings.rng index 92a1d748ab..3ed15c3ed4 100644 --- a/src/relaxng/settings.rng +++ b/src/relaxng/settings.rng @@ -259,11 +259,6 @@ - - - - - diff --git a/src/settings.F90 b/src/settings.F90 index 6c8a610361..16f35341c7 100644 --- a/src/settings.F90 +++ b/src/settings.F90 @@ -115,9 +115,6 @@ module settings real(C_DOUBLE), bind(C) :: res_scat_energy_min real(C_DOUBLE), bind(C) :: res_scat_energy_max - ! Is CMFD active - logical(C_BOOL), bind(C) :: cmfd_run - ! No reduction at end of batch logical(C_BOOL), bind(C) :: reduce_tallies diff --git a/src/settings.cpp b/src/settings.cpp index 261536813c..3cfbd4286e 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -689,11 +689,6 @@ void read_settings_xml() } } - // Check for cmfd run - if (check_for_node(root, "run_cmfd")) { - cmfd_run = get_node_value_bool(root, "run_cmfd"); - } - // Resonance scattering parameters if (check_for_node(root, "resonance_scattering")) { xml_node node_res_scat = root.child("resonance_scattering"); diff --git a/src/simulation.cpp b/src/simulation.cpp index 8459c9c71f..3591953cb8 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -25,14 +25,9 @@ namespace openmc { // data/functions from Fortran side -extern "C" bool cmfd_on; - extern "C" void accumulate_tallies(); extern "C" void allocate_tally_results(); extern "C" void check_triggers(); -extern "C" void cmfd_init_batch(); -extern "C" void cmfd_tally_init(); -extern "C" void execute_cmfd(); extern "C" void init_tally_routines(); extern "C" void load_state_point(); extern "C" void print_batch_keff(); @@ -96,9 +91,6 @@ int openmc_simulation_init() // Allocate tally results arrays if they're not allocated yet allocate_tally_results(); - // Activate the CMFD tallies - cmfd_tally_init(); - // Call Fortran initialization simulation_init_f(); set_micro_xs(); @@ -349,11 +341,6 @@ void initialize_batch() } } - // check CMFD initialize batch - if (settings::run_mode == RUN_MODE_EIGENVALUE) { - if (settings::cmfd_run) cmfd_init_batch(); - } - // Add user tallies to active tallies list setup_active_tallies(); } @@ -373,8 +360,6 @@ void finalize_batch() } if (settings::run_mode == RUN_MODE_EIGENVALUE) { - // Perform CMFD calculation if on - if (cmfd_on) execute_cmfd(); // Write batch output if (mpi::master && settings::verbosity >= 7) print_batch_keff(); } diff --git a/src/state_point.F90 b/src/state_point.F90 index 9172f1fba0..d69217d8b7 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -14,7 +14,6 @@ module state_point use, intrinsic :: ISO_C_BINDING use bank_header, only: Bank - use cmfd_header use constants use endf, only: reaction_name use error, only: fatal_error, warning, write_message @@ -62,7 +61,7 @@ contains integer :: i_xs integer, allocatable :: id_array(:) integer(HID_T) :: file_id - integer(HID_T) :: cmfd_group, tallies_group, tally_group, & + integer(HID_T) :: tallies_group, tally_group, & filters_group, filter_group, derivs_group, & deriv_group, runtime_group character(MAX_WORD_LEN), allocatable :: str_array(:) @@ -167,23 +166,6 @@ contains ! Write out information for eigenvalue run if (run_mode == MODE_EIGENVALUE) then call write_eigenvalue_hdf5(file_id) - - ! Write out CMFD info - if (cmfd_on) then - call write_attribute(file_id, "cmfd_on", 1) - - cmfd_group = create_group(file_id, "cmfd") - call write_dataset(cmfd_group, "indices", cmfd % indices) - call write_dataset(cmfd_group, "k_cmfd", cmfd % k_cmfd) - call write_dataset(cmfd_group, "cmfd_src", cmfd % cmfd_src) - call write_dataset(cmfd_group, "cmfd_entropy", cmfd % entropy) - call write_dataset(cmfd_group, "cmfd_balance", cmfd % balance) - call write_dataset(cmfd_group, "cmfd_dominance", cmfd % dom) - call write_dataset(cmfd_group, "cmfd_srccmp", cmfd % src_cmp) - call close_group(cmfd_group) - else - call write_attribute(file_id, "cmfd_on", 0) - end if end if tallies_group = create_group(file_id, "tallies") @@ -411,13 +393,6 @@ contains end if call write_dataset(runtime_group, "accumulating tallies", & time_tallies_elapsed()) - if (cmfd_run) then - call write_dataset(runtime_group, "CMFD", time_cmfd % get_value()) - call write_dataset(runtime_group, "CMFD building matrices", & - time_cmfdbuild % get_value()) - call write_dataset(runtime_group, "CMFD solving matrices", & - time_cmfdsolve % get_value()) - end if call write_dataset(runtime_group, "total", time_total_elapsed()) call close_group(runtime_group) @@ -451,7 +426,6 @@ contains integer, allocatable :: array(:) integer(C_INT64_T) :: seed integer(HID_T) :: file_id - integer(HID_T) :: cmfd_group integer(HID_T) :: tallies_group integer(HID_T) :: tally_group logical :: source_present @@ -546,26 +520,6 @@ contains ! Take maximum of statepoint n_inactive and input n_inactive n_inactive = max(n_inactive, int_array(1)) - - ! Read in to see if CMFD was on - call read_attribute(int_array(1), file_id, "cmfd_on") - - ! Read in CMFD info - if (int_array(1) == 1) then - cmfd_group = open_group(file_id, "cmfd") - call read_dataset(cmfd % indices, cmfd_group, "indices") - call read_dataset(cmfd % k_cmfd(1:restart_batch), cmfd_group, "k_cmfd") - call read_dataset(cmfd % cmfd_src, cmfd_group, "cmfd_src") - call read_dataset(cmfd % entropy(1:restart_batch), cmfd_group, & - "cmfd_entropy") - call read_dataset(cmfd % balance(1:restart_batch), cmfd_group, & - "cmfd_balance") - call read_dataset(cmfd % dom(1:restart_batch), cmfd_group, & - "cmfd_dominance") - call read_dataset(cmfd % src_cmp(1:restart_batch), cmfd_group, & - "cmfd_srccmp") - call close_group(cmfd_group) - end if end if ! Read number of realizations for global tallies diff --git a/tests/regression_tests/cmfd_feed/cmfd.xml b/tests/regression_tests/cmfd_feed/cmfd.xml deleted file mode 100644 index ae50243c03..0000000000 --- a/tests/regression_tests/cmfd_feed/cmfd.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - -10 -1 -1 - 10 1 1 - 10 1 1 - 0.0 0.0 1.0 1.0 1.0 1.0 - - - 5 - dominance - true - 1.e-15 1.e-20 - diff --git a/tests/regression_tests/cmfd_feed/results_true.dat b/tests/regression_tests/cmfd_feed/results_true.dat index aba219ba83..c175dd7db9 100644 --- a/tests/regression_tests/cmfd_feed/results_true.dat +++ b/tests/regression_tests/cmfd_feed/results_true.dat @@ -391,10 +391,6 @@ cmfd indices 1.000000E+00 1.000000E+00 k cmfd -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.170416E+00 1.172966E+00 1.165537E+00 @@ -412,10 +408,6 @@ k cmfd 1.167848E+00 1.165116E+00 cmfd entropy -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.203643E+00 3.207943E+00 3.213367E+00 @@ -433,10 +425,6 @@ cmfd entropy 3.233193E+00 3.232564E+00 cmfd balance -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 4.009062E-03 4.431773E-03 3.152666E-03 @@ -446,18 +434,14 @@ cmfd balance 1.502427E-03 1.589825E-03 1.566020E-03 -1.219160E-03 -1.017888E-03 -9.771622E-04 -1.010120E-03 -1.073382E-03 -1.172758E-03 -9.827332E-04 +1.170009E-03 +9.507594E-04 +9.072590E-04 +1.009003E-03 +1.064703E-03 +1.163610E-03 +9.756308E-04 cmfd dominance ratio - 0.000E+00 - 0.000E+00 - 0.000E+00 - 0.000E+00 5.397E-01 5.425E-01 5.481E-01 @@ -475,10 +459,6 @@ cmfd dominance ratio 5.524E-01 5.523E-01 cmfd openmc source comparison -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 6.959834E-03 5.655657E-03 3.886185E-03 diff --git a/tests/regression_tests/cmfd_feed/settings.xml b/tests/regression_tests/cmfd_feed/settings.xml index 742aa22330..24b0b6ab50 100644 --- a/tests/regression_tests/cmfd_feed/settings.xml +++ b/tests/regression_tests/cmfd_feed/settings.xml @@ -23,7 +23,4 @@ 10 - - true - diff --git a/tests/regression_tests/cmfd_feed/test.py b/tests/regression_tests/cmfd_feed/test.py index 4fc39d96ad..04ac442e0e 100644 --- a/tests/regression_tests/cmfd_feed/test.py +++ b/tests/regression_tests/cmfd_feed/test.py @@ -1,6 +1,138 @@ from tests.testing_harness import CMFDTestHarness +from openmc import cmfd +import numpy as np +import scipy.sparse + + +def test_cmfd_physical_adjoint(): + """Test physical adjoint functionality of CMFD + + This test runs CMFD with a physical adjoint calculation and asserts that + the adjoint k-effective and flux vector are equal to the non-adjoint + k-effective and flux vector at the last batch (equivalent for 1 group + problems). + + """ + # Initialize and set CMFD mesh + cmfd_mesh = cmfd.CMFDMesh() + cmfd_mesh.lower_left = -10.0, -1.0, -1.0 + cmfd_mesh.upper_right = 10.0, 1.0, 1.0 + cmfd_mesh.dimension = 10, 1, 1 + cmfd_mesh.albedo = 0.0, 0.0, 1.0, 1.0, 1.0, 1.0 + + # Initialize and run CMFDRun object + cmfd_run = cmfd.CMFDRun() + cmfd_run.mesh = cmfd_mesh + cmfd_run.begin = 5 + cmfd_run.feedback = True + cmfd_run.gauss_seidel_tolerance = [1.e-15, 1.e-20] + cmfd_run.run_adjoint = True + cmfd_run.adjoint_type = 'physical' + cmfd_run.run() + assert(np.all(cmfd_run._phi == cmfd_run._adj_phi)) + assert(cmfd_run._adj_keff == cmfd_run._keff) + + +def test_cmfd_math_adjoint(): + """Test mathematical adjoint functionality of CMFD + + This test runs CMFD with a mathematical adjoint calculation and asserts + that the adjoint k-effective and flux vector are equal to the non-adjoint + k-effective and flux vector at the last batch (equivalent for 1 group + problems). + + """ + # Initialize and set CMFD mesh + cmfd_mesh = cmfd.CMFDMesh() + cmfd_mesh.lower_left = -10.0, -1.0, -1.0 + cmfd_mesh.upper_right = 10.0, 1.0, 1.0 + cmfd_mesh.dimension = 10, 1, 1 + cmfd_mesh.albedo = 0.0, 0.0, 1.0, 1.0, 1.0, 1.0 + + # Initialize and run CMFDRun object + cmfd_run = cmfd.CMFDRun() + cmfd_run.mesh = cmfd_mesh + cmfd_run.begin = 5 + cmfd_run.feedback = True + cmfd_run.gauss_seidel_tolerance = [1.e-15, 1.e-20] + cmfd_run.run_adjoint = True + cmfd_run.adjoint_type = 'math' + cmfd_run.run() + assert(np.all(cmfd_run._phi == cmfd_run._adj_phi)) + assert(cmfd_run._adj_keff == cmfd_run._keff) + + +def test_cmfd_write_matrices(): + """Test write matrices functionality of CMFD + + This test runs CMFD with feedback and loads the loss/production matrices + and flux vector that are saved to disk, and checks to make sure these + values are consistent with each other and simulation results. + + """ + # Initialize and set CMFD mesh + cmfd_mesh = cmfd.CMFDMesh() + cmfd_mesh.lower_left = -10.0, -1.0, -1.0 + cmfd_mesh.upper_right = 10.0, 1.0, 1.0 + cmfd_mesh.dimension = 10, 1, 1 + cmfd_mesh.albedo = 0.0, 0.0, 1.0, 1.0, 1.0, 1.0 + + # Initialize and run CMFDRun object + cmfd_run = cmfd.CMFDRun() + cmfd_run.mesh = cmfd_mesh + cmfd_run.begin = 5 + cmfd_run.display = {'dominance': True} + cmfd_run.feedback = True + cmfd_run.gauss_seidel_tolerance = [1.e-15, 1.e-20] + cmfd_run.write_matrices = True + cmfd_run.run() + + # Load loss matrix from numpy output file + loss_np = scipy.sparse.load_npz('loss.npz').todense() + # Load loss matrix from data file + loss_dat = np.loadtxt("loss.dat", delimiter=',') + + # Go through each element of loss_dat and compare to loss_np + for elem in loss_dat: + assert(np.isclose(loss_np[int(elem[0]), int(elem[1])], elem[2])) + + # Load production matrix from numpy output file + prod_np = scipy.sparse.load_npz('prod.npz').todense() + # Load production matrix from data file + prod_dat = np.loadtxt("prod.dat", delimiter=',') + + # Go through each element of prod_dat and compare to prod_np + for elem in prod_dat: + assert(np.isclose(prod_np[int(elem[0]), int(elem[1])], elem[2])) + + # Load flux vector from numpy output file + flux_np = np.load('fluxvec.npy') + # Load flux from data file + flux_dat = np.loadtxt("fluxvec.dat", delimiter='\n') + + # Compare flux from numpy file, .dat file, and from simulation + assert(np.all(np.isclose(flux_np, cmfd_run._phi))) + assert(np.all(np.isclose(flux_np, flux_dat))) def test_cmfd_feed(): - harness = CMFDTestHarness('statepoint.20.h5') + """Test 1 group CMFD solver with CMFD feedback""" + # Initialize and set CMFD mesh + cmfd_mesh = cmfd.CMFDMesh() + cmfd_mesh.lower_left = -10.0, -1.0, -1.0 + cmfd_mesh.upper_right = 10.0, 1.0, 1.0 + cmfd_mesh.dimension = 10, 1, 1 + cmfd_mesh.albedo = 0.0, 0.0, 1.0, 1.0, 1.0, 1.0 + + # Initialize and run CMFDRun object + cmfd_run = cmfd.CMFDRun() + cmfd_run.mesh = cmfd_mesh + cmfd_run.begin = 5 + cmfd_run.display = {'dominance': True} + cmfd_run.feedback = True + cmfd_run.gauss_seidel_tolerance = [1.e-15, 1.e-20] + cmfd_run.run() + + # Initialize and run CMFD test harness + harness = CMFDTestHarness('statepoint.20.h5', cmfd_run) harness.main() diff --git a/tests/regression_tests/cmfd_feed_2g/__init__.py b/tests/regression_tests/cmfd_feed_2g/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regression_tests/cmfd_feed_2g/geometry.xml b/tests/regression_tests/cmfd_feed_2g/geometry.xml new file mode 100644 index 0000000000..9fdd2ddcb6 --- /dev/null +++ b/tests/regression_tests/cmfd_feed_2g/geometry.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + 2 2 + -1.25984 -1.25984 + 1.25984 1.25984 + + 763 763 + 763 763 + + + + + + + + + + + + + + diff --git a/tests/regression_tests/cmfd_feed_2g/materials.xml b/tests/regression_tests/cmfd_feed_2g/materials.xml new file mode 100644 index 0000000000..bfa2d8842d --- /dev/null +++ b/tests/regression_tests/cmfd_feed_2g/materials.xml @@ -0,0 +1,59 @@ + + + + 300 + + + + + + 300 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 300 + + + + + + + + + + 300 + + + + + + + + + + diff --git a/tests/regression_tests/cmfd_feed_2g/results_true.dat b/tests/regression_tests/cmfd_feed_2g/results_true.dat new file mode 100644 index 0000000000..2be15c4e88 --- /dev/null +++ b/tests/regression_tests/cmfd_feed_2g/results_true.dat @@ -0,0 +1,434 @@ +k-combined: +1.038883E+00 1.017026E-02 +tally 1: +1.167304E+02 +1.362680E+03 +1.157179E+02 +1.339273E+03 +1.167087E+02 +1.362782E+03 +1.164493E+02 +1.356411E+03 +tally 2: +4.400233E+01 +9.698989E+01 +6.541957E+01 +2.144299E+02 +1.863245E+02 +1.738212E+03 +1.038047E+02 +5.390768E+02 +4.368854E+01 +9.564001E+01 +6.489620E+01 +2.109923E+02 +1.836691E+02 +1.687792E+03 +1.022706E+02 +5.232407E+02 +4.433402E+01 +9.859049E+01 +6.548060E+01 +2.150470E+02 +1.839219E+02 +1.692209E+03 +1.031634E+02 +5.323852E+02 +4.380686E+01 +9.606723E+01 +6.480355E+01 +2.103905E+02 +1.868847E+02 +1.747254E+03 +1.038109E+02 +5.391360E+02 +tally 3: +6.192979E+01 +1.921761E+02 +0.000000E+00 +0.000000E+00 +2.033842E-02 +4.375294E-05 +4.296338E+00 +9.280716E-01 +3.493776E+00 +6.157094E-01 +0.000000E+00 +0.000000E+00 +9.876716E+01 +4.880113E+02 +9.043140E-01 +4.156054E-02 +6.138292E+01 +1.887853E+02 +0.000000E+00 +0.000000E+00 +1.591722E-02 +2.328432E-05 +4.296798E+00 +9.289349E-01 +3.584967E+00 +6.444749E-01 +0.000000E+00 +0.000000E+00 +9.717439E+01 +4.724254E+02 +8.435691E-01 +3.666151E-02 +6.192881E+01 +1.924016E+02 +0.000000E+00 +0.000000E+00 +1.796382E-02 +3.190854E-05 +4.343238E+00 +9.514039E-01 +3.504683E+00 +6.157615E-01 +0.000000E+00 +0.000000E+00 +9.818871E+01 +4.822799E+02 +8.401346E-01 +3.664037E-02 +6.130607E+01 +1.882837E+02 +0.000000E+00 +0.000000E+00 +1.220252E-02 +1.711667E-05 +4.245413E+00 +9.056738E-01 +3.468894E+00 +6.033679E-01 +0.000000E+00 +0.000000E+00 +9.885292E+01 +4.888720E+02 +9.509199E-01 +4.670951E-02 +tally 4: +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +9.200260E+00 +4.243011E+00 +3.728022E+01 +6.953801E+01 +9.178009E+00 +4.218903E+00 +3.738925E+01 +6.993833E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +9.019225E+00 +4.079528E+00 +3.709185E+01 +6.883085E+01 +9.037478E+00 +4.095846E+00 +3.710303E+01 +6.888447E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +9.178009E+00 +4.218903E+00 +3.738925E+01 +6.993833E+01 +9.200260E+00 +4.243011E+00 +3.728022E+01 +6.953801E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +9.097360E+00 +4.147125E+00 +3.700603E+01 +6.851110E+01 +9.003417E+00 +4.059372E+00 +3.717266E+01 +6.912709E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +9.042233E+00 +4.097018E+00 +3.711597E+01 +6.891834E+01 +9.107597E+00 +4.161211E+00 +3.715168E+01 +6.904544E+01 +9.037478E+00 +4.095846E+00 +3.710303E+01 +6.888447E+01 +9.019225E+00 +4.079528E+00 +3.709185E+01 +6.883085E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +9.107597E+00 +4.161211E+00 +3.715168E+01 +6.904544E+01 +9.042233E+00 +4.097018E+00 +3.711597E+01 +6.891834E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +9.003417E+00 +4.059372E+00 +3.717266E+01 +6.912709E+01 +9.097360E+00 +4.147125E+00 +3.700603E+01 +6.851110E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +tally 5: +6.195013E+01 +1.923032E+02 +1.022516E+02 +5.230716E+02 +1.418310E+01 +1.011490E+01 +4.677537E+01 +1.094859E+02 +6.139884E+01 +1.888834E+02 +1.007512E+02 +5.078162E+02 +1.403236E+01 +9.884347E+00 +4.598486E+01 +1.058070E+02 +6.194677E+01 +1.925125E+02 +1.016864E+02 +5.172531E+02 +1.407797E+01 +9.971785E+00 +4.623118E+01 +1.069663E+02 +6.131828E+01 +1.883600E+02 +1.023107E+02 +5.236668E+02 +1.375796E+01 +9.501501E+00 +4.638162E+01 +1.076532E+02 +cmfd indices +2.000000E+00 +2.000000E+00 +1.000000E+00 +2.000000E+00 +k cmfd +1.034617E+00 +1.034521E+00 +1.036479E+00 +1.035658E+00 +1.032505E+00 +1.027277E+00 +1.029326E+00 +1.029881E+00 +1.035579E+00 +1.034753E+00 +1.031963E+00 +1.034797E+00 +1.035383E+00 +1.035908E+00 +1.035629E+00 +1.036034E+00 +cmfd entropy +1.999493E+00 +1.999058E+00 +1.999331E+00 +1.999370E+00 +1.999383E+00 +1.999455E+00 +1.999640E+00 +1.999810E+00 +1.999881E+00 +1.999937E+00 +1.999973E+00 +1.999965E+00 +1.999972E+00 +1.999982E+00 +1.999998E+00 +1.999984E+00 +cmfd balance +3.993043E-04 +4.168558E-04 +6.384693E-04 +3.928217E-04 +3.789833E-04 +2.684860E-04 +4.849910E-04 +1.084019E-03 +1.091775E-03 +5.459767E-04 +4.455545E-04 +4.011468E-04 +3.710248E-04 +3.577152E-04 +3.845993E-04 +3.900672E-04 +cmfd dominance ratio + 6.239E-03 + 6.303E-03 + 6.347E-03 + 6.388E-03 + 6.340E-03 + 6.334E-03 + 6.270E-03 + 6.173E-03 + 6.068E-03 + 6.061E-03 + 6.016E-03 + 6.108E-03 + 6.117E-03 + 6.074E-03 + 6.005E-03 + 5.996E-03 +cmfd openmc source comparison +1.931386E-05 +2.839162E-05 +1.407962E-05 +6.718148E-06 +9.164193E-06 +1.382539E-05 +2.871606E-05 +4.192300E-05 +5.327516E-05 +6.566500E-05 +6.655541E-05 +6.034977E-05 +6.004677E-05 +5.711623E-05 +6.271264E-05 +6.425363E-05 +cmfd source +2.510278E-01 +2.480592E-01 +2.501750E-01 +2.507380E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 diff --git a/tests/regression_tests/cmfd_feed_2g/settings.xml b/tests/regression_tests/cmfd_feed_2g/settings.xml new file mode 100644 index 0000000000..9e483e6a09 --- /dev/null +++ b/tests/regression_tests/cmfd_feed_2g/settings.xml @@ -0,0 +1,25 @@ + + + + + eigenvalue + 20 + 10 + 1000 + + + + + -1.25984 -1.25984 -1 1.25984 1.25984 1 + + + + + + 2 2 1 + -1.25984 -1.25984 -1.0 + 1.25984 1.25984 1.0 + + 10 + + diff --git a/tests/regression_tests/cmfd_feed_2g/tallies.xml b/tests/regression_tests/cmfd_feed_2g/tallies.xml new file mode 100644 index 0000000000..477a078331 --- /dev/null +++ b/tests/regression_tests/cmfd_feed_2g/tallies.xml @@ -0,0 +1,21 @@ + + + + + regular + -1.25984 -1.25984 -1.0 + 1.25984 1.25984 1.0 + 2 2 1 + + + + mesh + 1 + + + + 1 + flux + + + diff --git a/tests/regression_tests/cmfd_feed_2g/test.py b/tests/regression_tests/cmfd_feed_2g/test.py new file mode 100644 index 0000000000..22cc7e1602 --- /dev/null +++ b/tests/regression_tests/cmfd_feed_2g/test.py @@ -0,0 +1,28 @@ +from tests.testing_harness import CMFDTestHarness +from openmc import cmfd +import numpy as np + + +def test_cmfd_feed_2g(): + """Test 2 group CMFD solver results with CMFD feedback""" + # Initialize and set CMFD mesh + cmfd_mesh = cmfd.CMFDMesh() + cmfd_mesh.lower_left = -1.25984, -1.25984, -1.0 + cmfd_mesh.upper_right = 1.25984, 1.25984, 1.0 + cmfd_mesh.dimension = 2, 2, 1 + cmfd_mesh.energy = [0.0, 0.625, 20000000] + cmfd_mesh.albedo = 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 + + # Initialize and run CMFDRun object + cmfd_run = cmfd.CMFDRun() + cmfd_run.mesh = cmfd_mesh + cmfd_run.begin = 5 + cmfd_run.display = {'dominance': True} + cmfd_run.feedback = True + cmfd_run.downscatter = True + cmfd_run.gauss_seidel_tolerance = [1.e-15, 1.e-20] + cmfd_run.run() + + # Initialize and run CMFD test harness + harness = CMFDTestHarness('statepoint.20.h5', cmfd_run) + harness.main() diff --git a/tests/regression_tests/cmfd_feed_ng/__init__.py b/tests/regression_tests/cmfd_feed_ng/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regression_tests/cmfd_feed_ng/geometry.xml b/tests/regression_tests/cmfd_feed_ng/geometry.xml new file mode 100644 index 0000000000..9fdd2ddcb6 --- /dev/null +++ b/tests/regression_tests/cmfd_feed_ng/geometry.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + 2 2 + -1.25984 -1.25984 + 1.25984 1.25984 + + 763 763 + 763 763 + + + + + + + + + + + + + + diff --git a/tests/regression_tests/cmfd_feed_ng/materials.xml b/tests/regression_tests/cmfd_feed_ng/materials.xml new file mode 100644 index 0000000000..bfa2d8842d --- /dev/null +++ b/tests/regression_tests/cmfd_feed_ng/materials.xml @@ -0,0 +1,59 @@ + + + + 300 + + + + + + 300 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 300 + + + + + + + + + + 300 + + + + + + + + + + diff --git a/tests/regression_tests/cmfd_feed_ng/results_true.dat b/tests/regression_tests/cmfd_feed_ng/results_true.dat new file mode 100644 index 0000000000..fea3d38b8b --- /dev/null +++ b/tests/regression_tests/cmfd_feed_ng/results_true.dat @@ -0,0 +1,621 @@ +k-combined: +1.030507E+00 9.667655E-03 +tally 1: +1.159445E+02 +1.344573E+03 +1.159285E+02 +1.344429E+03 +1.161340E+02 +1.349209E+03 +1.160434E+02 +1.347427E+03 +tally 2: +3.482032E+01 +7.610041E+01 +5.123532E+01 +1.648226E+02 +1.063498E+01 +7.110316E+00 +9.001212E+00 +5.085589E+00 +1.435947E+02 +1.322778E+03 +7.400822E+01 +3.425876E+02 +3.415144E+01 +7.318850E+01 +5.046314E+01 +1.597317E+02 +1.028893E+01 +6.661534E+00 +8.653997E+00 +4.698660E+00 +1.442064E+02 +1.338979E+03 +7.405073E+01 +3.428774E+02 +3.431740E+01 +7.390622E+01 +5.109288E+01 +1.639090E+02 +1.034894E+01 +6.746021E+00 +8.657005E+00 +4.712481E+00 +1.383790E+02 +1.200016E+03 +7.320761E+01 +3.351661E+02 +3.426169E+01 +7.373156E+01 +5.042174E+01 +1.596859E+02 +1.041331E+01 +6.828453E+00 +8.729287E+00 +4.779221E+00 +1.374766E+02 +1.182015E+03 +7.390660E+01 +3.416196E+02 +tally 3: +4.842028E+01 +1.472522E+02 +0.000000E+00 +0.000000E+00 +1.766768E-02 +3.669087E-05 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +3.489090E+00 +7.670426E-01 +2.522457E+00 +3.988419E-01 +0.000000E+00 +0.000000E+00 +6.323118E+00 +2.515554E+00 +0.000000E+00 +0.000000E+00 +1.000000E-03 +1.000000E-06 +9.772798E-02 +7.833336E-04 +3.020916E-01 +5.962947E-03 +0.000000E+00 +0.000000E+00 +2.669802E+00 +4.494602E-01 +0.000000E+00 +0.000000E+00 +6.995726E+01 +3.061105E+02 +6.245439E-01 +2.516241E-02 +4.767259E+01 +1.425559E+02 +0.000000E+00 +0.000000E+00 +1.511055E-02 +2.706989E-05 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +3.359388E+00 +7.162206E-01 +2.420435E+00 +3.693966E-01 +0.000000E+00 +0.000000E+00 +6.097921E+00 +2.335376E+00 +0.000000E+00 +0.000000E+00 +2.973834E-03 +2.952905E-06 +9.585516E-02 +7.950754E-04 +3.306195E-01 +7.226156E-03 +0.000000E+00 +0.000000E+00 +2.606740E+00 +4.273433E-01 +0.000000E+00 +0.000000E+00 +7.004531E+01 +3.068103E+02 +5.609353E-01 +2.054700E-02 +4.844434E+01 +1.474236E+02 +0.000000E+00 +0.000000E+00 +6.909228E-03 +6.822381E-06 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +3.337728E+00 +7.036827E-01 +2.418767E+00 +3.680337E-01 +0.000000E+00 +0.000000E+00 +6.114057E+00 +2.352184E+00 +0.000000E+00 +0.000000E+00 +1.000000E-03 +1.000000E-06 +1.057559E-01 +8.659764E-04 +3.268970E-01 +6.949165E-03 +0.000000E+00 +0.000000E+00 +2.538042E+00 +4.047397E-01 +0.000000E+00 +0.000000E+00 +6.922289E+01 +2.996756E+02 +6.190458E-01 +2.456695E-02 +4.757150E+01 +1.421640E+02 +0.000000E+00 +0.000000E+00 +8.765775E-03 +1.054666E-05 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +3.436793E+00 +7.430145E-01 +2.473841E+00 +3.843375E-01 +0.000000E+00 +0.000000E+00 +6.104077E+00 +2.339598E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.061129E-01 +8.936114E-04 +3.092546E-01 +6.384841E-03 +0.000000E+00 +0.000000E+00 +2.539292E+00 +4.045561E-01 +0.000000E+00 +0.000000E+00 +6.994273E+01 +3.059846E+02 +5.887450E-01 +2.235244E-02 +tally 4: +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +7.109363E+00 +3.174618E+00 +2.099346E+00 +2.784777E-01 +2.785196E+01 +4.850659E+01 +7.069021E+00 +3.132588E+00 +2.149589E+00 +2.900131E-01 +2.768129E+01 +4.790811E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +7.175941E+00 +3.224156E+00 +2.135542E+00 +2.863424E-01 +2.728334E+01 +4.655111E+01 +7.206774E+00 +3.256252E+00 +2.075924E+00 +2.712640E-01 +2.747967E+01 +4.722188E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +7.069021E+00 +3.132588E+00 +2.149589E+00 +2.900131E-01 +2.768129E+01 +4.790811E+01 +7.109363E+00 +3.174618E+00 +2.099346E+00 +2.784777E-01 +2.785196E+01 +4.850659E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +7.108601E+00 +3.175833E+00 +2.103259E+00 +2.783890E-01 +2.784173E+01 +4.846581E+01 +7.107753E+00 +3.173064E+00 +2.087728E+00 +2.737368E-01 +2.779197E+01 +4.828855E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +7.148041E+00 +3.216417E+00 +2.124230E+00 +2.831606E-01 +2.743879E+01 +4.708424E+01 +7.081749E+00 +3.151711E+00 +2.062609E+00 +2.682691E-01 +2.761154E+01 +4.768316E+01 +7.206774E+00 +3.256252E+00 +2.075924E+00 +2.712640E-01 +2.747967E+01 +4.722188E+01 +7.175941E+00 +3.224156E+00 +2.135542E+00 +2.863424E-01 +2.728334E+01 +4.655111E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +7.081749E+00 +3.151711E+00 +2.062609E+00 +2.682691E-01 +2.761154E+01 +4.768316E+01 +7.148041E+00 +3.216417E+00 +2.124230E+00 +2.831606E-01 +2.743879E+01 +4.708424E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +7.107753E+00 +3.173064E+00 +2.087728E+00 +2.737368E-01 +2.779197E+01 +4.828855E+01 +7.108601E+00 +3.175833E+00 +2.103259E+00 +2.783890E-01 +2.784173E+01 +4.846581E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +tally 5: +4.843795E+01 +1.473599E+02 +8.846575E+00 +4.912671E+00 +7.292412E+01 +3.326224E+02 +1.096673E+01 +7.549697E+00 +4.119614E+00 +1.064248E+00 +3.323487E+01 +6.913295E+01 +4.768770E+01 +1.426471E+02 +8.521329E+00 +4.554207E+00 +7.297983E+01 +3.330439E+02 +1.083867E+01 +7.393362E+00 +3.955093E+00 +9.810363E-01 +3.309790E+01 +6.852254E+01 +4.845125E+01 +1.474656E+02 +8.533824E+00 +4.579629E+00 +7.208076E+01 +3.249387E+02 +1.100395E+01 +7.627044E+00 +3.908798E+00 +9.610162E-01 +3.287630E+01 +6.763801E+01 +4.758027E+01 +1.422161E+02 +8.577918E+00 +4.614174E+00 +7.278634E+01 +3.313433E+02 +1.072190E+01 +7.216342E+00 +3.922303E+00 +9.640585E-01 +3.334768E+01 +6.955922E+01 +cmfd indices +2.000000E+00 +2.000000E+00 +1.000000E+00 +3.000000E+00 +k cmfd +1.024488E+00 +1.022339E+00 +1.024720E+00 +1.025459E+00 +1.029180E+00 +1.022688E+00 +1.019305E+00 +1.020356E+00 +1.023384E+00 +1.023519E+00 +1.026360E+00 +cmfd entropy +1.999710E+00 +1.999709E+00 +1.999704E+00 +1.999747E+00 +1.999885E+00 +1.999911E+00 +1.999905E+00 +1.999936E+00 +1.999873E+00 +1.999826E+00 +1.999768E+00 +cmfd balance +3.179889E-04 +4.166064E-04 +2.746180E-04 +3.345930E-04 +5.129605E-04 +4.008511E-04 +3.628451E-04 +3.401727E-04 +3.166255E-04 +2.642675E-04 +2.482640E-04 +cmfd dominance ratio + 3.737E-03 + 3.786E-03 + 3.798E-03 + 3.789E-03 + 3.741E-03 + 3.772E-03 + 3.793E-03 + 3.684E-03 + 3.740E-03 + 3.764E-03 + 3.798E-03 +cmfd openmc source comparison +3.559244E-05 +4.429506E-05 +2.739679E-05 +2.715448E-05 +5.105340E-05 +4.069206E-05 +4.033877E-05 +3.669734E-05 +3.540373E-05 +2.907247E-05 +2.734478E-05 +cmfd source +2.564687E-01 +2.445733E-01 +2.473769E-01 +2.515811E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 diff --git a/tests/regression_tests/cmfd_feed_ng/settings.xml b/tests/regression_tests/cmfd_feed_ng/settings.xml new file mode 100644 index 0000000000..9e483e6a09 --- /dev/null +++ b/tests/regression_tests/cmfd_feed_ng/settings.xml @@ -0,0 +1,25 @@ + + + + + eigenvalue + 20 + 10 + 1000 + + + + + -1.25984 -1.25984 -1 1.25984 1.25984 1 + + + + + + 2 2 1 + -1.25984 -1.25984 -1.0 + 1.25984 1.25984 1.0 + + 10 + + diff --git a/tests/regression_tests/cmfd_feed_ng/tallies.xml b/tests/regression_tests/cmfd_feed_ng/tallies.xml new file mode 100644 index 0000000000..477a078331 --- /dev/null +++ b/tests/regression_tests/cmfd_feed_ng/tallies.xml @@ -0,0 +1,21 @@ + + + + + regular + -1.25984 -1.25984 -1.0 + 1.25984 1.25984 1.0 + 2 2 1 + + + + mesh + 1 + + + + 1 + flux + + + diff --git a/tests/regression_tests/cmfd_feed_ng/test.py b/tests/regression_tests/cmfd_feed_ng/test.py new file mode 100644 index 0000000000..db0b44f3e1 --- /dev/null +++ b/tests/regression_tests/cmfd_feed_ng/test.py @@ -0,0 +1,29 @@ +from tests.testing_harness import CMFDTestHarness +from openmc import cmfd +import numpy as np + + +def test_cmfd_feed_ng(): + """Test n group CMFD solver with CMFD feedback""" + # Initialize and set CMFD mesh + cmfd_mesh = cmfd.CMFDMesh() + cmfd_mesh.lower_left = -1.25984, -1.25984, -1.0 + cmfd_mesh.upper_right = 1.25984, 1.25984, 1.0 + cmfd_mesh.dimension = 2, 2, 1 + cmfd_mesh.energy = [0.0, 0.625, 5.53080, 20000000] + cmfd_mesh.albedo = 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 + + # Initialize and run CMFDRun object + cmfd_run = cmfd.CMFDRun() + cmfd_run.mesh = cmfd_mesh + cmfd_run.reset = [5] + cmfd_run.begin = 10 + cmfd_run.display = {'dominance': True} + cmfd_run.feedback = True + cmfd_run.downscatter = True + cmfd_run.gauss_seidel_tolerance = [1.e-15, 1.e-20] + cmfd_run.run() + + # Initialize and run CMFD test harness + harness = CMFDTestHarness('statepoint.20.h5', cmfd_run) + harness.main() diff --git a/tests/regression_tests/cmfd_nofeed/cmfd.xml b/tests/regression_tests/cmfd_nofeed/cmfd.xml deleted file mode 100644 index 2e13cff6b8..0000000000 --- a/tests/regression_tests/cmfd_nofeed/cmfd.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - -10 -1 -1 - 10 1 1 - 10 1 1 - 0.0 0.0 1.0 1.0 1.0 1.0 - - - 5 - dominance - false - 1.e-15 1.e-20 - - diff --git a/tests/regression_tests/cmfd_nofeed/results_true.dat b/tests/regression_tests/cmfd_nofeed/results_true.dat index 23368457d0..33dff8811f 100644 --- a/tests/regression_tests/cmfd_nofeed/results_true.dat +++ b/tests/regression_tests/cmfd_nofeed/results_true.dat @@ -391,10 +391,6 @@ cmfd indices 1.000000E+00 1.000000E+00 k cmfd -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.170416E+00 1.172572E+00 1.171159E+00 @@ -412,10 +408,6 @@ k cmfd 1.153714E+00 1.159485E+00 cmfd entropy -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.203643E+00 3.204555E+00 3.210935E+00 @@ -433,10 +425,6 @@ cmfd entropy 3.221580E+00 3.220523E+00 cmfd balance -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 4.009062E-03 4.869661E-03 2.997290E-03 @@ -446,18 +434,14 @@ cmfd balance 1.403979E-03 1.398429E-03 1.818398E-03 -1.761250E-03 -1.646649E-03 -1.480119E-03 -1.399559E-03 -1.400162E-03 -1.178363E-03 -1.292280E-03 +1.458254E-03 +1.437876E-03 +1.276844E-03 +1.289036E-03 +1.330300E-03 +1.139999E-03 +1.233647E-03 cmfd dominance ratio - 0.000E+00 - 0.000E+00 - 0.000E+00 - 0.000E+00 5.397E-01 5.405E-01 5.412E-01 @@ -475,10 +459,6 @@ cmfd dominance ratio 5.461E-01 5.443E-01 cmfd openmc source comparison -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 6.959834E-03 5.494667E-03 4.076255E-03 diff --git a/tests/regression_tests/cmfd_nofeed/settings.xml b/tests/regression_tests/cmfd_nofeed/settings.xml index b56acb4b76..24b0b6ab50 100644 --- a/tests/regression_tests/cmfd_nofeed/settings.xml +++ b/tests/regression_tests/cmfd_nofeed/settings.xml @@ -23,7 +23,4 @@ 10 - - true - diff --git a/tests/regression_tests/cmfd_nofeed/test.py b/tests/regression_tests/cmfd_nofeed/test.py index a3c7301738..36f6930268 100644 --- a/tests/regression_tests/cmfd_nofeed/test.py +++ b/tests/regression_tests/cmfd_nofeed/test.py @@ -1,6 +1,26 @@ from tests.testing_harness import CMFDTestHarness +from openmc import cmfd +import numpy as np def test_cmfd_nofeed(): - harness = CMFDTestHarness('statepoint.20.h5') + """Test 1 group CMFD solver without CMFD feedback""" + # Initialize and set CMFD mesh + cmfd_mesh = cmfd.CMFDMesh() + cmfd_mesh.lower_left = -10.0, -1.0, -1.0 + cmfd_mesh.upper_right = 10.0, 1.0, 1.0 + cmfd_mesh.dimension = 10, 1, 1 + cmfd_mesh.albedo = 0.0, 0.0, 1.0, 1.0, 1.0, 1.0 + + # Initialize and run CMFDRun object + cmfd_run = cmfd.CMFDRun() + cmfd_run.mesh = cmfd_mesh + cmfd_run.begin = 5 + cmfd_run.display = {'dominance': True} + cmfd_run.feedback = False + cmfd_run.gauss_seidel_tolerance = [1.e-15, 1.e-20] + cmfd_run.run() + + # Initialize and run CMFD test harness + harness = CMFDTestHarness('statepoint.20.h5', cmfd_run) harness.main() diff --git a/tests/testing_harness.py b/tests/testing_harness.py index fb07575ce7..d8baf32061 100644 --- a/tests/testing_harness.py +++ b/tests/testing_harness.py @@ -133,35 +133,67 @@ class HashedTestHarness(TestHarness): class CMFDTestHarness(TestHarness): """Specialized TestHarness for running OpenMC CMFD tests.""" - def _get_results(self): - """Digest info in the statepoint and return as a string.""" + def __init__(self, statepoint_name, cmfd_run): + self._sp_name = statepoint_name + self._create_cmfd_result_str(cmfd_run) - # Write out the eigenvalue and tallies. - outstr = super()._get_results() + def _create_cmfd_result_str(self, cmfd_run): + """Create CMFD result string from variables of CMFDRun instance""" + outstr = 'cmfd indices\n' + outstr += '\n'.join(['{0:12.6E}'.format(x) for x in cmfd_run.indices]) + outstr += '\nk cmfd\n' + outstr += '\n'.join(['{0:12.6E}'.format(x) for x in cmfd_run.k_cmfd]) + outstr += '\ncmfd entropy\n' + outstr += '\n'.join(['{0:12.6E}'.format(x) for x in cmfd_run.entropy]) + outstr += '\ncmfd balance\n' + outstr += '\n'.join(['{0:12.6E}'.format(x) for x in cmfd_run.balance]) + outstr += '\ncmfd dominance ratio\n' + outstr += '\n'.join(['{0:10.3E}'.format(x) for x in cmfd_run.dom]) + outstr += '\ncmfd openmc source comparison\n' + outstr += '\n'.join(['{0:12.6E}'.format(x) for x in cmfd_run.src_cmp]) + outstr += '\ncmfd source\n' + cmfdsrc = np.reshape(cmfd_run.cmfd_src, np.product(cmfd_run.indices), + order='F') + outstr += '\n'.join(['{0:12.6E}'.format(x) for x in cmfdsrc]) + outstr += '\n' + self._cmfdrun_results = outstr - # Read the statepoint file. - statepoint = glob.glob(self._sp_name)[0] - with openmc.StatePoint(statepoint) as sp: - # Write out CMFD data. - outstr += 'cmfd indices\n' - outstr += '\n'.join(['{0:12.6E}'.format(x) for x in sp.cmfd_indices]) - outstr += '\nk cmfd\n' - outstr += '\n'.join(['{0:12.6E}'.format(x) for x in sp.k_cmfd]) - outstr += '\ncmfd entropy\n' - outstr += '\n'.join(['{0:12.6E}'.format(x) for x in sp.cmfd_entropy]) - outstr += '\ncmfd balance\n' - outstr += '\n'.join(['{0:12.6E}'.format(x) for x in sp.cmfd_balance]) - outstr += '\ncmfd dominance ratio\n' - outstr += '\n'.join(['{0:10.3E}'.format(x) for x in sp.cmfd_dominance]) - outstr += '\ncmfd openmc source comparison\n' - outstr += '\n'.join(['{0:12.6E}'.format(x) for x in sp.cmfd_srccmp]) - outstr += '\ncmfd source\n' - cmfdsrc = np.reshape(sp.cmfd_src, np.product(sp.cmfd_indices), - order='F') - outstr += '\n'.join(['{0:12.6E}'.format(x) for x in cmfdsrc]) - outstr += '\n' + def execute_test(self): + """Don't call _run_openmc as OpenMC will be called through C API for + CMFD tests, and write CMFD results that were passsed as argument - return outstr + """ + try: + self._test_output_created() + results = self._get_results() + results += self._cmfdrun_results + self._write_results(results) + self._compare_results() + finally: + self._cleanup() + + def update_results(self): + """Don't call _run_openmc as OpenMC will be called through C API for + CMFD tests, and write CMFD results that were passsed as argument + + """ + try: + self._test_output_created() + results = self._get_results() + results += self._cmfdrun_results + self._write_results(results) + self._overwrite_results() + finally: + self._cleanup() + + def _cleanup(self): + """Delete output files for numpy matrices and flux vectors.""" + super()._cleanup() + output = ['loss.npz', 'loss.dat', 'prod.npz', 'prod.dat', + 'fluxvec.npy', 'fluxvec.dat'] + for f in output: + if os.path.exists(f): + os.remove(f) class ParticleRestartTestHarness(TestHarness): diff --git a/tests/unit_tests/test_settings.py b/tests/unit_tests/test_settings.py index 30ec0e683c..a78f074c99 100644 --- a/tests/unit_tests/test_settings.py +++ b/tests/unit_tests/test_settings.py @@ -21,7 +21,6 @@ def test_export_to_xml(run_in_tmpdir): s.confidence_intervals = True s.cross_sections = '/path/to/cross_sections.xml' s.ptables = True - s.run_cmfd = False s.seed = 17 s.survival_biasing = True s.cutoff = {'weight': 0.25, 'weight_avg': 0.5, 'energy': 1.0e-5}