Example #1
0
    def _M(self, flux):
        """Private routine for inner Ax=b solves with the fission source.

        Solves a fixed source problem using the fission source for a given flux
        distribution. This corresponds to the right hand side of the generalized
        kAX = MX eigenvalue problem.

        Parameters
        ----------
        flux : numpy.ndarray
            The flux used to compute the fission source

        Returns
        -------
        residual : numpy.ndarray
            The residual array between input and computed fluxes

        """

        # Remove imaginary components from NumPy array
        flux = np.real(flux).astype(self._precision)

        # Apply operator to flux
        self._m_count += 1
        self._moc_solver.setFluxes(flux)
        self._moc_solver.fissionTransportSweep()
        flux = self._moc_solver.getFluxes(self._op_size)

        py_printf('NORMAL', "Performed M operator sweep number %d",
                  self._m_count)

        # Return new flux
        return flux
Example #2
0
    def _F(self, flux):
        """Private routine for outer eigenvalue solver method.

        Uses a Krylov subspace method (e.g., GMRES, BICGSTAB) from the
        scipy.linalg package to solve the AX=B fixed scatter source problem.

        Parameters
        ----------
        flux : numpy.ndarray
            The flux array returned from the scipy.linalg.eigs routine

        Returns
        -------
        flux : numpy.ndarray
            The flux computed from the fission/scatter fixed source calculations

        """

        import scipy.sparse.linalg as linalg

        # Apply operator to flux - get updated flux from fission source
        flux = self._M_op * flux

        # Solve AX=B fixed scatter source problem using Krylov subspace method
        if self._inner_method == 'gmres':
            flux, x = linalg.gmres(self._A_op, flux, tol=self._inner_tol)
        elif self._inner_method == 'lgmres':
            flux, x = linalg.lgmres(self._A_op, flux, tol=self._inner_tol)
        elif self._inner_method == 'bicgstab':
            flux, x = linalg.bicgstab(self._A_op, flux, tol=self._inner_tol)
        elif self._inner_method == 'cgs':
            flux, x = linalg.cgs(self._A_op, flux, tol=self._inner_tol)
        else:
            py_printf('ERROR', 'Unable to use %s to solve Ax=b',
                      self._inner_method)

        # Check that solve completed without error before returning new flux
        if x != 0:
            py_printf('ERROR', 'Unable to solve Ax=b with %s',
                      self._inner_method)
        else:
            return flux
Example #3
0
    def _A(self, flux):
        """Private routine for inner Ax=b solves with the scattering source.

        Solves a fixed source problem using the scatter source for a given flux
        distribution. This corresponds to the left hand side of the generalized
        kAX = MX eigenvalue problem.

        Parameters
        ----------
        flux : numpy.ndarray
            The flux used to compute the scattering source

        Returns
        -------
        residual : numpy.ndarray
            The residual array between input and computed fluxes

        """

        # Remove imaginary components from NumPy array
        flux = np.real(flux).astype(self._precision)
        flux_old = np.copy(flux)

        # Apply operator to flux
        self._a_count += 1
        self._moc_solver.setFluxes(flux)
        self._moc_solver.scatterTransportSweep()
        flux = self._moc_solver.getFluxes(self._op_size)

        # Print report to screen to update user on progress
        if self._a_count % self._interval == 0:
            py_printf('NORMAL', "Performed A operator sweep number %d",
                      self._a_count)
        else:
            py_printf('INFO', "Performed A operator sweep number %d",
                      self._a_count)

        # Return flux residual
        return flux_old - flux
Example #4
0
    def parseArguments(self):
        """This method parses command line options and assigns the appropriate
        values to the corresponding class attributes."""

        try:
            self._opts, self._args = \
                getopt.getopt(sys.argv[1:], self.short_args, self.long_args)
        except getopt.GetoptError as err:
            py_printf('ERROR', str(err))

        # Parse the command line arguments - error checking will occur
        # at the setter method level in C++
        for opt, arg in self.opts:

            # Print a report of all supported runtime options and exit
            if opt in ('-h', '--help'):

                print('{:-^80}'.format(''))
                print('{: ^80}'.format('OpenMOC v.0.1.1 runtime options'))
                print('{:-^80}'.format(''))
                print('')

                help_msg = '\t{: <35}'.format('-h, --help')
                help_msg += 'Report OpenMOC runtime options\n'
                print(help_msg)

                num_azim = '\t{: <35}'.format('-a, --num-azim=<4>')
                num_azim += 'the number of azimuthal angles\n'
                print(num_azim)

                azim_spacing = '\t{: <35}'.format('-s, --azim-spacing=<0.1>')
                azim_spacing += 'The azimuthal track spacing [cm]\n'
                print(azim_spacing)

                max_iters = '\t{: <35}'.format('-i, --max-iters=<1000>')
                max_iters += 'The max number of source iterations\n'
                print(max_iters)

                tolerance = '\t{: <35}'.format('-c, --tolerance=<1E-5>')
                tolerance += 'The source convergence tolerance\n'
                print(tolerance)

                num_omp_threads = '\t{: <35}'.format('-t, --num-omp-threads=<1>')
                num_omp_threads += 'The number of OpenMP threads\n'
                print(num_omp_threads)

                num_threadblocks = '\t{: <35}'.format('-b, ' + \
                                       '--num-thread-blocks=<64>')
                num_threadblocks += 'The number of GPU threadblocks\n'
                print(num_threadblocks)

                num_threads_per_block = \
                    '\t{: <35}'.format('-g, --num-threads-per-block=<64>')
                num_threads_per_block += 'The number of GPU threads per block\n'
                print(num_threads_per_block)

                sys.exit()

            elif opt in ('-a', '--num-azim'):
                self._num_azim = int(arg)
            elif opt in ('-s', '--azim-spacing'):
                self._azim_spacing = float(arg)
            elif opt in ('-i', '--max-iters'):
                self._max_iters = int(arg)
            elif opt in ('-c', '--tolerance'):
                self._tolerance = float(arg)
            elif opt in ('-t', '--num-omp-threads'):
                self._num_omp_threads = int(arg)
            elif opt in ('-b', '--num-thread-blocks'):
                self._num_thread_blocks = int(arg)
            elif opt in ('-g', '--num-threads-per-block'):
                self._num_threads_per_block = int(arg)
