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.