Пример #1
0
 def background(self, background):
     cv.check_type('plot background', background, Iterable, Integral)
     cv.check_length('plot background', background, 3)
     for rgb in background:
         cv.check_greater_than('plot background', rgb, 0, True)
         cv.check_less_than('plot background', rgb, 256)
     self._background = background
Пример #2
0
 def mask_background(self, mask_background):
     check_type('plot mask background', mask_background, Iterable, Integral)
     check_length('plot mask background', mask_background, 3)
     for rgb in mask_background:
         check_greater_than('plot mask background', rgb, 0, True)
         check_less_than('plot mask background', rgb, 256)
     self._mask_background = mask_background
Пример #3
0
 def num_l(self, num_l):
     if num_l is not None:
         cv.check_type('num_l', num_l, Integral)
         cv.check_greater_than('num_l', num_l, 1, equality=True)
         cv.check_less_than('num_l', num_l, 4, equality=True)
         # There is an if block in _evaluate that assumes num_l <= 4.
     self._num_l = num_l
Пример #4
0
 def num_l(self, num_l):
     if num_l is not None:
         cv.check_type('num_l', num_l, Integral)
         cv.check_greater_than('num_l', num_l, 1, equality=True)
         cv.check_less_than('num_l', num_l, 4, equality=True)
         # There is an if block in _evaluate that assumes num_l <= 4.
     self._num_l = num_l
Пример #5
0
 def mask_background(self, mask_background):
     cv.check_type('plot mask background', mask_background, Iterable, Integral)
     cv.check_length('plot mask background', mask_background, 3)
     for rgb in mask_background:
         cv.check_greater_than('plot mask background', rgb, 0, True)
         cv.check_less_than('plot mask background', rgb, 256)
     self._mask_background = mask_background
Пример #6
0
 def albedo(self, albedo):
     check_type('CMFD mesh albedo', albedo, Iterable, Real)
     check_length('CMFD mesh albedo', albedo, 6)
     for a in albedo:
         check_greater_than('CMFD mesh albedo', a, 0, True)
         check_less_than('CMFD mesh albedo', a, 1, True)
     self._albedo = albedo
Пример #7
0
 def background(self, background):
     cv.check_type('plot background', background, Iterable, Integral)
     cv.check_length('plot background', background, 3)
     for rgb in background:
         cv.check_greater_than('plot background',rgb, 0, True)
         cv.check_less_than('plot background', rgb, 256)
     self._background = background
Пример #8
0
    def get_group_bounds(self, group):
        """Returns the energy boundaries for the energy group of interest.

        Parameters
        ----------
        group : int
            The energy group index, starting at 1 for the highest energies

        Returns
        -------
        2-tuple
            The low and high energy bounds for the group in eV

        Raises
        ------
        ValueError
            If the group edges have not yet been set.

        """

        if self.group_edges is None:
            msg = 'Unable to get energy group bounds for group "{0}" since ' \
                  'the group edges have not yet been set'.format(group)
            raise ValueError(msg)

        cv.check_greater_than('group', group, 0)
        cv.check_less_than('group', group, self.num_groups, equality=True)

        lower = self.group_edges[self.num_groups - group]
        upper = self.group_edges[self.num_groups - group + 1]
        return lower, upper
Пример #9
0
    def meshlines(self, meshlines):
        cv.check_type('plot meshlines', meshlines, dict)
        if 'type' not in meshlines:
            msg = 'Unable to set on plot the meshlines "{0}" which ' \
                  'does not have a "type" key'.format(meshlines)
            raise ValueError(msg)

        elif meshlines['type'] not in ['tally', 'entropy', 'ufs', 'cmfd']:
            msg = 'Unable to set the meshlines with ' \
                  'type "{0}"'.format(meshlines['type'])
            raise ValueError(msg)

        if 'id' in meshlines:
            cv.check_type('plot meshlines id', meshlines['id'], Integral)
            cv.check_greater_than('plot meshlines id', meshlines['id'], 0,
                                  equality=True)

        if 'linewidth' in meshlines:
            cv.check_type('plot mesh linewidth', meshlines['linewidth'], Integral)
            cv.check_greater_than('plot mesh linewidth', meshlines['linewidth'],
                                  0, equality=True)

        if 'color' in meshlines:
            cv.check_type('plot meshlines color', meshlines['color'], Iterable,
                          Integral)
            cv.check_length('plot meshlines color', meshlines['color'], 3)
            for rgb in meshlines['color']:
                cv.check_greater_than('plot meshlines color', rgb, 0, True)
                cv.check_less_than('plot meshlines color', rgb, 256)

        self._meshlines = meshlines
Пример #10
0
    def get_group_bounds(self, group):
        """Returns the energy boundaries for the energy group of interest.

        Parameters
        ----------
        group : Integral
            The energy group index, starting at 1 for the highest energies

        Returns
        -------
        2-tuple
            The low and high energy bounds for the group in MeV

        Raises
        ------
        ValueError
            If the group edges have not yet been set.

        """

        if self.group_edges is None:
            msg = 'Unable to get energy group bounds for group "{0}" since ' \
                  'the group edges have not yet been set'.format(group)
            raise ValueError(msg)

        cv.check_greater_than('group', group, 0)
        cv.check_less_than('group', group, self.num_groups, equality=True)

        lower = self.group_edges[self.num_groups-group]
        upper = self.group_edges[self.num_groups-group+1]
        return lower, upper
Пример #11
0
    def add_s_alpha_beta(self, name, fraction=1.0):
        r"""Add an :math:`S(\alpha,\beta)` table to the material

        Parameters
        ----------
        name : str
            Name of the :math:`S(\alpha,\beta)` table
        fraction : float
            The fraction of relevant nuclei that are affected by the
            :math:`S(\alpha,\beta)` table.  For example, if the material is a
            block of carbon that is 60% graphite and 40% amorphous then add a
            graphite :math:`S(\alpha,\beta)` table with fraction=0.6.

        """

        if self._macroscopic is not None:
            msg = 'Unable to add an S(a,b) table to Material ID="{}" as a ' \
                  'macroscopic data-set has already been added'.format(self._id)
            raise ValueError(msg)

        if not isinstance(name, str):
            msg = 'Unable to add an S(a,b) table to Material ID="{}" with a ' \
                        'non-string table name "{}"'.format(self._id, name)
            raise ValueError(msg)

        cv.check_type('S(a,b) fraction', fraction, Real)
        cv.check_greater_than('S(a,b) fraction', fraction, 0.0, True)
        cv.check_less_than('S(a,b) fraction', fraction, 1.0, True)
        self._sab.append((name, fraction))
Пример #12
0
 def albedo(self, albedo):
     check_type('CMFD mesh albedo', albedo, Iterable, Real)
     check_length('CMFD mesh albedo', albedo, 6)
     for a in albedo:
         check_greater_than('CMFD mesh albedo', a, 0, True)
         check_less_than('CMFD mesh albedo', a, 1, True)
     self._albedo = albedo
Пример #13
0
    def highlight_domains(self,
                          geometry,
                          domains,
                          seed=1,
                          alpha=0.5,
                          background='gray'):
        """Use alpha compositing to highlight one or more domains in the plot.

        This routine generates a color scheme and applies alpha compositing
        to make all domains except the highlighted ones appear partially
        transparent.

        Parameters
        ----------
        geometry : openmc.Geometry
            The geometry for which the plot is defined
        domains : Iterable of Integral
            A collection of the domain IDs to highlight in the plot
        seed : Integral
            The random number seed used to generate the color scheme
        alpha : Real in [0,1]
            The value to apply in alpha compisiting
        background : 3-tuple of Integral or 'white' or 'black' or 'gray'
            The background color to apply in alpha compisiting

        """

        cv.check_iterable_type('domains', domains, Integral)
        cv.check_type('alpha', alpha, Real)
        cv.check_greater_than('alpha', alpha, 0., equality=True)
        cv.check_less_than('alpha', alpha, 1., equality=True)

        # Get a background (R,G,B) tuple to apply in alpha compositing
        if isinstance(background, basestring):
            if background == 'white':
                background = (255, 255, 255)
            elif background == 'black':
                background = (0, 0, 0)
            elif background == 'gray':
                background = (160, 160, 160)
            else:
                msg = 'The background "{}" is not defined'.format(background)
                raise ValueError(msg)

        cv.check_iterable_type('background', background, Integral)

        # Generate a color scheme
        self.colorize(geometry, seed)

        # Apply alpha compositing to the colors for all domains
        # other than those the user wishes to highlight
        for domain_id in self.col_spec:
            if domain_id not in domains:
                r, g, b = self.col_spec[domain_id]
                r = int(((1 - alpha) * background[0]) + (alpha * r))
                g = int(((1 - alpha) * background[1]) + (alpha * g))
                b = int(((1 - alpha) * background[2]) + (alpha * b))
                self._col_spec[domain_id] = (r, g, b)