Example #5
0
    def parseArguments(self):
        """This method parses command line options and assigns the appropriate
        values to the corresponding class attributes."""

        try:
            self._opts, self._args = \
                getopt.getopt(sys.argv[1:], self.short_args, self.long_args)
        except getopt.GetoptError as err:
            py_printf('ERROR', str(err))

        # Parse the command line arguments - error checking will occur
        # at the setter method level in C++
        for opt, arg in self.opts:

            # Print a report of all supported runtime options and exit
            if opt in ('-h', '--help'):

                print('{:-^80}'.format(''))
                print('{: ^80}'.format('OpenMOC v.0.1.1 runtime options'))
                print('{:-^80}'.format(''))
                print('')

                help_msg = '\t{: <35}'.format('-h, --help')
                help_msg += 'Report OpenMOC runtime options\n'
                print(help_msg)

                num_azim = '\t{: <35}'.format('-a, --num-azim=<4>')
                num_azim += 'the number of azimuthal angles\n'
                print(num_azim)

                track_spacing = '\t{: <35}'.format('-s, --track-spacing=<0.1>')
                track_spacing += 'The track spacing [cm]\n'
                print(track_spacing)

                max_iters = '\t{: <35}'.format('-i, --max-iters=<1000>')
                max_iters += 'The max number of source iterations\n'
                print(max_iters)

                tolerance = '\t{: <35}'.format('-c, --tolerance=<1E-5>')
                tolerance += 'The source convergence tolerance\n'
                print(tolerance)

                num_omp_threads = '\t{: <35}'.format('-t, --num-omp-threads=<1>')
                num_omp_threads += 'The number of OpenMP threads\n'
                print(num_omp_threads)

                num_threadblocks = '\t{: <35}'.format('-b, ' + \
                                       '--num-thread-blocks=<64>')
                num_threadblocks += 'The number of GPU threadblocks\n'
                print(num_threadblocks)

                num_threads_per_block = \
                    '\t{: <35}'.format('-g, --num-threads-per-block=<64>')
                num_threads_per_block += 'The number of GPU threads per block\n'
                print(num_threads_per_block)

                sys.exit()

            elif opt in ('-a', '--num-azim'):
                    self._num_azim = int(arg)
            elif opt in ('-s', '--track-spacing'):
                self._track_spacing = float(arg)
            elif opt in ('-i', '--max-iters'):
                self._max_iters = int(arg)
            elif opt in ('-c', '--tolerance'):
                self._tolerance = float(arg)
            elif opt in ('-t', '--num-omp-threads'):
                self._num_omp_threads = int(arg)
            elif opt in ('-b', '--num-thread-blocks'):
                self._num_thread_blocks = int(arg)
            elif opt in ('-g', '--num-threads-per-block'):
                self._num_threads_per_block = int(arg)
Example #6
0
    def computeEigenmodes(self,
                          solver_mode=openmoc.FORWARD,
                          num_modes=5,
                          inner_method='gmres',
                          outer_tol=1e-5,
                          inner_tol=1e-6,
                          interval=10):
        """Compute all eigenmodes in the problem using the scipy.linalg package.

        Parameters
        ----------
        solver_mode : {openmoc.FORWARD, openmoc.ADJOINT}
            The type of eigenmodes to compute (default is openmoc.FORWARD)
        num_modes : Integral
            The number of eigenmodes to compute (default is 5)
        inner_method : {'gmres', 'lgmres', 'bicgstab', 'cgs'}
            Krylov subspace method used for the Ax=b solve (default is 'gmres')
        outer_tol : Real
            The tolerance on the outer eigenvalue solve (default is 1E-5)
        inner_tol : Real
            The tolerance on the inner Ax=b solve (default is 1E-5)
        interval : Integral
            The inner iteration interval for logging messages (default is 10)
        """

        # Ensure that vacuum boundary conditions are used
        geometry = self._moc_solver.getGeometry()
        if (geometry.getMinXBoundaryType() != openmoc.VACUUM
                or geometry.getMaxXBoundaryType() != openmoc.VACUUM
                or geometry.getMinYBoundaryType() != openmoc.VACUUM
                or geometry.getMaxYBoundaryType() != openmoc.VACUUM):
            py_printf('ERROR', 'All boundary conditions must be ' + \
                      'VACUUM for the IRAMSolver')

        import scipy.sparse.linalg as linalg

        # Set solution-dependent class attributes based on parameters
        # These are accessed and used by the LinearOperators
        self._num_modes = num_modes
        self._outer_tol = outer_tol

        self.initializeOperators(solver_mode, inner_method, inner_tol,
                                 interval)

        # Solve the eigenvalue problem
        timer = openmoc.Timer()
        timer.startTimer()
        vals, vecs = linalg.eigs(self._F_op,
                                 k=self._num_modes,
                                 tol=self._outer_tol)
        timer.stopTimer()

        # Print a timer report
        tot_time = timer.getTime()
        time_per_mode = tot_time / self._num_modes
        tot_time = '{0:.4e} sec'.format(tot_time)
        time_per_mode = '{0:.4e} sec'.format(time_per_mode)
        py_printf('RESULT', 'Total time to solution'.ljust(53, '.') + tot_time)
        py_printf('RESULT',
                  'Solution time per mode'.ljust(53, '.') + time_per_mode)

        # Store the eigenvalues and eigenvectors
        self._eigenvalues = vals
        self._eigenvectors = vecs

        # Restore the material data
        self._moc_solver.resetMaterials(solver_mode)
