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
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
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
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)
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)
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)
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
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
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)
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
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)