Пример #14
0
    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
Пример #15
0
    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
Пример #16
0
    def highlight_domains(self,
                          geometry,
                          domains,
                          seed=1,
                          alpha=0.5,
                          background='gray'):
        """Use alpha compositing to highlight one or more domains in the plot.

        This routine generates a color scheme and applies alpha compositing to
        make all domains except the highlighted ones appear partially
        transparent.

        Parameters
        ----------
        geometry : openmc.Geometry
            The geometry for which the plot is defined
        domains : Iterable of openmc.Cell or openmc.Material
            A collection of the domain IDs to highlight in the plot
        seed : int
            The random number seed used to generate the color scheme
        alpha : float
            The value between 0 and 1 to apply in alpha compisiting
        background : 3-tuple of int or str
            The background color to apply in alpha compisiting

        """

        cv.check_type('domains', domains, Iterable,
                      (openmc.Cell, openmc.Material))
        cv.check_type('alpha', alpha, Real)
        cv.check_greater_than('alpha', alpha, 0., equality=True)
        cv.check_less_than('alpha', alpha, 1., equality=True)
        cv.check_type('background', background, Iterable)

        # Get a background (R,G,B) tuple to apply in alpha compositing
        if isinstance(background, str):
            if background.lower() not in _SVG_COLORS:
                raise ValueError(
                    "'{}' is not a valid color.".format(background))
            background = _SVG_COLORS[background.lower()]

        # Generate a color scheme
        self.colorize(geometry, seed)

        # Apply alpha compositing to the colors for all domains
        # other than those the user wishes to highlight
        for domain, color in self.colors.items():
            if domain not in domains:
                if isinstance(color, str):
                    color = _SVG_COLORS[color.lower()]
                r, g, b = color
                r = int(((1 - alpha) * background[0]) + (alpha * r))
                g = int(((1 - alpha) * background[1]) + (alpha * g))
                b = int(((1 - alpha) * background[2]) + (alpha * b))
                self._colors[domain] = (r, g, b)
Пример #17
0
 def _check_color(err_string, color):
     cv.check_type(err_string, color, Iterable)
     if isinstance(color, str):
         if color.lower() not in _SVG_COLORS:
             raise ValueError("'{}' is not a valid color.".format(color))
     else:
         cv.check_length(err_string, color, 3)
         for rgb in color:
             cv.check_type(err_string, rgb, Real)
             cv.check_greater_than('RGB component', rgb, 0, True)
             cv.check_less_than('RGB component', rgb, 256)
Пример #18
0
    def highlight_domains(self, geometry, domains, seed=1,
                          alpha=0.5, background='gray'):
        """Use alpha compositing to highlight one or more domains in the plot.

        This routine generates a color scheme and applies alpha compositing
        to make all domains except the highlighted ones appear partially
        transparent.

        Parameters
        ----------
        geometry : openmc.Geometry
            The geometry for which the plot is defined
        domains : Iterable of Integral
            A collection of the domain IDs to highlight in the plot
        seed : Integral
            The random number seed used to generate the color scheme
        alpha : Real in [0,1]
            The value to apply in alpha compisiting
        background : 3-tuple of Integral or 'white' or 'black' or 'gray'
            The background color to apply in alpha compisiting

        """

        cv.check_iterable_type('domains', domains, Integral)
        cv.check_type('alpha', alpha, Real)
        cv.check_greater_than('alpha', alpha, 0., equality=True)
        cv.check_less_than('alpha', alpha, 1., equality=True)

        # Get a background (R,G,B) tuple to apply in alpha compositing
        if isinstance(background, basestring):
            if background == 'white':
                background = (255, 255, 255)
            elif background == 'black':
                background = (0, 0, 0)
            elif background == 'gray':
                background = (160, 160, 160)
            else:
                msg = 'The background "{}" is not defined'.format(background)
                raise ValueError(msg)

        cv.check_iterable_type('background', background, Integral)

        # Generate a color scheme
        self.colorize(geometry, seed)

        # Apply alpha compositing to the colors for all domains
        # other than those the user wishes to highlight
        for domain_id in self.col_spec:
            if domain_id not in domains:
                r, g, b = self.col_spec[domain_id]
                r = int(((1-alpha) * background[0]) + (alpha * r))
                g = int(((1-alpha) * background[1]) + (alpha * g))
                b = int(((1-alpha) * background[2]) + (alpha * b))
                self._col_spec[domain_id] = (r, g, b)
Пример #19
0
 def mask_background(self, mask_background):
     cv.check_type('plot mask background', mask_background, Iterable)
     if isinstance(mask_background, string_types):
         if mask_background.lower() not in _SVG_COLORS:
             raise ValueError("'{}' is not a valid color.".format(mask_background))
     else:
         cv.check_length('plot mask_background', mask_background, 3)
         for rgb in mask_background:
             cv.check_greater_than('plot mask background', rgb, 0, True)
             cv.check_less_than('plot mask background', rgb, 256)
     self._mask_background = mask_background
Пример #20
0
    def legendre_order(self, legendre_order):
        cv.check_type('legendre_order', legendre_order, Integral)
        cv.check_greater_than('legendre_order', legendre_order, 0, equality=True)
        cv.check_less_than('legendre_order', legendre_order, 10, equality=True)

        if self.correction == 'P0' and legendre_order > 0:
            msg = 'The P0 correction will be ignored since the scattering ' \
                  'order {} is greater than zero'.format(self.legendre_order)
            warn(msg, RuntimeWarning)
            self.correction = None

        self._legendre_order = legendre_order
Пример #21
0
    def highlight_domains(self, geometry, domains, seed=1,
                          alpha=0.5, background='gray'):
        """Use alpha compositing to highlight one or more domains in the plot.

        This routine generates a color scheme and applies alpha compositing to
        make all domains except the highlighted ones appear partially
        transparent.

        Parameters
        ----------
        geometry : openmc.Geometry
            The geometry for which the plot is defined
        domains : Iterable of openmc.Cell or openmc.Material
            A collection of the domain IDs to highlight in the plot
        seed : int
            The random number seed used to generate the color scheme
        alpha : float
            The value between 0 and 1 to apply in alpha compisiting
        background : 3-tuple of int or str
            The background color to apply in alpha compisiting

        """

        cv.check_type('domains', domains, Iterable,
                      (openmc.Cell, openmc.Material))
        cv.check_type('alpha', alpha, Real)
        cv.check_greater_than('alpha', alpha, 0., equality=True)
        cv.check_less_than('alpha', alpha, 1., equality=True)
        cv.check_type('background', background, Iterable)

        # Get a background (R,G,B) tuple to apply in alpha compositing
        if isinstance(background, string_types):
            if background.lower() not in _SVG_COLORS:
                raise ValueError("'{}' is not a valid color.".format(background))
            background = _SVG_COLORS[background.lower()]

        # Generate a color scheme
        self.colorize(geometry, seed)

        # Apply alpha compositing to the colors for all domains
        # other than those the user wishes to highlight
        for domain, color in self.colors.items():
            if domain not in domains:
                if isinstance(color, string_types):
                    color = _SVG_COLORS[color.lower()]
                r, g, b = color
                r = int(((1-alpha) * background[0]) + (alpha * r))
                g = int(((1-alpha) * background[1]) + (alpha * g))
                b = int(((1-alpha) * background[2]) + (alpha * b))
                self._colors[domain] = (r, g, b)