Example #7
0
def _load_openmc_src(mgxs_lib, solver):
    """Assign fixed sources to an OpenMOC model from an OpenMC MGXS library.

    This routine computes the fission source and scattering source in
    each domain in an OpenMC MGXS library and assigns it as a fixed source
    for an OpenMOC calculation. This is a helper routine for the
    compute_sph_factors(...) routine.

    Parameters
    ----------
    mgxs_lib : openmc.mgxs.Library object
        An OpenMC multi-group cross section library
    solver : openmoc.Solver
        An OpenMOC solver into which to load the fixed sources

    Returns
    -------
    openmc_fluxes : numpy.ndarray of Real
        A NumPy array of the OpenMC fluxes indexed by domain and energy group

    """

    # Retrieve dictionary of OpenMOC domains corresponding to OpenMC domains
    geometry = solver.getGeometry()
    if mgxs_lib.domain_type == 'material':
        openmoc_domains = geometry.getAllMaterials()
    else:
        openmoc_domains = geometry.getAllCells()

    # Create variables for the number of domains and energy groups
    num_groups = geometry.getNumEnergyGroups()
    num_domains = len(mgxs_lib.domains)
    openmc_fluxes = np.zeros((num_domains, num_groups))
    keff = mgxs_lib.keff

    # Create mapping of FSRs-to-domains to optimize fixed source setup
    domains_to_fsrs = collections.defaultdict(list)
    for fsr_id in range(geometry.getNumFSRs()):
        cell = geometry.findCellContainingFSR(fsr_id)
        if mgxs_lib.domain_type == 'material':
            domain = cell.getFillMaterial()
        else:
            domain = cell
        domains_to_fsrs[domain.getId()].append(fsr_id)

    # Compute fixed sources for all domains in the MGXS library
    for i, openmc_domain in enumerate(mgxs_lib.domains):

        # Ignore domains which cannot be found in the OpenMOC Geometry
        if openmc_domain.id not in openmoc_domains:
            continue

        # Get OpenMOC domain corresponding to the OpenMC domain
        openmoc_domain = openmoc_domains[openmc_domain.id]

        # If this domain is not found in the OpenMOC geometry, ignore it
        if openmoc_domain.getNumInstances() == 0:
            continue

        # Compute the total volume filled by this domain throughout the geometry
        tot_volume = openmoc_domain.getVolume()

        # Extract an openmc.mgxs.MGXS object for the scattering matrix
        if 'consistent nu-scatter matrix' in mgxs_lib.mgxs_types:
            scatter = mgxs_lib.get_mgxs(openmoc_domain.getId(),
                                        'consistent nu-scatter matrix')
        elif 'nu-scatter matrix' in mgxs_lib.mgxs_types:
            scatter = mgxs_lib.get_mgxs(openmoc_domain.getId(),
                                        'nu-scatter matrix')
        elif 'consistent scatter matrix' in mgxs_lib.mgxs_types:
            scatter = mgxs_lib.get_mgxs(openmoc_domain.getId(),
                                        'consistent scatter matrix')
        elif 'scatter matrix' in mgxs_lib.mgxs_types:
            scatter = mgxs_lib.get_mgxs(openmoc_domain.getId(),
                                        'scatter matrix')
        else:
            py_printf(
                'ERROR', 'Unable to compute SPH factors for an OpenMC '
                'MGXS library without scattering matrices')

        # Extract an openmc.mgxs.MGXS object for the nu-fission cross section
        if 'nu-fission' in mgxs_lib.mgxs_types:
            nu_fission = mgxs_lib.get_mgxs(openmoc_domain.getId(),
                                           'nu-fission')
        else:
            py_printf(
                'ERROR', 'Unable to compute SPH factors for an OpenMC '
                'MGXS library without nu-fission cross sections')

        # Extract an openmc.mgxs.MGXS object for the chi fission spectrum
        if 'chi' in mgxs_lib.mgxs_types:
            chi = mgxs_lib.get_mgxs(openmoc_domain.getId(), 'chi')
        else:
            py_printf(
                'ERROR', 'Unable to compute SPH factors for an OpenMC '
                'MGXS library without chi fission spectrum')

        # Retrieve the OpenMC volume-integrated flux for this domain from
        # the nu-fission MGXS and store it for SPH factor calculation
        flux = nu_fission.tallies['flux'].mean.flatten()
        openmc_fluxes[i, :] = np.atleast_1d(np.flipud(flux))
        openmc_fluxes[i, :] /= tot_volume

        # Extract a NumPy array for each MGXS summed across all nuclides
        scatter = scatter.get_xs(nuclides='sum')
        nu_fission = nu_fission.get_xs(nuclides='sum')
        chi = chi.get_xs(nuclides='sum')

        # Compute and store volume-averaged fission + scatter sources
        for group in range(num_groups):

            # Compute the source for this group from fission and scattering
            in_scatter = scatter[:, group] * openmc_fluxes[i, :]
            fission = (chi[group] / keff) * nu_fission * openmc_fluxes[i, :]
            source = np.sum(in_scatter) + np.sum(fission)

            # Assign the source to this domain
            if mgxs_lib.domain_type == 'material':
                solver.setFixedSourceByMaterial(openmoc_domain, group + 1,
                                                source)
            else:
                solver.setFixedSourceByCell(openmoc_domain, group + 1, source)

    return openmc_fluxes
