From 6f4da065803008a18740e65218b116ee0a1114fa Mon Sep 17 00:00:00 2001 From: Adam Nelson Date: Thu, 22 Oct 2020 09:51:14 -0500 Subject: [PATCH] Added ability to handle ArbitraryMatrixXS types (i.e., we can now create (n,3n) energy transition matrices). This is in addition to the previous commit's incorporation of the same for vector MGXS with the ArbitraryXS type. Also gave the user the ability to request that a domain's macroscopic Chi values be used for all isotopes in a domain. This is important because some codes do not create macroscopic Chi's with flux-weighting of the group-wise fission source, they instead assume a constant group-wise flux. This leads to errors downstream with microscopic MGXS in codes like OpenMC's MG mode and even heavily used production codes like DIF3D. --- openmc/mgxs/library.py | 67 ++++++++++-- openmc/mgxs/mgxs.py | 225 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 263 insertions(+), 29 deletions(-) diff --git a/openmc/mgxs/library.py b/openmc/mgxs/library.py index e2ba0bc6a1..2b8b71e02d 100644 --- a/openmc/mgxs/library.py +++ b/openmc/mgxs/library.py @@ -612,8 +612,10 @@ class Library: ---------- domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh or Integral The material, cell, or universe object of interest (or its ID) - mgxs_type : {'total', 'transport', 'nu-transport', 'absorption', 'capture', 'fission', 'nu-fission', 'kappa-fission', 'scatter', 'nu-scatter', 'scatter matrix', 'nu-scatter matrix', 'multiplicity matrix', 'nu-fission matrix', chi', 'chi-prompt', 'inverse-velocity', 'prompt-nu-fission', 'prompt-nu-fission matrix', 'delayed-nu-fission', 'delayed-nu-fission matrix', 'chi-delayed', 'beta'} - The type of multi-group cross section object to return + mgxs_type : str + The type of multi-group cross section object to return; allowable + values are those MGXS to the Library and present in the + mgxs_types attribute. Returns ------- @@ -912,7 +914,7 @@ class Library: return pickle.load(f) def get_xsdata(self, domain, xsdata_name, nuclide='total', xs_type='macro', - subdomain=None): + subdomain=None, apply_domain_chi=False): """Generates an openmc.XSdata object describing a multi-group cross section dataset for writing to an openmc.MGXSLibrary object. @@ -939,6 +941,15 @@ class Library: mesh cell of interest in the openmc.RegularMesh object. Note: this parameter currently only supports subdomains within a mesh, and not the subdomains of a distribcell. + apply_domain_chi : bool + This parameter sets whether (True) or not (False) the + domain-averaged values of chi, chi-prompt, and chi-delayed are to + be applied to each of the nuclidic fission energy spectra of a + domain. In effect, if this is True, then every nuclide in the + domain receives the same flux-weighted Chi. This is useful for + downstream multigroup solvers that pre-compute a material-specific + chi before the transport solve provides group-wise fluxes. Defaults + to False. Returns ------- @@ -1046,18 +1057,30 @@ class Library: if 'chi' in self.mgxs_types: mymgxs = self.get_mgxs(domain, 'chi') - xsdata.set_chi_mgxs(mymgxs, xs_type=xs_type, nuclide=[nuclide], + if apply_domain_chi and nuclide != "total": + nuc = "sum" + else: + nuc = nuclide + xsdata.set_chi_mgxs(mymgxs, xs_type=xs_type, nuclide=[nuc], subdomain=subdomain) if 'chi-prompt' in self.mgxs_types: mymgxs = self.get_mgxs(domain, 'chi-prompt') + if apply_domain_chi and nuclide != "total": + nuc = "sum" + else: + nuc = nuclide xsdata.set_chi_prompt_mgxs(mymgxs, xs_type=xs_type, - nuclide=[nuclide], subdomain=subdomain) + nuclide=[nuc], subdomain=subdomain) if 'chi-delayed' in self.mgxs_types: mymgxs = self.get_mgxs(domain, 'chi-delayed') + if apply_domain_chi and nuclide != "total": + nuc = "sum" + else: + nuc = nuclide xsdata.set_chi_delayed_mgxs(mymgxs, xs_type=xs_type, - nuclide=[nuclide], subdomain=subdomain) + nuclide=[nuc], subdomain=subdomain) if 'nu-fission' in self.mgxs_types: mymgxs = self.get_mgxs(domain, 'nu-fission') @@ -1196,7 +1219,8 @@ class Library: return xsdata - def create_mg_library(self, xs_type='macro', xsdata_names=None): + def create_mg_library(self, xs_type='macro', xsdata_names=None, + apply_domain_chi=False): """Creates an openmc.MGXSLibrary object to contain the MGXS data for the Multi-Group mode of OpenMC. @@ -1213,6 +1237,15 @@ class Library: xsdata_names : Iterable of str List of names to apply to the "xsdata" entries in the resultant mgxs data file. Defaults to 'set1', 'set2', ... + apply_domain_chi : bool + This parameter sets whether (True) or not (False) the + domain-averaged values of chi, chi-prompt, and chi-delayed are to + be applied to each of the nuclidic fission energy spectra of a + domain. In effect, if this is True, then every nuclide in the + domain receives the same flux-weighted Chi. This is useful for + downstream multigroup solvers that pre-compute a material-specific + chi before the transport solve provides group-wise fluxes. Defaults + to False. Returns ------- @@ -1284,13 +1317,15 @@ class Library: xsdata_name = xsdata_names[i] xsdata = self.get_xsdata(domain, xsdata_name, - nuclide=nuclide, xs_type=xs_type) + nuclide=nuclide, xs_type=xs_type, + apply_domain_chi=apply_domain_chi) mgxs_file.add_xsdata(xsdata) return mgxs_file - def create_mg_mode(self, xsdata_names=None, bc=['reflective'] * 6): + def create_mg_mode(self, xsdata_names=None, bc=['reflective'] * 6, + apply_domain_chi=False): """Creates an openmc.MGXSLibrary object to contain the MGXS data for the Multi-Group mode of OpenMC as well as the associated openmc.Materials and openmc.Geometry objects. @@ -1314,6 +1349,15 @@ class Library: (if applying to a 3D mesh) provided in the following order: [x min, x max, y min, y max, z min, z max]. 2-D cells do not contain the z min and z max entries. + apply_domain_chi : bool + This parameter sets whether (True) or not (False) the + domain-averaged values of chi, chi-prompt, and chi-delayed are to + be applied to each of the nuclidic fission energy spectra of a + domain. In effect, if this is True, then every nuclide in the + domain receives the same flux-weighted Chi. This is useful for + downstream multigroup solvers that pre-compute a material-specific + chi before the transport solve provides group-wise fluxes. Defaults + to False. Returns ------- @@ -1354,7 +1398,8 @@ class Library: cv.check_length("domains", self.domains, 1, 1) # Get the MGXS File Data - mgxs_file = self.create_mg_library('macro', xsdata_names) + mgxs_file = self.create_mg_library('macro', xsdata_names, + apply_domain_chi=apply_domain_chi) # Now move on the creating the geometry and assigning materials if self.domain_type == 'mesh': @@ -1415,7 +1460,7 @@ class Library: if not isinstance(cell.fill, openmc.Material): warn('If the library domain includes a lattice or universe cell ' 'in conjunction with a consituent cell of that lattice/universe, ' - 'the multi-group simulation will fail') + 'the multi-group simulation will fail') if cell.id == domain.id: cell.fill = material diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index bbcfef576e..6cd3b4f4df 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -699,8 +699,13 @@ class MGXS: Parameters ---------- - mgxs_type : {'total', 'transport', 'nu-transport', 'absorption', 'capture', 'fission', 'nu-fission', 'kappa-fission', 'scatter', 'nu-scatter', 'scatter matrix', 'nu-scatter matrix', 'multiplicity matrix', 'nu-fission matrix', 'chi', 'chi-prompt', 'inverse-velocity', 'prompt-nu-fission', 'prompt-nu-fission matrix', 'current', 'diffusion-coefficient', 'nu-diffusion-coefficient', mt} - The type of multi-group cross section object to return + mgxs_type : str or Integral + The type of multi-group cross section object to return; valid + values are members of MGXS_TYPES, or the reaction types that are + the keys of REACTION_MT. Note that if a reaction type from + REACTION_MT is used, it can be appended with ' matrix' to obtain + a multigroup matrix (from incoming to outgoing energy groups) for + reactions with a neutron in an outgoing channel. domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} @@ -728,8 +733,6 @@ class MGXS: """ - cv.check_value('mgxs_type', mgxs_type, MGXS_TYPES) - if mgxs_type == 'total': mgxs = TotalXS(domain, domain_type, energy_groups) elif mgxs_type == 'transport': @@ -784,11 +787,28 @@ class MGXS: elif mgxs_type == 'nu-diffusion-coefficient': mgxs = DiffusionCoefficient(domain, domain_type, energy_groups, nu=True) else: - if mgxs_type in REACTION_MT.keys() + REACTION_MT.values(): + # Now parse the REACTION_MT options, or raise an error + if mgxs_type in REACTION_MT.keys(): # Then it is a reaction not covered by the above that is - # supported by the ArbitraryMTXS Class - mgxs = ArbitraryMTXS(mgxs_type, domain, domain_type, + # supported by the ArbitraryXS Class + mgxs = ArbitraryXS(mgxs_type, domain, domain_type, energy_groups) + elif mgxs_type.endswith("matrix"): + # Then we should see if it is a valid REACTION_MT type + # To do that, split it into words + split_mgxs_types = mgxs_type.split(" ") + rxn = split_mgxs_types[0] + + # Check for correctness + if rxn not in REACTION_MT.keys(): + # This is an invalid type so let the user know. + msg = 'Unable to set "{0}" to "{1}"'.format("mgxs_type", + mgxs_type) + \ + " as it is not an accepted MGXS type." + raise ValueError(msg) + + mgxs = ArbitraryMatrixXS(mgxs_type, domain, domain_type, + energy_groups) else: # This is an invalid type so let the user know. msg = 'Unable to set "{0}" to "{1}"'.format("mgxs_type", @@ -3862,35 +3882,38 @@ class ScatterXS(MGXS): self._valid_estimators = ['analog'] - -class ArbitraryMTXS(MGXS): +class ArbitraryXS(MGXS): r"""A multi-group cross section for an arbitrary reaction type. This class can be used for both OpenMC input generation and tally data post-processing to compute spatially-homogenized and energy-integrated - multi-group total cross sections for multi-group neutronics calculations. At - a minimum, one needs to set the :attr:`TotalXS.energy_groups` and - :attr:`TotalXS.domain` properties. Tallies for the flux and appropriate + multi-group total cross sections for multi-group neutronics calculations. + At a minimum, one needs to set the :attr:`ArbitraryXS.energy_groups` and + :attr:`ArbitraryXS.domain` properties. Tallies for the flux and appropriate reaction rates over the specified domain are generated automatically via the - :attr:`TotalXS.tallies` property, which can then be appended to a + :attr:`ArbitraryXS.tallies` property, which can then be appended to a :class:`openmc.Tallies` instance. For post-processing, the :meth:`MGXS.load_from_statepoint` will pull in the necessary data to compute multi-group cross sections from a :class:`openmc.StatePoint` instance. The derived multi-group cross section - can then be obtained from the :attr:`TotalXS.xs_tally` property. + can then be obtained from the :attr:`ArbitraryXS.xs_tally` property. For a spatial domain :math:`V` and energy group :math:`[E_g,E_{g-1}]`, the - total cross section is calculated as: + requested cross section is calculated as: .. math:: \frac{\int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; - \sigma_t (r, E) \psi (r, E, \Omega)}{\int_{r \in V} dr \int_{4\pi} - d\Omega \int_{E_g}^{E_{g-1}} dE \; \psi (r, E, \Omega)}. + \sigma_X (r, E) \psi (r, E, \Omega)}{\int_{r \in V} dr \int_{4\pi} + d\Omega \int_{E_g}^{E_{g-1}} dE \; \psi (r, E, \Omega)} + + where :math:`_\sigma_X` is the requested reaction type of interest. Parameters ---------- + rxn_type : str + Reaction type (e.g., '(n,2n)', '(n,Xt)', etc.) domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} @@ -3914,7 +3937,7 @@ class ArbitraryMTXS(MGXS): name : str, optional Name of the multi-group cross section rxn_type : str - Reaction type (e.g., 'total', 'nu-fission', etc.) + Reaction type (e.g., '(n,2n)', '(n,Xt)', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh @@ -3981,6 +4004,172 @@ class ArbitraryMTXS(MGXS): num_polar, num_azimuthal) self._rxn_type = rxn_type + +class ArbitraryMatrixXS(MatrixMGXS): + r"""A multi-group matrix cross section for an arbitrary reaction type. + + This class can be used for both OpenMC input generation and tally data + post-processing to compute spatially-homogenized and energy-integrated + multi-group cross sections for multi-group neutronics calculations. At a + minimum, one needs to set the :attr:`ArbitraryMatrixXS.energy_groups` and + :attr:`ArbitraryMatrixXS.domain` properties. Tallies for the flux and + appropriate reaction rates over the specified domain are generated + automatically via the :attr:`ArbitraryMatrixXS.tallies` property, which can + then be appended to a :class:`openmc.Tallies` instance. + + For post-processing, the :meth:`MGXS.load_from_statepoint` will pull in the + necessary data to compute multi-group cross sections from a + :class:`openmc.StatePoint` instance. The derived multi-group cross section + can then be obtained from the :attr:`ArbitraryMatrixXS.xs_tally` property. + + For a spatial domain :math:`V`, incoming energy group + :math:`[E_{g'},E_{g'-1}]`, and outgoing energy group :math:`[E_g,E_{g-1}]`, + the fission production is calculated as: + + .. math:: + + \begin{aligned} + \langle \sigma_{X,g'\rightarrow g} \phi \rangle &= \int_{r \in V} dr + \int_{4\pi} d\Omega' \int_{E_{g'}}^{E_{g'-1}} dE' \int_{E_g}^{E_{g-1}} dE + \; \chi(E) \sigma_X (r, E') \psi(r, E', \Omega')\\ + \langle \phi \rangle &= \int_{r \in V} dr \int_{4\pi} d\Omega + \int_{E_g}^{E_{g-1}} dE \; \psi (r, E, \Omega) \\ + \sigma_{X,g'\rightarrow g} &= \frac{\langle \sigma_{X,g'\rightarrow + g} \phi \rangle}{\langle \phi \rangle} + \end{aligned} + + where :math:`_\sigma_X` is the requested reaction type of interest. + + Parameters + ---------- + rxn_type : str + Reaction type (e.g., '(n,2n)', '(n,nta)', etc.). Valid names have + neutrons as a product. + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh + The domain for spatial homogenization + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} + The domain type for spatial homogenization + groups : openmc.mgxs.EnergyGroups + The energy group structure for energy condensation + by_nuclide : bool + If true, computes cross sections for each nuclide in domain + name : str, optional + Name of the multi-group cross section. Used as a label to identify + tallies in OpenMC 'tallies.xml' file. + num_polar : Integral, optional + Number of equi-width polar angle bins for angle discretization; + defaults to one bin + num_azimuthal : Integral, optional + Number of equi-width azimuthal angle bins for angle discretization; + defaults to one bin + prompt : bool + If true, computes cross sections which only includes prompt neutrons; + defaults to False which includes prompt and delayed in total + + Attributes + ---------- + name : str, optional + Name of the multi-group cross section + rxn_type : str + Reaction type (e.g., 'total', 'nu-fission', etc.) + prompt : bool + If true, computes cross sections which only includes prompt neutrons + by_nuclide : bool + If true, computes cross sections for each nuclide in domain + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh + Domain for spatial homogenization + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} + Domain type for spatial homogenization + energy_groups : openmc.mgxs.EnergyGroups + Energy group structure for energy condensation + num_polar : Integral + Number of equi-width polar angle bins for angle discretization + num_azimuthal : Integral + Number of equi-width azimuthal angle bins for angle discretization + tally_trigger : openmc.Trigger + An (optional) tally precision trigger given to each tally used to + compute the cross section + scores : list of str + The scores in each tally used to compute the multi-group cross section + filters : list of openmc.Filter + The filters in each tally used to compute the multi-group cross section + tally_keys : list of str + The keys into the tallies dictionary for each tally used to compute + the multi-group cross section + estimator : 'analog' + The tally estimator used to compute the multi-group cross section + tallies : collections.OrderedDict + OpenMC tallies needed to compute the multi-group cross section. The keys + are strings listed in the :attr:`NuFissionMatrixXS.tally_keys` + property and values are instances of :class:`openmc.Tally`. + rxn_rate_tally : openmc.Tally + Derived tally for the reaction rate tally used in the numerator to + compute the multi-group cross section. This attribute is None + unless the multi-group cross section has been computed. + xs_tally : openmc.Tally + Derived tally for the multi-group cross section. This attribute + is None unless the multi-group cross section has been computed. + num_subdomains : int + The number of subdomains is unity for 'material', 'cell' and 'universe' + domain types. This is equal to the number of cell instances + for 'distribcell' domain types (it is equal to unity prior to loading + tally data from a statepoint file). + num_nuclides : int + The number of nuclides for which the multi-group cross section is + being tracked. This is unity if the by_nuclide attribute is False. + nuclides : Iterable of str or 'sum' + The optional user-specified nuclides for which to compute cross + sections (e.g., 'U238', 'O16'). If by_nuclide is True but nuclides + are not specified by the user, all nuclides in the spatial domain + are included. This attribute is 'sum' if by_nuclide is false. + sparse : bool + Whether or not the MGXS' tallies use SciPy's LIL sparse matrix format + for compressed data storage + loaded_sp : bool + Whether or not a statepoint file has been loaded with tally data + derived : bool + Whether or not the MGXS is merged from one or more other MGXS + hdf5_key : str + The key used to index multi-group cross sections in an HDF5 data store + + """ + + def __init__(self, domain=None, domain_type=None, groups=None, + by_nuclide=False, name='', num_polar=1, + num_azimuthal=1, prompt=False): + super().__init__(domain, domain_type, groups, by_nuclide, name, + num_polar, num_azimuthal) + # Do that with the error variable just because PEP8 puts us in + # a hard place for multi-line if conditionals + # First we see if there are more words than there should be, + # then we make sure its a valid reaction, then we dont allow + # non (n,...) type reactions like 'heating-local' as they cant + # be matrices + split_rxn_type = rxn_type.split(" ") + error = len(split_rxn_type) > 2 or rxn not in REACTION_MT.keys() or \ + not rxn.startswith("(") + if error: + # This is an invalid type so let the user know. + msg = 'Unable to set "{0}" to "{1}"'.format("rxns_type", + rxn_type) + \ + " as it is not an accepted ArbitraryMatrixXS type." + raise ValueError(msg) + + # See if the rxn can be a matrix type; we've already made sure its + # an (X,Y) type of reaction, get the product (Y) + _, product = rxn.strip("()").split(',', maxsplit=2) + if "n" not in product: + msg = 'Unable to set "{0}" to "{1}"'.format("rxns_type", + rxn_type) + \ + " as it is not an accepted ArbitraryMatrixXS type." + raise ValueError(msg) + + # If we made it here, then we are good to go + self._rxn_type = rxn_type + self._estimator = 'analog' + self._valid_estimators = ['analog'] + + class ScatterMatrixXS(MatrixMGXS): r"""A scattering matrix multi-group cross section with the cosine of the change-in-angle represented as one or more Legendre moments or a histogram.