Пример #22
0
    def colors(self, colors):
        cv.check_type('plot colors', colors, Mapping)
        for key, value in colors.items():
            cv.check_type('plot color key', key, (openmc.Cell, openmc.Material))
            cv.check_type('plot color value', value, Iterable)
            if isinstance(value, string_types):
                if value.lower() not in _SVG_COLORS:
                    raise ValueError("'{}' is not a valid color.".format(value))
            else:
                cv.check_length('plot color (RGB)', value, 3)
                for component in value:
                    cv.check_type('RGB component', component, Real)
                    cv.check_greater_than('RGB component', component, 0, True)
                    cv.check_less_than('RGB component', component, 255, True)

        self._colors = colors
Пример #23
0
    def get_group_indices(self, groups="all"):
        """Returns the array indices for one or more energy groups.

        Parameters
        ----------
        groups : str, tuple
            The energy groups of interest - a tuple of the energy group indices,
            starting at 1 for the highest energies (default is 'all')

        Returns
        -------
        numpy.ndarray
            The ndarray array indices for each energy group of interest

        Raises
        ------
        ValueError
            If the group edges have not yet been set, or if a group is requested
            that is outside the bounds of the number of energy groups.

        """

        if self.group_edges is None:
            msg = (
                'Unable to get energy group indices for groups "{0}" since '
                "the group edges have not yet been set".format(groups)
            )
            raise ValueError(msg)

        if groups == "all":
            return np.arange(self.num_groups)
        else:
            indices = np.zeros(len(groups), dtype=np.int)

        for i, group in enumerate(groups):
            cv.check_greater_than("group", group, 0)
            cv.check_less_than("group", group, self.num_groups, equality=True)
            indices[i] = group - 1

        return indices
Пример #24
0
    def get_group_indices(self, groups='all'):
        """Returns the array indices for one or more energy groups.

        Parameters
        ----------
        groups : str, tuple
            The energy groups of interest - a tuple of the energy group indices,
            starting at 1 for the highest energies (default is 'all')

        Returns
        -------
        numpy.ndarray
            The ndarray array indices for each energy group of interest

        Raises
        ------
        ValueError
            If the group edges have not yet been set, or if a group is requested
            that is outside the bounds of the number of energy groups.

        """

        if self.group_edges is None:
            msg = 'Unable to get energy group indices for groups "{0}" since ' \
                  'the group edges have not yet been set'.format(groups)
            raise ValueError(msg)

        if groups == 'all':
            return np.arange(self.num_groups)
        else:
            indices = np.zeros(len(groups), dtype=np.int)

        for i, group in enumerate(groups):
            cv.check_greater_than('group', group, 0)
            cv.check_less_than('group', group, self.num_groups, equality=True)
            indices[i] = group - 1

        return indices
Пример #25
0
    def get_condensed_groups(self, coarse_groups):
        """Return a coarsened version of this EnergyGroups object.

        This method merges together energy groups in this object into wider
        energy groups as defined by the list of groups specified by the user,
        and returns a new, coarse EnergyGroups object.

        Parameters
        ----------
        coarse_groups : Iterable of 2-tuple
            The energy groups of interest - a list of 2-tuples, each directly
            corresponding to one of the new coarse groups. The values in the
            2-tuples are upper/lower energy groups used to construct a new
            coarse group. For example, if [(1,2), (3,4)] was used as the coarse
            groups, fine groups 1 and 2 would be merged into coarse group 1
            while fine groups 3 and 4 would be merged into coarse group 2.

        Returns
        -------
        EnergyGroups
            A coarsened version of this EnergyGroups object.

        Raises
        ------
        ValueError
            If the group edges have not yet been set.
        """

        cv.check_type('group edges', coarse_groups, Iterable)
        for group in coarse_groups:
            cv.check_type('group edges', group, Iterable)
            cv.check_length('group edges', group, 2)
            cv.check_greater_than('lower group', group[0], 1, True)
            cv.check_less_than('lower group', group[0], self.num_groups, True)
            cv.check_greater_than('upper group', group[0], 1, True)
            cv.check_less_than('upper group', group[0], self.num_groups, True)
            cv.check_less_than('lower group', group[0], group[1], False)

        # Compute the group indices into the coarse group
        group_bounds = [group[1] for group in coarse_groups]
        group_bounds.insert(0, coarse_groups[0][0])

        # Determine the indices mapping the fine-to-coarse energy groups
        group_bounds = np.asarray(group_bounds)
        group_indices = np.flipud(self.num_groups - group_bounds)
        group_indices[-1] += 1

        # Determine the edges between coarse energy groups and sort
        # in increasing order in case the user passed in unordered groups
        group_edges = self.group_edges[group_indices]
        group_edges = np.sort(group_edges)

        # Create a new condensed EnergyGroups object
        condensed_groups = EnergyGroups()
        condensed_groups.group_edges = group_edges

        return condensed_groups
Пример #26
0
    def get_condensed_groups(self, coarse_groups):
        """Return a coarsened version of this EnergyGroups object.

        This method merges together energy groups in this object into wider
        energy groups as defined by the list of groups specified by the user,
        and returns a new, coarse EnergyGroups object.

        Parameters
        ----------
        coarse_groups : Iterable of 2-tuple
            The energy groups of interest - a list of 2-tuples, each directly
            corresponding to one of the new coarse groups. The values in the
            2-tuples are upper/lower energy groups used to construct a new
            coarse group. For example, if [(1,2), (3,4)] was used as the coarse
            groups, fine groups 1 and 2 would be merged into coarse group 1
            while fine groups 3 and 4 would be merged into coarse group 2.

        Returns
        -------
        openmc.mgxs.EnergyGroups
            A coarsened version of this EnergyGroups object.

        Raises
        ------
        ValueError
            If the group edges have not yet been set.
        """

        cv.check_type('group edges', coarse_groups, Iterable)
        for group in coarse_groups:
            cv.check_type('group edges', group, Iterable)
            cv.check_length('group edges', group, 2)
            cv.check_greater_than('lower group', group[0], 1, True)
            cv.check_less_than('lower group', group[0], self.num_groups, True)
            cv.check_greater_than('upper group', group[0], 1, True)
            cv.check_less_than('upper group', group[0], self.num_groups, True)
            cv.check_less_than('lower group', group[0], group[1], False)

        # Compute the group indices into the coarse group
        group_bounds = [group[1] for group in coarse_groups]
        group_bounds.insert(0, coarse_groups[0][0])

        # Determine the indices mapping the fine-to-coarse energy groups
        group_bounds = np.asarray(group_bounds)
        group_indices = np.flipud(self.num_groups - group_bounds)
        group_indices[-1] += 1

        # Determine the edges between coarse energy groups and sort
        # in increasing order in case the user passed in unordered groups
        group_edges = self.group_edges[group_indices]
        group_edges = np.sort(group_edges)

        # Create a new condensed EnergyGroups object
        condensed_groups = EnergyGroups()
        condensed_groups.group_edges = group_edges

        return condensed_groups
Пример #27
0
    def add_s_alpha_beta(self, name, fraction=1.0):
        r"""Add an :math:`S(\alpha,\beta)` table to the material

        Parameters
        ----------
        name : str
            Name of the :math:`S(\alpha,\beta)` table
        fraction : float
            The fraction of relevant nuclei that are affected by the
            :math:`S(\alpha,\beta)` table.  For example, if the material is a
            block of carbon that is 60% graphite and 40% amorphous then add a
            graphite :math:`S(\alpha,\beta)` table with fraction=0.6.

        """

        if self._macroscopic is not None:
            msg = 'Unable to add an S(a,b) table to Material ID="{}" as a ' \
                  'macroscopic data-set has already been added'.format(self._id)
            raise ValueError(msg)

        if not isinstance(name, string_types):
            msg = 'Unable to add an S(a,b) table to Material ID="{}" with a ' \
                        'non-string table name "{}"'.format(self._id, name)
            raise ValueError(msg)

        cv.check_type('S(a,b) fraction', fraction, Real)
        cv.check_greater_than('S(a,b) fraction', fraction, 0.0, True)
        cv.check_less_than('S(a,b) fraction', fraction, 1.0, True)

        new_name = openmc.data.get_thermal_name(name)
        if new_name != name:
            msg = 'OpenMC S(a,b) tables follow the GND naming convention. ' \
                  'Table "{}" is being renamed as "{}".'.format(name, new_name)
            warnings.warn(msg)

        self._sab.append((new_name, fraction))