Example #8
0
def load_from_hdf5(filename='mgxs.h5',
                   directory='mgxs',
                   geometry=None,
                   domain_type='material',
                   suffix=''):
    """This routine loads an HDF5 file of multi-group cross section data.

    The routine instantiates material with multi-group cross section data and
    returns a dictionary of each Material object keyed by its name or ID. An OpenMOC
    geometry may optionally be given and the routine will directly insert the
    multi-group cross sections into each material in the geometry. If a geometry
    is passed in, materials from the geometry will be used in place of those
    instantiated by this routine.

    Parameters
    ----------
    filename : str
        Filename for cross sections HDF5 file (default is 'mgxs.h5')
    directory : str
        Directory for cross sections HDF5 file (default is 'mgxs')
    geometry : openmoc.Geometry, optional
        An optional geometry populated with materials, cells, etc.
    domain_type : str
        The domain type ('material' or 'cell') upon which the cross sections
        are defined (default is 'material')
    suffix : str, optional
        An optional string suffix to index the HDF5 file beyond the assumed
        domain_type/domain_id/mgxs_type group sequence (default is '')

    Returns
    -------
    materials : dict
        A dictionary of Materials keyed by ID

    """

    cv.check_type('filename', filename, basestring)
    cv.check_type('directory', directory, basestring)
    cv.check_value('domain_type', domain_type, ('material', 'cell'))
    cv.check_type('suffix', suffix, basestring)
    if geometry:
        cv.check_type('geometry', geometry, openmoc.Geometry)

    # Create a h5py file handle for the file
    import h5py
    filename = os.path.join(directory, filename)
    f = h5py.File(filename, 'r')

    # Check that the file has an 'energy groups' attribute
    if '# groups' not in f.attrs:
        py_printf(
            'ERROR', 'Unable to load HDF5 file "%s" since it does '
            'not contain an \'# groups\' attribute', filename)

    if domain_type not in f.keys():
        py_printf(
            'ERROR', 'Unable to load HDF5 file "%s" since it does '
            'not contain domain type "%s"', filename, domain_type)

    # Instantiate dictionary to hold Materials to return to user
    materials = {}
    old_materials = {}
    num_groups = int(f.attrs['# groups'])

    # If a Geometry was passed in, extract all cells or materials from it
    if geometry:
        if domain_type == 'material':
            domains = geometry.getAllMaterials()
        elif domain_type == 'cell':
            domains = geometry.getAllMaterialCells()
        else:
            py_printf('ERROR', 'Domain type "%s" is not supported',
                      domain_type)

    # Iterate over all domains (e.g., materials or cells) in the HDF5 file
    for domain_spec in sorted(f[domain_type]):

        py_printf('INFO', 'Importing cross sections for %s "%s"', domain_type,
                  str(domain_spec))

        # Create shortcut to HDF5 group for this domain
        domain_group = f[domain_type][domain_spec]

        # If domain_spec is an integer, it is an ID; otherwise a string name
        if domain_spec.isdigit():
            domain_spec = int(domain_spec)
        else:
            domain_spec = str(domain_spec)

        # If using an OpenMOC Geometry, extract a Material from it
        if geometry:

            if domain_type == 'material':
                material = _get_domain(domains, domain_spec)

            elif domain_type == 'cell':
                cell = _get_domain(domains, domain_spec)
                material = cell.getFillMaterial()

                # If the user filled multiple Cells with the same Material,
                # the Material must be cloned for each unique Cell
                if material != None:
                    if len(domains) > geometry.getNumMaterials():
                        old_materials[material.getId()] = material
                        material = material.clone()

                # If the Cell does not contain a Material, create one for it
                else:
                    if isinstance(domain_spec, int):
                        material = openmoc.Material(id=domain_spec)
                    else:
                        # Reproducibly hash the domain name into an integer ID
                        domain_id = hashlib.md5(domain_spec.encode('utf-8'))
                        domain_id = int(domain_id.hexdigest()[:4], 16)
                        material = \
                            openmoc.Material(id=domain_id, name=domain_spec)

                # Fill the Cell with the new Material
                cell.setFill(material)

        # If not Geometry, instantiate a new Material with the ID/name
        else:
            if isinstance(domain_spec, int):
                material = openmoc.Material(id=domain_spec)
            else:
                # Reproducibly hash the domain name into an integer ID
                domain_id = hashlib.md5(domain_spec.encode('utf-8'))
                domain_id = int(domain_id.hexdigest()[:4], 16)
                material = openmoc.Material(id=domain_id, name=domain_spec)

        # Add material to the collection
        materials[domain_spec] = material
        material.setNumEnergyGroups(num_groups)

        # Search for the total/transport cross section
        if 'transport' in domain_group:
            sigma = _get_numpy_array(domain_group, 'transport', suffix)
            material.setSigmaT(sigma)
            py_printf('DEBUG', 'Loaded "transport" MGXS for "%s %s"',
                      domain_type, str(domain_spec))
        elif 'total' in domain_group:
            sigma = _get_numpy_array(domain_group, 'total', suffix)
            material.setSigmaT(sigma)
            py_printf('DEBUG', 'Loaded "total" MGXS for "%s %s"', domain_type,
                      str(domain_spec))
        else:
            py_printf('WARNING', 'No "total" or "transport" MGXS found for'
                      '"%s %s"', domain_type, str(domain_spec))

        # Search for the fission production cross section
        if 'nu-fission' in domain_group:
            sigma = _get_numpy_array(domain_group, 'nu-fission', suffix)
            material.setNuSigmaF(sigma)
            py_printf('DEBUG', 'Loaded "nu-fission" MGXS for "%s %s"',
                      domain_type, str(domain_spec))
        else:
            py_printf('WARNING', 'No "nu-fission" MGXS found for'
                      '"%s %s"', domain_type, str(domain_spec))

        # Search for the scattering matrix cross section
        if 'consistent nu-scatter matrix' in domain_group:
            sigma = _get_numpy_array(domain_group,
                                     'consistent nu-scatter matrix', suffix)
            material.setSigmaS(sigma)
            py_printf(
                'DEBUG',
                'Loaded "consistent nu-scatter matrix" MGXS for "%s %s"',
                domain_type, str(domain_spec))
        elif 'nu-scatter matrix' in domain_group:
            sigma = _get_numpy_array(domain_group, 'nu-scatter matrix', suffix)
            material.setSigmaS(sigma)
            py_printf('DEBUG', 'Loaded "nu-scatter matrix" MGXS for "%s %s"',
                      domain_type, str(domain_spec))
        elif 'consistent scatter matrix' in domain_group:
            sigma = _get_numpy_array(domain_group, 'consistent scatter matrix',
                                     suffix)
            material.setSigmaS(sigma)
            py_printf('DEBUG',
                      'Loaded "consistent scatter matrix" MGXS for "%s %s"',
                      domain_type, str(domain_spec))
        elif 'scatter matrix' in domain_group:
            sigma = _get_numpy_array(domain_group, 'scatter matrix', suffix)
            material.setSigmaS(sigma)
            py_printf('DEBUG', 'Loaded "scatter matrix" MGXS for "%s %s"',
                      domain_type, str(domain_spec))
        else:
            py_printf('WARNING', 'No "scatter matrix" found for "%s %s"',
                      domain_type, str(domain_spec))

        # Search for chi (fission spectrum)
        if 'chi' in domain_group:
            chi = _get_numpy_array(domain_group, 'chi', suffix)
            material.setChi(chi)
            py_printf('DEBUG', 'Loaded "chi" MGXS for "%s %s"', domain_type,
                      str(domain_spec))
        else:
            py_printf('WARNING', 'No "chi" MGXS found for "%s %s"',
                      domain_type, str(domain_spec))

        # Search for optional cross sections
        if 'fission' in domain_group:
            sigma = _get_numpy_array(domain_group, 'fission', suffix)
            material.setSigmaF(sigma)
            py_printf('DEBUG', 'Loaded "fission" MGXS for "%s %s"',
                      domain_type, str(domain_spec))

    # Inform SWIG to garbage collect any old Materials from the Geometry
    for material_id in old_materials:
        old_materials[material_id].thisown = False

    # Return collection of materials
    return materials
