def set_density(self, units, density=NO_DENSITY): """Set the density of the material Parameters ---------- units : str Physical units of density density : float, optional Value of the density. Must be specified unless units is given as 'sum'. """ check_type('the density for Material ID="{0}"'.format(self._id), density, Real) check_value("density units", units, DENSITY_UNITS) if density == NO_DENSITY and units is not "sum": msg = ( 'Unable to set the density Material ID="{0}" ' "because a density must be set when not using " "sum unit".format(self._id) ) raise ValueError(msg) self._density = density self._density_units = units
def set_density(self, units, density=NO_DENSITY): """Set the density of the material Parameters ---------- units : str Physical units of density density : float, optional Value of the density. Must be specified unless units is given as 'sum'. """ check_type('the density for Material ID="{0}"'.format(self._id), density, Real) check_value('density units', units, DENSITY_UNITS) if density == NO_DENSITY and units is not 'sum': msg = 'Unable to set the density Material ID="{0}" ' \ 'because a density must be set when not using ' \ 'sum unit'.format(self._id) raise ValueError(msg) self._density = density self._density_units = units
def num_electrons(self, num_electrons): cv.check_type('number of electrons', num_electrons, Mapping) for subshell, num in num_electrons.items(): cv.check_value('subshell', subshell, _SUBSHELLS) cv.check_type('number of electrons', num, Real) cv.check_greater_than('number of electrons', num, 0.0, True) self._num_electrons = num_electrons
def binding_energy(self, binding_energy): cv.check_type('binding energies', binding_energy, Mapping) for subshell, energy in binding_energy.items(): cv.check_value('subshell', subshell, _SUBSHELLS) cv.check_type('binding energy', energy, Real) cv.check_greater_than('binding energy', energy, 0.0, True) self._binding_energy = binding_energy
def set_source_angle(self, stype, params=[]): """Defined the angular distribution of the external/starting source. Parameters ---------- stype : str The type of angular distribution. Valid options are "isotropic" and "monodirectional". The angle of the particle emitted from a source site is isotropic if the "isotropic" option is given. The angle of the particle emitted from a source site is the direction specified in ``params`` if the "monodirectional" option is given. params : Iterable of float For an "isotropic" angular distribution, ``params`` should not be specified. For a "monodirectional" angular distribution, ``params`` should be given as three floats which specify the angular cosines with respect to each axis. """ check_type("source angle type", stype, basestring) check_value("source angle type", stype, ["isotropic", "monodirectional"]) check_type("source angle parameters", params, Iterable, Real) if stype == "isotropic" and params is not None: msg = ( "Unable to set source angle parameters since they are not " "it is not supported for isotropic type sources" ) raise ValueError(msg) elif stype == "monodirectional": check_length("source angle parameters for a monodirectional " "source", params, 3) self._source_angle_type = stype self._source_angle_params = params
def set_source_space(self, stype, params): """Defined the spatial bounds of the external/starting source. Parameters ---------- stype : str The type of spatial distribution. Valid options are "box", "fission", and "point". A "box" spatial distribution has coordinates sampled uniformly in a parallelepiped. A "fission" spatial distribution samples locations from a "box" distribution but only locations in fissionable materials are accepted. A "point" spatial distribution has coordinates specified by a triplet. params : Iterable of float For a "box" or "fission" spatial distribution, ``params`` should be given as six real numbers, the first three of which specify the lower-left corner of a parallelepiped and the last three of which specify the upper-right corner. Source sites are sampled uniformly through that parallelepiped. For a "point" spatial distribution, ``params`` should be given as three real numbers which specify the (x,y,z) location of an isotropic point source """ check_type("source space type", stype, basestring) check_value("source space type", stype, ["box", "fission", "point"]) check_type("source space parameters", params, Iterable, Real) if stype in ["box", "fission"]: check_length("source space parameters for a " "box/fission distribution", params, 6) elif stype == "point": check_length("source space parameters for a point source", params, 3) self._source_space_type = stype self._source_space_params = params
def set_density(self, units, density=None): """Set the density of the material Parameters ---------- units : {'g/cm3', 'g/cc', 'kg/m3', 'atom/b-cm', 'atom/cm3', 'sum', 'macro'} Physical units of density. density : float, optional Value of the density. Must be specified unless units is given as 'sum'. """ cv.check_value('density units', units, DENSITY_UNITS) self._density_units = units if units == 'sum': if density is not None: msg = 'Density "{}" for Material ID="{}" is ignored ' \ 'because the unit is "sum"'.format(density, self.id) warnings.warn(msg) else: if density is None: msg = 'Unable to set the density for Material ID="{}" ' \ 'because a density value must be given when not using ' \ '"sum" unit'.format(self.id) raise ValueError(msg) cv.check_type('the density for Material ID="{}"'.format(self.id), density, Real) self._density = density
def add_nuclide(self, nuclide, percent, percent_type='ao'): """Add a nuclide to the material Parameters ---------- nuclide : str Nuclide to add, e.g., 'Mo95' percent : float Atom or weight percent percent_type : {'ao', 'wo'} 'ao' for atom percent and 'wo' for weight percent """ cv.check_type('nuclide', nuclide, str) cv.check_type('percent', percent, Real) cv.check_value('percent type', percent_type, {'ao', 'wo'}) if self._macroscopic is not None: msg = 'Unable to add a Nuclide to Material ID="{}" as a ' \ 'macroscopic data-set has already been added'.format(self._id) raise ValueError(msg) # If nuclide name doesn't look valid, give a warning try: Z, _, _ = openmc.data.zam(nuclide) except ValueError as e: warnings.warn(str(e)) else: # For actinides, have the material be depletable by default if Z >= 89: self.depletable = True self._nuclides.append(NuclideTuple(nuclide, percent, percent_type))
def set_density(self, units, density=None): """Set the density of the material Parameters ---------- units : {'g/cm3', 'g/cc', 'km/cm3', 'atom/b-cm', 'atom/cm3', 'sum', 'macro'} Physical units of density. density : float, optional Value of the density. Must be specified unless units is given as 'sum'. """ cv.check_value('density units', units, DENSITY_UNITS) self._density_units = units if units is 'sum': if density is not None: msg = 'Density "{0}" for Material ID="{1}" is ignored ' \ 'because the unit is "sum"'.format(density, self.id) warnings.warn(msg) else: if density is None: msg = 'Unable to set the density for Material ID="{0}" ' \ 'because a density value must be given when not using ' \ '"sum" unit'.format(self.id) raise ValueError(msg) cv.check_type('the density for Material ID="{0}"'.format(self.id), density, Real) self._density = density
def set_fission_mgxs(self, fission, nuclide='total', xs_type='macro'): """This method allows for an openmc.mgxs.FissionXS to be used to set the fission cross section for this XSdata object. Parameters ---------- fission: openmc.mgxs.FissionXS MGXS Object containing the fission cross section for the domain of interest. nuclide : str Individual nuclide (or 'total' if obtaining material-wise data) to gather data for. Defaults to 'total'. xs_type: {'macro', 'micro'} Provide the macro or micro cross section in units of cm^-1 or barns. Defaults to 'macro'. See also -------- openmc.mgxs.Library.create_mg_library() openmc.mgxs.Library.get_xsdata """ check_type('fission', fission, openmc.mgxs.FissionXS) check_value('energy_groups', fission.energy_groups, [self.energy_groups]) check_value('domain_type', fission.domain_type, ['universe', 'cell', 'material']) if self.representation is 'isotropic': self._fission = fission.get_xs(nuclides=nuclide, xs_type=xs_type) elif self.representation is 'angle': msg = 'Angular-Dependent MGXS have not yet been implemented' raise ValueError(msg)
def mgxs_types(self, mgxs_types): if mgxs_types == 'all': self._mgxs_types = openmc.mgxs.MGXS_TYPES else: cv.check_iterable_type('mgxs_types', mgxs_types, basestring) for mgxs_type in mgxs_types: cv.check_value('mgxs_type', mgxs_type, openmc.mgxs.MGXS_TYPES) self._mgxs_types = mgxs_types
def correction(self, correction): cv.check_value('correction', correction, ('P0', None)) if correction == 'P0' and self.legendre_order > 0: warn('The P0 correction will be ignored since the scattering ' 'order "{}" is greater than zero'.format(self.legendre_order)) self._correction = correction
def total(self, total): check_type('total', total, Iterable, expected_iter_type=Real) # Convert to a numpy array so we can easily get the shape for # checking nptotal = np.asarray(total) check_value('total shape', nptotal.shape, [self.vector_shape]) self._total = nptotal
def get_condensed_library(self, coarse_groups): """Construct an energy-condensed version of this library. This routine condenses each of the multi-group cross sections in the library to a coarse energy group structure. NOTE: This routine must be called after the load_from_statepoint(...) routine loads the tallies from the statepoint into each of the cross sections. Parameters ---------- coarse_groups : openmc.mgxs.EnergyGroups The coarse energy group structure of interest Returns ------- Library A new multi-group cross section library condensed to the group structure of interest Raises ------ ValueError When this method is called before a statepoint has been loaded See also -------- MGXS.get_condensed_xs(coarse_groups) """ if self.sp_filename is None: msg = 'Unable to get a condensed coarse group cross section ' \ 'library since the statepoint has not yet been loaded' raise ValueError(msg) cv.check_type('coarse_groups', coarse_groups, openmc.mgxs.EnergyGroups) cv.check_less_than('coarse groups', coarse_groups.num_groups, self.num_groups, equality=True) cv.check_value('upper coarse energy', coarse_groups.group_edges[-1], [self.energy_groups.group_edges[-1]]) cv.check_value('lower coarse energy', coarse_groups.group_edges[0], [self.energy_groups.group_edges[0]]) # Clone this Library to initialize the condensed version condensed_library = copy.deepcopy(self) condensed_library.energy_groups = coarse_groups # Condense the MGXS for each domain and mgxs type for domain in self.domains: for mgxs_type in self.mgxs_types: mgxs = condensed_library.get_mgxs(domain, mgxs_type) condensed_mgxs = mgxs.get_condensed_xs(coarse_groups) condensed_library.all_mgxs[ domain.id][mgxs_type] = condensed_mgxs return condensed_library
def scatter(self, scatter): # Convert to a numpy array so we can easily get the shape for # checking npscatter = np.asarray(scatter) check_iterable_type('scatter', npscatter, Real, max_depth=len(npscatter.shape)) check_value('scatter shape', npscatter.shape, [self.pn_matrix_shape]) self._scatter = npscatter
def absorption(self, absorption): check_type('absorption', absorption, Iterable, expected_iter_type=Real) # Convert to a numpy array so we can easily get the shape for # checking npabsorption = np.asarray(absorption) check_value('absorption shape', npabsorption.shape, [self.vector_shape]) self._absorption = npabsorption
def output(self, output): cv.check_type('output', output, Mapping) for key, value in output.items(): cv.check_value('output key', key, ('summary', 'tallies', 'path')) if key in ('summary', 'tallies'): cv.check_type("output['{}']".format(key), value, bool) else: cv.check_type("output['path']", value, string_types) self._output = output
def multiplicity(self, multiplicity): # Convert to a numpy array so we can easily get the shape for # checking npmultiplicity = np.asarray(multiplicity) check_iterable_type('multiplicity', npmultiplicity, Real, max_depth=len(npmultiplicity.shape)) check_value('multiplicity shape', npmultiplicity.shape, [self.matrix_shape]) self._multiplicity = npmultiplicity
def get_condensed_library(self, coarse_groups): """Construct an energy-condensed version of this library. This routine condenses each of the multi-group cross sections in the library to a coarse energy group structure. NOTE: This routine must be called after the load_from_statepoint(...) routine loads the tallies from the statepoint into each of the cross sections. Parameters ---------- coarse_groups : openmc.mgxs.EnergyGroups The coarse energy group structure of interest Returns ------- Library A new multi-group cross section library condensed to the group structure of interest Raises ------ ValueError When this method is called before a statepoint has been loaded See also -------- MGXS.get_condensed_xs(coarse_groups) """ if self.sp_filename is None: msg = 'Unable to get a condensed coarse group cross section ' \ 'library since the statepoint has not yet been loaded' raise ValueError(msg) cv.check_type('coarse_groups', coarse_groups, openmc.mgxs.EnergyGroups) cv.check_less_than('coarse groups', coarse_groups.num_groups, self.num_groups, equality=True) cv.check_value('upper coarse energy', coarse_groups.group_edges[-1], [self.energy_groups.group_edges[-1]]) cv.check_value('lower coarse energy', coarse_groups.group_edges[0], [self.energy_groups.group_edges[0]]) # Clone this Library to initialize the condensed version condensed_library = copy.deepcopy(self) condensed_library.energy_groups = coarse_groups # Condense the MGXS for each domain and mgxs type for domain in self.domains: for mgxs_type in self.mgxs_types: mgxs = condensed_library.get_mgxs(domain, mgxs_type) condensed_mgxs = mgxs.get_condensed_xs(coarse_groups) condensed_library.all_mgxs[domain.id][mgxs_type] = condensed_mgxs return condensed_library
def fission(self, fission): check_type('fission', fission, Iterable, expected_iter_type=Real) # Convert to a numpy array so we can easily get the shape for # checking npfission = np.asarray(fission) check_value('fission shape', npfission.shape, [self.vector_shape]) self._fission = npfission if np.sum(self._fission) > 0.0: self._fissionable = True
def tabular_legendre(self, tabular_legendre): cv.check_type('tabular_legendre settings', tabular_legendre, Mapping) for key, value in tabular_legendre.items(): cv.check_value('tabular_legendre key', key, ['enable', 'num_points']) if key == 'enable': cv.check_type('enable tabular_legendre', value, bool) elif key == 'num_points': cv.check_type('num_points tabular_legendre', value, Integral) cv.check_greater_than('num_points tabular_legendre', value, 0) self._tabular_legendre = tabular_legendre
def __init__(self, num, name, mat_names, groups, order, geom, rad, ref_k, params): cv.check_value('groups', groups, GROUPS) cv.check_value('order', order, ORDER) cv.check_value('groups', groups, GROUPS) cv.check_value('geom', geom, GEOM) self.number = num self.name = name self.mat_names = mat_names self.groups = groups self.order = order self.geom = geom self.rad = rad self.ref_k = ref_k self.mesh_dim = params['mesh_dim'] self.batches = params['batches'] self.inactive = params['inactive'] self.particles = params['particles'] if 'tab_leg' in params: self.tab_leg = params['tab_leg'] else: self.tab_leg = None if 'tally' in params: self.tally = params['tally'] else: self.tally = False
def temperature(self, temperature): cv.check_type('temperature settings', temperature, Mapping) for key, value in temperature.items(): cv.check_value('temperature key', key, ['default', 'method', 'tolerance', 'multipole']) if key == 'default': cv.check_type('default temperature', value, Real) elif key == 'method': cv.check_value('temperature method', value, ['nearest', 'interpolation']) elif key == 'tolerance': cv.check_type('temperature tolerance', value, Real) elif key == 'multipole': cv.check_type('temperature multipole', value, bool) self._temperature = temperature
def __init__(self, center_base, height, radius, axis='z', **kwargs): cx, cy, cz = center_base check_greater_than('cylinder height', height, 0.0) check_greater_than('cylinder radius', radius, 0.0) check_value('cylinder axis', axis, ('x', 'y', 'z')) if axis == 'x': self.cyl = openmc.XCylinder(y0=cy, z0=cz, r=radius, **kwargs) self.bottom = openmc.XPlane(x0=cx, **kwargs) self.top = openmc.XPlane(x0=cx + height, **kwargs) elif axis == 'y': self.cyl = openmc.YCylinder(x0=cx, z0=cz, r=radius, **kwargs) self.bottom = openmc.YPlane(y0=cy, **kwargs) self.top = openmc.YPlane(y0=cy + height, **kwargs) elif axis == 'z': self.cyl = openmc.ZCylinder(x0=cx, y0=cy, r=radius, **kwargs) self.bottom = openmc.ZPlane(z0=cz, **kwargs) self.top = openmc.ZPlane(z0=cz + height, **kwargs)
def deplete(self, timesteps, chain_file=None, method='cecm', fission_q=None, **kwargs): """Deplete model using specified timesteps/power Parameters ---------- timesteps : iterable of float Array of timesteps in units of [s]. Note that values are not cumulative. chain_file : str, optional Path to the depletion chain XML file. Defaults to the chain found under the ``depletion_chain`` in the :envvar:`OPENMC_CROSS_SECTIONS` environment variable if it exists. method : str Integration method used for depletion (e.g., 'cecm', 'predictor') fission_q : dict, optional Dictionary of nuclides and their fission Q values [eV]. If not given, values will be pulled from the ``chain_file``. **kwargs Keyword arguments passed to integration function (e.g., :func:`openmc.deplete.integrator.cecm`) """ # Import the depletion module. This is done here rather than the module # header to delay importing openmc.lib (through openmc.deplete) which # can be tough to install properly. import openmc.deplete as dep # Create OpenMC transport operator op = dep.Operator( self.geometry, self.settings, chain_file, fission_q=fission_q, ) # Perform depletion check_value('method', method, ('cecm', 'predictor', 'cf4', 'epc_rk4', 'si_celi', 'si_leqi', 'celi', 'leqi')) getattr(dep.integrator, method)(op, timesteps, **kwargs)
def from_geometry(cls, geometry, basis='xy', slice_coord=0.): """Return plot that encompasses a geometry. Parameters ---------- geometry : openmc.Geometry The geometry the base the plot off of basis : {'xy', 'xz', 'yz'} The basis directions for the plot slice_coord : float The level at which the slice plot should be plotted. For example, if the basis is 'xy', this would indicate the z value used in the origin. """ cv.check_type('geometry', geometry, openmc.Geometry) cv.check_value('basis', basis, _BASES) # Decide which axes to keep if basis == 'xy': pick_index = (0, 1) slice_index = 2 elif basis == 'yz': pick_index = (1, 2) slice_index = 0 elif basis == 'xz': pick_index = (0, 2) slice_index = 1 # Get lower-left and upper-right coordinates for desired axes lower_left, upper_right = geometry.bounding_box lower_left = lower_left[np.array(pick_index)] upper_right = upper_right[np.array(pick_index)] if np.any(np.isinf((lower_left, upper_right))): raise ValueError('The geometry does not appear to be bounded ' 'in the {} plane.'.format(basis)) plot = cls() plot.origin = np.insert((lower_left + upper_right)/2, slice_index, slice_coord) plot.width = upper_right - lower_left return plot
def set_chi_mgxs(self, chi, nuclide='total', xs_type='macro'): """This method allows for an openmc.mgxs.Chi to be used to set chi for this XSdata object. Parameters ---------- chi: openmc.mgxs.Chi MGXS Object containing chi for the domain of interest. nuclide : str Individual nuclide (or 'total' if obtaining material-wise data) to gather data for. Defaults to 'total'. xs_type: {'macro', 'micro'} Provide the macro or micro cross section in units of cm^-1 or barns. Defaults to 'macro'. See also -------- openmc.mgxs.Library.create_mg_library() openmc.mgxs.Library.get_xsdata """ if self.use_chi is not None: if not self.use_chi: msg = 'Providing chi when nu_fission already provided as a ' \ 'matrix!' raise ValueError(msg) check_type('chi', chi, openmc.mgxs.Chi) check_value('energy_groups', chi.energy_groups, [self.energy_groups]) check_value('domain_type', chi.domain_type, ['universe', 'cell', 'material']) if self.representation is 'isotropic': self._chi = chi.get_xs(nuclides=nuclide, xs_type=xs_type) elif self.representation is 'angle': msg = 'Angular-Dependent MGXS have not yet been implemented' raise ValueError(msg) if self.use_chi is not None: self.use_chi = True
def set_scatter_mgxs(self, scatter, nuclide='total', xs_type='macro'): """This method allows for an openmc.mgxs.ScatterMatrixXS to be used to set the scatter matrix cross section for this XSdata object. If the XSdata.order attribute has not yet been set, then it will be set based on the properties of scatter. Parameters ---------- scatter: openmc.mgxs.ScatterMatrixXS MGXS Object containing the scatter matrix cross section for the domain of interest. nuclide : str Individual nuclide (or 'total' if obtaining material-wise data) to gather data for. Defaults to 'total'. xs_type: {'macro', 'micro'} Provide the macro or micro cross section in units of cm^-1 or barns. Defaults to 'macro'. See also -------- openmc.mgxs.Library.create_mg_library() openmc.mgxs.Library.get_xsdata """ check_type('scatter', scatter, openmc.mgxs.ScatterMatrixXS) check_value('energy_groups', scatter.energy_groups, [self.energy_groups]) check_value('domain_type', scatter.domain_type, ['universe', 'cell', 'material']) if self.scatt_type != 'legendre': msg = 'Anisotropic scattering representations other than ' \ 'Legendre expansions have not yet been implemented in ' \ 'openmc.mgxs.' raise ValueError(msg) # If the user has not defined XSdata.order, then we will set # the order based on the data within scatter. # Otherwise, we will check to see that XSdata.order to match # the order of scatter if self.order is None: self.order = scatter.legendre_order else: check_value('legendre_order', scatter.legendre_order, [self.order]) if self.representation is 'isotropic': # Get the scattering orders in the outermost dimension self._scatter = np.zeros((self.num_orders, self.energy_groups.num_groups, self.energy_groups.num_groups)) for moment in range(self.num_orders): self._scatter[moment, :, :] = scatter.get_xs(nuclides=nuclide, xs_type=xs_type, moment=moment) elif self.representation is 'angle': msg = 'Angular-Dependent MGXS have not yet been implemented' raise ValueError(msg)
def set_source_energy(self, stype, params=[]): """Defined the energy distribution of the external/starting source. Parameters ---------- stype : str The type of energy distribution. Valid options are "monoenergetic", "watt", and "maxwell". The "monoenergetic" option produces source sites at a single energy. The "watt" option produces source sites whose energy is sampled from a Watt fission spectrum. The "maxwell" option produce source sites whose energy is sampled from a Maxwell fission spectrum. params : Iterable of float For a "monoenergetic" energy distribution, ``params`` should be given as the energy in MeV of the source sites. For a "watt" energy distribution, ``params`` should be given as two real numbers :math:`a` and :math:`b` that parameterize the distribution :math:`p(E) dE = c e^{-E/a} \sinh \sqrt{b \, E} dE`. For a "maxwell" energy distribution, ``params`` should be given as one real number :math:`a` that parameterizes the distribution :math:`p(E) dE = c E e^{-E/a} dE`. """ check_type('source energy type', stype, basestring) check_value('source energy type', stype, ['monoenergetic', 'watt', 'maxwell']) check_type('source energy parameters', params, Iterable, Real) if stype in ['monoenergetic', 'maxwell']: check_length( 'source energy parameters for a monoenergetic ' 'or Maxwell source', params, 1) elif stype == 'watt': check_length('source energy parameters for a Watt source', params, 2) self._source_energy_type = stype self._source_energy_params = params
def nu_fission(self, nu_fission): # The NuFissionXS class does not have the capability to produce # a fission matrix and therefore if this path is pursued, we know # chi must be used. # nu_fission can be given as a vector or a matrix # Vector is used when chi also exists. # Matrix is used when chi does not exist. # We have to check that the correct form is given, but only if # chi already has been set. If not, we just check that this is OK # and set the use_chi flag accordingly # Convert to a numpy array so we can easily get the shape for # checking npnu_fission = np.asarray(nu_fission) check_iterable_type('nu_fission', npnu_fission, Real, max_depth=len(npnu_fission.shape)) if self.use_chi is not None: if self.use_chi: check_value('nu_fission shape', npnu_fission.shape, [self.vector_shape]) else: check_value('nu_fission shape', npnu_fission.shape, [self.matrix_shape]) else: check_value('nu_fission shape', npnu_fission.shape, [self.vector_shape, self.matrix_shape]) # Find out if we have a nu-fission matrix or vector # and set a flag to allow other methods to check this later. self.use_chi = (npnu_fission.shape == self.vector_shape) self._nu_fission = npnu_fission if np.sum(self._nu_fission) > 0.0: self._fissionable = True
def set_source_energy(self, stype, params=[]): """Defined the energy distribution of the external/starting source. Parameters ---------- stype : str The type of energy distribution. Valid options are "monoenergetic", "watt", and "maxwell". The "monoenergetic" option produces source sites at a single energy. The "watt" option produces source sites whose energy is sampled from a Watt fission spectrum. The "maxwell" option produce source sites whose energy is sampled from a Maxwell fission spectrum. params : Iterable of float For a "monoenergetic" energy distribution, ``params`` should be given as the energy in MeV of the source sites. For a "watt" energy distribution, ``params`` should be given as two real numbers :math:`a` and :math:`b` that parameterize the distribution :math:`p(E) dE = c e^{-E/a} \sinh \sqrt{b \, E} dE`. For a "maxwell" energy distribution, ``params`` should be given as one real number :math:`a` that parameterizes the distribution :math:`p(E) dE = c E e^{-E/a} dE`. """ check_type('source energy type', stype, basestring) check_value('source energy type', stype, ['monoenergetic', 'watt', 'maxwell']) check_type('source energy parameters', params, Iterable, Real) if stype in ['monoenergetic', 'maxwell']: check_length('source energy parameters for a monoenergetic ' 'or Maxwell source', params, 1) elif stype == 'watt': check_length('source energy parameters for a Watt source', params, 2) self._source_energy_type = stype self._source_energy_params = params
def resonance_scattering(self, res): cv.check_type('resonance scattering settings', res, Mapping) keys = ('enable', 'method', 'energy_min', 'energy_max', 'nuclides') for key, value in res.items(): cv.check_value('resonance scattering dictionary key', key, keys) if key == 'enable': cv.check_type('resonance scattering enable', value, bool) elif key == 'method': cv.check_value('resonance scattering method', value, _RES_SCAT_METHODS) elif key == 'energy_min': name = 'resonance scattering minimum energy' cv.check_type(name, value, Real) cv.check_greater_than(name, value, 0) elif key == 'energy_max': name = 'resonance scattering minimum energy' cv.check_type(name, value, Real) cv.check_greater_than(name, value, 0) elif key == 'nuclides': cv.check_type('resonance scattering nuclides', value, Iterable, string_types) self._resonance_scattering = res
def set_source_space(self, stype, params): """Defined the spatial bounds of the external/starting source. Parameters ---------- stype : str The type of spatial distribution. Valid options are "box", "fission", and "point". A "box" spatial distribution has coordinates sampled uniformly in a parallelepiped. A "fission" spatial distribution samples locations from a "box" distribution but only locations in fissionable materials are accepted. A "point" spatial distribution has coordinates specified by a triplet. params : Iterable of float For a "box" or "fission" spatial distribution, ``params`` should be given as six real numbers, the first three of which specify the lower-left corner of a parallelepiped and the last three of which specify the upper-right corner. Source sites are sampled uniformly through that parallelepiped. For a "point" spatial distribution, ``params`` should be given as three real numbers which specify the (x,y,z) location of an isotropic point source """ check_type('source space type', stype, basestring) check_value('source space type', stype, ['box', 'fission', 'point']) check_type('source space parameters', params, Iterable, Real) if stype in ['box', 'fission']: check_length( 'source space parameters for a ' 'box/fission distribution', params, 6) elif stype == 'point': check_length('source space parameters for a point source', params, 3) self._source_space_type = stype self._source_space_params = params
def set_source_angle(self, stype, params=[]): """Defined the angular distribution of the external/starting source. Parameters ---------- stype : str The type of angular distribution. Valid options are "isotropic" and "monodirectional". The angle of the particle emitted from a source site is isotropic if the "isotropic" option is given. The angle of the particle emitted from a source site is the direction specified in ``params`` if the "monodirectional" option is given. params : Iterable of float For an "isotropic" angular distribution, ``params`` should not be specified. For a "monodirectional" angular distribution, ``params`` should be given as three floats which specify the angular cosines with respect to each axis. """ check_type('source angle type', stype, basestring) check_value('source angle type', stype, ['isotropic', 'monodirectional']) check_type('source angle parameters', params, Iterable, Real) if stype == 'isotropic' and params is not None: msg = 'Unable to set source angle parameters since they are not ' \ 'it is not supported for isotropic type sources' raise ValueError(msg) elif stype == 'monodirectional': check_length( 'source angle parameters for a monodirectional ' 'source', params, 3) self._source_angle_type = stype self._source_angle_params = params
def mix_materials(cls, materials, fracs, percent_type='ao', name=None): """Mix materials together based on atom, weight, or volume fractions .. versionadded:: 0.12 Parameters ---------- materials : Iterable of openmc.Material Materials to combine fracs : Iterable of float Fractions of each material to be combined percent_type : {'ao', 'wo', 'vo'} Type of percentage, must be one of 'ao', 'wo', or 'vo', to signify atom percent (molar percent), weight percent, or volume percent, optional. Defaults to 'ao' name : str The name for the new material, optional. Defaults to concatenated names of input materials with percentages indicated inside parentheses. Returns ------- openmc.Material Mixture of the materials """ cv.check_type('materials', materials, Iterable, Material) cv.check_type('fracs', fracs, Iterable, Real) cv.check_value('percent type', percent_type, {'ao', 'wo', 'vo'}) fracs = np.asarray(fracs) void_frac = 1. - np.sum(fracs) # Warn that fractions don't add to 1, set remainder to void, or raise # an error if percent_type isn't 'vo' if not np.isclose(void_frac, 0.): if percent_type in ('ao', 'wo'): msg = ('A non-zero void fraction is not acceptable for ' 'percent_type: {}'.format(percent_type)) raise ValueError(msg) else: msg = ('Warning: sum of fractions do not add to 1, void ' 'fraction set to {}'.format(void_frac)) warnings.warn(msg) # Calculate appropriate weights which are how many cc's of each # material are found in 1cc of the composite material amms = np.asarray([mat.average_molar_mass for mat in materials]) mass_dens = np.asarray([mat.get_mass_density() for mat in materials]) if percent_type == 'ao': wgts = fracs * amms / mass_dens wgts /= np.sum(wgts) elif percent_type == 'wo': wgts = fracs / mass_dens wgts /= np.sum(wgts) elif percent_type == 'vo': wgts = fracs # If any of the involved materials contain S(a,b) tables raise an error sab_names = set(sab[0] for mat in materials for sab in mat._sab) if sab_names: msg = ('Currently we do not support mixing materials containing ' 'S(a,b) tables') raise NotImplementedError(msg) # Add nuclide densities weighted by appropriate fractions nuclides_per_cc = defaultdict(float) mass_per_cc = defaultdict(float) for mat, wgt in zip(materials, wgts): for nuc, atoms_per_bcm in mat.get_nuclide_atom_densities().values( ): nuc_per_cc = wgt * 1.e24 * atoms_per_bcm nuclides_per_cc[nuc] += nuc_per_cc mass_per_cc[nuc] += nuc_per_cc*openmc.data.atomic_mass(nuc) / \ openmc.data.AVOGADRO # Create the new material with the desired name if name is None: name = '-'.join( ['{}({})'.format(m.name, f) for m, f in zip(materials, fracs)]) new_mat = openmc.Material(name=name) # Compute atom fractions of nuclides and add them to the new material tot_nuclides_per_cc = np.sum( [dens for dens in nuclides_per_cc.values()]) for nuc, atom_dens in nuclides_per_cc.items(): new_mat.add_nuclide(nuc, atom_dens / tot_nuclides_per_cc, 'ao') # Compute mass density for the new material and set it new_density = np.sum([dens for dens in mass_per_cc.values()]) new_mat.set_density('g/cm3', new_density) # If any of the involved materials is depletable, the new material is # depletable new_mat.depletable = any(mat.depletable for mat in materials) return new_mat
def get_atoms(self, mat, nuc, nuc_units="atoms", time_units="s"): """Get number of nuclides over time from a single material .. note:: Initial values for some isotopes that do not appear in initial concentrations may be non-zero, depending on the value of :class:`openmc.deplete.Operator` ``dilute_initial``. The :class:`openmc.deplete.Operator` adds isotopes according to this setting, which can be set to zero. Parameters ---------- mat : str Material name to evaluate nuc : str Nuclide name to evaluate nuc_units : {"atoms", "atom/b-cm", "atom/cm3"}, optional Units for the returned concentration. Default is ``"atoms"`` .. versionadded:: 0.12 time_units : {"s", "min", "h", "d"}, optional Units for the returned time array. Default is ``"s"`` to return the value in seconds. .. versionadded:: 0.12 Returns ------- times : numpy.ndarray Array of times in units of ``time_units`` concentrations : numpy.ndarray Concentration of specified nuclide in units of ``nuc_units`` """ cv.check_value("time_units", time_units, {"s", "d", "min", "h"}) cv.check_value("nuc_units", nuc_units, {"atoms", "atom/b-cm", "atom/cm3"}) times = np.empty_like(self, dtype=float) concentrations = np.empty_like(self, dtype=float) # Evaluate value in each region for i, result in enumerate(self): times[i] = result.time[0] concentrations[i] = result[0, mat, nuc] # Unit conversions if time_units == "d": times /= (60 * 60 * 24) elif time_units == "h": times /= (60 * 60) elif time_units == "min": times /= 60 if nuc_units != "atoms": # Divide by volume to get density concentrations /= self[0].volume[mat] if nuc_units == "atom/b-cm": # 1 barn = 1e-24 cm^2 concentrations *= 1e-24 return times, concentrations
def add_element(self, element, percent, percent_type='ao', enrichment=None): """Add a natural element to the material Parameters ---------- element : str Element to add, e.g., 'Zr' percent : float Atom or weight percent percent_type : {'ao', 'wo'}, optional 'ao' for atom percent and 'wo' for weight percent. Defaults to atom percent. enrichment : float, optional Enrichment for U235 in weight percent. For example, input 4.95 for 4.95 weight percent enriched U. Default is None (natural composition). """ cv.check_type('nuclide', element, str) cv.check_type('percent', percent, Real) cv.check_value('percent type', percent_type, {'ao', 'wo'}) if self._macroscopic is not None: msg = 'Unable to add an Element to Material ID="{}" as a ' \ 'macroscopic data-set has already been added'.format(self._id) raise ValueError(msg) if enrichment is not None: if not isinstance(enrichment, Real): msg = 'Unable to add an Element to Material ID="{}" with a ' \ 'non-floating point enrichment value "{}"'\ .format(self._id, enrichment) raise ValueError(msg) elif element != 'U': msg = 'Unable to use enrichment for element {} which is not ' \ 'uranium for Material ID="{}"'.format(element, self._id) raise ValueError(msg) # Check that the enrichment is in the valid range cv.check_less_than('enrichment', enrichment, 100. / 1.008) cv.check_greater_than('enrichment', enrichment, 0., equality=True) if enrichment > 5.0: msg = 'A uranium enrichment of {} was given for Material ID='\ '"{}". OpenMC assumes the U234/U235 mass ratio is '\ 'constant at 0.008, which is only valid at low ' \ 'enrichments. Consider setting the isotopic ' \ 'composition manually for enrichments over 5%.'.\ format(enrichment, self._id) warnings.warn(msg) # Make sure element name is just that if not element.isalpha(): raise ValueError("Element name should be given by the " "element's symbol, e.g., 'Zr'") # Add naturally-occuring isotopes element = openmc.Element(element) for nuclide in element.expand(percent, percent_type, enrichment): self.add_nuclide(*nuclide)
def type(self, meshtype): cv.check_type('type for mesh ID="{0}"'.format(self._id), meshtype, basestring) cv.check_value('type for mesh ID="{0}"'.format(self._id), meshtype, ['regular']) self._type = meshtype
def type(self, meshtype): cv.check_type('type for mesh ID="{0}"'.format(self._id), meshtype, string_types) cv.check_value('type for mesh ID="{0}"'.format(self._id), meshtype, ['regular']) self._type = meshtype
def build_cells(self, bc=['reflective'] * 6): """Generates a lattice of universes with the same dimensionality as the mesh object. The individual cells/universes produced will not have material definitions applied and so downstream code will have to apply that information. Parameters ---------- bc : iterable of {'reflective', 'periodic', 'transmission', or 'vacuum'} Boundary conditions for each of the four faces of a rectangle (if aplying to a 2D mesh) or six faces of a parallelepiped (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. Returns ------- root_cell : openmc.Cell The cell containing the lattice representing the mesh geometry; this cell is a single parallelepiped with boundaries matching the outermost mesh boundary with the boundary conditions from bc applied. cells : iterable of openmc.Cell The list of cells within each lattice position mimicking the mesh geometry. """ cv.check_length('bc', bc, length_min=4, length_max=6) for entry in bc: cv.check_value( 'bc', entry, ['transmission', 'vacuum', 'reflective', 'periodic']) # Build the cell which will contain the lattice xplanes = [ openmc.XPlane(x0=self.lower_left[0], boundary_type=bc[0]), openmc.XPlane(x0=self.upper_right[0], boundary_type=bc[1]) ] if len(self.dimension) == 1: yplanes = [ openmc.YPlane(y0=-1e10, boundary_type='reflective'), openmc.YPlane(y0=1e10, boundary_type='reflective') ] else: yplanes = [ openmc.YPlane(y0=self.lower_left[1], boundary_type=bc[2]), openmc.YPlane(y0=self.upper_right[1], boundary_type=bc[3]) ] if len(self.dimension) <= 2: # Would prefer to have the z ranges be the max supported float, but # these values are apparently different between python and Fortran. # Choosing a safe and sane default. # Values of +/-1e10 are used here as there seems to be an # inconsistency between what numpy uses as the max float and what # Fortran expects for a real(8), so this avoids code complication # and achieves the same goal. zplanes = [ openmc.ZPlane(z0=-1e10, boundary_type='reflective'), openmc.ZPlane(z0=1e10, boundary_type='reflective') ] else: zplanes = [ openmc.ZPlane(z0=self.lower_left[2], boundary_type=bc[4]), openmc.ZPlane(z0=self.upper_right[2], boundary_type=bc[5]) ] root_cell = openmc.Cell() root_cell.region = ((+xplanes[0] & -xplanes[1]) & (+yplanes[0] & -yplanes[1]) & (+zplanes[0] & -zplanes[1])) # Build the universes which will be used for each of the [i,j,k] # locations within the mesh. # We will concurrently build cells to assign to these universes cells = [] universes = [] for [i, j, k] in self.cell_generator(): cells.append(openmc.Cell()) universes.append(openmc.Universe()) universes[-1].add_cell(cells[-1]) lattice = openmc.RectLattice() lattice.lower_left = self.lower_left # Assign the universe and rotate to match the indexing expected for # the lattice lattice.universes = np.rot90(np.reshape(universes, self.dimension)) if self.width is not None: lattice.pitch = self.width else: dx = ((self.upper_right[0] - self.lower_left[0]) / self.dimension[0]) if len(self.dimension) == 1: lattice.pitch = [dx] elif len(self.dimension) == 2: dy = ((self.upper_right[1] - self.lower_left[1]) / self.dimension[1]) lattice.pitch = [dx, dy] else: dy = ((self.upper_right[1] - self.lower_left[1]) / self.dimension[1]) dz = ((self.upper_right[2] - self.lower_left[2]) / self.dimension[2]) lattice.pitch = [dx, dy, dz] # Fill Cell with the Lattice root_cell.fill = lattice return root_cell, cells
def display(self, display): check_type('CMFD display', display, string_types) check_value('CMFD display', display, ['balance', 'dominance', 'entropy', 'source']) self._display = display
def formalism(self, formalism): if formalism is not None: cv.check_type('formalism', formalism, string_types) cv.check_value('formalism', formalism, ('MLBW', 'RM')) self._formalism = formalism
def map(self, meshmap): check_type('CMFD mesh map', meshmap, Iterable, Integral) for m in meshmap: check_value('CMFD mesh map', m, [1, 2]) self._map = meshmap
def variable(self, var): if var is not None: cv.check_type('derivative variable', var, string_types) cv.check_value('derivative variable', var, ('density', 'nuclide_density', 'temperature')) self._variable = var
def color_by(self, color_by): cv.check_value('plot color_by', color_by, ['cell', 'material']) self._color_by = color_by
def type(self, plottype): cv.check_value('plot type', plottype, ['slice', 'voxel']) self._type = plottype
def basis(self, basis): cv.check_value('plot basis', basis, _BASES) self._basis = basis
def add_element(self, element, percent, percent_type='ao', enrichment=None, enrichment_target=None, enrichment_type=None): """Add a natural element to the material Parameters ---------- element : str Element to add, e.g., 'Zr' or 'Zirconium' percent : float Atom or weight percent percent_type : {'ao', 'wo'}, optional 'ao' for atom percent and 'wo' for weight percent. Defaults to atom percent. enrichment : float, optional Enrichment of an enrichment_taget nuclide in percent (ao or wo). If enrichment_taget is not supplied then it is enrichment for U235 in weight percent. For example, input 4.95 for 4.95 weight percent enriched U. Default is None (natural composition). enrichment_target: str, optional Single nuclide name to enrich from a natural composition (e.g., 'O16') .. versionadded:: 0.12 enrichment_type: {'ao', 'wo'}, optional 'ao' for enrichment as atom percent and 'wo' for weight percent. Default is: 'ao' for two-isotope enrichment; 'wo' for U enrichment .. versionadded:: 0.12 Notes ----- General enrichment procedure is allowed only for elements composed of two isotopes. If `enrichment_target` is given without `enrichment` natural composition is added to the material. """ cv.check_type('nuclide', element, str) cv.check_type('percent', percent, Real) cv.check_value('percent type', percent_type, {'ao', 'wo'}) # Make sure element name is just that if not element.isalpha(): raise ValueError( "Element name should be given by the " "element's symbol or name, e.g., 'Zr', 'zirconium'") # Allow for element identifier to be given as a symbol or name if len(element) > 2: el = element.lower() element = openmc.data.ELEMENT_SYMBOL.get(el) if element is None: msg = 'Element name "{}" not recognised'.format(el) raise ValueError(msg) else: if element[0].islower(): msg = 'Element name "{}" should start with an uppercase ' \ 'letter'.format(element) raise ValueError(msg) if len(element) == 2 and element[1].isupper(): msg = 'Element name "{}" should end with a lowercase ' \ 'letter'.format(element) raise ValueError(msg) # skips the first entry of ATOMIC_SYMBOL which is n for neutron if element not in list(openmc.data.ATOMIC_SYMBOL.values())[1:]: msg = 'Element name "{}" not recognised'.format(element) raise ValueError(msg) if self._macroscopic is not None: msg = 'Unable to add an Element to Material ID="{}" as a ' \ 'macroscopic data-set has already been added'.format(self._id) raise ValueError(msg) if enrichment is not None and enrichment_target is None: if not isinstance(enrichment, Real): msg = 'Unable to add an Element to Material ID="{}" with a ' \ 'non-floating point enrichment value "{}"'\ .format(self._id, enrichment) raise ValueError(msg) elif element != 'U': msg = 'Unable to use enrichment for element {} which is not ' \ 'uranium for Material ID="{}"'.format(element, self._id) raise ValueError(msg) # Check that the enrichment is in the valid range cv.check_less_than('enrichment', enrichment, 100. / 1.008) cv.check_greater_than('enrichment', enrichment, 0., equality=True) if enrichment > 5.0: msg = 'A uranium enrichment of {} was given for Material ID='\ '"{}". OpenMC assumes the U234/U235 mass ratio is '\ 'constant at 0.008, which is only valid at low ' \ 'enrichments. Consider setting the isotopic ' \ 'composition manually for enrichments over 5%.'.\ format(enrichment, self._id) warnings.warn(msg) # Add naturally-occuring isotopes element = openmc.Element(element) for nuclide in element.expand(percent, percent_type, enrichment, enrichment_target, enrichment_type): self.add_nuclide(*nuclide)
def interpolation(self, interpolation): cv.check_value('interpolation', interpolation, [2, 5]) self._interpolation = interpolation
def get_metadata(zaid, metastable_scheme='nndc'): """Return basic identifying data for a nuclide with a given ZAID. Parameters ---------- zaid : int ZAID (1000*Z + A) obtained from a library metastable_scheme : {'nndc', 'mcnp'} Determine how ZAID identifiers are to be interpreted in the case of a metastable nuclide. Because the normal ZAID (=1000*Z + A) does not encode metastable information, different conventions are used among different libraries. In MCNP libraries, the convention is to add 400 for a metastable nuclide except for Am242m, for which 95242 is metastable and 95642 (or 1095242 in newer libraries) is the ground state. For NNDC libraries, ZAID is given as 1000*Z + A + 100*m. Returns ------- name : str Name of the table element : str The atomic symbol of the isotope in the table; e.g., Zr. Z : int Number of protons in the nucleus mass_number : int Number of nucleons in the nucleus metastable : int Metastable state of the nucleus. A value of zero indicates ground state. """ cv.check_type('zaid', zaid, int) cv.check_value('metastable_scheme', metastable_scheme, ['nndc', 'mcnp']) Z = zaid // 1000 mass_number = zaid % 1000 if metastable_scheme == 'mcnp': if zaid > 1000000: # New SZA format Z = Z % 1000 if zaid == 1095242: metastable = 0 else: metastable = zaid // 1000000 else: if zaid == 95242: metastable = 1 elif zaid == 95642: metastable = 0 else: metastable = 1 if mass_number > 300 else 0 elif metastable_scheme == 'nndc': metastable = 1 if mass_number > 300 else 0 while mass_number > 3 * Z: mass_number -= 100 # Determine name element = ATOMIC_SYMBOL[Z] name = gnd_name(Z, mass_number, metastable) return (name, element, Z, mass_number, metastable)
def interpolation(self, interpolation): cv.check_value('interpolation', interpolation, _INTERPOLATION_SCHEMES) self._interpolation = interpolation