Пример #28
0
    def add_element(self, element, percent, percent_type='ao', enrichment=None):
        """Add a natural element to the material

        Parameters
        ----------
        element : str
            Element to add
        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).

        """

        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 not isinstance(element, string_types):
            msg = 'Unable to add an Element to Material ID="{}" with a ' \
                  'non-string value "{}"'.format(self._id, element)
            raise ValueError(msg)

        if not isinstance(percent, Real):
            msg = 'Unable to add an Element to Material ID="{}" with a ' \
                  'non-floating point value "{}"'.format(self._id, percent)
            raise ValueError(msg)

        if percent_type not in ['ao', 'wo']:
            msg = 'Unable to add an Element to Material ID="{}" with a ' \
                  'percent type "{}"'.format(self._id, percent_type)
            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)

        # Add naturally-occuring isotopes
        element = openmc.Element(element)
        for nuclide in element.expand(percent, percent_type, enrichment):
            self._nuclides.append(nuclide)
Пример #29
0
 def verbosity(self, verbosity):
     cv.check_type('verbosity', verbosity, Integral)
     cv.check_greater_than('verbosity', verbosity, 1, True)
     cv.check_less_than('verbosity', verbosity, 10, True)
     self._verbosity = verbosity
Пример #30
0
def search_for_keff(model_builder, initial_guess=None, target=1.0,
                    bracket=None, model_args=None, tol=None,
                    bracketed_method='bisect', print_iterations=False,
                    print_output=False, **kwargs):
    """Function to perform a keff search by modifying a model parametrized by a
    single independent variable.

    Parameters
    ----------
    model_builder : collections.Callable
        Callable function which builds a model according to a passed
        parameter. This function must return an openmc.model.Model object.
    initial_guess : Real, optional
        Initial guess for the parameter to be searched in
        `model_builder`. One of `guess` or `bracket` must be provided.
    target : Real, optional
        keff value to search for, defaults to 1.0.
    bracket : None or Iterable of Real, optional
        Bracketing interval to search for the solution; if not provided,
        a generic non-bracketing method is used. If provided, the brackets
        are used. Defaults to no brackets provided. One of `guess` or `bracket`
        must be provided. If both are provided, the bracket will be
        preferentially used.
    model_args : dict, optional
        Keyword-based arguments to pass to the `model_builder` method. Defaults
        to no arguments.
    tol : float
        Tolerance to pass to the search method
    bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional
        Solution method to use; only applies if
        `bracket` is set, otherwise the Secant method is used.
        Defaults to 'bisect'.
    print_iterations : bool
        Whether or not to print the guess and the result during the iteration
        process. Defaults to False.
    print_output : bool
        Whether or not to print the OpenMC output during the iterations.
        Defaults to False.
    **kwargs
        All remaining keyword arguments are passed to the root-finding
        method.

    Returns
    -------
    zero_value : float
        Estimated value of the variable parameter where keff is the
        targeted value
    guesses : List of Real
        List of guesses attempted by the search
    results : List of 2-tuple of Real
        List of keffs and uncertainties corresponding to the guess attempted by
        the search

    """

    if initial_guess is not None:
        cv.check_type('initial_guess', initial_guess, Real)
    if bracket is not None:
        cv.check_iterable_type('bracket', bracket, Real)
        cv.check_length('bracket', bracket, 2)
        cv.check_less_than('bracket values', bracket[0], bracket[1])
    if model_args is None:
        model_args = {}
    else:
        cv.check_type('model_args', model_args, dict)
    cv.check_type('target', target, Real)
    cv.check_type('tol', tol, Real)
    cv.check_value('bracketed_method', bracketed_method,
                   _SCALAR_BRACKETED_METHODS)
    cv.check_type('print_iterations', print_iterations, bool)
    cv.check_type('print_output', print_output, bool)
    cv.check_type('model_builder', model_builder, Callable)

    # Run the model builder function once to make sure it provides the correct
    # output type
    if bracket is not None:
        model = model_builder(bracket[0], **model_args)
    elif initial_guess is not None:
        model = model_builder(initial_guess, **model_args)
    cv.check_type('model_builder return', model, openmc.model.Model)

    # Set the iteration data storage variables
    guesses = []
    results = []

    # Set the searching function (for easy replacement should a later
    # generic function be added.
    search_function = _search_keff

    if bracket is not None:
        # Generate our arguments
        args = {'f': search_function, 'a': bracket[0], 'b': bracket[1]}
        if tol is not None:
            args['rtol'] = tol

        # Set the root finding method
        if bracketed_method == 'brentq':
            root_finder = sopt.brentq
        elif bracketed_method == 'brenth':
            root_finder = sopt.brenth
        elif bracketed_method == 'ridder':
            root_finder = sopt.ridder
        elif bracketed_method == 'bisect':
            root_finder = sopt.bisect

    elif initial_guess is not None:

        # Generate our arguments
        args = {'func': search_function, 'x0': initial_guess}
        if tol is not None:
            args['tol'] = tol

        # Set the root finding method
        root_finder = sopt.newton

    else:
        raise ValueError("Either the 'bracket' or 'initial_guess' parameters "
                         "must be set")

    # Add information to be passed to the searching function
    args['args'] = (target, model_builder, model_args, print_iterations,
                    print_output, guesses, results)

    # Create a new dictionary with the arguments from args and kwargs
    args.update(kwargs)

    # Perform the search
    zero_value = root_finder(**args)

    return zero_value, guesses, results
Пример #31
0
    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)