Example #9
0
def compute_sph_factors(mgxs_lib,
                        max_sph_iters=30,
                        sph_tol=1E-5,
                        fix_src_tol=1E-5,
                        num_azim=4,
                        azim_spacing=0.1,
                        zcoord=0.0,
                        num_threads=1,
                        throttle_output=True,
                        geometry=None,
                        track_generator=None,
                        solver=None,
                        sph_domains=None):
    """Compute SPH factors for an OpenMC multi-group cross section library.

    This routine coputes SuPerHomogenisation (SPH) factors for an OpenMC MGXS
    library. The SPH scheme is outlined by Alain Hebert in the following paper:

        Hebert, A., "A Consistent Technique for the Pin-by-Pin
        Homogenization of a Pressurized Water Reactor Assembly."
        Nuclear Science and Engineering, 113 (3), pp. 227-233, 1993.

    The SPH factors are needed to preserve reaction rates in heterogeneous
    geometries. The energy condensation process leads to a bias between
    ultrafine and coarse energy group calculations. This bias is a result of the
    use of scalar flux-weighting to compute MGXS without properly accounting for
    angular-dependence of the flux.

    Parameters
    ----------
    mgxs_lib : openmc.mgxs.Library
        An OpenMC multi-group cross section library
    max_sph_iters : Integral
        The maximum number of SPH iterations (default is 30)
    sph_tol : Real
        The tolerance on the SPH factor convergence (default is 1E-5)
    fix_src_tol : Real
        The tolerance on the MOC fixed source calculations (default is 1E-5)
    num_azim : Integral
        The number of azimuthal angles (default is 4)
    azim_spacing : Real
        The track spacing (default is 0.1 centimeters)
    zcoord : Real
        The coordinate on the z-axis (default is 0.)
    num_threads : Real
        The number of OpenMP threads (default is 1)
    throttle_output : bool
        Whether to suppress output from fixed source calculations (default is True)
    geometry : openmoc.Geometry
        An optional openmoc geometry to compute SPH factors on
    track_generator : openmoc.TrackGenerator
        An optional track generator to avoid initializing it in this routine
    solver : openmoc.Solver
        An optional openmoc solver to compute SPH factors with
    sph_domains : list of int
        A list of domain (cell or material, based on mgxs_lib domain type) ids,
        in which SPH factors should be computed. Default is only fissonable FSRs

    Returns
    -------
    fsrs_to_sph : numpy.ndarray of Real
        A NumPy array of SPH factors indexed by FSR and energy group
    sph_mgxs_lib : openmc.mgxs.Library
        An OpenMC MGXS library with the SPH factors applied to each MGXS
    sph_to_fsrs_indices : numpy.ndarray of Integral
        A NumPy array of all FSRs to which SPH factors were applied

    """

    import openmc.mgxs

    cv.check_type('mgxs_lib', mgxs_lib, openmc.mgxs.Library)

    # For Python 2.X.X
    if sys.version_info[0] == 2:
        from openmc.openmoc_compatible import get_openmoc_geometry
        from process import get_scalar_fluxes
    # For Python 3.X.X
    else:
        from openmc.openmoc_compatible import get_openmoc_geometry
        from openmoc.process import get_scalar_fluxes

    py_printf('NORMAL', 'Computing SPH factors...')

    if not geometry:
        # Create an OpenMOC Geometry from the OpenMC Geometry
        geometry = get_openmoc_geometry(mgxs_lib.geometry)

        # Load the MGXS library data into the OpenMOC geometry
        load_openmc_mgxs_lib(mgxs_lib, geometry)

    if not track_generator:
        # Initialize an OpenMOC TrackGenerator
        track_generator = openmoc.TrackGenerator(geometry, num_azim,
                                                 azim_spacing)
        track_generator.setZCoord(zcoord)
        track_generator.generateTracks()
        track_generator.initializeVolumes()
    else:
        track_generator.initializeVolumes()
        py_printf(
            'WARNING', 'Using provided track generator, ignoring '
            'arguments for track generation settings')

    if not solver:
        # Initialize an OpenMOC Solver
        solver = openmoc.CPUSolver(track_generator)
        solver.setConvergenceThreshold(fix_src_tol)
        solver.setNumThreads(num_threads)
    else:
        py_printf(
            'WARNING', 'Using provided solver, ignoring arguments for '
            'solver settings')

    # Get all OpenMOC domains
    if mgxs_lib.domain_type == 'material':
        openmoc_domains = geometry.getAllMaterials()
    elif mgxs_lib.domain_type == 'cell':
        openmoc_domains = geometry.getAllMaterialCells()
    else:
        py_printf(
            'ERROR', 'SPH factors cannot be applied for an OpenMC MGXS '
            'library of domain type %s', mgxs_lib.domain_type)

    if not sph_domains:
        sph_domains = []
        # If unspecified, apply sph factors in fissionable regions
        for openmoc_domain in openmoc_domains.values():
            if openmoc_domain.isFissionable():
                sph_domains.append(openmoc_domain.getId())

    openmc_fluxes = _load_openmc_src(mgxs_lib, solver)

    # Initialize SPH factors
    num_groups = geometry.getNumEnergyGroups()
    num_fsrs = geometry.getNumFSRs()

    # Map FSRs to domains (and vice versa) to compute domain-averaged fluxes
    fsrs_to_domains = np.zeros(num_fsrs)
    domains_to_fsrs = collections.defaultdict(list)
    sph_to_fsr_indices = []

    for fsr in range(num_fsrs):
        cell = geometry.findCellContainingFSR(fsr)

        if mgxs_lib.domain_type == 'material':
            domain = cell.getFillMaterial()
        else:
            domain = cell

        fsrs_to_domains[fsr] = domain.getId()
        domains_to_fsrs[domain.getId()].append(fsr)

        if domain.getId() in sph_domains:
            sph_to_fsr_indices.append(fsr)

    # Build a list of indices into the SPH array for fissionable domains
    sph_to_domain_indices = []
    for i, openmc_domain in enumerate(mgxs_lib.domains):
        if openmc_domain.id in openmoc_domains:
            openmoc_domain = openmoc_domains[openmc_domain.id]
            if openmoc_domain.getId() in sph_domains:
                sph_to_domain_indices.append(i)

    py_printf('NORMAL', 'Computing SPH factors for %d "%s" domains',
              len(sph_to_domain_indices), mgxs_lib.domain_type)

    # Initialize array of domain-averaged fluxes and SPH factors
    num_domains = len(mgxs_lib.domains)
    openmoc_fluxes = np.zeros((num_domains, num_groups))
    sph = np.ones((num_domains, num_groups))

    # Store starting verbosity log level
    log_level = openmoc.get_log_level()

    # SPH iteration loop
    for i in range(max_sph_iters):

        # Run fixed source calculation with suppressed output
        if throttle_output:
            openmoc.set_log_level('WARNING')

        # Disable flux resets between SPH iterations for speed
        if i == 1:
            solver.setRestartStatus(True)

        # Fixed source calculation
        solver.computeFlux()

        # Restore log output level
        if throttle_output:
            openmoc.set_log_level('NORMAL')

        # Extract the FSR scalar fluxes
        fsr_fluxes = get_scalar_fluxes(solver)

        # Compute the domain-averaged flux in each energy group
        for j, openmc_domain in enumerate(mgxs_lib.domains):
            domain_fluxes = fsr_fluxes[fsrs_to_domains == openmc_domain.id, :]
            openmoc_fluxes[j, :] = np.mean(domain_fluxes, axis=0)

        # Compute SPH factors
        old_sph = np.copy(sph)
        sph = openmc_fluxes / openmoc_fluxes
        sph = np.nan_to_num(sph)
        sph[sph == 0.0] = 1.0

        # Compute SPH factor residuals
        res = np.abs((sph - old_sph) / old_sph)
        res = np.nan_to_num(res)

        # Extract residuals for fissionable domains only
        res = res[sph_to_domain_indices, :]

        # Report maximum SPH factor residual
        py_printf('NORMAL', 'SPH Iteration %d:\tres = %1.3e', i, res.max())

        # Create a new MGXS library with cross sections updated by SPH factors
        sph_mgxs_lib = _apply_sph_factors(mgxs_lib, geometry, sph, sph_domains)

        # Load the new MGXS library data into the OpenMOC geometry
        load_openmc_mgxs_lib(sph_mgxs_lib, geometry)

        # Check max SPH factor residual for this domain for convergence
        if res.max() < sph_tol and i > 0:
            break

    # Warn user if SPH factors did not converge
    else:
        py_printf('WARNING', 'SPH factors did not converge')

    # Collect SPH factors for each FSR, energy group
    fsrs_to_sph = np.ones((num_fsrs, num_groups), dtype=np.float)
    for i, openmc_domain in enumerate(mgxs_lib.domains):
        if openmc_domain.id in openmoc_domains:
            openmoc_domain = openmoc_domains[openmc_domain.id]
            if openmoc_domain.getId() in sph_domains:
                fsr_ids = domains_to_fsrs[openmc_domain.id]
                fsrs_to_sph[fsr_ids, :] = sph[i, :]

    return fsrs_to_sph, sph_mgxs_lib, np.array(sph_to_fsr_indices)
Example #10
0
def load_openmc_mgxs_lib(mgxs_lib, geometry=None):
    """This routine loads an OpenMC Library of multi-group cross section data.

    The routine instantiates materials with multi-group cross section data and
    returns a dictionary of each material keyed by its ID. An OpenMOC geometry
    may optionally be given and the routine will directly insert the multi-group
    cross sections into each material in the geometry. If a geometry is passed
    in, materials from the geometry will be used in place of those instantiated
    by this routine.

    Parameters
    ----------
    mgxs_lib : openmc.mgxs.Library
        An OpenMC multi-group cross section library library
    geometry : openmoc.Geometry, optional
        An optional geometry populated with materials, cells, etc.

    Returns
    -------
    materials : dict
        A dictionary of Materials keyed by ID

    """

    # Attempt to import openmc
    try:
        import openmc
    except ImportError:
        py_printf('ERROR', 'The OpenMC code must be installed on your system')

    cv.check_type('mgxs_lib', mgxs_lib, openmc.mgxs.Library)
    if geometry:
        cv.check_type('geometry', geometry, openmoc.Geometry)

    # Instantiate dictionary to hold Materials to return to user
    materials = {}
    old_materials = {}
    num_groups = mgxs_lib.num_groups
    domain_type = mgxs_lib.domain_type

    # If a Geometry was passed in, extract all cells or materials from it
    if geometry:
        if domain_type == 'material':
            domains = geometry.getAllMaterials()
        elif domain_type == 'cell':
            domains = geometry.getAllMaterialCells()
        else:
            py_printf(
                'ERROR', 'Unable to load a cross sections library with '
                'domain type %s', mgxs_lib.domain_type)

    # Iterate over all domains (e.g., materials or cells) in the HDF5 file
    for domain in mgxs_lib.domains:

        # If using an OpenMOC Geometry, extract a Material from it
        if geometry:

            if domain_type == 'material':
                material = _get_domain(domains, domain.id)

                # Ignore materials which cannot be found in the OpenMOC Geometry
                if material is None:
                    domain_name = domain.name.replace('%', '%%')
                    py_printf('WARNING',
                              'Ignoring cross sections for %s "%d" "%s"',
                              domain_type, domain.id, str(domain_name))
                    continue

            elif domain_type == 'cell':
                cell = _get_domain(domains, domain.id)

                # Ignore cells which cannot be found in the OpenMOC Geometry
                if cell is None:
                    domain_name = domain.name.replace('%', '%%')
                    py_printf('WARNING',
                              'Ignoring cross sections for %s "%d" "%s"',
                              domain_type, domain.id, str(domain_name))
                    continue
                else:
                    material = cell.getFillMaterial()

                # If the user filled multiple Cells with the same Material,
                # the Material must be cloned for each unique Cell
                if material != None:
                    if len(domains) > geometry.getNumMaterials():
                        old_materials[material.getId()] = material
                        material = material.clone()

                # If the Cell does not contain a Material, create one for it
                else:
                    material = openmoc.Material(id=domain.id)

                # Fill the Cell with the new Material
                cell.setFill(material)

        # If not Geometry, instantiate a new Material with the ID/name
        else:
            material = openmoc.Material(id=domain.id)

        domain_name = domain.name.replace('%', '%%')
        py_printf('INFO', 'Importing cross sections for %s "%d" "%s"',
                  domain_type, domain.id, str(domain_name))

        # Add material to the collection
        materials[domain.id] = material
        material.setNumEnergyGroups(num_groups)

        # Search for the total/transport cross section
        if 'transport' in mgxs_lib.mgxs_types:
            mgxs = mgxs_lib.get_mgxs(domain, 'transport')
            sigma = mgxs.get_xs(nuclides='sum')
            material.setSigmaT(sigma)
            py_printf('DEBUG', 'Loaded "transport" MGXS for "%s %d"',
                      domain_type, domain.id)
        elif 'nu-transport' in mgxs_lib.mgxs_types:
            mgxs = mgxs_lib.get_mgxs(domain, 'nu-transport')
            sigma = mgxs.get_xs(nuclides='sum')
            material.setSigmaT(sigma)
            py_printf('DEBUG', 'Loaded "nu-transport" MGXS for "%s %d"',
                      domain_type, domain.id)
        elif 'total' in mgxs_lib.mgxs_types:
            mgxs = mgxs_lib.get_mgxs(domain, 'total')
            sigma = mgxs.get_xs(nuclides='sum')
            material.setSigmaT(sigma)
            py_printf('DEBUG', 'Loaded "total" MGXS for "%s %d"', domain_type,
                      domain.id)
        else:
            py_printf('WARNING', 'No "total" or "transport" MGXS found for'
                      '"%s %d"', domain_type, domain.id)

        # Search for the fission production cross section
        if 'nu-fission' in mgxs_lib.mgxs_types:
            mgxs = mgxs_lib.get_mgxs(domain, 'nu-fission')
            sigma = mgxs.get_xs(nuclides='sum')
            material.setNuSigmaF(sigma)
            py_printf('DEBUG', 'Loaded "nu-fission" MGXS for "%s %d"',
                      domain_type, domain.id)
        else:
            py_printf('WARNING', 'No "nu-fission" MGXS found for'
                      '"%s %d"', domain_type, domain.id)

        # Search for the scattering matrix cross section
        if 'consistent nu-scatter matrix' in mgxs_lib.mgxs_types:
            mgxs = mgxs_lib.get_mgxs(domain, 'consistent nu-scatter matrix')
            sigma = mgxs.get_xs(nuclides='sum').flatten()
            material.setSigmaS(sigma)
            py_printf(
                'DEBUG',
                'Loaded "consistent nu-scatter matrix" MGXS for "%s %d"',
                domain_type, domain.id)
        elif 'nu-scatter matrix' in mgxs_lib.mgxs_types:
            mgxs = mgxs_lib.get_mgxs(domain, 'nu-scatter matrix')
            sigma = mgxs.get_xs(nuclides='sum').flatten()
            material.setSigmaS(sigma)
            py_printf('DEBUG', 'Loaded "nu-scatter matrix" MGXS for "%s %d"',
                      domain_type, domain.id)
        elif 'consistent scatter matrix' in mgxs_lib.mgxs_types:
            mgxs = mgxs_lib.get_mgxs(domain, 'consistent scatter matrix')
            sigma = mgxs.get_xs(nuclides='sum').flatten()
            material.setSigmaS(sigma)
            py_printf('DEBUG',
                      'Loaded "consistent scatter matrix" MGXS for "%s %d"',
                      domain_type, domain.id)
        elif 'scatter matrix' in mgxs_lib.mgxs_types:
            mgxs = mgxs_lib.get_mgxs(domain, 'scatter matrix')
            sigma = mgxs.get_xs(nuclides='sum').flatten()
            material.setSigmaS(sigma)
            py_printf('DEBUG', 'Loaded "scatter matrix" MGXS for "%s %d"',
                      domain_type, domain.id)
        else:
            py_printf(
                'WARNING', 'No "scatter matrix" or "nu-scatter matrix" '
                'found for "%s %d"', domain_type, domain.id)

        # Search for chi (fission spectrum)
        if 'chi' in mgxs_lib.mgxs_types:
            mgxs = mgxs_lib.get_mgxs(domain, 'chi')
            chi = mgxs.get_xs(nuclides='sum')
            material.setChi(chi)
            py_printf('DEBUG', 'Loaded "chi" MGXS for "%s %d"', domain_type,
                      domain.id)
        else:
            py_printf('WARNING', 'No "chi" MGXS found for "%s %d"',
                      domain_type, domain.id)

        # Search for optional cross sections
        if 'fission' in mgxs_lib.mgxs_types:
            mgxs = mgxs_lib.get_mgxs(domain, 'fission')
            sigma = mgxs.get_xs(nuclides='sum')
            material.setSigmaF(sigma)
            py_printf('DEBUG', 'Loaded "fission" MGXS for "%s %d"',
                      domain_type, domain.id)

    # Inform SWIG to garbage collect any old Materials from the Geometry
    for material_id in old_materials:
        old_materials[material_id].thisown = False

    # Return collection of materials
    return materials