Пример #32
0
    def get_xsdata(self, domain, xsdata_name, nuclide='total', xs_type='macro',
                   xs_id='1m', order=None, tabular_legendre=None,
                   tabular_points=33):
        """Generates an openmc.XSdata object describing a multi-group cross section
        data set for eventual combination in to an openmc.MGXSLibrary object
        (i.e., the library).

        Parameters
        ----------
        domain : openmc.Material or openmc.Cell or openmc.Universe
            The domain for spatial homogenization
        xsdata_name : str
            Name to apply to the "xsdata" entry produced by this method
        nuclide : str
            A nuclide name string (e.g., 'U-235').  Defaults to 'total' to
            obtain a material-wise macroscopic cross section.
        xs_type: {'macro', 'micro'}
            Provide the macro or micro cross section in units of cm^-1 or
            barns. Defaults to 'macro'. If the Library object is not tallied by
            nuclide this will be set to 'macro' regardless.
        xs_ids : str
            Cross section set identifier. Defaults to '1m'.
        order : int
            Scattering order for this data entry.  Default is None,
            which will set the XSdata object to use the order of the
            Library.
        tabular_legendre : None or bool
            Flag to denote whether or not the Legendre expansion of the
            scattering angular distribution is to be converted to a tabular
            representation by OpenMC.  A value of `True` means that it is to be
            converted while a value of `False` means that it will not be.
            Defaults to `None` which leaves the default behavior of OpenMC in
            place (the distribution is converted to a tabular representation).
        tabular_points : int
            This parameter is not used unless the ``tabular_legendre``
            parameter is set to `True`.  In this case, this parameter sets the
            number of equally-spaced points in the domain of [-1,1] to be used
            in building the tabular distribution. Default is `33`.

        Returns
        -------
        xsdata : openmc.XSdata
            Multi-Group Cross Section data set object.

        Raises
        ------
        ValueError
            When the Library object is initialized with insufficient types of
            cross sections for the Library.

        See also
        --------
        Library.create_mg_library()

        """

        cv.check_type('domain', domain, (openmc.Material, openmc.Cell,
                                         openmc.Cell))
        cv.check_type('xsdata_name', xsdata_name, basestring)
        cv.check_type('nuclide', nuclide, basestring)
        cv.check_value('xs_type', xs_type, ['macro', 'micro'])
        cv.check_type('xs_id', xs_id, basestring)
        cv.check_type('order', order, (type(None), Integral))
        if order is not None:
            cv.check_greater_than('order', order, 0, equality=True)
            cv.check_less_than('order', order, 10, equality=True)
        cv.check_type('tabular_legendre', tabular_legendre,
                      (type(None), bool))
        if tabular_points is not None:
            cv.check_greater_than('tabular_points', tabular_points, 1)

        # Make sure statepoint has been loaded
        if self._sp_filename is None:
            msg = 'A StatePoint must be loaded before calling ' \
                  'the create_mg_library() function'
            raise ValueError(msg)

        # If gathering material-specific data, set the xs_type to macro
        if not self.by_nuclide:
            xs_type = 'macro'

        # Build & add metadata to XSdata object
        name = xsdata_name
        if nuclide is not 'total':
            name += '_' + nuclide
        name += '.' + xs_id
        xsdata = openmc.XSdata(name, self.energy_groups)

        if order is None:
            # Set the order to the Library's order (the defualt behavior)
            xsdata.order = self.legendre_order
        else:
            # Set the order of the xsdata object to the minimum of
            # the provided order or the Library's order.
            xsdata.order = min(order, self.legendre_order)

        # Set the tabular_legendre option if needed
        if tabular_legendre is not None:
            xsdata.tabular_legendre = {'enable': tabular_legendre,
                                       'num_points': tabular_points}

        if nuclide is not 'total':
            xsdata.zaid = self._nuclides[nuclide][0]
            xsdata.awr = self._nuclides[nuclide][1]

        # Now get xs data itself
        if 'nu-transport' in self.mgxs_types and self.correction == 'P0':
            mymgxs = self.get_mgxs(domain, 'nu-transport')
            xsdata.set_total_mgxs(mymgxs, xs_type=xs_type, nuclide=[nuclide])
        elif 'total' in self.mgxs_types:
            mymgxs = self.get_mgxs(domain, 'total')
            xsdata.set_total_mgxs(mymgxs, xs_type=xs_type, nuclide=[nuclide])
        if 'absorption' in self.mgxs_types:
            mymgxs = self.get_mgxs(domain, 'absorption')
            xsdata.set_absorption_mgxs(mymgxs, xs_type=xs_type,
                                       nuclide=[nuclide])
        if 'fission' in self.mgxs_types:
            mymgxs = self.get_mgxs(domain, 'fission')
            xsdata.set_fission_mgxs(mymgxs, xs_type=xs_type,
                                    nuclide=[nuclide])
        if 'kappa-fission' in self.mgxs_types:
            mymgxs = self.get_mgxs(domain, 'kappa-fission')
            xsdata.set_kappa_fission_mgxs(mymgxs, xs_type=xs_type,
                                          nuclide=[nuclide])
        # For chi and nu-fission we can either have only a nu-fission matrix
        # provided, or vectors of chi and nu-fission provided
        if 'nu-fission matrix' in self.mgxs_types:
            mymgxs = self.get_mgxs(domain, 'nu-fission matrix')
            xsdata.set_nu_fission_mgxs(mymgxs, xs_type=xs_type,
                                       nuclide=[nuclide])
        else:
            if 'chi' in self.mgxs_types:
                mymgxs = self.get_mgxs(domain, 'chi')
                xsdata.set_chi_mgxs(mymgxs, xs_type=xs_type, nuclide=[nuclide])
            if 'nu-fission' in self.mgxs_types:
                mymgxs = self.get_mgxs(domain, 'nu-fission')
                xsdata.set_nu_fission_mgxs(mymgxs, xs_type=xs_type,
                                           nuclide=[nuclide])
        # If multiplicity matrix is available, prefer that
        if 'multiplicity matrix' in self.mgxs_types:
            mymgxs = self.get_mgxs(domain, 'multiplicity matrix')
            xsdata.set_multiplicity_mgxs(mymgxs, xs_type=xs_type,
                                         nuclide=[nuclide])
            using_multiplicity = True
        # multiplicity wil fall back to using scatter and nu-scatter
        elif ((('scatter matrix' in self.mgxs_types) and
               ('nu-scatter matrix' in self.mgxs_types))):
            scatt_mgxs = self.get_mgxs(domain, 'scatter matrix')
            nuscatt_mgxs = self.get_mgxs(domain, 'nu-scatter matrix')
            xsdata.set_multiplicity_mgxs(nuscatt_mgxs, scatt_mgxs,
                                         xs_type=xs_type, nuclide=[nuclide])
            using_multiplicity = True
        else:
            using_multiplicity = False

        if using_multiplicity:
            nuscatt_mgxs = self.get_mgxs(domain, 'nu-scatter matrix')
            xsdata.set_scatter_mgxs(nuscatt_mgxs, xs_type=xs_type,
                                    nuclide=[nuclide])
        else:
            if 'nu-scatter matrix' in self.mgxs_types:
                nuscatt_mgxs = self.get_mgxs(domain, 'nu-scatter matrix')
                xsdata.set_scatter_mgxs(nuscatt_mgxs, xs_type=xs_type,
                                        nuclide=[nuclide])

                # Since we are not using multiplicity, then
                # scattering multiplication (nu-scatter) must be
                # accounted for approximately by using an adjusted
                # absorption cross section.
                if 'total' in self.mgxs_types:
                    xsdata.absorption = \
                        np.subtract(xsdata.total,
                                    np.sum(xsdata.scatter[0, :, :], axis=1))

        return xsdata
Пример #33
0
    def add_element(self,
                    element,
                    percent,
                    percent_type='ao',
                    enrichment=None):
        """Add a natural element to the material

        Parameters
        ----------
        element : openmc.Element or str
            Element to add
        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).

        """

        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 not isinstance(element, string_types + (openmc.Element, )):
            msg = 'Unable to add an Element to Material ID="{}" with a ' \
                  'non-Element value "{}"'.format(self._id, element)
            raise ValueError(msg)

        if not isinstance(percent, Real):
            msg = 'Unable to add an Element to Material ID="{}" with a ' \
                  'non-floating point value "{}"'.format(self._id, percent)
            raise ValueError(msg)

        if percent_type not in ['ao', 'wo']:
            msg = 'Unable to add an Element to Material ID="{}" with a ' \
                  'percent type "{}"'.format(self._id, percent_type)
            raise ValueError(msg)

        # Copy this Element to separate it from same Element in other Materials
        if isinstance(element, openmc.Element):
            element = deepcopy(element)
        else:
            element = openmc.Element(element)

        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.name != 'U':
                msg = 'Unable to use enrichment for element {} which is not ' \
                      'uranium for Material ID="{}"'.format(element.name,
                                                             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)

        self._elements.append((element, percent, percent_type, enrichment))