Example #11
0
  def parseArguments(self):
    try:
      opts, args = getopt.getopt(sys.argv[1:],
                                 'hfa:s:i:c:t:b:g:r:l:',
                                 ['help',
                                  'num-azim=',
                                  'track-spacing=',
                                  'tolerance=',
                                  'max-iters=',
                                  'num-omp-threads=',
                                  'num-thread-blocks=',
                                  'num-gpu-threads=',
                                  'relax-factor=',
                                  'cmfd-acceleration',
                                  'mesh-level='])

    except getopt.GetoptError as err:
      log.py_printf('WARNING', str(err))
      pass


    # Parse the command line arguments - error checking will occur
    # at the setter method level in C++
    for opt, arg in opts:

      # Print a report of all supported runtime options and exit
      if opt in ('-h', '--help'):

        print '{:-^80}'.format('')
        print '{: ^80}'.format('OpenMOC v.0.1.1 runtime options')
        print '{:-^80}'.format('')
        print

        help_msg = '\t{: <35}'.format('-h, --help')
        help_msg = 'Report OpenMOC runtime options\n'
        print help_msg

        num_azim = '\t{: <35}'.format('-a, --num-azim=<4>')
        num_azim += 'the number of azimuthal angles\n'
        print num_azim

        track_spacing = '\t{: <35}'.format('-s, --track-spacing=<0.1>')
        track_spacing += 'The track spacing [cm]\n'
        print track_spacing

        max_iters = '\t{: <35}'.format('-i, --max-iters=<1000>')
        max_iters += 'The max number of source iterations\n'
        print max_iters

        tolerance = '\t{: <35}'.format('-c, --tolerance=<1E-5>')
        tolerance += 'The source convergence tolerance\n'
        print tolerance

        num_omp_threads = '\t{: <35}'.format('-t, --num-omp-threads=<1>')
        num_omp_threads += 'The number of OpenMP threads\n'
        print num_omp_threads

        num_gpu_threadblocks = '\t{: <35}'.format('-b, ' + \
                               '--num-gpu-threadblocks=<64>')
        num_gpu_threadblocks += 'The number of GPU threadblocks\n'
        print num_gpu_threadblocks

        num_gpu_threads = '\t{: <35}'.format('-g, --num-gpu-threads=<64>')
        num_gpu_threads += 'The number of GPU threads per block\n'
        print num_gpu_threads

        relax_factor = '\t{: <35}'.format('-r, --relax-factor=<0.6>')
        relax_factor += 'The cmfd relaxation factor\n'
        print relax_factor

        acceleration = '\t{: <35}'.format('-f, --cmfd-acceleration=<False>')
        acceleration += 'The cmfd acceleration flag\n'
        print acceleration

        mesh_level = '\t{: <35}'.format('-l, --mesh-level=<-1>')
        mesh_level += 'The mesh level\n'
        print mesh_level

        sys.exit()

      elif opt in ('-a', '--num-azim'):
        self._num_azim = int(arg)

      elif opt in ('-s', '--track-spacing'):
        self._track_spacing = float(arg)

      elif opt in ('-i', '--max-iters'):
        self._max_iters = int(arg)

      elif opt in ('-c', '--tolerance'):
        self._tolerance = float(arg)

      elif opt in ('-t', '--num-omp-threads'):
        self._num_omp_threads = int(arg)

      elif opt in ('-b', '--num-thread-blocks'):
        self._num_thread_blocks = int(arg)

      elif opt in ('-g', '--num-gpu-threads'):
        self._num_gpu_threads = int(arg)

      elif opt in ('-f', '--cmfd-acceleration'):
        self._use_cmfd_acceleration = True

      elif opt in ('-r', '--relax-factor'):
        self._cmfd_relax_factor = float(arg)

      elif opt in ('-l', '--mesh-level'):
        self._cmfd_mesh_level = int(arg)