Пример #34
0
    def get_bin(self, bin_index):
        """Returns the filter bin for some filter bin index.

        Parameters
        ----------
        bin_index : Integral
            The zero-based index into the filter's array of bins. The bin
            index for 'material', 'surface', 'cell', 'cellborn', and 'universe'
            filters corresponds to the ID in the filter's list of bins. For
            'distribcell' tallies the bin index necessarily can only be zero
            since only one cell can be tracked per tally. The bin index for
            'energy' and 'energyout' filters corresponds to the energy range of
            interest in the filter bins of energies. The bin index for 'mesh'
            filters is the index into the flattened array of (x,y) or (x,y,z)
            mesh cell bins.

        Returns
        -------
        bin : 1-, 2-, or 3-tuple of Real
            The bin in the Tally data array. The bin for 'material', surface',
            'cell', 'cellborn', 'universe' and 'distribcell' filters is a
            1-tuple of the ID corresponding to the appropriate filter bin.
            The bin for 'energy' and 'energyout' filters is a 2-tuple of the
            lower and upper energies bounding the energy interval for the filter
            bin. The bin for 'mesh' tallies is a 2-tuple or 3-tuple of the x,y
            or x,y,z mesh cell indices corresponding to the bin in a 2D/3D mesh.

        See also
        --------
        Filter.get_bin_index()

        """

        cv.check_type('bin_index', bin_index, Integral)
        cv.check_greater_than('bin_index', bin_index, 0, equality=True)
        cv.check_less_than('bin_index', bin_index, self.num_bins)

        if self.type == 'mesh':

            # Construct 3-tuple of x,y,z cell indices for a 3D mesh
            if len(self.mesh.dimension) == 3:
                nx, ny, nz = self.mesh.dimension
                x = bin_index / (ny * nz)
                y = (bin_index - (x * ny * nz)) / nz
                z = bin_index - (x * ny * nz) - (y * nz)
                filter_bin = (x, y, z)

            # Construct 2-tuple of x,y cell indices for a 2D mesh
            else:
                nx, ny = self.mesh.dimension
                x = bin_index / ny
                y = bin_index - (x * ny)
                filter_bin = (x, y)

        # Construct 2-tuple of lower, upper energies for energy(out) filters
        elif self.type in ['energy', 'energyout']:
            filter_bin = (self.bins[bin_index], self.bins[bin_index + 1])
        # Construct 1-tuple of with the cell ID for distribcell filters
        elif self.type == 'distribcell':
            filter_bin = (self.bins[0], )
        # Construct 1-tuple with domain ID (e.g., material) for other filters
        else:
            filter_bin = (self.bins[bin_index], )

        return filter_bin
Пример #35
0
def search_for_keff(model_builder,
                    initial_guess=None,
                    target=1.0,
                    bracket=None,
                    model_args=None,
                    tol=None,
                    bracketed_method='bisect',
                    print_iterations=False,
                    print_output=False,
                    **kwargs):
    """Function to perform a keff search by modifying a model parametrized by a
    single independent variable.

    Parameters
    ----------
    model_builder : collections.Callable
        Callable function which builds a model according to a passed
        parameter. This function must return an openmc.model.Model object.
    initial_guess : Real, optional
        Initial guess for the parameter to be searched in
        `model_builder`. One of `guess` or `bracket` must be provided.
    target : Real, optional
        keff value to search for, defaults to 1.0.
    bracket : None or Iterable of Real, optional
        Bracketing interval to search for the solution; if not provided,
        a generic non-bracketing method is used. If provided, the brackets
        are used. Defaults to no brackets provided. One of `guess` or `bracket`
        must be provided. If both are provided, the bracket will be
        preferentially used.
    model_args : dict, optional
        Keyword-based arguments to pass to the `model_builder` method. Defaults
        to no arguments.
    tol : float
        Tolerance to pass to the search method
    bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional
        Solution method to use; only applies if
        `bracket` is set, otherwise the Secant method is used.
        Defaults to 'bisect'.
    print_iterations : bool
        Whether or not to print the guess and the result during the iteration
        process. Defaults to False.
    print_output : bool
        Whether or not to print the OpenMC output during the iterations.
        Defaults to False.
    **kwargs
        All remaining keyword arguments are passed to the root-finding
        method.

    Returns
    -------
    zero_value : float
        Estimated value of the variable parameter where keff is the
        targeted value
    guesses : List of Real
        List of guesses attempted by the search
    results : List of 2-tuple of Real
        List of keffs and uncertainties corresponding to the guess attempted by
        the search

    """

    if initial_guess is not None:
        cv.check_type('initial_guess', initial_guess, Real)
    if bracket is not None:
        cv.check_iterable_type('bracket', bracket, Real)
        cv.check_length('bracket', bracket, 2)
        cv.check_less_than('bracket values', bracket[0], bracket[1])
    if model_args is None:
        model_args = {}
    else:
        cv.check_type('model_args', model_args, dict)
    cv.check_type('target', target, Real)
    cv.check_type('tol', tol, Real)
    cv.check_value('bracketed_method', bracketed_method,
                   _SCALAR_BRACKETED_METHODS)
    cv.check_type('print_iterations', print_iterations, bool)
    cv.check_type('print_output', print_output, bool)
    cv.check_type('model_builder', model_builder, Callable)

    # Run the model builder function once to make sure it provides the correct
    # output type
    if bracket is not None:
        model = model_builder(bracket[0], **model_args)
    elif initial_guess is not None:
        model = model_builder(initial_guess, **model_args)
    cv.check_type('model_builder return', model, openmc.model.Model)

    # Set the iteration data storage variables
    guesses = []
    results = []

    # Set the searching function (for easy replacement should a later
    # generic function be added.
    search_function = _search_keff

    if bracket is not None:
        # Generate our arguments
        args = {'f': search_function, 'a': bracket[0], 'b': bracket[1]}
        if tol is not None:
            args['rtol'] = tol

        # Set the root finding method
        if bracketed_method == 'brentq':
            root_finder = sopt.brentq
        elif bracketed_method == 'brenth':
            root_finder = sopt.brenth
        elif bracketed_method == 'ridder':
            root_finder = sopt.ridder
        elif bracketed_method == 'bisect':
            root_finder = sopt.bisect

    elif initial_guess is not None:

        # Generate our arguments
        args = {'func': search_function, 'x0': initial_guess}
        if tol is not None:
            args['tol'] = tol

        # Set the root finding method
        root_finder = sopt.newton

    else:
        raise ValueError("Either the 'bracket' or 'initial_guess' parameters "
                         "must be set")

    # Add information to be passed to the searching function
    args['args'] = (target, model_builder, model_args, print_iterations,
                    print_output, guesses, results)

    # Create a new dictionary with the arguments from args and kwargs
    args.update(kwargs)

    # Perform the search
    zero_value = root_finder(**args)

    return zero_value, guesses, results
Пример #36
0
 def verbosity(self, verbosity):
     check_type('verbosity', verbosity, Integral)
     check_greater_than('verbosity', verbosity, 1, True)
     check_less_than('verbosity', verbosity, 10, True)
     self._verbosity = verbosity
Пример #37
0
    def expand(self,
               percent,
               percent_type,
               enrichment=None,
               enrichment_target=None,
               enrichment_type=None,
               cross_sections=None):
        """Expand natural element into its naturally-occurring isotopes.

        An optional cross_sections argument or the :envvar:`OPENMC_CROSS_SECTIONS`
        environment variable is used to specify a cross_sections.xml file.
        If the cross_sections.xml file is found, the element is expanded only
        into the isotopes/nuclides present in cross_sections.xml. If no
        cross_sections.xml file is found, the element is expanded based on its
        naturally occurring isotopes.

        Parameters
        ----------
        percent : float
            Atom or weight percent
        percent_type : {'ao', 'wo'}
            'ao' for atom percent and 'wo' for weight 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
        cross_sections : str, optional
            Location of cross_sections.xml file. Default is None.

        Returns
        -------
        isotopes : list
            Naturally-occurring isotopes of the element. Each item of the list
            is a tuple consisting of a nuclide string, the atom/weight percent,
            and the string 'ao' or 'wo'.

        Raises
        ------
        ValueError
            No data is available for any of natural isotopes of the element
        ValueError
            If only some natural isotopes are available in the cross-section data
            library and the element is not O, W, or Ta
        ValueError
            If a non-naturally-occurring isotope is requested
        ValueError
            If enrichment is requested of an element with more than two
            naturally-occurring isotopes.
        ValueError
            If enrichment procedure for Uranium is used when element is not
            Uranium.
        ValueError
            Uranium enrichment is requested with enrichment_type=='ao'

        Notes
        -----
        When the `enrichment` argument is specified, a correlation from
        `ORNL/CSD/TM-244 <https://doi.org/10.2172/5561567>`_ is used to
        calculate the weight fractions of U234, U235, U236, and U238. Namely,
        the weight fraction of U234 and U236 are taken to be 0.89% and 0.46%,
        respectively, of the U235 weight fraction. The remainder of the
        isotopic weight is assigned to U238.

        When the `enrichment` argument is specified with `enrichment_target`, a
        general enrichment procedure is used for elements composed of exactly
        two naturally-occurring isotopes. `enrichment` is interpreted as atom
        percent by default but can be controlled by the `enrichment_type`
        argument.

        """
        # Check input
        if enrichment_type is not None:
            cv.check_value('enrichment_type', enrichment_type, {'ao', 'wo'})

        if enrichment is not None:
            cv.check_less_than('enrichment', enrichment, 100.0, equality=True)
            cv.check_greater_than('enrichment', enrichment, 0., equality=True)

        # Get the nuclides present in nature
        natural_nuclides = {name for name, abundance in natural_isotopes(self)}

        # Create dict to store the expanded nuclides and abundances
        abundances = OrderedDict()

        # If cross_sections is None, get the cross sections from the
        # OPENMC_CROSS_SECTIONS environment variable
        if cross_sections is None:
            cross_sections = os.environ.get('OPENMC_CROSS_SECTIONS')

        # If a cross_sections library is present, check natural nuclides
        # against the nuclides in the library
        if cross_sections is not None:
            library_nuclides = set()
            tree = ET.parse(cross_sections)
            root = tree.getroot()
            for child in root.findall('library'):
                nuclide = child.attrib['materials']
                if re.match(r'{}\d+'.format(self), nuclide) and \
                   '_m' not in nuclide:
                    library_nuclides.add(nuclide)

            # Get a set of the mutual and absent nuclides. Convert to lists
            # and sort to avoid different ordering between Python 2 and 3.
            mutual_nuclides = natural_nuclides.intersection(library_nuclides)
            absent_nuclides = natural_nuclides.difference(mutual_nuclides)
            mutual_nuclides = sorted(list(mutual_nuclides))
            absent_nuclides = sorted(list(absent_nuclides))

            # If all natural nuclides are present in the library,
            # expand element using all natural nuclides
            if len(absent_nuclides) == 0:
                for nuclide in mutual_nuclides:
                    abundances[nuclide] = NATURAL_ABUNDANCE[nuclide]

            # If no natural elements are present in the library, check if the
            # 0 nuclide is present. If so, set the abundance to 1 for this
            # nuclide. Else, raise an error.
            elif len(mutual_nuclides) == 0:
                nuclide_0 = self + '0'
                if nuclide_0 in library_nuclides:
                    abundances[nuclide_0] = 1.0
                else:
                    msg = 'Unable to expand element {0} because the cross '\
                          'section library provided does not contain any of '\
                          'the natural isotopes for that element.'\
                          .format(self)
                    raise ValueError(msg)

            # If some, but not all, natural nuclides are in the library, add
            # the mutual nuclides. For the absent nuclides, add them based on
            # our knowledge of the common cross section libraries
            # (ENDF, JEFF, and JENDL)
            else:
                # Add the mutual isotopes
                for nuclide in mutual_nuclides:
                    abundances[nuclide] = NATURAL_ABUNDANCE[nuclide]

                # Adjust the abundances for the absent nuclides
                for nuclide in absent_nuclides:
                    if nuclide in ['O17', 'O18'] and 'O16' in mutual_nuclides:
                        abundances['O16'] += NATURAL_ABUNDANCE[nuclide]
                    elif nuclide == 'Ta180' and 'Ta181' in mutual_nuclides:
                        abundances['Ta181'] += NATURAL_ABUNDANCE[nuclide]
                    elif nuclide == 'W180' and 'W182' in mutual_nuclides:
                        abundances['W182'] += NATURAL_ABUNDANCE[nuclide]
                    else:
                        msg = 'Unsure how to partition natural abundance of ' \
                              'isotope {0} into other natural isotopes of ' \
                              'this element that are present in the cross ' \
                              'section library provided. Consider adding ' \
                              'the isotopes of this element individually.'
                        raise ValueError(msg)

        # If a cross_section library is not present, expand the element into
        # its natural nuclides
        else:
            for nuclide in natural_nuclides:
                abundances[nuclide] = NATURAL_ABUNDANCE[nuclide]

        # Modify mole fractions if enrichment provided
        # Old treatment for Uranium
        if enrichment is not None and enrichment_target is None:

            # Check that the element is Uranium
            if self.name != 'U':
                msg = ('Enrichment procedure for Uranium was requested, '
                       'but the isotope is {} not U'.format(self))
                raise ValueError(msg)

            # Check that enrichment_type is not 'ao'
            if enrichment_type == 'ao':
                msg = ('Enrichment procedure for Uranium requires that '
                       'enrichment value is provided as wo%.')
                raise ValueError(msg)

            # Calculate the mass fractions of isotopes
            abundances['U234'] = 0.0089 * enrichment
            abundances['U235'] = enrichment
            abundances['U236'] = 0.0046 * enrichment
            abundances['U238'] = 100.0 - 1.0135 * enrichment

            # Convert the mass fractions to mole fractions
            for nuclide in abundances.keys():
                abundances[nuclide] /= atomic_mass(nuclide)

            # Normalize the mole fractions to one
            sum_abundances = sum(abundances.values())
            for nuclide in abundances.keys():
                abundances[nuclide] /= sum_abundances

        # Modify mole fractions if enrichment provided
        # New treatment for arbitrary element
        elif enrichment is not None and enrichment_target is not None:

            # Provide more informative error message for U235
            if enrichment_target == 'U235':
                msg = ("There is a special procedure for enrichment of U235 "
                       "in U. To invoke it, the arguments 'enrichment_target'"
                       "and 'enrichment_type' should be omitted. Provide "
                       "a value only for 'enrichment' in weight percent.")
                raise ValueError(msg)

            # Check if it is two-isotope mixture
            if len(abundances) != 2:
                msg = (
                    'Element {} does not consist of two naturally-occurring '
                    'isotopes. Please enter isotopic abundances manually.'.
                    format(self))
                raise ValueError(msg)

            # Check if the target nuclide is present in the mixture
            if enrichment_target not in abundances:
                msg = (
                    'The target nuclide {} is not one of the naturally-occurring '
                    'isotopes ({})'.format(enrichment_target,
                                           list(abundances)))
                raise ValueError(msg)

            # If weight percent enrichment is requested convert to mass fractions
            if enrichment_type == 'wo':
                # Convert the atomic abundances to weight fractions
                # Compute the element atomic mass
                element_am = sum(
                    atomic_mass(nuc) * abundances[nuc] for nuc in abundances)

                # Convert Molar Fractions to mass fractions
                for nuclide in abundances:
                    abundances[nuclide] *= atomic_mass(nuclide) / element_am

                # Normalize to one
                sum_abundances = sum(abundances.values())
                for nuclide in abundances:
                    abundances[nuclide] /= sum_abundances

            # Enrich the mixture
            # The procedure is more generic that it needs to be. It allows
            # to enrich mixtures of more then 2 isotopes, keeping the ratios
            # of non-enriched nuclides the same as in natural composition

            # Get fraction of non-enriched isotopes in nat. composition
            non_enriched = 1.0 - abundances[enrichment_target]
            tail_fraction = 1.0 - enrichment / 100.0

            # Enrich all nuclides
            # Do bogus operation for enrichment target but overwrite immediatly
            # to avoid if statement in the loop
            for nuclide, fraction in abundances.items():
                abundances[nuclide] = tail_fraction * fraction / non_enriched
            abundances[enrichment_target] = enrichment / 100.0

            # Convert back to atomic fractions if requested
            if enrichment_type == 'wo':
                # Convert the mass fractions to mole fractions
                for nuclide in abundances:
                    abundances[nuclide] /= atomic_mass(nuclide)

                # Normalize the mole fractions to one
                sum_abundances = sum(abundances.values())
                for nuclide in abundances:
                    abundances[nuclide] /= sum_abundances

        # Compute the ratio of the nuclide atomic masses to the element
        # atomic mass
        if percent_type == 'wo':

            # Compute the element atomic mass
            element_am = 0.
            for nuclide in abundances.keys():
                element_am += atomic_mass(nuclide) * abundances[nuclide]

            # Convert the molar fractions to mass fractions
            for nuclide in abundances.keys():
                abundances[nuclide] *= atomic_mass(nuclide) / element_am

            # Normalize the mass fractions to one
            sum_abundances = sum(abundances.values())
            for nuclide in abundances.keys():
                abundances[nuclide] /= sum_abundances

        # Create a list of the isotopes in this element
        isotopes = []
        for nuclide, abundance in abundances.items():
            isotopes.append((nuclide, percent * abundance, percent_type))

        return isotopes
Пример #38
0
    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)
Пример #39
0
 def verbosity(self, verbosity):
     check_type("verbosity", verbosity, Integral)
     check_greater_than("verbosity", verbosity, 1, True)
     check_less_than("verbosity", verbosity, 10, True)
     self._verbosity = verbosity
Пример #40
0
    def get_bin(self, bin_index):
        """Returns the filter bin for some filter bin index.

        Parameters
        ----------
        bin_index : Integral
            The zero-based index into the filter's array of bins. The bin
            index for 'material', 'surface', 'cell', 'cellborn', and 'universe'
            filters corresponds to the ID in the filter's list of bins. For
            'distribcell' tallies the bin index necessarily can only be zero
            since only one cell can be tracked per tally. The bin index for
            'energy' and 'energyout' filters corresponds to the energy range of
            interest in the filter bins of energies. The bin index for 'mesh'
            filters is the index into the flattened array of (x,y) or (x,y,z)
            mesh cell bins.

        Returns
        -------
        bin : 1-, 2-, or 3-tuple of Real
            The bin in the Tally data array. The bin for 'material', surface',
            'cell', 'cellborn', 'universe' and 'distribcell' filters is a
            1-tuple of the ID corresponding to the appropriate filter bin.
            The bin for 'energy' and 'energyout' filters is a 2-tuple of the
            lower and upper energies bounding the energy interval for the filter
            bin. The bin for 'mesh' tallies is a 2-tuple or 3-tuple of the x,y
            or x,y,z mesh cell indices corresponding to the bin in a 2D/3D mesh.

        See also
        --------
        Filter.get_bin_index()

        """

        cv.check_type('bin_index', bin_index, Integral)
        cv.check_greater_than('bin_index', bin_index, 0, equality=True)
        cv.check_less_than('bin_index', bin_index, self.num_bins)

        if self.type == 'mesh':

            # Construct 3-tuple of x,y,z cell indices for a 3D mesh
            if len(self.mesh.dimension) == 3:
                nx, ny, nz = self.mesh.dimension
                x = bin_index / (ny * nz)
                y = (bin_index - (x * ny * nz)) / nz
                z = bin_index - (x * ny * nz) - (y * nz)
                filter_bin = (x, y, z)

            # Construct 2-tuple of x,y cell indices for a 2D mesh
            else:
                nx, ny = self.mesh.dimension
                x = bin_index / ny
                y = bin_index - (x * ny)
                filter_bin = (x, y)

        # Construct 2-tuple of lower, upper energies for energy(out) filters
        elif self.type in ['energy', 'energyout']:
            filter_bin = (self.bins[bin_index], self.bins[bin_index+1])
        # Construct 1-tuple of with the cell ID for distribcell filters
        elif self.type == 'distribcell':
            filter_bin = (self.bins[0],)
        # Construct 1-tuple with domain ID (e.g., material) for other filters
        else:
            filter_bin = (self.bins[bin_index],)

        return filter_bin
Пример #41
0
def pin(surfaces, items, subdivisions=None, divide_vols=True, **kwargs):
    """Convenience function for building a fuel pin

    Parameters
    ----------
    surfaces : iterable of :class:`openmc.Cylinder`
        Cylinders used to define boundaries
        between items. All cylinders must be
        concentric and of the same orientation, e.g.
        all :class:`openmc.ZCylinder`
    items : iterable
        Objects to go between ``surfaces``. These can be anything
        that can fill a :class:`openmc.Cell`, including
        :class:`openmc.Material`, or other :class:`openmc.Universe`
        objects. There must be one more item than surfaces,
        which will span all space outside the final ring.
    subdivisions : None or dict of int to int
        Dictionary describing which rings to subdivide and how
        many times. Keys are indexes of the annular rings
        to be divided. Will construct equal area rings
    divide_vols : bool
        If this evaluates to ``True``, then volumes of subdivided
        :class:`openmc.Material` instances will also be divided by the
        number of divisions.  Otherwise the volume of the
        original material will not be modified before subdivision
    kwargs:
        Additional key-word arguments to be passed to
        :class:`openmc.Universe`, like ``name="Fuel pin"``

    Returns
    -------
    :class:`openmc.Universe`
        Universe of concentric cylinders filled with the desired
        items
    """
    if "cells" in kwargs:
        raise SyntaxError(
            "Cells will be set by this function, not from input arguments.")
    check_type("items", items, Iterable)
    check_length("surfaces", surfaces, len(items) - 1, len(items) - 1)
    # Check that all surfaces are of similar orientation
    check_type("surface", surfaces[0], Cylinder)
    surf_type = type(surfaces[0])
    check_iterable_type("surfaces", surfaces[1:], surf_type)

    # Check for increasing radii and equal centers
    if surf_type is ZCylinder:
        center_getter = attrgetter("x0", "y0")
    elif surf_type is YCylinder:
        center_getter = attrgetter("x0", "z0")
    elif surf_type is XCylinder:
        center_getter = attrgetter("z0", "y0")
    else:
        raise TypeError("Not configured to interpret {} surfaces".format(
            surf_type.__name__))

    centers = set()
    prev_rad = 0
    for ix, surf in enumerate(surfaces):
        cur_rad = surf.r
        if cur_rad <= prev_rad:
            raise ValueError(
                "Surfaces do not appear to be increasing in radius. "
                "Surface {} at index {} has radius {:7.3e} compared to "
                "previous radius of {:7.5e}".format(surf.id, ix, cur_rad,
                                                    prev_rad))
        prev_rad = cur_rad
        centers.add(center_getter(surf))

    if len(centers) > 1:
        raise ValueError(
            "Surfaces do not appear to be concentric. The following "
            "centers were found: {}".format(centers))

    if subdivisions is not None:
        check_length("subdivisions", subdivisions, 1, len(surfaces))
        orig_indexes = list(subdivisions.keys())
        check_iterable_type("ring indexes", orig_indexes, int)
        check_iterable_type("number of divisions", list(subdivisions.values()),
                            int)
        for ix in orig_indexes:
            if ix < 0:
                subdivisions[len(surfaces) + ix] = subdivisions.pop(ix)
        # Dissallow subdivision on outer most, infinite region
        check_less_than("outer ring",
                        max(subdivisions),
                        len(surfaces),
                        equality=True)

        # ensure ability to concatenate
        if not isinstance(items, list):
            items = list(items)
        if not isinstance(surfaces, list):
            surfaces = list(surfaces)

        # generate equal area divisions
        # Adding N - 1 new regions
        # N - 2 surfaces are made
        # Original cell is not removed, but not occupies last ring
        for ring_index in reversed(sorted(subdivisions.keys())):
            nr = subdivisions[ring_index]
            new_surfs = []

            lower_rad = 0.0 if ring_index == 0 else surfaces[ring_index - 1].r

            upper_rad = surfaces[ring_index].r

            area_term = (upper_rad**2 - lower_rad**2) / nr

            for new_index in range(nr - 1):
                lower_rad = sqrt(area_term + lower_rad**2)
                new_surfs.append(surf_type(r=lower_rad))

            surfaces = (surfaces[:ring_index] + new_surfs +
                        surfaces[ring_index:])

            filler = items[ring_index]
            if (divide_vols and hasattr(filler, "volume")
                    and filler.volume is not None):
                filler.volume /= nr

            items[ring_index:ring_index] = [
                filler.clone() for _i in range(nr - 1)
            ]

    # Build the universe
    regions = subdivide(surfaces)
    cells = [Cell(fill=f, region=r) for r, f in zip(regions, items)]
    return Universe(cells=cells, **kwargs)